mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-03 21:48:27 +00:00
Merge branch 'version-12-hotfix' of https://github.com/frappe/erpnext into remarks-fix-v12
This commit is contained in:
@@ -910,75 +910,8 @@
|
|||||||
},
|
},
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
},
|
},
|
||||||
"Passiva": {
|
"Passiva - Verbindlichkeiten": {
|
||||||
"root_type": "Liability",
|
"root_type": "Liability",
|
||||||
"A - Eigenkapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"I - Gezeichnetes Kapital": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"II - Kapitalr\u00fccklage": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"III - Gewinnr\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"1 - gesetzliche R\u00fccklage": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"4 - andere Gewinnr\u00fccklagen": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
|
||||||
"is_group": 1,
|
|
||||||
"Gewinnr\u00fccklagen (BilMoG)": {
|
|
||||||
"account_number": "2963"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
|
||||||
"account_number": "2964"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
|
||||||
"account_number": "2965"
|
|
||||||
},
|
|
||||||
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
|
||||||
"account_number": "2966"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2967"
|
|
||||||
},
|
|
||||||
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2968"
|
|
||||||
},
|
|
||||||
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
|
||||||
"account_number": "2969"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"IV - Gewinnvortrag/Verlustvortrag": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
|
||||||
"account_type": "Equity",
|
|
||||||
"is_group": 1
|
|
||||||
},
|
|
||||||
"Einlagen stiller Gesellschafter": {
|
|
||||||
"account_number": "9295"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"B - R\u00fcckstellungen": {
|
"B - R\u00fcckstellungen": {
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
"1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": {
|
||||||
@@ -1595,6 +1528,143 @@
|
|||||||
},
|
},
|
||||||
"is_group": 1
|
"is_group": 1
|
||||||
},
|
},
|
||||||
|
"Passiva - Eigenkapital": {
|
||||||
|
"root_type": "Equity",
|
||||||
|
"A - Eigenkapital": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"I - Gezeichnetes Kapital": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gezeichnetes Kapital": {
|
||||||
|
"account_number": "2900",
|
||||||
|
"account_type": "Equity"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": {
|
||||||
|
"account_number": "2901"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": {
|
||||||
|
"account_number": "2902"
|
||||||
|
},
|
||||||
|
"Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": {
|
||||||
|
"account_number": "2903"
|
||||||
|
},
|
||||||
|
"R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||||
|
"account_number": "2906"
|
||||||
|
},
|
||||||
|
"Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": {
|
||||||
|
"account_number": "2907"
|
||||||
|
},
|
||||||
|
"Kapitalerh\u00f6hung aus Gesellschaftsmitteln": {
|
||||||
|
"account_number": "2908"
|
||||||
|
},
|
||||||
|
"Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": {
|
||||||
|
"account_number": "2910"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"II - Kapitalr\u00fccklage": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Kapitalr\u00fccklage": {
|
||||||
|
"account_number": "2920"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": {
|
||||||
|
"account_number": "2925"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": {
|
||||||
|
"account_number": "2926"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": {
|
||||||
|
"account_number": "2927"
|
||||||
|
},
|
||||||
|
"Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": {
|
||||||
|
"account_number": "2928"
|
||||||
|
},
|
||||||
|
"Nachschusskapital (Gegenkonto 1299)": {
|
||||||
|
"account_number": "2929"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"III - Gewinnr\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"1 - gesetzliche R\u00fccklage": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gesetzliche R\u00fccklage": {
|
||||||
|
"account_number": "2930"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": {
|
||||||
|
"account_number": "2935"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"3 - satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Satzungsm\u00e4\u00dfige R\u00fccklagen": {
|
||||||
|
"account_number": "2950"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4 - andere Gewinnr\u00fccklagen": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Andere Gewinnr\u00fccklagen": {
|
||||||
|
"account_number": "2960"
|
||||||
|
},
|
||||||
|
"Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": {
|
||||||
|
"account_number": "2961"
|
||||||
|
},
|
||||||
|
"Eigenkapitalanteil von Wertaufholungen": {
|
||||||
|
"account_number": "2962"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": {
|
||||||
|
"is_group": 1,
|
||||||
|
"Gewinnr\u00fccklagen (BilMoG)": {
|
||||||
|
"account_number": "2963"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": {
|
||||||
|
"account_number": "2964"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": {
|
||||||
|
"account_number": "2965"
|
||||||
|
},
|
||||||
|
"Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": {
|
||||||
|
"account_number": "2966"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2967"
|
||||||
|
},
|
||||||
|
"Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2968"
|
||||||
|
},
|
||||||
|
"Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": {
|
||||||
|
"account_number": "2969"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
"IV - Gewinnvortrag/Verlustvortrag": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1,
|
||||||
|
"Gewinnvortrag vor Verwendung": {
|
||||||
|
"account_number": "2970"
|
||||||
|
},
|
||||||
|
"Verlustvortrag vor Verwendung": {
|
||||||
|
"account_number": "2978"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
"Einlagen stiller Gesellschafter": {
|
||||||
|
"account_number": "9295"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"1 - Umsatzerl\u00f6se": {
|
"1 - Umsatzerl\u00f6se": {
|
||||||
"root_type": "Income",
|
"root_type": "Income",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
|
|||||||
@@ -245,6 +245,9 @@ def get():
|
|||||||
"account_number": "2200"
|
"account_number": "2200"
|
||||||
},
|
},
|
||||||
_("Duties and Taxes"): {
|
_("Duties and Taxes"): {
|
||||||
|
_("TDS Payable"): {
|
||||||
|
"account_number": "2310"
|
||||||
|
},
|
||||||
"account_type": "Tax",
|
"account_type": "Tax",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"account_number": "2300"
|
"account_number": "2300"
|
||||||
|
|||||||
@@ -9,11 +9,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
|
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
|
||||||
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
|
||||||
test_dependencies = ["Item", "Cost Center"]
|
test_dependencies = ["Item", "Cost Center"]
|
||||||
|
|
||||||
class TestBankTransaction(unittest.TestCase):
|
class TestBankTransaction(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
make_pos_profile()
|
||||||
add_transactions()
|
add_transactions()
|
||||||
add_payments()
|
add_payments()
|
||||||
|
|
||||||
@@ -27,6 +29,9 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
|
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
|
||||||
frappe.db.sql("""delete from `tabPayment Entry`""")
|
frappe.db.sql("""delete from `tabPayment Entry`""")
|
||||||
|
|
||||||
|
# Delete POS Profile
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
frappe.flags.test_bank_transactions_created = False
|
frappe.flags.test_bank_transactions_created = False
|
||||||
frappe.flags.test_payments_created = False
|
frappe.flags.test_payments_created = False
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,8 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
"due_date": row.due_date,
|
"due_date": row.due_date,
|
||||||
"posting_date": row.posting_date,
|
"posting_date": row.posting_date,
|
||||||
frappe.scrub(party_type): row.party,
|
frappe.scrub(party_type): row.party,
|
||||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
|
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
||||||
|
"update_stock": 0
|
||||||
})
|
})
|
||||||
|
|
||||||
accounting_dimension = get_accounting_dimensions()
|
accounting_dimension = get_accounting_dimensions()
|
||||||
|
|||||||
@@ -7,17 +7,25 @@ import frappe
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
test_dependencies = ["Customer", "Supplier"]
|
test_dependencies = ["Customer", "Supplier"]
|
||||||
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
|
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
|
||||||
|
from erpnext.controllers.accounts_controller import AccountMissingError
|
||||||
|
|
||||||
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||||
def make_invoices(self, invoice_type="Sales"):
|
def setUp(self):
|
||||||
|
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
|
make_company()
|
||||||
|
|
||||||
|
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
|
||||||
doc = frappe.get_single("Opening Invoice Creation Tool")
|
doc = frappe.get_single("Opening Invoice Creation Tool")
|
||||||
args = get_opening_invoice_creation_dict(invoice_type=invoice_type)
|
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
||||||
|
party_1=party_1, party_2=party_2)
|
||||||
doc.update(args)
|
doc.update(args)
|
||||||
return doc.make_invoices()
|
return doc.make_invoices()
|
||||||
|
|
||||||
def test_opening_sales_invoice_creation(self):
|
def test_opening_sales_invoice_creation(self):
|
||||||
invoices = self.make_invoices()
|
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
|
||||||
|
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
||||||
|
|
||||||
self.assertEqual(len(invoices), 2)
|
self.assertEqual(len(invoices), 2)
|
||||||
expected_value = {
|
expected_value = {
|
||||||
@@ -27,6 +35,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
self.check_expected_values(invoices, expected_value)
|
self.check_expected_values(invoices, expected_value)
|
||||||
|
|
||||||
|
si = frappe.get_doc("Sales Invoice", invoices[0])
|
||||||
|
|
||||||
|
# Check if update stock is not enabled
|
||||||
|
self.assertEqual(si.update_stock, 0)
|
||||||
|
|
||||||
|
property_setter.delete()
|
||||||
|
|
||||||
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
||||||
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
||||||
|
|
||||||
@@ -36,7 +51,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
|
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
|
||||||
|
|
||||||
def test_opening_purchase_invoice_creation(self):
|
def test_opening_purchase_invoice_creation(self):
|
||||||
invoices = self.make_invoices(invoice_type="Purchase")
|
invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
|
||||||
|
|
||||||
self.assertEqual(len(invoices), 2)
|
self.assertEqual(len(invoices), 2)
|
||||||
expected_value = {
|
expected_value = {
|
||||||
@@ -46,6 +61,28 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
|
self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
|
||||||
|
|
||||||
|
def test_opening_sales_invoice_creation_with_missing_debit_account(self):
|
||||||
|
company = "_Test Opening Invoice Company"
|
||||||
|
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
|
||||||
|
|
||||||
|
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
|
||||||
|
frappe.db.set_value("Company", company, "default_receivable_account", "")
|
||||||
|
|
||||||
|
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
|
||||||
|
cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
|
||||||
|
"is_group": 1, "company": "_Test Opening Invoice Company"})
|
||||||
|
cc.insert(ignore_mandatory=True)
|
||||||
|
cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
|
||||||
|
"company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
|
||||||
|
cc2.insert()
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
|
||||||
|
|
||||||
|
self.assertRaises(AccountMissingError, self.make_invoices, company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
|
||||||
|
|
||||||
def get_opening_invoice_creation_dict(**args):
|
def get_opening_invoice_creation_dict(**args):
|
||||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||||
company = args.get("company", "_Test Company")
|
company = args.get("company", "_Test Company")
|
||||||
@@ -57,7 +94,7 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
{
|
{
|
||||||
"qty": 1.0,
|
"qty": 1.0,
|
||||||
"outstanding_amount": 300,
|
"outstanding_amount": 300,
|
||||||
"party": "_Test {0}".format(party),
|
"party": args.get("party_1") or "_Test {0}".format(party),
|
||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
@@ -66,7 +103,7 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
{
|
{
|
||||||
"qty": 2.0,
|
"qty": 2.0,
|
||||||
"outstanding_amount": 250,
|
"outstanding_amount": 250,
|
||||||
"party": "_Test {0} 1".format(party),
|
"party": args.get("party_2") or "_Test {0} 1".format(party),
|
||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
@@ -76,4 +113,31 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
})
|
})
|
||||||
|
|
||||||
invoice_dict.update(args)
|
invoice_dict.update(args)
|
||||||
return invoice_dict
|
return invoice_dict
|
||||||
|
|
||||||
|
def make_company():
|
||||||
|
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
|
return frappe.get_doc("Company", "_Test Opening Invoice Company")
|
||||||
|
|
||||||
|
company = frappe.new_doc("Company")
|
||||||
|
company.company_name = "_Test Opening Invoice Company"
|
||||||
|
company.abbr = "_TOIC"
|
||||||
|
company.default_currency = "INR"
|
||||||
|
company.country = "India"
|
||||||
|
company.insert()
|
||||||
|
return company
|
||||||
|
|
||||||
|
def make_customer(customer=None):
|
||||||
|
customer_name = customer or "Opening Customer"
|
||||||
|
customer = frappe.get_doc({
|
||||||
|
"doctype": "Customer",
|
||||||
|
"customer_name": customer_name,
|
||||||
|
"customer_group": "All Customer Groups",
|
||||||
|
"customer_type": "Company",
|
||||||
|
"territory": "All Territories"
|
||||||
|
})
|
||||||
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
customer.insert(ignore_permissions=True)
|
||||||
|
return customer.name
|
||||||
|
else:
|
||||||
|
return frappe.db.exists("Customer", customer_name)
|
||||||
@@ -404,6 +404,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
@@ -467,6 +468,7 @@
|
|||||||
"options": "UOM"
|
"options": "UOM"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"description": "If rate is zero them item will be treated as \"Free Item\"",
|
||||||
"fieldname": "free_item_rate",
|
"fieldname": "free_item_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rate"
|
"label": "Rate"
|
||||||
@@ -554,7 +556,8 @@
|
|||||||
],
|
],
|
||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"modified": "2019-12-18 17:29:22.957077",
|
"links": [],
|
||||||
|
"modified": "2020-12-04 00:36:24.698219",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
|||||||
@@ -467,6 +467,22 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
||||||
item.delete()
|
item.delete()
|
||||||
|
|
||||||
|
def test_pricing_rule_for_transaction(self):
|
||||||
|
make_item("Water Flask 1")
|
||||||
|
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||||
|
make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
|
||||||
|
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
|
||||||
|
|
||||||
|
si = create_sales_invoice(qty=5, do_not_submit=True)
|
||||||
|
self.assertEquals(len(si.items), 2)
|
||||||
|
self.assertEquals(si.items[1].rate, 10)
|
||||||
|
|
||||||
|
si1 = create_sales_invoice(qty=2, do_not_submit=True)
|
||||||
|
self.assertEquals(len(si1.items), 1)
|
||||||
|
|
||||||
|
for doc in [si, si1]:
|
||||||
|
doc.delete()
|
||||||
|
|
||||||
def make_pricing_rule(**args):
|
def make_pricing_rule(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
@@ -484,15 +500,23 @@ def make_pricing_rule(**args):
|
|||||||
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
|
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
|
||||||
"discount_percentage": args.discount_percentage or 0.0,
|
"discount_percentage": args.discount_percentage or 0.0,
|
||||||
"rate": args.rate or 0.0,
|
"rate": args.rate or 0.0,
|
||||||
"margin_type": args.margin_type,
|
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
||||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0
|
"condition": args.condition or '',
|
||||||
|
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for field in ["free_item", "free_qty", "free_item_rate", "priority",
|
||||||
|
"margin_type", "price_or_product_discount"]:
|
||||||
|
if args.get(field):
|
||||||
|
doc.set(field, args.get(field))
|
||||||
|
|
||||||
apply_on = doc.apply_on.replace(' ', '_').lower()
|
apply_on = doc.apply_on.replace(' ', '_').lower()
|
||||||
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
|
||||||
doc.append(child_table.get(doc.apply_on), {
|
|
||||||
apply_on: args.get(apply_on) or "_Test Item"
|
if doc.apply_on != "Transaction":
|
||||||
})
|
doc.append(child_table.get(doc.apply_on), {
|
||||||
|
apply_on: args.get(apply_on) or "_Test Item"
|
||||||
|
})
|
||||||
|
|
||||||
doc.insert(ignore_permissions=True)
|
doc.insert(ignore_permissions=True)
|
||||||
if args.get(apply_on) and apply_on != "item_code":
|
if args.get(apply_on) and apply_on != "item_code":
|
||||||
|
|||||||
@@ -453,6 +453,9 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
|
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
|
||||||
doc.total, pricing_rules)
|
doc.total, pricing_rules)
|
||||||
|
|
||||||
|
if not pricing_rules:
|
||||||
|
remove_free_item(doc)
|
||||||
|
|
||||||
for d in pricing_rules:
|
for d in pricing_rules:
|
||||||
if d.price_or_product_discount == 'Price':
|
if d.price_or_product_discount == 'Price':
|
||||||
if d.apply_discount_on:
|
if d.apply_discount_on:
|
||||||
@@ -476,6 +479,12 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
get_product_discount_rule(d, item_details, doc=doc)
|
get_product_discount_rule(d, item_details, doc=doc)
|
||||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||||
doc.set_missing_values()
|
doc.set_missing_values()
|
||||||
|
doc.calculate_taxes_and_totals()
|
||||||
|
|
||||||
|
def remove_free_item(doc):
|
||||||
|
for d in doc.items:
|
||||||
|
if d.is_free_item:
|
||||||
|
doc.remove(d)
|
||||||
|
|
||||||
def get_applied_pricing_rules(pricing_rules):
|
def get_applied_pricing_rules(pricing_rules):
|
||||||
if pricing_rules:
|
if pricing_rules:
|
||||||
@@ -488,7 +497,7 @@ def get_applied_pricing_rules(pricing_rules):
|
|||||||
|
|
||||||
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||||
free_item = pricing_rule.free_item
|
free_item = pricing_rule.free_item
|
||||||
if pricing_rule.same_item:
|
if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
|
||||||
free_item = item_details.item_code or args.item_code
|
free_item = item_details.item_code or args.item_code
|
||||||
|
|
||||||
if not free_item:
|
if not free_item:
|
||||||
@@ -517,13 +526,17 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
|||||||
if item_details.get("parenttype") == 'Sales Order':
|
if item_details.get("parenttype") == 'Sales Order':
|
||||||
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
|
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
|
||||||
|
|
||||||
company = args.get('company') or doc.company
|
company = doc.company
|
||||||
item_details.free_item_data['income_account'] = get_default_income_account(
|
if args and args.get("company"):
|
||||||
args=args,
|
company = args.get("company")
|
||||||
item=get_item_defaults(free_item, company),
|
|
||||||
item_group=get_item_group_defaults(free_item, company),
|
if args:
|
||||||
brand=get_brand_defaults(free_item, company),
|
item_details.free_item_data['income_account'] = get_default_income_account(
|
||||||
)
|
args=args,
|
||||||
|
item=get_item_defaults(free_item, company),
|
||||||
|
item_group=get_item_group_defaults(free_item, company),
|
||||||
|
brand=get_brand_defaults(free_item, company),
|
||||||
|
)
|
||||||
|
|
||||||
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
||||||
if pricing_rule_args.get('item_code'):
|
if pricing_rule_args.get('item_code'):
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
throw(_("Conversion rate cannot be 0 or 1"))
|
throw(_("Conversion rate cannot be 0 or 1"))
|
||||||
|
|
||||||
def validate_credit_to_acc(self):
|
def validate_credit_to_acc(self):
|
||||||
|
if not self.credit_to:
|
||||||
|
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
|
||||||
|
if not self.credit_to:
|
||||||
|
self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
|
||||||
|
|
||||||
account = frappe.db.get_value("Account", self.credit_to,
|
account = frappe.db.get_value("Account", self.credit_to,
|
||||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||||
|
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ def update_multi_mode_option(doc, pos_profile):
|
|||||||
|
|
||||||
def get_mode_of_payment(doc):
|
def get_mode_of_payment(doc):
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select mpa.default_account, mpa.parent, mp.type as type
|
select mpa.default_account, mpa.parent, mp.type as type
|
||||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||||
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
|
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
|
||||||
{'company': doc.company}, as_dict=1)
|
{'company': doc.company}, as_dict=1)
|
||||||
|
|
||||||
@@ -394,6 +394,14 @@ def get_pricing_rule_data(doc):
|
|||||||
between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')
|
between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')
|
||||||
order by priority desc, name desc""",
|
order by priority desc, name desc""",
|
||||||
{'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
|
{'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
|
||||||
|
|
||||||
|
for row in pricing_rules:
|
||||||
|
if row.apply_on:
|
||||||
|
doctype = "Pricing Rule " + row.apply_on
|
||||||
|
apply_on = frappe.scrub(row.apply_on)
|
||||||
|
row[apply_on] = [d.get(apply_on) for d in frappe.get_all(doctype,
|
||||||
|
filters = {"parent": row.name}, fields = [apply_on])]
|
||||||
|
|
||||||
return pricing_rules
|
return pricing_rules
|
||||||
|
|
||||||
|
|
||||||
@@ -434,10 +442,10 @@ def make_invoice(pos_profile, doc_list={}, email_queue_list={}, customers_list={
|
|||||||
name_list.append(name)
|
name_list.append(name)
|
||||||
|
|
||||||
email_queue = make_email_queue(email_queue_list)
|
email_queue = make_email_queue(email_queue_list)
|
||||||
|
|
||||||
if isinstance(pos_profile, string_types):
|
if isinstance(pos_profile, string_types):
|
||||||
pos_profile = json.loads(pos_profile)
|
pos_profile = json.loads(pos_profile)
|
||||||
|
|
||||||
customers = get_customers_list(pos_profile)
|
customers = get_customers_list(pos_profile)
|
||||||
return {
|
return {
|
||||||
'invoice': name_list,
|
'invoice': name_list,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{% include "erpnext/regional/india/taxes.js" %}
|
{% include "erpnext/regional/india/taxes.js" %}
|
||||||
|
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
|
||||||
|
|
||||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||||
|
erpnext.setup_einvoice_actions('Sales Invoice')
|
||||||
|
|
||||||
frappe.ui.form.on("Sales Invoice", {
|
frappe.ui.form.on("Sales Invoice", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
|||||||
@@ -225,9 +225,9 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
|
super(SalesInvoice, self).before_cancel()
|
||||||
self.update_time_sheet(None)
|
self.update_time_sheet(None)
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
super(SalesInvoice, self).on_cancel()
|
super(SalesInvoice, self).on_cancel()
|
||||||
|
|
||||||
@@ -398,6 +398,8 @@ class SalesInvoice(SellingController):
|
|||||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||||
if not self.pos_profile:
|
if not self.pos_profile:
|
||||||
pos_profile = get_pos_profile(self.company) or {}
|
pos_profile = get_pos_profile(self.company) or {}
|
||||||
|
if not pos_profile:
|
||||||
|
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||||
self.pos_profile = pos_profile.get('name')
|
self.pos_profile = pos_profile.get('name')
|
||||||
|
|
||||||
pos = {}
|
pos = {}
|
||||||
@@ -467,6 +469,11 @@ class SalesInvoice(SellingController):
|
|||||||
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
|
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
|
||||||
|
|
||||||
def validate_debit_to_acc(self):
|
def validate_debit_to_acc(self):
|
||||||
|
if not self.debit_to:
|
||||||
|
self.debit_to = get_party_account("Customer", self.customer, self.company)
|
||||||
|
if not self.debit_to:
|
||||||
|
self.raise_missing_debit_credit_account_error("Customer", self.customer)
|
||||||
|
|
||||||
account = frappe.get_cached_value("Account", self.debit_to,
|
account = frappe.get_cached_value("Account", self.debit_to,
|
||||||
["account_type", "report_type", "account_currency"], as_dict=True)
|
["account_type", "report_type", "account_currency"], as_dict=True)
|
||||||
|
|
||||||
|
|||||||
@@ -690,7 +690,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertFalse(gle)
|
self.assertFalse(gle)
|
||||||
|
|
||||||
def test_pos_gl_entry_with_perpetual_inventory(self):
|
def test_pos_gl_entry_with_perpetual_inventory(self):
|
||||||
make_pos_profile()
|
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||||
|
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||||
|
|
||||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
||||||
|
|
||||||
@@ -773,7 +774,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def test_pos_change_amount(self):
|
def test_pos_change_amount(self):
|
||||||
make_pos_profile()
|
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||||
|
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||||
|
|
||||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
||||||
|
|
||||||
@@ -795,7 +797,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
def test_make_pos_invoice(self):
|
def test_make_pos_invoice(self):
|
||||||
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
|
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
|
||||||
|
|
||||||
pos_profile = make_pos_profile()
|
pos_profile = make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||||
|
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
|
||||||
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
|
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
|
||||||
|
|
||||||
@@ -1838,93 +1841,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
|
||||||
|
|
||||||
def test_eway_bill_json(self):
|
def test_eway_bill_json(self):
|
||||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
si = make_sales_invoice_for_ewaybill()
|
||||||
address = frappe.get_doc({
|
|
||||||
"address_line1": "_Test Address Line 1",
|
|
||||||
"address_title": "_Test Address for Eway bill",
|
|
||||||
"address_type": "Billing",
|
|
||||||
"city": "_Test City",
|
|
||||||
"state": "Test State",
|
|
||||||
"country": "India",
|
|
||||||
"doctype": "Address",
|
|
||||||
"is_primary_address": 1,
|
|
||||||
"phone": "+91 0000000000",
|
|
||||||
"gstin": "27AAECE4835E1ZR",
|
|
||||||
"gst_state": "Maharashtra",
|
|
||||||
"gst_state_number": "27",
|
|
||||||
"pincode": "401108"
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
address.append("links", {
|
|
||||||
"link_doctype": "Company",
|
|
||||||
"link_name": "_Test Company"
|
|
||||||
})
|
|
||||||
|
|
||||||
address.save()
|
|
||||||
|
|
||||||
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
|
||||||
address = frappe.get_doc({
|
|
||||||
"address_line1": "_Test Address Line 1",
|
|
||||||
"address_title": "_Test Customer-Address for Eway bill",
|
|
||||||
"address_type": "Shipping",
|
|
||||||
"city": "_Test City",
|
|
||||||
"state": "Test State",
|
|
||||||
"country": "India",
|
|
||||||
"doctype": "Address",
|
|
||||||
"is_primary_address": 1,
|
|
||||||
"phone": "+91 0000000000",
|
|
||||||
"gst_state": "Maharashtra",
|
|
||||||
"gst_state_number": "27",
|
|
||||||
"pincode": "410038"
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
address.append("links", {
|
|
||||||
"link_doctype": "Customer",
|
|
||||||
"link_name": "_Test Customer"
|
|
||||||
})
|
|
||||||
|
|
||||||
address.save()
|
|
||||||
|
|
||||||
gst_settings = frappe.get_doc("GST Settings")
|
|
||||||
|
|
||||||
gst_account = frappe.get_all(
|
|
||||||
"GST Account",
|
|
||||||
fields=["cgst_account", "sgst_account", "igst_account"],
|
|
||||||
filters = {"company": "_Test Company"})
|
|
||||||
|
|
||||||
if not gst_account:
|
|
||||||
gst_settings.append("gst_accounts", {
|
|
||||||
"company": "_Test Company",
|
|
||||||
"cgst_account": "CGST - _TC",
|
|
||||||
"sgst_account": "SGST - _TC",
|
|
||||||
"igst_account": "IGST - _TC",
|
|
||||||
})
|
|
||||||
|
|
||||||
gst_settings.save()
|
|
||||||
|
|
||||||
si = create_sales_invoice(do_not_save =1, rate = '60000')
|
|
||||||
|
|
||||||
si.distance = 2000
|
|
||||||
si.company_address = "_Test Address for Eway bill-Billing"
|
|
||||||
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
|
||||||
si.vehicle_no = "KA12KA1234"
|
|
||||||
si.gst_category = "Registered Regular"
|
|
||||||
|
|
||||||
si.append("taxes", {
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": "CGST - _TC",
|
|
||||||
"cost_center": "Main - _TC",
|
|
||||||
"description": "CGST @ 9.0",
|
|
||||||
"rate": 9
|
|
||||||
})
|
|
||||||
|
|
||||||
si.append("taxes", {
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": "SGST - _TC",
|
|
||||||
"cost_center": "Main - _TC",
|
|
||||||
"description": "SGST @ 9.0",
|
|
||||||
"rate": 9
|
|
||||||
})
|
|
||||||
|
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
@@ -1940,6 +1857,187 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
||||||
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
||||||
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
||||||
|
|
||||||
|
def test_einvoice_submission_without_irn(self):
|
||||||
|
# init
|
||||||
|
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||||
|
country = frappe.flags.country
|
||||||
|
frappe.flags.country = 'India'
|
||||||
|
|
||||||
|
si = make_sales_invoice_for_ewaybill()
|
||||||
|
self.assertRaises(frappe.ValidationError, si.submit)
|
||||||
|
|
||||||
|
si.irn = 'test_irn'
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
# reset
|
||||||
|
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||||
|
frappe.flags.country = country
|
||||||
|
|
||||||
|
def test_einvoice_json(self):
|
||||||
|
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||||
|
|
||||||
|
customer_gstin = '27AACCM7806M1Z3'
|
||||||
|
customer_gstin_dtls = {
|
||||||
|
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
|
||||||
|
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
|
||||||
|
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
||||||
|
}
|
||||||
|
company_gstin = '27AAECE4835E1ZR'
|
||||||
|
company_gstin_dtls = {
|
||||||
|
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
|
||||||
|
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
|
||||||
|
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
||||||
|
}
|
||||||
|
# set cache gstin details to avoid fetching details which will require connection to GSP servers
|
||||||
|
frappe.local.gstin_cache = {}
|
||||||
|
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
|
||||||
|
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
|
||||||
|
|
||||||
|
si = make_sales_invoice_for_ewaybill()
|
||||||
|
si.naming_series = 'INV-2020-.#####'
|
||||||
|
si.items = []
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 2,
|
||||||
|
"rate": 100,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item 2",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 4,
|
||||||
|
"rate": 150,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
|
||||||
|
total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']])
|
||||||
|
|
||||||
|
self.assertEqual(einvoice['Version'], '1.1')
|
||||||
|
self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value)
|
||||||
|
self.assertTrue(einvoice['EwbDtls'])
|
||||||
|
|
||||||
|
def make_sales_invoice_for_ewaybill():
|
||||||
|
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||||
|
address = frappe.get_doc({
|
||||||
|
"address_line1": "_Test Address Line 1",
|
||||||
|
"address_title": "_Test Address for Eway bill",
|
||||||
|
"address_type": "Billing",
|
||||||
|
"city": "_Test City",
|
||||||
|
"state": "Test State",
|
||||||
|
"country": "India",
|
||||||
|
"doctype": "Address",
|
||||||
|
"is_primary_address": 1,
|
||||||
|
"phone": "+910000000000",
|
||||||
|
"gstin": "27AAECE4835E1ZR",
|
||||||
|
"gst_state": "Maharashtra",
|
||||||
|
"gst_state_number": "27",
|
||||||
|
"pincode": "401108"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
address.append("links", {
|
||||||
|
"link_doctype": "Company",
|
||||||
|
"link_name": "_Test Company"
|
||||||
|
})
|
||||||
|
|
||||||
|
address.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
||||||
|
address = frappe.get_doc({
|
||||||
|
"address_line1": "_Test Address Line 1",
|
||||||
|
"address_title": "_Test Customer-Address for Eway bill",
|
||||||
|
"address_type": "Shipping",
|
||||||
|
"city": "_Test City",
|
||||||
|
"state": "Test State",
|
||||||
|
"country": "India",
|
||||||
|
"doctype": "Address",
|
||||||
|
"is_primary_address": 1,
|
||||||
|
"phone": "+910000000000",
|
||||||
|
"gstin": "27AACCM7806M1Z3",
|
||||||
|
"gst_state": "Maharashtra",
|
||||||
|
"gst_state_number": "27",
|
||||||
|
"pincode": "410038"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
address.append("links", {
|
||||||
|
"link_doctype": "Customer",
|
||||||
|
"link_name": "_Test Customer"
|
||||||
|
})
|
||||||
|
|
||||||
|
address.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "Supplier",
|
||||||
|
"supplier_name": "_Test Transporter",
|
||||||
|
"country": "India",
|
||||||
|
"supplier_group": "_Test Supplier Group",
|
||||||
|
"supplier_type": "Company",
|
||||||
|
"is_transporter": 1
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
gst_settings = frappe.get_doc("GST Settings")
|
||||||
|
|
||||||
|
gst_account = frappe.get_all(
|
||||||
|
"GST Account",
|
||||||
|
fields=["cgst_account", "sgst_account", "igst_account"],
|
||||||
|
filters = {"company": "_Test Company"})
|
||||||
|
|
||||||
|
if not gst_account:
|
||||||
|
gst_settings.append("gst_accounts", {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"cgst_account": "CGST - _TC",
|
||||||
|
"sgst_account": "SGST - _TC",
|
||||||
|
"igst_account": "IGST - _TC",
|
||||||
|
})
|
||||||
|
|
||||||
|
gst_settings.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save =1, rate = '60000')
|
||||||
|
|
||||||
|
si.distance = 2000
|
||||||
|
si.company_address = "_Test Address for Eway bill-Billing"
|
||||||
|
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
||||||
|
si.vehicle_no = "KA12KA1234"
|
||||||
|
si.gst_category = "Registered Regular"
|
||||||
|
si.mode_of_transport = 'Road'
|
||||||
|
si.transporter = '_Test Transporter'
|
||||||
|
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "CGST - _TC",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"description": "CGST @ 9.0",
|
||||||
|
"rate": 9
|
||||||
|
})
|
||||||
|
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "SGST - _TC",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"description": "SGST @ 9.0",
|
||||||
|
"rate": 9
|
||||||
|
})
|
||||||
|
|
||||||
|
return si
|
||||||
|
|
||||||
def test_item_tax_validity(self):
|
def test_item_tax_validity(self):
|
||||||
item = frappe.get_doc("Item", "_Test Item 2")
|
item = frappe.get_doc("Item", "_Test Item 2")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"cancelation_date",
|
"cancelation_date",
|
||||||
"trial_period_start",
|
"trial_period_start",
|
||||||
"trial_period_end",
|
"trial_period_end",
|
||||||
|
"generate_new_invoices_past_due_date",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
"current_invoice_start",
|
"current_invoice_start",
|
||||||
"current_invoice_end",
|
"current_invoice_end",
|
||||||
@@ -183,8 +184,7 @@
|
|||||||
"fieldname": "invoices",
|
"fieldname": "invoices",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Invoices",
|
"label": "Invoices",
|
||||||
"options": "Subscription Invoice",
|
"options": "Subscription Invoice"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@@ -195,9 +195,16 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
|
||||||
|
"fieldname": "generate_new_invoices_past_due_date",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Generate New Invoices Past Due Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2020-08-27 23:30:02.504042",
|
"modified": "2020-11-29 22:46:14.879289",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Subscription",
|
"name": "Subscription",
|
||||||
|
|||||||
@@ -408,6 +408,15 @@ class Subscription(Document):
|
|||||||
else:
|
else:
|
||||||
self.set_status_grace_period()
|
self.set_status_grace_period()
|
||||||
|
|
||||||
|
if getdate() > getdate(self.current_invoice_end):
|
||||||
|
self.update_subscription_period(add_days(self.current_invoice_end, 1))
|
||||||
|
|
||||||
|
# Generate invoices periodically even if current invoice are unpaid
|
||||||
|
if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice()
|
||||||
|
or self.is_prepaid_to_invoice()):
|
||||||
|
prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
|
||||||
|
self.generate_invoice(prorate)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_not_outstanding(invoice):
|
def is_not_outstanding(invoice):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2018,34 +2018,57 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
|||||||
|
|
||||||
apply_pricing_rule: function () {
|
apply_pricing_rule: function () {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
|
var remove_item = false;
|
||||||
$.each(this.frm.doc["items"], function (n, item) {
|
$.each(this.frm.doc["items"], function (n, item) {
|
||||||
var pricing_rule = me.get_pricing_rule(item)
|
var pricing_rule = me.get_pricing_rule(item)
|
||||||
me.validate_pricing_rule(pricing_rule)
|
me.validate_pricing_rule(pricing_rule)
|
||||||
if (pricing_rule.length) {
|
if (pricing_rule.length) {
|
||||||
item.pricing_rule = pricing_rule[0].name;
|
if (pricing_rule[0].price_or_product_discount == "Price") {
|
||||||
item.margin_type = pricing_rule[0].margin_type;
|
item.pricing_rule = pricing_rule[0].name;
|
||||||
item.price_list_rate = pricing_rule[0].price || item.price_list_rate;
|
item.margin_type = pricing_rule[0].margin_type;
|
||||||
item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount;
|
item.price_list_rate = pricing_rule[0].price || item.price_list_rate;
|
||||||
item.discount_percentage = pricing_rule[0].discount_percentage || 0.0;
|
item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount;
|
||||||
me.apply_pricing_rule_on_item(item)
|
item.discount_percentage = pricing_rule[0].discount_percentage || 0.0;
|
||||||
|
me.apply_pricing_rule_on_item(item)
|
||||||
|
} else {
|
||||||
|
me.child = frappe.model.add_child(me.frm.doc, me.frm.doc.doctype + " Item", "items");
|
||||||
|
me.child.item_code = pricing_rule[0].same_item ? item.item_code : pricing_rule[0].free_item;
|
||||||
|
me.child.item_name = pricing_rule[0].same_item ? item.item_name : pricing_rule[0].free_item;
|
||||||
|
me.child.stock_uom = pricing_rule[0].same_item ? item.stock_uom : pricing_rule[0].free_item_uom;
|
||||||
|
me.child.uom = pricing_rule[0].same_item ? item.uom : pricing_rule[0].free_item_uom;
|
||||||
|
me.child.conversion_factor = 1;
|
||||||
|
me.child.qty = pricing_rule.qty || 1;
|
||||||
|
me.child.is_free_item = 1;
|
||||||
|
me.child.brand = pricing_rule[0].same_item ? item.brand : "";
|
||||||
|
me.child.description = pricing_rule[0].same_item ? item.description : pricing_rule[0].free_item;
|
||||||
|
}
|
||||||
} else if (item.pricing_rule) {
|
} else if (item.pricing_rule) {
|
||||||
item.price_list_rate = me.price_list_data[item.item_code]
|
item.price_list_rate = me.price_list_data[item.item_code]
|
||||||
item.margin_rate_or_amount = 0.0;
|
item.margin_rate_or_amount = 0.0;
|
||||||
item.discount_percentage = 0.0;
|
item.discount_percentage = 0.0;
|
||||||
item.pricing_rule = null;
|
item.pricing_rule = null;
|
||||||
me.apply_pricing_rule_on_item(item)
|
me.apply_pricing_rule_on_item(item)
|
||||||
|
} else if (item.is_free_item) {
|
||||||
|
remove_item = true;
|
||||||
|
item.qty = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if(item.discount_percentage > 0) {
|
if(item.discount_percentage > 0) {
|
||||||
me.apply_pricing_rule_on_item(item)
|
me.apply_pricing_rule_on_item(item)
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if (remove_item) {
|
||||||
|
this.remove_zero_qty_items_from_cart();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
get_pricing_rule: function (item) {
|
get_pricing_rule: function (item) {
|
||||||
var me = this;
|
var me = this;
|
||||||
return $.grep(this.pricing_rules, function (data) {
|
return $.grep(this.pricing_rules, function (data) {
|
||||||
if (item.qty >= data.min_qty && (item.qty <= (data.max_qty ? data.max_qty : item.qty))) {
|
me.get_mixed_min_max_qty_and_amt(data, item);
|
||||||
|
if (data.mixed_qty >= data.min_qty && (data.mixed_qty <= (data.max_qty ? data.max_qty : data.mixed_qty))) {
|
||||||
if (me.validate_item_condition(data, item)) {
|
if (me.validate_item_condition(data, item)) {
|
||||||
if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) {
|
if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) {
|
||||||
return me.validate_condition(data)
|
return me.validate_condition(data)
|
||||||
@@ -2057,11 +2080,26 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_mixed_min_max_qty_and_amt: function(data, item) {
|
||||||
|
var apply_on = frappe.model.scrub(data.apply_on);
|
||||||
|
data.mixed_qty = 0.0
|
||||||
|
if (data.mixed_conditions && in_list(data[apply_on], item[apply_on])) {
|
||||||
|
this.frm.doc.items.forEach(d => {
|
||||||
|
if (in_list(data[apply_on], d[apply_on])) {
|
||||||
|
data.mixed_qty += d.qty;
|
||||||
|
data.mixed_amt += d.amount;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
data.mixed_qty = item.qty;
|
||||||
|
data.mixed_amt = item.amount;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
validate_item_condition: function (data, item) {
|
validate_item_condition: function (data, item) {
|
||||||
var apply_on = frappe.model.scrub(data.apply_on);
|
var apply_on = frappe.model.scrub(data.apply_on);
|
||||||
|
|
||||||
return (data.apply_on == 'Item Group')
|
return in_list(data[apply_on], item[apply_on]);
|
||||||
? this.validate_item_group(data.item_group, item.item_group) : (data[apply_on] == item[apply_on]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
validate_item_group: function (pr_item_group, cart_item_group) {
|
validate_item_group: function (pr_item_group, cart_item_group) {
|
||||||
|
|||||||
166
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
166
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
{%- 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 {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||||
|
{% if letter_head and not no_letterhead %}
|
||||||
|
<div class="letter-head">{{ letter_head }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="print-heading">
|
||||||
|
<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if print_settings.repeat_header_footer %}
|
||||||
|
<div id="footer-html" class="visible-pdf">
|
||||||
|
{% if not no_letterhead and footer %}
|
||||||
|
<div class="letter-head-footer">
|
||||||
|
{{ footer }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-center small page-number visible-pdf">
|
||||||
|
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||||
|
<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
|
||||||
|
<div class="col-xs-7 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-5 column-break">
|
||||||
|
<img src="{{ doc.qrcode_image }}" alt="QRCode Image" style="
|
||||||
|
width: 175px; height: 175px;
|
||||||
|
float: right; border: 1px solid gray;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
">
|
||||||
|
</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>
|
||||||
|
{% for item in einvoice.ItemList %}
|
||||||
|
<tr>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</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": 1,
|
||||||
|
"doc_type": "Sales Invoice",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Print Format",
|
||||||
|
"font": "Default",
|
||||||
|
"html": "",
|
||||||
|
"idx": 0,
|
||||||
|
"line_breaks": 1,
|
||||||
|
"modified": "2020-10-23 19:54:40.634936",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
@@ -42,11 +42,13 @@
|
|||||||
|
|
||||||
{% if(filters.show_future_payments) { %}
|
{% if(filters.show_future_payments) { %}
|
||||||
{% var balance_row = data.slice(-1).pop();
|
{% var balance_row = data.slice(-1).pop();
|
||||||
var range1 = report.columns[11].label;
|
var start = filters.based_on_payment_terms ? 13 : 11;
|
||||||
var range2 = report.columns[12].label;
|
var range1 = report.columns[start].label;
|
||||||
var range3 = report.columns[13].label;
|
var range2 = report.columns[start+1].label;
|
||||||
var range4 = report.columns[14].label;
|
var range3 = report.columns[start+2].label;
|
||||||
var range5 = report.columns[15].label;
|
var range4 = report.columns[start+3].label;
|
||||||
|
var range5 = report.columns[start+4].label;
|
||||||
|
var range6 = report.columns[start+5].label;
|
||||||
%}
|
%}
|
||||||
{% if(balance_row) { %}
|
{% if(balance_row) { %}
|
||||||
<table class="table table-bordered table-condensed">
|
<table class="table table-bordered table-condensed">
|
||||||
@@ -70,20 +72,34 @@
|
|||||||
<th>{%= __(range3) %}</th>
|
<th>{%= __(range3) %}</th>
|
||||||
<th>{%= __(range4) %}</th>
|
<th>{%= __(range4) %}</th>
|
||||||
<th>{%= __(range5) %}</th>
|
<th>{%= __(range5) %}</th>
|
||||||
|
<th>{%= __(range6) %}</th>
|
||||||
<th>{%= __("Total") %}</th>
|
<th>{%= __("Total") %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{%= __("Total Outstanding") %}</td>
|
<td>{%= __("Total Outstanding") %}</td>
|
||||||
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
|
<td class="text-right">
|
||||||
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
|
{%= format_number(balance_row["age"], null, 2) %}
|
||||||
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
|
</td>
|
||||||
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
|
<td class="text-right">
|
||||||
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
|
{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
|
||||||
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
|
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<td>{%= __("Future Payments") %}</td>
|
<td>{%= __("Future Payments") %}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
@@ -91,6 +107,7 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
<td></td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
|
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
|
||||||
</td>
|
</td>
|
||||||
@@ -101,6 +118,7 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
<th></th>
|
||||||
<th class="text-right">
|
<th class="text-right">
|
||||||
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
|
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -218,15 +236,15 @@
|
|||||||
<td></td>
|
<td></td>
|
||||||
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
|
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
|
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
|
||||||
|
|
||||||
{% if(!filters.show_future_payments) { %}
|
{% if(!filters.show_future_payments) { %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
|
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td>
|
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">
|
<td style="text-align: right">
|
||||||
{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
|
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||||
|
|
||||||
{% if(filters.show_future_payments) { %}
|
{% if(filters.show_future_payments) { %}
|
||||||
{% if(report.report_name === "Accounts Receivable") { %}
|
{% if(report.report_name === "Accounts Receivable") { %}
|
||||||
@@ -234,8 +252,8 @@
|
|||||||
{%= data[i]["po_no"] %}</td>
|
{%= data[i]["po_no"] %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
|
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } else { %}
|
{% } else { %}
|
||||||
@@ -256,10 +274,10 @@
|
|||||||
{% } else { %}
|
{% } else { %}
|
||||||
<td><b>{%= __("Total") %}</b></td>
|
<td><b>{%= __("Total") %}</b></td>
|
||||||
{% } %}
|
{% } %}
|
||||||
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
|
||||||
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
|
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||||
{% } %}
|
{% } %}
|
||||||
{% } %}
|
{% } %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -14,19 +14,11 @@ def execute(filters=None):
|
|||||||
|
|
||||||
def get_column():
|
def get_column():
|
||||||
return [
|
return [
|
||||||
_("Delivery Note") + ":Link/Delivery Note:160",
|
_("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
|
||||||
_("Date") + ":Date:100",
|
_("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
|
||||||
_("Customer") + ":Link/Customer:120",
|
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
|
||||||
_("Customer Name") + "::120",
|
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
|
||||||
_("Item Code") + ":Link/Item:120",
|
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
|
||||||
_("Amount") + ":Currency:100",
|
|
||||||
_("Billed Amount") + ":Currency:100",
|
|
||||||
_("Returned Amount") + ":Currency:120",
|
|
||||||
_("Pending Amount") + ":Currency:100",
|
|
||||||
_("Item Name") + "::120",
|
|
||||||
_("Description") + "::120",
|
|
||||||
_("Project") + ":Link/Project:120",
|
|
||||||
_("Company") + ":Link/Company:120",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_args():
|
def get_args():
|
||||||
|
|||||||
@@ -17,26 +17,18 @@ def get_ordered_to_be_billed_data(args):
|
|||||||
|
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
Select
|
Select
|
||||||
`{parent_tab}`.name, `{parent_tab}`.{date_field},
|
`{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
|
||||||
`{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
|
{project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
|
||||||
`{child_tab}`.item_code,
|
|
||||||
`{child_tab}`.base_amount,
|
|
||||||
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
|
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
|
||||||
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
|
(`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
|
||||||
(`{child_tab}`.base_amount -
|
`{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
|
||||||
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
|
|
||||||
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
|
|
||||||
`{child_tab}`.item_name, `{child_tab}`.description,
|
|
||||||
{project_field}, `{parent_tab}`.company
|
|
||||||
from
|
from
|
||||||
`{parent_tab}`, `{child_tab}`
|
`{parent_tab}`, `{child_tab}`
|
||||||
where
|
where
|
||||||
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
|
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
|
||||||
and `{parent_tab}`.status not in ('Closed', 'Completed')
|
and `{parent_tab}`.status not in ('Closed', 'Completed')
|
||||||
and `{child_tab}`.amount > 0
|
and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt *
|
||||||
and (`{child_tab}`.base_amount -
|
ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount
|
||||||
round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
|
|
||||||
(`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
|
|
||||||
order by
|
order by
|
||||||
`{parent_tab}`.{order} {order_by}
|
`{parent_tab}`.{order} {order_by}
|
||||||
""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,
|
""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,
|
||||||
|
|||||||
@@ -14,19 +14,11 @@ def execute(filters=None):
|
|||||||
|
|
||||||
def get_column():
|
def get_column():
|
||||||
return [
|
return [
|
||||||
_("Purchase Receipt") + ":Link/Purchase Receipt:160",
|
_("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
|
||||||
_("Date") + ":Date:100",
|
_("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
|
||||||
_("Supplier") + ":Link/Supplier:120",
|
_("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
|
||||||
_("Supplier Name") + "::120",
|
_("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
|
||||||
_("Item Code") + ":Link/Item:120",
|
_("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
|
||||||
_("Amount") + ":Currency:100",
|
|
||||||
_("Billed Amount") + ":Currency:100",
|
|
||||||
_("Returned Amount") + ":Currency:120",
|
|
||||||
_("Pending Amount") + ":Currency:120",
|
|
||||||
_("Item Name") + "::120",
|
|
||||||
_("Description") + "::120",
|
|
||||||
_("Project") + ":Link/Project:120",
|
|
||||||
_("Company") + ":Link/Company:120",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_args():
|
def get_args():
|
||||||
|
|||||||
@@ -75,7 +75,10 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
|
|||||||
else:
|
else:
|
||||||
return ((fy.name, fy.year_start_date, fy.year_end_date),)
|
return ((fy.name, fy.year_start_date, fy.year_end_date),)
|
||||||
|
|
||||||
error_msg = _("""{0} {1} not in any active Fiscal Year.""").format(label, formatdate(transaction_date))
|
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
|
||||||
|
if company:
|
||||||
|
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
|
||||||
|
|
||||||
if verbose==1: frappe.msgprint(error_msg)
|
if verbose==1: frappe.msgprint(error_msg)
|
||||||
raise FiscalYearError(error_msg)
|
raise FiscalYearError(error_msg)
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
if (frm.doc.docstatus == 0) {
|
if (frm.doc.docstatus == 0) {
|
||||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||||
|
frm.set_df_property('depreciation_start_date', 'reqd', 1, frm.doc.name, 'finance_books');
|
||||||
|
frm.refresh_field('finance_books');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -323,7 +325,10 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
calculate_depreciation: function(frm) {
|
calculate_depreciation: function(frm) {
|
||||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||||
frm.trigger('set_finance_book');
|
|
||||||
|
if (frm.doc.calculate_depreciation) {
|
||||||
|
frm.trigger('set_finance_book');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
gross_purchase_amount: function(frm) {
|
gross_purchase_amount: function(frm) {
|
||||||
|
|||||||
@@ -50,6 +50,5 @@ frappe.ui.form.on('Asset Category', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
|
||||||
"creation": "2018-05-08 14:44:37.095570",
|
"creation": "2018-05-08 14:44:37.095570",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@@ -54,9 +53,7 @@
|
|||||||
"fieldname": "depreciation_start_date",
|
"fieldname": "depreciation_start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Depreciation Posting Date",
|
"label": "Depreciation Posting Date"
|
||||||
"mandatory_depends_on": "eval:parent.doctype == 'Asset'",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -84,10 +81,8 @@
|
|||||||
"label": "Rate of Depreciation"
|
"label": "Rate of Depreciation"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"modified": "2020-12-30 15:43:03.188256",
|
||||||
"modified": "2020-10-30 15:22:29.119868",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from frappe.model.document import Document
|
|||||||
class AssetValueAdjustment(Document):
|
class AssetValueAdjustment(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_date()
|
self.validate_date()
|
||||||
self.set_difference_amount()
|
|
||||||
self.set_current_asset_value()
|
self.set_current_asset_value()
|
||||||
|
self.set_difference_amount()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.make_depreciation_entry()
|
self.make_depreciation_entry()
|
||||||
|
|||||||
@@ -148,24 +148,23 @@ def get_data(filters):
|
|||||||
for asset in assets_record:
|
for asset in assets_record:
|
||||||
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
|
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
|
||||||
- flt(depreciation_amount_map.get(asset.name))
|
- flt(depreciation_amount_map.get(asset.name))
|
||||||
if asset_value:
|
row = {
|
||||||
row = {
|
"asset_id": asset.asset_id,
|
||||||
"asset_id": asset.name,
|
"asset_name": asset.asset_name,
|
||||||
"asset_name": asset.asset_name,
|
"status": asset.status,
|
||||||
"status": asset.status,
|
"department": asset.department,
|
||||||
"department": asset.department,
|
"cost_center": asset.cost_center,
|
||||||
"cost_center": asset.cost_center,
|
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
|
||||||
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
|
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
||||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
|
||||||
"depreciated_amount": depreciation_amount_map.get(asset.name) or 0.0,
|
"available_for_use_date": asset.available_for_use_date,
|
||||||
"available_for_use_date": asset.available_for_use_date,
|
"location": asset.location,
|
||||||
"location": asset.location,
|
"asset_category": asset.asset_category,
|
||||||
"asset_category": asset.asset_category,
|
"purchase_date": asset.purchase_date,
|
||||||
"purchase_date": asset.purchase_date,
|
"asset_value": asset_value
|
||||||
"asset_value": asset_value
|
}
|
||||||
}
|
data.append(row)
|
||||||
data.append(row)
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
|
|||||||
from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
|
from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
|
||||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||||
|
|
||||||
|
class AccountMissingError(frappe.ValidationError): pass
|
||||||
|
|
||||||
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
|
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
|
||||||
|
|
||||||
class AccountsController(TransactionBase):
|
class AccountsController(TransactionBase):
|
||||||
@@ -106,8 +108,14 @@ class AccountsController(TransactionBase):
|
|||||||
self.validate_deferred_start_and_end_date()
|
self.validate_deferred_start_and_end_date()
|
||||||
|
|
||||||
validate_regional(self)
|
validate_regional(self)
|
||||||
|
|
||||||
|
validate_einvoice_fields(self)
|
||||||
|
|
||||||
if self.doctype != 'Material Request':
|
if self.doctype != 'Material Request':
|
||||||
apply_pricing_rule_on_transaction(self)
|
apply_pricing_rule_on_transaction(self)
|
||||||
|
|
||||||
|
def before_cancel(self):
|
||||||
|
validate_einvoice_fields(self)
|
||||||
|
|
||||||
def validate_deferred_start_and_end_date(self):
|
def validate_deferred_start_and_end_date(self):
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
@@ -711,6 +719,21 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
return self._abbr
|
return self._abbr
|
||||||
|
|
||||||
|
def raise_missing_debit_credit_account_error(self, party_type, party):
|
||||||
|
"""Raise an error if debit to/credit to account does not exist."""
|
||||||
|
db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
|
||||||
|
rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
|
||||||
|
|
||||||
|
link_to_party = frappe.utils.get_link_to_form(party_type, party)
|
||||||
|
link_to_company = frappe.utils.get_link_to_form("Company", self.company)
|
||||||
|
|
||||||
|
message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
|
||||||
|
message += "<br>" + _("Please set one of the following:") + "<br>"
|
||||||
|
message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
|
||||||
|
message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
|
||||||
|
|
||||||
|
frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
|
||||||
|
|
||||||
def validate_party(self):
|
def validate_party(self):
|
||||||
party_type, party = self.get_party()
|
party_type, party = self.get_party()
|
||||||
validate_party_frozen_disabled(party_type, party)
|
validate_party_frozen_disabled(party_type, party)
|
||||||
@@ -1406,3 +1429,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def validate_regional(doc):
|
def validate_regional(doc):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def validate_einvoice_fields(doc):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class SellingController(StockController):
|
|||||||
self.validate_max_discount()
|
self.validate_max_discount()
|
||||||
self.validate_selling_price()
|
self.validate_selling_price()
|
||||||
self.set_qty_as_per_stock_uom()
|
self.set_qty_as_per_stock_uom()
|
||||||
self.set_po_nos()
|
self.set_po_nos(for_validate=True)
|
||||||
self.set_gross_profit()
|
self.set_gross_profit()
|
||||||
set_default_income_account_for_item(self)
|
set_default_income_account_for_item(self)
|
||||||
self.set_customer_address()
|
self.set_customer_address()
|
||||||
@@ -364,20 +364,28 @@ class SellingController(StockController):
|
|||||||
}))
|
}))
|
||||||
self.make_sl_entries(sl_entries)
|
self.make_sl_entries(sl_entries)
|
||||||
|
|
||||||
def set_po_nos(self):
|
def set_po_nos(self, for_validate=False):
|
||||||
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
|
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
|
||||||
|
if for_validate and self.po_no:
|
||||||
|
return
|
||||||
self.set_pos_for_sales_invoice()
|
self.set_pos_for_sales_invoice()
|
||||||
if self.doctype == 'Delivery Note' and hasattr(self, "items"):
|
if self.doctype == 'Delivery Note' and hasattr(self, "items"):
|
||||||
|
if for_validate and self.po_no:
|
||||||
|
return
|
||||||
self.set_pos_for_delivery_note()
|
self.set_pos_for_delivery_note()
|
||||||
|
|
||||||
def set_pos_for_sales_invoice(self):
|
def set_pos_for_sales_invoice(self):
|
||||||
po_nos = []
|
po_nos = []
|
||||||
|
if self.po_no:
|
||||||
|
po_nos.append(self.po_no)
|
||||||
self.get_po_nos('Sales Order', 'sales_order', po_nos)
|
self.get_po_nos('Sales Order', 'sales_order', po_nos)
|
||||||
self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
|
self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
|
||||||
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
||||||
|
|
||||||
def set_pos_for_delivery_note(self):
|
def set_pos_for_delivery_note(self):
|
||||||
po_nos = []
|
po_nos = []
|
||||||
|
if self.po_no:
|
||||||
|
po_nos.append(self.po_no)
|
||||||
self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
|
self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
|
||||||
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
|
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
|
||||||
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
||||||
|
|||||||
@@ -246,22 +246,26 @@ class StatusUpdater(Document):
|
|||||||
if not args.get("second_source_extra_cond"):
|
if not args.get("second_source_extra_cond"):
|
||||||
args["second_source_extra_cond"] = ""
|
args["second_source_extra_cond"] = ""
|
||||||
|
|
||||||
args['second_source_condition'] = """ + ifnull((select sum(%(second_source_field)s)
|
args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
|
||||||
from `tab%(second_source_dt)s`
|
from `tab%(second_source_dt)s`
|
||||||
where `%(second_join_field)s`="%(detail_id)s"
|
where `%(second_join_field)s`="%(detail_id)s"
|
||||||
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0) """ % args
|
and (`tab%(second_source_dt)s`.docstatus=1)
|
||||||
|
%(second_source_extra_cond)s), 0) """ % args)[0][0]
|
||||||
|
|
||||||
if args['detail_id']:
|
if args['detail_id']:
|
||||||
if not args.get("extra_cond"): args["extra_cond"] = ""
|
if not args.get("extra_cond"): args["extra_cond"] = ""
|
||||||
|
|
||||||
frappe.db.sql("""update `tab%(target_dt)s`
|
args["source_dt_value"] = frappe.db.sql("""
|
||||||
set %(target_field)s = (
|
|
||||||
(select ifnull(sum(%(source_field)s), 0)
|
(select ifnull(sum(%(source_field)s), 0)
|
||||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||||
and (docstatus=1 %(cond)s) %(extra_cond)s)
|
and (docstatus=1 %(cond)s) %(extra_cond)s)
|
||||||
%(second_source_condition)s
|
""" % args)[0][0] or 0.0
|
||||||
)
|
|
||||||
%(update_modified)s
|
if args['second_source_condition']:
|
||||||
|
args["source_dt_value"] += flt(args['second_source_condition'])
|
||||||
|
|
||||||
|
frappe.db.sql("""update `tab%(target_dt)s`
|
||||||
|
set %(target_field)s = %(source_dt_value)s %(update_modified)s
|
||||||
where name='%(detail_id)s'""" % args)
|
where name='%(detail_id)s'""" % args)
|
||||||
|
|
||||||
def _update_percent_field_in_targets(self, args, update_modified=True):
|
def _update_percent_field_in_targets(self, args, update_modified=True):
|
||||||
|
|||||||
@@ -227,9 +227,9 @@ class StockController(AccountsController):
|
|||||||
|
|
||||||
def check_expense_account(self, item):
|
def check_expense_account(self, item):
|
||||||
if not item.get("expense_account"):
|
if not item.get("expense_account"):
|
||||||
frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
|
msg = _("Please set an Expense Account in the Items table")
|
||||||
Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
|
frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
|
||||||
title=_("Expense Account Missing"))
|
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
is_expense_account = frappe.db.get_value("Account",
|
is_expense_account = frappe.db.get_value("Account",
|
||||||
@@ -242,11 +242,12 @@ class StockController(AccountsController):
|
|||||||
_(self.doctype), self.name, item.get("item_code")))
|
_(self.doctype), self.name, item.get("item_code")))
|
||||||
|
|
||||||
def delete_auto_created_batches(self):
|
def delete_auto_created_batches(self):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
if not d.batch_no: continue
|
if not d.batch_no: continue
|
||||||
|
|
||||||
serial_nos = get_serial_nos(d.serial_no)
|
serial_nos = [sr.name for sr in frappe.get_all("Serial No",
|
||||||
|
{'batch_no': d.batch_no, 'status': 'Inactive'})]
|
||||||
|
|
||||||
if serial_nos:
|
if serial_nos:
|
||||||
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
|
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
|
||||||
|
|
||||||
|
|||||||
@@ -244,6 +244,15 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
|
|||||||
"""Shipping lines represents the shipping details,
|
"""Shipping lines represents the shipping details,
|
||||||
each such shipping detail consists of a list of tax_lines"""
|
each such shipping detail consists of a list of tax_lines"""
|
||||||
for shipping_charge in shipping_lines:
|
for shipping_charge in shipping_lines:
|
||||||
|
if shipping_charge.get("price"):
|
||||||
|
taxes.append({
|
||||||
|
"charge_type": _("Actual"),
|
||||||
|
"account_head": get_tax_account_head(shipping_charge),
|
||||||
|
"description": shipping_charge["title"],
|
||||||
|
"tax_amount": shipping_charge["price"],
|
||||||
|
"cost_center": shopify_settings.cost_center
|
||||||
|
})
|
||||||
|
|
||||||
for tax in shipping_charge.get("tax_lines"):
|
for tax in shipping_charge.get("tax_lines"):
|
||||||
taxes.append({
|
taxes.append({
|
||||||
"charge_type": _("Actual"),
|
"charge_type": _("Actual"),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import frappe
|
|||||||
import unittest
|
import unittest
|
||||||
from frappe.utils.make_random import get_random
|
from frappe.utils.make_random import get_random
|
||||||
from frappe.utils import nowdate, add_days, getdate
|
from frappe.utils import nowdate, add_days, getdate
|
||||||
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
|
||||||
test_dependencies = ["Company"]
|
test_dependencies = ["Company"]
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ class TestFeeValidity(unittest.TestCase):
|
|||||||
def test_fee_validity(self):
|
def test_fee_validity(self):
|
||||||
frappe.db.sql("""delete from `tabPatient Appointment`""")
|
frappe.db.sql("""delete from `tabPatient Appointment`""")
|
||||||
frappe.db.sql("""delete from `tabFee Validity`""")
|
frappe.db.sql("""delete from `tabFee Validity`""")
|
||||||
|
make_pos_profile()
|
||||||
patient = get_random("Patient")
|
patient = get_random("Patient")
|
||||||
practitioner = get_random("Healthcare Practitioner")
|
practitioner = get_random("Healthcare Practitioner")
|
||||||
department = get_random("Medical Department")
|
department = get_random("Medical Department")
|
||||||
|
|||||||
@@ -239,6 +239,9 @@ doc_events = {
|
|||||||
"Website Settings": {
|
"Website Settings": {
|
||||||
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
|
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
|
||||||
},
|
},
|
||||||
|
"Tax Category": {
|
||||||
|
"validate": "erpnext.regional.india.utils.validate_tax_category"
|
||||||
|
},
|
||||||
"Sales Invoice": {
|
"Sales Invoice": {
|
||||||
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"],
|
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"],
|
||||||
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
|
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
|
||||||
@@ -357,7 +360,8 @@ regional_overrides = {
|
|||||||
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
|
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
|
||||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries'
|
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
||||||
|
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
|
||||||
},
|
},
|
||||||
'United Arab Emirates': {
|
'United Arab Emirates': {
|
||||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data'
|
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data'
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
approvers = []
|
approvers = []
|
||||||
department_details = {}
|
department_details = {}
|
||||||
department_list = []
|
department_list = []
|
||||||
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
|
employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver"], as_dict=True)
|
||||||
|
|
||||||
employee_department = filters.get("department") or employee.department
|
employee_department = filters.get("department") or employee.department
|
||||||
if employee_department:
|
if employee_department:
|
||||||
@@ -36,8 +36,10 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
|
|
||||||
if filters.get("doctype") == "Leave Application":
|
if filters.get("doctype") == "Leave Application":
|
||||||
parentfield = "leave_approvers"
|
parentfield = "leave_approvers"
|
||||||
else:
|
field_name = "Leave Approver"
|
||||||
|
elif filters.get("doctype") == "Expense Claim":
|
||||||
parentfield = "expense_approvers"
|
parentfield = "expense_approvers"
|
||||||
|
field_name = "Expense Approver"
|
||||||
if department_list:
|
if department_list:
|
||||||
for d in department_list:
|
for d in department_list:
|
||||||
approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
|
approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
|
||||||
@@ -47,4 +49,10 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
and approver.parentfield = %s
|
and approver.parentfield = %s
|
||||||
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
|
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
|
||||||
|
|
||||||
|
if len(approvers) == 0:
|
||||||
|
error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name))
|
||||||
|
if department_list:
|
||||||
|
error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
|
||||||
|
frappe.throw(error_msg, title=_(field_name + " Missing"))
|
||||||
|
|
||||||
return set(tuple(approver) for approver in approvers)
|
return set(tuple(approver) for approver in approvers)
|
||||||
|
|||||||
@@ -302,7 +302,9 @@ class PayrollEntry(Document):
|
|||||||
jv_name = journal_entry.name
|
jv_name = journal_entry.name
|
||||||
self.update_salary_slip_status(jv_name = jv_name)
|
self.update_salary_slip_status(jv_name = jv_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.msgprint(e)
|
if type(e) in (str, list, tuple):
|
||||||
|
frappe.msgprint(e)
|
||||||
|
raise
|
||||||
|
|
||||||
return jv_name
|
return jv_name
|
||||||
|
|
||||||
@@ -379,9 +381,13 @@ class PayrollEntry(Document):
|
|||||||
employees_to_mark_attendance = []
|
employees_to_mark_attendance = []
|
||||||
days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
|
days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
|
||||||
for employee_detail in self.employees:
|
for employee_detail in self.employees:
|
||||||
days_holiday = self.get_count_holidays_of_employee(employee_detail.employee)
|
employee_joining_date = frappe.db.get_value("Employee", employee_detail.employee, 'date_of_joining')
|
||||||
days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee)
|
start_date = self.start_date
|
||||||
days_in_payroll = date_diff(self.end_date, self.start_date) + 1
|
if employee_joining_date > getdate(self.start_date):
|
||||||
|
start_date = employee_joining_date
|
||||||
|
days_holiday = self.get_count_holidays_of_employee(employee_detail.employee, start_date)
|
||||||
|
days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee, start_date)
|
||||||
|
days_in_payroll = date_diff(self.end_date, start_date) + 1
|
||||||
if days_in_payroll > days_holiday + days_attendance_marked:
|
if days_in_payroll > days_holiday + days_attendance_marked:
|
||||||
employees_to_mark_attendance.append({
|
employees_to_mark_attendance.append({
|
||||||
"employee": employee_detail.employee,
|
"employee": employee_detail.employee,
|
||||||
@@ -389,22 +395,25 @@ class PayrollEntry(Document):
|
|||||||
})
|
})
|
||||||
return employees_to_mark_attendance
|
return employees_to_mark_attendance
|
||||||
|
|
||||||
def get_count_holidays_of_employee(self, employee):
|
def get_count_holidays_of_employee(self, employee, start_date):
|
||||||
holiday_list = get_holiday_list_for_employee(employee)
|
holiday_list = get_holiday_list_for_employee(employee)
|
||||||
holidays = 0
|
holidays = 0
|
||||||
if holiday_list:
|
if holiday_list:
|
||||||
days = frappe.db.sql("""select count(*) from tabHoliday where
|
days = frappe.db.sql("""select count(*) from tabHoliday where
|
||||||
parent=%s and holiday_date between %s and %s""", (holiday_list,
|
parent=%s and holiday_date between %s and %s""", (holiday_list,
|
||||||
self.start_date, self.end_date))
|
start_date, self.end_date))
|
||||||
if days and days[0][0]:
|
if days and days[0][0]:
|
||||||
holidays = days[0][0]
|
holidays = days[0][0]
|
||||||
return holidays
|
return holidays
|
||||||
|
|
||||||
def get_count_employee_attendance(self, employee):
|
def get_count_employee_attendance(self, employee, start_date):
|
||||||
marked_days = 0
|
marked_days = 0
|
||||||
attendances = frappe.db.sql("""select count(*) from tabAttendance where
|
attendances = frappe.get_all("Attendance",
|
||||||
employee=%s and docstatus=1 and attendance_date between %s and %s""",
|
fields = ["count(*)"],
|
||||||
(employee, self.start_date, self.end_date))
|
filters = {
|
||||||
|
"employee": employee,
|
||||||
|
"attendance_date": ('between', [start_date, self.end_date])
|
||||||
|
}, as_list=1)
|
||||||
if attendances and attendances[0][0]:
|
if attendances and attendances[0][0]:
|
||||||
marked_days = attendances[0][0]
|
marked_days = attendances[0][0]
|
||||||
return marked_days
|
return marked_days
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from frappe.model.mapper import get_mapped_doc
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class OperationMismatchError(frappe.ValidationError): pass
|
class OperationMismatchError(frappe.ValidationError): pass
|
||||||
|
class JobCardCancelError(frappe.ValidationError): pass
|
||||||
|
|
||||||
class JobCard(Document):
|
class JobCard(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -110,39 +111,54 @@ class JobCard(Document):
|
|||||||
for_quantity, time_in_mins = 0, 0
|
for_quantity, time_in_mins = 0, 0
|
||||||
from_time_list, to_time_list = [], []
|
from_time_list, to_time_list = [], []
|
||||||
|
|
||||||
field = "operation_id"
|
|
||||||
data = frappe.get_all('Job Card',
|
data = frappe.get_all('Job Card',
|
||||||
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
|
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
|
||||||
filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)})
|
filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id})
|
||||||
|
|
||||||
if data and len(data) > 0:
|
if data and len(data) > 0:
|
||||||
for_quantity = data[0].completed_qty
|
for_quantity = flt(data[0].completed_qty)
|
||||||
time_in_mins = data[0].time_in_mins
|
time_in_mins = flt(data[0].time_in_mins)
|
||||||
|
|
||||||
if self.get(field):
|
wo = frappe.get_doc('Work Order', self.work_order)
|
||||||
time_data = frappe.db.sql("""
|
if self.operation_id:
|
||||||
|
self.validate_produced_quantity(for_quantity, wo)
|
||||||
|
self.update_work_order_data(for_quantity, time_in_mins, wo)
|
||||||
|
|
||||||
|
def validate_produced_quantity(self, for_quantity, wo):
|
||||||
|
if self.docstatus < 2: return
|
||||||
|
|
||||||
|
if wo.produced_qty > for_quantity:
|
||||||
|
first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
|
||||||
|
.format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
|
||||||
|
|
||||||
|
second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
|
||||||
|
.format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
|
||||||
|
|
||||||
|
frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
|
||||||
|
JobCardCancelError, title = _("Error"))
|
||||||
|
|
||||||
|
def update_work_order_data(self, for_quantity, time_in_mins, wo):
|
||||||
|
time_data = frappe.db.sql("""
|
||||||
SELECT
|
SELECT
|
||||||
min(from_time) as start_time, max(to_time) as end_time
|
min(from_time) as start_time, max(to_time) as end_time
|
||||||
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
|
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
|
||||||
WHERE
|
WHERE
|
||||||
jctl.parent = jc.name and jc.work_order = %s
|
jctl.parent = jc.name and jc.work_order = %s
|
||||||
and jc.{0} = %s and jc.docstatus = 1
|
and jc.operation_id = %s and jc.docstatus = 1
|
||||||
""".format(field), (self.work_order, self.get(field)), as_dict=1)
|
""", (self.work_order, self.operation_id), as_dict=1)
|
||||||
|
|
||||||
wo = frappe.get_doc('Work Order', self.work_order)
|
for data in wo.operations:
|
||||||
|
if data.get("name") == self.operation_id:
|
||||||
|
data.completed_qty = for_quantity
|
||||||
|
data.actual_operation_time = time_in_mins
|
||||||
|
data.actual_start_time = time_data[0].start_time if time_data else None
|
||||||
|
data.actual_end_time = time_data[0].end_time if time_data else None
|
||||||
|
|
||||||
for data in wo.operations:
|
wo.flags.ignore_validate_update_after_submit = True
|
||||||
if data.get("name") == self.get(field):
|
wo.update_operation_status()
|
||||||
data.completed_qty = for_quantity
|
wo.calculate_operating_cost()
|
||||||
data.actual_operation_time = time_in_mins
|
wo.set_actual_dates()
|
||||||
data.actual_start_time = time_data[0].start_time if time_data else None
|
wo.save()
|
||||||
data.actual_end_time = time_data[0].end_time if time_data else None
|
|
||||||
|
|
||||||
wo.flags.ignore_validate_update_after_submit = True
|
|
||||||
wo.update_operation_status()
|
|
||||||
wo.calculate_operating_cost()
|
|
||||||
wo.set_actual_dates()
|
|
||||||
wo.save()
|
|
||||||
|
|
||||||
def set_transferred_qty(self, update_status=False):
|
def set_transferred_qty(self, update_status=False):
|
||||||
if not self.items:
|
if not self.items:
|
||||||
@@ -224,17 +240,19 @@ def get_operation_details(work_order, operation):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_operations(doctype, txt, searchfield, start, page_len, filters):
|
def get_operations(doctype, txt, searchfield, start, page_len, filters):
|
||||||
if filters.get("work_order"):
|
if not filters.get("work_order"):
|
||||||
args = {"parent": filters.get("work_order")}
|
frappe.msgprint(_("Please select a Work Order first."))
|
||||||
if txt:
|
return []
|
||||||
args["operation"] = ("like", "%{0}%".format(txt))
|
args = {"parent": filters.get("work_order")}
|
||||||
|
if txt:
|
||||||
|
args["operation"] = ("like", "%{0}%".format(txt))
|
||||||
|
|
||||||
return frappe.get_all("Work Order Operation",
|
return frappe.get_all("Work Order Operation",
|
||||||
filters = args,
|
filters = args,
|
||||||
fields = ["distinct operation as operation"],
|
fields = ["distinct operation as operation"],
|
||||||
limit_start = start,
|
limit_start = start,
|
||||||
limit_page_length = page_len,
|
limit_page_length = page_len,
|
||||||
order_by="idx asc", as_list=1)
|
order_by="idx asc", as_list=1)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_material_request(source_name, target_doc=None):
|
def make_material_request(source_name, target_doc=None):
|
||||||
|
|||||||
@@ -5,16 +5,17 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import unittest
|
import unittest
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import flt, time_diff_in_hours, now, add_days, cint
|
from frappe.utils import flt, now, cint, add_to_date
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||||
from erpnext.manufacturing.doctype.work_order.work_order \
|
|
||||||
import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError
|
|
||||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||||
from erpnext.stock.utils import get_bin
|
from erpnext.stock.utils import get_bin
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
|
||||||
|
ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError)
|
||||||
|
from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
|
||||||
|
|
||||||
class TestWorkOrder(unittest.TestCase):
|
class TestWorkOrder(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -319,6 +320,29 @@ class TestWorkOrder(unittest.TestCase):
|
|||||||
|
|
||||||
allow_overproduction("overproduction_percentage_for_work_order", 0)
|
allow_overproduction("overproduction_percentage_for_work_order", 0)
|
||||||
|
|
||||||
|
def test_finished_good_valuation_rate(self):
|
||||||
|
allow_overproduction("overproduction_percentage_for_work_order", 0)
|
||||||
|
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
|
||||||
|
test_stock_entry.make_stock_entry(item_code="_Test Item",
|
||||||
|
target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0)
|
||||||
|
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
|
||||||
|
target="_Test Warehouse - _TC", qty=10, basic_rate=1000.0)
|
||||||
|
|
||||||
|
ste_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 2))
|
||||||
|
ste_doc.submit()
|
||||||
|
|
||||||
|
ste_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
|
||||||
|
ste_doc.save()
|
||||||
|
|
||||||
|
self.assertEquals(ste_doc.total_incoming_value, ste_doc.total_outgoing_value)
|
||||||
|
|
||||||
|
for row in ste_doc.items:
|
||||||
|
if row.t_warehouse and not row.s_warehouse:
|
||||||
|
row.valuation_rate = 120
|
||||||
|
ste_doc.save()
|
||||||
|
|
||||||
|
self.assertEquals(ste_doc.total_incoming_value, ste_doc.total_outgoing_value)
|
||||||
|
|
||||||
def test_over_production_for_sales_order(self):
|
def test_over_production_for_sales_order(self):
|
||||||
so = make_sales_order(item_code="_Test FG Item", qty=2)
|
so = make_sales_order(item_code="_Test FG Item", qty=2)
|
||||||
|
|
||||||
@@ -374,14 +398,41 @@ class TestWorkOrder(unittest.TestCase):
|
|||||||
data = frappe.get_cached_value('BOM',
|
data = frappe.get_cached_value('BOM',
|
||||||
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
|
||||||
|
|
||||||
if data:
|
bom, bom_item = data
|
||||||
bom, bom_item = data
|
|
||||||
|
|
||||||
bom_doc = frappe.get_doc('BOM', bom)
|
bom_doc = frappe.get_doc('BOM', bom)
|
||||||
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
|
work_order = make_wo_order_test_record(item=bom_item, qty=1,
|
||||||
|
bom_no=bom, source_warehouse="_Test Warehouse - _TC")
|
||||||
|
|
||||||
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
|
for row in work_order.required_items:
|
||||||
self.assertEqual(len(job_cards), len(bom_doc.operations))
|
test_stock_entry.make_stock_entry(item_code=row.item_code,
|
||||||
|
target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
|
||||||
|
|
||||||
|
ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
|
||||||
|
ste.submit()
|
||||||
|
|
||||||
|
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
|
||||||
|
self.assertEqual(len(job_cards), len(bom_doc.operations))
|
||||||
|
|
||||||
|
for i, job_card in enumerate(job_cards):
|
||||||
|
doc = frappe.get_doc("Job Card", job_card)
|
||||||
|
doc.append("time_logs", {
|
||||||
|
"from_time": now(),
|
||||||
|
"hours": i,
|
||||||
|
"to_time": add_to_date(now(), i),
|
||||||
|
"completed_qty": doc.for_quantity
|
||||||
|
})
|
||||||
|
doc.submit()
|
||||||
|
|
||||||
|
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
|
||||||
|
ste1.submit()
|
||||||
|
|
||||||
|
for job_card in job_cards:
|
||||||
|
doc = frappe.get_doc("Job Card", job_card)
|
||||||
|
self.assertRaises(JobCardCancelError, doc.cancel)
|
||||||
|
|
||||||
|
ste1.cancel()
|
||||||
|
ste.cancel()
|
||||||
|
|
||||||
def test_work_order_with_non_transfer_item(self):
|
def test_work_order_with_non_transfer_item(self):
|
||||||
items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
|
items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
|
||||||
@@ -455,6 +506,39 @@ class TestWorkOrder(unittest.TestCase):
|
|||||||
work_order1.save()
|
work_order1.save()
|
||||||
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
|
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
|
||||||
|
|
||||||
|
def test_partial_material_consumption(self):
|
||||||
|
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
|
||||||
|
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
|
||||||
|
|
||||||
|
ste_cancel_list = []
|
||||||
|
ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
|
||||||
|
target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
|
||||||
|
ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
|
||||||
|
target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
|
||||||
|
|
||||||
|
ste_cancel_list.extend([ste1, ste2])
|
||||||
|
|
||||||
|
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
|
||||||
|
s.submit()
|
||||||
|
ste_cancel_list.append(s)
|
||||||
|
|
||||||
|
ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
|
||||||
|
ste1.submit()
|
||||||
|
ste_cancel_list.append(ste1)
|
||||||
|
|
||||||
|
print(wo_order.name)
|
||||||
|
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
|
||||||
|
self.assertEquals(ste3.fg_completed_qty, 2)
|
||||||
|
|
||||||
|
expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
|
||||||
|
for row in ste3.items:
|
||||||
|
self.assertEquals(row.qty, expected_qty.get(row.item_code))
|
||||||
|
|
||||||
|
for ste_doc in ste_cancel_list:
|
||||||
|
ste_doc.cancel()
|
||||||
|
|
||||||
|
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
|
||||||
|
|
||||||
def get_scrap_item_details(bom_no):
|
def get_scrap_item_details(bom_no):
|
||||||
scrap_items = {}
|
scrap_items = {}
|
||||||
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
|
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
|
||||||
|
|||||||
@@ -521,7 +521,8 @@ erpnext.work_order = {
|
|||||||
var tbl = frm.doc.required_items || [];
|
var tbl = frm.doc.required_items || [];
|
||||||
var tbl_lenght = tbl.length;
|
var tbl_lenght = tbl.length;
|
||||||
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
||||||
if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
|
||||||
|
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ class WorkOrder(Document):
|
|||||||
and (entry.purpose = "Material Consumption for Manufacture"
|
and (entry.purpose = "Material Consumption for Manufacture"
|
||||||
or entry.purpose = "Manufacture")
|
or entry.purpose = "Manufacture")
|
||||||
and entry.docstatus = 1
|
and entry.docstatus = 1
|
||||||
and detail.parent = entry.name
|
and detail.parent = entry.name and IFNULL(t_warehouse, "") = ""
|
||||||
and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', {
|
and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', {
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'item': d.item_code
|
'item': d.item_code
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ frappe.query_reports["BOM Stock Report"] = {
|
|||||||
],
|
],
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
if (column.id == "Item"){
|
if (column.id == "item") {
|
||||||
if (data["Enough Parts to Build"] > 0){
|
if (data["enough_parts_to_build"] > 0) {
|
||||||
value = `<a style='color:green' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
|
value = `<a style='color:green' href="#Form/Item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
|
||||||
} else {
|
} else {
|
||||||
value = `<a style='color:red' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
|
value = `<a style='color:red' href="#Form/Item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -678,3 +678,4 @@ erpnext.patches.v12_0.update_state_code_for_daman_and_diu
|
|||||||
erpnext.patches.v12_0.rename_lost_reason_detail
|
erpnext.patches.v12_0.rename_lost_reason_detail
|
||||||
erpnext.patches.v12_0.update_leave_application_status
|
erpnext.patches.v12_0.update_leave_application_status
|
||||||
erpnext.patches.v12_0.update_payment_entry_status
|
erpnext.patches.v12_0.update_payment_entry_status
|
||||||
|
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
|
||||||
|
|||||||
48
erpnext/patches/v12_0/setup_einvoice_fields.py
Normal file
48
erpnext/patches/v12_0/setup_einvoice_fields.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
|
from erpnext.regional.india.setup import add_permissions, add_print_formats
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||||
|
if not company:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.reload_doc("regional", "doctype", "e_invoice_settings")
|
||||||
|
custom_fields = {
|
||||||
|
'Sales Invoice': [
|
||||||
|
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='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
|
||||||
|
|
||||||
|
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=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'),
|
||||||
|
|
||||||
|
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)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
create_custom_fields(custom_fields, update=True)
|
||||||
|
add_permissions()
|
||||||
|
add_print_formats()
|
||||||
|
|
||||||
|
t = {
|
||||||
|
'mode_of_transport': [{'default': None}],
|
||||||
|
'ewaybill': [
|
||||||
|
{'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
for field, conditions in t.items():
|
||||||
|
for c in conditions:
|
||||||
|
[(prop, value)] = c.items()
|
||||||
|
frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)
|
||||||
@@ -2,6 +2,7 @@ import frappe
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
|
||||||
|
from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import get_shopping_cart_settings
|
||||||
|
|
||||||
def get_field_filter_data():
|
def get_field_filter_data():
|
||||||
product_settings = get_product_settings()
|
product_settings = get_product_settings()
|
||||||
@@ -249,6 +250,8 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
|||||||
|
|
||||||
optional_attributes = item_cache.get_optional_attributes()
|
optional_attributes = item_cache.get_optional_attributes()
|
||||||
exact_match = []
|
exact_match = []
|
||||||
|
shopping_cart_settings = get_shopping_cart_settings()
|
||||||
|
allow_items_not_in_stock = cint(shopping_cart_settings.allow_items_not_in_stock)
|
||||||
# search for exact match if all selected attributes are required attributes
|
# search for exact match if all selected attributes are required attributes
|
||||||
if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)):
|
if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)):
|
||||||
item_attribute_value_map = item_cache.get_item_attribute_value_map()
|
item_attribute_value_map = item_cache.get_item_attribute_value_map()
|
||||||
@@ -263,7 +266,6 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
|||||||
if exact_match:
|
if exact_match:
|
||||||
data = get_product_info_for_website(exact_match[0])
|
data = get_product_info_for_website(exact_match[0])
|
||||||
product_info = data.product_info
|
product_info = data.product_info
|
||||||
product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
|
|
||||||
if not data.cart_settings.show_price:
|
if not data.cart_settings.show_price:
|
||||||
product_info = None
|
product_info = None
|
||||||
else:
|
else:
|
||||||
@@ -275,6 +277,7 @@ def get_next_attribute_and_values(item_code, selected_attributes):
|
|||||||
'filtered_items_count': filtered_items_count,
|
'filtered_items_count': filtered_items_count,
|
||||||
'filtered_items': filtered_items if filtered_items_count < 10 else [],
|
'filtered_items': filtered_items if filtered_items_count < 10 else [],
|
||||||
'exact_match': exact_match,
|
'exact_match': exact_match,
|
||||||
|
'allow_items_not_in_stock': allow_items_not_in_stock,
|
||||||
'product_info': product_info
|
'product_info': product_info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('E Invoice Request Log', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "EINV-REQ-.#####",
|
||||||
|
"creation": "2020-12-08 12:54:08.175992",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"user",
|
||||||
|
"url",
|
||||||
|
"headers",
|
||||||
|
"response",
|
||||||
|
"column_break_7",
|
||||||
|
"timestamp",
|
||||||
|
"reference_invoice",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "user",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "User",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_invoice",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Reference Invoice",
|
||||||
|
"options": "Sales Invoice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "headers",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Headers",
|
||||||
|
"options": "JSON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "data",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Data",
|
||||||
|
"options": "JSON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Now",
|
||||||
|
"fieldname": "timestamp",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Timestamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "response",
|
||||||
|
"fieldtype": "Code",
|
||||||
|
"label": "Response",
|
||||||
|
"options": "JSON"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "url",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "URL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_7",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-24 21:09:38.882866",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "E Invoice Request Log",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "System Manager",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts Manager",
|
||||||
|
"share": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class EInvoiceRequestLog(Document):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestEInvoiceRequestLog(unittest.TestCase):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('E Invoice Settings', {
|
||||||
|
refresh(frm) {
|
||||||
|
const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
|
||||||
|
frm.dashboard.set_headline(
|
||||||
|
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-09-24 16:23:16.235722",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enable",
|
||||||
|
"section_break_2",
|
||||||
|
"credentials",
|
||||||
|
"auth_token",
|
||||||
|
"token_expiry"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enable",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "enable",
|
||||||
|
"fieldname": "section_break_2",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "auth_token",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "token_expiry",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"hidden": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "credentials",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Credentials",
|
||||||
|
"mandatory_depends_on": "enable",
|
||||||
|
"options": "E Invoice User"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-22 15:34:57.280044",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "E Invoice Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class EInvoiceSettings(Document):
|
||||||
|
def validate(self):
|
||||||
|
if self.enable and not self.credentials:
|
||||||
|
frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.'))
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestEInvoiceSettings(unittest.TestCase):
|
||||||
|
pass
|
||||||
0
erpnext/regional/doctype/e_invoice_user/__init__.py
Normal file
0
erpnext/regional/doctype/e_invoice_user/__init__.py
Normal file
48
erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
Normal file
48
erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-12-22 15:02:46.229474",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"gstin",
|
||||||
|
"username",
|
||||||
|
"password"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "gstin",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "GSTIN",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "username",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Username",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "password",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Password",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-22 15:10:53.466205",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "E Invoice User",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
10
erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
Normal file
10
erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class EInvoiceUser(Document):
|
||||||
|
pass
|
||||||
@@ -192,19 +192,20 @@ class GSTR3BReport(Document):
|
|||||||
for d in self.report_dict["itc_elg"]["itc_avl"]:
|
for d in self.report_dict["itc_elg"]["itc_avl"]:
|
||||||
|
|
||||||
itc_type = itc_type_map.get(d["ty"])
|
itc_type = itc_type_map.get(d["ty"])
|
||||||
gst_category = ["Registered Regular"]
|
|
||||||
|
|
||||||
if d["ty"] == 'ISRC':
|
if d["ty"] == 'ISRC':
|
||||||
reverse_charge = "Y"
|
reverse_charge = ["Y"]
|
||||||
itc_type = 'All Other ITC'
|
itc_type = 'All Other ITC'
|
||||||
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
|
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
|
||||||
else:
|
else:
|
||||||
reverse_charge = "N"
|
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
|
||||||
|
reverse_charge = ["N", "Y"]
|
||||||
|
|
||||||
for account_head in self.account_heads:
|
for account_head in self.account_heads:
|
||||||
for category in gst_category:
|
for category in gst_category:
|
||||||
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
|
for charge_type in reverse_charge:
|
||||||
d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
|
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
|
||||||
|
d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
|
||||||
|
|
||||||
for key in ['iamt', 'camt', 'samt', 'csamt']:
|
for key in ['iamt', 'camt', 'samt', 'csamt']:
|
||||||
net_itc[key] += flt(d[key], 2)
|
net_itc[key] += flt(d[key], 2)
|
||||||
@@ -264,7 +265,8 @@ class GSTR3BReport(Document):
|
|||||||
|
|
||||||
def get_itc_details(self):
|
def get_itc_details(self):
|
||||||
itc_amount = frappe.db.sql("""
|
itc_amount = frappe.db.sql("""
|
||||||
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge
|
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
|
||||||
|
t.account_head, s.eligibility_for_itc, s.reverse_charge
|
||||||
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
|
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
|
||||||
where s.docstatus = 1 and t.parent = s.name
|
where s.docstatus = 1 and t.parent = s.name
|
||||||
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
||||||
@@ -388,7 +390,7 @@ class GSTR3BReport(Document):
|
|||||||
tax_template = 'Purchase Taxes and Charges'
|
tax_template = 'Purchase Taxes and Charges'
|
||||||
|
|
||||||
tax_amounts = frappe.db.sql("""
|
tax_amounts = frappe.db.sql("""
|
||||||
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head
|
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
|
||||||
from `tab{doctype}` s , `tab{template}` t
|
from `tab{doctype}` s , `tab{template}` t
|
||||||
where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
|
where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
|
||||||
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
||||||
|
|||||||
0
erpnext/regional/india/e_invoice/__init__.py
Normal file
0
erpnext/regional/india/e_invoice/__init__.py
Normal file
31
erpnext/regional/india/e_invoice/einv_item_template.json
Normal file
31
erpnext/regional/india/e_invoice/einv_item_template.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{{
|
||||||
|
"SlNo": "{item.sr_no}",
|
||||||
|
"PrdDesc": "{item.description}",
|
||||||
|
"IsServc": "{item.is_service_item}",
|
||||||
|
"HsnCd": "{item.gst_hsn_code}",
|
||||||
|
"Barcde": "{item.barcode}",
|
||||||
|
"Unit": "{item.uom}",
|
||||||
|
"Qty": "{item.qty}",
|
||||||
|
"FreeQty": "{item.free_qty}",
|
||||||
|
"UnitPrice": "{item.unit_rate}",
|
||||||
|
"TotAmt": "{item.gross_amount}",
|
||||||
|
"Discount": "{item.discount_amount}",
|
||||||
|
"AssAmt": "{item.taxable_value}",
|
||||||
|
"PrdSlNo": "{item.serial_no}",
|
||||||
|
"GstRt": "{item.tax_rate}",
|
||||||
|
"IgstAmt": "{item.igst_amount}",
|
||||||
|
"CgstAmt": "{item.cgst_amount}",
|
||||||
|
"SgstAmt": "{item.sgst_amount}",
|
||||||
|
"CesRt": "{item.cess_rate}",
|
||||||
|
"CesAmt": "{item.cess_amount}",
|
||||||
|
"CesNonAdvlAmt": "{item.cess_nadv_amount}",
|
||||||
|
"StateCesRt": "{item.state_cess_rate}",
|
||||||
|
"StateCesAmt": "{item.state_cess_amount}",
|
||||||
|
"StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}",
|
||||||
|
"OthChrg": "{item.other_charges}",
|
||||||
|
"TotItemVal": "{item.total_value}",
|
||||||
|
"BchDtls": {{
|
||||||
|
"Nm": "{item.batch_no}",
|
||||||
|
"ExpDt": "{item.batch_expiry_date}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
110
erpnext/regional/india/e_invoice/einv_template.json
Normal file
110
erpnext/regional/india/e_invoice/einv_template.json
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
{{
|
||||||
|
"Version": "1.1",
|
||||||
|
"TranDtls": {{
|
||||||
|
"TaxSch": "{transaction_details.tax_scheme}",
|
||||||
|
"SupTyp": "{transaction_details.supply_type}",
|
||||||
|
"RegRev": "{transaction_details.reverse_charge}",
|
||||||
|
"EcmGstin": "{transaction_details.ecom_gstin}",
|
||||||
|
"IgstOnIntra": "{transaction_details.igst_on_intra}"
|
||||||
|
}},
|
||||||
|
"DocDtls": {{
|
||||||
|
"Typ": "{doc_details.invoice_type}",
|
||||||
|
"No": "{doc_details.invoice_name}",
|
||||||
|
"Dt": "{doc_details.invoice_date}"
|
||||||
|
}},
|
||||||
|
"SellerDtls": {{
|
||||||
|
"Gstin": "{seller_details.gstin}",
|
||||||
|
"LglNm": "{seller_details.legal_name}",
|
||||||
|
"TrdNm": "{seller_details.trade_name}",
|
||||||
|
"Loc": "{seller_details.location}",
|
||||||
|
"Pin": "{seller_details.pincode}",
|
||||||
|
"Stcd": "{seller_details.state_code}",
|
||||||
|
"Addr1": "{seller_details.address_line1}",
|
||||||
|
"Addr2": "{seller_details.address_line2}",
|
||||||
|
"Ph": "{seller_details.phone}",
|
||||||
|
"Em": "{seller_details.email}"
|
||||||
|
}},
|
||||||
|
"BuyerDtls": {{
|
||||||
|
"Gstin": "{buyer_details.gstin}",
|
||||||
|
"LglNm": "{buyer_details.legal_name}",
|
||||||
|
"TrdNm": "{buyer_details.trade_name}",
|
||||||
|
"Addr1": "{buyer_details.address_line1}",
|
||||||
|
"Addr2": "{buyer_details.address_line2}",
|
||||||
|
"Loc": "{buyer_details.location}",
|
||||||
|
"Pin": "{buyer_details.pincode}",
|
||||||
|
"Stcd": "{buyer_details.state_code}",
|
||||||
|
"Ph": "{buyer_details.phone}",
|
||||||
|
"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}
|
||||||
|
],
|
||||||
|
"ValDtls": {{
|
||||||
|
"AssVal": "{invoice_value_details.base_net_total}",
|
||||||
|
"CgstVal": "{invoice_value_details.total_cgst_amt}",
|
||||||
|
"SgstVal": "{invoice_value_details.total_sgst_amt}",
|
||||||
|
"IgstVal": "{invoice_value_details.total_igst_amt}",
|
||||||
|
"CesVal": "{invoice_value_details.total_cess_amt}",
|
||||||
|
"Discount": "{invoice_value_details.invoice_discount_amt}",
|
||||||
|
"RndOffAmt": "{invoice_value_details.round_off}",
|
||||||
|
"OthChrg": "{invoice_value_details.total_other_charges}",
|
||||||
|
"TotInvVal": "{invoice_value_details.base_grand_total}",
|
||||||
|
"TotInvValFc": "{invoice_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}"
|
||||||
|
}}
|
||||||
|
}}
|
||||||
956
erpnext/regional/india/e_invoice/einv_validation.json
Normal file
956
erpnext/regional/india/e_invoice/einv_validation.json
Normal file
@@ -0,0 +1,956 @@
|
|||||||
|
{
|
||||||
|
"Version": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 6,
|
||||||
|
"description": "Version of the schema"
|
||||||
|
},
|
||||||
|
"Irn": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 64,
|
||||||
|
"maxLength": 64,
|
||||||
|
"description": "Invoice Reference Number"
|
||||||
|
},
|
||||||
|
"TranDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"TaxSch": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 10,
|
||||||
|
"enum": ["GST"],
|
||||||
|
"description": "GST- Goods and Services Tax Scheme"
|
||||||
|
},
|
||||||
|
"SupTyp": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 10,
|
||||||
|
"enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"],
|
||||||
|
"description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export"
|
||||||
|
},
|
||||||
|
"RegRev": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 1,
|
||||||
|
"enum": ["Y", "N"],
|
||||||
|
"description": "Y- whether the tax liability is payable under reverse charge"
|
||||||
|
},
|
||||||
|
"EcmGstin": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 15,
|
||||||
|
"maxLength": 15,
|
||||||
|
"pattern": "([0-9]{2}[0-9A-Z]{13})",
|
||||||
|
"description": "E-Commerce GSTIN",
|
||||||
|
"validationMsg": "E-Commerce GSTIN is invalid"
|
||||||
|
},
|
||||||
|
"IgstOnIntra": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 1,
|
||||||
|
"enum": ["Y", "N"],
|
||||||
|
"description": "Y- indicates the supply is intra state but chargeable to IGST"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["TaxSch", "SupTyp"]
|
||||||
|
},
|
||||||
|
"DocDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Typ": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 3,
|
||||||
|
"enum": ["INV", "CRN", "DBN"],
|
||||||
|
"description": "Document Type"
|
||||||
|
},
|
||||||
|
"No": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 16,
|
||||||
|
"pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$",
|
||||||
|
"description": "Document Number",
|
||||||
|
"validationMsg": "Document Number should not be starting with 0, / and -"
|
||||||
|
},
|
||||||
|
"Dt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 10,
|
||||||
|
"maxLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Document Date"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["Typ", "No", "Dt"]
|
||||||
|
},
|
||||||
|
"SellerDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Gstin": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 15,
|
||||||
|
"maxLength": 15,
|
||||||
|
"pattern": "([0-9]{2}[0-9A-Z]{13})",
|
||||||
|
"description": "Supplier GSTIN",
|
||||||
|
"validationMsg": "Company GSTIN is invalid"
|
||||||
|
},
|
||||||
|
"LglNm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Legal Name"
|
||||||
|
},
|
||||||
|
"TrdNm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Tradename"
|
||||||
|
},
|
||||||
|
"Addr1": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 1"
|
||||||
|
},
|
||||||
|
"Addr2": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 2"
|
||||||
|
},
|
||||||
|
"Loc": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 50,
|
||||||
|
"description": "Location"
|
||||||
|
},
|
||||||
|
"Pin": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 100000,
|
||||||
|
"maximum": 999999,
|
||||||
|
"description": "Pincode"
|
||||||
|
},
|
||||||
|
"Stcd": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 2,
|
||||||
|
"description": "Supplier State Code"
|
||||||
|
},
|
||||||
|
"Ph": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 6,
|
||||||
|
"maxLength": 12,
|
||||||
|
"description": "Phone"
|
||||||
|
},
|
||||||
|
"Em": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 6,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Email-Id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||||
|
},
|
||||||
|
"BuyerDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Gstin": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 15,
|
||||||
|
"pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
|
||||||
|
"description": "Buyer GSTIN",
|
||||||
|
"validationMsg": "Customer GSTIN is invalid"
|
||||||
|
},
|
||||||
|
"LglNm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Legal Name"
|
||||||
|
},
|
||||||
|
"TrdNm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Trade Name"
|
||||||
|
},
|
||||||
|
"Pos": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 2,
|
||||||
|
"description": "Place of Supply State code"
|
||||||
|
},
|
||||||
|
"Addr1": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 1"
|
||||||
|
},
|
||||||
|
"Addr2": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 2"
|
||||||
|
},
|
||||||
|
"Loc": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Location"
|
||||||
|
},
|
||||||
|
"Pin": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 100000,
|
||||||
|
"maximum": 999999,
|
||||||
|
"description": "Pincode"
|
||||||
|
},
|
||||||
|
"Stcd": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 2,
|
||||||
|
"description": "Buyer State Code"
|
||||||
|
},
|
||||||
|
"Ph": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 6,
|
||||||
|
"maxLength": 12,
|
||||||
|
"description": "Phone"
|
||||||
|
},
|
||||||
|
"Em": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 6,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Email-Id"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"]
|
||||||
|
},
|
||||||
|
"DispDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Nm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Dispatch Address Name"
|
||||||
|
},
|
||||||
|
"Addr1": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 1"
|
||||||
|
},
|
||||||
|
"Addr2": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 2"
|
||||||
|
},
|
||||||
|
"Loc": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Location"
|
||||||
|
},
|
||||||
|
"Pin": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 100000,
|
||||||
|
"maximum": 999999,
|
||||||
|
"description": "Pincode"
|
||||||
|
},
|
||||||
|
"Stcd": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 2,
|
||||||
|
"description": "State Code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||||
|
},
|
||||||
|
"ShipDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Gstin": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 15,
|
||||||
|
"minLength": 3,
|
||||||
|
"pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
|
||||||
|
"description": "Shipping Address GSTIN",
|
||||||
|
"validationMsg": "Shipping Address GSTIN is invalid"
|
||||||
|
},
|
||||||
|
"LglNm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Legal Name"
|
||||||
|
},
|
||||||
|
"TrdNm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Trade Name"
|
||||||
|
},
|
||||||
|
"Addr1": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 1"
|
||||||
|
},
|
||||||
|
"Addr2": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Address Line 2"
|
||||||
|
},
|
||||||
|
"Loc": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Location"
|
||||||
|
},
|
||||||
|
"Pin": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 100000,
|
||||||
|
"maximum": 999999,
|
||||||
|
"description": "Pincode"
|
||||||
|
},
|
||||||
|
"Stcd": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 2,
|
||||||
|
"description": "State Code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||||
|
},
|
||||||
|
"ItemList": {
|
||||||
|
"type": "Array",
|
||||||
|
"properties": {
|
||||||
|
"SlNo": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 6,
|
||||||
|
"description": "Serial No. of Item"
|
||||||
|
},
|
||||||
|
"PrdDesc": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 300,
|
||||||
|
"description": "Item Name"
|
||||||
|
},
|
||||||
|
"IsServc": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 1,
|
||||||
|
"enum": ["Y", "N"],
|
||||||
|
"description": "Is Service Item"
|
||||||
|
},
|
||||||
|
"HsnCd": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 4,
|
||||||
|
"maxLength": 8,
|
||||||
|
"description": "HSN Code"
|
||||||
|
},
|
||||||
|
"Barcde": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 30,
|
||||||
|
"description": "Barcode"
|
||||||
|
},
|
||||||
|
"Qty": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 9999999999.999,
|
||||||
|
"description": "Quantity"
|
||||||
|
},
|
||||||
|
"FreeQty": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 9999999999.999,
|
||||||
|
"description": "Free Quantity"
|
||||||
|
},
|
||||||
|
"Unit": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 8,
|
||||||
|
"description": "UOM"
|
||||||
|
},
|
||||||
|
"UnitPrice": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.999,
|
||||||
|
"description": "Rate"
|
||||||
|
},
|
||||||
|
"TotAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Gross Amount"
|
||||||
|
},
|
||||||
|
"Discount": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Discount"
|
||||||
|
},
|
||||||
|
"PreTaxVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Pre tax value"
|
||||||
|
},
|
||||||
|
"AssAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Taxable Value"
|
||||||
|
},
|
||||||
|
"GstRt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999.999,
|
||||||
|
"description": "GST Rate"
|
||||||
|
},
|
||||||
|
"IgstAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "IGST Amount"
|
||||||
|
},
|
||||||
|
"CgstAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "CGST Amount"
|
||||||
|
},
|
||||||
|
"SgstAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "SGST Amount"
|
||||||
|
},
|
||||||
|
"CesRt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999.999,
|
||||||
|
"description": "Cess Rate"
|
||||||
|
},
|
||||||
|
"CesAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Cess Amount (Advalorem)"
|
||||||
|
},
|
||||||
|
"CesNonAdvlAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Cess Amount (Non-Advalorem)"
|
||||||
|
},
|
||||||
|
"StateCesRt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999.999,
|
||||||
|
"description": "State CESS Rate"
|
||||||
|
},
|
||||||
|
"StateCesAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "State CESS Amount"
|
||||||
|
},
|
||||||
|
"StateCesNonAdvlAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "State CESS Amount (Non Advalorem)"
|
||||||
|
},
|
||||||
|
"OthChrg": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Other Charges"
|
||||||
|
},
|
||||||
|
"TotItemVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Total Item Value"
|
||||||
|
},
|
||||||
|
"OrdLineRef": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 50,
|
||||||
|
"description": "Order line reference"
|
||||||
|
},
|
||||||
|
"OrgCntry": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 2,
|
||||||
|
"maxLength": 2,
|
||||||
|
"description": "Origin Country"
|
||||||
|
},
|
||||||
|
"PrdSlNo": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"description": "Serial number"
|
||||||
|
},
|
||||||
|
"BchDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Nm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 20,
|
||||||
|
"description": "Batch number"
|
||||||
|
},
|
||||||
|
"ExpDt": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 10,
|
||||||
|
"minLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Batch Expiry Date"
|
||||||
|
},
|
||||||
|
"WrDt": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 10,
|
||||||
|
"minLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Warranty Date"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["Nm"]
|
||||||
|
},
|
||||||
|
"AttribDtls": {
|
||||||
|
"type": "Array",
|
||||||
|
"Attribute": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Nm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Attribute name of the item"
|
||||||
|
},
|
||||||
|
"Val": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Attribute value of the item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"SlNo",
|
||||||
|
"IsServc",
|
||||||
|
"HsnCd",
|
||||||
|
"UnitPrice",
|
||||||
|
"TotAmt",
|
||||||
|
"AssAmt",
|
||||||
|
"GstRt",
|
||||||
|
"TotItemVal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ValDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"AssVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Total Assessable value of all items"
|
||||||
|
},
|
||||||
|
"CgstVal": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"minimum": 0,
|
||||||
|
"description": "Total CGST value of all items"
|
||||||
|
},
|
||||||
|
"SgstVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Total SGST value of all items"
|
||||||
|
},
|
||||||
|
"IgstVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Total IGST value of all items"
|
||||||
|
},
|
||||||
|
"CesVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Total CESS value of all items"
|
||||||
|
},
|
||||||
|
"StCesVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Total State CESS value of all items"
|
||||||
|
},
|
||||||
|
"Discount": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Invoice Discount"
|
||||||
|
},
|
||||||
|
"OthChrg": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Other Charges"
|
||||||
|
},
|
||||||
|
"RndOffAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": -99.99,
|
||||||
|
"maximum": 99.99,
|
||||||
|
"description": "Rounded off Amount"
|
||||||
|
},
|
||||||
|
"TotInvVal": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Final Invoice Value "
|
||||||
|
},
|
||||||
|
"TotInvValFc": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Final Invoice value in Foreign Currency"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["AssVal", "TotInvVal"]
|
||||||
|
},
|
||||||
|
"PayDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Nm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Payee Name"
|
||||||
|
},
|
||||||
|
"AccDet": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 18,
|
||||||
|
"description": "Bank Account Number of Payee"
|
||||||
|
},
|
||||||
|
"Mode": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 18,
|
||||||
|
"description": "Mode of Payment"
|
||||||
|
},
|
||||||
|
"FinInsBr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 11,
|
||||||
|
"description": "Branch or IFSC code"
|
||||||
|
},
|
||||||
|
"PayTerm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Terms of Payment"
|
||||||
|
},
|
||||||
|
"PayInstr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Payment Instruction"
|
||||||
|
},
|
||||||
|
"CrTrn": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Credit Transfer"
|
||||||
|
},
|
||||||
|
"DirDr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Direct Debit"
|
||||||
|
},
|
||||||
|
"CrDay": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 9999,
|
||||||
|
"description": "Credit Days"
|
||||||
|
},
|
||||||
|
"PaidAmt": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Advance Amount"
|
||||||
|
},
|
||||||
|
"PaymtDue": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 99999999999999.99,
|
||||||
|
"description": "Outstanding Amount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RefDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"InvRm": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 100,
|
||||||
|
"minLength": 3,
|
||||||
|
"pattern": "^[0-9A-Za-z/-]{3,100}$",
|
||||||
|
"description": "Remarks/Note"
|
||||||
|
},
|
||||||
|
"DocPerdDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"InvStDt": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 10,
|
||||||
|
"minLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Invoice Period Start Date"
|
||||||
|
},
|
||||||
|
"InvEndDt": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 10,
|
||||||
|
"minLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Invoice Period End Date"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["InvStDt ", "InvEndDt "]
|
||||||
|
},
|
||||||
|
"PrecDocDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"InvNo": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 16,
|
||||||
|
"pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$",
|
||||||
|
"description": "Reference of Original Invoice"
|
||||||
|
},
|
||||||
|
"InvDt": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 10,
|
||||||
|
"minLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Date of Orginal Invoice"
|
||||||
|
},
|
||||||
|
"OthRefNo": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"description": "Other Reference"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["InvNo", "InvDt"],
|
||||||
|
"ContrDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"RecAdvRefr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"pattern": "^([0-9A-Za-z/-]){1,20}$",
|
||||||
|
"description": "Receipt Advice No."
|
||||||
|
},
|
||||||
|
"RecAdvDt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 10,
|
||||||
|
"maxLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Date of receipt advice"
|
||||||
|
},
|
||||||
|
"TendRefr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"pattern": "^([0-9A-Za-z/-]){1,20}$",
|
||||||
|
"description": "Lot/Batch Reference No."
|
||||||
|
},
|
||||||
|
"ContrRefr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"pattern": "^([0-9A-Za-z/-]){1,20}$",
|
||||||
|
"description": "Contract Reference Number"
|
||||||
|
},
|
||||||
|
"ExtRefr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"pattern": "^([0-9A-Za-z/-]){1,20}$",
|
||||||
|
"description": "Any other reference"
|
||||||
|
},
|
||||||
|
"ProjRefr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"pattern": "^([0-9A-Za-z/-]){1,20}$",
|
||||||
|
"description": "Project Reference Number"
|
||||||
|
},
|
||||||
|
"PORefr": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 16,
|
||||||
|
"pattern": "^([0-9A-Za-z/-]){1,16}$",
|
||||||
|
"description": "PO Reference Number"
|
||||||
|
},
|
||||||
|
"PORefDt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 10,
|
||||||
|
"maxLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "PO Reference date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AddlDocDtls": {
|
||||||
|
"type": "Array",
|
||||||
|
"properties": {
|
||||||
|
"Url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Supporting document URL"
|
||||||
|
},
|
||||||
|
"Docs": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 1000,
|
||||||
|
"description": "Supporting document in Base64 Format"
|
||||||
|
},
|
||||||
|
"Info": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 1000,
|
||||||
|
"description": "Any additional information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"ExpDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ShipBNo": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 20,
|
||||||
|
"description": "Shipping Bill No."
|
||||||
|
},
|
||||||
|
"ShipBDt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 10,
|
||||||
|
"maxLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Shipping Bill Date"
|
||||||
|
},
|
||||||
|
"Port": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 2,
|
||||||
|
"maxLength": 10,
|
||||||
|
"pattern": "^[0-9A-Za-z]{2,10}$",
|
||||||
|
"description": "Port Code. Refer the master"
|
||||||
|
},
|
||||||
|
"RefClm": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 1,
|
||||||
|
"description": "Claiming Refund. Y/N"
|
||||||
|
},
|
||||||
|
"ForCur": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 16,
|
||||||
|
"description": "Additional Currency Code. Refer the master"
|
||||||
|
},
|
||||||
|
"CntCode": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 2,
|
||||||
|
"maxLength": 2,
|
||||||
|
"description": "Country Code. Refer the master"
|
||||||
|
},
|
||||||
|
"ExpDuty": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 999999999999.99,
|
||||||
|
"description": "Export Duty"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EwbDtls": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"TransId": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 15,
|
||||||
|
"maxLength": 15,
|
||||||
|
"description": "Transporter GSTIN"
|
||||||
|
},
|
||||||
|
"TransName": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 100,
|
||||||
|
"description": "Transporter Name"
|
||||||
|
},
|
||||||
|
"TransMode": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1,
|
||||||
|
"minLength": 1,
|
||||||
|
"enum": ["1", "2", "3", "4"],
|
||||||
|
"description": "Mode of Transport"
|
||||||
|
},
|
||||||
|
"Distance": {
|
||||||
|
"type": "number",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 9999,
|
||||||
|
"description": "Distance"
|
||||||
|
},
|
||||||
|
"TransDocNo": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 15,
|
||||||
|
"pattern": "^([0-9A-Z/-]){1,15}$",
|
||||||
|
"description": "Tranport Document Number"
|
||||||
|
},
|
||||||
|
"TransDocDt": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 10,
|
||||||
|
"maxLength": 10,
|
||||||
|
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||||
|
"description": "Transport Document Date"
|
||||||
|
},
|
||||||
|
"VehNo": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 4,
|
||||||
|
"maxLength": 20,
|
||||||
|
"description": "Vehicle Number"
|
||||||
|
},
|
||||||
|
"VehType": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 1,
|
||||||
|
"enum": ["O", "R"],
|
||||||
|
"description": "Vehicle Type"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["Distance"]
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"Version",
|
||||||
|
"TranDtls",
|
||||||
|
"DocDtls",
|
||||||
|
"SellerDtls",
|
||||||
|
"BuyerDtls",
|
||||||
|
"ItemList",
|
||||||
|
"ValDtls"
|
||||||
|
]
|
||||||
|
}
|
||||||
309
erpnext/regional/india/e_invoice/einvoice.js
Normal file
309
erpnext/regional/india/e_invoice/einvoice.js
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
erpnext.setup_einvoice_actions = (doctype) => {
|
||||||
|
frappe.ui.form.on(doctype, {
|
||||||
|
refresh(frm) {
|
||||||
|
const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable");
|
||||||
|
const supply_type = frm.doc.gst_category;
|
||||||
|
const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type);
|
||||||
|
const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin;
|
||||||
|
|
||||||
|
if (!einvoicing_enabled || !valid_supply_type || company_transaction) return;
|
||||||
|
|
||||||
|
const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
|
||||||
|
|
||||||
|
const add_custom_button = (label, action) => {
|
||||||
|
if (!frm.custom_buttons[label]) {
|
||||||
|
frm.add_custom_button(label, action, __('E Invoicing'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ewaybill && irn) {
|
||||||
|
frm.set_df_property('ewaybill', 'read_only', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!irn && !__unsaved) {
|
||||||
|
const action = () => {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
|
||||||
|
args: { doctype, docname: name },
|
||||||
|
freeze: true,
|
||||||
|
callback: (res) => {
|
||||||
|
const einvoice = res.message;
|
||||||
|
show_einvoice_preview(frm, einvoice);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
add_custom_button(__("Generate IRN"), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irn && !irn_cancelled && !ewaybill) {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
"label": "Reason",
|
||||||
|
"fieldname": "reason",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": "1-Duplicate",
|
||||||
|
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Remark",
|
||||||
|
"fieldname": "remark",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const action = () => {
|
||||||
|
const d = new frappe.ui.Dialog({
|
||||||
|
title: __("Cancel IRN"),
|
||||||
|
fields: fields,
|
||||||
|
primary_action: function() {
|
||||||
|
const data = d.get_values();
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.regional.india.e_invoice.utils.cancel_irn',
|
||||||
|
args: {
|
||||||
|
doctype,
|
||||||
|
docname: name,
|
||||||
|
irn: irn,
|
||||||
|
reason: data.reason.split('-')[0],
|
||||||
|
remark: data.remark
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: () => frm.reload_doc() || d.hide(),
|
||||||
|
error: () => d.hide()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
primary_action_label: __('Submit')
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
};
|
||||||
|
add_custom_button(__("Cancel IRN"), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irn && !irn_cancelled && !ewaybill) {
|
||||||
|
const action = () => {
|
||||||
|
const d = new frappe.ui.Dialog({
|
||||||
|
title: __('Generate E-Way Bill'),
|
||||||
|
wide: 1,
|
||||||
|
fields: get_ewaybill_fields(frm),
|
||||||
|
primary_action: function() {
|
||||||
|
const data = d.get_values();
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill',
|
||||||
|
args: {
|
||||||
|
doctype,
|
||||||
|
docname: name,
|
||||||
|
irn,
|
||||||
|
...data
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: () => frm.reload_doc() || d.hide(),
|
||||||
|
error: () => d.hide()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
primary_action_label: __('Submit')
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
};
|
||||||
|
|
||||||
|
add_custom_button(__("Generate E-Way Bill"), action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
"label": "Reason",
|
||||||
|
"fieldname": "reason",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"reqd": 1,
|
||||||
|
"default": "1-Duplicate",
|
||||||
|
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Remark",
|
||||||
|
"fieldname": "remark",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const action = () => {
|
||||||
|
const d = new frappe.ui.Dialog({
|
||||||
|
title: __('Cancel E-Way Bill'),
|
||||||
|
fields: fields,
|
||||||
|
primary_action: function() {
|
||||||
|
const data = d.get_values();
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
|
||||||
|
args: {
|
||||||
|
doctype,
|
||||||
|
docname: name,
|
||||||
|
eway_bill: ewaybill,
|
||||||
|
reason: data.reason.split('-')[0],
|
||||||
|
remark: data.remark
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
callback: () => frm.reload_doc() || d.hide(),
|
||||||
|
error: () => d.hide()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
primary_action_label: __('Submit')
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
};
|
||||||
|
add_custom_button(__("Cancel E-Way Bill"), action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const get_ewaybill_fields = (frm) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'fieldname': 'transporter',
|
||||||
|
'label': 'Transporter',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Supplier',
|
||||||
|
'default': frm.doc.transporter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'gst_transporter_id',
|
||||||
|
'label': 'GST Transporter ID',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'fetch_from': 'transporter.gst_transporter_id',
|
||||||
|
'default': frm.doc.gst_transporter_id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'driver',
|
||||||
|
'label': 'Driver',
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Driver',
|
||||||
|
'default': frm.doc.driver
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'lr_no',
|
||||||
|
'label': 'Transport Receipt No',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'default': frm.doc.lr_no
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'vehicle_no',
|
||||||
|
'label': 'Vehicle No',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'depends_on': 'eval:(doc.mode_of_transport === "Road")',
|
||||||
|
'default': frm.doc.vehicle_no
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'distance',
|
||||||
|
'label': 'Distance (in km)',
|
||||||
|
'fieldtype': 'Float',
|
||||||
|
'default': frm.doc.distance
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'transporter_col_break',
|
||||||
|
'fieldtype': 'Column Break',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'transporter_name',
|
||||||
|
'label': 'Transporter Name',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'fetch_from': 'transporter.name',
|
||||||
|
'read_only': 1,
|
||||||
|
'default': frm.doc.transporter_name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'mode_of_transport',
|
||||||
|
'label': 'Mode of Transport',
|
||||||
|
'fieldtype': 'Select',
|
||||||
|
'options': `\nRoad\nAir\nRail\nShip`,
|
||||||
|
'default': frm.doc.mode_of_transport
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'driver_name',
|
||||||
|
'label': 'Driver Name',
|
||||||
|
'fieldtype': 'Data',
|
||||||
|
'fetch_from': 'driver.full_name',
|
||||||
|
'read_only': 1,
|
||||||
|
'default': frm.doc.driver_name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'lr_date',
|
||||||
|
'label': 'Transport Receipt Date',
|
||||||
|
'fieldtype': 'Date',
|
||||||
|
'default': frm.doc.lr_date
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldname': 'gst_vehicle_type',
|
||||||
|
'label': 'GST Vehicle Type',
|
||||||
|
'fieldtype': 'Select',
|
||||||
|
'options': `Regular\nOver Dimensional Cargo (ODC)`,
|
||||||
|
'depends_on': 'eval:(doc.mode_of_transport === "Road")',
|
||||||
|
'default': frm.doc.gst_vehicle_type
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const request_irn_generation = (frm) => {
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.regional.india.e_invoice.utils.generate_irn',
|
||||||
|
args: { doctype: frm.doc.doctype, docname: frm.doc.name },
|
||||||
|
freeze: true,
|
||||||
|
callback: () => frm.reload_doc()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const get_preview_dialog = (frm, action) => {
|
||||||
|
const dialog = new frappe.ui.Dialog({
|
||||||
|
title: __("Preview"),
|
||||||
|
wide: 1,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
"label": "Preview",
|
||||||
|
"fieldname": "preview_html",
|
||||||
|
"fieldtype": "HTML"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primary_action: () => action(frm) || dialog.hide(),
|
||||||
|
primary_action_label: __('Generate IRN')
|
||||||
|
});
|
||||||
|
return dialog;
|
||||||
|
};
|
||||||
|
|
||||||
|
const show_einvoice_preview = (frm, einvoice) => {
|
||||||
|
const preview_dialog = get_preview_dialog(frm, request_irn_generation);
|
||||||
|
|
||||||
|
// initialize e-invoice fields
|
||||||
|
einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate();
|
||||||
|
frm.doc.signed_einvoice = JSON.stringify(einvoice);
|
||||||
|
|
||||||
|
// initialize preview wrapper
|
||||||
|
const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper;
|
||||||
|
$preview_wrapper.html(
|
||||||
|
`<div>
|
||||||
|
<div class="print-preview">
|
||||||
|
<div class="print-format"></div>
|
||||||
|
</div>
|
||||||
|
<div class="page-break-message text-muted text-center text-medium margin-top"></div>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.www.printview.get_html_and_style",
|
||||||
|
args: {
|
||||||
|
doc: frm.doc,
|
||||||
|
print_format: "GST E-Invoice",
|
||||||
|
no_letterhead: 1
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
if (!r.exc) {
|
||||||
|
$preview_wrapper.find(".print-format").html(r.message.html);
|
||||||
|
const style = `
|
||||||
|
.print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; }
|
||||||
|
.print-preview { min-height: 0px; }
|
||||||
|
.modal-dialog { width: 720px; }`;
|
||||||
|
|
||||||
|
frappe.dom.set_style(style, "custom-print-style");
|
||||||
|
preview_dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
779
erpnext/regional/india/e_invoice/utils.py
Normal file
779
erpnext/regional/india/e_invoice/utils.py
Normal file
@@ -0,0 +1,779 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import jwt
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import frappe
|
||||||
|
import traceback
|
||||||
|
from frappe import _, bold
|
||||||
|
from pyqrcode import create as qrcreate
|
||||||
|
from frappe.integrations.utils import make_post_request, make_get_request
|
||||||
|
from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
|
||||||
|
from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
|
||||||
|
|
||||||
|
def validate_einvoice_fields(doc):
|
||||||
|
einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
|
||||||
|
invalid_doctype = doc.doctype not in ['Sales Invoice']
|
||||||
|
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
|
||||||
|
company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
|
||||||
|
|
||||||
|
if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return
|
||||||
|
|
||||||
|
if doc.docstatus == 0 and doc._action == 'save':
|
||||||
|
if doc.irn:
|
||||||
|
frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
|
||||||
|
if len(doc.name) > 16:
|
||||||
|
raise_document_name_too_long_error()
|
||||||
|
|
||||||
|
elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
|
||||||
|
frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
|
||||||
|
|
||||||
|
elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
|
||||||
|
frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed'))
|
||||||
|
|
||||||
|
def raise_document_name_too_long_error():
|
||||||
|
title = _('Document ID Too Long')
|
||||||
|
msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ')
|
||||||
|
msg += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
|
||||||
|
msg += '<br><br>'
|
||||||
|
msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
|
||||||
|
bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
|
||||||
|
)
|
||||||
|
msg += _('Please account for ammended documents too. ')
|
||||||
|
frappe.throw(msg, title=title)
|
||||||
|
|
||||||
|
def read_json(name):
|
||||||
|
file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name))
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
return cstr(f.read())
|
||||||
|
|
||||||
|
def get_transaction_details(invoice):
|
||||||
|
supply_type = ''
|
||||||
|
if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
|
||||||
|
elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
|
||||||
|
elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
|
||||||
|
elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
|
||||||
|
|
||||||
|
if not supply_type:
|
||||||
|
rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
|
||||||
|
frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
|
||||||
|
title=_('Invalid Supply Type'))
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
tax_scheme='GST',
|
||||||
|
supply_type=supply_type,
|
||||||
|
reverse_charge=invoice.reverse_charge
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_doc_details(invoice):
|
||||||
|
invoice_type = 'CRN' if invoice.is_return else 'INV'
|
||||||
|
|
||||||
|
invoice_name = invoice.name
|
||||||
|
invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
invoice_type=invoice_type,
|
||||||
|
invoice_name=invoice_name,
|
||||||
|
invoice_date=invoice_date
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_party_details(address_name):
|
||||||
|
address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
||||||
|
gstin = address.get('gstin')
|
||||||
|
|
||||||
|
gstin_details = get_gstin_details(gstin)
|
||||||
|
legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName')
|
||||||
|
location = gstin_details.get('AddrLoc') or address.get('city')
|
||||||
|
state_code = gstin_details.get('StateCode')
|
||||||
|
pincode = 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')
|
||||||
|
# get last 10 digit
|
||||||
|
phone = phone.replace(" ", "")[-10:] if phone else ''
|
||||||
|
|
||||||
|
if state_code == 97:
|
||||||
|
# according to einvoice standard
|
||||||
|
pincode = 999999
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
gstin=gstin, legal_name=legal_name, location=location,
|
||||||
|
pincode=pincode, state_code=state_code, address_line1=address_line1,
|
||||||
|
address_line2=address_line2, email=email_id, phone=phone
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_gstin_details(gstin):
|
||||||
|
if not hasattr(frappe.local, 'gstin_cache'):
|
||||||
|
frappe.local.gstin_cache = {}
|
||||||
|
|
||||||
|
key = gstin
|
||||||
|
details = frappe.local.gstin_cache.get(key)
|
||||||
|
if details:
|
||||||
|
return details
|
||||||
|
|
||||||
|
details = frappe.cache().hget('gstin_cache', key)
|
||||||
|
if details:
|
||||||
|
frappe.local.gstin_cache[key] = details
|
||||||
|
return details
|
||||||
|
|
||||||
|
if not details:
|
||||||
|
return GSPConnector.get_gstin_details(gstin)
|
||||||
|
|
||||||
|
def get_overseas_address_details(address_name):
|
||||||
|
address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
|
||||||
|
'Address', address_name, ['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(invoice):
|
||||||
|
item_list = []
|
||||||
|
|
||||||
|
for d in invoice.items:
|
||||||
|
einvoice_item_schema = read_json('einv_item_template')
|
||||||
|
item = frappe._dict({})
|
||||||
|
item.update(d.as_dict())
|
||||||
|
|
||||||
|
item.sr_no = d.idx
|
||||||
|
item.discount_amount = abs(item.discount_amount * item.qty)
|
||||||
|
item.description = d.item_name
|
||||||
|
item.qty = abs(item.qty)
|
||||||
|
item.unit_rate = abs(item.base_net_amount / item.qty)
|
||||||
|
item.gross_amount = abs(item.base_net_amount)
|
||||||
|
item.taxable_value = abs(item.base_net_amount)
|
||||||
|
|
||||||
|
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||||
|
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||||
|
item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
|
||||||
|
|
||||||
|
item = update_item_taxes(invoice, item)
|
||||||
|
|
||||||
|
item.total_value = abs(
|
||||||
|
item.taxable_value + item.igst_amount + item.sgst_amount +
|
||||||
|
item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
|
||||||
|
)
|
||||||
|
einv_item = einvoice_item_schema.format(item=item)
|
||||||
|
item_list.append(einv_item)
|
||||||
|
|
||||||
|
return ', '.join(item_list)
|
||||||
|
|
||||||
|
def update_item_taxes(invoice, item):
|
||||||
|
gst_accounts = get_gst_accounts(invoice.company)
|
||||||
|
gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
|
||||||
|
|
||||||
|
for attr in [
|
||||||
|
'tax_rate', 'cess_rate', 'cess_nadv_amount',
|
||||||
|
'cgst_amount', 'sgst_amount', 'igst_amount',
|
||||||
|
'cess_amount', 'cess_nadv_amount', 'other_charges'
|
||||||
|
]:
|
||||||
|
item[attr] = 0
|
||||||
|
|
||||||
|
for t in invoice.taxes:
|
||||||
|
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
|
||||||
|
if t.account_head in gst_accounts_list:
|
||||||
|
if t.account_head in gst_accounts.cess_account:
|
||||||
|
if t.charge_type == 'On Item Quantity':
|
||||||
|
item.cess_nadv_amount += abs(item_tax_detail[1])
|
||||||
|
else:
|
||||||
|
item.cess_rate += item_tax_detail[0]
|
||||||
|
item.cess_amount += abs(item_tax_detail[1])
|
||||||
|
elif t.account_head in gst_accounts.igst_account:
|
||||||
|
item.tax_rate += item_tax_detail[0]
|
||||||
|
item.igst_amount += abs(item_tax_detail[1])
|
||||||
|
elif t.account_head in gst_accounts.sgst_account:
|
||||||
|
item.tax_rate += item_tax_detail[0]
|
||||||
|
item.sgst_amount += abs(item_tax_detail[1])
|
||||||
|
elif t.account_head in gst_accounts.cgst_account:
|
||||||
|
item.tax_rate += item_tax_detail[0]
|
||||||
|
item.cgst_amount += abs(item_tax_detail[1])
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def get_invoice_value_details(invoice):
|
||||||
|
invoice_value_details = frappe._dict(dict())
|
||||||
|
invoice_value_details.base_net_total = abs(invoice.base_net_total)
|
||||||
|
invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0
|
||||||
|
# discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off
|
||||||
|
invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0)
|
||||||
|
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
|
||||||
|
invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
|
||||||
|
|
||||||
|
invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
|
||||||
|
|
||||||
|
return invoice_value_details
|
||||||
|
|
||||||
|
def update_invoice_taxes(invoice, invoice_value_details):
|
||||||
|
gst_accounts = get_gst_accounts(invoice.company)
|
||||||
|
gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
|
||||||
|
|
||||||
|
invoice_value_details.total_cgst_amt = 0
|
||||||
|
invoice_value_details.total_sgst_amt = 0
|
||||||
|
invoice_value_details.total_igst_amt = 0
|
||||||
|
invoice_value_details.total_cess_amt = 0
|
||||||
|
invoice_value_details.total_other_charges = 0
|
||||||
|
for t in invoice.taxes:
|
||||||
|
if t.account_head in gst_accounts_list:
|
||||||
|
if t.account_head in gst_accounts.cess_account:
|
||||||
|
invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
elif t.account_head in gst_accounts.igst_account:
|
||||||
|
invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
elif t.account_head in gst_accounts.sgst_account:
|
||||||
|
invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
elif t.account_head in gst_accounts.cgst_account:
|
||||||
|
invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
else:
|
||||||
|
invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
|
||||||
|
return invoice_value_details
|
||||||
|
|
||||||
|
def get_payment_details(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(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=format_date(invoice_date, 'dd/mm/yyyy')
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_eway_bill_details(invoice):
|
||||||
|
if invoice.is_return:
|
||||||
|
frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed'))
|
||||||
|
|
||||||
|
mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
|
||||||
|
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
gstin=invoice.gst_transporter_id,
|
||||||
|
name=invoice.transporter_name,
|
||||||
|
mode_of_transport=mode_of_transport[invoice.mode_of_transport],
|
||||||
|
distance=invoice.distance or 0,
|
||||||
|
document_name=invoice.lr_no,
|
||||||
|
document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
|
||||||
|
vehicle_no=invoice.vehicle_no,
|
||||||
|
vehicle_type=vehicle_type[invoice.gst_vehicle_type]
|
||||||
|
))
|
||||||
|
|
||||||
|
def make_einvoice(invoice):
|
||||||
|
schema = read_json('einv_template')
|
||||||
|
|
||||||
|
transaction_details = get_transaction_details(invoice)
|
||||||
|
item_list = get_item_list(invoice)
|
||||||
|
doc_details = get_doc_details(invoice)
|
||||||
|
invoice_value_details = get_invoice_value_details(invoice)
|
||||||
|
seller_details = get_party_details(invoice.company_address)
|
||||||
|
|
||||||
|
if invoice.gst_category == 'Overseas':
|
||||||
|
buyer_details = get_overseas_address_details(invoice.customer_address)
|
||||||
|
else:
|
||||||
|
buyer_details = get_party_details(invoice.customer_address)
|
||||||
|
place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin
|
||||||
|
place_of_supply = place_of_supply[:2]
|
||||||
|
buyer_details.update(dict(place_of_supply=place_of_supply))
|
||||||
|
|
||||||
|
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
|
||||||
|
if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
|
||||||
|
shipping_details = get_party_details(invoice.shipping_address_name)
|
||||||
|
|
||||||
|
if invoice.is_pos and invoice.base_paid_amount:
|
||||||
|
payment_details = get_payment_details(invoice)
|
||||||
|
|
||||||
|
if invoice.is_return and invoice.return_against:
|
||||||
|
prev_doc_details = get_return_doc_reference(invoice)
|
||||||
|
|
||||||
|
if invoice.transporter:
|
||||||
|
eway_bill_details = get_eway_bill_details(invoice)
|
||||||
|
|
||||||
|
# not yet implemented
|
||||||
|
dispatch_details = period_details = export_details = frappe._dict({})
|
||||||
|
|
||||||
|
einvoice = schema.format(
|
||||||
|
transaction_details=transaction_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, invoice_value_details=invoice_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
|
||||||
|
)
|
||||||
|
einvoice = json.loads(einvoice)
|
||||||
|
|
||||||
|
validations = json.loads(read_json('einv_validation'))
|
||||||
|
errors = validate_einvoice(validations, einvoice)
|
||||||
|
if errors:
|
||||||
|
message = "\n".join([
|
||||||
|
"E Invoice: ", json.dumps(einvoice, indent=4),
|
||||||
|
"-" * 50,
|
||||||
|
"Errors: ", json.dumps(errors, indent=4)
|
||||||
|
])
|
||||||
|
frappe.log_error(title="E Invoice Validation Failed", message=message)
|
||||||
|
throw_error_list(errors, _('E Invoice Validation Failed'))
|
||||||
|
|
||||||
|
return einvoice
|
||||||
|
|
||||||
|
def throw_error_list(errors, title):
|
||||||
|
if len(errors) > 1:
|
||||||
|
li = ['<li>'+ d +'</li>' for d in errors]
|
||||||
|
frappe.throw("<ul style='padding-left: 20px'>{}</ul>".format(''.join(li)), title=title)
|
||||||
|
else:
|
||||||
|
frappe.throw(errors[0], title=title)
|
||||||
|
|
||||||
|
def validate_einvoice(validations, einvoice, errors=[]):
|
||||||
|
for fieldname, field_validation in validations.items():
|
||||||
|
value = einvoice.get(fieldname, None)
|
||||||
|
if not value or value == "None":
|
||||||
|
# remove keys with empty values
|
||||||
|
einvoice.pop(fieldname, None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
value_type = field_validation.get("type").lower()
|
||||||
|
if value_type in ['object', 'array']:
|
||||||
|
child_validations = field_validation.get('properties')
|
||||||
|
|
||||||
|
if isinstance(value, list):
|
||||||
|
for d in value:
|
||||||
|
validate_einvoice(child_validations, d, errors)
|
||||||
|
if not d:
|
||||||
|
# remove empty dicts
|
||||||
|
einvoice.pop(fieldname, None)
|
||||||
|
else:
|
||||||
|
validate_einvoice(child_validations, value, errors)
|
||||||
|
if not value:
|
||||||
|
# remove empty dicts
|
||||||
|
einvoice.pop(fieldname, None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# convert to int or str
|
||||||
|
if value_type == 'string':
|
||||||
|
einvoice[fieldname] = str(value)
|
||||||
|
elif value_type == 'number':
|
||||||
|
is_integer = '.' not in str(field_validation.get('maximum'))
|
||||||
|
precision = 3 if '.999' in str(field_validation.get('maximum')) else 2
|
||||||
|
einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value)
|
||||||
|
value = einvoice[fieldname]
|
||||||
|
|
||||||
|
max_length = field_validation.get('maxLength')
|
||||||
|
minimum = flt(field_validation.get('minimum'))
|
||||||
|
maximum = flt(field_validation.get('maximum'))
|
||||||
|
pattern_str = field_validation.get('pattern')
|
||||||
|
pattern = re.compile(pattern_str or '')
|
||||||
|
|
||||||
|
label = field_validation.get('description') or fieldname
|
||||||
|
|
||||||
|
if value_type == 'string' and len(value) > max_length:
|
||||||
|
errors.append(_('{} should not exceed {} characters').format(label, max_length))
|
||||||
|
if value_type == 'number' and (value > maximum or value < minimum):
|
||||||
|
errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum))
|
||||||
|
if pattern_str and not pattern.match(value):
|
||||||
|
errors.append(field_validation.get('validationMsg'))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
class RequestFailed(Exception): pass
|
||||||
|
|
||||||
|
class GSPConnector():
|
||||||
|
def __init__(self, doctype=None, docname=None):
|
||||||
|
self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
|
||||||
|
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
|
||||||
|
self.credentials = self.get_credentials()
|
||||||
|
|
||||||
|
self.base_url = 'https://gsp.adaequare.com/'
|
||||||
|
self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token'
|
||||||
|
self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin'
|
||||||
|
self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice'
|
||||||
|
self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn'
|
||||||
|
self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel'
|
||||||
|
self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi'
|
||||||
|
self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill'
|
||||||
|
|
||||||
|
def get_credentials(self):
|
||||||
|
if self.invoice:
|
||||||
|
gstin = self.get_seller_gstin()
|
||||||
|
credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
|
||||||
|
else:
|
||||||
|
credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
|
||||||
|
return credentials
|
||||||
|
|
||||||
|
def get_seller_gstin(self):
|
||||||
|
gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
|
||||||
|
if not gstin:
|
||||||
|
frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
|
||||||
|
return gstin
|
||||||
|
|
||||||
|
def get_auth_token(self):
|
||||||
|
if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
|
||||||
|
self.fetch_auth_token()
|
||||||
|
|
||||||
|
return self.e_invoice_settings.auth_token
|
||||||
|
|
||||||
|
def make_request(self, request_type, url, headers=None, data=None):
|
||||||
|
if request_type == 'post':
|
||||||
|
res = make_post_request(url, headers=headers, data=data)
|
||||||
|
else:
|
||||||
|
res = make_get_request(url, headers=headers, data=data)
|
||||||
|
|
||||||
|
self.log_request(url, headers, data, res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def log_request(self, url, headers, data, res):
|
||||||
|
headers.update({ 'password': self.credentials.password })
|
||||||
|
request_log = frappe.get_doc({
|
||||||
|
"doctype": "E Invoice Request Log",
|
||||||
|
"user": frappe.session.user,
|
||||||
|
"reference_invoice": self.invoice.name if self.invoice else None,
|
||||||
|
"url": url,
|
||||||
|
"headers": json.dumps(headers, indent=4) if headers else None,
|
||||||
|
"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
|
||||||
|
"response": json.dumps(res, indent=4) if res else None
|
||||||
|
})
|
||||||
|
request_log.insert(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
def fetch_auth_token(self):
|
||||||
|
headers = {
|
||||||
|
'gspappid': frappe.conf.einvoice_client_id,
|
||||||
|
'gspappsecret': frappe.conf.einvoice_client_secret
|
||||||
|
}
|
||||||
|
res = {}
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.authenticate_url, headers)
|
||||||
|
self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
|
||||||
|
self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
|
||||||
|
self.e_invoice_settings.save()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(res)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def get_headers(self):
|
||||||
|
return {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'user_name': self.credentials.username,
|
||||||
|
'password': self.credentials.get_password(),
|
||||||
|
'gstin': self.credentials.gstin,
|
||||||
|
'authorization': self.get_auth_token(),
|
||||||
|
'requestid': str(base64.b64encode(os.urandom(18))),
|
||||||
|
}
|
||||||
|
|
||||||
|
def fetch_gstin_details(self, gstin):
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
try:
|
||||||
|
params = '?gstin={gstin}'.format(gstin=gstin)
|
||||||
|
res = self.make_request('get', self.gstin_details_url + params, headers)
|
||||||
|
if res.get('success'):
|
||||||
|
return res.get('result')
|
||||||
|
else:
|
||||||
|
self.log_error(res)
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
self.raise_error()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error()
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_gstin_details(gstin):
|
||||||
|
'''fetch and cache GSTIN details'''
|
||||||
|
if not hasattr(frappe.local, 'gstin_cache'):
|
||||||
|
frappe.local.gstin_cache = {}
|
||||||
|
|
||||||
|
key = gstin
|
||||||
|
gsp_connector = GSPConnector()
|
||||||
|
details = gsp_connector.fetch_gstin_details(gstin)
|
||||||
|
|
||||||
|
frappe.local.gstin_cache[key] = details
|
||||||
|
frappe.cache().hset('gstin_cache', key, details)
|
||||||
|
return details
|
||||||
|
|
||||||
|
def generate_irn(self):
|
||||||
|
headers = self.get_headers()
|
||||||
|
einvoice = make_einvoice(self.invoice)
|
||||||
|
data = json.dumps(einvoice, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.generate_irn_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.set_einvoice_data(res.get('result'))
|
||||||
|
|
||||||
|
elif '2150' in res.get('message'):
|
||||||
|
# IRN already generated but not updated in invoice
|
||||||
|
# Extract the IRN from the response description and fetch irn details
|
||||||
|
irn = res.get('result')[0].get('Desc').get('Irn')
|
||||||
|
irn_details = self.get_irn_details(irn)
|
||||||
|
if irn_details:
|
||||||
|
self.set_einvoice_data(irn_details)
|
||||||
|
else:
|
||||||
|
raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \
|
||||||
|
Contact ERPNext support to resolve the issue.')
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def get_irn_details(self, irn):
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
try:
|
||||||
|
params = '?irn={irn}'.format(irn=irn)
|
||||||
|
res = self.make_request('get', self.irn_details_url + params, headers)
|
||||||
|
if res.get('success'):
|
||||||
|
return res.get('result')
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error()
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def cancel_irn(self, irn, reason, remark):
|
||||||
|
headers = self.get_headers()
|
||||||
|
data = json.dumps({
|
||||||
|
'Irn': irn,
|
||||||
|
'Cnlrsn': reason,
|
||||||
|
'Cnlrem': remark
|
||||||
|
}, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.cancel_irn_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.invoice.irn_cancelled = 1
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('IRN Cancelled - {}').format(remark)
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def generate_eway_bill(self, **kwargs):
|
||||||
|
args = frappe._dict(kwargs)
|
||||||
|
|
||||||
|
headers = self.get_headers()
|
||||||
|
eway_bill_details = get_eway_bill_details(args)
|
||||||
|
data = json.dumps({
|
||||||
|
'Irn': args.irn,
|
||||||
|
'Distance': cint(eway_bill_details.distance),
|
||||||
|
'TransMode': eway_bill_details.mode_of_transport,
|
||||||
|
'TransId': eway_bill_details.gstin,
|
||||||
|
'TransName': eway_bill_details.transporter,
|
||||||
|
'TrnDocDt': eway_bill_details.document_date,
|
||||||
|
'TrnDocNo': eway_bill_details.document_name,
|
||||||
|
'VehNo': eway_bill_details.vehicle_no,
|
||||||
|
'VehType': eway_bill_details.vehicle_type
|
||||||
|
}, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.generate_ewaybill_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.invoice.ewaybill = res.get('result').get('EwbNo')
|
||||||
|
self.invoice.eway_bill_cancelled = 0
|
||||||
|
self.invoice.update(args)
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('E-Way Bill Generated')
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def cancel_eway_bill(self, eway_bill, reason, remark):
|
||||||
|
headers = self.get_headers()
|
||||||
|
data = json.dumps({
|
||||||
|
'ewbNo': eway_bill,
|
||||||
|
'cancelRsnCode': reason,
|
||||||
|
'cancelRmrk': remark
|
||||||
|
}, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.invoice.ewaybill = ''
|
||||||
|
self.invoice.eway_bill_cancelled = 1
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('E-Way Bill Cancelled - {}').format(remark)
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def sanitize_error_message(self, message):
|
||||||
|
'''
|
||||||
|
On validation errors, response message looks something like this:
|
||||||
|
message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable,
|
||||||
|
3095 : Supplier GSTIN is inactive'
|
||||||
|
we search for string between ':' to extract the error messages
|
||||||
|
errors = [
|
||||||
|
': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ',
|
||||||
|
': Test'
|
||||||
|
]
|
||||||
|
then we trim down the message by looping over errors
|
||||||
|
'''
|
||||||
|
errors = re.findall(': [^:]+', message)
|
||||||
|
for idx, e in enumerate(errors):
|
||||||
|
# remove colons
|
||||||
|
errors[idx] = errors[idx].replace(':', '').strip()
|
||||||
|
# if not last
|
||||||
|
if idx != len(errors) - 1:
|
||||||
|
# remove last 7 chars eg: ', 3095 '
|
||||||
|
errors[idx] = errors[idx][:-6]
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def log_error(self, data={}):
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
data = json.loads(data)
|
||||||
|
|
||||||
|
seperator = "--" * 50
|
||||||
|
err_tb = traceback.format_exc()
|
||||||
|
err_msg = str(sys.exc_info()[1])
|
||||||
|
data = json.dumps(data, indent=4)
|
||||||
|
|
||||||
|
message = "\n".join([
|
||||||
|
"Error", err_msg, seperator,
|
||||||
|
"Data:", data, seperator,
|
||||||
|
"Exception:", err_tb
|
||||||
|
])
|
||||||
|
frappe.log_error(title=_('E Invoice Request Failed'), message=message)
|
||||||
|
|
||||||
|
def raise_error(self, raise_exception=False, errors=[]):
|
||||||
|
title = _('E Invoice Request Failed')
|
||||||
|
if errors:
|
||||||
|
throw_error_list(errors, title)
|
||||||
|
else:
|
||||||
|
link_to_error_list = '<a href="desk#List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
|
||||||
|
frappe.msgprint(
|
||||||
|
_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
|
||||||
|
title=title,
|
||||||
|
raise_exception=raise_exception,
|
||||||
|
indicator='red'
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_einvoice_data(self, res):
|
||||||
|
enc_signed_invoice = res.get('SignedInvoice')
|
||||||
|
dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data']
|
||||||
|
|
||||||
|
self.invoice.irn = res.get('Irn')
|
||||||
|
self.invoice.ewaybill = res.get('EwbNo')
|
||||||
|
self.invoice.signed_einvoice = dec_signed_invoice
|
||||||
|
self.invoice.signed_qr_code = res.get('SignedQRCode')
|
||||||
|
|
||||||
|
self.attach_qrcode_image()
|
||||||
|
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('IRN Generated')
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
def attach_qrcode_image(self):
|
||||||
|
qrcode = self.invoice.signed_qr_code
|
||||||
|
doctype = self.invoice.doctype
|
||||||
|
docname = self.invoice.name
|
||||||
|
|
||||||
|
_file = frappe.new_doc('File')
|
||||||
|
_file.update({
|
||||||
|
'file_name': 'QRCode_{}.png'.format(docname),
|
||||||
|
'attached_to_doctype': doctype,
|
||||||
|
'attached_to_name': docname,
|
||||||
|
'content': 'qrcode',
|
||||||
|
'is_private': 1
|
||||||
|
})
|
||||||
|
_file.insert()
|
||||||
|
frappe.db.commit()
|
||||||
|
url = qrcreate(qrcode, error='L')
|
||||||
|
abs_file_path = os.path.abspath(_file.get_full_path())
|
||||||
|
url.png(abs_file_path, scale=2, quiet_zone=1)
|
||||||
|
|
||||||
|
self.invoice.qrcode_image = _file.file_url
|
||||||
|
|
||||||
|
def update_invoice(self):
|
||||||
|
self.invoice.flags.ignore_validate_update_after_submit = True
|
||||||
|
self.invoice.flags.ignore_validate = True
|
||||||
|
self.invoice.save()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_einvoice(doctype, docname):
|
||||||
|
invoice = frappe.get_doc(doctype, docname)
|
||||||
|
return make_einvoice(invoice)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def generate_irn(doctype, docname):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.generate_irn()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def cancel_irn(doctype, docname, irn, reason, remark):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.cancel_irn(irn, reason, remark)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def generate_eway_bill(doctype, docname, **kwargs):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.generate_eway_bill(**kwargs)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
||||||
@@ -77,7 +77,7 @@ def add_custom_roles_for_reports():
|
|||||||
)).insert()
|
)).insert()
|
||||||
|
|
||||||
def add_permissions():
|
def add_permissions():
|
||||||
for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
|
for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'):
|
||||||
add_permission(doctype, 'All', 0)
|
add_permission(doctype, 'All', 0)
|
||||||
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
||||||
add_permission(doctype, role, 0)
|
add_permission(doctype, role, 0)
|
||||||
@@ -93,9 +93,10 @@ def add_permissions():
|
|||||||
def add_print_formats():
|
def add_print_formats():
|
||||||
frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
|
frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
|
||||||
frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
|
frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
|
||||||
|
frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
|
||||||
|
|
||||||
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
|
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
|
||||||
name in('GST POS Invoice', 'GST Tax Invoice') """)
|
name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """)
|
||||||
|
|
||||||
def make_custom_fields(update=True):
|
def make_custom_fields(update=True):
|
||||||
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
||||||
@@ -332,7 +333,6 @@ def make_custom_fields(update=True):
|
|||||||
'label': 'Mode of Transport',
|
'label': 'Mode of Transport',
|
||||||
'fieldtype': 'Select',
|
'fieldtype': 'Select',
|
||||||
'options': '\nRoad\nAir\nRail\nShip',
|
'options': '\nRoad\nAir\nRail\nShip',
|
||||||
'default': 'Road',
|
|
||||||
'insert_after': 'transporter_name',
|
'insert_after': 'transporter_name',
|
||||||
'print_hide': 1,
|
'print_hide': 1,
|
||||||
'translatable': 0
|
'translatable': 0
|
||||||
@@ -369,13 +369,34 @@ def make_custom_fields(update=True):
|
|||||||
'fieldname': 'ewaybill',
|
'fieldname': 'ewaybill',
|
||||||
'label': 'e-Way Bill No.',
|
'label': 'e-Way Bill No.',
|
||||||
'fieldtype': 'Data',
|
'fieldtype': 'Data',
|
||||||
'depends_on': 'eval:(doc.docstatus === 1)',
|
'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)',
|
||||||
'allow_on_submit': 1,
|
'allow_on_submit': 1,
|
||||||
'insert_after': 'tax_id',
|
'insert_after': 'tax_id',
|
||||||
'translatable': 0
|
'translatable': 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
si_einvoice_fields = [
|
||||||
|
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='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
|
||||||
|
|
||||||
|
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=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'),
|
||||||
|
|
||||||
|
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 = {
|
custom_fields = {
|
||||||
'Address': [
|
'Address': [
|
||||||
dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
|
dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
|
||||||
@@ -388,7 +409,7 @@ def make_custom_fields(update=True):
|
|||||||
'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields,
|
'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields,
|
||||||
'Purchase Order': purchase_invoice_gst_fields,
|
'Purchase Order': purchase_invoice_gst_fields,
|
||||||
'Purchase Receipt': purchase_invoice_gst_fields,
|
'Purchase Receipt': purchase_invoice_gst_fields,
|
||||||
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
|
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
|
||||||
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
|
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
|
||||||
'Sales Order': sales_invoice_gst_fields,
|
'Sales Order': sales_invoice_gst_fields,
|
||||||
'Tax Category': inter_state_gst_field,
|
'Tax Category': inter_state_gst_field,
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
|
|||||||
tax_category: function(frm) {
|
tax_category: function(frm) {
|
||||||
frm.trigger('get_tax_template');
|
frm.trigger('get_tax_template');
|
||||||
},
|
},
|
||||||
|
customer_address: function(frm) {
|
||||||
|
frm.trigger('get_tax_template');
|
||||||
|
},
|
||||||
get_tax_template: function(frm) {
|
get_tax_template: function(frm) {
|
||||||
if (!frm.doc.company) return;
|
if (!frm.doc.company) return;
|
||||||
|
|
||||||
@@ -16,6 +19,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
|
|||||||
'shipping_address': frm.doc.shipping_address || '',
|
'shipping_address': frm.doc.shipping_address || '',
|
||||||
'shipping_address_name': frm.doc.shipping_address_name || '',
|
'shipping_address_name': frm.doc.shipping_address_name || '',
|
||||||
'customer_address': frm.doc.customer_address || '',
|
'customer_address': frm.doc.customer_address || '',
|
||||||
|
'supplier_address': frm.doc.supplier_address,
|
||||||
'customer': frm.doc.customer,
|
'customer': frm.doc.customer,
|
||||||
'supplier': frm.doc.supplier,
|
'supplier': frm.doc.supplier,
|
||||||
'supplier_gstin': frm.doc.supplier_gstin,
|
'supplier_gstin': frm.doc.supplier_gstin,
|
||||||
@@ -28,12 +32,15 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
|
|||||||
args: {
|
args: {
|
||||||
party_details: JSON.stringify(party_details),
|
party_details: JSON.stringify(party_details),
|
||||||
doctype: frm.doc.doctype,
|
doctype: frm.doc.doctype,
|
||||||
company: frm.doc.company,
|
company: frm.doc.company
|
||||||
return_taxes: 1
|
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
frm.set_value('taxes_and_charges', r.message.taxes_and_charges);
|
frm.set_value('taxes_and_charges', r.message.taxes_and_charges);
|
||||||
|
frm.set_value('place_of_supply', r.message.place_of_supply);
|
||||||
|
} else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) {
|
||||||
|
frm.set_value('taxes_and_charges', '');
|
||||||
|
frm.set_value('taxes', []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping
|
|||||||
from six import string_types
|
from six import string_types
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
|
from frappe.model.utils import get_fetch_values
|
||||||
|
|
||||||
def validate_gstin_for_india(doc, method):
|
def validate_gstin_for_india(doc, method):
|
||||||
if hasattr(doc, 'gst_state') and doc.gst_state:
|
if hasattr(doc, 'gst_state') and doc.gst_state:
|
||||||
@@ -51,6 +52,13 @@ def validate_gstin_for_india(doc, method):
|
|||||||
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
|
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
|
||||||
.format(doc.gst_state_number))
|
.format(doc.gst_state_number))
|
||||||
|
|
||||||
|
def validate_tax_category(doc, method):
|
||||||
|
if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}):
|
||||||
|
if doc.is_inter_state:
|
||||||
|
frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state))
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state))
|
||||||
|
|
||||||
def update_gst_category(doc, method):
|
def update_gst_category(doc, method):
|
||||||
for link in doc.links:
|
for link in doc.links:
|
||||||
if link.link_doctype in ['Customer', 'Supplier']:
|
if link.link_doctype in ['Customer', 'Supplier']:
|
||||||
@@ -85,8 +93,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'):
|
|||||||
total += digit
|
total += digit
|
||||||
factor = 2 if factor == 1 else 1
|
factor = 2 if factor == 1 else 1
|
||||||
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
|
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
|
||||||
frappe.throw(_("""Invalid {0}! The check digit validation has failed.
|
frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
|
||||||
Please ensure you've typed the {0} correctly.""".format(label)))
|
|
||||||
|
|
||||||
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
|
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
|
||||||
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
|
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
|
||||||
@@ -149,24 +156,31 @@ def get_place_of_supply(party_details, doctype):
|
|||||||
return cstr(address.gst_state_number) + "-" + cstr(address.gst_state)
|
return cstr(address.gst_state_number) + "-" + cstr(address.gst_state)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_regional_address_details(party_details, doctype, company, return_taxes=None):
|
def get_regional_address_details(party_details, doctype, company):
|
||||||
|
|
||||||
if isinstance(party_details, string_types):
|
if isinstance(party_details, string_types):
|
||||||
party_details = json.loads(party_details)
|
party_details = json.loads(party_details)
|
||||||
party_details = frappe._dict(party_details)
|
party_details = frappe._dict(party_details)
|
||||||
|
|
||||||
|
update_party_details(party_details, doctype)
|
||||||
|
|
||||||
party_details.place_of_supply = get_place_of_supply(party_details, doctype)
|
party_details.place_of_supply = get_place_of_supply(party_details, doctype)
|
||||||
|
|
||||||
|
if is_internal_transfer(party_details, doctype):
|
||||||
|
party_details.taxes_and_charges = ''
|
||||||
|
party_details.taxes = ''
|
||||||
|
return party_details
|
||||||
|
|
||||||
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
||||||
master_doctype = "Sales Taxes and Charges Template"
|
master_doctype = "Sales Taxes and Charges Template"
|
||||||
|
|
||||||
get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
|
get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
|
||||||
get_tax_template_based_on_category(master_doctype, company, party_details)
|
get_tax_template_based_on_category(master_doctype, company, party_details)
|
||||||
|
|
||||||
if party_details.get('taxes_and_charges') and return_taxes:
|
if party_details.get('taxes_and_charges'):
|
||||||
return party_details
|
return party_details
|
||||||
|
|
||||||
if not party_details.company_gstin:
|
if not party_details.company_gstin:
|
||||||
return
|
return party_details
|
||||||
|
|
||||||
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
|
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
|
||||||
master_doctype = "Purchase Taxes and Charges Template"
|
master_doctype = "Purchase Taxes and Charges Template"
|
||||||
@@ -174,15 +188,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N
|
|||||||
get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
|
get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
|
||||||
get_tax_template_based_on_category(master_doctype, company, party_details)
|
get_tax_template_based_on_category(master_doctype, company, party_details)
|
||||||
|
|
||||||
if party_details.get('taxes_and_charges') and return_taxes:
|
if party_details.get('taxes_and_charges'):
|
||||||
return party_details
|
return party_details
|
||||||
|
|
||||||
if not party_details.supplier_gstin:
|
if not party_details.supplier_gstin:
|
||||||
return
|
return party_details
|
||||||
|
|
||||||
if not party_details.place_of_supply: return
|
if not party_details.place_of_supply: return party_details
|
||||||
|
|
||||||
if not party_details.company_gstin: return
|
if not party_details.company_gstin: return party_details
|
||||||
|
|
||||||
if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
|
if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
|
||||||
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
|
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
|
||||||
@@ -192,12 +206,27 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N
|
|||||||
default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2])
|
default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2])
|
||||||
|
|
||||||
if not default_tax:
|
if not default_tax:
|
||||||
return
|
return party_details
|
||||||
party_details["taxes_and_charges"] = default_tax
|
party_details["taxes_and_charges"] = default_tax
|
||||||
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
|
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
|
||||||
|
|
||||||
if return_taxes:
|
return party_details
|
||||||
return party_details
|
|
||||||
|
def update_party_details(party_details, doctype):
|
||||||
|
for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']:
|
||||||
|
if party_details.get(address_field):
|
||||||
|
party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field)))
|
||||||
|
|
||||||
|
def is_internal_transfer(party_details, doctype):
|
||||||
|
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
||||||
|
destination_gstin = party_details.company_gstin
|
||||||
|
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
|
||||||
|
destination_gstin = party_details.supplier_gstin
|
||||||
|
|
||||||
|
if party_details.gstin == destination_gstin:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
False
|
||||||
|
|
||||||
def get_tax_template_based_on_category(master_doctype, company, party_details):
|
def get_tax_template_based_on_category(master_doctype, company, party_details):
|
||||||
if not party_details.get('tax_category'):
|
if not party_details.get('tax_category'):
|
||||||
@@ -501,6 +530,9 @@ def get_address_details(data, doc, company_address, billing_address):
|
|||||||
data.actualToStateCode = data.toStateCode
|
data.actualToStateCode = data.toStateCode
|
||||||
shipping_address = billing_address
|
shipping_address = billing_address
|
||||||
|
|
||||||
|
if doc.gst_category == 'SEZ':
|
||||||
|
data.toStateCode = 99
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_item_list(data, doc):
|
def get_item_list(data, doc):
|
||||||
@@ -734,4 +766,4 @@ def make_regional_gl_entries(gl_entries, doc):
|
|||||||
}, account_currency, item=tax)
|
}, account_currency, item=tax)
|
||||||
)
|
)
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class Gstr1Report(object):
|
|||||||
{select_columns}
|
{select_columns}
|
||||||
from `tab{doctype}`
|
from `tab{doctype}`
|
||||||
where docstatus = 1 {where_conditions}
|
where docstatus = 1 {where_conditions}
|
||||||
|
and is_opening = 'No'
|
||||||
order by posting_date desc
|
order by posting_date desc
|
||||||
""".format(select_columns=self.select_columns, doctype=self.doctype,
|
""".format(select_columns=self.select_columns, doctype=self.doctype,
|
||||||
where_conditions=conditions), self.filters, as_dict=1)
|
where_conditions=conditions), self.filters, as_dict=1)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Delivery Note': 'Delivery Note',
|
'Delivery Note': 'Delivery Note',
|
||||||
'Pick List': 'Pick List',
|
'Pick List': 'Pick List',
|
||||||
'Sales Invoice': 'Invoice',
|
'Sales Invoice': 'Sales Invoice',
|
||||||
'Material Request': 'Material Request',
|
'Material Request': 'Material Request',
|
||||||
'Purchase Order': 'Purchase Order',
|
'Purchase Order': 'Purchase Order',
|
||||||
'Project': 'Project',
|
'Project': 'Project',
|
||||||
|
|||||||
@@ -1515,6 +1515,9 @@ class POSItems {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_items({start = 0, page_length = 40, search_value='', item_group=this.parent_item_group}={}) {
|
get_items({start = 0, page_length = 40, search_value='', item_group=this.parent_item_group}={}) {
|
||||||
|
if (!this.frm.doc.pos_profile)
|
||||||
|
return;
|
||||||
|
|
||||||
const price_list = this.frm.doc.selling_price_list;
|
const price_list = this.frm.doc.selling_price_list;
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ def delete_company_transactions(company_name):
|
|||||||
tabDocField where fieldtype='Link' and options='Company'"""):
|
tabDocField where fieldtype='Link' and options='Company'"""):
|
||||||
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
|
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
|
||||||
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
||||||
"Purchase Taxes and Charges Template", "POS Profile", 'BOM'):
|
"Purchase Taxes and Charges Template", "POS Profile", 'BOM',
|
||||||
|
"Item default", "Customer", "Supplier"):
|
||||||
delete_for_doctype(doctype, company_name)
|
delete_for_doctype(doctype, company_name)
|
||||||
|
|
||||||
# reset company values
|
# reset company values
|
||||||
|
|||||||
@@ -539,7 +539,8 @@ def make_purchase_invoice(source_name, target_doc=None):
|
|||||||
"doctype": "Purchase Invoice",
|
"doctype": "Purchase Invoice",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"supplier_warehouse":"supplier_warehouse",
|
"supplier_warehouse":"supplier_warehouse",
|
||||||
"is_return": "is_return"
|
"is_return": "is_return",
|
||||||
|
"bill_date": "bill_date"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"docstatus": ["=", 1],
|
"docstatus": ["=", 1],
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
|
|
||||||
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
|
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
|
||||||
serial_no=serial_no, basic_rate=100, do_not_submit=True)
|
serial_no=serial_no, basic_rate=100, do_not_submit=True)
|
||||||
self.assertRaises(SerialNoDuplicateError, se.submit)
|
se.submit()
|
||||||
|
|
||||||
def test_auto_asset_creation(self):
|
def test_auto_asset_creation(self):
|
||||||
asset_item = "Test Asset Item"
|
asset_item = "Test Asset Item"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate
|
from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate, get_link_to_form
|
||||||
from erpnext.stock.get_item_details import get_reserved_qty_for_so
|
from erpnext.stock.get_item_details import get_reserved_qty_for_so
|
||||||
|
|
||||||
from frappe import _, ValidationError
|
from frappe import _, ValidationError
|
||||||
@@ -238,7 +238,7 @@ def validate_serial_no(sle, item_det):
|
|||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
if frappe.db.exists("Serial No", serial_no):
|
if frappe.db.exists("Serial No", serial_no):
|
||||||
sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
|
sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
|
||||||
"delivery_document_no", "delivery_document_type", "warehouse",
|
"delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
|
||||||
"purchase_document_no", "company"], as_dict=1)
|
"purchase_document_no", "company"], as_dict=1)
|
||||||
|
|
||||||
if sr.item_code!=sle.item_code:
|
if sr.item_code!=sle.item_code:
|
||||||
@@ -246,9 +246,10 @@ def validate_serial_no(sle, item_det):
|
|||||||
frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no,
|
frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no,
|
||||||
sle.item_code), SerialNoItemError)
|
sle.item_code), SerialNoItemError)
|
||||||
|
|
||||||
if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle):
|
if cint(sle.actual_qty) > 0 and has_serial_no_exists(sr, sle):
|
||||||
frappe.throw(_("Serial No {0} has already been received").format(serial_no),
|
doc_name = frappe.bold(get_link_to_form(sr.purchase_document_type, sr.purchase_document_no))
|
||||||
SerialNoDuplicateError)
|
frappe.throw(_("Serial No {0} has already been received in the {1} #{2}")
|
||||||
|
.format(frappe.bold(serial_no), sr.purchase_document_type, doc_name), SerialNoDuplicateError)
|
||||||
|
|
||||||
if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
|
if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
|
||||||
and sle.voucher_type == sr.delivery_document_type):
|
and sle.voucher_type == sr.delivery_document_type):
|
||||||
@@ -339,7 +340,7 @@ def validate_so_serial_no(sr, sales_order,):
|
|||||||
only deliver reserved {1} against {0}. Serial No {2} cannot
|
only deliver reserved {1} against {0}. Serial No {2} cannot
|
||||||
be delivered""").format(sales_order, sr.item_code, sr.name))
|
be delivered""").format(sales_order, sr.item_code, sr.name))
|
||||||
|
|
||||||
def has_duplicate_serial_no(sn, sle):
|
def has_serial_no_exists(sn, sle):
|
||||||
if (sn.warehouse and not sle.skip_serial_no_validaiton
|
if (sn.warehouse and not sle.skip_serial_no_validaiton
|
||||||
and sle.voucher_type != 'Stock Reconciliation'):
|
and sle.voucher_type != 'Stock Reconciliation'):
|
||||||
return True
|
return True
|
||||||
@@ -349,12 +350,13 @@ def has_duplicate_serial_no(sn, sle):
|
|||||||
|
|
||||||
status = False
|
status = False
|
||||||
if sn.purchase_document_no:
|
if sn.purchase_document_no:
|
||||||
if sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and \
|
if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and
|
||||||
sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]:
|
sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]):
|
||||||
status = True
|
status = True
|
||||||
|
|
||||||
if status and sle.voucher_type == 'Stock Entry' and \
|
# If status is receipt then system will allow to in-ward the delivered serial no
|
||||||
frappe.db.get_value('Stock Entry', sle.voucher_no, 'purpose') != 'Material Receipt':
|
if (status and sle.voucher_type == 'Stock Entry' and frappe.db.get_value('Stock Entry',
|
||||||
|
sle.voucher_no, 'purpose') in ("Material Receipt", "Material Transfer")):
|
||||||
status = False
|
status = False
|
||||||
|
|
||||||
return status
|
return status
|
||||||
@@ -408,7 +410,7 @@ def auto_make_serial_nos(args):
|
|||||||
if is_new:
|
if is_new:
|
||||||
created_numbers.append(sr.name)
|
created_numbers.append(sr.name)
|
||||||
|
|
||||||
form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
|
form_links = list(map(lambda d: get_link_to_form('Serial No', d), created_numbers))
|
||||||
if len(form_links) == 1:
|
if len(form_links) == 1:
|
||||||
frappe.msgprint(_("Serial No {0} created").format(form_links[0]))
|
frappe.msgprint(_("Serial No {0} created").format(form_links[0]))
|
||||||
elif len(form_links) > 0:
|
elif len(form_links) > 0:
|
||||||
|
|||||||
@@ -322,11 +322,12 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
method: "erpnext.stock.get_item_details.get_serial_no",
|
method: "erpnext.stock.get_item_details.get_serial_no",
|
||||||
args: {"args": args},
|
args: {"args": args},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (!r.exe && r.message){
|
if (!r.exe && r.message) {
|
||||||
frappe.model.set_value(cdt, cdn, "serial_no", r.message);
|
frappe.model.set_value(cdt, cdn, "serial_no", r.message);
|
||||||
}
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -736,6 +737,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fg_completed_qty: function() {
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
|
||||||
get_items: function() {
|
get_items: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
|
if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
|
||||||
@@ -745,6 +750,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
|||||||
// if work order / bom is mentioned, get items
|
// if work order / bom is mentioned, get items
|
||||||
return this.frm.call({
|
return this.frm.call({
|
||||||
doc: me.frm.doc,
|
doc: me.frm.doc,
|
||||||
|
freeze: true,
|
||||||
method: "get_items",
|
method: "get_items",
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) refresh_field("items");
|
if(!r.exc) refresh_field("items");
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ class StockEntry(StockController):
|
|||||||
self.update_transferred_qty()
|
self.update_transferred_qty()
|
||||||
self.update_quality_inspection()
|
self.update_quality_inspection()
|
||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
|
self.delete_linked_stock_entry()
|
||||||
|
|
||||||
def set_job_card_data(self):
|
def set_job_card_data(self):
|
||||||
if self.job_card and not self.work_order:
|
if self.job_card and not self.work_order:
|
||||||
@@ -160,6 +161,12 @@ class StockEntry(StockController):
|
|||||||
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
|
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
|
||||||
.format(self.job_card))
|
.format(self.job_card))
|
||||||
|
|
||||||
|
def delete_linked_stock_entry(self):
|
||||||
|
if self.purpose == "Send to Warehouse":
|
||||||
|
for d in frappe.get_all("Stock Entry", filters={"docstatus": 0,
|
||||||
|
"outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"}):
|
||||||
|
frappe.delete_doc("Stock Entry", d.name)
|
||||||
|
|
||||||
def set_transfer_qty(self):
|
def set_transfer_qty(self):
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if not flt(item.qty):
|
if not flt(item.qty):
|
||||||
@@ -453,7 +460,7 @@ class StockEntry(StockController):
|
|||||||
scrap_material_cost += flt(d.basic_amount)
|
scrap_material_cost += flt(d.basic_amount)
|
||||||
|
|
||||||
number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
|
number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse])
|
||||||
if (fg_basic_rate == 0.0 and number_of_fg_items == 1) or update_finished_item_rate:
|
if number_of_fg_items == 1 or update_finished_item_rate:
|
||||||
self.set_basic_rate_for_finished_goods(raw_material_cost, scrap_material_cost)
|
self.set_basic_rate_for_finished_goods(raw_material_cost, scrap_material_cost)
|
||||||
|
|
||||||
def get_args_for_incoming_rate(self, item):
|
def get_args_for_incoming_rate(self, item):
|
||||||
@@ -1003,31 +1010,31 @@ class StockEntry(StockController):
|
|||||||
wo = frappe.get_doc("Work Order", self.work_order)
|
wo = frappe.get_doc("Work Order", self.work_order)
|
||||||
wo_items = frappe.get_all('Work Order Item',
|
wo_items = frappe.get_all('Work Order Item',
|
||||||
filters={'parent': self.work_order},
|
filters={'parent': self.work_order},
|
||||||
fields=["item_code", "required_qty", "consumed_qty"]
|
fields=["item_code", "required_qty", "consumed_qty", "transferred_qty", "source_warehouse"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
|
||||||
for item in wo_items:
|
for item in wo_items:
|
||||||
qty = item.required_qty
|
|
||||||
|
|
||||||
item_account_details = get_item_defaults(item.item_code, self.company)
|
item_account_details = get_item_defaults(item.item_code, self.company)
|
||||||
# Take into account consumption if there are any.
|
# Take into account consumption if there are any.
|
||||||
if self.purpose == 'Manufacture':
|
|
||||||
req_qty_each = flt(item.required_qty / wo.qty)
|
wo_item_qty = item.transferred_qty or item.required_qty
|
||||||
if (flt(item.consumed_qty) != 0):
|
|
||||||
remaining_qty = flt(item.consumed_qty) - (flt(wo.produced_qty) * req_qty_each)
|
req_qty_each = (
|
||||||
exhaust_qty = req_qty_each * wo.produced_qty
|
(flt(wo_item_qty) - flt(item.consumed_qty)) /
|
||||||
if remaining_qty > exhaust_qty :
|
(flt(work_order_qty) - flt(wo.produced_qty))
|
||||||
if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1:
|
)
|
||||||
qty =0
|
|
||||||
else:
|
qty = req_qty_each * flt(self.fg_completed_qty)
|
||||||
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
|
|
||||||
else:
|
|
||||||
qty = req_qty_each * flt(self.fg_completed_qty)
|
|
||||||
|
|
||||||
if qty > 0:
|
if qty > 0:
|
||||||
|
from_warehouse = wo.wip_warehouse
|
||||||
|
if wo.skip_transfer and not wo.from_wip_warehouse:
|
||||||
|
from_warehouse = item.source_warehouse
|
||||||
|
|
||||||
self.add_to_stock_entry_detail({
|
self.add_to_stock_entry_detail({
|
||||||
item.item_code: {
|
item.item_code: {
|
||||||
"from_warehouse": wo.wip_warehouse,
|
"from_warehouse": from_warehouse,
|
||||||
"to_warehouse": "",
|
"to_warehouse": "",
|
||||||
"qty": qty,
|
"qty": qty,
|
||||||
"item_name": item.item_name,
|
"item_name": item.item_name,
|
||||||
@@ -1108,13 +1115,15 @@ class StockEntry(StockController):
|
|||||||
else:
|
else:
|
||||||
qty = req_qty_each * flt(self.fg_completed_qty)
|
qty = req_qty_each * flt(self.fg_completed_qty)
|
||||||
|
|
||||||
|
|
||||||
elif backflushed_materials.get(item.item_code):
|
elif backflushed_materials.get(item.item_code):
|
||||||
for d in backflushed_materials.get(item.item_code):
|
for d in backflushed_materials.get(item.item_code):
|
||||||
if d.get(item.warehouse):
|
if d.get(item.warehouse):
|
||||||
if (qty > req_qty):
|
if (qty > req_qty):
|
||||||
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
|
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
|
||||||
|
|
||||||
|
if consumed_qty:
|
||||||
|
qty -= consumed_qty
|
||||||
|
|
||||||
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
|
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
|
||||||
qty = frappe.utils.ceil(qty)
|
qty = frappe.utils.ceil(qty)
|
||||||
|
|
||||||
|
|||||||
@@ -918,7 +918,7 @@
|
|||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2019-07-04 01:19:07.738045",
|
"modified": "2020-12-18 19:56:06.343314",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Settings",
|
"name": "Stock Settings",
|
||||||
@@ -949,7 +949,7 @@
|
|||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
"show_name_in_global_search": 0,
|
"show_name_in_global_search": 0,
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"track_changes": 0,
|
"track_changes": 1,
|
||||||
"track_seen": 0,
|
"track_seen": 0,
|
||||||
"track_views": 0
|
"track_views": 0
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ def get_stock_ledger_entries(filters, items):
|
|||||||
select
|
select
|
||||||
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
|
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
|
||||||
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
||||||
sle.item_code as name, sle.voucher_no
|
sle.item_code as name, sle.voucher_no, sle.stock_value
|
||||||
from
|
from
|
||||||
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
||||||
where sle.docstatus < 2 %s %s
|
where sle.docstatus < 2 %s %s
|
||||||
@@ -196,7 +196,7 @@ def get_item_warehouse_map(filters, sle):
|
|||||||
else:
|
else:
|
||||||
qty_diff = flt(d.actual_qty)
|
qty_diff = flt(d.actual_qty)
|
||||||
|
|
||||||
value_diff = flt(d.stock_value_difference)
|
value_diff = flt(d.stock_value) - flt(qty_dict.bal_val)
|
||||||
|
|
||||||
if d.posting_date < from_date:
|
if d.posting_date < from_date:
|
||||||
qty_dict.opening_qty += qty_diff
|
qty_dict.opening_qty += qty_diff
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class ItemConfigure {
|
|||||||
this.dialog.$status_area.empty();
|
this.dialog.$status_area.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info }) {
|
get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info, allow_items_not_in_stock }) {
|
||||||
const exact_match_message = __('1 exact match.');
|
const exact_match_message = __('1 exact match.');
|
||||||
const one_item = exact_match.length === 1 ?
|
const one_item = exact_match.length === 1 ?
|
||||||
exact_match[0] :
|
exact_match[0] :
|
||||||
@@ -194,7 +194,7 @@ class ItemConfigure {
|
|||||||
filtered_items[0] : '';
|
filtered_items[0] : '';
|
||||||
|
|
||||||
// Allow Add to Cart if adding out of stock items enabled in Shopping Cart else check stock.
|
// Allow Add to Cart if adding out of stock items enabled in Shopping Cart else check stock.
|
||||||
const in_stock = product_info.allow_items_not_in_stock ? 1 : product_info.in_stock;
|
const in_stock = allow_items_not_in_stock ? 1 : product_info && product_info.in_stock;
|
||||||
const add_to_cart = `<a href data-action="btn_add_to_cart" data-item-code="${one_item}">${__('Add to cart')}</a>`;
|
const add_to_cart = `<a href data-action="btn_add_to_cart" data-item-code="${one_item}">${__('Add to cart')}</a>`;
|
||||||
const product_action = in_stock ? add_to_cart : `<a style="color:#74808b;">${__('Not in Stock')}</a>`;
|
const product_action = in_stock ? add_to_cart : `<a style="color:#74808b;">${__('Not in Stock')}</a>`;
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ frappe.ready(() => {
|
|||||||
reqd: 1
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __('Pin Code'),
|
label: __('Postal Code'),
|
||||||
fieldname: 'pincode',
|
fieldname: 'pincode',
|
||||||
fieldtype: 'Data'
|
fieldtype: 'Data'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
|
{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
|
||||||
<label>{{ charge.get_formatted("description") }}</label></div>
|
<label>{{ charge.get_formatted("description") }}</label>
|
||||||
|
</div>
|
||||||
<div class="col-xs-7 text-right">
|
<div class="col-xs-7 text-right">
|
||||||
{{ frappe.format_value(frappe.utils.flt(charge.tax_amount),
|
{{ charge.get_formatted('tax_amount', doc) }}
|
||||||
table_meta.get_field("tax_amount"), doc, currency=doc.currency) }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ PyGithub==1.44.1
|
|||||||
python-stdnum==1.12
|
python-stdnum==1.12
|
||||||
Unidecode==1.1.1
|
Unidecode==1.1.1
|
||||||
WooCommerce==2.1.1
|
WooCommerce==2.1.1
|
||||||
|
pycryptodome==3.9.8
|
||||||
Reference in New Issue
Block a user