mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-02 19:59:12 +00:00
Merge remote-tracking branch 'upstream/develop' into remove-india
This commit is contained in:
@@ -13,6 +13,7 @@ def check_deletion_permission(doc, method):
|
||||
if region in ["Nepal", "France"] and doc.docstatus != 0:
|
||||
frappe.throw(_("Deletion is not permitted for country {0}").format(region))
|
||||
|
||||
|
||||
def create_transaction_log(doc, method):
|
||||
"""
|
||||
Appends the transaction to a chain of hashed logs for legal resons.
|
||||
@@ -24,10 +25,11 @@ def create_transaction_log(doc, method):
|
||||
|
||||
data = str(doc.as_dict())
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Transaction Log",
|
||||
"reference_doctype": doc.doctype,
|
||||
"document_name": doc.name,
|
||||
"data": data
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Transaction Log",
|
||||
"reference_doctype": doc.doctype,
|
||||
"document_name": doc.name,
|
||||
"data": data,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
@@ -2,17 +2,20 @@
|
||||
import os
|
||||
import frappe
|
||||
|
||||
|
||||
def set_up_address_templates(default_country=None):
|
||||
for country, html in get_address_templates():
|
||||
is_default = 1 if country == default_country else 0
|
||||
update_address_template(country, html, is_default)
|
||||
|
||||
|
||||
def get_address_templates():
|
||||
"""
|
||||
Return country and path for all HTML files in this directory.
|
||||
|
||||
Returns a list of dicts.
|
||||
"""
|
||||
|
||||
def country(file_name):
|
||||
"""Convert 'united_states.html' to 'United States'."""
|
||||
suffix_pos = file_name.find(".html")
|
||||
@@ -45,9 +48,6 @@ def update_address_template(country, html, is_default=0):
|
||||
frappe.db.set_value("Address Template", country, "template", html)
|
||||
frappe.db.set_value("Address Template", country, "is_default", is_default)
|
||||
else:
|
||||
frappe.get_doc(dict(
|
||||
doctype="Address Template",
|
||||
country=country,
|
||||
is_default=is_default,
|
||||
template=html
|
||||
)).insert()
|
||||
frappe.get_doc(
|
||||
dict(doctype="Address Template", country=country, is_default=is_default, template=html)
|
||||
).insert()
|
||||
|
||||
@@ -9,13 +9,11 @@ def ensure_country(country):
|
||||
if frappe.db.exists("Country", country):
|
||||
return frappe.get_doc("Country", country)
|
||||
else:
|
||||
c = frappe.get_doc({
|
||||
"doctype": "Country",
|
||||
"country_name": country
|
||||
})
|
||||
c = frappe.get_doc({"doctype": "Country", "country_name": country})
|
||||
c.insert()
|
||||
return c
|
||||
|
||||
|
||||
class TestRegionalAddressTemplate(TestCase):
|
||||
def test_get_address_templates(self):
|
||||
"""Get the countries and paths from the templates directory."""
|
||||
@@ -34,11 +32,9 @@ class TestRegionalAddressTemplate(TestCase):
|
||||
"""Update an existing Address Template."""
|
||||
country = ensure_country("Germany")
|
||||
if not frappe.db.exists("Address Template", country.name):
|
||||
template = frappe.get_doc({
|
||||
"doctype": "Address Template",
|
||||
"country": country.name,
|
||||
"template": "EXISTING"
|
||||
}).insert()
|
||||
template = frappe.get_doc(
|
||||
{"doctype": "Address Template", "country": country.name, "template": "EXISTING"}
|
||||
).insert()
|
||||
|
||||
update_address_template(country.name, "NEW")
|
||||
doc = frappe.get_doc("Address Template", country.name)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('DATEV Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.add_custom_button('Show Report', () => frappe.set_route('query-report', 'DATEV'), "fa fa-table");
|
||||
}
|
||||
});
|
||||
@@ -1,125 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:client",
|
||||
"creation": "2019-08-13 23:56:34.259906",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"client",
|
||||
"client_number",
|
||||
"column_break_2",
|
||||
"consultant_number",
|
||||
"consultant",
|
||||
"section_break_4",
|
||||
"account_number_length",
|
||||
"column_break_6",
|
||||
"temporary_against_account_number"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "client",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Client",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "client_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Client ID",
|
||||
"length": 5,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "consultant",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Consultant",
|
||||
"options": "Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname": "consultant_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Consultant ID",
|
||||
"length": 7,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "4",
|
||||
"fieldname": "account_number_length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Account Number Length",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"fieldname": "temporary_against_account_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Temporary Against Account Number",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-11-19 19:00:09.088816",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "DATEV Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
|
||||
class TestDATEVSettings(unittest.TestCase):
|
||||
pass
|
||||
144352
erpnext/regional/doctype/gst_settings/hsn_code_data.json
Normal file
144352
erpnext/regional/doctype/gst_settings/hsn_code_data.json
Normal file
File diff suppressed because it is too large
Load Diff
39
erpnext/regional/doctype/hsn_tax_rate/hsn_tax_rate.json
Normal file
39
erpnext/regional/doctype/hsn_tax_rate/hsn_tax_rate.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2022-05-11 13:32:42.534779",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"minimum_taxable_value",
|
||||
"tax_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "minimum_taxable_value",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Minimum Taxable Value"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "tax_rate",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Rate"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-15 15:37:56.152470",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "HSN Tax Rate",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class DATEVSettings(Document):
|
||||
class HSNTaxRate(Document):
|
||||
pass
|
||||
@@ -27,11 +27,10 @@ class ImportSupplierInvoice(Document):
|
||||
self.name = "Import Invoice on " + format_datetime(self.creation)
|
||||
|
||||
def import_xml_data(self):
|
||||
zip_file = frappe.get_doc("File", {
|
||||
"file_url": self.zip_file,
|
||||
"attached_to_doctype": self.doctype,
|
||||
"attached_to_name": self.name
|
||||
})
|
||||
zip_file = frappe.get_doc(
|
||||
"File",
|
||||
{"file_url": self.zip_file, "attached_to_doctype": self.doctype, "attached_to_name": self.name},
|
||||
)
|
||||
|
||||
self.publish("File Import", _("Processing XML Files"), 1, 3)
|
||||
|
||||
@@ -65,10 +64,10 @@ class ImportSupplierInvoice(Document):
|
||||
"bill_no": line.Numero.text,
|
||||
"total_discount": 0,
|
||||
"items": [],
|
||||
"buying_price_list": self.default_buying_price_list
|
||||
"buying_price_list": self.default_buying_price_list,
|
||||
}
|
||||
|
||||
if not invoices_args.get("bill_no", ''):
|
||||
if not invoices_args.get("bill_no", ""):
|
||||
frappe.throw(_("Numero has not set in the XML file"))
|
||||
|
||||
supp_dict = get_supplier_details(file_content)
|
||||
@@ -84,15 +83,23 @@ class ImportSupplierInvoice(Document):
|
||||
self.file_count += 1
|
||||
if pi_name:
|
||||
self.purchase_invoices_count += 1
|
||||
file_save = save_file(file_name, encoded_content, "Purchase Invoice",
|
||||
pi_name, folder=None, decode=False, is_private=0, df=None)
|
||||
file_save = save_file(
|
||||
file_name,
|
||||
encoded_content,
|
||||
"Purchase Invoice",
|
||||
pi_name,
|
||||
folder=None,
|
||||
decode=False,
|
||||
is_private=0,
|
||||
df=None,
|
||||
)
|
||||
|
||||
def prepare_items_for_invoice(self, file_content, invoices_args):
|
||||
qty = 1
|
||||
rate, tax_rate = [0 ,0]
|
||||
rate, tax_rate = [0, 0]
|
||||
uom = self.default_uom
|
||||
|
||||
#read file for item information
|
||||
# read file for item information
|
||||
for line in file_content.find_all("DettaglioLinee"):
|
||||
if line.find("PrezzoUnitario") and line.find("PrezzoTotale"):
|
||||
rate = flt(line.PrezzoUnitario.text) or 0
|
||||
@@ -103,30 +110,34 @@ class ImportSupplierInvoice(Document):
|
||||
if line.find("UnitaMisura"):
|
||||
uom = create_uom(line.UnitaMisura.text)
|
||||
|
||||
if (rate < 0 and line_total < 0):
|
||||
if rate < 0 and line_total < 0:
|
||||
qty *= -1
|
||||
invoices_args["return_invoice"] = 1
|
||||
|
||||
if line.find("AliquotaIVA"):
|
||||
tax_rate = flt(line.AliquotaIVA.text)
|
||||
|
||||
line_str = re.sub('[^A-Za-z0-9]+', '-', line.Descrizione.text)
|
||||
line_str = re.sub("[^A-Za-z0-9]+", "-", line.Descrizione.text)
|
||||
item_name = line_str[0:140]
|
||||
|
||||
invoices_args['items'].append({
|
||||
"item_code": self.item_code,
|
||||
"item_name": item_name,
|
||||
"description": line_str,
|
||||
"qty": qty,
|
||||
"uom": uom,
|
||||
"rate": abs(rate),
|
||||
"conversion_factor": 1.0,
|
||||
"tax_rate": tax_rate
|
||||
})
|
||||
invoices_args["items"].append(
|
||||
{
|
||||
"item_code": self.item_code,
|
||||
"item_name": item_name,
|
||||
"description": line_str,
|
||||
"qty": qty,
|
||||
"uom": uom,
|
||||
"rate": abs(rate),
|
||||
"conversion_factor": 1.0,
|
||||
"tax_rate": tax_rate,
|
||||
}
|
||||
)
|
||||
|
||||
for disc_line in line.find_all("ScontoMaggiorazione"):
|
||||
if disc_line.find("Percentuale"):
|
||||
invoices_args["total_discount"] += flt((flt(disc_line.Percentuale.text) / 100) * (rate * qty))
|
||||
invoices_args["total_discount"] += flt(
|
||||
(flt(disc_line.Percentuale.text) / 100) * (rate * qty)
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def process_file_data(self):
|
||||
@@ -134,10 +145,13 @@ class ImportSupplierInvoice(Document):
|
||||
frappe.enqueue_doc(self.doctype, self.name, "import_xml_data", queue="long", timeout=3600)
|
||||
|
||||
def publish(self, title, message, count, total):
|
||||
frappe.publish_realtime("import_invoice_update", {"title": title, "message": message, "count": count, "total": total})
|
||||
frappe.publish_realtime(
|
||||
"import_invoice_update", {"title": title, "message": message, "count": count, "total": total}
|
||||
)
|
||||
|
||||
|
||||
def get_file_content(file_name, zip_file_object):
|
||||
content = ''
|
||||
content = ""
|
||||
encoded_content = zip_file_object.read(file_name)
|
||||
|
||||
try:
|
||||
@@ -146,117 +160,126 @@ def get_file_content(file_name, zip_file_object):
|
||||
try:
|
||||
content = encoded_content.decode("utf-16")
|
||||
except UnicodeDecodeError as e:
|
||||
frappe.log_error(message=e, title="UTF-16 encoding error for File Name: " + file_name)
|
||||
frappe.log_error("UTF-16 encoding error for File Name: " + file_name)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def get_supplier_details(file_content):
|
||||
supplier_info = {}
|
||||
for line in file_content.find_all("CedentePrestatore"):
|
||||
supplier_info['tax_id'] = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text
|
||||
supplier_info["tax_id"] = line.DatiAnagrafici.IdPaese.text + line.DatiAnagrafici.IdCodice.text
|
||||
if line.find("CodiceFiscale"):
|
||||
supplier_info['fiscal_code'] = line.DatiAnagrafici.CodiceFiscale.text
|
||||
supplier_info["fiscal_code"] = line.DatiAnagrafici.CodiceFiscale.text
|
||||
|
||||
if line.find("RegimeFiscale"):
|
||||
supplier_info['fiscal_regime'] = line.DatiAnagrafici.RegimeFiscale.text
|
||||
supplier_info["fiscal_regime"] = line.DatiAnagrafici.RegimeFiscale.text
|
||||
|
||||
if line.find("Denominazione"):
|
||||
supplier_info['supplier'] = line.DatiAnagrafici.Anagrafica.Denominazione.text
|
||||
supplier_info["supplier"] = line.DatiAnagrafici.Anagrafica.Denominazione.text
|
||||
|
||||
if line.find("Nome"):
|
||||
supplier_info['supplier'] = (line.DatiAnagrafici.Anagrafica.Nome.text
|
||||
+ " " + line.DatiAnagrafici.Anagrafica.Cognome.text)
|
||||
supplier_info["supplier"] = (
|
||||
line.DatiAnagrafici.Anagrafica.Nome.text + " " + line.DatiAnagrafici.Anagrafica.Cognome.text
|
||||
)
|
||||
|
||||
supplier_info['address_line1'] = line.Sede.Indirizzo.text
|
||||
supplier_info['city'] = line.Sede.Comune.text
|
||||
supplier_info["address_line1"] = line.Sede.Indirizzo.text
|
||||
supplier_info["city"] = line.Sede.Comune.text
|
||||
if line.find("Provincia"):
|
||||
supplier_info['province'] = line.Sede.Provincia.text
|
||||
supplier_info["province"] = line.Sede.Provincia.text
|
||||
|
||||
supplier_info['pin_code'] = line.Sede.CAP.text
|
||||
supplier_info['country'] = get_country(line.Sede.Nazione.text)
|
||||
supplier_info["pin_code"] = line.Sede.CAP.text
|
||||
supplier_info["country"] = get_country(line.Sede.Nazione.text)
|
||||
|
||||
return supplier_info
|
||||
|
||||
|
||||
def get_taxes_from_file(file_content, tax_account):
|
||||
taxes = []
|
||||
#read file for taxes information
|
||||
# read file for taxes information
|
||||
for line in file_content.find_all("DatiRiepilogo"):
|
||||
if line.find("AliquotaIVA"):
|
||||
if line.find("EsigibilitaIVA"):
|
||||
descr = line.EsigibilitaIVA.text
|
||||
else:
|
||||
descr = "None"
|
||||
taxes.append({
|
||||
"charge_type": "Actual",
|
||||
"account_head": tax_account,
|
||||
"tax_rate": flt(line.AliquotaIVA.text) or 0,
|
||||
"description": descr,
|
||||
"tax_amount": flt(line.Imposta.text) if len(line.find("Imposta"))!=0 else 0
|
||||
})
|
||||
taxes.append(
|
||||
{
|
||||
"charge_type": "Actual",
|
||||
"account_head": tax_account,
|
||||
"tax_rate": flt(line.AliquotaIVA.text) or 0,
|
||||
"description": descr,
|
||||
"tax_amount": flt(line.Imposta.text) if len(line.find("Imposta")) != 0 else 0,
|
||||
}
|
||||
)
|
||||
|
||||
return taxes
|
||||
|
||||
|
||||
def get_payment_terms_from_file(file_content):
|
||||
terms = []
|
||||
#Get mode of payment dict from setup
|
||||
mop_options = frappe.get_meta('Mode of Payment').fields[4].options
|
||||
mop_str = re.sub('\n', ',', mop_options)
|
||||
# Get mode of payment dict from setup
|
||||
mop_options = frappe.get_meta("Mode of Payment").fields[4].options
|
||||
mop_str = re.sub("\n", ",", mop_options)
|
||||
mop_dict = dict(item.split("-") for item in mop_str.split(","))
|
||||
#read file for payment information
|
||||
# read file for payment information
|
||||
for line in file_content.find_all("DettaglioPagamento"):
|
||||
mop_code = line.ModalitaPagamento.text + '-' + mop_dict.get(line.ModalitaPagamento.text)
|
||||
mop_code = line.ModalitaPagamento.text + "-" + mop_dict.get(line.ModalitaPagamento.text)
|
||||
if line.find("DataScadenzaPagamento"):
|
||||
due_date = dateutil.parser.parse(line.DataScadenzaPagamento.text).strftime("%Y-%m-%d")
|
||||
else:
|
||||
due_date = today()
|
||||
terms.append({
|
||||
"mode_of_payment_code": mop_code,
|
||||
"bank_account_iban": line.IBAN.text if line.find("IBAN") else "",
|
||||
"due_date": due_date,
|
||||
"payment_amount": line.ImportoPagamento.text
|
||||
})
|
||||
terms.append(
|
||||
{
|
||||
"mode_of_payment_code": mop_code,
|
||||
"bank_account_iban": line.IBAN.text if line.find("IBAN") else "",
|
||||
"due_date": due_date,
|
||||
"payment_amount": line.ImportoPagamento.text,
|
||||
}
|
||||
)
|
||||
|
||||
return terms
|
||||
|
||||
|
||||
def get_destination_code_from_file(file_content):
|
||||
destination_code = ''
|
||||
destination_code = ""
|
||||
for line in file_content.find_all("DatiTrasmissione"):
|
||||
destination_code = line.CodiceDestinatario.text
|
||||
|
||||
return destination_code
|
||||
|
||||
|
||||
def create_supplier(supplier_group, args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
existing_supplier_name = frappe.db.get_value("Supplier",
|
||||
filters={"tax_id": args.tax_id}, fieldname="name")
|
||||
existing_supplier_name = frappe.db.get_value(
|
||||
"Supplier", filters={"tax_id": args.tax_id}, fieldname="name"
|
||||
)
|
||||
if existing_supplier_name:
|
||||
pass
|
||||
else:
|
||||
existing_supplier_name = frappe.db.get_value("Supplier",
|
||||
filters={"name": args.supplier}, fieldname="name")
|
||||
existing_supplier_name = frappe.db.get_value(
|
||||
"Supplier", filters={"name": args.supplier}, fieldname="name"
|
||||
)
|
||||
|
||||
if existing_supplier_name:
|
||||
filters = [
|
||||
["Dynamic Link", "link_doctype", "=", "Supplier"],
|
||||
["Dynamic Link", "link_name", "=", args.existing_supplier_name],
|
||||
["Dynamic Link", "parenttype", "=", "Contact"]
|
||||
]
|
||||
["Dynamic Link", "link_doctype", "=", "Supplier"],
|
||||
["Dynamic Link", "link_name", "=", args.existing_supplier_name],
|
||||
["Dynamic Link", "parenttype", "=", "Contact"],
|
||||
]
|
||||
|
||||
if not frappe.get_list("Contact", filters):
|
||||
new_contact = frappe.new_doc("Contact")
|
||||
new_contact.first_name = args.supplier[:30]
|
||||
new_contact.append('links', {
|
||||
"link_doctype": "Supplier",
|
||||
"link_name": existing_supplier_name
|
||||
})
|
||||
new_contact.append("links", {"link_doctype": "Supplier", "link_name": existing_supplier_name})
|
||||
new_contact.insert(ignore_mandatory=True)
|
||||
|
||||
return existing_supplier_name
|
||||
else:
|
||||
|
||||
new_supplier = frappe.new_doc("Supplier")
|
||||
new_supplier.supplier_name = re.sub('&', '&', args.supplier)
|
||||
new_supplier.supplier_name = re.sub("&", "&", args.supplier)
|
||||
new_supplier.supplier_group = supplier_group
|
||||
new_supplier.tax_id = args.tax_id
|
||||
new_supplier.fiscal_code = args.fiscal_code
|
||||
@@ -265,23 +288,21 @@ def create_supplier(supplier_group, args):
|
||||
|
||||
new_contact = frappe.new_doc("Contact")
|
||||
new_contact.first_name = args.supplier[:30]
|
||||
new_contact.append('links', {
|
||||
"link_doctype": "Supplier",
|
||||
"link_name": new_supplier.name
|
||||
})
|
||||
new_contact.append("links", {"link_doctype": "Supplier", "link_name": new_supplier.name})
|
||||
|
||||
new_contact.insert(ignore_mandatory=True)
|
||||
|
||||
return new_supplier.name
|
||||
|
||||
|
||||
def create_address(supplier_name, args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
filters = [
|
||||
["Dynamic Link", "link_doctype", "=", "Supplier"],
|
||||
["Dynamic Link", "link_name", "=", supplier_name],
|
||||
["Dynamic Link", "parenttype", "=", "Address"]
|
||||
]
|
||||
["Dynamic Link", "link_doctype", "=", "Supplier"],
|
||||
["Dynamic Link", "link_name", "=", supplier_name],
|
||||
["Dynamic Link", "parenttype", "=", "Address"],
|
||||
]
|
||||
|
||||
existing_address = frappe.get_list("Address", filters)
|
||||
|
||||
@@ -300,50 +321,52 @@ def create_address(supplier_name, args):
|
||||
|
||||
for address in existing_address:
|
||||
address_doc = frappe.get_doc("Address", address["name"])
|
||||
if (address_doc.address_line1 == new_address_doc.address_line1 and
|
||||
address_doc.pincode == new_address_doc.pincode):
|
||||
if (
|
||||
address_doc.address_line1 == new_address_doc.address_line1
|
||||
and address_doc.pincode == new_address_doc.pincode
|
||||
):
|
||||
return address
|
||||
|
||||
new_address_doc.append("links", {
|
||||
"link_doctype": "Supplier",
|
||||
"link_name": supplier_name
|
||||
})
|
||||
new_address_doc.append("links", {"link_doctype": "Supplier", "link_name": supplier_name})
|
||||
new_address_doc.address_type = "Billing"
|
||||
new_address_doc.insert(ignore_mandatory=True)
|
||||
return new_address_doc.name
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def create_purchase_invoice(supplier_name, file_name, args, name):
|
||||
args = frappe._dict(args)
|
||||
pi = frappe.get_doc({
|
||||
"doctype": "Purchase Invoice",
|
||||
"company": args.company,
|
||||
"currency": erpnext.get_company_currency(args.company),
|
||||
"naming_series": args.naming_series,
|
||||
"supplier": supplier_name,
|
||||
"is_return": args.is_return,
|
||||
"posting_date": today(),
|
||||
"bill_no": args.bill_no,
|
||||
"buying_price_list": args.buying_price_list,
|
||||
"bill_date": args.bill_date,
|
||||
"destination_code": args.destination_code,
|
||||
"document_type": args.document_type,
|
||||
"disable_rounded_total": 1,
|
||||
"items": args["items"],
|
||||
"taxes": args["taxes"]
|
||||
})
|
||||
pi = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Invoice",
|
||||
"company": args.company,
|
||||
"currency": erpnext.get_company_currency(args.company),
|
||||
"naming_series": args.naming_series,
|
||||
"supplier": supplier_name,
|
||||
"is_return": args.is_return,
|
||||
"posting_date": today(),
|
||||
"bill_no": args.bill_no,
|
||||
"buying_price_list": args.buying_price_list,
|
||||
"bill_date": args.bill_date,
|
||||
"destination_code": args.destination_code,
|
||||
"document_type": args.document_type,
|
||||
"disable_rounded_total": 1,
|
||||
"items": args["items"],
|
||||
"taxes": args["taxes"],
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
pi.set_missing_values()
|
||||
pi.insert(ignore_mandatory=True)
|
||||
|
||||
#if discount exists in file, apply any discount on grand total
|
||||
# if discount exists in file, apply any discount on grand total
|
||||
if args.total_discount > 0:
|
||||
pi.apply_discount_on = "Grand Total"
|
||||
pi.discount_amount = args.total_discount
|
||||
pi.save()
|
||||
#adjust payment amount to match with grand total calculated
|
||||
# adjust payment amount to match with grand total calculated
|
||||
calc_total = 0
|
||||
adj = 0
|
||||
for term in args.terms:
|
||||
@@ -352,31 +375,35 @@ def create_purchase_invoice(supplier_name, file_name, args, name):
|
||||
adj = calc_total - flt(pi.grand_total)
|
||||
pi.payment_schedule = []
|
||||
for term in args.terms:
|
||||
pi.append('payment_schedule',{"mode_of_payment_code": term["mode_of_payment_code"],
|
||||
"bank_account_iban": term["bank_account_iban"],
|
||||
"due_date": term["due_date"],
|
||||
"payment_amount": flt(term["payment_amount"]) - adj })
|
||||
pi.append(
|
||||
"payment_schedule",
|
||||
{
|
||||
"mode_of_payment_code": term["mode_of_payment_code"],
|
||||
"bank_account_iban": term["bank_account_iban"],
|
||||
"due_date": term["due_date"],
|
||||
"payment_amount": flt(term["payment_amount"]) - adj,
|
||||
},
|
||||
)
|
||||
adj = 0
|
||||
pi.imported_grand_total = calc_total
|
||||
pi.save()
|
||||
return pi.name
|
||||
except Exception as e:
|
||||
frappe.db.set_value("Import Supplier Invoice", name, "status", "Error")
|
||||
frappe.log_error(message=e,
|
||||
title="Create Purchase Invoice: " + args.get("bill_no") + "File Name: " + file_name)
|
||||
pi.log_error("Unable to create Puchase Invoice")
|
||||
return None
|
||||
|
||||
|
||||
def get_country(code):
|
||||
existing_country_name = frappe.db.get_value("Country",
|
||||
filters={"code": code}, fieldname="name")
|
||||
existing_country_name = frappe.db.get_value("Country", filters={"code": code}, fieldname="name")
|
||||
if existing_country_name:
|
||||
return existing_country_name
|
||||
else:
|
||||
frappe.throw(_("Country Code in File does not match with country code set up in the system"))
|
||||
|
||||
|
||||
def create_uom(uom):
|
||||
existing_uom = frappe.db.get_value("UOM",
|
||||
filters={"uom_name": uom}, fieldname="uom_name")
|
||||
existing_uom = frappe.db.get_value("UOM", filters={"uom_name": uom}, fieldname="uom_name")
|
||||
if existing_uom:
|
||||
return existing_uom
|
||||
else:
|
||||
|
||||
@@ -5,28 +5,26 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_custom_roles_for_reports()
|
||||
|
||||
|
||||
def make_custom_fields():
|
||||
custom_fields = {
|
||||
'Company': [
|
||||
dict(fieldname='siren_number', label='SIREN Number',
|
||||
fieldtype='Data', insert_after='website')
|
||||
"Company": [
|
||||
dict(fieldname="siren_number", label="SIREN Number", fieldtype="Data", insert_after="website")
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields)
|
||||
|
||||
def add_custom_roles_for_reports():
|
||||
report_name = 'Fichier des Ecritures Comptables [FEC]'
|
||||
|
||||
if not frappe.db.get_value('Custom Role', dict(report=report_name)):
|
||||
frappe.get_doc(dict(
|
||||
doctype='Custom Role',
|
||||
report=report_name,
|
||||
roles= [
|
||||
dict(role='Accounts Manager')
|
||||
]
|
||||
)).insert()
|
||||
def add_custom_roles_for_reports():
|
||||
report_name = "Fichier des Ecritures Comptables [FEC]"
|
||||
|
||||
if not frappe.db.get_value("Custom Role", dict(report=report_name)):
|
||||
frappe.get_doc(
|
||||
dict(doctype="Custom Role", report=report_name, roles=[dict(role="Accounts Manager")])
|
||||
).insert()
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
|
||||
# don't remove this function it is used in tests
|
||||
def test_method():
|
||||
'''test function'''
|
||||
return 'overridden'
|
||||
"""test function"""
|
||||
return "overridden"
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_custom_roles_for_reports()
|
||||
|
||||
|
||||
def make_custom_fields():
|
||||
custom_fields = {
|
||||
'Party Account': [
|
||||
dict(fieldname='debtor_creditor_number', label='Debtor/Creditor Number',
|
||||
fieldtype='Data', insert_after='account', translatable=0)
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields)
|
||||
|
||||
|
||||
def add_custom_roles_for_reports():
|
||||
"""Add Access Control to UAE VAT 201."""
|
||||
if not frappe.db.get_value('Custom Role', dict(report='DATEV')):
|
||||
frappe.get_doc(dict(
|
||||
doctype='Custom Role',
|
||||
report='DATEV',
|
||||
roles= [
|
||||
dict(role='Accounts User'),
|
||||
dict(role='Accounts Manager')
|
||||
]
|
||||
)).insert()
|
||||
@@ -1,496 +0,0 @@
|
||||
"""Constants used in datev.py."""
|
||||
|
||||
TRANSACTION_COLUMNS = [
|
||||
# All possible columns must tbe listed here, because DATEV requires them to
|
||||
# be present in the CSV.
|
||||
# ---
|
||||
# Umsatz
|
||||
"Umsatz (ohne Soll/Haben-Kz)",
|
||||
"Soll/Haben-Kennzeichen",
|
||||
"WKZ Umsatz",
|
||||
"Kurs",
|
||||
"Basis-Umsatz",
|
||||
"WKZ Basis-Umsatz",
|
||||
# Konto/Gegenkonto
|
||||
"Konto",
|
||||
"Gegenkonto (ohne BU-Schlüssel)",
|
||||
"BU-Schlüssel",
|
||||
# Datum
|
||||
"Belegdatum",
|
||||
# Rechnungs- / Belegnummer
|
||||
"Belegfeld 1",
|
||||
# z.B. Fälligkeitsdatum Format: TTMMJJ
|
||||
"Belegfeld 2",
|
||||
# Skonto-Betrag / -Abzug (Der Wert 0 ist unzulässig)
|
||||
"Skonto",
|
||||
# Beschreibung des Buchungssatzes
|
||||
"Buchungstext",
|
||||
# Mahn- / Zahl-Sperre (1 = Postensperre)
|
||||
"Postensperre",
|
||||
"Diverse Adressnummer",
|
||||
"Geschäftspartnerbank",
|
||||
"Sachverhalt",
|
||||
# Keine Mahnzinsen
|
||||
"Zinssperre",
|
||||
# Link auf den Buchungsbeleg (Programmkürzel + GUID)
|
||||
"Beleglink",
|
||||
# Beleginfo
|
||||
"Beleginfo - Art 1",
|
||||
"Beleginfo - Inhalt 1",
|
||||
"Beleginfo - Art 2",
|
||||
"Beleginfo - Inhalt 2",
|
||||
"Beleginfo - Art 3",
|
||||
"Beleginfo - Inhalt 3",
|
||||
"Beleginfo - Art 4",
|
||||
"Beleginfo - Inhalt 4",
|
||||
"Beleginfo - Art 5",
|
||||
"Beleginfo - Inhalt 5",
|
||||
"Beleginfo - Art 6",
|
||||
"Beleginfo - Inhalt 6",
|
||||
"Beleginfo - Art 7",
|
||||
"Beleginfo - Inhalt 7",
|
||||
"Beleginfo - Art 8",
|
||||
"Beleginfo - Inhalt 8",
|
||||
# Zuordnung des Geschäftsvorfalls für die Kostenrechnung
|
||||
"KOST1 - Kostenstelle",
|
||||
"KOST2 - Kostenstelle",
|
||||
"KOST-Menge",
|
||||
# USt-ID-Nummer (Beispiel: DE133546770)
|
||||
"EU-Mitgliedstaat u. USt-IdNr.",
|
||||
# Der im EU-Bestimmungsland gültige Steuersatz
|
||||
"EU-Steuersatz",
|
||||
# I = Ist-Versteuerung,
|
||||
# K = keine Umsatzsteuerrechnung
|
||||
# P = Pauschalierung (z. B. für Land- und Forstwirtschaft),
|
||||
# S = Soll-Versteuerung
|
||||
"Abw. Versteuerungsart",
|
||||
# Sachverhalte gem. § 13b Abs. 1 Satz 1 Nrn. 1.-5. UStG
|
||||
"Sachverhalt L+L",
|
||||
# Steuersatz / Funktion zum L+L-Sachverhalt (Beispiel: Wert 190 für 19%)
|
||||
"Funktionsergänzung L+L",
|
||||
# Bei Verwendung des BU-Schlüssels 49 für „andere Steuersätze“ muss der
|
||||
# steuerliche Sachverhalt mitgegeben werden
|
||||
"BU 49 Hauptfunktionstyp",
|
||||
"BU 49 Hauptfunktionsnummer",
|
||||
"BU 49 Funktionsergänzung",
|
||||
# Zusatzinformationen, besitzen den Charakter eines Notizzettels und können
|
||||
# frei erfasst werden.
|
||||
"Zusatzinformation - Art 1",
|
||||
"Zusatzinformation - Inhalt 1",
|
||||
"Zusatzinformation - Art 2",
|
||||
"Zusatzinformation - Inhalt 2",
|
||||
"Zusatzinformation - Art 3",
|
||||
"Zusatzinformation - Inhalt 3",
|
||||
"Zusatzinformation - Art 4",
|
||||
"Zusatzinformation - Inhalt 4",
|
||||
"Zusatzinformation - Art 5",
|
||||
"Zusatzinformation - Inhalt 5",
|
||||
"Zusatzinformation - Art 6",
|
||||
"Zusatzinformation - Inhalt 6",
|
||||
"Zusatzinformation - Art 7",
|
||||
"Zusatzinformation - Inhalt 7",
|
||||
"Zusatzinformation - Art 8",
|
||||
"Zusatzinformation - Inhalt 8",
|
||||
"Zusatzinformation - Art 9",
|
||||
"Zusatzinformation - Inhalt 9",
|
||||
"Zusatzinformation - Art 10",
|
||||
"Zusatzinformation - Inhalt 10",
|
||||
"Zusatzinformation - Art 11",
|
||||
"Zusatzinformation - Inhalt 11",
|
||||
"Zusatzinformation - Art 12",
|
||||
"Zusatzinformation - Inhalt 12",
|
||||
"Zusatzinformation - Art 13",
|
||||
"Zusatzinformation - Inhalt 13",
|
||||
"Zusatzinformation - Art 14",
|
||||
"Zusatzinformation - Inhalt 14",
|
||||
"Zusatzinformation - Art 15",
|
||||
"Zusatzinformation - Inhalt 15",
|
||||
"Zusatzinformation - Art 16",
|
||||
"Zusatzinformation - Inhalt 16",
|
||||
"Zusatzinformation - Art 17",
|
||||
"Zusatzinformation - Inhalt 17",
|
||||
"Zusatzinformation - Art 18",
|
||||
"Zusatzinformation - Inhalt 18",
|
||||
"Zusatzinformation - Art 19",
|
||||
"Zusatzinformation - Inhalt 19",
|
||||
"Zusatzinformation - Art 20",
|
||||
"Zusatzinformation - Inhalt 20",
|
||||
# Wirkt sich nur bei Sachverhalt mit SKR 14 Land- und Forstwirtschaft aus,
|
||||
# für andere SKR werden die Felder beim Import / Export überlesen bzw.
|
||||
# leer exportiert.
|
||||
"Stück",
|
||||
"Gewicht",
|
||||
# 1 = Lastschrift
|
||||
# 2 = Mahnung
|
||||
# 3 = Zahlung
|
||||
"Zahlweise",
|
||||
"Forderungsart",
|
||||
# JJJJ
|
||||
"Veranlagungsjahr",
|
||||
# TTMMJJJJ
|
||||
"Zugeordnete Fälligkeit",
|
||||
# 1 = Einkauf von Waren
|
||||
# 2 = Erwerb von Roh-Hilfs- und Betriebsstoffen
|
||||
"Skontotyp",
|
||||
# Allgemeine Bezeichnung, des Auftrags / Projekts.
|
||||
"Auftragsnummer",
|
||||
# AA = Angeforderte Anzahlung / Abschlagsrechnung
|
||||
# AG = Erhaltene Anzahlung (Geldeingang)
|
||||
# AV = Erhaltene Anzahlung (Verbindlichkeit)
|
||||
# SR = Schlussrechnung
|
||||
# SU = Schlussrechnung (Umbuchung)
|
||||
# SG = Schlussrechnung (Geldeingang)
|
||||
# SO = Sonstige
|
||||
"Buchungstyp",
|
||||
"USt-Schlüssel (Anzahlungen)",
|
||||
"EU-Mitgliedstaat (Anzahlungen)",
|
||||
"Sachverhalt L+L (Anzahlungen)",
|
||||
"EU-Steuersatz (Anzahlungen)",
|
||||
"Erlöskonto (Anzahlungen)",
|
||||
# Wird beim Import durch SV (Stapelverarbeitung) ersetzt.
|
||||
"Herkunft-Kz",
|
||||
# Wird von DATEV verwendet.
|
||||
"Leerfeld",
|
||||
# Format TTMMJJJJ
|
||||
"KOST-Datum",
|
||||
# Vom Zahlungsempfänger individuell vergebenes Kennzeichen eines Mandats
|
||||
# (z.B. Rechnungs- oder Kundennummer).
|
||||
"SEPA-Mandatsreferenz",
|
||||
# 1 = Skontosperre
|
||||
# 0 = Keine Skontosperre
|
||||
"Skontosperre",
|
||||
# Gesellschafter und Sonderbilanzsachverhalt
|
||||
"Gesellschaftername",
|
||||
# Amtliche Nummer aus der Feststellungserklärung
|
||||
"Beteiligtennummer",
|
||||
"Identifikationsnummer",
|
||||
"Zeichnernummer",
|
||||
# Format TTMMJJJJ
|
||||
"Postensperre bis",
|
||||
# Gesellschafter und Sonderbilanzsachverhalt
|
||||
"Bezeichnung SoBil-Sachverhalt",
|
||||
"Kennzeichen SoBil-Buchung",
|
||||
# 0 = keine Festschreibung
|
||||
# 1 = Festschreibung
|
||||
"Festschreibung",
|
||||
# Format TTMMJJJJ
|
||||
"Leistungsdatum",
|
||||
# Format TTMMJJJJ
|
||||
"Datum Zuord. Steuerperiode",
|
||||
# OPOS-Informationen, Format TTMMJJJJ
|
||||
"Fälligkeit",
|
||||
# G oder 1 = Generalumkehr
|
||||
# 0 = keine Generalumkehr
|
||||
"Generalumkehr (GU)",
|
||||
# Steuersatz für Steuerschlüssel
|
||||
"Steuersatz",
|
||||
# Beispiel: DE für Deutschland
|
||||
"Land"
|
||||
]
|
||||
|
||||
DEBTOR_CREDITOR_COLUMNS = [
|
||||
# All possible columns must tbe listed here, because DATEV requires them to
|
||||
# be present in the CSV.
|
||||
# Columns "Leerfeld" have been replaced with "Leerfeld #" to not confuse pandas
|
||||
# ---
|
||||
"Konto",
|
||||
"Name (Adressatentyp Unternehmen)",
|
||||
"Unternehmensgegenstand",
|
||||
"Name (Adressatentyp natürl. Person)",
|
||||
"Vorname (Adressatentyp natürl. Person)",
|
||||
"Name (Adressatentyp keine Angabe)",
|
||||
"Adressatentyp",
|
||||
"Kurzbezeichnung",
|
||||
"EU-Land",
|
||||
"EU-USt-IdNr.",
|
||||
"Anrede",
|
||||
"Titel/Akad. Grad",
|
||||
"Adelstitel",
|
||||
"Namensvorsatz",
|
||||
"Adressart",
|
||||
"Straße",
|
||||
"Postfach",
|
||||
"Postleitzahl",
|
||||
"Ort",
|
||||
"Land",
|
||||
"Versandzusatz",
|
||||
"Adresszusatz",
|
||||
"Abweichende Anrede",
|
||||
"Abw. Zustellbezeichnung 1",
|
||||
"Abw. Zustellbezeichnung 2",
|
||||
"Kennz. Korrespondenzadresse",
|
||||
"Adresse gültig von",
|
||||
"Adresse gültig bis",
|
||||
"Telefon",
|
||||
"Bemerkung (Telefon)",
|
||||
"Telefon Geschäftsleitung",
|
||||
"Bemerkung (Telefon GL)",
|
||||
"E-Mail",
|
||||
"Bemerkung (E-Mail)",
|
||||
"Internet",
|
||||
"Bemerkung (Internet)",
|
||||
"Fax",
|
||||
"Bemerkung (Fax)",
|
||||
"Sonstige",
|
||||
"Bemerkung (Sonstige)",
|
||||
"Bankleitzahl 1",
|
||||
"Bankbezeichnung 1",
|
||||
"Bankkonto-Nummer 1",
|
||||
"Länderkennzeichen 1",
|
||||
"IBAN 1",
|
||||
"Leerfeld 1",
|
||||
"SWIFT-Code 1",
|
||||
"Abw. Kontoinhaber 1",
|
||||
"Kennz. Haupt-Bankverb. 1",
|
||||
"Bankverb. 1 Gültig von",
|
||||
"Bankverb. 1 Gültig bis",
|
||||
"Bankleitzahl 2",
|
||||
"Bankbezeichnung 2",
|
||||
"Bankkonto-Nummer 2",
|
||||
"Länderkennzeichen 2",
|
||||
"IBAN 2",
|
||||
"Leerfeld 2",
|
||||
"SWIFT-Code 2",
|
||||
"Abw. Kontoinhaber 2",
|
||||
"Kennz. Haupt-Bankverb. 2",
|
||||
"Bankverb. 2 gültig von",
|
||||
"Bankverb. 2 gültig bis",
|
||||
"Bankleitzahl 3",
|
||||
"Bankbezeichnung 3",
|
||||
"Bankkonto-Nummer 3",
|
||||
"Länderkennzeichen 3",
|
||||
"IBAN 3",
|
||||
"Leerfeld 3",
|
||||
"SWIFT-Code 3",
|
||||
"Abw. Kontoinhaber 3",
|
||||
"Kennz. Haupt-Bankverb. 3",
|
||||
"Bankverb. 3 gültig von",
|
||||
"Bankverb. 3 gültig bis",
|
||||
"Bankleitzahl 4",
|
||||
"Bankbezeichnung 4",
|
||||
"Bankkonto-Nummer 4",
|
||||
"Länderkennzeichen 4",
|
||||
"IBAN 4",
|
||||
"Leerfeld 4",
|
||||
"SWIFT-Code 4",
|
||||
"Abw. Kontoinhaber 4",
|
||||
"Kennz. Haupt-Bankverb. 4",
|
||||
"Bankverb. 4 Gültig von",
|
||||
"Bankverb. 4 Gültig bis",
|
||||
"Bankleitzahl 5",
|
||||
"Bankbezeichnung 5",
|
||||
"Bankkonto-Nummer 5",
|
||||
"Länderkennzeichen 5",
|
||||
"IBAN 5",
|
||||
"Leerfeld 5",
|
||||
"SWIFT-Code 5",
|
||||
"Abw. Kontoinhaber 5",
|
||||
"Kennz. Haupt-Bankverb. 5",
|
||||
"Bankverb. 5 gültig von",
|
||||
"Bankverb. 5 gültig bis",
|
||||
"Leerfeld 6",
|
||||
"Briefanrede",
|
||||
"Grußformel",
|
||||
"Kundennummer",
|
||||
"Steuernummer",
|
||||
"Sprache",
|
||||
"Ansprechpartner",
|
||||
"Vertreter",
|
||||
"Sachbearbeiter",
|
||||
"Diverse-Konto",
|
||||
"Ausgabeziel",
|
||||
"Währungssteuerung",
|
||||
"Kreditlimit (Debitor)",
|
||||
"Zahlungsbedingung",
|
||||
"Fälligkeit in Tagen (Debitor)",
|
||||
"Skonto in Prozent (Debitor)",
|
||||
"Kreditoren-Ziel 1 (Tage)",
|
||||
"Kreditoren-Skonto 1 (%)",
|
||||
"Kreditoren-Ziel 2 (Tage)",
|
||||
"Kreditoren-Skonto 2 (%)",
|
||||
"Kreditoren-Ziel 3 Brutto (Tage)",
|
||||
"Kreditoren-Ziel 4 (Tage)",
|
||||
"Kreditoren-Skonto 4 (%)",
|
||||
"Kreditoren-Ziel 5 (Tage)",
|
||||
"Kreditoren-Skonto 5 (%)",
|
||||
"Mahnung",
|
||||
"Kontoauszug",
|
||||
"Mahntext 1",
|
||||
"Mahntext 2",
|
||||
"Mahntext 3",
|
||||
"Kontoauszugstext",
|
||||
"Mahnlimit Betrag",
|
||||
"Mahnlimit %",
|
||||
"Zinsberechnung",
|
||||
"Mahnzinssatz 1",
|
||||
"Mahnzinssatz 2",
|
||||
"Mahnzinssatz 3",
|
||||
"Lastschrift",
|
||||
"Verfahren",
|
||||
"Mandantenbank",
|
||||
"Zahlungsträger",
|
||||
"Indiv. Feld 1",
|
||||
"Indiv. Feld 2",
|
||||
"Indiv. Feld 3",
|
||||
"Indiv. Feld 4",
|
||||
"Indiv. Feld 5",
|
||||
"Indiv. Feld 6",
|
||||
"Indiv. Feld 7",
|
||||
"Indiv. Feld 8",
|
||||
"Indiv. Feld 9",
|
||||
"Indiv. Feld 10",
|
||||
"Indiv. Feld 11",
|
||||
"Indiv. Feld 12",
|
||||
"Indiv. Feld 13",
|
||||
"Indiv. Feld 14",
|
||||
"Indiv. Feld 15",
|
||||
"Abweichende Anrede (Rechnungsadresse)",
|
||||
"Adressart (Rechnungsadresse)",
|
||||
"Straße (Rechnungsadresse)",
|
||||
"Postfach (Rechnungsadresse)",
|
||||
"Postleitzahl (Rechnungsadresse)",
|
||||
"Ort (Rechnungsadresse)",
|
||||
"Land (Rechnungsadresse)",
|
||||
"Versandzusatz (Rechnungsadresse)",
|
||||
"Adresszusatz (Rechnungsadresse)",
|
||||
"Abw. Zustellbezeichnung 1 (Rechnungsadresse)",
|
||||
"Abw. Zustellbezeichnung 2 (Rechnungsadresse)",
|
||||
"Adresse Gültig von (Rechnungsadresse)",
|
||||
"Adresse Gültig bis (Rechnungsadresse)",
|
||||
"Bankleitzahl 6",
|
||||
"Bankbezeichnung 6",
|
||||
"Bankkonto-Nummer 6",
|
||||
"Länderkennzeichen 6",
|
||||
"IBAN 6",
|
||||
"Leerfeld 7",
|
||||
"SWIFT-Code 6",
|
||||
"Abw. Kontoinhaber 6",
|
||||
"Kennz. Haupt-Bankverb. 6",
|
||||
"Bankverb 6 gültig von",
|
||||
"Bankverb 6 gültig bis",
|
||||
"Bankleitzahl 7",
|
||||
"Bankbezeichnung 7",
|
||||
"Bankkonto-Nummer 7",
|
||||
"Länderkennzeichen 7",
|
||||
"IBAN 7",
|
||||
"Leerfeld 8",
|
||||
"SWIFT-Code 7",
|
||||
"Abw. Kontoinhaber 7",
|
||||
"Kennz. Haupt-Bankverb. 7",
|
||||
"Bankverb 7 gültig von",
|
||||
"Bankverb 7 gültig bis",
|
||||
"Bankleitzahl 8",
|
||||
"Bankbezeichnung 8",
|
||||
"Bankkonto-Nummer 8",
|
||||
"Länderkennzeichen 8",
|
||||
"IBAN 8",
|
||||
"Leerfeld 9",
|
||||
"SWIFT-Code 8",
|
||||
"Abw. Kontoinhaber 8",
|
||||
"Kennz. Haupt-Bankverb. 8",
|
||||
"Bankverb 8 gültig von",
|
||||
"Bankverb 8 gültig bis",
|
||||
"Bankleitzahl 9",
|
||||
"Bankbezeichnung 9",
|
||||
"Bankkonto-Nummer 9",
|
||||
"Länderkennzeichen 9",
|
||||
"IBAN 9",
|
||||
"Leerfeld 10",
|
||||
"SWIFT-Code 9",
|
||||
"Abw. Kontoinhaber 9",
|
||||
"Kennz. Haupt-Bankverb. 9",
|
||||
"Bankverb 9 gültig von",
|
||||
"Bankverb 9 gültig bis",
|
||||
"Bankleitzahl 10",
|
||||
"Bankbezeichnung 10",
|
||||
"Bankkonto-Nummer 10",
|
||||
"Länderkennzeichen 10",
|
||||
"IBAN 10",
|
||||
"Leerfeld 11",
|
||||
"SWIFT-Code 10",
|
||||
"Abw. Kontoinhaber 10",
|
||||
"Kennz. Haupt-Bankverb. 10",
|
||||
"Bankverb 10 gültig von",
|
||||
"Bankverb 10 gültig bis",
|
||||
"Nummer Fremdsystem",
|
||||
"Insolvent",
|
||||
"SEPA-Mandatsreferenz 1",
|
||||
"SEPA-Mandatsreferenz 2",
|
||||
"SEPA-Mandatsreferenz 3",
|
||||
"SEPA-Mandatsreferenz 4",
|
||||
"SEPA-Mandatsreferenz 5",
|
||||
"SEPA-Mandatsreferenz 6",
|
||||
"SEPA-Mandatsreferenz 7",
|
||||
"SEPA-Mandatsreferenz 8",
|
||||
"SEPA-Mandatsreferenz 9",
|
||||
"SEPA-Mandatsreferenz 10",
|
||||
"Verknüpftes OPOS-Konto",
|
||||
"Mahnsperre bis",
|
||||
"Lastschriftsperre bis",
|
||||
"Zahlungssperre bis",
|
||||
"Gebührenberechnung",
|
||||
"Mahngebühr 1",
|
||||
"Mahngebühr 2",
|
||||
"Mahngebühr 3",
|
||||
"Pauschalberechnung",
|
||||
"Verzugspauschale 1",
|
||||
"Verzugspauschale 2",
|
||||
"Verzugspauschale 3",
|
||||
"Alternativer Suchname",
|
||||
"Status",
|
||||
"Anschrift manuell geändert (Korrespondenzadresse)",
|
||||
"Anschrift individuell (Korrespondenzadresse)",
|
||||
"Anschrift manuell geändert (Rechnungsadresse)",
|
||||
"Anschrift individuell (Rechnungsadresse)",
|
||||
"Fristberechnung bei Debitor",
|
||||
"Mahnfrist 1",
|
||||
"Mahnfrist 2",
|
||||
"Mahnfrist 3",
|
||||
"Letzte Frist"
|
||||
]
|
||||
|
||||
ACCOUNT_NAME_COLUMNS = [
|
||||
# Account number
|
||||
"Konto",
|
||||
# Account name
|
||||
"Kontenbeschriftung",
|
||||
# Language of the account name
|
||||
# "de-DE" or "en-GB"
|
||||
"Sprach-ID"
|
||||
]
|
||||
|
||||
class DataCategory():
|
||||
|
||||
"""Field of the CSV Header."""
|
||||
|
||||
DEBTORS_CREDITORS = "16"
|
||||
ACCOUNT_NAMES = "20"
|
||||
TRANSACTIONS = "21"
|
||||
POSTING_TEXT_CONSTANTS = "67"
|
||||
|
||||
class FormatName():
|
||||
|
||||
"""Field of the CSV Header, corresponds to DataCategory."""
|
||||
|
||||
DEBTORS_CREDITORS = "Debitoren/Kreditoren"
|
||||
ACCOUNT_NAMES = "Kontenbeschriftungen"
|
||||
TRANSACTIONS = "Buchungsstapel"
|
||||
POSTING_TEXT_CONSTANTS = "Buchungstextkonstanten"
|
||||
|
||||
class Transactions():
|
||||
DATA_CATEGORY = DataCategory.TRANSACTIONS
|
||||
FORMAT_NAME = FormatName.TRANSACTIONS
|
||||
FORMAT_VERSION = "9"
|
||||
COLUMNS = TRANSACTION_COLUMNS
|
||||
|
||||
class DebtorsCreditors():
|
||||
DATA_CATEGORY = DataCategory.DEBTORS_CREDITORS
|
||||
FORMAT_NAME = FormatName.DEBTORS_CREDITORS
|
||||
FORMAT_VERSION = "5"
|
||||
COLUMNS = DEBTOR_CREDITOR_COLUMNS
|
||||
|
||||
class AccountNames():
|
||||
DATA_CATEGORY = DataCategory.ACCOUNT_NAMES
|
||||
FORMAT_NAME = FormatName.ACCOUNT_NAMES
|
||||
FORMAT_VERSION = "2"
|
||||
COLUMNS = ACCOUNT_NAME_COLUMNS
|
||||
@@ -1,180 +0,0 @@
|
||||
import datetime
|
||||
import zipfile
|
||||
from csv import QUOTE_NONNUMERIC
|
||||
from io import BytesIO
|
||||
|
||||
import frappe
|
||||
import pandas as pd
|
||||
from frappe import _
|
||||
|
||||
from .datev_constants import DataCategory
|
||||
|
||||
|
||||
def get_datev_csv(data, filters, csv_class):
|
||||
"""
|
||||
Fill in missing columns and return a CSV in DATEV Format.
|
||||
|
||||
For automatic processing, DATEV requires the first line of the CSV file to
|
||||
hold meta data such as the length of account numbers oder the category of
|
||||
the data.
|
||||
|
||||
Arguments:
|
||||
data -- array of dictionaries
|
||||
filters -- dict
|
||||
csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS
|
||||
"""
|
||||
empty_df = pd.DataFrame(columns=csv_class.COLUMNS)
|
||||
data_df = pd.DataFrame.from_records(data)
|
||||
result = empty_df.append(data_df, sort=True)
|
||||
|
||||
if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS:
|
||||
result['Belegdatum'] = pd.to_datetime(result['Belegdatum'])
|
||||
|
||||
result['Beleginfo - Inhalt 6'] = pd.to_datetime(result['Beleginfo - Inhalt 6'])
|
||||
result['Beleginfo - Inhalt 6'] = result['Beleginfo - Inhalt 6'].dt.strftime('%d%m%Y')
|
||||
|
||||
result['Fälligkeit'] = pd.to_datetime(result['Fälligkeit'])
|
||||
result['Fälligkeit'] = result['Fälligkeit'].dt.strftime('%d%m%y')
|
||||
|
||||
result.sort_values(by='Belegdatum', inplace=True, kind='stable', ignore_index=True)
|
||||
|
||||
if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES:
|
||||
result['Sprach-ID'] = 'de-DE'
|
||||
|
||||
data = result.to_csv(
|
||||
# Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
|
||||
sep=';',
|
||||
# European decimal seperator
|
||||
decimal=',',
|
||||
# Windows "ANSI" encoding
|
||||
encoding='latin_1',
|
||||
# format date as DDMM
|
||||
date_format='%d%m',
|
||||
# Windows line terminator
|
||||
line_terminator='\r\n',
|
||||
# Do not number rows
|
||||
index=False,
|
||||
# Use all columns defined above
|
||||
columns=csv_class.COLUMNS,
|
||||
# Quote most fields, even currency values with "," separator
|
||||
quoting=QUOTE_NONNUMERIC
|
||||
)
|
||||
|
||||
data = data.encode('latin_1', errors='replace')
|
||||
|
||||
header = get_header(filters, csv_class)
|
||||
header = ';'.join(header).encode('latin_1', errors='replace')
|
||||
|
||||
# 1st Row: Header with meta data
|
||||
# 2nd Row: Data heading (Überschrift der Nutzdaten), included in `data` here.
|
||||
# 3rd - nth Row: Data (Nutzdaten)
|
||||
return header + b'\r\n' + data
|
||||
|
||||
|
||||
def get_header(filters, csv_class):
|
||||
description = filters.get('voucher_type', csv_class.FORMAT_NAME)
|
||||
company = filters.get('company')
|
||||
datev_settings = frappe.get_doc('DATEV Settings', {'client': company})
|
||||
default_currency = frappe.get_value('Company', company, 'default_currency')
|
||||
coa = frappe.get_value('Company', company, 'chart_of_accounts')
|
||||
coa_short_code = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
|
||||
|
||||
header = [
|
||||
# DATEV format
|
||||
# "DTVF" = created by DATEV software,
|
||||
# "EXTF" = created by other software
|
||||
'"EXTF"',
|
||||
# version of the DATEV format
|
||||
# 141 = 1.41,
|
||||
# 510 = 5.10,
|
||||
# 720 = 7.20
|
||||
'700',
|
||||
csv_class.DATA_CATEGORY,
|
||||
'"%s"' % csv_class.FORMAT_NAME,
|
||||
# Format version (regarding format name)
|
||||
csv_class.FORMAT_VERSION,
|
||||
# Generated on
|
||||
datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '000',
|
||||
# Imported on -- stays empty
|
||||
'',
|
||||
# Origin. Any two symbols, will be replaced by "SV" on import.
|
||||
'"EN"',
|
||||
# I = Exported by
|
||||
'"%s"' % frappe.session.user,
|
||||
# J = Imported by -- stays empty
|
||||
'',
|
||||
# K = Tax consultant number (Beraternummer)
|
||||
datev_settings.get('consultant_number', '0000000'),
|
||||
# L = Tax client number (Mandantennummer)
|
||||
datev_settings.get('client_number', '00000'),
|
||||
# M = Start of the fiscal year (Wirtschaftsjahresbeginn)
|
||||
frappe.utils.formatdate(filters.get('fiscal_year_start'), 'yyyyMMdd'),
|
||||
# N = Length of account numbers (Sachkontenlänge)
|
||||
str(filters.get('account_number_length', 4)),
|
||||
# O = Transaction batch start date (YYYYMMDD)
|
||||
frappe.utils.formatdate(filters.get('from_date'), 'yyyyMMdd') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
|
||||
# P = Transaction batch end date (YYYYMMDD)
|
||||
frappe.utils.formatdate(filters.get('to_date'), 'yyyyMMdd') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
|
||||
# Q = Description (for example, "Sales Invoice") Max. 30 chars
|
||||
'"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
|
||||
# R = Diktatkürzel
|
||||
'',
|
||||
# S = Buchungstyp
|
||||
# 1 = Transaction batch (Finanzbuchführung),
|
||||
# 2 = Annual financial statement (Jahresabschluss)
|
||||
'1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
|
||||
# T = Rechnungslegungszweck
|
||||
# 0 oder leer = vom Rechnungslegungszweck unabhängig
|
||||
# 50 = Handelsrecht
|
||||
# 30 = Steuerrecht
|
||||
# 64 = IFRS
|
||||
# 40 = Kalkulatorik
|
||||
# 11 = Reserviert
|
||||
# 12 = Reserviert
|
||||
'0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
|
||||
# U = Festschreibung
|
||||
# TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
|
||||
'0',
|
||||
# V = Default currency, for example, "EUR"
|
||||
'"%s"' % default_currency if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
|
||||
# reserviert
|
||||
'',
|
||||
# Derivatskennzeichen
|
||||
'',
|
||||
# reserviert
|
||||
'',
|
||||
# reserviert
|
||||
'',
|
||||
# SKR
|
||||
'"%s"' % coa_short_code,
|
||||
# Branchen-Lösungs-ID
|
||||
'',
|
||||
# reserviert
|
||||
'',
|
||||
# reserviert
|
||||
'',
|
||||
# Anwendungsinformation (Verarbeitungskennzeichen der abgebenden Anwendung)
|
||||
''
|
||||
]
|
||||
return header
|
||||
|
||||
|
||||
def zip_and_download(zip_filename, csv_files):
|
||||
"""
|
||||
Put CSV files in a zip archive and send that to the client.
|
||||
|
||||
Params:
|
||||
zip_filename Name of the zip file
|
||||
csv_files list of dicts [{'file_name': 'my_file.csv', 'csv_data': 'comma,separated,values'}]
|
||||
"""
|
||||
zip_buffer = BytesIO()
|
||||
|
||||
zip_file = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
|
||||
for csv_file in csv_files:
|
||||
zip_file.writestr(csv_file.get('file_name'), csv_file.get('csv_data'))
|
||||
|
||||
zip_file.close()
|
||||
|
||||
frappe.response['filecontent'] = zip_buffer.getvalue()
|
||||
frappe.response['filename'] = zip_filename
|
||||
frappe.response['type'] = 'binary'
|
||||
@@ -1,77 +1,172 @@
|
||||
fiscal_regimes = [
|
||||
"RF01-Ordinario",
|
||||
"RF02-Contribuenti minimi (art.1, c.96-117, L. 244/07)",
|
||||
"RF04-Agricoltura e attività connesse e pesca (artt.34 e 34-bis, DPR 633/72)",
|
||||
"RF05-Vendita sali e tabacchi (art.74, c.1, DPR. 633/72)",
|
||||
"RF06-Commercio fiammiferi (art.74, c.1, DPR 633/72)",
|
||||
"RF07-Editoria (art.74, c.1, DPR 633/72)",
|
||||
"RF08-Gestione servizi telefonia pubblica (art.74, c.1, DPR 633/72)",
|
||||
"RF09-Rivendita documenti di trasporto pubblico e di sosta (art.74, c.1, DPR 633/72)",
|
||||
"RF10-Intrattenimenti, giochi e altre attività di cui alla tariffa allegata al DPR 640/72 (art.74, c.6, DPR 633/72)",
|
||||
"RF11-Agenzie viaggi e turismo (art.74-ter, DPR 633/72)",
|
||||
"RF12-Agriturismo (art.5, c.2, L. 413/91)",
|
||||
"RF13-Vendite a domicilio (art.25-bis, c.6, DPR 600/73)",
|
||||
"RF14-Rivendita beni usati, oggetti d’arte, d’antiquariato o da collezione (art.36, DL 41/95)",
|
||||
"RF15-Agenzie di vendite all’asta di oggetti d’arte, antiquariato o da collezione (art.40-bis, DL 41/95)",
|
||||
"RF16-IVA per cassa P.A. (art.6, c.5, DPR 633/72)",
|
||||
"RF17-IVA per cassa (art. 32-bis, DL 83/2012)",
|
||||
"RF18-Altro",
|
||||
"RF19-Regime forfettario (art.1, c.54-89, L. 190/2014)"
|
||||
"RF01-Ordinario",
|
||||
"RF02-Contribuenti minimi (art.1, c.96-117, L. 244/07)",
|
||||
"RF04-Agricoltura e attività connesse e pesca (artt.34 e 34-bis, DPR 633/72)",
|
||||
"RF05-Vendita sali e tabacchi (art.74, c.1, DPR. 633/72)",
|
||||
"RF06-Commercio fiammiferi (art.74, c.1, DPR 633/72)",
|
||||
"RF07-Editoria (art.74, c.1, DPR 633/72)",
|
||||
"RF08-Gestione servizi telefonia pubblica (art.74, c.1, DPR 633/72)",
|
||||
"RF09-Rivendita documenti di trasporto pubblico e di sosta (art.74, c.1, DPR 633/72)",
|
||||
"RF10-Intrattenimenti, giochi e altre attività di cui alla tariffa allegata al DPR 640/72 (art.74, c.6, DPR 633/72)",
|
||||
"RF11-Agenzie viaggi e turismo (art.74-ter, DPR 633/72)",
|
||||
"RF12-Agriturismo (art.5, c.2, L. 413/91)",
|
||||
"RF13-Vendite a domicilio (art.25-bis, c.6, DPR 600/73)",
|
||||
"RF14-Rivendita beni usati, oggetti d’arte, d’antiquariato o da collezione (art.36, DL 41/95)",
|
||||
"RF15-Agenzie di vendite all’asta di oggetti d’arte, antiquariato o da collezione (art.40-bis, DL 41/95)",
|
||||
"RF16-IVA per cassa P.A. (art.6, c.5, DPR 633/72)",
|
||||
"RF17-IVA per cassa (art. 32-bis, DL 83/2012)",
|
||||
"RF18-Altro",
|
||||
"RF19-Regime forfettario (art.1, c.54-89, L. 190/2014)",
|
||||
]
|
||||
|
||||
tax_exemption_reasons = [
|
||||
"N1-Escluse ex art. 15",
|
||||
"N2-Non Soggette",
|
||||
"N3-Non Imponibili",
|
||||
"N4-Esenti",
|
||||
"N5-Regime del margine / IVA non esposta in fattura",
|
||||
"N6-Inversione Contabile",
|
||||
"N7-IVA assolta in altro stato UE"
|
||||
"N1-Escluse ex art. 15",
|
||||
"N2-Non Soggette",
|
||||
"N3-Non Imponibili",
|
||||
"N4-Esenti",
|
||||
"N5-Regime del margine / IVA non esposta in fattura",
|
||||
"N6-Inversione Contabile",
|
||||
"N7-IVA assolta in altro stato UE",
|
||||
]
|
||||
|
||||
mode_of_payment_codes = [
|
||||
"MP01-Contanti",
|
||||
"MP02-Assegno",
|
||||
"MP03-Assegno circolare",
|
||||
"MP04-Contanti presso Tesoreria",
|
||||
"MP05-Bonifico",
|
||||
"MP06-Vaglia cambiario",
|
||||
"MP07-Bollettino bancario",
|
||||
"MP08-Carta di pagamento",
|
||||
"MP09-RID",
|
||||
"MP10-RID utenze",
|
||||
"MP11-RID veloce",
|
||||
"MP12-RIBA",
|
||||
"MP13-MAV",
|
||||
"MP14-Quietanza erario",
|
||||
"MP15-Giroconto su conti di contabilità speciale",
|
||||
"MP16-Domiciliazione bancaria",
|
||||
"MP17-Domiciliazione postale",
|
||||
"MP18-Bollettino di c/c postale",
|
||||
"MP19-SEPA Direct Debit",
|
||||
"MP20-SEPA Direct Debit CORE",
|
||||
"MP21-SEPA Direct Debit B2B",
|
||||
"MP22-Trattenuta su somme già riscosse"
|
||||
"MP01-Contanti",
|
||||
"MP02-Assegno",
|
||||
"MP03-Assegno circolare",
|
||||
"MP04-Contanti presso Tesoreria",
|
||||
"MP05-Bonifico",
|
||||
"MP06-Vaglia cambiario",
|
||||
"MP07-Bollettino bancario",
|
||||
"MP08-Carta di pagamento",
|
||||
"MP09-RID",
|
||||
"MP10-RID utenze",
|
||||
"MP11-RID veloce",
|
||||
"MP12-RIBA",
|
||||
"MP13-MAV",
|
||||
"MP14-Quietanza erario",
|
||||
"MP15-Giroconto su conti di contabilità speciale",
|
||||
"MP16-Domiciliazione bancaria",
|
||||
"MP17-Domiciliazione postale",
|
||||
"MP18-Bollettino di c/c postale",
|
||||
"MP19-SEPA Direct Debit",
|
||||
"MP20-SEPA Direct Debit CORE",
|
||||
"MP21-SEPA Direct Debit B2B",
|
||||
"MP22-Trattenuta su somme già riscosse",
|
||||
]
|
||||
|
||||
vat_collectability_options = [
|
||||
"I-Immediata",
|
||||
"D-Differita",
|
||||
"S-Scissione dei Pagamenti"
|
||||
]
|
||||
vat_collectability_options = ["I-Immediata", "D-Differita", "S-Scissione dei Pagamenti"]
|
||||
|
||||
state_codes = {'Siracusa': 'SR', 'Bologna': 'BO', 'Grosseto': 'GR', 'Caserta': 'CE', 'Alessandria': 'AL', 'Ancona': 'AN', 'Pavia': 'PV',
|
||||
'Benevento or Beneventum': 'BN', 'Modena': 'MO', 'Lodi': 'LO', 'Novara': 'NO', 'Avellino': 'AV', 'Verona': 'VR', 'Forli-Cesena': 'FC',
|
||||
'Caltanissetta': 'CL', 'Brescia': 'BS', 'Rieti': 'RI', 'Treviso': 'TV', 'Ogliastra': 'OG', 'Olbia-Tempio': 'OT', 'Bergamo': 'BG',
|
||||
'Napoli': 'NA', 'Campobasso': 'CB', 'Fermo': 'FM', 'Roma': 'RM', 'Lucca': 'LU', 'Rovigo': 'RO', 'Piacenza': 'PC', 'Monza and Brianza': 'MB',
|
||||
'La Spezia': 'SP', 'Pescara': 'PE', 'Vercelli': 'VC', 'Enna': 'EN', 'Nuoro': 'NU', 'Medio Campidano': 'MD', 'Trieste': 'TS', 'Aosta': 'AO',
|
||||
'Firenze': 'FI', 'Trapani': 'TP', 'Messina': 'ME', 'Teramo': 'TE', 'Udine': 'UD', 'Verbano-Cusio-Ossola': 'VB', 'Padua': 'PD',
|
||||
'Reggio Emilia': 'RE', 'Frosinone': 'FR', 'Taranto': 'TA', 'Catanzaro': 'CZ', 'Belluno': 'BL', 'Pordenone': 'PN', 'Viterbo': 'VT',
|
||||
'Gorizia': 'GO', 'Vatican City': 'SCV', 'Ferrara': 'FE', 'Chieti': 'CH', 'Crotone': 'KR', 'Foggia': 'FG', 'Perugia': 'PG', 'Bari': 'BA',
|
||||
'Massa-Carrara': 'MS', 'Pisa': 'PI', 'Latina': 'LT', 'Salerno': 'SA', 'Turin': 'TO', 'Lecco': 'LC', 'Lecce': 'LE', 'Pistoia': 'PT', 'Como': 'CO',
|
||||
'Barletta-Andria-Trani': 'BT', 'Mantua': 'MN', 'Ragusa': 'RG', 'Macerata': 'MC', 'Imperia': 'IM', 'Palermo': 'PA', 'Matera': 'MT', "L'Aquila": 'AQ',
|
||||
'Milano': 'MI', 'Catania': 'CT', 'Pesaro e Urbino': 'PU', 'Potenza': 'PZ', 'Republic of San Marino': 'RSM', 'Genoa': 'GE', 'Brindisi': 'BR',
|
||||
'Cagliari': 'CA', 'Siena': 'SI', 'Vibo Valentia': 'VV', 'Reggio Calabria': 'RC', 'Ascoli Piceno': 'AP', 'Carbonia-Iglesias': 'CI', 'Oristano': 'OR',
|
||||
'Asti': 'AT', 'Ravenna': 'RA', 'Vicenza': 'VI', 'Savona': 'SV', 'Biella': 'BI', 'Rimini': 'RN', 'Agrigento': 'AG', 'Prato': 'PO', 'Cuneo': 'CN',
|
||||
'Cosenza': 'CS', 'Livorno or Leghorn': 'LI', 'Sondrio': 'SO', 'Cremona': 'CR', 'Isernia': 'IS', 'Trento': 'TN', 'Terni': 'TR', 'Bolzano/Bozen': 'BZ',
|
||||
'Parma': 'PR', 'Varese': 'VA', 'Venezia': 'VE', 'Sassari': 'SS', 'Arezzo': 'AR'}
|
||||
state_codes = {
|
||||
"Siracusa": "SR",
|
||||
"Bologna": "BO",
|
||||
"Grosseto": "GR",
|
||||
"Caserta": "CE",
|
||||
"Alessandria": "AL",
|
||||
"Ancona": "AN",
|
||||
"Pavia": "PV",
|
||||
"Benevento or Beneventum": "BN",
|
||||
"Modena": "MO",
|
||||
"Lodi": "LO",
|
||||
"Novara": "NO",
|
||||
"Avellino": "AV",
|
||||
"Verona": "VR",
|
||||
"Forli-Cesena": "FC",
|
||||
"Caltanissetta": "CL",
|
||||
"Brescia": "BS",
|
||||
"Rieti": "RI",
|
||||
"Treviso": "TV",
|
||||
"Ogliastra": "OG",
|
||||
"Olbia-Tempio": "OT",
|
||||
"Bergamo": "BG",
|
||||
"Napoli": "NA",
|
||||
"Campobasso": "CB",
|
||||
"Fermo": "FM",
|
||||
"Roma": "RM",
|
||||
"Lucca": "LU",
|
||||
"Rovigo": "RO",
|
||||
"Piacenza": "PC",
|
||||
"Monza and Brianza": "MB",
|
||||
"La Spezia": "SP",
|
||||
"Pescara": "PE",
|
||||
"Vercelli": "VC",
|
||||
"Enna": "EN",
|
||||
"Nuoro": "NU",
|
||||
"Medio Campidano": "MD",
|
||||
"Trieste": "TS",
|
||||
"Aosta": "AO",
|
||||
"Firenze": "FI",
|
||||
"Trapani": "TP",
|
||||
"Messina": "ME",
|
||||
"Teramo": "TE",
|
||||
"Udine": "UD",
|
||||
"Verbano-Cusio-Ossola": "VB",
|
||||
"Padua": "PD",
|
||||
"Reggio Emilia": "RE",
|
||||
"Frosinone": "FR",
|
||||
"Taranto": "TA",
|
||||
"Catanzaro": "CZ",
|
||||
"Belluno": "BL",
|
||||
"Pordenone": "PN",
|
||||
"Viterbo": "VT",
|
||||
"Gorizia": "GO",
|
||||
"Vatican City": "SCV",
|
||||
"Ferrara": "FE",
|
||||
"Chieti": "CH",
|
||||
"Crotone": "KR",
|
||||
"Foggia": "FG",
|
||||
"Perugia": "PG",
|
||||
"Bari": "BA",
|
||||
"Massa-Carrara": "MS",
|
||||
"Pisa": "PI",
|
||||
"Latina": "LT",
|
||||
"Salerno": "SA",
|
||||
"Turin": "TO",
|
||||
"Lecco": "LC",
|
||||
"Lecce": "LE",
|
||||
"Pistoia": "PT",
|
||||
"Como": "CO",
|
||||
"Barletta-Andria-Trani": "BT",
|
||||
"Mantua": "MN",
|
||||
"Ragusa": "RG",
|
||||
"Macerata": "MC",
|
||||
"Imperia": "IM",
|
||||
"Palermo": "PA",
|
||||
"Matera": "MT",
|
||||
"L'Aquila": "AQ",
|
||||
"Milano": "MI",
|
||||
"Catania": "CT",
|
||||
"Pesaro e Urbino": "PU",
|
||||
"Potenza": "PZ",
|
||||
"Republic of San Marino": "RSM",
|
||||
"Genoa": "GE",
|
||||
"Brindisi": "BR",
|
||||
"Cagliari": "CA",
|
||||
"Siena": "SI",
|
||||
"Vibo Valentia": "VV",
|
||||
"Reggio Calabria": "RC",
|
||||
"Ascoli Piceno": "AP",
|
||||
"Carbonia-Iglesias": "CI",
|
||||
"Oristano": "OR",
|
||||
"Asti": "AT",
|
||||
"Ravenna": "RA",
|
||||
"Vicenza": "VI",
|
||||
"Savona": "SV",
|
||||
"Biella": "BI",
|
||||
"Rimini": "RN",
|
||||
"Agrigento": "AG",
|
||||
"Prato": "PO",
|
||||
"Cuneo": "CN",
|
||||
"Cosenza": "CS",
|
||||
"Livorno or Leghorn": "LI",
|
||||
"Sondrio": "SO",
|
||||
"Cremona": "CR",
|
||||
"Isernia": "IS",
|
||||
"Trento": "TN",
|
||||
"Terni": "TR",
|
||||
"Bolzano/Bozen": "BZ",
|
||||
"Parma": "PR",
|
||||
"Varese": "VA",
|
||||
"Venezia": "VE",
|
||||
"Sassari": "SS",
|
||||
"Arezzo": "AR",
|
||||
}
|
||||
|
||||
@@ -7,213 +7,489 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
from erpnext.regional.italy import fiscal_regimes, tax_exemption_reasons, mode_of_payment_codes, vat_collectability_options
|
||||
from erpnext.regional.italy import (
|
||||
fiscal_regimes,
|
||||
tax_exemption_reasons,
|
||||
mode_of_payment_codes,
|
||||
vat_collectability_options,
|
||||
)
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
setup_report()
|
||||
add_permissions()
|
||||
|
||||
|
||||
def make_custom_fields(update=True):
|
||||
invoice_item_fields = [
|
||||
dict(fieldname='tax_rate', label='Tax Rate',
|
||||
fieldtype='Float', insert_after='description',
|
||||
print_hide=1, hidden=1, read_only=1),
|
||||
dict(fieldname='tax_amount', label='Tax Amount',
|
||||
fieldtype='Currency', insert_after='tax_rate',
|
||||
print_hide=1, hidden=1, read_only=1, options="currency"),
|
||||
dict(fieldname='total_amount', label='Total Amount',
|
||||
fieldtype='Currency', insert_after='tax_amount',
|
||||
print_hide=1, hidden=1, read_only=1, options="currency")
|
||||
dict(
|
||||
fieldname="tax_rate",
|
||||
label="Tax Rate",
|
||||
fieldtype="Float",
|
||||
insert_after="description",
|
||||
print_hide=1,
|
||||
hidden=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="tax_amount",
|
||||
label="Tax Amount",
|
||||
fieldtype="Currency",
|
||||
insert_after="tax_rate",
|
||||
print_hide=1,
|
||||
hidden=1,
|
||||
read_only=1,
|
||||
options="currency",
|
||||
),
|
||||
dict(
|
||||
fieldname="total_amount",
|
||||
label="Total Amount",
|
||||
fieldtype="Currency",
|
||||
insert_after="tax_amount",
|
||||
print_hide=1,
|
||||
hidden=1,
|
||||
read_only=1,
|
||||
options="currency",
|
||||
),
|
||||
]
|
||||
|
||||
customer_po_fields = [
|
||||
dict(fieldname='customer_po_details', label='Customer PO',
|
||||
fieldtype='Section Break', insert_after='image'),
|
||||
dict(fieldname='customer_po_no', label='Customer PO No',
|
||||
fieldtype='Data', insert_after='customer_po_details',
|
||||
fetch_from = 'sales_order.po_no',
|
||||
print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1),
|
||||
dict(fieldname='customer_po_clm_brk', label='',
|
||||
fieldtype='Column Break', insert_after='customer_po_no',
|
||||
print_hide=1, read_only=1),
|
||||
dict(fieldname='customer_po_date', label='Customer PO Date',
|
||||
fieldtype='Date', insert_after='customer_po_clm_brk',
|
||||
fetch_from = 'sales_order.po_date',
|
||||
print_hide=1, allow_on_submit=1, fetch_if_empty= 1, read_only=1, no_copy=1)
|
||||
dict(
|
||||
fieldname="customer_po_details",
|
||||
label="Customer PO",
|
||||
fieldtype="Section Break",
|
||||
insert_after="image",
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_po_no",
|
||||
label="Customer PO No",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_po_details",
|
||||
fetch_from="sales_order.po_no",
|
||||
print_hide=1,
|
||||
allow_on_submit=1,
|
||||
fetch_if_empty=1,
|
||||
read_only=1,
|
||||
no_copy=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_po_clm_brk",
|
||||
label="",
|
||||
fieldtype="Column Break",
|
||||
insert_after="customer_po_no",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_po_date",
|
||||
label="Customer PO Date",
|
||||
fieldtype="Date",
|
||||
insert_after="customer_po_clm_brk",
|
||||
fetch_from="sales_order.po_date",
|
||||
print_hide=1,
|
||||
allow_on_submit=1,
|
||||
fetch_if_empty=1,
|
||||
read_only=1,
|
||||
no_copy=1,
|
||||
),
|
||||
]
|
||||
|
||||
custom_fields = {
|
||||
'Company': [
|
||||
dict(fieldname='sb_e_invoicing', label='E-Invoicing',
|
||||
fieldtype='Section Break', insert_after='date_of_establishment', print_hide=1),
|
||||
dict(fieldname='fiscal_regime', label='Fiscal Regime',
|
||||
fieldtype='Select', insert_after='sb_e_invoicing', print_hide=1,
|
||||
options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), fiscal_regimes))),
|
||||
dict(fieldname='fiscal_code', label='Fiscal Code', fieldtype='Data', insert_after='fiscal_regime', print_hide=1,
|
||||
description=_("Applicable if the company is an Individual or a Proprietorship")),
|
||||
dict(fieldname='vat_collectability', label='VAT Collectability',
|
||||
fieldtype='Select', insert_after='fiscal_code', print_hide=1,
|
||||
options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), vat_collectability_options))),
|
||||
dict(fieldname='cb_e_invoicing1', fieldtype='Column Break', insert_after='vat_collectability', print_hide=1),
|
||||
dict(fieldname='registrar_office_province', label='Province of the Registrar Office',
|
||||
fieldtype='Data', insert_after='cb_e_invoicing1', print_hide=1, length=2),
|
||||
dict(fieldname='registration_number', label='Registration Number',
|
||||
fieldtype='Data', insert_after='registrar_office_province', print_hide=1, length=20),
|
||||
dict(fieldname='share_capital_amount', label='Share Capital',
|
||||
fieldtype='Currency', insert_after='registration_number', print_hide=1,
|
||||
description=_('Applicable if the company is SpA, SApA or SRL')),
|
||||
dict(fieldname='no_of_members', label='No of Members',
|
||||
fieldtype='Select', insert_after='share_capital_amount', print_hide=1,
|
||||
options="\nSU-Socio Unico\nSM-Piu Soci", description=_("Applicable if the company is a limited liability company")),
|
||||
dict(fieldname='liquidation_state', label='Liquidation State',
|
||||
fieldtype='Select', insert_after='no_of_members', print_hide=1,
|
||||
options="\nLS-In Liquidazione\nLN-Non in Liquidazione")
|
||||
"Company": [
|
||||
dict(
|
||||
fieldname="sb_e_invoicing",
|
||||
label="E-Invoicing",
|
||||
fieldtype="Section Break",
|
||||
insert_after="date_of_establishment",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="fiscal_regime",
|
||||
label="Fiscal Regime",
|
||||
fieldtype="Select",
|
||||
insert_after="sb_e_invoicing",
|
||||
print_hide=1,
|
||||
options="\n".join(map(lambda x: frappe.safe_decode(x, encoding="utf-8"), fiscal_regimes)),
|
||||
),
|
||||
dict(
|
||||
fieldname="fiscal_code",
|
||||
label="Fiscal Code",
|
||||
fieldtype="Data",
|
||||
insert_after="fiscal_regime",
|
||||
print_hide=1,
|
||||
description=_("Applicable if the company is an Individual or a Proprietorship"),
|
||||
),
|
||||
dict(
|
||||
fieldname="vat_collectability",
|
||||
label="VAT Collectability",
|
||||
fieldtype="Select",
|
||||
insert_after="fiscal_code",
|
||||
print_hide=1,
|
||||
options="\n".join(
|
||||
map(lambda x: frappe.safe_decode(x, encoding="utf-8"), vat_collectability_options)
|
||||
),
|
||||
),
|
||||
dict(
|
||||
fieldname="cb_e_invoicing1",
|
||||
fieldtype="Column Break",
|
||||
insert_after="vat_collectability",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="registrar_office_province",
|
||||
label="Province of the Registrar Office",
|
||||
fieldtype="Data",
|
||||
insert_after="cb_e_invoicing1",
|
||||
print_hide=1,
|
||||
length=2,
|
||||
),
|
||||
dict(
|
||||
fieldname="registration_number",
|
||||
label="Registration Number",
|
||||
fieldtype="Data",
|
||||
insert_after="registrar_office_province",
|
||||
print_hide=1,
|
||||
length=20,
|
||||
),
|
||||
dict(
|
||||
fieldname="share_capital_amount",
|
||||
label="Share Capital",
|
||||
fieldtype="Currency",
|
||||
insert_after="registration_number",
|
||||
print_hide=1,
|
||||
description=_("Applicable if the company is SpA, SApA or SRL"),
|
||||
),
|
||||
dict(
|
||||
fieldname="no_of_members",
|
||||
label="No of Members",
|
||||
fieldtype="Select",
|
||||
insert_after="share_capital_amount",
|
||||
print_hide=1,
|
||||
options="\nSU-Socio Unico\nSM-Piu Soci",
|
||||
description=_("Applicable if the company is a limited liability company"),
|
||||
),
|
||||
dict(
|
||||
fieldname="liquidation_state",
|
||||
label="Liquidation State",
|
||||
fieldtype="Select",
|
||||
insert_after="no_of_members",
|
||||
print_hide=1,
|
||||
options="\nLS-In Liquidazione\nLN-Non in Liquidazione",
|
||||
),
|
||||
],
|
||||
'Sales Taxes and Charges': [
|
||||
dict(fieldname='tax_exemption_reason', label='Tax Exemption Reason',
|
||||
fieldtype='Select', insert_after='included_in_print_rate', print_hide=1,
|
||||
"Sales Taxes and Charges": [
|
||||
dict(
|
||||
fieldname="tax_exemption_reason",
|
||||
label="Tax Exemption Reason",
|
||||
fieldtype="Select",
|
||||
insert_after="included_in_print_rate",
|
||||
print_hide=1,
|
||||
depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0',
|
||||
options="\n" + "\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), tax_exemption_reasons))),
|
||||
dict(fieldname='tax_exemption_law', label='Tax Exempt Under',
|
||||
fieldtype='Text', insert_after='tax_exemption_reason', print_hide=1,
|
||||
depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0')
|
||||
options="\n"
|
||||
+ "\n".join(map(lambda x: frappe.safe_decode(x, encoding="utf-8"), tax_exemption_reasons)),
|
||||
),
|
||||
dict(
|
||||
fieldname="tax_exemption_law",
|
||||
label="Tax Exempt Under",
|
||||
fieldtype="Text",
|
||||
insert_after="tax_exemption_reason",
|
||||
print_hide=1,
|
||||
depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0',
|
||||
),
|
||||
],
|
||||
'Customer': [
|
||||
dict(fieldname='fiscal_code', label='Fiscal Code', fieldtype='Data', insert_after='tax_id', print_hide=1),
|
||||
dict(fieldname='recipient_code', label='Recipient Code',
|
||||
fieldtype='Data', insert_after='fiscal_code', print_hide=1, default="0000000"),
|
||||
dict(fieldname='pec', label='Recipient PEC',
|
||||
fieldtype='Data', insert_after='fiscal_code', print_hide=1),
|
||||
dict(fieldname='is_public_administration', label='Is Public Administration',
|
||||
fieldtype='Check', insert_after='is_internal_customer', print_hide=1,
|
||||
"Customer": [
|
||||
dict(
|
||||
fieldname="fiscal_code",
|
||||
label="Fiscal Code",
|
||||
fieldtype="Data",
|
||||
insert_after="tax_id",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="recipient_code",
|
||||
label="Recipient Code",
|
||||
fieldtype="Data",
|
||||
insert_after="fiscal_code",
|
||||
print_hide=1,
|
||||
default="0000000",
|
||||
),
|
||||
dict(
|
||||
fieldname="pec",
|
||||
label="Recipient PEC",
|
||||
fieldtype="Data",
|
||||
insert_after="fiscal_code",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="is_public_administration",
|
||||
label="Is Public Administration",
|
||||
fieldtype="Check",
|
||||
insert_after="is_internal_customer",
|
||||
print_hide=1,
|
||||
description=_("Set this if the customer is a Public Administration company."),
|
||||
depends_on='eval:doc.customer_type=="Company"'),
|
||||
dict(fieldname='first_name', label='First Name', fieldtype='Data',
|
||||
insert_after='salutation', print_hide=1, depends_on='eval:doc.customer_type!="Company"'),
|
||||
dict(fieldname='last_name', label='Last Name', fieldtype='Data',
|
||||
insert_after='first_name', print_hide=1, depends_on='eval:doc.customer_type!="Company"')
|
||||
depends_on='eval:doc.customer_type=="Company"',
|
||||
),
|
||||
dict(
|
||||
fieldname="first_name",
|
||||
label="First Name",
|
||||
fieldtype="Data",
|
||||
insert_after="salutation",
|
||||
print_hide=1,
|
||||
depends_on='eval:doc.customer_type!="Company"',
|
||||
),
|
||||
dict(
|
||||
fieldname="last_name",
|
||||
label="Last Name",
|
||||
fieldtype="Data",
|
||||
insert_after="first_name",
|
||||
print_hide=1,
|
||||
depends_on='eval:doc.customer_type!="Company"',
|
||||
),
|
||||
],
|
||||
'Mode of Payment': [
|
||||
dict(fieldname='mode_of_payment_code', label='Code',
|
||||
fieldtype='Select', insert_after='included_in_print_rate', print_hide=1,
|
||||
options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), mode_of_payment_codes)))
|
||||
"Mode of Payment": [
|
||||
dict(
|
||||
fieldname="mode_of_payment_code",
|
||||
label="Code",
|
||||
fieldtype="Select",
|
||||
insert_after="included_in_print_rate",
|
||||
print_hide=1,
|
||||
options="\n".join(
|
||||
map(lambda x: frappe.safe_decode(x, encoding="utf-8"), mode_of_payment_codes)
|
||||
),
|
||||
)
|
||||
],
|
||||
'Payment Schedule': [
|
||||
dict(fieldname='mode_of_payment_code', label='Code',
|
||||
fieldtype='Select', insert_after='mode_of_payment', print_hide=1,
|
||||
options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), mode_of_payment_codes)),
|
||||
fetch_from="mode_of_payment.mode_of_payment_code", read_only=1),
|
||||
dict(fieldname='bank_account', label='Bank Account',
|
||||
fieldtype='Link', insert_after='mode_of_payment_code', print_hide=1,
|
||||
options="Bank Account"),
|
||||
dict(fieldname='bank_account_name', label='Bank Name',
|
||||
fieldtype='Data', insert_after='bank_account', print_hide=1,
|
||||
fetch_from="bank_account.bank", read_only=1),
|
||||
dict(fieldname='bank_account_no', label='Bank Account No',
|
||||
fieldtype='Data', insert_after='bank_account_name', print_hide=1,
|
||||
fetch_from="bank_account.bank_account_no", read_only=1),
|
||||
dict(fieldname='bank_account_iban', label='IBAN',
|
||||
fieldtype='Data', insert_after='bank_account_name', print_hide=1,
|
||||
fetch_from="bank_account.iban", read_only=1),
|
||||
dict(fieldname='bank_account_swift_number', label='Swift Code (BIC)',
|
||||
fieldtype='Data', insert_after='bank_account_iban', print_hide=1,
|
||||
fetch_from="bank_account.swift_number", read_only=1),
|
||||
"Payment Schedule": [
|
||||
dict(
|
||||
fieldname="mode_of_payment_code",
|
||||
label="Code",
|
||||
fieldtype="Select",
|
||||
insert_after="mode_of_payment",
|
||||
print_hide=1,
|
||||
options="\n".join(
|
||||
map(lambda x: frappe.safe_decode(x, encoding="utf-8"), mode_of_payment_codes)
|
||||
),
|
||||
fetch_from="mode_of_payment.mode_of_payment_code",
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="bank_account",
|
||||
label="Bank Account",
|
||||
fieldtype="Link",
|
||||
insert_after="mode_of_payment_code",
|
||||
print_hide=1,
|
||||
options="Bank Account",
|
||||
),
|
||||
dict(
|
||||
fieldname="bank_account_name",
|
||||
label="Bank Name",
|
||||
fieldtype="Data",
|
||||
insert_after="bank_account",
|
||||
print_hide=1,
|
||||
fetch_from="bank_account.bank",
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="bank_account_no",
|
||||
label="Bank Account No",
|
||||
fieldtype="Data",
|
||||
insert_after="bank_account_name",
|
||||
print_hide=1,
|
||||
fetch_from="bank_account.bank_account_no",
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="bank_account_iban",
|
||||
label="IBAN",
|
||||
fieldtype="Data",
|
||||
insert_after="bank_account_name",
|
||||
print_hide=1,
|
||||
fetch_from="bank_account.iban",
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="bank_account_swift_number",
|
||||
label="Swift Code (BIC)",
|
||||
fieldtype="Data",
|
||||
insert_after="bank_account_iban",
|
||||
print_hide=1,
|
||||
fetch_from="bank_account.swift_number",
|
||||
read_only=1,
|
||||
),
|
||||
],
|
||||
"Sales Invoice": [
|
||||
dict(fieldname='vat_collectability', label='VAT Collectability',
|
||||
fieldtype='Select', insert_after='taxes_and_charges', print_hide=1,
|
||||
options="\n".join(map(lambda x: frappe.safe_decode(x, encoding='utf-8'), vat_collectability_options)),
|
||||
fetch_from="company.vat_collectability"),
|
||||
dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing',
|
||||
fieldtype='Section Break', insert_after='against_income_account', print_hide=1),
|
||||
dict(fieldname='company_fiscal_code', label='Company Fiscal Code',
|
||||
fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
|
||||
fetch_from="company.fiscal_code"),
|
||||
dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime',
|
||||
fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1,
|
||||
fetch_from="company.fiscal_regime"),
|
||||
dict(fieldname='cb_e_invoicing_reference', fieldtype='Column Break',
|
||||
insert_after='company_fiscal_regime', print_hide=1),
|
||||
dict(fieldname='customer_fiscal_code', label='Customer Fiscal Code',
|
||||
fieldtype='Data', insert_after='cb_e_invoicing_reference', read_only=1,
|
||||
fetch_from="customer.fiscal_code"),
|
||||
dict(fieldname='type_of_document', label='Type of Document',
|
||||
fieldtype='Select', insert_after='customer_fiscal_code',
|
||||
options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'),
|
||||
],
|
||||
'Purchase Invoice Item': invoice_item_fields,
|
||||
'Sales Order Item': invoice_item_fields,
|
||||
'Delivery Note Item': invoice_item_fields,
|
||||
'Sales Invoice Item': invoice_item_fields + customer_po_fields,
|
||||
'Quotation Item': invoice_item_fields,
|
||||
'Purchase Order Item': invoice_item_fields,
|
||||
'Purchase Receipt Item': invoice_item_fields,
|
||||
'Supplier Quotation Item': invoice_item_fields,
|
||||
'Address': [
|
||||
dict(fieldname='country_code', label='Country Code',
|
||||
fieldtype='Data', insert_after='country', print_hide=1, read_only=0,
|
||||
fetch_from="country.code"),
|
||||
dict(fieldname='state_code', label='State Code',
|
||||
fieldtype='Data', insert_after='state', print_hide=1)
|
||||
],
|
||||
'Purchase Invoice': [
|
||||
dict(fieldname='document_type', label='Document Type',
|
||||
fieldtype='Data', insert_after='company', print_hide=1, read_only=1
|
||||
dict(
|
||||
fieldname="vat_collectability",
|
||||
label="VAT Collectability",
|
||||
fieldtype="Select",
|
||||
insert_after="taxes_and_charges",
|
||||
print_hide=1,
|
||||
options="\n".join(
|
||||
map(lambda x: frappe.safe_decode(x, encoding="utf-8"), vat_collectability_options)
|
||||
),
|
||||
dict(fieldname='destination_code', label='Destination Code',
|
||||
fieldtype='Data', insert_after='company', print_hide=1, read_only=1
|
||||
),
|
||||
dict(fieldname='imported_grand_total', label='Imported Grand Total',
|
||||
fieldtype='Data', insert_after='update_auto_repeat_reference', print_hide=1, read_only=1
|
||||
)
|
||||
fetch_from="company.vat_collectability",
|
||||
),
|
||||
dict(
|
||||
fieldname="sb_e_invoicing_reference",
|
||||
label="E-Invoicing",
|
||||
fieldtype="Section Break",
|
||||
insert_after="against_income_account",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="company_fiscal_code",
|
||||
label="Company Fiscal Code",
|
||||
fieldtype="Data",
|
||||
insert_after="sb_e_invoicing_reference",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
fetch_from="company.fiscal_code",
|
||||
),
|
||||
dict(
|
||||
fieldname="company_fiscal_regime",
|
||||
label="Company Fiscal Regime",
|
||||
fieldtype="Data",
|
||||
insert_after="company_fiscal_code",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
fetch_from="company.fiscal_regime",
|
||||
),
|
||||
dict(
|
||||
fieldname="cb_e_invoicing_reference",
|
||||
fieldtype="Column Break",
|
||||
insert_after="company_fiscal_regime",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_fiscal_code",
|
||||
label="Customer Fiscal Code",
|
||||
fieldtype="Data",
|
||||
insert_after="cb_e_invoicing_reference",
|
||||
read_only=1,
|
||||
fetch_from="customer.fiscal_code",
|
||||
),
|
||||
dict(
|
||||
fieldname="type_of_document",
|
||||
label="Type of Document",
|
||||
fieldtype="Select",
|
||||
insert_after="customer_fiscal_code",
|
||||
options="\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27",
|
||||
),
|
||||
],
|
||||
'Purchase Taxes and Charges': [
|
||||
dict(fieldname='tax_rate', label='Tax Rate',
|
||||
fieldtype='Data', insert_after='parenttype', print_hide=1, read_only=0
|
||||
)
|
||||
"Purchase Invoice Item": invoice_item_fields,
|
||||
"Sales Order Item": invoice_item_fields,
|
||||
"Delivery Note Item": invoice_item_fields,
|
||||
"Sales Invoice Item": invoice_item_fields + customer_po_fields,
|
||||
"Quotation Item": invoice_item_fields,
|
||||
"Purchase Order Item": invoice_item_fields,
|
||||
"Purchase Receipt Item": invoice_item_fields,
|
||||
"Supplier Quotation Item": invoice_item_fields,
|
||||
"Address": [
|
||||
dict(
|
||||
fieldname="country_code",
|
||||
label="Country Code",
|
||||
fieldtype="Data",
|
||||
insert_after="country",
|
||||
print_hide=1,
|
||||
read_only=0,
|
||||
fetch_from="country.code",
|
||||
),
|
||||
dict(
|
||||
fieldname="state_code",
|
||||
label="State Code",
|
||||
fieldtype="Data",
|
||||
insert_after="state",
|
||||
print_hide=1,
|
||||
),
|
||||
],
|
||||
"Purchase Invoice": [
|
||||
dict(
|
||||
fieldname="document_type",
|
||||
label="Document Type",
|
||||
fieldtype="Data",
|
||||
insert_after="company",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="destination_code",
|
||||
label="Destination Code",
|
||||
fieldtype="Data",
|
||||
insert_after="company",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="imported_grand_total",
|
||||
label="Imported Grand Total",
|
||||
fieldtype="Data",
|
||||
insert_after="update_auto_repeat_reference",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
],
|
||||
"Purchase Taxes and Charges": [
|
||||
dict(
|
||||
fieldname="tax_rate",
|
||||
label="Tax Rate",
|
||||
fieldtype="Data",
|
||||
insert_after="parenttype",
|
||||
print_hide=1,
|
||||
read_only=0,
|
||||
)
|
||||
],
|
||||
"Supplier": [
|
||||
dict(
|
||||
fieldname="fiscal_code",
|
||||
label="Fiscal Code",
|
||||
fieldtype="Data",
|
||||
insert_after="tax_id",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="fiscal_regime",
|
||||
label="Fiscal Regime",
|
||||
fieldtype="Select",
|
||||
insert_after="fiscal_code",
|
||||
print_hide=1,
|
||||
read_only=1,
|
||||
options="\nRF01\nRF02\nRF04\nRF05\nRF06\nRF07\nRF08\nRF09\nRF10\nRF11\nRF12\nRF13\nRF14\nRF15\nRF16\nRF17\nRF18\nRF19",
|
||||
),
|
||||
],
|
||||
'Supplier': [
|
||||
dict(fieldname='fiscal_code', label='Fiscal Code',
|
||||
fieldtype='Data', insert_after='tax_id', print_hide=1, read_only=1
|
||||
),
|
||||
dict(fieldname='fiscal_regime', label='Fiscal Regime',
|
||||
fieldtype='Select', insert_after='fiscal_code', print_hide=1, read_only=1,
|
||||
options= "\nRF01\nRF02\nRF04\nRF05\nRF06\nRF07\nRF08\nRF09\nRF10\nRF11\nRF12\nRF13\nRF14\nRF15\nRF16\nRF17\nRF18\nRF19"
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=update)
|
||||
create_custom_fields(custom_fields, ignore_validate=frappe.flags.in_patch, update=update)
|
||||
|
||||
|
||||
def setup_report():
|
||||
report_name = 'Electronic Invoice Register'
|
||||
report_name = "Electronic Invoice Register"
|
||||
frappe.db.set_value("Report", report_name, "disabled", 0)
|
||||
|
||||
if not frappe.db.get_value('Custom Role', dict(report=report_name)):
|
||||
frappe.get_doc(dict(
|
||||
doctype='Custom Role',
|
||||
report=report_name,
|
||||
roles= [
|
||||
dict(role='Accounts User'),
|
||||
dict(role='Accounts Manager')
|
||||
]
|
||||
)).insert()
|
||||
if not frappe.db.get_value("Custom Role", dict(report=report_name)):
|
||||
frappe.get_doc(
|
||||
dict(
|
||||
doctype="Custom Role",
|
||||
report=report_name,
|
||||
roles=[dict(role="Accounts User"), dict(role="Accounts Manager")],
|
||||
)
|
||||
).insert()
|
||||
|
||||
|
||||
def add_permissions():
|
||||
doctype = 'Import Supplier Invoice'
|
||||
add_permission(doctype, 'All', 0)
|
||||
doctype = "Import Supplier Invoice"
|
||||
add_permission(doctype, "All", 0)
|
||||
|
||||
for role in ('Accounts Manager', 'Accounts User','Purchase User', 'Auditor'):
|
||||
for role in ("Accounts Manager", "Accounts User", "Purchase User", "Auditor"):
|
||||
add_permission(doctype, role, 0)
|
||||
update_permission_property(doctype, role, 0, 'print', 1)
|
||||
update_permission_property(doctype, role, 0, 'report', 1)
|
||||
update_permission_property(doctype, role, 0, "print", 1)
|
||||
update_permission_property(doctype, role, 0, "report", 1)
|
||||
|
||||
if role in ('Accounts Manager', 'Accounts User'):
|
||||
update_permission_property(doctype, role, 0, 'write', 1)
|
||||
update_permission_property(doctype, role, 0, 'create', 1)
|
||||
if role in ("Accounts Manager", "Accounts User"):
|
||||
update_permission_property(doctype, role, 0, "write", 1)
|
||||
update_permission_property(doctype, role, 0, "create", 1)
|
||||
|
||||
update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1)
|
||||
add_permission(doctype, 'Accounts Manager', 1)
|
||||
update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1)
|
||||
update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
|
||||
update_permission_property(doctype, "Accounts Manager", 0, "delete", 1)
|
||||
add_permission(doctype, "Accounts Manager", 1)
|
||||
update_permission_property(doctype, "Accounts Manager", 1, "write", 1)
|
||||
update_permission_property(doctype, "Accounts Manager", 1, "create", 1)
|
||||
|
||||
@@ -11,41 +11,41 @@ from erpnext.regional.italy import state_codes
|
||||
|
||||
|
||||
def update_itemised_tax_data(doc):
|
||||
if not doc.taxes: return
|
||||
if not doc.taxes:
|
||||
return
|
||||
|
||||
if doc.doctype == "Purchase Invoice": return
|
||||
if doc.doctype == "Purchase Invoice":
|
||||
return
|
||||
|
||||
itemised_tax = get_itemised_tax(doc.taxes)
|
||||
|
||||
for row in doc.items:
|
||||
tax_rate = 0.0
|
||||
if itemised_tax.get(row.item_code):
|
||||
tax_rate = sum([tax.get('tax_rate', 0) for d, tax in itemised_tax.get(row.item_code).items()])
|
||||
tax_rate = sum([tax.get("tax_rate", 0) for d, tax in itemised_tax.get(row.item_code).items()])
|
||||
|
||||
row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
|
||||
row.tax_amount = flt((row.net_amount * tax_rate) / 100, row.precision("net_amount"))
|
||||
row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def export_invoices(filters=None):
|
||||
frappe.has_permission('Sales Invoice', throw=True)
|
||||
frappe.has_permission("Sales Invoice", throw=True)
|
||||
|
||||
invoices = frappe.get_all(
|
||||
"Sales Invoice",
|
||||
filters=get_conditions(filters),
|
||||
fields=["name", "company_tax_id"]
|
||||
"Sales Invoice", filters=get_conditions(filters), fields=["name", "company_tax_id"]
|
||||
)
|
||||
|
||||
attachments = get_e_invoice_attachments(invoices)
|
||||
|
||||
zip_filename = "{0}-einvoices.zip".format(
|
||||
frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
|
||||
zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
|
||||
|
||||
download_zip(attachments, zip_filename)
|
||||
|
||||
|
||||
def prepare_invoice(invoice, progressive_number):
|
||||
#set company information
|
||||
# set company information
|
||||
company = frappe.get_doc("Company", invoice.company)
|
||||
|
||||
invoice.progressive_number = progressive_number
|
||||
@@ -54,15 +54,17 @@ def prepare_invoice(invoice, progressive_number):
|
||||
company_address = frappe.get_doc("Address", invoice.company_address)
|
||||
invoice.company_address_data = company_address
|
||||
|
||||
#Set invoice type
|
||||
# Set invoice type
|
||||
if not invoice.type_of_document:
|
||||
if invoice.is_return and invoice.return_against:
|
||||
invoice.type_of_document = "TD04" #Credit Note (Nota di Credito)
|
||||
invoice.return_against_unamended = get_unamended_name(frappe.get_doc("Sales Invoice", invoice.return_against))
|
||||
invoice.type_of_document = "TD04" # Credit Note (Nota di Credito)
|
||||
invoice.return_against_unamended = get_unamended_name(
|
||||
frappe.get_doc("Sales Invoice", invoice.return_against)
|
||||
)
|
||||
else:
|
||||
invoice.type_of_document = "TD01" #Sales Invoice (Fattura)
|
||||
invoice.type_of_document = "TD01" # Sales Invoice (Fattura)
|
||||
|
||||
#set customer information
|
||||
# set customer information
|
||||
invoice.customer_data = frappe.get_doc("Customer", invoice.customer)
|
||||
customer_address = frappe.get_doc("Address", invoice.customer_address)
|
||||
invoice.customer_address_data = customer_address
|
||||
@@ -79,8 +81,10 @@ def prepare_invoice(invoice, progressive_number):
|
||||
tax_data = get_invoice_summary(invoice.e_invoice_items, invoice.taxes)
|
||||
invoice.tax_data = tax_data
|
||||
|
||||
#Check if stamp duty (Bollo) of 2 EUR exists.
|
||||
stamp_duty_charge_row = next((tax for tax in invoice.taxes if tax.charge_type == "Actual" and tax.tax_amount == 2.0 ), None)
|
||||
# Check if stamp duty (Bollo) of 2 EUR exists.
|
||||
stamp_duty_charge_row = next(
|
||||
(tax for tax in invoice.taxes if tax.charge_type == "Actual" and tax.tax_amount == 2.0), None
|
||||
)
|
||||
if stamp_duty_charge_row:
|
||||
invoice.stamp_duty = stamp_duty_charge_row.tax_amount
|
||||
|
||||
@@ -90,24 +94,28 @@ def prepare_invoice(invoice, progressive_number):
|
||||
|
||||
customer_po_data = {}
|
||||
for d in invoice.e_invoice_items:
|
||||
if (d.customer_po_no and d.customer_po_date
|
||||
and d.customer_po_no not in customer_po_data):
|
||||
if d.customer_po_no and d.customer_po_date and d.customer_po_no not in customer_po_data:
|
||||
customer_po_data[d.customer_po_no] = d.customer_po_date
|
||||
|
||||
invoice.customer_po_data = customer_po_data
|
||||
|
||||
return invoice
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
filters = json.loads(filters)
|
||||
|
||||
conditions = {"docstatus": 1, "company_tax_id": ("!=", "")}
|
||||
|
||||
if filters.get("company"): conditions["company"] = filters["company"]
|
||||
if filters.get("customer"): conditions["customer"] = filters["customer"]
|
||||
if filters.get("company"):
|
||||
conditions["company"] = filters["company"]
|
||||
if filters.get("customer"):
|
||||
conditions["customer"] = filters["customer"]
|
||||
|
||||
if filters.get("from_date"): conditions["posting_date"] = (">=", filters["from_date"])
|
||||
if filters.get("to_date"): conditions["posting_date"] = ("<=", filters["to_date"])
|
||||
if filters.get("from_date"):
|
||||
conditions["posting_date"] = (">=", filters["from_date"])
|
||||
if filters.get("to_date"):
|
||||
conditions["posting_date"] = ("<=", filters["to_date"])
|
||||
|
||||
if filters.get("from_date") and filters.get("to_date"):
|
||||
conditions["posting_date"] = ("between", [filters.get("from_date"), filters.get("to_date")])
|
||||
@@ -119,10 +127,9 @@ def download_zip(files, output_filename):
|
||||
import zipfile
|
||||
|
||||
zip_stream = io.BytesIO()
|
||||
with zipfile.ZipFile(zip_stream, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
with zipfile.ZipFile(zip_stream, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for file in files:
|
||||
file_path = frappe.utils.get_files_path(
|
||||
file.file_name, is_private=file.is_private)
|
||||
file_path = frappe.utils.get_files_path(file.file_name, is_private=file.is_private)
|
||||
|
||||
zip_file.write(file_path, arcname=file.file_name)
|
||||
|
||||
@@ -131,20 +138,21 @@ def download_zip(files, output_filename):
|
||||
frappe.local.response.type = "download"
|
||||
zip_stream.close()
|
||||
|
||||
|
||||
def get_invoice_summary(items, taxes):
|
||||
summary_data = frappe._dict()
|
||||
for tax in taxes:
|
||||
#Include only VAT charges.
|
||||
# Include only VAT charges.
|
||||
if tax.charge_type == "Actual":
|
||||
continue
|
||||
|
||||
#Charges to appear as items in the e-invoice.
|
||||
# Charges to appear as items in the e-invoice.
|
||||
if tax.charge_type in ["On Previous Row Total", "On Previous Row Amount"]:
|
||||
reference_row = next((row for row in taxes if row.idx == int(tax.row_id or 0)), None)
|
||||
if reference_row:
|
||||
items.append(
|
||||
frappe._dict(
|
||||
idx=len(items)+1,
|
||||
idx=len(items) + 1,
|
||||
item_code=reference_row.description,
|
||||
item_name=reference_row.description,
|
||||
description=reference_row.description,
|
||||
@@ -157,11 +165,11 @@ def get_invoice_summary(items, taxes):
|
||||
net_amount=reference_row.tax_amount,
|
||||
taxable_amount=reference_row.tax_amount,
|
||||
item_tax_rate={tax.account_head: tax.rate},
|
||||
charges=True
|
||||
charges=True,
|
||||
)
|
||||
)
|
||||
|
||||
#Check item tax rates if tax rate is zero.
|
||||
# Check item tax rates if tax rate is zero.
|
||||
if tax.rate == 0:
|
||||
for item in items:
|
||||
item_tax_rate = item.item_tax_rate
|
||||
@@ -171,8 +179,15 @@ def get_invoice_summary(items, taxes):
|
||||
if item_tax_rate and tax.account_head in item_tax_rate:
|
||||
key = cstr(item_tax_rate[tax.account_head])
|
||||
if key not in summary_data:
|
||||
summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0,
|
||||
"tax_exemption_reason": "", "tax_exemption_law": ""})
|
||||
summary_data.setdefault(
|
||||
key,
|
||||
{
|
||||
"tax_amount": 0.0,
|
||||
"taxable_amount": 0.0,
|
||||
"tax_exemption_reason": "",
|
||||
"tax_exemption_law": "",
|
||||
},
|
||||
)
|
||||
|
||||
summary_data[key]["tax_amount"] += item.tax_amount
|
||||
summary_data[key]["taxable_amount"] += item.net_amount
|
||||
@@ -180,93 +195,138 @@ def get_invoice_summary(items, taxes):
|
||||
summary_data[key]["tax_exemption_reason"] = tax.tax_exemption_reason
|
||||
summary_data[key]["tax_exemption_law"] = tax.tax_exemption_law
|
||||
|
||||
if summary_data.get("0.0") and tax.charge_type in ["On Previous Row Total",
|
||||
"On Previous Row Amount"]:
|
||||
if summary_data.get("0.0") and tax.charge_type in [
|
||||
"On Previous Row Total",
|
||||
"On Previous Row Amount",
|
||||
]:
|
||||
summary_data[key]["taxable_amount"] = tax.total
|
||||
|
||||
if summary_data == {}: #Implies that Zero VAT has not been set on any item.
|
||||
summary_data.setdefault("0.0", {"tax_amount": 0.0, "taxable_amount": tax.total,
|
||||
"tax_exemption_reason": tax.tax_exemption_reason, "tax_exemption_law": tax.tax_exemption_law})
|
||||
if summary_data == {}: # Implies that Zero VAT has not been set on any item.
|
||||
summary_data.setdefault(
|
||||
"0.0",
|
||||
{
|
||||
"tax_amount": 0.0,
|
||||
"taxable_amount": tax.total,
|
||||
"tax_exemption_reason": tax.tax_exemption_reason,
|
||||
"tax_exemption_law": tax.tax_exemption_law,
|
||||
},
|
||||
)
|
||||
|
||||
else:
|
||||
item_wise_tax_detail = json.loads(tax.item_wise_tax_detail)
|
||||
for rate_item in [tax_item for tax_item in item_wise_tax_detail.items() if tax_item[1][0] == tax.rate]:
|
||||
for rate_item in [
|
||||
tax_item for tax_item in item_wise_tax_detail.items() if tax_item[1][0] == tax.rate
|
||||
]:
|
||||
key = cstr(tax.rate)
|
||||
if not summary_data.get(key): summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0})
|
||||
if not summary_data.get(key):
|
||||
summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0})
|
||||
summary_data[key]["tax_amount"] += rate_item[1][1]
|
||||
summary_data[key]["taxable_amount"] += sum([item.net_amount for item in items if item.item_code == rate_item[0]])
|
||||
summary_data[key]["taxable_amount"] += sum(
|
||||
[item.net_amount for item in items if item.item_code == rate_item[0]]
|
||||
)
|
||||
|
||||
for item in items:
|
||||
key = cstr(tax.rate)
|
||||
if item.get("charges"):
|
||||
if not summary_data.get(key): summary_data.setdefault(key, {"taxable_amount": 0.0})
|
||||
if not summary_data.get(key):
|
||||
summary_data.setdefault(key, {"taxable_amount": 0.0})
|
||||
summary_data[key]["taxable_amount"] += item.taxable_amount
|
||||
|
||||
return summary_data
|
||||
|
||||
#Preflight for successful e-invoice export.
|
||||
|
||||
# Preflight for successful e-invoice export.
|
||||
def sales_invoice_validate(doc):
|
||||
#Validate company
|
||||
if doc.doctype != 'Sales Invoice':
|
||||
# Validate company
|
||||
if doc.doctype != "Sales Invoice":
|
||||
return
|
||||
|
||||
if not doc.company_address:
|
||||
frappe.throw(_("Please set an Address on the Company '%s'" % doc.company), title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Please set an Address on the Company '%s'" % doc.company),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
else:
|
||||
validate_address(doc.company_address)
|
||||
|
||||
company_fiscal_regime = frappe.get_cached_value("Company", doc.company, 'fiscal_regime')
|
||||
company_fiscal_regime = frappe.get_cached_value("Company", doc.company, "fiscal_regime")
|
||||
if not company_fiscal_regime:
|
||||
frappe.throw(_("Fiscal Regime is mandatory, kindly set the fiscal regime in the company {0}")
|
||||
.format(doc.company))
|
||||
frappe.throw(
|
||||
_("Fiscal Regime is mandatory, kindly set the fiscal regime in the company {0}").format(
|
||||
doc.company
|
||||
)
|
||||
)
|
||||
else:
|
||||
doc.company_fiscal_regime = company_fiscal_regime
|
||||
|
||||
doc.company_tax_id = frappe.get_cached_value("Company", doc.company, 'tax_id')
|
||||
doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, 'fiscal_code')
|
||||
doc.company_tax_id = frappe.get_cached_value("Company", doc.company, "tax_id")
|
||||
doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, "fiscal_code")
|
||||
if not doc.company_tax_id and not doc.company_fiscal_code:
|
||||
frappe.throw(_("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company), title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
|
||||
#Validate customer details
|
||||
# Validate customer details
|
||||
customer = frappe.get_doc("Customer", doc.customer)
|
||||
|
||||
if customer.customer_type == "Individual":
|
||||
doc.customer_fiscal_code = customer.fiscal_code
|
||||
if not doc.customer_fiscal_code:
|
||||
frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Please set Fiscal Code for the customer '%s'" % doc.customer),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
else:
|
||||
if customer.is_public_administration:
|
||||
doc.customer_fiscal_code = customer.fiscal_code
|
||||
if not doc.customer_fiscal_code:
|
||||
frappe.throw(_("Please set Fiscal Code for the public administration '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Please set Fiscal Code for the public administration '%s'" % doc.customer),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
else:
|
||||
doc.tax_id = customer.tax_id
|
||||
if not doc.tax_id:
|
||||
frappe.throw(_("Please set Tax ID for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Please set Tax ID for the customer '%s'" % doc.customer),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
|
||||
if not doc.customer_address:
|
||||
frappe.throw(_("Please set the Customer Address"), title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(_("Please set the Customer Address"), title=_("E-Invoicing Information Missing"))
|
||||
else:
|
||||
validate_address(doc.customer_address)
|
||||
|
||||
if not len(doc.taxes):
|
||||
frappe.throw(_("Please set at least one row in the Taxes and Charges Table"), title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Please set at least one row in the Taxes and Charges Table"),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
else:
|
||||
for row in doc.taxes:
|
||||
if row.rate == 0 and row.tax_amount == 0 and not row.tax_exemption_reason:
|
||||
frappe.throw(_("Row {0}: Please set at Tax Exemption Reason in Sales Taxes and Charges").format(row.idx),
|
||||
title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Row {0}: Please set at Tax Exemption Reason in Sales Taxes and Charges").format(row.idx),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
|
||||
for schedule in doc.payment_schedule:
|
||||
if schedule.mode_of_payment and not schedule.mode_of_payment_code:
|
||||
schedule.mode_of_payment_code = frappe.get_cached_value('Mode of Payment',
|
||||
schedule.mode_of_payment, 'mode_of_payment_code')
|
||||
schedule.mode_of_payment_code = frappe.get_cached_value(
|
||||
"Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"
|
||||
)
|
||||
|
||||
#Ensure payment details are valid for e-invoice.
|
||||
|
||||
# Ensure payment details are valid for e-invoice.
|
||||
def sales_invoice_on_submit(doc, method):
|
||||
#Validate payment details
|
||||
if get_company_country(doc.company) not in ['Italy',
|
||||
'Italia', 'Italian Republic', 'Repubblica Italiana']:
|
||||
# Validate payment details
|
||||
if get_company_country(doc.company) not in [
|
||||
"Italy",
|
||||
"Italia",
|
||||
"Italian Republic",
|
||||
"Repubblica Italiana",
|
||||
]:
|
||||
return
|
||||
|
||||
if not len(doc.payment_schedule):
|
||||
@@ -274,38 +334,53 @@ def sales_invoice_on_submit(doc, method):
|
||||
else:
|
||||
for schedule in doc.payment_schedule:
|
||||
if not schedule.mode_of_payment:
|
||||
frappe.throw(_("Row {0}: Please set the Mode of Payment in Payment Schedule").format(schedule.idx),
|
||||
title=_("E-Invoicing Information Missing"))
|
||||
elif not frappe.db.get_value("Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"):
|
||||
frappe.throw(_("Row {0}: Please set the correct code on Mode of Payment {1}").format(schedule.idx, schedule.mode_of_payment),
|
||||
title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Row {0}: Please set the Mode of Payment in Payment Schedule").format(schedule.idx),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
elif not frappe.db.get_value(
|
||||
"Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row {0}: Please set the correct code on Mode of Payment {1}").format(
|
||||
schedule.idx, schedule.mode_of_payment
|
||||
),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
|
||||
prepare_and_attach_invoice(doc)
|
||||
|
||||
|
||||
def prepare_and_attach_invoice(doc, replace=False):
|
||||
progressive_name, progressive_number = get_progressive_name_and_number(doc, replace)
|
||||
|
||||
invoice = prepare_invoice(doc, progressive_number)
|
||||
item_meta = frappe.get_meta("Sales Invoice Item")
|
||||
|
||||
invoice_xml = frappe.render_template('erpnext/regional/italy/e-invoice.xml',
|
||||
context={"doc": invoice, "item_meta": item_meta}, is_path=True)
|
||||
invoice_xml = frappe.render_template(
|
||||
"erpnext/regional/italy/e-invoice.xml",
|
||||
context={"doc": invoice, "item_meta": item_meta},
|
||||
is_path=True,
|
||||
)
|
||||
|
||||
invoice_xml = invoice_xml.replace("&", "&")
|
||||
|
||||
xml_filename = progressive_name + ".xml"
|
||||
|
||||
_file = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": xml_filename,
|
||||
"attached_to_doctype": doc.doctype,
|
||||
"attached_to_name": doc.name,
|
||||
"is_private": True,
|
||||
"content": invoice_xml
|
||||
})
|
||||
_file = frappe.get_doc(
|
||||
{
|
||||
"doctype": "File",
|
||||
"file_name": xml_filename,
|
||||
"attached_to_doctype": doc.doctype,
|
||||
"attached_to_name": doc.name,
|
||||
"is_private": True,
|
||||
"content": invoice_xml,
|
||||
}
|
||||
)
|
||||
_file.save()
|
||||
return _file
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_single_invoice(docname):
|
||||
doc = frappe.get_doc("Sales Invoice", docname)
|
||||
@@ -314,17 +389,24 @@ def generate_single_invoice(docname):
|
||||
e_invoice = prepare_and_attach_invoice(doc, True)
|
||||
return e_invoice.file_url
|
||||
|
||||
|
||||
# Delete e-invoice attachment on cancel.
|
||||
def sales_invoice_on_cancel(doc, method):
|
||||
if get_company_country(doc.company) not in ['Italy',
|
||||
'Italia', 'Italian Republic', 'Repubblica Italiana']:
|
||||
if get_company_country(doc.company) not in [
|
||||
"Italy",
|
||||
"Italia",
|
||||
"Italian Republic",
|
||||
"Repubblica Italiana",
|
||||
]:
|
||||
return
|
||||
|
||||
for attachment in get_e_invoice_attachments(doc):
|
||||
remove_file(attachment.name, attached_to_doctype=doc.doctype, attached_to_name=doc.name)
|
||||
|
||||
|
||||
def get_company_country(company):
|
||||
return frappe.get_cached_value('Company', company, 'country')
|
||||
return frappe.get_cached_value("Company", company, "country")
|
||||
|
||||
|
||||
def get_e_invoice_attachments(invoices):
|
||||
if not isinstance(invoices, list):
|
||||
@@ -338,16 +420,14 @@ def get_e_invoice_attachments(invoices):
|
||||
invoice.company_tax_id
|
||||
if invoice.company_tax_id.startswith("IT")
|
||||
else "IT" + invoice.company_tax_id
|
||||
) for invoice in invoices
|
||||
)
|
||||
for invoice in invoices
|
||||
}
|
||||
|
||||
attachments = frappe.get_all(
|
||||
"File",
|
||||
fields=("name", "file_name", "attached_to_name", "is_private"),
|
||||
filters= {
|
||||
"attached_to_name": ('in', tax_id_map),
|
||||
"attached_to_doctype": 'Sales Invoice'
|
||||
}
|
||||
filters={"attached_to_name": ("in", tax_id_map), "attached_to_doctype": "Sales Invoice"},
|
||||
)
|
||||
|
||||
out = []
|
||||
@@ -355,21 +435,24 @@ def get_e_invoice_attachments(invoices):
|
||||
if (
|
||||
attachment.file_name
|
||||
and attachment.file_name.endswith(".xml")
|
||||
and attachment.file_name.startswith(
|
||||
tax_id_map.get(attachment.attached_to_name))
|
||||
and attachment.file_name.startswith(tax_id_map.get(attachment.attached_to_name))
|
||||
):
|
||||
out.append(attachment)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def validate_address(address_name):
|
||||
fields = ["pincode", "city", "country_code"]
|
||||
data = frappe.get_cached_value("Address", address_name, fields, as_dict=1) or {}
|
||||
|
||||
for field in fields:
|
||||
if not data.get(field):
|
||||
frappe.throw(_("Please set {0} for address {1}").format(field.replace('-',''), address_name),
|
||||
title=_("E-Invoicing Information Missing"))
|
||||
frappe.throw(
|
||||
_("Please set {0} for address {1}").format(field.replace("-", ""), address_name),
|
||||
title=_("E-Invoicing Information Missing"),
|
||||
)
|
||||
|
||||
|
||||
def get_unamended_name(doc):
|
||||
attributes = ["naming_series", "amended_from"]
|
||||
@@ -382,6 +465,7 @@ def get_unamended_name(doc):
|
||||
else:
|
||||
return doc.name
|
||||
|
||||
|
||||
def get_progressive_name_and_number(doc, replace=False):
|
||||
if replace:
|
||||
for attachment in get_e_invoice_attachments(doc):
|
||||
@@ -389,24 +473,30 @@ def get_progressive_name_and_number(doc, replace=False):
|
||||
filename = attachment.file_name.split(".xml")[0]
|
||||
return filename, filename.split("_")[1]
|
||||
|
||||
company_tax_id = doc.company_tax_id if doc.company_tax_id.startswith("IT") else "IT" + doc.company_tax_id
|
||||
company_tax_id = (
|
||||
doc.company_tax_id if doc.company_tax_id.startswith("IT") else "IT" + doc.company_tax_id
|
||||
)
|
||||
progressive_name = frappe.model.naming.make_autoname(company_tax_id + "_.#####")
|
||||
progressive_number = progressive_name.split("_")[1]
|
||||
|
||||
return progressive_name, progressive_number
|
||||
|
||||
|
||||
def set_state_code(doc, method):
|
||||
if doc.get('country_code'):
|
||||
if doc.get("country_code"):
|
||||
doc.country_code = doc.country_code.upper()
|
||||
|
||||
if not doc.get('state'):
|
||||
if not doc.get("state"):
|
||||
return
|
||||
|
||||
if not (hasattr(doc, "state_code") and doc.country in ["Italy", "Italia", "Italian Republic", "Repubblica Italiana"]):
|
||||
if not (
|
||||
hasattr(doc, "state_code")
|
||||
and doc.country in ["Italy", "Italia", "Italian Republic", "Repubblica Italiana"]
|
||||
):
|
||||
return
|
||||
|
||||
state_codes_lower = {key.lower():value for key,value in state_codes.items()}
|
||||
state_codes_lower = {key.lower(): value for key, value in state_codes.items()}
|
||||
|
||||
state = doc.get('state','').lower()
|
||||
state = doc.get("state", "").lower()
|
||||
if state_codes_lower.get(state):
|
||||
doc.state_code = state_codes_lower.get(state)
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
frappe.query_reports["DATEV"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company") || frappe.defaults.get_global_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"default": moment().subtract(1, 'month').startOf('month').format(),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"default": moment().subtract(1, 'month').endOf('month').format(),
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"label": __("Voucher Type"),
|
||||
"fieldtype": "Select",
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nPayment Entry\nExpense Claim\nPayroll Entry\nBank Reconciliation\nAsset\nStock Entry"
|
||||
}
|
||||
],
|
||||
onload: function(query_report) {
|
||||
let company = frappe.query_report.get_filter_value('company');
|
||||
frappe.db.exists('DATEV Settings', company).then((settings_exist) => {
|
||||
if (!settings_exist) {
|
||||
frappe.confirm(__('DATEV Settings for your Company are missing. Would you like to create them now?'),
|
||||
() => frappe.new_doc('DATEV Settings', {'company': company})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
query_report.page.add_menu_item(__("Download DATEV File"), () => {
|
||||
const filters = encodeURIComponent(
|
||||
JSON.stringify(
|
||||
query_report.get_values()
|
||||
)
|
||||
);
|
||||
window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`);
|
||||
});
|
||||
|
||||
query_report.page.add_menu_item(__("Change DATEV Settings"), () => {
|
||||
let company = frappe.query_report.get_filter_value('company'); // read company from filters again – it might have changed by now.
|
||||
frappe.set_route('Form', 'DATEV Settings', company);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2019-04-24 08:45:16.650129",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-04-06 12:23:00.379517",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "DATEV",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "DATEV",
|
||||
"report_type": "Script Report",
|
||||
"roles": []
|
||||
}
|
||||
@@ -1,586 +0,0 @@
|
||||
"""
|
||||
Provide a report and downloadable CSV according to the German DATEV format.
|
||||
|
||||
- Query report showing only the columns that contain data, formatted nicely for
|
||||
dispay to the user.
|
||||
- CSV download functionality `download_datev_csv` that provides a CSV file with
|
||||
all required columns. Used to import the data into the DATEV Software.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.regional.germany.utils.datev.datev_constants import (
|
||||
AccountNames,
|
||||
DebtorsCreditors,
|
||||
Transactions,
|
||||
)
|
||||
from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, zip_and_download
|
||||
|
||||
COLUMNS = [
|
||||
{
|
||||
"label": "Umsatz (ohne Soll/Haben-Kz)",
|
||||
"fieldname": "Umsatz (ohne Soll/Haben-Kz)",
|
||||
"fieldtype": "Currency",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Soll/Haben-Kennzeichen",
|
||||
"fieldname": "Soll/Haben-Kennzeichen",
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Konto",
|
||||
"fieldname": "Konto",
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Gegenkonto (ohne BU-Schlüssel)",
|
||||
"fieldname": "Gegenkonto (ohne BU-Schlüssel)",
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "BU-Schlüssel",
|
||||
"fieldname": "BU-Schlüssel",
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Belegdatum",
|
||||
"fieldname": "Belegdatum",
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Belegfeld 1",
|
||||
"fieldname": "Belegfeld 1",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Buchungstext",
|
||||
"fieldname": "Buchungstext",
|
||||
"fieldtype": "Text",
|
||||
"width": 300
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 1",
|
||||
"fieldname": "Beleginfo - Art 1",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 1",
|
||||
"fieldname": "Beleginfo - Inhalt 1",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "Beleginfo - Art 1",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 2",
|
||||
"fieldname": "Beleginfo - Art 2",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 2",
|
||||
"fieldname": "Beleginfo - Inhalt 2",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "Beleginfo - Art 2",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 3",
|
||||
"fieldname": "Beleginfo - Art 3",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 3",
|
||||
"fieldname": "Beleginfo - Inhalt 3",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "Beleginfo - Art 3",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 4",
|
||||
"fieldname": "Beleginfo - Art 4",
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 4",
|
||||
"fieldname": "Beleginfo - Inhalt 4",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 5",
|
||||
"fieldname": "Beleginfo - Art 5",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 5",
|
||||
"fieldname": "Beleginfo - Inhalt 5",
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Art 6",
|
||||
"fieldname": "Beleginfo - Art 6",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": "Beleginfo - Inhalt 6",
|
||||
"fieldname": "Beleginfo - Inhalt 6",
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": "Fälligkeit",
|
||||
"fieldname": "Fälligkeit",
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
"""Entry point for frappe."""
|
||||
data = []
|
||||
if filters and validate(filters):
|
||||
fn = 'temporary_against_account_number'
|
||||
filters[fn] = frappe.get_value('DATEV Settings', filters.get('company'), fn)
|
||||
data = get_transactions(filters, as_dict=0)
|
||||
|
||||
return COLUMNS, data
|
||||
|
||||
|
||||
def validate(filters):
|
||||
"""Make sure all mandatory filters and settings are present."""
|
||||
company = filters.get('company')
|
||||
if not company:
|
||||
frappe.throw(_('<b>Company</b> is a mandatory filter.'))
|
||||
|
||||
from_date = filters.get('from_date')
|
||||
if not from_date:
|
||||
frappe.throw(_('<b>From Date</b> is a mandatory filter.'))
|
||||
|
||||
to_date = filters.get('to_date')
|
||||
if not to_date:
|
||||
frappe.throw(_('<b>To Date</b> is a mandatory filter.'))
|
||||
|
||||
validate_fiscal_year(from_date, to_date, company)
|
||||
|
||||
if not frappe.db.exists('DATEV Settings', filters.get('company')):
|
||||
msg = 'Please create DATEV Settings for Company {}'.format(filters.get('company'))
|
||||
frappe.log_error(msg, title='DATEV Settings missing')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def validate_fiscal_year(from_date, to_date, company):
|
||||
from_fiscal_year = get_fiscal_year(date=from_date, company=company)
|
||||
to_fiscal_year = get_fiscal_year(date=to_date, company=company)
|
||||
if from_fiscal_year != to_fiscal_year:
|
||||
frappe.throw(_('Dates {} and {} are not in the same fiscal year.').format(from_date, to_date))
|
||||
|
||||
|
||||
def get_transactions(filters, as_dict=1):
|
||||
def run(params_method, filters):
|
||||
extra_fields, extra_joins, extra_filters = params_method(filters)
|
||||
return run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=as_dict)
|
||||
|
||||
def sort_by(row):
|
||||
# "Belegdatum" is in the fifth column when list format is used
|
||||
return row["Belegdatum" if as_dict else 5]
|
||||
|
||||
type_map = {
|
||||
# specific query methods for some voucher types
|
||||
"Payment Entry": get_payment_entry_params,
|
||||
"Sales Invoice": get_sales_invoice_params,
|
||||
"Purchase Invoice": get_purchase_invoice_params
|
||||
}
|
||||
|
||||
only_voucher_type = filters.get("voucher_type")
|
||||
transactions = []
|
||||
|
||||
for voucher_type, get_voucher_params in type_map.items():
|
||||
if only_voucher_type and only_voucher_type != voucher_type:
|
||||
continue
|
||||
|
||||
transactions.extend(run(params_method=get_voucher_params, filters=filters))
|
||||
|
||||
if not only_voucher_type or only_voucher_type not in type_map:
|
||||
# generic query method for all other voucher types
|
||||
filters["exclude_voucher_types"] = type_map.keys()
|
||||
transactions.extend(run(params_method=get_generic_params, filters=filters))
|
||||
|
||||
return sorted(transactions, key=sort_by)
|
||||
|
||||
|
||||
def get_payment_entry_params(filters):
|
||||
extra_fields = """
|
||||
, 'Zahlungsreferenz' as 'Beleginfo - Art 5'
|
||||
, pe.reference_no as 'Beleginfo - Inhalt 5'
|
||||
, 'Buchungstag' as 'Beleginfo - Art 6'
|
||||
, pe.reference_date as 'Beleginfo - Inhalt 6'
|
||||
, '' as 'Fälligkeit'
|
||||
"""
|
||||
|
||||
extra_joins = """
|
||||
LEFT JOIN `tabPayment Entry` pe
|
||||
ON gl.voucher_no = pe.name
|
||||
"""
|
||||
|
||||
extra_filters = """
|
||||
AND gl.voucher_type = 'Payment Entry'
|
||||
"""
|
||||
|
||||
return extra_fields, extra_joins, extra_filters
|
||||
|
||||
|
||||
def get_sales_invoice_params(filters):
|
||||
extra_fields = """
|
||||
, '' as 'Beleginfo - Art 5'
|
||||
, '' as 'Beleginfo - Inhalt 5'
|
||||
, '' as 'Beleginfo - Art 6'
|
||||
, '' as 'Beleginfo - Inhalt 6'
|
||||
, si.due_date as 'Fälligkeit'
|
||||
"""
|
||||
|
||||
extra_joins = """
|
||||
LEFT JOIN `tabSales Invoice` si
|
||||
ON gl.voucher_no = si.name
|
||||
"""
|
||||
|
||||
extra_filters = """
|
||||
AND gl.voucher_type = 'Sales Invoice'
|
||||
"""
|
||||
|
||||
return extra_fields, extra_joins, extra_filters
|
||||
|
||||
|
||||
def get_purchase_invoice_params(filters):
|
||||
extra_fields = """
|
||||
, 'Lieferanten-Rechnungsnummer' as 'Beleginfo - Art 5'
|
||||
, pi.bill_no as 'Beleginfo - Inhalt 5'
|
||||
, 'Lieferanten-Rechnungsdatum' as 'Beleginfo - Art 6'
|
||||
, pi.bill_date as 'Beleginfo - Inhalt 6'
|
||||
, pi.due_date as 'Fälligkeit'
|
||||
"""
|
||||
|
||||
extra_joins = """
|
||||
LEFT JOIN `tabPurchase Invoice` pi
|
||||
ON gl.voucher_no = pi.name
|
||||
"""
|
||||
|
||||
extra_filters = """
|
||||
AND gl.voucher_type = 'Purchase Invoice'
|
||||
"""
|
||||
|
||||
return extra_fields, extra_joins, extra_filters
|
||||
|
||||
|
||||
def get_generic_params(filters):
|
||||
# produce empty fields so all rows will have the same length
|
||||
extra_fields = """
|
||||
, '' as 'Beleginfo - Art 5'
|
||||
, '' as 'Beleginfo - Inhalt 5'
|
||||
, '' as 'Beleginfo - Art 6'
|
||||
, '' as 'Beleginfo - Inhalt 6'
|
||||
, '' as 'Fälligkeit'
|
||||
"""
|
||||
extra_joins = ""
|
||||
|
||||
if filters.get("exclude_voucher_types"):
|
||||
# exclude voucher types that are queried by a dedicated method
|
||||
exclude = "({})".format(', '.join("'{}'".format(key) for key in filters.get("exclude_voucher_types")))
|
||||
extra_filters = "AND gl.voucher_type NOT IN {}".format(exclude)
|
||||
|
||||
# if voucher type filter is set, allow only this type
|
||||
if filters.get("voucher_type"):
|
||||
extra_filters += " AND gl.voucher_type = %(voucher_type)s"
|
||||
|
||||
return extra_fields, extra_joins, extra_filters
|
||||
|
||||
|
||||
def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1):
|
||||
"""
|
||||
Get a list of accounting entries.
|
||||
|
||||
Select GL Entries joined with Account and Party Account in order to get the
|
||||
account numbers. Returns a list of accounting entries.
|
||||
|
||||
Arguments:
|
||||
filters -- dict of filters to be passed to the sql query
|
||||
as_dict -- return as list of dicts [0,1]
|
||||
"""
|
||||
query = """
|
||||
SELECT
|
||||
|
||||
/* either debit or credit amount; always positive */
|
||||
case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)',
|
||||
|
||||
/* 'H' when credit, 'S' when debit */
|
||||
case gl.debit when 0 then 'H' else 'S' end as 'Soll/Haben-Kennzeichen',
|
||||
|
||||
/* account number or, if empty, party account number */
|
||||
acc.account_number as 'Konto',
|
||||
|
||||
/* against number or, if empty, party against number */
|
||||
%(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
|
||||
|
||||
/* disable automatic VAT deduction */
|
||||
'40' as 'BU-Schlüssel',
|
||||
|
||||
gl.posting_date as 'Belegdatum',
|
||||
gl.voucher_no as 'Belegfeld 1',
|
||||
REPLACE(LEFT(gl.remarks, 60), '\n', ' ') as 'Buchungstext',
|
||||
gl.voucher_type as 'Beleginfo - Art 1',
|
||||
gl.voucher_no as 'Beleginfo - Inhalt 1',
|
||||
gl.against_voucher_type as 'Beleginfo - Art 2',
|
||||
gl.against_voucher as 'Beleginfo - Inhalt 2',
|
||||
gl.party_type as 'Beleginfo - Art 3',
|
||||
gl.party as 'Beleginfo - Inhalt 3',
|
||||
case gl.party_type when 'Customer' then 'Debitorennummer' when 'Supplier' then 'Kreditorennummer' else NULL end as 'Beleginfo - Art 4',
|
||||
par.debtor_creditor_number as 'Beleginfo - Inhalt 4'
|
||||
|
||||
{extra_fields}
|
||||
|
||||
FROM `tabGL Entry` gl
|
||||
|
||||
/* Kontonummer */
|
||||
LEFT JOIN `tabAccount` acc
|
||||
ON gl.account = acc.name
|
||||
|
||||
LEFT JOIN `tabParty Account` par
|
||||
ON par.parent = gl.party
|
||||
AND par.parenttype = gl.party_type
|
||||
AND par.company = %(company)s
|
||||
|
||||
{extra_joins}
|
||||
|
||||
WHERE gl.company = %(company)s
|
||||
AND DATE(gl.posting_date) >= %(from_date)s
|
||||
AND DATE(gl.posting_date) <= %(to_date)s
|
||||
|
||||
{extra_filters}
|
||||
|
||||
ORDER BY 'Belegdatum', gl.voucher_no""".format(
|
||||
extra_fields=extra_fields,
|
||||
extra_joins=extra_joins,
|
||||
extra_filters=extra_filters
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(query, filters, as_dict=as_dict)
|
||||
|
||||
return gl_entries
|
||||
|
||||
|
||||
def get_customers(filters):
|
||||
"""
|
||||
Get a list of Customers.
|
||||
|
||||
Arguments:
|
||||
filters -- dict of filters to be passed to the sql query
|
||||
"""
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
|
||||
par.debtor_creditor_number as 'Konto',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Company' THEN cus.customer_name
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp Unternehmen)',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Individual' THEN TRIM(SUBSTR(cus.customer_name, LOCATE(' ', cus.customer_name)))
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp natürl. Person)',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(cus.customer_name, ' ', 1), ' ', -1)
|
||||
ELSE null
|
||||
END as 'Vorname (Adressatentyp natürl. Person)',
|
||||
CASE cus.customer_type
|
||||
WHEN 'Individual' THEN '1'
|
||||
WHEN 'Company' THEN '2'
|
||||
ELSE '0'
|
||||
END as 'Adressatentyp',
|
||||
adr.address_line1 as 'Straße',
|
||||
adr.pincode as 'Postleitzahl',
|
||||
adr.city as 'Ort',
|
||||
UPPER(country.code) as 'Land',
|
||||
adr.address_line2 as 'Adresszusatz',
|
||||
adr.email_id as 'E-Mail',
|
||||
adr.phone as 'Telefon',
|
||||
adr.fax as 'Fax',
|
||||
cus.website as 'Internet',
|
||||
cus.tax_id as 'Steuernummer'
|
||||
|
||||
FROM `tabCustomer` cus
|
||||
|
||||
left join `tabParty Account` par
|
||||
on par.parent = cus.name
|
||||
and par.parenttype = 'Customer'
|
||||
and par.company = %(company)s
|
||||
|
||||
left join `tabDynamic Link` dyn_adr
|
||||
on dyn_adr.link_name = cus.name
|
||||
and dyn_adr.link_doctype = 'Customer'
|
||||
and dyn_adr.parenttype = 'Address'
|
||||
|
||||
left join `tabAddress` adr
|
||||
on adr.name = dyn_adr.parent
|
||||
and adr.is_primary_address = '1'
|
||||
|
||||
left join `tabCountry` country
|
||||
on country.name = adr.country
|
||||
|
||||
WHERE adr.is_primary_address = '1'
|
||||
""", filters, as_dict=1)
|
||||
|
||||
|
||||
def get_suppliers(filters):
|
||||
"""
|
||||
Get a list of Suppliers.
|
||||
|
||||
Arguments:
|
||||
filters -- dict of filters to be passed to the sql query
|
||||
"""
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
|
||||
par.debtor_creditor_number as 'Konto',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Company' THEN sup.supplier_name
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp Unternehmen)',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Individual' THEN TRIM(SUBSTR(sup.supplier_name, LOCATE(' ', sup.supplier_name)))
|
||||
ELSE null
|
||||
END as 'Name (Adressatentyp natürl. Person)',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Individual' THEN SUBSTRING_INDEX(SUBSTRING_INDEX(sup.supplier_name, ' ', 1), ' ', -1)
|
||||
ELSE null
|
||||
END as 'Vorname (Adressatentyp natürl. Person)',
|
||||
CASE sup.supplier_type
|
||||
WHEN 'Individual' THEN '1'
|
||||
WHEN 'Company' THEN '2'
|
||||
ELSE '0'
|
||||
END as 'Adressatentyp',
|
||||
adr.address_line1 as 'Straße',
|
||||
adr.pincode as 'Postleitzahl',
|
||||
adr.city as 'Ort',
|
||||
UPPER(country.code) as 'Land',
|
||||
adr.address_line2 as 'Adresszusatz',
|
||||
adr.email_id as 'E-Mail',
|
||||
adr.phone as 'Telefon',
|
||||
adr.fax as 'Fax',
|
||||
sup.website as 'Internet',
|
||||
sup.tax_id as 'Steuernummer',
|
||||
case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis'
|
||||
|
||||
FROM `tabSupplier` sup
|
||||
|
||||
left join `tabParty Account` par
|
||||
on par.parent = sup.name
|
||||
and par.parenttype = 'Supplier'
|
||||
and par.company = %(company)s
|
||||
|
||||
left join `tabDynamic Link` dyn_adr
|
||||
on dyn_adr.link_name = sup.name
|
||||
and dyn_adr.link_doctype = 'Supplier'
|
||||
and dyn_adr.parenttype = 'Address'
|
||||
|
||||
left join `tabAddress` adr
|
||||
on adr.name = dyn_adr.parent
|
||||
and adr.is_primary_address = '1'
|
||||
|
||||
left join `tabCountry` country
|
||||
on country.name = adr.country
|
||||
|
||||
WHERE adr.is_primary_address = '1'
|
||||
""", filters, as_dict=1)
|
||||
|
||||
|
||||
def get_account_names(filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
|
||||
account_number as 'Konto',
|
||||
LEFT(account_name, 40) as 'Kontenbeschriftung',
|
||||
'de-DE' as 'Sprach-ID'
|
||||
|
||||
FROM `tabAccount`
|
||||
WHERE company = %(company)s
|
||||
AND is_group = 0
|
||||
AND account_number != ''
|
||||
""", filters, as_dict=1)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_datev_csv(filters):
|
||||
"""
|
||||
Provide accounting entries for download in DATEV format.
|
||||
|
||||
Validate the filters, get the data, produce the CSV file and provide it for
|
||||
download. Can be called like this:
|
||||
|
||||
GET /api/method/erpnext.regional.report.datev.datev.download_datev_csv
|
||||
|
||||
Arguments / Params:
|
||||
filters -- dict of filters to be passed to the sql query
|
||||
"""
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
validate(filters)
|
||||
company = filters.get('company')
|
||||
|
||||
fiscal_year = get_fiscal_year(date=filters.get('from_date'), company=company)
|
||||
filters['fiscal_year_start'] = fiscal_year[1]
|
||||
|
||||
# set chart of accounts used
|
||||
coa = frappe.get_value('Company', company, 'chart_of_accounts')
|
||||
filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
|
||||
|
||||
datev_settings = frappe.get_doc('DATEV Settings', company)
|
||||
filters['account_number_length'] = datev_settings.account_number_length
|
||||
filters['temporary_against_account_number'] = datev_settings.temporary_against_account_number
|
||||
|
||||
transactions = get_transactions(filters)
|
||||
account_names = get_account_names(filters)
|
||||
customers = get_customers(filters)
|
||||
suppliers = get_suppliers(filters)
|
||||
|
||||
zip_name = '{} DATEV.zip'.format(frappe.utils.datetime.date.today())
|
||||
zip_and_download(zip_name, [
|
||||
{
|
||||
'file_name': 'EXTF_Buchungsstapel.csv',
|
||||
'csv_data': get_datev_csv(transactions, filters, csv_class=Transactions)
|
||||
},
|
||||
{
|
||||
'file_name': 'EXTF_Kontenbeschriftungen.csv',
|
||||
'csv_data': get_datev_csv(account_names, filters, csv_class=AccountNames)
|
||||
},
|
||||
{
|
||||
'file_name': 'EXTF_Kunden.csv',
|
||||
'csv_data': get_datev_csv(customers, filters, csv_class=DebtorsCreditors)
|
||||
},
|
||||
{
|
||||
'file_name': 'EXTF_Lieferanten.csv',
|
||||
'csv_data': get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors)
|
||||
},
|
||||
])
|
||||
@@ -1,242 +0,0 @@
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
from unittest import TestCase
|
||||
|
||||
import frappe
|
||||
from frappe.utils import cstr, now_datetime, today
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.regional.germany.utils.datev.datev_constants import (
|
||||
AccountNames,
|
||||
DebtorsCreditors,
|
||||
Transactions,
|
||||
)
|
||||
from erpnext.regional.germany.utils.datev.datev_csv import get_datev_csv, get_header
|
||||
from erpnext.regional.report.datev.datev import (
|
||||
download_datev_csv,
|
||||
get_account_names,
|
||||
get_customers,
|
||||
get_suppliers,
|
||||
get_transactions,
|
||||
)
|
||||
|
||||
|
||||
def make_company(company_name, abbr):
|
||||
if not frappe.db.exists("Company", company_name):
|
||||
company = frappe.get_doc({
|
||||
"doctype": "Company",
|
||||
"company_name": company_name,
|
||||
"abbr": abbr,
|
||||
"default_currency": "EUR",
|
||||
"country": "Germany",
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
"chart_of_accounts": "SKR04 mit Kontonummern"
|
||||
})
|
||||
company.insert()
|
||||
else:
|
||||
company = frappe.get_doc("Company", company_name)
|
||||
|
||||
# indempotent
|
||||
company.create_default_warehouses()
|
||||
|
||||
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
|
||||
company.create_default_cost_center()
|
||||
|
||||
company.save()
|
||||
return company
|
||||
|
||||
def setup_fiscal_year():
|
||||
fiscal_year = None
|
||||
year = cstr(now_datetime().year)
|
||||
if not frappe.db.get_value("Fiscal Year", {"year": year}, "name"):
|
||||
try:
|
||||
fiscal_year = frappe.get_doc({
|
||||
"doctype": "Fiscal Year",
|
||||
"year": year,
|
||||
"year_start_date": "{0}-01-01".format(year),
|
||||
"year_end_date": "{0}-12-31".format(year)
|
||||
})
|
||||
fiscal_year.insert()
|
||||
except frappe.NameError:
|
||||
pass
|
||||
|
||||
if fiscal_year:
|
||||
fiscal_year.set_as_default()
|
||||
|
||||
def make_customer_with_account(customer_name, company):
|
||||
acc_name = frappe.db.get_value("Account", {
|
||||
"account_name": customer_name,
|
||||
"company": company.name
|
||||
}, "name")
|
||||
|
||||
if not acc_name:
|
||||
acc = frappe.get_doc({
|
||||
"doctype": "Account",
|
||||
"parent_account": "1 - Forderungen aus Lieferungen und Leistungen - _TG",
|
||||
"account_name": customer_name,
|
||||
"company": company.name,
|
||||
"account_type": "Receivable",
|
||||
"account_number": "10001"
|
||||
})
|
||||
acc.insert()
|
||||
acc_name = acc.name
|
||||
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
customer = frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
"customer_name": customer_name,
|
||||
"customer_type": "Company",
|
||||
"accounts": [{
|
||||
"company": company.name,
|
||||
"account": acc_name
|
||||
}]
|
||||
})
|
||||
customer.insert()
|
||||
else:
|
||||
customer = frappe.get_doc("Customer", customer_name)
|
||||
|
||||
return customer
|
||||
|
||||
def make_item(item_code, company):
|
||||
warehouse_name = frappe.db.get_value("Warehouse", {
|
||||
"warehouse_name": "Stores",
|
||||
"company": company.name
|
||||
}, "name")
|
||||
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
item = frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": item_code,
|
||||
"item_name": item_code,
|
||||
"description": item_code,
|
||||
"item_group": "All Item Groups",
|
||||
"is_stock_item": 0,
|
||||
"is_purchase_item": 0,
|
||||
"is_customer_provided_item": 0,
|
||||
"item_defaults": [{
|
||||
"default_warehouse": warehouse_name,
|
||||
"company": company.name
|
||||
}]
|
||||
})
|
||||
item.insert()
|
||||
else:
|
||||
item = frappe.get_doc("Item", item_code)
|
||||
return item
|
||||
|
||||
def make_datev_settings(company):
|
||||
if not frappe.db.exists("DATEV Settings", company.name):
|
||||
frappe.get_doc({
|
||||
"doctype": "DATEV Settings",
|
||||
"client": company.name,
|
||||
"client_number": "12345",
|
||||
"consultant_number": "67890",
|
||||
"temporary_against_account_number": "9999"
|
||||
}).insert()
|
||||
|
||||
|
||||
class TestDatev(TestCase):
|
||||
def setUp(self):
|
||||
self.company = make_company("_Test GmbH", "_TG")
|
||||
self.customer = make_customer_with_account("_Test Kunde GmbH", self.company)
|
||||
self.filters = {
|
||||
"company": self.company.name,
|
||||
"from_date": today(),
|
||||
"to_date": today(),
|
||||
"temporary_against_account_number": "9999"
|
||||
}
|
||||
|
||||
make_datev_settings(self.company)
|
||||
item = make_item("_Test Item", self.company)
|
||||
setup_fiscal_year()
|
||||
|
||||
warehouse = frappe.db.get_value("Item Default", {
|
||||
"parent": item.name,
|
||||
"company": self.company.name
|
||||
}, "default_warehouse")
|
||||
|
||||
income_account = frappe.db.get_value("Account", {
|
||||
"account_number": "4200",
|
||||
"company": self.company.name
|
||||
}, "name")
|
||||
|
||||
tax_account = frappe.db.get_value("Account", {
|
||||
"account_number": "3806",
|
||||
"company": self.company.name
|
||||
}, "name")
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=self.company.name,
|
||||
customer=self.customer.name,
|
||||
currency=self.company.default_currency,
|
||||
debit_to=self.customer.accounts[0].account,
|
||||
income_account="4200 - Erlöse - _TG",
|
||||
expense_account="6990 - Herstellungskosten - _TG",
|
||||
cost_center=self.company.cost_center,
|
||||
warehouse=warehouse,
|
||||
item=item.name,
|
||||
do_not_save=1
|
||||
)
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": tax_account,
|
||||
"description": "Umsatzsteuer 19 %",
|
||||
"rate": 19,
|
||||
"cost_center": self.company.cost_center
|
||||
})
|
||||
|
||||
si.cost_center = self.company.cost_center
|
||||
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
def test_columns(self):
|
||||
def is_subset(get_data, allowed_keys):
|
||||
"""
|
||||
Validate that the dict contains only allowed keys.
|
||||
|
||||
Params:
|
||||
get_data -- Function that returns a list of dicts.
|
||||
allowed_keys -- List of allowed keys
|
||||
"""
|
||||
data = get_data(self.filters)
|
||||
if data == []:
|
||||
# No data and, therefore, no columns is okay
|
||||
return True
|
||||
actual_set = set(data[0].keys())
|
||||
# allowed set must be interpreted as unicode to match the actual set
|
||||
allowed_set = set({frappe.as_unicode(key) for key in allowed_keys})
|
||||
return actual_set.issubset(allowed_set)
|
||||
|
||||
self.assertTrue(is_subset(get_transactions, Transactions.COLUMNS))
|
||||
self.assertTrue(is_subset(get_customers, DebtorsCreditors.COLUMNS))
|
||||
self.assertTrue(is_subset(get_suppliers, DebtorsCreditors.COLUMNS))
|
||||
self.assertTrue(is_subset(get_account_names, AccountNames.COLUMNS))
|
||||
|
||||
def test_header(self):
|
||||
self.assertTrue(Transactions.DATA_CATEGORY in get_header(self.filters, Transactions))
|
||||
self.assertTrue(AccountNames.DATA_CATEGORY in get_header(self.filters, AccountNames))
|
||||
self.assertTrue(DebtorsCreditors.DATA_CATEGORY in get_header(self.filters, DebtorsCreditors))
|
||||
|
||||
def test_csv(self):
|
||||
test_data = [{
|
||||
"Umsatz (ohne Soll/Haben-Kz)": 100,
|
||||
"Soll/Haben-Kennzeichen": "H",
|
||||
"Kontonummer": "4200",
|
||||
"Gegenkonto (ohne BU-Schlüssel)": "10000",
|
||||
"Belegdatum": today(),
|
||||
"Buchungstext": "No remark",
|
||||
"Beleginfo - Art 1": "Sales Invoice",
|
||||
"Beleginfo - Inhalt 1": "SINV-0001"
|
||||
}]
|
||||
get_datev_csv(data=test_data, filters=self.filters, csv_class=Transactions)
|
||||
|
||||
def test_download(self):
|
||||
"""Assert that the returned file is a ZIP file."""
|
||||
download_datev_csv(self.filters)
|
||||
|
||||
# zipfile.is_zipfile() expects a file-like object
|
||||
zip_buffer = BytesIO()
|
||||
zip_buffer.write(frappe.response['filecontent'])
|
||||
|
||||
self.assertTrue(zipfile.is_zipfile(zip_buffer))
|
||||
@@ -26,31 +26,42 @@ def execute(filters=None):
|
||||
|
||||
|
||||
def validate_filters(filters, account_details):
|
||||
if not filters.get('company'):
|
||||
frappe.throw(_('{0} is mandatory').format(_('Company')))
|
||||
if not filters.get("company"):
|
||||
frappe.throw(_("{0} is mandatory").format(_("Company")))
|
||||
|
||||
if not filters.get('fiscal_year'):
|
||||
frappe.throw(_('{0} is mandatory').format(_('Fiscal Year')))
|
||||
if not filters.get("fiscal_year"):
|
||||
frappe.throw(_("{0} is mandatory").format(_("Fiscal Year")))
|
||||
|
||||
|
||||
def set_account_currency(filters):
|
||||
|
||||
filters["company_currency"] = frappe.get_cached_value('Company', filters.company, "default_currency")
|
||||
filters["company_currency"] = frappe.get_cached_value(
|
||||
"Company", filters.company, "default_currency"
|
||||
)
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [
|
||||
"JournalCode" + "::90", "JournalLib" + "::90",
|
||||
"EcritureNum" + ":Dynamic Link:90", "EcritureDate" + "::90",
|
||||
"CompteNum" + ":Link/Account:100", "CompteLib" + ":Link/Account:200",
|
||||
"CompAuxNum" + "::90", "CompAuxLib" + "::90",
|
||||
"PieceRef" + "::90", "PieceDate" + "::90",
|
||||
"EcritureLib" + "::90", "Debit" + "::90", "Credit" + "::90",
|
||||
"EcritureLet" + "::90", "DateLet" +
|
||||
"::90", "ValidDate" + "::90",
|
||||
"Montantdevise" + "::90", "Idevise" + "::90"
|
||||
"JournalCode" + "::90",
|
||||
"JournalLib" + "::90",
|
||||
"EcritureNum" + ":Dynamic Link:90",
|
||||
"EcritureDate" + "::90",
|
||||
"CompteNum" + ":Link/Account:100",
|
||||
"CompteLib" + ":Link/Account:200",
|
||||
"CompAuxNum" + "::90",
|
||||
"CompAuxLib" + "::90",
|
||||
"PieceRef" + "::90",
|
||||
"PieceDate" + "::90",
|
||||
"EcritureLib" + "::90",
|
||||
"Debit" + "::90",
|
||||
"Credit" + "::90",
|
||||
"EcritureLet" + "::90",
|
||||
"DateLet" + "::90",
|
||||
"ValidDate" + "::90",
|
||||
"Montantdevise" + "::90",
|
||||
"Idevise" + "::90",
|
||||
]
|
||||
|
||||
return columns
|
||||
@@ -66,10 +77,14 @@ def get_result(filters):
|
||||
|
||||
def get_gl_entries(filters):
|
||||
|
||||
group_by_condition = "group by voucher_type, voucher_no, account" \
|
||||
if filters.get("group_by_voucher") else "group by gl.name"
|
||||
group_by_condition = (
|
||||
"group by voucher_type, voucher_no, account"
|
||||
if filters.get("group_by_voucher")
|
||||
else "group by gl.name"
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
gl.posting_date as GlPostDate, gl.name as GlName, gl.account, gl.transaction_date,
|
||||
sum(gl.debit) as debit, sum(gl.credit) as credit,
|
||||
@@ -99,8 +114,12 @@ def get_gl_entries(filters):
|
||||
left join `tabMember` mem on gl.party = mem.name
|
||||
where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s
|
||||
{group_by_condition}
|
||||
order by GlPostDate, voucher_no"""\
|
||||
.format(group_by_condition=group_by_condition), filters, as_dict=1)
|
||||
order by GlPostDate, voucher_no""".format(
|
||||
group_by_condition=group_by_condition
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
return gl_entries
|
||||
|
||||
@@ -108,25 +127,37 @@ def get_gl_entries(filters):
|
||||
def get_result_as_list(data, filters):
|
||||
result = []
|
||||
|
||||
company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
|
||||
accounts = frappe.get_all("Account", filters={"Company": filters.company}, fields=["name", "account_number"])
|
||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||
accounts = frappe.get_all(
|
||||
"Account", filters={"Company": filters.company}, fields=["name", "account_number"]
|
||||
)
|
||||
|
||||
for d in data:
|
||||
|
||||
JournalCode = re.split("-|/|[0-9]", d.get("voucher_no"))[0]
|
||||
|
||||
if d.get("voucher_no").startswith("{0}-".format(JournalCode)) or d.get("voucher_no").startswith("{0}/".format(JournalCode)):
|
||||
if d.get("voucher_no").startswith("{0}-".format(JournalCode)) or d.get("voucher_no").startswith(
|
||||
"{0}/".format(JournalCode)
|
||||
):
|
||||
EcritureNum = re.split("-|/", d.get("voucher_no"))[1]
|
||||
else:
|
||||
EcritureNum = re.search(r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE).group(1)
|
||||
EcritureNum = re.search(
|
||||
r"{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE
|
||||
).group(1)
|
||||
|
||||
EcritureDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd")
|
||||
|
||||
account_number = [account.account_number for account in accounts if account.name == d.get("account")]
|
||||
account_number = [
|
||||
account.account_number for account in accounts if account.name == d.get("account")
|
||||
]
|
||||
if account_number[0] is not None:
|
||||
CompteNum = account_number[0]
|
||||
CompteNum = account_number[0]
|
||||
else:
|
||||
frappe.throw(_("Account number for account {0} is not available.<br> Please setup your Chart of Accounts correctly.").format(d.get("account")))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Account number for account {0} is not available.<br> Please setup your Chart of Accounts correctly."
|
||||
).format(d.get("account"))
|
||||
)
|
||||
|
||||
if d.get("party_type") == "Customer":
|
||||
CompAuxNum = d.get("cusName")
|
||||
@@ -172,19 +203,45 @@ def get_result_as_list(data, filters):
|
||||
|
||||
PieceDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd")
|
||||
|
||||
debit = '{:.2f}'.format(d.get("debit")).replace(".", ",")
|
||||
debit = "{:.2f}".format(d.get("debit")).replace(".", ",")
|
||||
|
||||
credit = '{:.2f}'.format(d.get("credit")).replace(".", ",")
|
||||
credit = "{:.2f}".format(d.get("credit")).replace(".", ",")
|
||||
|
||||
Idevise = d.get("account_currency")
|
||||
|
||||
if Idevise != company_currency:
|
||||
Montantdevise = '{:.2f}'.format(d.get("debitCurr")).replace(".", ",") if d.get("debitCurr") != 0 else '{:.2f}'.format(d.get("creditCurr")).replace(".", ",")
|
||||
Montantdevise = (
|
||||
"{:.2f}".format(d.get("debitCurr")).replace(".", ",")
|
||||
if d.get("debitCurr") != 0
|
||||
else "{:.2f}".format(d.get("creditCurr")).replace(".", ",")
|
||||
)
|
||||
else:
|
||||
Montantdevise = '{:.2f}'.format(d.get("debit")).replace(".", ",") if d.get("debit") != 0 else '{:.2f}'.format(d.get("credit")).replace(".", ",")
|
||||
Montantdevise = (
|
||||
"{:.2f}".format(d.get("debit")).replace(".", ",")
|
||||
if d.get("debit") != 0
|
||||
else "{:.2f}".format(d.get("credit")).replace(".", ",")
|
||||
)
|
||||
|
||||
row = [JournalCode, d.get("voucher_type"), EcritureNum, EcritureDate, CompteNum, d.get("account"), CompAuxNum, CompAuxLib,
|
||||
PieceRef, PieceDate, EcritureLib, debit, credit, "", "", ValidDate, Montantdevise, Idevise]
|
||||
row = [
|
||||
JournalCode,
|
||||
d.get("voucher_type"),
|
||||
EcritureNum,
|
||||
EcritureDate,
|
||||
CompteNum,
|
||||
d.get("account"),
|
||||
CompAuxNum,
|
||||
CompAuxLib,
|
||||
PieceRef,
|
||||
PieceDate,
|
||||
EcritureLib,
|
||||
debit,
|
||||
credit,
|
||||
"",
|
||||
"",
|
||||
ValidDate,
|
||||
Montantdevise,
|
||||
Idevise,
|
||||
]
|
||||
|
||||
result.append(row)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.utils.data import fmt_money
|
||||
from frappe.utils.jinja import render_template
|
||||
from frappe.utils.pdf import get_pdf
|
||||
from frappe.utils.print_format import read_multi_pdf
|
||||
from PyPDF2 import PdfFileWriter
|
||||
from PyPDF2 import PdfWriter
|
||||
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
@@ -20,23 +20,21 @@ IRS_1099_FORMS_FILE_EXTENSION = ".pdf"
|
||||
def execute(filters=None):
|
||||
filters = filters if isinstance(filters, frappe._dict) else frappe._dict(filters)
|
||||
if not filters:
|
||||
filters.setdefault('fiscal_year', get_fiscal_year(nowdate())[0])
|
||||
filters.setdefault('company', frappe.db.get_default("company"))
|
||||
filters.setdefault("fiscal_year", get_fiscal_year(nowdate())[0])
|
||||
filters.setdefault("company", frappe.db.get_default("company"))
|
||||
|
||||
region = frappe.db.get_value("Company",
|
||||
filters={"name": filters.company},
|
||||
fieldname=["country"])
|
||||
region = frappe.db.get_value("Company", filters={"name": filters.company}, fieldname=["country"])
|
||||
|
||||
if region != 'United States':
|
||||
if region != "United States":
|
||||
return [], []
|
||||
|
||||
data = []
|
||||
columns = get_columns()
|
||||
conditions = ""
|
||||
if filters.supplier_group:
|
||||
conditions += "AND s.supplier_group = %s" %frappe.db.escape(filters.get("supplier_group"))
|
||||
conditions += "AND s.supplier_group = %s" % frappe.db.escape(filters.get("supplier_group"))
|
||||
|
||||
data = frappe.db.sql("""
|
||||
data = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
s.supplier_group as "supplier_group",
|
||||
gl.party AS "supplier",
|
||||
@@ -49,7 +47,7 @@ def execute(filters=None):
|
||||
s.name = gl.party
|
||||
AND s.irs_1099 = 1
|
||||
AND gl.fiscal_year = %(fiscal_year)s
|
||||
AND gl.party_type = "Supplier"
|
||||
AND gl.party_type = 'Supplier'
|
||||
AND gl.company = %(company)s
|
||||
{conditions}
|
||||
|
||||
@@ -57,10 +55,12 @@ def execute(filters=None):
|
||||
gl.party
|
||||
|
||||
ORDER BY
|
||||
gl.party DESC""".format(conditions=conditions), {
|
||||
"fiscal_year": filters.fiscal_year,
|
||||
"company": filters.company
|
||||
}, as_dict=True)
|
||||
gl.party DESC""".format(
|
||||
conditions=conditions
|
||||
),
|
||||
{"fiscal_year": filters.fiscal_year, "company": filters.company},
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
return columns, data
|
||||
|
||||
@@ -72,37 +72,29 @@ def get_columns():
|
||||
"label": _("Supplier Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group",
|
||||
"width": 200
|
||||
"width": 200,
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
"label": _("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 200
|
||||
"width": 200,
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_id",
|
||||
"label": _("Tax ID"),
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "payments",
|
||||
"label": _("Total Payments"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 200
|
||||
}
|
||||
{"fieldname": "tax_id", "label": _("Tax ID"), "fieldtype": "Data", "width": 200},
|
||||
{"fieldname": "payments", "label": _("Total Payments"), "fieldtype": "Currency", "width": 200},
|
||||
]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def irs_1099_print(filters):
|
||||
if not filters:
|
||||
frappe._dict({
|
||||
"company": frappe.db.get_default("Company"),
|
||||
"fiscal_year": frappe.db.get_default("Fiscal Year")
|
||||
})
|
||||
frappe._dict(
|
||||
{
|
||||
"company": frappe.db.get_default("Company"),
|
||||
"fiscal_year": frappe.db.get_default("Fiscal Year"),
|
||||
}
|
||||
)
|
||||
else:
|
||||
filters = frappe._dict(json.loads(filters))
|
||||
|
||||
@@ -114,7 +106,7 @@ def irs_1099_print(filters):
|
||||
|
||||
columns, data = execute(filters)
|
||||
template = frappe.get_doc("Print Format", "IRS 1099 Form").html
|
||||
output = PdfFileWriter()
|
||||
output = PdfWriter()
|
||||
|
||||
for row in data:
|
||||
row["fiscal_year"] = fiscal_year
|
||||
@@ -122,17 +114,21 @@ def irs_1099_print(filters):
|
||||
row["company_tin"] = company_tin
|
||||
row["payer_street_address"] = company_address
|
||||
row["recipient_street_address"], row["recipient_city_state"] = get_street_address_html(
|
||||
"Supplier", row.supplier)
|
||||
"Supplier", row.supplier
|
||||
)
|
||||
row["payments"] = fmt_money(row["payments"], precision=0, currency="USD")
|
||||
pdf = get_pdf(render_template(template, row), output=output if output else None)
|
||||
|
||||
frappe.local.response.filename = f"{filters.fiscal_year} {filters.company} IRS 1099 Forms{IRS_1099_FORMS_FILE_EXTENSION}"
|
||||
frappe.local.response.filename = (
|
||||
f"{filters.fiscal_year} {filters.company} IRS 1099 Forms{IRS_1099_FORMS_FILE_EXTENSION}"
|
||||
)
|
||||
frappe.local.response.filecontent = read_multi_pdf(output)
|
||||
frappe.local.response.type = "download"
|
||||
|
||||
|
||||
def get_payer_address_html(company):
|
||||
address_list = frappe.db.sql("""
|
||||
address_list = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
name
|
||||
FROM
|
||||
@@ -142,7 +138,10 @@ def get_payer_address_html(company):
|
||||
ORDER BY
|
||||
address_type="Postal" DESC, address_type="Billing" DESC
|
||||
LIMIT 1
|
||||
""", {"company": company}, as_dict=True)
|
||||
""",
|
||||
{"company": company},
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
address_display = ""
|
||||
if address_list:
|
||||
@@ -153,7 +152,8 @@ def get_payer_address_html(company):
|
||||
|
||||
|
||||
def get_street_address_html(party_type, party):
|
||||
address_list = frappe.db.sql("""
|
||||
address_list = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
link.parent
|
||||
FROM
|
||||
@@ -166,7 +166,10 @@ def get_street_address_html(party_type, party):
|
||||
address.address_type="Postal" DESC,
|
||||
address.address_type="Billing" DESC
|
||||
LIMIT 1
|
||||
""", {"party": party}, as_dict=True)
|
||||
""",
|
||||
{"party": party},
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
street_address = city_state = ""
|
||||
if address_list:
|
||||
|
||||
@@ -14,6 +14,7 @@ def execute(filters=None):
|
||||
data = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
@@ -48,101 +49,136 @@ def get_columns():
|
||||
"label": _("Currency"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 150,
|
||||
"hidden": 1
|
||||
}
|
||||
"hidden": 1,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
|
||||
# Validate if vat settings exist
|
||||
company = filters.get('company')
|
||||
company_currency = frappe.get_cached_value('Company', company, "default_currency")
|
||||
company = filters.get("company")
|
||||
company_currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||
|
||||
if frappe.db.exists('KSA VAT Setting', company) is None:
|
||||
url = get_url_to_list('KSA VAT Setting')
|
||||
if frappe.db.exists("KSA VAT Setting", company) is None:
|
||||
url = get_url_to_list("KSA VAT Setting")
|
||||
frappe.msgprint(_('Create <a href="{}">KSA VAT Setting</a> for this company').format(url))
|
||||
return data
|
||||
|
||||
ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company)
|
||||
ksa_vat_setting = frappe.get_doc("KSA VAT Setting", company)
|
||||
|
||||
# Sales Heading
|
||||
append_data(data, 'VAT on Sales', '', '', '', company_currency)
|
||||
append_data(data, "VAT on Sales", "", "", "", company_currency)
|
||||
|
||||
grand_total_taxable_amount = 0
|
||||
grand_total_taxable_adjustment_amount = 0
|
||||
grand_total_tax = 0
|
||||
|
||||
for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts:
|
||||
total_taxable_amount, total_taxable_adjustment_amount, \
|
||||
total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice')
|
||||
(
|
||||
total_taxable_amount,
|
||||
total_taxable_adjustment_amount,
|
||||
total_tax,
|
||||
) = get_tax_data_for_each_vat_setting(vat_setting, filters, "Sales Invoice")
|
||||
|
||||
# Adding results to data
|
||||
append_data(data, vat_setting.title, total_taxable_amount,
|
||||
total_taxable_adjustment_amount, total_tax, company_currency)
|
||||
append_data(
|
||||
data,
|
||||
vat_setting.title,
|
||||
total_taxable_amount,
|
||||
total_taxable_adjustment_amount,
|
||||
total_tax,
|
||||
company_currency,
|
||||
)
|
||||
|
||||
grand_total_taxable_amount += total_taxable_amount
|
||||
grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount
|
||||
grand_total_tax += total_tax
|
||||
|
||||
# Sales Grand Total
|
||||
append_data(data, 'Grand Total', grand_total_taxable_amount,
|
||||
grand_total_taxable_adjustment_amount, grand_total_tax, company_currency)
|
||||
append_data(
|
||||
data,
|
||||
"Grand Total",
|
||||
grand_total_taxable_amount,
|
||||
grand_total_taxable_adjustment_amount,
|
||||
grand_total_tax,
|
||||
company_currency,
|
||||
)
|
||||
|
||||
# Blank Line
|
||||
append_data(data, '', '', '', '', company_currency)
|
||||
append_data(data, "", "", "", "", company_currency)
|
||||
|
||||
# Purchase Heading
|
||||
append_data(data, 'VAT on Purchases', '', '', '', company_currency)
|
||||
append_data(data, "VAT on Purchases", "", "", "", company_currency)
|
||||
|
||||
grand_total_taxable_amount = 0
|
||||
grand_total_taxable_adjustment_amount = 0
|
||||
grand_total_tax = 0
|
||||
|
||||
for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts:
|
||||
total_taxable_amount, total_taxable_adjustment_amount, \
|
||||
total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice')
|
||||
(
|
||||
total_taxable_amount,
|
||||
total_taxable_adjustment_amount,
|
||||
total_tax,
|
||||
) = get_tax_data_for_each_vat_setting(vat_setting, filters, "Purchase Invoice")
|
||||
|
||||
# Adding results to data
|
||||
append_data(data, vat_setting.title, total_taxable_amount,
|
||||
total_taxable_adjustment_amount, total_tax, company_currency)
|
||||
append_data(
|
||||
data,
|
||||
vat_setting.title,
|
||||
total_taxable_amount,
|
||||
total_taxable_adjustment_amount,
|
||||
total_tax,
|
||||
company_currency,
|
||||
)
|
||||
|
||||
grand_total_taxable_amount += total_taxable_amount
|
||||
grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount
|
||||
grand_total_tax += total_tax
|
||||
|
||||
# Purchase Grand Total
|
||||
append_data(data, 'Grand Total', grand_total_taxable_amount,
|
||||
grand_total_taxable_adjustment_amount, grand_total_tax, company_currency)
|
||||
append_data(
|
||||
data,
|
||||
"Grand Total",
|
||||
grand_total_taxable_amount,
|
||||
grand_total_taxable_adjustment_amount,
|
||||
grand_total_tax,
|
||||
company_currency,
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype):
|
||||
'''
|
||||
"""
|
||||
(KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n
|
||||
calculates and returns \n
|
||||
total_taxable_amount, total_taxable_adjustment_amount, total_tax'''
|
||||
from_date = filters.get('from_date')
|
||||
to_date = filters.get('to_date')
|
||||
total_taxable_amount, total_taxable_adjustment_amount, total_tax"""
|
||||
from_date = filters.get("from_date")
|
||||
to_date = filters.get("to_date")
|
||||
|
||||
# Initiate variables
|
||||
total_taxable_amount = 0
|
||||
total_taxable_adjustment_amount = 0
|
||||
total_tax = 0
|
||||
# Fetch All Invoices
|
||||
invoices = frappe.get_all(doctype,
|
||||
filters ={
|
||||
'docstatus': 1,
|
||||
'posting_date': ['between', [from_date, to_date]]
|
||||
}, fields =['name', 'is_return'])
|
||||
invoices = frappe.get_all(
|
||||
doctype,
|
||||
filters={"docstatus": 1, "posting_date": ["between", [from_date, to_date]]},
|
||||
fields=["name", "is_return"],
|
||||
)
|
||||
|
||||
for invoice in invoices:
|
||||
invoice_items = frappe.get_all(f'{doctype} Item',
|
||||
filters ={
|
||||
'docstatus': 1,
|
||||
'parent': invoice.name,
|
||||
'item_tax_template': vat_setting.item_tax_template
|
||||
}, fields =['item_code', 'net_amount'])
|
||||
invoice_items = frappe.get_all(
|
||||
f"{doctype} Item",
|
||||
filters={
|
||||
"docstatus": 1,
|
||||
"parent": invoice.name,
|
||||
"item_tax_template": vat_setting.item_tax_template,
|
||||
},
|
||||
fields=["item_code", "net_amount"],
|
||||
)
|
||||
|
||||
for item in invoice_items:
|
||||
# Summing up total taxable amount
|
||||
@@ -158,24 +194,31 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype):
|
||||
return total_taxable_amount, total_taxable_adjustment_amount, total_tax
|
||||
|
||||
|
||||
|
||||
def append_data(data, title, amount, adjustment_amount, vat_amount, company_currency):
|
||||
"""Returns data with appended value."""
|
||||
data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount,
|
||||
"currency": company_currency})
|
||||
data.append(
|
||||
{
|
||||
"title": _(title),
|
||||
"amount": amount,
|
||||
"adjustment_amount": adjustment_amount,
|
||||
"vat_amount": vat_amount,
|
||||
"currency": company_currency,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def get_tax_amount(item_code, account_head, doctype, parent):
|
||||
if doctype == 'Sales Invoice':
|
||||
tax_doctype = 'Sales Taxes and Charges'
|
||||
if doctype == "Sales Invoice":
|
||||
tax_doctype = "Sales Taxes and Charges"
|
||||
|
||||
elif doctype == 'Purchase Invoice':
|
||||
tax_doctype = 'Purchase Taxes and Charges'
|
||||
elif doctype == "Purchase Invoice":
|
||||
tax_doctype = "Purchase Taxes and Charges"
|
||||
|
||||
item_wise_tax_detail = frappe.get_value(tax_doctype, {
|
||||
'docstatus': 1,
|
||||
'parent': parent,
|
||||
'account_head': account_head
|
||||
}, 'item_wise_tax_detail')
|
||||
item_wise_tax_detail = frappe.get_value(
|
||||
tax_doctype,
|
||||
{"docstatus": 1, "parent": parent, "account_head": account_head},
|
||||
"item_wise_tax_detail",
|
||||
)
|
||||
|
||||
tax_amount = 0
|
||||
if item_wise_tax_detail and len(item_wise_tax_detail) > 0:
|
||||
|
||||
@@ -18,6 +18,7 @@ from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse_account
|
||||
|
||||
test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"]
|
||||
|
||||
|
||||
class TestUaeVat201(TestCase):
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
@@ -34,9 +35,9 @@ class TestUaeVat201(TestCase):
|
||||
|
||||
create_warehouse("_Test UAE VAT Supplier Warehouse", company="_Test Company UAE VAT")
|
||||
|
||||
make_item("_Test UAE VAT Item", properties = {"is_zero_rated": 0, "is_exempt": 0})
|
||||
make_item("_Test UAE VAT Zero Rated Item", properties = {"is_zero_rated": 1, "is_exempt": 0})
|
||||
make_item("_Test UAE VAT Exempt Item", properties = {"is_zero_rated": 0, "is_exempt": 1})
|
||||
make_item("_Test UAE VAT Item", properties={"is_zero_rated": 0, "is_exempt": 0})
|
||||
make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0})
|
||||
make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1})
|
||||
|
||||
make_sales_invoices()
|
||||
|
||||
@@ -52,27 +53,30 @@ class TestUaeVat201(TestCase):
|
||||
"raw_amount": amount,
|
||||
"raw_vat_amount": vat,
|
||||
}
|
||||
self.assertEqual(amounts_by_emirate["Sharjah"]["raw_amount"],100)
|
||||
self.assertEqual(amounts_by_emirate["Sharjah"]["raw_vat_amount"],5)
|
||||
self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"],200)
|
||||
self.assertEqual(amounts_by_emirate["Dubai"]["raw_vat_amount"],10)
|
||||
self.assertEqual(get_tourist_tax_return_total(filters),100)
|
||||
self.assertEqual(get_tourist_tax_return_tax(filters),2)
|
||||
self.assertEqual(get_zero_rated_total(filters),100)
|
||||
self.assertEqual(get_exempt_total(filters),100)
|
||||
self.assertEqual(get_standard_rated_expenses_total(filters),250)
|
||||
self.assertEqual(get_standard_rated_expenses_tax(filters),1)
|
||||
self.assertEqual(amounts_by_emirate["Sharjah"]["raw_amount"], 100)
|
||||
self.assertEqual(amounts_by_emirate["Sharjah"]["raw_vat_amount"], 5)
|
||||
self.assertEqual(amounts_by_emirate["Dubai"]["raw_amount"], 200)
|
||||
self.assertEqual(amounts_by_emirate["Dubai"]["raw_vat_amount"], 10)
|
||||
self.assertEqual(get_tourist_tax_return_total(filters), 100)
|
||||
self.assertEqual(get_tourist_tax_return_tax(filters), 2)
|
||||
self.assertEqual(get_zero_rated_total(filters), 100)
|
||||
self.assertEqual(get_exempt_total(filters), 100)
|
||||
self.assertEqual(get_standard_rated_expenses_total(filters), 250)
|
||||
self.assertEqual(get_standard_rated_expenses_tax(filters), 1)
|
||||
|
||||
|
||||
def make_company(company_name, abbr):
|
||||
if not frappe.db.exists("Company", company_name):
|
||||
company = frappe.get_doc({
|
||||
"doctype": "Company",
|
||||
"company_name": company_name,
|
||||
"abbr": abbr,
|
||||
"default_currency": "AED",
|
||||
"country": "United Arab Emirates",
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
})
|
||||
company = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": company_name,
|
||||
"abbr": abbr,
|
||||
"default_currency": "AED",
|
||||
"country": "United Arab Emirates",
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
}
|
||||
)
|
||||
company.insert()
|
||||
else:
|
||||
company = frappe.get_doc("Company", company_name)
|
||||
@@ -85,50 +89,51 @@ def make_company(company_name, abbr):
|
||||
company.save()
|
||||
return company
|
||||
|
||||
|
||||
def set_vat_accounts():
|
||||
if not frappe.db.exists("UAE VAT Settings", "_Test Company UAE VAT"):
|
||||
vat_accounts = frappe.get_all(
|
||||
"Account",
|
||||
fields=["name"],
|
||||
filters = {
|
||||
"company": "_Test Company UAE VAT",
|
||||
"is_group": 0,
|
||||
"account_type": "Tax"
|
||||
}
|
||||
filters={"company": "_Test Company UAE VAT", "is_group": 0, "account_type": "Tax"},
|
||||
)
|
||||
|
||||
uae_vat_accounts = []
|
||||
for account in vat_accounts:
|
||||
uae_vat_accounts.append({
|
||||
"doctype": "UAE VAT Account",
|
||||
"account": account.name
|
||||
})
|
||||
uae_vat_accounts.append({"doctype": "UAE VAT Account", "account": account.name})
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"company": "_Test Company UAE VAT",
|
||||
"uae_vat_accounts": uae_vat_accounts,
|
||||
"doctype": "UAE VAT Settings",
|
||||
}
|
||||
).insert()
|
||||
|
||||
frappe.get_doc({
|
||||
"company": "_Test Company UAE VAT",
|
||||
"uae_vat_accounts": uae_vat_accounts,
|
||||
"doctype": "UAE VAT Settings",
|
||||
}).insert()
|
||||
|
||||
def make_customer():
|
||||
if not frappe.db.exists("Customer", "_Test UAE Customer"):
|
||||
customer = frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
"customer_name": "_Test UAE Customer",
|
||||
"customer_type": "Company",
|
||||
})
|
||||
customer = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Customer",
|
||||
"customer_name": "_Test UAE Customer",
|
||||
"customer_type": "Company",
|
||||
}
|
||||
)
|
||||
customer.insert()
|
||||
else:
|
||||
customer = frappe.get_doc("Customer", "_Test UAE Customer")
|
||||
|
||||
|
||||
def make_supplier():
|
||||
if not frappe.db.exists("Supplier", "_Test UAE Supplier"):
|
||||
frappe.get_doc({
|
||||
"supplier_group": "Local",
|
||||
"supplier_name": "_Test UAE Supplier",
|
||||
"supplier_type": "Individual",
|
||||
"doctype": "Supplier",
|
||||
}).insert()
|
||||
frappe.get_doc(
|
||||
{
|
||||
"supplier_group": "Local",
|
||||
"supplier_name": "_Test UAE Supplier",
|
||||
"supplier_type": "Individual",
|
||||
"doctype": "Supplier",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def create_warehouse(warehouse_name, properties=None, company=None):
|
||||
if not company:
|
||||
@@ -148,17 +153,20 @@ def create_warehouse(warehouse_name, properties=None, company=None):
|
||||
else:
|
||||
return warehouse_id
|
||||
|
||||
|
||||
def make_item(item_code, properties=None):
|
||||
if frappe.db.exists("Item", item_code):
|
||||
return frappe.get_doc("Item", item_code)
|
||||
|
||||
item = frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": item_code,
|
||||
"item_name": item_code,
|
||||
"description": item_code,
|
||||
"item_group": "Products"
|
||||
})
|
||||
item = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item",
|
||||
"item_code": item_code,
|
||||
"item_name": item_code,
|
||||
"description": item_code,
|
||||
"item_group": "Products",
|
||||
}
|
||||
)
|
||||
|
||||
if properties:
|
||||
item.update(properties)
|
||||
@@ -167,71 +175,77 @@ def make_item(item_code, properties=None):
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def make_sales_invoices():
|
||||
def make_sales_invoices_wrapper(emirate, item, tax = True, tourist_tax= False):
|
||||
def make_sales_invoices_wrapper(emirate, item, tax=True, tourist_tax=False):
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company UAE VAT",
|
||||
customer = '_Test UAE Customer',
|
||||
currency = 'AED',
|
||||
warehouse = 'Finished Goods - _TCUV',
|
||||
debit_to = 'Debtors - _TCUV',
|
||||
income_account = 'Sales - _TCUV',
|
||||
expense_account = 'Cost of Goods Sold - _TCUV',
|
||||
cost_center = 'Main - _TCUV',
|
||||
item = item,
|
||||
do_not_save=1
|
||||
customer="_Test UAE Customer",
|
||||
currency="AED",
|
||||
warehouse="Finished Goods - _TCUV",
|
||||
debit_to="Debtors - _TCUV",
|
||||
income_account="Sales - _TCUV",
|
||||
expense_account="Cost of Goods Sold - _TCUV",
|
||||
cost_center="Main - _TCUV",
|
||||
item=item,
|
||||
do_not_save=1,
|
||||
)
|
||||
si.vat_emirate = emirate
|
||||
if tax:
|
||||
si.append(
|
||||
"taxes", {
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "VAT 5% - _TCUV",
|
||||
"cost_center": "Main - _TCUV",
|
||||
"description": "VAT 5% @ 5.0",
|
||||
"rate": 5.0
|
||||
}
|
||||
"rate": 5.0,
|
||||
},
|
||||
)
|
||||
if tourist_tax:
|
||||
si.tourist_tax_return = 2
|
||||
si.submit()
|
||||
|
||||
#Define Item Names
|
||||
# Define Item Names
|
||||
uae_item = "_Test UAE VAT Item"
|
||||
uae_exempt_item = "_Test UAE VAT Exempt Item"
|
||||
uae_zero_rated_item = "_Test UAE VAT Zero Rated Item"
|
||||
|
||||
#Sales Invoice with standard rated expense in Dubai
|
||||
make_sales_invoices_wrapper('Dubai', uae_item)
|
||||
#Sales Invoice with standard rated expense in Sharjah
|
||||
make_sales_invoices_wrapper('Sharjah', uae_item)
|
||||
#Sales Invoice with Tourist Tax Return
|
||||
make_sales_invoices_wrapper('Dubai', uae_item, True, True)
|
||||
#Sales Invoice with Exempt Item
|
||||
make_sales_invoices_wrapper('Sharjah', uae_exempt_item, False)
|
||||
#Sales Invoice with Zero Rated Item
|
||||
make_sales_invoices_wrapper('Sharjah', uae_zero_rated_item, False)
|
||||
# Sales Invoice with standard rated expense in Dubai
|
||||
make_sales_invoices_wrapper("Dubai", uae_item)
|
||||
# Sales Invoice with standard rated expense in Sharjah
|
||||
make_sales_invoices_wrapper("Sharjah", uae_item)
|
||||
# Sales Invoice with Tourist Tax Return
|
||||
make_sales_invoices_wrapper("Dubai", uae_item, True, True)
|
||||
# Sales Invoice with Exempt Item
|
||||
make_sales_invoices_wrapper("Sharjah", uae_exempt_item, False)
|
||||
# Sales Invoice with Zero Rated Item
|
||||
make_sales_invoices_wrapper("Sharjah", uae_zero_rated_item, False)
|
||||
|
||||
|
||||
def create_purchase_invoices():
|
||||
pi = make_purchase_invoice(
|
||||
company="_Test Company UAE VAT",
|
||||
supplier = '_Test UAE Supplier',
|
||||
supplier_warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV',
|
||||
warehouse = '_Test UAE VAT Supplier Warehouse - _TCUV',
|
||||
currency = 'AED',
|
||||
cost_center = 'Main - _TCUV',
|
||||
expense_account = 'Cost of Goods Sold - _TCUV',
|
||||
item = "_Test UAE VAT Item",
|
||||
supplier="_Test UAE Supplier",
|
||||
supplier_warehouse="_Test UAE VAT Supplier Warehouse - _TCUV",
|
||||
warehouse="_Test UAE VAT Supplier Warehouse - _TCUV",
|
||||
currency="AED",
|
||||
cost_center="Main - _TCUV",
|
||||
expense_account="Cost of Goods Sold - _TCUV",
|
||||
item="_Test UAE VAT Item",
|
||||
do_not_save=1,
|
||||
uom = "Nos"
|
||||
uom="Nos",
|
||||
)
|
||||
pi.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "VAT 5% - _TCUV",
|
||||
"cost_center": "Main - _TCUV",
|
||||
"description": "VAT 5% @ 5.0",
|
||||
"rate": 5.0,
|
||||
},
|
||||
)
|
||||
pi.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "VAT 5% - _TCUV",
|
||||
"cost_center": "Main - _TCUV",
|
||||
"description": "VAT 5% @ 5.0",
|
||||
"rate": 5.0
|
||||
})
|
||||
|
||||
pi.recoverable_standard_rated_expenses = 1
|
||||
|
||||
|
||||
@@ -11,21 +11,12 @@ def execute(filters=None):
|
||||
data, emirates, amounts_by_emirate = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns():
|
||||
"""Creates a list of dictionaries that are used to generate column headers of the data table."""
|
||||
return [
|
||||
{
|
||||
"fieldname": "no",
|
||||
"label": _("No"),
|
||||
"fieldtype": "Data",
|
||||
"width": 50
|
||||
},
|
||||
{
|
||||
"fieldname": "legend",
|
||||
"label": _("Legend"),
|
||||
"fieldtype": "Data",
|
||||
"width": 300
|
||||
},
|
||||
{"fieldname": "no", "label": _("No"), "fieldtype": "Data", "width": 50},
|
||||
{"fieldname": "legend", "label": _("Legend"), "fieldtype": "Data", "width": 300},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"label": _("Amount (AED)"),
|
||||
@@ -37,41 +28,53 @@ def get_columns():
|
||||
"label": _("VAT Amount (AED)"),
|
||||
"fieldtype": "Currency",
|
||||
"width": 150,
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
def get_data(filters = None):
|
||||
|
||||
def get_data(filters=None):
|
||||
"""Returns the list of dictionaries. Each dictionary is a row in the datatable and chart data."""
|
||||
data = []
|
||||
emirates, amounts_by_emirate = append_vat_on_sales(data, filters)
|
||||
append_vat_on_expenses(data, filters)
|
||||
return data, emirates, amounts_by_emirate
|
||||
|
||||
|
||||
def append_vat_on_sales(data, filters):
|
||||
"""Appends Sales and All Other Outputs."""
|
||||
append_data(data, '', _('VAT on Sales and All Other Outputs'), '', '')
|
||||
append_data(data, "", _("VAT on Sales and All Other Outputs"), "", "")
|
||||
|
||||
emirates, amounts_by_emirate = standard_rated_expenses_emiratewise(data, filters)
|
||||
|
||||
append_data(data, '2',
|
||||
_('Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme'),
|
||||
frappe.format((-1) * get_tourist_tax_return_total(filters), 'Currency'),
|
||||
frappe.format((-1) * get_tourist_tax_return_tax(filters), 'Currency'))
|
||||
append_data(
|
||||
data,
|
||||
"2",
|
||||
_("Tax Refunds provided to Tourists under the Tax Refunds for Tourists Scheme"),
|
||||
frappe.format((-1) * get_tourist_tax_return_total(filters), "Currency"),
|
||||
frappe.format((-1) * get_tourist_tax_return_tax(filters), "Currency"),
|
||||
)
|
||||
|
||||
append_data(data, '3', _('Supplies subject to the reverse charge provision'),
|
||||
frappe.format(get_reverse_charge_total(filters), 'Currency'),
|
||||
frappe.format(get_reverse_charge_tax(filters), 'Currency'))
|
||||
append_data(
|
||||
data,
|
||||
"3",
|
||||
_("Supplies subject to the reverse charge provision"),
|
||||
frappe.format(get_reverse_charge_total(filters), "Currency"),
|
||||
frappe.format(get_reverse_charge_tax(filters), "Currency"),
|
||||
)
|
||||
|
||||
append_data(data, '4', _('Zero Rated'),
|
||||
frappe.format(get_zero_rated_total(filters), 'Currency'), "-")
|
||||
append_data(
|
||||
data, "4", _("Zero Rated"), frappe.format(get_zero_rated_total(filters), "Currency"), "-"
|
||||
)
|
||||
|
||||
append_data(data, '5', _('Exempt Supplies'),
|
||||
frappe.format(get_exempt_total(filters), 'Currency'),"-")
|
||||
append_data(
|
||||
data, "5", _("Exempt Supplies"), frappe.format(get_exempt_total(filters), "Currency"), "-"
|
||||
)
|
||||
|
||||
append_data(data, '', '', '', '')
|
||||
append_data(data, "", "", "", "")
|
||||
|
||||
return emirates, amounts_by_emirate
|
||||
|
||||
|
||||
def standard_rated_expenses_emiratewise(data, filters):
|
||||
"""Append emiratewise standard rated expenses and vat."""
|
||||
total_emiratewise = get_total_emiratewise(filters)
|
||||
@@ -82,44 +85,61 @@ def standard_rated_expenses_emiratewise(data, filters):
|
||||
"legend": emirate,
|
||||
"raw_amount": amount,
|
||||
"raw_vat_amount": vat,
|
||||
"amount": frappe.format(amount, 'Currency'),
|
||||
"vat_amount": frappe.format(vat, 'Currency'),
|
||||
"amount": frappe.format(amount, "Currency"),
|
||||
"vat_amount": frappe.format(vat, "Currency"),
|
||||
}
|
||||
amounts_by_emirate = append_emiratewise_expenses(data, emirates, amounts_by_emirate)
|
||||
return emirates, amounts_by_emirate
|
||||
|
||||
|
||||
def append_emiratewise_expenses(data, emirates, amounts_by_emirate):
|
||||
"""Append emiratewise standard rated expenses and vat."""
|
||||
for no, emirate in enumerate(emirates, 97):
|
||||
if emirate in amounts_by_emirate:
|
||||
amounts_by_emirate[emirate]["no"] = _('1{0}').format(chr(no))
|
||||
amounts_by_emirate[emirate]["legend"] = _('Standard rated supplies in {0}').format(emirate)
|
||||
amounts_by_emirate[emirate]["no"] = _("1{0}").format(chr(no))
|
||||
amounts_by_emirate[emirate]["legend"] = _("Standard rated supplies in {0}").format(emirate)
|
||||
data.append(amounts_by_emirate[emirate])
|
||||
else:
|
||||
append_data(data, _('1{0}').format(chr(no)),
|
||||
_('Standard rated supplies in {0}').format(emirate),
|
||||
frappe.format(0, 'Currency'), frappe.format(0, 'Currency'))
|
||||
append_data(
|
||||
data,
|
||||
_("1{0}").format(chr(no)),
|
||||
_("Standard rated supplies in {0}").format(emirate),
|
||||
frappe.format(0, "Currency"),
|
||||
frappe.format(0, "Currency"),
|
||||
)
|
||||
return amounts_by_emirate
|
||||
|
||||
|
||||
def append_vat_on_expenses(data, filters):
|
||||
"""Appends Expenses and All Other Inputs."""
|
||||
append_data(data, '', _('VAT on Expenses and All Other Inputs'), '', '')
|
||||
append_data(data, '9', _('Standard Rated Expenses'),
|
||||
frappe.format(get_standard_rated_expenses_total(filters), 'Currency'),
|
||||
frappe.format(get_standard_rated_expenses_tax(filters), 'Currency'))
|
||||
append_data(data, '10', _('Supplies subject to the reverse charge provision'),
|
||||
frappe.format(get_reverse_charge_recoverable_total(filters), 'Currency'),
|
||||
frappe.format(get_reverse_charge_recoverable_tax(filters), 'Currency'))
|
||||
append_data(data, "", _("VAT on Expenses and All Other Inputs"), "", "")
|
||||
append_data(
|
||||
data,
|
||||
"9",
|
||||
_("Standard Rated Expenses"),
|
||||
frappe.format(get_standard_rated_expenses_total(filters), "Currency"),
|
||||
frappe.format(get_standard_rated_expenses_tax(filters), "Currency"),
|
||||
)
|
||||
append_data(
|
||||
data,
|
||||
"10",
|
||||
_("Supplies subject to the reverse charge provision"),
|
||||
frappe.format(get_reverse_charge_recoverable_total(filters), "Currency"),
|
||||
frappe.format(get_reverse_charge_recoverable_tax(filters), "Currency"),
|
||||
)
|
||||
|
||||
|
||||
def append_data(data, no, legend, amount, vat_amount):
|
||||
"""Returns data with appended value."""
|
||||
data.append({"no": no, "legend":legend, "amount": amount, "vat_amount": vat_amount})
|
||||
data.append({"no": no, "legend": legend, "amount": amount, "vat_amount": vat_amount})
|
||||
|
||||
|
||||
def get_total_emiratewise(filters):
|
||||
"""Returns Emiratewise Amount and Taxes."""
|
||||
conditions = get_conditions(filters)
|
||||
try:
|
||||
return frappe.db.sql("""
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount)
|
||||
from
|
||||
@@ -131,52 +151,54 @@ def get_total_emiratewise(filters):
|
||||
{where_conditions}
|
||||
group by
|
||||
s.vat_emirate;
|
||||
""".format(where_conditions=conditions), filters)
|
||||
""".format(
|
||||
where_conditions=conditions
|
||||
),
|
||||
filters,
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_emirates():
|
||||
"""Returns a List of emirates in the order that they are to be displayed."""
|
||||
return [
|
||||
'Abu Dhabi',
|
||||
'Dubai',
|
||||
'Sharjah',
|
||||
'Ajman',
|
||||
'Umm Al Quwain',
|
||||
'Ras Al Khaimah',
|
||||
'Fujairah'
|
||||
]
|
||||
return ["Abu Dhabi", "Dubai", "Sharjah", "Ajman", "Umm Al Quwain", "Ras Al Khaimah", "Fujairah"]
|
||||
|
||||
|
||||
def get_filters(filters):
|
||||
"""The conditions to be used to filter data to calculate the total sale."""
|
||||
query_filters = []
|
||||
if filters.get("company"):
|
||||
query_filters.append(["company", '=', filters['company']])
|
||||
query_filters.append(["company", "=", filters["company"]])
|
||||
if filters.get("from_date"):
|
||||
query_filters.append(["posting_date", '>=', filters['from_date']])
|
||||
query_filters.append(["posting_date", ">=", filters["from_date"]])
|
||||
if filters.get("from_date"):
|
||||
query_filters.append(["posting_date", '<=', filters['to_date']])
|
||||
query_filters.append(["posting_date", "<=", filters["to_date"]])
|
||||
return query_filters
|
||||
|
||||
|
||||
def get_reverse_charge_total(filters):
|
||||
"""Returns the sum of the total of each Purchase invoice made."""
|
||||
query_filters = get_filters(filters)
|
||||
query_filters.append(['reverse_charge', '=', 'Y'])
|
||||
query_filters.append(['docstatus', '=', 1])
|
||||
query_filters.append(["reverse_charge", "=", "Y"])
|
||||
query_filters.append(["docstatus", "=", 1])
|
||||
try:
|
||||
return frappe.db.get_all('Purchase Invoice',
|
||||
filters = query_filters,
|
||||
fields = ['sum(total)'],
|
||||
as_list=True,
|
||||
limit = 1
|
||||
)[0][0] or 0
|
||||
return (
|
||||
frappe.db.get_all(
|
||||
"Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_reverse_charge_tax(filters):
|
||||
"""Returns the sum of the tax of each Purchase invoice made."""
|
||||
conditions = get_conditions_join(filters)
|
||||
return frappe.db.sql("""
|
||||
return (
|
||||
frappe.db.sql(
|
||||
"""
|
||||
select sum(debit) from
|
||||
`tabPurchase Invoice` p inner join `tabGL Entry` gl
|
||||
on
|
||||
@@ -187,28 +209,38 @@ def get_reverse_charge_tax(filters):
|
||||
and gl.docstatus = 1
|
||||
and account in (select account from `tabUAE VAT Account` where parent=%(company)s)
|
||||
{where_conditions} ;
|
||||
""".format(where_conditions=conditions), filters)[0][0] or 0
|
||||
""".format(
|
||||
where_conditions=conditions
|
||||
),
|
||||
filters,
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
|
||||
|
||||
def get_reverse_charge_recoverable_total(filters):
|
||||
"""Returns the sum of the total of each Purchase invoice made with recoverable reverse charge."""
|
||||
query_filters = get_filters(filters)
|
||||
query_filters.append(['reverse_charge', '=', 'Y'])
|
||||
query_filters.append(['recoverable_reverse_charge', '>', '0'])
|
||||
query_filters.append(['docstatus', '=', 1])
|
||||
query_filters.append(["reverse_charge", "=", "Y"])
|
||||
query_filters.append(["recoverable_reverse_charge", ">", "0"])
|
||||
query_filters.append(["docstatus", "=", 1])
|
||||
try:
|
||||
return frappe.db.get_all('Purchase Invoice',
|
||||
filters = query_filters,
|
||||
fields = ['sum(total)'],
|
||||
as_list=True,
|
||||
limit = 1
|
||||
)[0][0] or 0
|
||||
return (
|
||||
frappe.db.get_all(
|
||||
"Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_reverse_charge_recoverable_tax(filters):
|
||||
"""Returns the sum of the tax of each Purchase invoice made."""
|
||||
conditions = get_conditions_join(filters)
|
||||
return frappe.db.sql("""
|
||||
return (
|
||||
frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
sum(debit * p.recoverable_reverse_charge / 100)
|
||||
from
|
||||
@@ -222,83 +254,107 @@ def get_reverse_charge_recoverable_tax(filters):
|
||||
and gl.docstatus = 1
|
||||
and account in (select account from `tabUAE VAT Account` where parent=%(company)s)
|
||||
{where_conditions} ;
|
||||
""".format(where_conditions=conditions), filters)[0][0] or 0
|
||||
""".format(
|
||||
where_conditions=conditions
|
||||
),
|
||||
filters,
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
|
||||
|
||||
def get_conditions_join(filters):
|
||||
"""The conditions to be used to filter data to calculate the total vat."""
|
||||
conditions = ""
|
||||
for opts in (("company", " and p.company=%(company)s"),
|
||||
for opts in (
|
||||
("company", " and p.company=%(company)s"),
|
||||
("from_date", " and p.posting_date>=%(from_date)s"),
|
||||
("to_date", " and p.posting_date<=%(to_date)s")):
|
||||
("to_date", " and p.posting_date<=%(to_date)s"),
|
||||
):
|
||||
if filters.get(opts[0]):
|
||||
conditions += opts[1]
|
||||
return conditions
|
||||
|
||||
|
||||
def get_standard_rated_expenses_total(filters):
|
||||
"""Returns the sum of the total of each Purchase invoice made with recoverable reverse charge."""
|
||||
query_filters = get_filters(filters)
|
||||
query_filters.append(['recoverable_standard_rated_expenses', '>', 0])
|
||||
query_filters.append(['docstatus', '=', 1])
|
||||
query_filters.append(["recoverable_standard_rated_expenses", ">", 0])
|
||||
query_filters.append(["docstatus", "=", 1])
|
||||
try:
|
||||
return frappe.db.get_all('Purchase Invoice',
|
||||
filters = query_filters,
|
||||
fields = ['sum(total)'],
|
||||
as_list=True,
|
||||
limit = 1
|
||||
)[0][0] or 0
|
||||
return (
|
||||
frappe.db.get_all(
|
||||
"Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_standard_rated_expenses_tax(filters):
|
||||
"""Returns the sum of the tax of each Purchase invoice made."""
|
||||
query_filters = get_filters(filters)
|
||||
query_filters.append(['recoverable_standard_rated_expenses', '>', 0])
|
||||
query_filters.append(['docstatus', '=', 1])
|
||||
query_filters.append(["recoverable_standard_rated_expenses", ">", 0])
|
||||
query_filters.append(["docstatus", "=", 1])
|
||||
try:
|
||||
return frappe.db.get_all('Purchase Invoice',
|
||||
filters = query_filters,
|
||||
fields = ['sum(recoverable_standard_rated_expenses)'],
|
||||
as_list=True,
|
||||
limit = 1
|
||||
)[0][0] or 0
|
||||
return (
|
||||
frappe.db.get_all(
|
||||
"Purchase Invoice",
|
||||
filters=query_filters,
|
||||
fields=["sum(recoverable_standard_rated_expenses)"],
|
||||
as_list=True,
|
||||
limit=1,
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_tourist_tax_return_total(filters):
|
||||
"""Returns the sum of the total of each Sales invoice with non zero tourist_tax_return."""
|
||||
query_filters = get_filters(filters)
|
||||
query_filters.append(['tourist_tax_return', '>', 0])
|
||||
query_filters.append(['docstatus', '=', 1])
|
||||
query_filters.append(["tourist_tax_return", ">", 0])
|
||||
query_filters.append(["docstatus", "=", 1])
|
||||
try:
|
||||
return frappe.db.get_all('Sales Invoice',
|
||||
filters = query_filters,
|
||||
fields = ['sum(total)'],
|
||||
as_list=True,
|
||||
limit = 1
|
||||
)[0][0] or 0
|
||||
return (
|
||||
frappe.db.get_all(
|
||||
"Sales Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_tourist_tax_return_tax(filters):
|
||||
"""Returns the sum of the tax of each Sales invoice with non zero tourist_tax_return."""
|
||||
query_filters = get_filters(filters)
|
||||
query_filters.append(['tourist_tax_return', '>', 0])
|
||||
query_filters.append(['docstatus', '=', 1])
|
||||
query_filters.append(["tourist_tax_return", ">", 0])
|
||||
query_filters.append(["docstatus", "=", 1])
|
||||
try:
|
||||
return frappe.db.get_all('Sales Invoice',
|
||||
filters = query_filters,
|
||||
fields = ['sum(tourist_tax_return)'],
|
||||
as_list=True,
|
||||
limit = 1
|
||||
)[0][0] or 0
|
||||
return (
|
||||
frappe.db.get_all(
|
||||
"Sales Invoice",
|
||||
filters=query_filters,
|
||||
fields=["sum(tourist_tax_return)"],
|
||||
as_list=True,
|
||||
limit=1,
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_zero_rated_total(filters):
|
||||
"""Returns the sum of each Sales Invoice Item Amount which is zero rated."""
|
||||
conditions = get_conditions(filters)
|
||||
try:
|
||||
return frappe.db.sql("""
|
||||
return (
|
||||
frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
sum(i.base_amount) as total
|
||||
from
|
||||
@@ -308,15 +364,24 @@ def get_zero_rated_total(filters):
|
||||
where
|
||||
s.docstatus = 1 and i.is_zero_rated = 1
|
||||
{where_conditions} ;
|
||||
""".format(where_conditions=conditions), filters)[0][0] or 0
|
||||
""".format(
|
||||
where_conditions=conditions
|
||||
),
|
||||
filters,
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_exempt_total(filters):
|
||||
"""Returns the sum of each Sales Invoice Item Amount which is Vat Exempt."""
|
||||
conditions = get_conditions(filters)
|
||||
try:
|
||||
return frappe.db.sql("""
|
||||
return (
|
||||
frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
sum(i.base_amount) as total
|
||||
from
|
||||
@@ -326,15 +391,25 @@ def get_exempt_total(filters):
|
||||
where
|
||||
s.docstatus = 1 and i.is_exempt = 1
|
||||
{where_conditions} ;
|
||||
""".format(where_conditions=conditions), filters)[0][0] or 0
|
||||
""".format(
|
||||
where_conditions=conditions
|
||||
),
|
||||
filters,
|
||||
)[0][0]
|
||||
or 0
|
||||
)
|
||||
except (IndexError, TypeError):
|
||||
return 0
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
"""The conditions to be used to filter data to calculate the total sale."""
|
||||
conditions = ""
|
||||
for opts in (("company", " and company=%(company)s"),
|
||||
for opts in (
|
||||
("company", " and company=%(company)s"),
|
||||
("from_date", " and posting_date>=%(from_date)s"),
|
||||
("to_date", " and posting_date<=%(to_date)s")):
|
||||
("to_date", " and posting_date<=%(to_date)s"),
|
||||
):
|
||||
if filters.get(opts[0]):
|
||||
conditions += opts[1]
|
||||
return conditions
|
||||
|
||||
@@ -18,14 +18,22 @@ class TestVATAuditReport(TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
make_company("_Test Company SA VAT", "_TCSV")
|
||||
|
||||
create_account(account_name="VAT - 0%", account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
|
||||
create_account(account_name="VAT - 15%", account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
|
||||
create_account(
|
||||
account_name="VAT - 0%",
|
||||
account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TCSV",
|
||||
company="_Test Company SA VAT",
|
||||
)
|
||||
create_account(
|
||||
account_name="VAT - 15%",
|
||||
account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TCSV",
|
||||
company="_Test Company SA VAT",
|
||||
)
|
||||
set_sa_vat_accounts()
|
||||
|
||||
make_item("_Test SA VAT Item")
|
||||
make_item("_Test SA VAT Zero Rated Item", properties = {"is_zero_rated": 1})
|
||||
make_item("_Test SA VAT Zero Rated Item", properties={"is_zero_rated": 1})
|
||||
|
||||
make_customer()
|
||||
make_supplier()
|
||||
@@ -38,34 +46,33 @@ class TestVATAuditReport(TestCase):
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company SA VAT'")
|
||||
|
||||
def test_vat_audit_report(self):
|
||||
filters = {
|
||||
"company": "_Test Company SA VAT",
|
||||
"from_date": today(),
|
||||
"to_date": today()
|
||||
}
|
||||
filters = {"company": "_Test Company SA VAT", "from_date": today(), "to_date": today()}
|
||||
columns, data = execute(filters)
|
||||
total_tax_amount = 0
|
||||
total_row_tax = 0
|
||||
for row in data:
|
||||
keys = row.keys()
|
||||
# skips total row tax_amount in if.. and skips section header in elif..
|
||||
if 'voucher_no' in keys:
|
||||
total_tax_amount = total_tax_amount + row['tax_amount']
|
||||
elif 'tax_amount' in keys:
|
||||
total_row_tax = total_row_tax + row['tax_amount']
|
||||
if "voucher_no" in keys:
|
||||
total_tax_amount = total_tax_amount + row["tax_amount"]
|
||||
elif "tax_amount" in keys:
|
||||
total_row_tax = total_row_tax + row["tax_amount"]
|
||||
|
||||
self.assertEqual(total_tax_amount, total_row_tax)
|
||||
|
||||
|
||||
def make_company(company_name, abbr):
|
||||
if not frappe.db.exists("Company", company_name):
|
||||
company = frappe.get_doc({
|
||||
"doctype": "Company",
|
||||
"company_name": company_name,
|
||||
"abbr": abbr,
|
||||
"default_currency": "ZAR",
|
||||
"country": "South Africa",
|
||||
"create_chart_of_accounts_based_on": "Standard Template"
|
||||
})
|
||||
company = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": company_name,
|
||||
"abbr": abbr,
|
||||
"default_currency": "ZAR",
|
||||
"country": "South Africa",
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
}
|
||||
)
|
||||
company.insert()
|
||||
else:
|
||||
company = frappe.get_doc("Company", company_name)
|
||||
@@ -79,86 +86,95 @@ def make_company(company_name, abbr):
|
||||
|
||||
return company
|
||||
|
||||
|
||||
def set_sa_vat_accounts():
|
||||
if not frappe.db.exists("South Africa VAT Settings", "_Test Company SA VAT"):
|
||||
vat_accounts = frappe.get_all(
|
||||
"Account",
|
||||
fields=["name"],
|
||||
filters = {
|
||||
"company": "_Test Company SA VAT",
|
||||
"is_group": 0,
|
||||
"account_type": "Tax"
|
||||
}
|
||||
filters={"company": "_Test Company SA VAT", "is_group": 0, "account_type": "Tax"},
|
||||
)
|
||||
|
||||
sa_vat_accounts = []
|
||||
for account in vat_accounts:
|
||||
sa_vat_accounts.append({
|
||||
"doctype": "South Africa VAT Account",
|
||||
"account": account.name
|
||||
})
|
||||
sa_vat_accounts.append({"doctype": "South Africa VAT Account", "account": account.name})
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"company": "_Test Company SA VAT",
|
||||
"vat_accounts": sa_vat_accounts,
|
||||
"doctype": "South Africa VAT Settings",
|
||||
}
|
||||
).insert()
|
||||
|
||||
frappe.get_doc({
|
||||
"company": "_Test Company SA VAT",
|
||||
"vat_accounts": sa_vat_accounts,
|
||||
"doctype": "South Africa VAT Settings",
|
||||
}).insert()
|
||||
|
||||
def make_customer():
|
||||
if not frappe.db.exists("Customer", "_Test SA Customer"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
"customer_name": "_Test SA Customer",
|
||||
"customer_type": "Company",
|
||||
}).insert()
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Customer",
|
||||
"customer_name": "_Test SA Customer",
|
||||
"customer_type": "Company",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def make_supplier():
|
||||
if not frappe.db.exists("Supplier", "_Test SA Supplier"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test SA Supplier",
|
||||
"supplier_type": "Company",
|
||||
"supplier_group":"All Supplier Groups"
|
||||
}).insert()
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test SA Supplier",
|
||||
"supplier_type": "Company",
|
||||
"supplier_group": "All Supplier Groups",
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def make_item(item_code, properties=None):
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
item = frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": item_code,
|
||||
"item_name": item_code,
|
||||
"description": item_code,
|
||||
"item_group": "Products"
|
||||
})
|
||||
item = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item",
|
||||
"item_code": item_code,
|
||||
"item_name": item_code,
|
||||
"description": item_code,
|
||||
"item_group": "Products",
|
||||
}
|
||||
)
|
||||
|
||||
if properties:
|
||||
item.update(properties)
|
||||
|
||||
item.insert()
|
||||
|
||||
|
||||
def make_sales_invoices():
|
||||
def make_sales_invoices_wrapper(item, rate, tax_account, tax_rate, tax=True):
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company SA VAT",
|
||||
customer = "_Test SA Customer",
|
||||
currency = "ZAR",
|
||||
customer="_Test SA Customer",
|
||||
currency="ZAR",
|
||||
item=item,
|
||||
rate=rate,
|
||||
warehouse = "Finished Goods - _TCSV",
|
||||
debit_to = "Debtors - _TCSV",
|
||||
income_account = "Sales - _TCSV",
|
||||
expense_account = "Cost of Goods Sold - _TCSV",
|
||||
cost_center = "Main - _TCSV",
|
||||
do_not_save=1
|
||||
warehouse="Finished Goods - _TCSV",
|
||||
debit_to="Debtors - _TCSV",
|
||||
income_account="Sales - _TCSV",
|
||||
expense_account="Cost of Goods Sold - _TCSV",
|
||||
cost_center="Main - _TCSV",
|
||||
do_not_save=1,
|
||||
)
|
||||
if tax:
|
||||
si.append("taxes", {
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": tax_account,
|
||||
"cost_center": "Main - _TCSV",
|
||||
"description": "VAT 15% @ 15.0",
|
||||
"rate": tax_rate
|
||||
})
|
||||
"rate": tax_rate,
|
||||
},
|
||||
)
|
||||
|
||||
si.submit()
|
||||
|
||||
@@ -168,27 +184,31 @@ def make_sales_invoices():
|
||||
make_sales_invoices_wrapper(test_item, 100.0, "VAT - 15% - _TCSV", 15.0)
|
||||
make_sales_invoices_wrapper(test_zero_rated_item, 100.0, "VAT - 0% - _TCSV", 0.0)
|
||||
|
||||
|
||||
def create_purchase_invoices():
|
||||
pi = make_purchase_invoice(
|
||||
company = "_Test Company SA VAT",
|
||||
supplier = "_Test SA Supplier",
|
||||
supplier_warehouse = "Finished Goods - _TCSV",
|
||||
warehouse = "Finished Goods - _TCSV",
|
||||
currency = "ZAR",
|
||||
cost_center = "Main - _TCSV",
|
||||
expense_account = "Cost of Goods Sold - _TCSV",
|
||||
item = "_Test SA VAT Item",
|
||||
qty = 1,
|
||||
rate = 100,
|
||||
uom = "Nos",
|
||||
do_not_save = 1
|
||||
company="_Test Company SA VAT",
|
||||
supplier="_Test SA Supplier",
|
||||
supplier_warehouse="Finished Goods - _TCSV",
|
||||
warehouse="Finished Goods - _TCSV",
|
||||
currency="ZAR",
|
||||
cost_center="Main - _TCSV",
|
||||
expense_account="Cost of Goods Sold - _TCSV",
|
||||
item="_Test SA VAT Item",
|
||||
qty=1,
|
||||
rate=100,
|
||||
uom="Nos",
|
||||
do_not_save=1,
|
||||
)
|
||||
pi.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "VAT - 15% - _TCSV",
|
||||
"cost_center": "Main - _TCSV",
|
||||
"description": "VAT 15% @ 15.0",
|
||||
"rate": 15.0,
|
||||
},
|
||||
)
|
||||
pi.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "VAT - 15% - _TCSV",
|
||||
"cost_center": "Main - _TCSV",
|
||||
"description": "VAT 15% @ 15.0",
|
||||
"rate": 15.0
|
||||
})
|
||||
|
||||
pi.submit()
|
||||
|
||||
@@ -12,8 +12,8 @@ from frappe.utils import formatdate, get_link_to_form
|
||||
def execute(filters=None):
|
||||
return VATAuditReport(filters).run()
|
||||
|
||||
class VATAuditReport(object):
|
||||
|
||||
class VATAuditReport(object):
|
||||
def __init__(self, filters=None):
|
||||
self.filters = frappe._dict(filters or {})
|
||||
self.columns = []
|
||||
@@ -27,8 +27,11 @@ class VATAuditReport(object):
|
||||
self.select_columns = """
|
||||
name as voucher_no,
|
||||
posting_date, remarks"""
|
||||
columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \
|
||||
columns = (
|
||||
", supplier as party, credit_to as account"
|
||||
if doctype == "Purchase Invoice"
|
||||
else ", customer as party, debit_to as account"
|
||||
)
|
||||
self.select_columns += columns
|
||||
|
||||
self.get_invoice_data(doctype)
|
||||
@@ -41,28 +44,36 @@ class VATAuditReport(object):
|
||||
return self.columns, self.data
|
||||
|
||||
def get_sa_vat_accounts(self):
|
||||
self.sa_vat_accounts = frappe.get_all("South Africa VAT Account",
|
||||
filters = {"parent": self.filters.company}, pluck="account")
|
||||
self.sa_vat_accounts = frappe.get_all(
|
||||
"South Africa VAT Account", filters={"parent": self.filters.company}, pluck="account"
|
||||
)
|
||||
if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
|
||||
link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings")
|
||||
link_to_settings = get_link_to_form(
|
||||
"South Africa VAT Settings", "", label="South Africa VAT Settings"
|
||||
)
|
||||
frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings))
|
||||
|
||||
def get_invoice_data(self, doctype):
|
||||
conditions = self.get_conditions()
|
||||
self.invoices = frappe._dict()
|
||||
|
||||
invoice_data = frappe.db.sql("""
|
||||
invoice_data = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
{select_columns}
|
||||
FROM
|
||||
`tab{doctype}`
|
||||
WHERE
|
||||
docstatus = 1 {where_conditions}
|
||||
and is_opening = "No"
|
||||
and is_opening = 'No'
|
||||
ORDER BY
|
||||
posting_date DESC
|
||||
""".format(select_columns=self.select_columns, doctype=doctype,
|
||||
where_conditions=conditions), self.filters, as_dict=1)
|
||||
""".format(
|
||||
select_columns=self.select_columns, doctype=doctype, where_conditions=conditions
|
||||
),
|
||||
self.filters,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
for d in invoice_data:
|
||||
self.invoices.setdefault(d.voucher_no, d)
|
||||
@@ -70,30 +81,35 @@ class VATAuditReport(object):
|
||||
def get_invoice_items(self, doctype):
|
||||
self.invoice_items = frappe._dict()
|
||||
|
||||
items = frappe.db.sql("""
|
||||
items = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
item_code, parent, base_net_amount, is_zero_rated
|
||||
FROM
|
||||
`tab%s Item`
|
||||
WHERE
|
||||
parent in (%s)
|
||||
""" % (doctype, ", ".join(["%s"]*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||
"""
|
||||
% (doctype, ", ".join(["%s"] * len(self.invoices))),
|
||||
tuple(self.invoices),
|
||||
as_dict=1,
|
||||
)
|
||||
for d in items:
|
||||
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {
|
||||
'net_amount': 0.0})
|
||||
self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('base_net_amount', 0)
|
||||
self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {"net_amount": 0.0})
|
||||
self.invoice_items[d.parent][d.item_code]["net_amount"] += d.get("base_net_amount", 0)
|
||||
self.invoice_items[d.parent][d.item_code]["is_zero_rated"] = d.is_zero_rated
|
||||
|
||||
def get_items_based_on_tax_rate(self, doctype):
|
||||
self.items_based_on_tax_rate = frappe._dict()
|
||||
self.item_tax_rate = frappe._dict()
|
||||
self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \
|
||||
else "Sales Taxes and Charges"
|
||||
self.tax_doctype = (
|
||||
"Purchase Taxes and Charges" if doctype == "Purchase Invoice" else "Sales Taxes and Charges"
|
||||
)
|
||||
|
||||
self.tax_details = frappe.db.sql("""
|
||||
self.tax_details = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
|
||||
parent, account_head, item_wise_tax_detail
|
||||
FROM
|
||||
`tab%s`
|
||||
WHERE
|
||||
@@ -101,10 +117,12 @@ class VATAuditReport(object):
|
||||
and parent in (%s)
|
||||
ORDER BY
|
||||
account_head
|
||||
""" % (self.tax_doctype, "%s", ", ".join(["%s"]*len(self.invoices.keys()))),
|
||||
tuple([doctype] + list(self.invoices.keys())))
|
||||
"""
|
||||
% (self.tax_doctype, "%s", ", ".join(["%s"] * len(self.invoices.keys()))),
|
||||
tuple([doctype] + list(self.invoices.keys())),
|
||||
)
|
||||
|
||||
for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
|
||||
for parent, account, item_wise_tax_detail in self.tax_details:
|
||||
if item_wise_tax_detail:
|
||||
try:
|
||||
if account in self.sa_vat_accounts:
|
||||
@@ -113,14 +131,15 @@ class VATAuditReport(object):
|
||||
continue
|
||||
for item_code, taxes in item_wise_tax_detail.items():
|
||||
is_zero_rated = self.invoice_items.get(parent).get(item_code).get("is_zero_rated")
|
||||
#to skip items with non-zero tax rate in multiple rows
|
||||
# to skip items with non-zero tax rate in multiple rows
|
||||
if taxes[0] == 0 and not is_zero_rated:
|
||||
continue
|
||||
tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes)
|
||||
tax_rate = self.get_item_amount_map(parent, item_code, taxes)
|
||||
|
||||
if tax_rate is not None:
|
||||
rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}) \
|
||||
.setdefault(tax_rate, [])
|
||||
rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}).setdefault(
|
||||
tax_rate, []
|
||||
)
|
||||
if item_code not in rate_based_dict:
|
||||
rate_based_dict.append(item_code)
|
||||
except ValueError:
|
||||
@@ -131,23 +150,30 @@ class VATAuditReport(object):
|
||||
tax_rate = taxes[0]
|
||||
tax_amount = taxes[1]
|
||||
gross_amount = net_amount + tax_amount
|
||||
item_amount_map = self.item_tax_rate.setdefault(parent, {}) \
|
||||
.setdefault(item_code, [])
|
||||
amount_dict = {
|
||||
"tax_rate": tax_rate,
|
||||
"gross_amount": gross_amount,
|
||||
"tax_amount": tax_amount,
|
||||
"net_amount": net_amount
|
||||
}
|
||||
item_amount_map.append(amount_dict)
|
||||
|
||||
return tax_rate, item_amount_map
|
||||
self.item_tax_rate.setdefault(parent, {}).setdefault(
|
||||
item_code,
|
||||
{
|
||||
"tax_rate": tax_rate,
|
||||
"gross_amount": 0.0,
|
||||
"tax_amount": 0.0,
|
||||
"net_amount": 0.0,
|
||||
},
|
||||
)
|
||||
|
||||
self.item_tax_rate[parent][item_code]["net_amount"] += net_amount
|
||||
self.item_tax_rate[parent][item_code]["tax_amount"] += tax_amount
|
||||
self.item_tax_rate[parent][item_code]["gross_amount"] += gross_amount
|
||||
|
||||
return tax_rate
|
||||
|
||||
def get_conditions(self):
|
||||
conditions = ""
|
||||
for opts in (("company", " and company=%(company)s"),
|
||||
for opts in (
|
||||
("company", " and company=%(company)s"),
|
||||
("from_date", " and posting_date>=%(from_date)s"),
|
||||
("to_date", " and posting_date<=%(to_date)s")):
|
||||
("to_date", " and posting_date<=%(to_date)s"),
|
||||
):
|
||||
if self.filters.get(opts[0]):
|
||||
conditions += opts[1]
|
||||
|
||||
@@ -174,19 +200,20 @@ class VATAuditReport(object):
|
||||
"gross_amount": total_gross,
|
||||
"tax_amount": total_tax,
|
||||
"net_amount": total_net,
|
||||
"bold":1
|
||||
"bold": 1,
|
||||
}
|
||||
self.data.append(total)
|
||||
self.data.append({})
|
||||
|
||||
def get_consolidated_data(self, doctype):
|
||||
consolidated_data_map={}
|
||||
consolidated_data_map = {}
|
||||
for inv, inv_data in self.invoices.items():
|
||||
if self.items_based_on_tax_rate.get(inv):
|
||||
for rate, items in self.items_based_on_tax_rate.get(inv).items():
|
||||
row = {"tax_amount": 0.0, "gross_amount": 0.0, "net_amount": 0.0}
|
||||
|
||||
consolidated_data_map.setdefault(rate, {"data": []})
|
||||
for item in items:
|
||||
row = {}
|
||||
item_details = self.item_tax_rate.get(inv).get(item)
|
||||
row["account"] = inv_data.get("account")
|
||||
row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy")
|
||||
@@ -195,78 +222,54 @@ class VATAuditReport(object):
|
||||
row["party_type"] = "Customer" if doctype == "Sales Invoice" else "Supplier"
|
||||
row["party"] = inv_data.get("party")
|
||||
row["remarks"] = inv_data.get("remarks")
|
||||
row["gross_amount"]= item_details[0].get("gross_amount")
|
||||
row["tax_amount"]= item_details[0].get("tax_amount")
|
||||
row["net_amount"]= item_details[0].get("net_amount")
|
||||
consolidated_data_map[rate]["data"].append(row)
|
||||
row["gross_amount"] += item_details.get("gross_amount")
|
||||
row["tax_amount"] += item_details.get("tax_amount")
|
||||
row["net_amount"] += item_details.get("net_amount")
|
||||
|
||||
consolidated_data_map[rate]["data"].append(row)
|
||||
|
||||
return consolidated_data_map
|
||||
|
||||
def get_columns(self):
|
||||
self.columns = [
|
||||
{
|
||||
"fieldname": "posting_date",
|
||||
"label": "Posting Date",
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
},
|
||||
{"fieldname": "posting_date", "label": "Posting Date", "fieldtype": "Data", "width": 200},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": "Account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 150
|
||||
"width": 150,
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"label": "Voucher Type",
|
||||
"fieldtype": "Data",
|
||||
"width": 140,
|
||||
"hidden": 1
|
||||
"hidden": 1,
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
"label": "Reference",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 150
|
||||
"width": 150,
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"label": "Party Type",
|
||||
"fieldtype": "Data",
|
||||
"width": 140,
|
||||
"hidden": 1
|
||||
"hidden": 1,
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"label": "Party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "remarks",
|
||||
"label": "Details",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "net_amount",
|
||||
"label": "Net Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_amount",
|
||||
"label": "Tax Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"fieldname": "gross_amount",
|
||||
"label": "Gross Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 130
|
||||
"width": 150,
|
||||
},
|
||||
{"fieldname": "remarks", "label": "Details", "fieldtype": "Data", "width": 150},
|
||||
{"fieldname": "net_amount", "label": "Net Amount", "fieldtype": "Currency", "width": 130},
|
||||
{"fieldname": "tax_amount", "label": "Tax Amount", "fieldtype": "Currency", "width": 130},
|
||||
{"fieldname": "gross_amount", "label": "Gross Amount", "fieldtype": "Currency", "width": 130},
|
||||
]
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
|
||||
import frappe
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting
|
||||
from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import (
|
||||
create_ksa_vat_setting,
|
||||
)
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
add_print_formats()
|
||||
add_permissions()
|
||||
make_custom_fields()
|
||||
|
||||
|
||||
def add_print_formats():
|
||||
frappe.reload_doc("regional", "print_format", "detailed_tax_invoice", force=True)
|
||||
frappe.reload_doc("regional", "print_format", "simplified_tax_invoice", force=True)
|
||||
@@ -18,19 +22,27 @@ def add_print_formats():
|
||||
frappe.reload_doc("regional", "print_format", "ksa_vat_invoice", force=True)
|
||||
frappe.reload_doc("regional", "print_format", "ksa_pos_invoice", force=True)
|
||||
|
||||
for d in ('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice', 'KSA VAT Invoice', 'KSA POS Invoice'):
|
||||
for d in (
|
||||
"Simplified Tax Invoice",
|
||||
"Detailed Tax Invoice",
|
||||
"Tax Invoice",
|
||||
"KSA VAT Invoice",
|
||||
"KSA POS Invoice",
|
||||
):
|
||||
frappe.db.set_value("Print Format", d, "disabled", 0)
|
||||
|
||||
|
||||
def add_permissions():
|
||||
"""Add Permissions for KSA VAT Setting."""
|
||||
add_permission('KSA VAT Setting', 'All', 0)
|
||||
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
||||
add_permission('KSA VAT Setting', role, 0)
|
||||
update_permission_property('KSA VAT Setting', role, 0, 'write', 1)
|
||||
update_permission_property('KSA VAT Setting', role, 0, 'create', 1)
|
||||
add_permission("KSA VAT Setting", "All", 0)
|
||||
for role in ("Accounts Manager", "Accounts User", "System Manager"):
|
||||
add_permission("KSA VAT Setting", role, 0)
|
||||
update_permission_property("KSA VAT Setting", role, 0, "write", 1)
|
||||
update_permission_property("KSA VAT Setting", role, 0, "create", 1)
|
||||
|
||||
"""Enable KSA VAT Report"""
|
||||
frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0)
|
||||
frappe.db.set_value("Report", "KSA VAT", "disabled", 0)
|
||||
|
||||
|
||||
def make_custom_fields():
|
||||
"""Create Custom fields
|
||||
@@ -38,71 +50,124 @@ def make_custom_fields():
|
||||
- Company Name in Arabic
|
||||
- Address in Arabic
|
||||
"""
|
||||
is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||
fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description',
|
||||
print_hide=1)
|
||||
is_zero_rated = dict(
|
||||
fieldname="is_zero_rated",
|
||||
label="Is Zero Rated",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_zero_rated",
|
||||
insert_after="description",
|
||||
print_hide=1,
|
||||
)
|
||||
|
||||
is_exempt = dict(fieldname='is_exempt', label='Is Exempt',
|
||||
fieldtype='Check', fetch_from='item_code.is_exempt', insert_after='is_zero_rated',
|
||||
print_hide=1)
|
||||
is_exempt = dict(
|
||||
fieldname="is_exempt",
|
||||
label="Is Exempt",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_exempt",
|
||||
insert_after="is_zero_rated",
|
||||
print_hide=1,
|
||||
)
|
||||
|
||||
purchase_invoice_fields = [
|
||||
dict(fieldname='company_trn', label='Company TRN',
|
||||
fieldtype='Read Only', insert_after='shipping_address',
|
||||
fetch_from='company.tax_id', print_hide=1),
|
||||
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
|
||||
fieldtype='Read Only', insert_after='supplier_name',
|
||||
fetch_from='supplier.supplier_name_in_arabic', print_hide=1)
|
||||
]
|
||||
dict(
|
||||
fieldname="company_trn",
|
||||
label="Company TRN",
|
||||
fieldtype="Read Only",
|
||||
insert_after="shipping_address",
|
||||
fetch_from="company.tax_id",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="supplier_name_in_arabic",
|
||||
label="Supplier Name in Arabic",
|
||||
fieldtype="Read Only",
|
||||
insert_after="supplier_name",
|
||||
fetch_from="supplier.supplier_name_in_arabic",
|
||||
print_hide=1,
|
||||
),
|
||||
]
|
||||
|
||||
sales_invoice_fields = [
|
||||
dict(fieldname='company_trn', label='Company TRN',
|
||||
fieldtype='Read Only', insert_after='company_address',
|
||||
fetch_from='company.tax_id', print_hide=1),
|
||||
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
|
||||
fieldtype='Read Only', insert_after='customer_name',
|
||||
fetch_from='customer.customer_name_in_arabic', print_hide=1),
|
||||
dict(fieldname='ksa_einv_qr', label='KSA E-Invoicing QR',
|
||||
fieldtype='Attach Image', read_only=1, no_copy=1, hidden=1)
|
||||
]
|
||||
dict(
|
||||
fieldname="company_trn",
|
||||
label="Company TRN",
|
||||
fieldtype="Read Only",
|
||||
insert_after="company_address",
|
||||
fetch_from="company.tax_id",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_name_in_arabic",
|
||||
label="Customer Name in Arabic",
|
||||
fieldtype="Read Only",
|
||||
insert_after="customer_name",
|
||||
fetch_from="customer.customer_name_in_arabic",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="ksa_einv_qr",
|
||||
label="KSA E-Invoicing QR",
|
||||
fieldtype="Attach Image",
|
||||
read_only=1,
|
||||
no_copy=1,
|
||||
hidden=1,
|
||||
),
|
||||
]
|
||||
|
||||
custom_fields = {
|
||||
'Item': [is_zero_rated, is_exempt],
|
||||
'Customer': [
|
||||
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
|
||||
fieldtype='Data', insert_after='customer_name'),
|
||||
"Item": [is_zero_rated, is_exempt],
|
||||
"Customer": [
|
||||
dict(
|
||||
fieldname="customer_name_in_arabic",
|
||||
label="Customer Name in Arabic",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_name",
|
||||
),
|
||||
],
|
||||
'Supplier': [
|
||||
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
|
||||
fieldtype='Data', insert_after='supplier_name'),
|
||||
"Supplier": [
|
||||
dict(
|
||||
fieldname="supplier_name_in_arabic",
|
||||
label="Supplier Name in Arabic",
|
||||
fieldtype="Data",
|
||||
insert_after="supplier_name",
|
||||
),
|
||||
],
|
||||
'Purchase Invoice': purchase_invoice_fields,
|
||||
'Purchase Order': purchase_invoice_fields,
|
||||
'Purchase Receipt': purchase_invoice_fields,
|
||||
'Sales Invoice': sales_invoice_fields,
|
||||
'POS Invoice': sales_invoice_fields,
|
||||
'Sales Order': sales_invoice_fields,
|
||||
'Delivery Note': sales_invoice_fields,
|
||||
'Sales Invoice Item': [is_zero_rated, is_exempt],
|
||||
'POS Invoice Item': [is_zero_rated, is_exempt],
|
||||
'Purchase Invoice Item': [is_zero_rated, is_exempt],
|
||||
'Sales Order Item': [is_zero_rated, is_exempt],
|
||||
'Delivery Note Item': [is_zero_rated, is_exempt],
|
||||
'Quotation Item': [is_zero_rated, is_exempt],
|
||||
'Purchase Order Item': [is_zero_rated, is_exempt],
|
||||
'Purchase Receipt Item': [is_zero_rated, is_exempt],
|
||||
'Supplier Quotation Item': [is_zero_rated, is_exempt],
|
||||
'Address': [
|
||||
dict(fieldname='address_in_arabic', label='Address in Arabic',
|
||||
fieldtype='Data',insert_after='address_line2')
|
||||
"Purchase Invoice": purchase_invoice_fields,
|
||||
"Purchase Order": purchase_invoice_fields,
|
||||
"Purchase Receipt": purchase_invoice_fields,
|
||||
"Sales Invoice": sales_invoice_fields,
|
||||
"POS Invoice": sales_invoice_fields,
|
||||
"Sales Order": sales_invoice_fields,
|
||||
"Delivery Note": sales_invoice_fields,
|
||||
"Sales Invoice Item": [is_zero_rated, is_exempt],
|
||||
"POS Invoice Item": [is_zero_rated, is_exempt],
|
||||
"Purchase Invoice Item": [is_zero_rated, is_exempt],
|
||||
"Sales Order Item": [is_zero_rated, is_exempt],
|
||||
"Delivery Note Item": [is_zero_rated, is_exempt],
|
||||
"Quotation Item": [is_zero_rated, is_exempt],
|
||||
"Purchase Order Item": [is_zero_rated, is_exempt],
|
||||
"Purchase Receipt Item": [is_zero_rated, is_exempt],
|
||||
"Supplier Quotation Item": [is_zero_rated, is_exempt],
|
||||
"Address": [
|
||||
dict(
|
||||
fieldname="address_in_arabic",
|
||||
label="Address in Arabic",
|
||||
fieldtype="Data",
|
||||
insert_after="address_line2",
|
||||
)
|
||||
],
|
||||
"Company": [
|
||||
dict(
|
||||
fieldname="company_name_in_arabic",
|
||||
label="Company Name In Arabic",
|
||||
fieldtype="Data",
|
||||
insert_after="company_name",
|
||||
)
|
||||
],
|
||||
'Company': [
|
||||
dict(fieldname='company_name_in_arabic', label='Company Name In Arabic',
|
||||
fieldtype='Data', insert_after='company_name')
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, ignore_validate=True, update=True)
|
||||
|
||||
|
||||
def update_regional_tax_settings(country, company):
|
||||
create_ksa_vat_setting(company)
|
||||
|
||||
@@ -13,21 +13,25 @@ from erpnext import get_region
|
||||
|
||||
def create_qr_code(doc, method=None):
|
||||
region = get_region(doc.company)
|
||||
if region not in ['Saudi Arabia']:
|
||||
if region not in ["Saudi Arabia"]:
|
||||
return
|
||||
|
||||
# if QR Code field not present, create it. Invoices without QR are invalid as per law.
|
||||
if not hasattr(doc, 'ksa_einv_qr'):
|
||||
create_custom_fields({
|
||||
doc.doctype: [
|
||||
dict(
|
||||
fieldname='ksa_einv_qr',
|
||||
label='KSA E-Invoicing QR',
|
||||
fieldtype='Attach Image',
|
||||
read_only=1, no_copy=1, hidden=1
|
||||
)
|
||||
]
|
||||
})
|
||||
if not hasattr(doc, "ksa_einv_qr"):
|
||||
create_custom_fields(
|
||||
{
|
||||
doc.doctype: [
|
||||
dict(
|
||||
fieldname="ksa_einv_qr",
|
||||
label="KSA E-Invoicing QR",
|
||||
fieldtype="Attach Image",
|
||||
read_only=1,
|
||||
no_copy=1,
|
||||
hidden=1,
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# Don't create QR Code if it already exists
|
||||
qr_code = doc.get("ksa_einv_qr")
|
||||
@@ -37,113 +41,129 @@ def create_qr_code(doc, method=None):
|
||||
meta = frappe.get_meta(doc.doctype)
|
||||
|
||||
if "ksa_einv_qr" in [d.fieldname for d in meta.get_image_fields()]:
|
||||
''' TLV conversion for
|
||||
"""TLV conversion for
|
||||
1. Seller's Name
|
||||
2. VAT Number
|
||||
3. Time Stamp
|
||||
4. Invoice Amount
|
||||
5. VAT Amount
|
||||
'''
|
||||
"""
|
||||
tlv_array = []
|
||||
# Sellers Name
|
||||
|
||||
seller_name = frappe.db.get_value(
|
||||
'Company',
|
||||
doc.company,
|
||||
'company_name_in_arabic')
|
||||
seller_name = frappe.db.get_value("Company", doc.company, "company_name_in_arabic")
|
||||
|
||||
if not seller_name:
|
||||
frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company))
|
||||
frappe.throw(_("Arabic name missing for {} in the company document").format(doc.company))
|
||||
|
||||
tag = bytes([1]).hex()
|
||||
length = bytes([len(seller_name.encode('utf-8'))]).hex()
|
||||
value = seller_name.encode('utf-8').hex()
|
||||
tlv_array.append(''.join([tag, length, value]))
|
||||
length = bytes([len(seller_name.encode("utf-8"))]).hex()
|
||||
value = seller_name.encode("utf-8").hex()
|
||||
tlv_array.append("".join([tag, length, value]))
|
||||
|
||||
# VAT Number
|
||||
tax_id = frappe.db.get_value('Company', doc.company, 'tax_id')
|
||||
tax_id = frappe.db.get_value("Company", doc.company, "tax_id")
|
||||
if not tax_id:
|
||||
frappe.throw(_('Tax ID missing for {} in the company document').format(doc.company))
|
||||
frappe.throw(_("Tax ID missing for {} in the company document").format(doc.company))
|
||||
|
||||
tag = bytes([2]).hex()
|
||||
length = bytes([len(tax_id)]).hex()
|
||||
value = tax_id.encode('utf-8').hex()
|
||||
tlv_array.append(''.join([tag, length, value]))
|
||||
value = tax_id.encode("utf-8").hex()
|
||||
tlv_array.append("".join([tag, length, value]))
|
||||
|
||||
# Time Stamp
|
||||
posting_date = getdate(doc.posting_date)
|
||||
time = get_time(doc.posting_time)
|
||||
seconds = time.hour * 60 * 60 + time.minute * 60 + time.second
|
||||
time_stamp = add_to_date(posting_date, seconds=seconds)
|
||||
time_stamp = time_stamp.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
time_stamp = time_stamp.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
tag = bytes([3]).hex()
|
||||
length = bytes([len(time_stamp)]).hex()
|
||||
value = time_stamp.encode('utf-8').hex()
|
||||
tlv_array.append(''.join([tag, length, value]))
|
||||
value = time_stamp.encode("utf-8").hex()
|
||||
tlv_array.append("".join([tag, length, value]))
|
||||
|
||||
# Invoice Amount
|
||||
invoice_amount = str(doc.grand_total)
|
||||
tag = bytes([4]).hex()
|
||||
length = bytes([len(invoice_amount)]).hex()
|
||||
value = invoice_amount.encode('utf-8').hex()
|
||||
tlv_array.append(''.join([tag, length, value]))
|
||||
value = invoice_amount.encode("utf-8").hex()
|
||||
tlv_array.append("".join([tag, length, value]))
|
||||
|
||||
# VAT Amount
|
||||
vat_amount = str(doc.total_taxes_and_charges)
|
||||
vat_amount = str(get_vat_amount(doc))
|
||||
|
||||
tag = bytes([5]).hex()
|
||||
length = bytes([len(vat_amount)]).hex()
|
||||
value = vat_amount.encode('utf-8').hex()
|
||||
tlv_array.append(''.join([tag, length, value]))
|
||||
value = vat_amount.encode("utf-8").hex()
|
||||
tlv_array.append("".join([tag, length, value]))
|
||||
|
||||
# Joining bytes into one
|
||||
tlv_buff = ''.join(tlv_array)
|
||||
tlv_buff = "".join(tlv_array)
|
||||
|
||||
# base64 conversion for QR Code
|
||||
base64_string = b64encode(bytes.fromhex(tlv_buff)).decode()
|
||||
|
||||
qr_image = io.BytesIO()
|
||||
url = qr_create(base64_string, error='L')
|
||||
url = qr_create(base64_string, error="L")
|
||||
url.png(qr_image, scale=2, quiet_zone=1)
|
||||
|
||||
name = frappe.generate_hash(doc.name, 5)
|
||||
|
||||
# making file
|
||||
filename = f"QRCode-{name}.png".replace(os.path.sep, "__")
|
||||
_file = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": filename,
|
||||
"is_private": 0,
|
||||
"content": qr_image.getvalue(),
|
||||
"attached_to_doctype": doc.get("doctype"),
|
||||
"attached_to_name": doc.get("name"),
|
||||
"attached_to_field": "ksa_einv_qr"
|
||||
})
|
||||
_file = frappe.get_doc(
|
||||
{
|
||||
"doctype": "File",
|
||||
"file_name": filename,
|
||||
"is_private": 0,
|
||||
"content": qr_image.getvalue(),
|
||||
"attached_to_doctype": doc.get("doctype"),
|
||||
"attached_to_name": doc.get("name"),
|
||||
"attached_to_field": "ksa_einv_qr",
|
||||
}
|
||||
)
|
||||
|
||||
_file.save()
|
||||
|
||||
# assigning to document
|
||||
doc.db_set('ksa_einv_qr', _file.file_url)
|
||||
doc.db_set("ksa_einv_qr", _file.file_url)
|
||||
doc.notify_update()
|
||||
|
||||
|
||||
def get_vat_amount(doc):
|
||||
vat_settings = frappe.db.get_value("KSA VAT Setting", {"company": doc.company})
|
||||
vat_accounts = []
|
||||
vat_amount = 0
|
||||
|
||||
if vat_settings:
|
||||
vat_settings_doc = frappe.get_cached_doc("KSA VAT Setting", vat_settings)
|
||||
|
||||
for row in vat_settings_doc.get("ksa_vat_sales_accounts"):
|
||||
vat_accounts.append(row.account)
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
if tax.account_head in vat_accounts:
|
||||
vat_amount += tax.tax_amount
|
||||
|
||||
return vat_amount
|
||||
|
||||
|
||||
def delete_qr_code_file(doc, method=None):
|
||||
region = get_region(doc.company)
|
||||
if region not in ['Saudi Arabia']:
|
||||
if region not in ["Saudi Arabia"]:
|
||||
return
|
||||
|
||||
if hasattr(doc, 'ksa_einv_qr'):
|
||||
if doc.get('ksa_einv_qr'):
|
||||
file_doc = frappe.get_list('File', {
|
||||
'file_url': doc.get('ksa_einv_qr')
|
||||
})
|
||||
if hasattr(doc, "ksa_einv_qr"):
|
||||
if doc.get("ksa_einv_qr"):
|
||||
file_doc = frappe.get_list("File", {"file_url": doc.get("ksa_einv_qr")})
|
||||
if len(file_doc):
|
||||
frappe.delete_doc('File', file_doc[0].name)
|
||||
frappe.delete_doc("File", file_doc[0].name)
|
||||
|
||||
|
||||
def delete_vat_settings_for_company(doc, method=None):
|
||||
if doc.country != 'Saudi Arabia':
|
||||
if doc.country != "Saudi Arabia":
|
||||
return
|
||||
|
||||
if frappe.db.exists('KSA VAT Setting', doc.name):
|
||||
frappe.delete_doc('KSA VAT Setting', doc.name)
|
||||
if frappe.db.exists("KSA VAT Setting", doc.name):
|
||||
frappe.delete_doc("KSA VAT Setting", doc.name)
|
||||
|
||||
@@ -5,39 +5,42 @@ import frappe
|
||||
|
||||
|
||||
def create_ksa_vat_setting(company):
|
||||
"""On creation of first company. Creates KSA VAT Setting"""
|
||||
"""On creation of first company. Creates KSA VAT Setting"""
|
||||
|
||||
company = frappe.get_doc('Company', company)
|
||||
company = frappe.get_doc("Company", company)
|
||||
|
||||
file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json')
|
||||
with open(file_path, 'r') as json_file:
|
||||
account_data = json.load(json_file)
|
||||
file_path = os.path.join(os.path.dirname(__file__), "..", "data", "ksa_vat_settings.json")
|
||||
with open(file_path, "r") as json_file:
|
||||
account_data = json.load(json_file)
|
||||
|
||||
# Creating KSA VAT Setting
|
||||
ksa_vat_setting = frappe.get_doc({
|
||||
'doctype': 'KSA VAT Setting',
|
||||
'company': company.name
|
||||
})
|
||||
# Creating KSA VAT Setting
|
||||
ksa_vat_setting = frappe.get_doc({"doctype": "KSA VAT Setting", "company": company.name})
|
||||
|
||||
for data in account_data:
|
||||
if data['type'] == 'Sales Account':
|
||||
for row in data['accounts']:
|
||||
item_tax_template = row['item_tax_template']
|
||||
account = row['account']
|
||||
ksa_vat_setting.append('ksa_vat_sales_accounts', {
|
||||
'title': row['title'],
|
||||
'item_tax_template': f'{item_tax_template} - {company.abbr}',
|
||||
'account': f'{account} - {company.abbr}'
|
||||
})
|
||||
for data in account_data:
|
||||
if data["type"] == "Sales Account":
|
||||
for row in data["accounts"]:
|
||||
item_tax_template = row["item_tax_template"]
|
||||
account = row["account"]
|
||||
ksa_vat_setting.append(
|
||||
"ksa_vat_sales_accounts",
|
||||
{
|
||||
"title": row["title"],
|
||||
"item_tax_template": f"{item_tax_template} - {company.abbr}",
|
||||
"account": f"{account} - {company.abbr}",
|
||||
},
|
||||
)
|
||||
|
||||
elif data['type'] == 'Purchase Account':
|
||||
for row in data['accounts']:
|
||||
item_tax_template = row['item_tax_template']
|
||||
account = row['account']
|
||||
ksa_vat_setting.append('ksa_vat_purchase_accounts', {
|
||||
'title': row['title'],
|
||||
'item_tax_template': f'{item_tax_template} - {company.abbr}',
|
||||
'account': f'{account} - {company.abbr}'
|
||||
})
|
||||
elif data["type"] == "Purchase Account":
|
||||
for row in data["accounts"]:
|
||||
item_tax_template = row["item_tax_template"]
|
||||
account = row["account"]
|
||||
ksa_vat_setting.append(
|
||||
"ksa_vat_purchase_accounts",
|
||||
{
|
||||
"title": row["title"],
|
||||
"item_tax_template": f"{item_tax_template} - {company.abbr}",
|
||||
"account": f"{account} - {company.abbr}",
|
||||
},
|
||||
)
|
||||
|
||||
ksa_vat_setting.save()
|
||||
ksa_vat_setting.save()
|
||||
|
||||
@@ -6,44 +6,53 @@ import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_permissions()
|
||||
|
||||
|
||||
def make_custom_fields(update=True):
|
||||
is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||
fieldtype='Check', fetch_from='item_code.is_zero_rated',
|
||||
insert_after='description', print_hide=1)
|
||||
is_zero_rated = dict(
|
||||
fieldname="is_zero_rated",
|
||||
label="Is Zero Rated",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_zero_rated",
|
||||
insert_after="description",
|
||||
print_hide=1,
|
||||
)
|
||||
custom_fields = {
|
||||
'Item': [
|
||||
dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||
fieldtype='Check', insert_after='item_group',
|
||||
print_hide=1)
|
||||
"Item": [
|
||||
dict(
|
||||
fieldname="is_zero_rated",
|
||||
label="Is Zero Rated",
|
||||
fieldtype="Check",
|
||||
insert_after="item_group",
|
||||
print_hide=1,
|
||||
)
|
||||
],
|
||||
'Sales Invoice Item': is_zero_rated,
|
||||
'Purchase Invoice Item': is_zero_rated
|
||||
"Sales Invoice Item": is_zero_rated,
|
||||
"Purchase Invoice Item": is_zero_rated,
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=update)
|
||||
|
||||
|
||||
def add_permissions():
|
||||
"""Add Permissions for South Africa VAT Settings and South Africa VAT Account
|
||||
and VAT Audit Report"""
|
||||
for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'):
|
||||
add_permission(doctype, 'All', 0)
|
||||
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
||||
and VAT Audit Report"""
|
||||
for doctype in ("South Africa VAT Settings", "South Africa VAT Account"):
|
||||
add_permission(doctype, "All", 0)
|
||||
for role in ("Accounts Manager", "Accounts User", "System Manager"):
|
||||
add_permission(doctype, role, 0)
|
||||
update_permission_property(doctype, role, 0, 'write', 1)
|
||||
update_permission_property(doctype, role, 0, 'create', 1)
|
||||
update_permission_property(doctype, role, 0, "write", 1)
|
||||
update_permission_property(doctype, role, 0, "create", 1)
|
||||
|
||||
|
||||
if not frappe.db.get_value('Custom Role', dict(report="VAT Audit Report")):
|
||||
frappe.get_doc(dict(
|
||||
doctype='Custom Role',
|
||||
report="VAT Audit Report",
|
||||
roles= [
|
||||
dict(role='Accounts User'),
|
||||
dict(role='Accounts Manager'),
|
||||
dict(role='Auditor')
|
||||
]
|
||||
)).insert()
|
||||
if not frappe.db.get_value("Custom Role", dict(report="VAT Audit Report")):
|
||||
frappe.get_doc(
|
||||
dict(
|
||||
doctype="Custom Role",
|
||||
report="VAT Audit Report",
|
||||
roles=[dict(role="Accounts User"), dict(role="Accounts Manager"), dict(role="Auditor")],
|
||||
)
|
||||
).insert()
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
def setup(company=None, patch=True):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
from erpnext.payroll.doctype.gratuity_rule.gratuity_rule import get_gratuity_rule
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_print_formats()
|
||||
@@ -14,145 +15,270 @@ def setup(company=None, patch=True):
|
||||
add_permissions()
|
||||
create_gratuity_rule()
|
||||
|
||||
|
||||
def make_custom_fields():
|
||||
is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||
fieldtype='Check', fetch_from='item_code.is_zero_rated', insert_after='description',
|
||||
print_hide=1)
|
||||
is_exempt = dict(fieldname='is_exempt', label='Is Exempt',
|
||||
fieldtype='Check', fetch_from='item_code.is_exempt', insert_after='is_zero_rated',
|
||||
print_hide=1)
|
||||
is_zero_rated = dict(
|
||||
fieldname="is_zero_rated",
|
||||
label="Is Zero Rated",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_zero_rated",
|
||||
insert_after="description",
|
||||
print_hide=1,
|
||||
)
|
||||
is_exempt = dict(
|
||||
fieldname="is_exempt",
|
||||
label="Is Exempt",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_exempt",
|
||||
insert_after="is_zero_rated",
|
||||
print_hide=1,
|
||||
)
|
||||
|
||||
invoice_fields = [
|
||||
dict(fieldname='vat_section', label='VAT Details', fieldtype='Section Break',
|
||||
insert_after='group_same_items', print_hide=1, collapsible=1),
|
||||
dict(fieldname='permit_no', label='Permit Number',
|
||||
fieldtype='Data', insert_after='vat_section', print_hide=1),
|
||||
dict(
|
||||
fieldname="vat_section",
|
||||
label="VAT Details",
|
||||
fieldtype="Section Break",
|
||||
insert_after="group_same_items",
|
||||
print_hide=1,
|
||||
collapsible=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="permit_no",
|
||||
label="Permit Number",
|
||||
fieldtype="Data",
|
||||
insert_after="vat_section",
|
||||
print_hide=1,
|
||||
),
|
||||
]
|
||||
|
||||
purchase_invoice_fields = [
|
||||
dict(fieldname='company_trn', label='Company TRN',
|
||||
fieldtype='Read Only', insert_after='shipping_address',
|
||||
fetch_from='company.tax_id', print_hide=1),
|
||||
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
|
||||
fieldtype='Read Only', insert_after='supplier_name',
|
||||
fetch_from='supplier.supplier_name_in_arabic', print_hide=1),
|
||||
dict(fieldname='recoverable_standard_rated_expenses', print_hide=1, default='0',
|
||||
label='Recoverable Standard Rated Expenses (AED)', insert_after='permit_no',
|
||||
fieldtype='Currency', ),
|
||||
dict(fieldname='reverse_charge', label='Reverse Charge Applicable',
|
||||
fieldtype='Select', insert_after='recoverable_standard_rated_expenses', print_hide=1,
|
||||
options='Y\nN', default='N'),
|
||||
dict(fieldname='recoverable_reverse_charge', label='Recoverable Reverse Charge (Percentage)',
|
||||
insert_after='reverse_charge', fieldtype='Percent', print_hide=1,
|
||||
depends_on="eval:doc.reverse_charge=='Y'", default='100.000'),
|
||||
]
|
||||
dict(
|
||||
fieldname="company_trn",
|
||||
label="Company TRN",
|
||||
fieldtype="Read Only",
|
||||
insert_after="shipping_address",
|
||||
fetch_from="company.tax_id",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="supplier_name_in_arabic",
|
||||
label="Supplier Name in Arabic",
|
||||
fieldtype="Read Only",
|
||||
insert_after="supplier_name",
|
||||
fetch_from="supplier.supplier_name_in_arabic",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="recoverable_standard_rated_expenses",
|
||||
print_hide=1,
|
||||
default="0",
|
||||
label="Recoverable Standard Rated Expenses (AED)",
|
||||
insert_after="permit_no",
|
||||
fieldtype="Currency",
|
||||
),
|
||||
dict(
|
||||
fieldname="reverse_charge",
|
||||
label="Reverse Charge Applicable",
|
||||
fieldtype="Select",
|
||||
insert_after="recoverable_standard_rated_expenses",
|
||||
print_hide=1,
|
||||
options="Y\nN",
|
||||
default="N",
|
||||
),
|
||||
dict(
|
||||
fieldname="recoverable_reverse_charge",
|
||||
label="Recoverable Reverse Charge (Percentage)",
|
||||
insert_after="reverse_charge",
|
||||
fieldtype="Percent",
|
||||
print_hide=1,
|
||||
depends_on="eval:doc.reverse_charge=='Y'",
|
||||
default="100.000",
|
||||
),
|
||||
]
|
||||
|
||||
sales_invoice_fields = [
|
||||
dict(fieldname='company_trn', label='Company TRN',
|
||||
fieldtype='Read Only', insert_after='company_address',
|
||||
fetch_from='company.tax_id', print_hide=1),
|
||||
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
|
||||
fieldtype='Read Only', insert_after='customer_name',
|
||||
fetch_from='customer.customer_name_in_arabic', print_hide=1),
|
||||
dict(fieldname='vat_emirate', label='VAT Emirate', insert_after='permit_no', fieldtype='Select',
|
||||
options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain',
|
||||
fetch_from='company_address.emirate'),
|
||||
dict(fieldname='tourist_tax_return', label='Tax Refund provided to Tourists (AED)',
|
||||
insert_after='vat_emirate', fieldtype='Currency', print_hide=1, default='0'),
|
||||
]
|
||||
dict(
|
||||
fieldname="company_trn",
|
||||
label="Company TRN",
|
||||
fieldtype="Read Only",
|
||||
insert_after="company_address",
|
||||
fetch_from="company.tax_id",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="customer_name_in_arabic",
|
||||
label="Customer Name in Arabic",
|
||||
fieldtype="Read Only",
|
||||
insert_after="customer_name",
|
||||
fetch_from="customer.customer_name_in_arabic",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="vat_emirate",
|
||||
label="VAT Emirate",
|
||||
insert_after="permit_no",
|
||||
fieldtype="Select",
|
||||
options="\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain",
|
||||
fetch_from="company_address.emirate",
|
||||
),
|
||||
dict(
|
||||
fieldname="tourist_tax_return",
|
||||
label="Tax Refund provided to Tourists (AED)",
|
||||
insert_after="vat_emirate",
|
||||
fieldtype="Currency",
|
||||
print_hide=1,
|
||||
default="0",
|
||||
),
|
||||
]
|
||||
|
||||
invoice_item_fields = [
|
||||
dict(fieldname='tax_code', label='Tax Code',
|
||||
fieldtype='Read Only', fetch_from='item_code.tax_code', insert_after='description',
|
||||
allow_on_submit=1, print_hide=1),
|
||||
dict(fieldname='tax_rate', label='Tax Rate',
|
||||
fieldtype='Float', insert_after='tax_code',
|
||||
print_hide=1, hidden=1, read_only=1),
|
||||
dict(fieldname='tax_amount', label='Tax Amount',
|
||||
fieldtype='Currency', insert_after='tax_rate',
|
||||
print_hide=1, hidden=1, read_only=1, options="currency"),
|
||||
dict(fieldname='total_amount', label='Total Amount',
|
||||
fieldtype='Currency', insert_after='tax_amount',
|
||||
print_hide=1, hidden=1, read_only=1, options="currency"),
|
||||
dict(
|
||||
fieldname="tax_code",
|
||||
label="Tax Code",
|
||||
fieldtype="Read Only",
|
||||
fetch_from="item_code.tax_code",
|
||||
insert_after="description",
|
||||
allow_on_submit=1,
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="tax_rate",
|
||||
label="Tax Rate",
|
||||
fieldtype="Float",
|
||||
insert_after="tax_code",
|
||||
print_hide=1,
|
||||
hidden=1,
|
||||
read_only=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="tax_amount",
|
||||
label="Tax Amount",
|
||||
fieldtype="Currency",
|
||||
insert_after="tax_rate",
|
||||
print_hide=1,
|
||||
hidden=1,
|
||||
read_only=1,
|
||||
options="currency",
|
||||
),
|
||||
dict(
|
||||
fieldname="total_amount",
|
||||
label="Total Amount",
|
||||
fieldtype="Currency",
|
||||
insert_after="tax_amount",
|
||||
print_hide=1,
|
||||
hidden=1,
|
||||
read_only=1,
|
||||
options="currency",
|
||||
),
|
||||
]
|
||||
|
||||
delivery_date_field = [
|
||||
dict(fieldname='delivery_date', label='Delivery Date',
|
||||
fieldtype='Date', insert_after='item_name', print_hide=1)
|
||||
dict(
|
||||
fieldname="delivery_date",
|
||||
label="Delivery Date",
|
||||
fieldtype="Date",
|
||||
insert_after="item_name",
|
||||
print_hide=1,
|
||||
)
|
||||
]
|
||||
|
||||
custom_fields = {
|
||||
'Item': [
|
||||
dict(fieldname='tax_code', label='Tax Code',
|
||||
fieldtype='Data', insert_after='item_group'),
|
||||
dict(fieldname='is_zero_rated', label='Is Zero Rated',
|
||||
fieldtype='Check', insert_after='tax_code',
|
||||
print_hide=1),
|
||||
dict(fieldname='is_exempt', label='Is Exempt',
|
||||
fieldtype='Check', insert_after='is_zero_rated',
|
||||
print_hide=1)
|
||||
"Item": [
|
||||
dict(fieldname="tax_code", label="Tax Code", fieldtype="Data", insert_after="item_group"),
|
||||
dict(
|
||||
fieldname="is_zero_rated",
|
||||
label="Is Zero Rated",
|
||||
fieldtype="Check",
|
||||
insert_after="tax_code",
|
||||
print_hide=1,
|
||||
),
|
||||
dict(
|
||||
fieldname="is_exempt",
|
||||
label="Is Exempt",
|
||||
fieldtype="Check",
|
||||
insert_after="is_zero_rated",
|
||||
print_hide=1,
|
||||
),
|
||||
],
|
||||
'Customer': [
|
||||
dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic',
|
||||
fieldtype='Data', insert_after='customer_name'),
|
||||
"Customer": [
|
||||
dict(
|
||||
fieldname="customer_name_in_arabic",
|
||||
label="Customer Name in Arabic",
|
||||
fieldtype="Data",
|
||||
insert_after="customer_name",
|
||||
),
|
||||
],
|
||||
'Supplier': [
|
||||
dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic',
|
||||
fieldtype='Data', insert_after='supplier_name'),
|
||||
"Supplier": [
|
||||
dict(
|
||||
fieldname="supplier_name_in_arabic",
|
||||
label="Supplier Name in Arabic",
|
||||
fieldtype="Data",
|
||||
insert_after="supplier_name",
|
||||
),
|
||||
],
|
||||
'Address': [
|
||||
dict(fieldname='emirate', label='Emirate', fieldtype='Select', insert_after='state',
|
||||
options='\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain')
|
||||
"Address": [
|
||||
dict(
|
||||
fieldname="emirate",
|
||||
label="Emirate",
|
||||
fieldtype="Select",
|
||||
insert_after="state",
|
||||
options="\nAbu Dhabi\nAjman\nDubai\nFujairah\nRas Al Khaimah\nSharjah\nUmm Al Quwain",
|
||||
)
|
||||
],
|
||||
'Purchase Invoice': purchase_invoice_fields + invoice_fields,
|
||||
'Purchase Order': purchase_invoice_fields + invoice_fields,
|
||||
'Purchase Receipt': purchase_invoice_fields + invoice_fields,
|
||||
'Sales Invoice': sales_invoice_fields + invoice_fields,
|
||||
'POS Invoice': sales_invoice_fields + invoice_fields,
|
||||
'Sales Order': sales_invoice_fields + invoice_fields,
|
||||
'Delivery Note': sales_invoice_fields + invoice_fields,
|
||||
'Sales Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
|
||||
'POS Invoice Item': invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
|
||||
'Purchase Invoice Item': invoice_item_fields,
|
||||
'Sales Order Item': invoice_item_fields,
|
||||
'Delivery Note Item': invoice_item_fields,
|
||||
'Quotation Item': invoice_item_fields,
|
||||
'Purchase Order Item': invoice_item_fields,
|
||||
'Purchase Receipt Item': invoice_item_fields,
|
||||
'Supplier Quotation Item': invoice_item_fields,
|
||||
"Purchase Invoice": purchase_invoice_fields + invoice_fields,
|
||||
"Purchase Order": purchase_invoice_fields + invoice_fields,
|
||||
"Purchase Receipt": purchase_invoice_fields + invoice_fields,
|
||||
"Sales Invoice": sales_invoice_fields + invoice_fields,
|
||||
"POS Invoice": sales_invoice_fields + invoice_fields,
|
||||
"Sales Order": sales_invoice_fields + invoice_fields,
|
||||
"Delivery Note": sales_invoice_fields + invoice_fields,
|
||||
"Sales Invoice Item": invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
|
||||
"POS Invoice Item": invoice_item_fields + delivery_date_field + [is_zero_rated, is_exempt],
|
||||
"Purchase Invoice Item": invoice_item_fields,
|
||||
"Sales Order Item": invoice_item_fields,
|
||||
"Delivery Note Item": invoice_item_fields,
|
||||
"Quotation Item": invoice_item_fields,
|
||||
"Purchase Order Item": invoice_item_fields,
|
||||
"Purchase Receipt Item": invoice_item_fields,
|
||||
"Supplier Quotation Item": invoice_item_fields,
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields)
|
||||
create_custom_fields(custom_fields, ignore_validate=True)
|
||||
|
||||
|
||||
def add_print_formats():
|
||||
frappe.reload_doc("regional", "print_format", "detailed_tax_invoice")
|
||||
frappe.reload_doc("regional", "print_format", "simplified_tax_invoice")
|
||||
frappe.reload_doc("regional", "print_format", "tax_invoice")
|
||||
|
||||
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
|
||||
name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """)
|
||||
frappe.db.sql(
|
||||
""" update `tabPrint Format` set disabled = 0 where
|
||||
name in('Simplified Tax Invoice', 'Detailed Tax Invoice', 'Tax Invoice') """
|
||||
)
|
||||
|
||||
|
||||
def add_custom_roles_for_reports():
|
||||
"""Add Access Control to UAE VAT 201."""
|
||||
if not frappe.db.get_value('Custom Role', dict(report='UAE VAT 201')):
|
||||
frappe.get_doc(dict(
|
||||
doctype='Custom Role',
|
||||
report='UAE VAT 201',
|
||||
roles= [
|
||||
dict(role='Accounts User'),
|
||||
dict(role='Accounts Manager'),
|
||||
dict(role='Auditor')
|
||||
]
|
||||
)).insert()
|
||||
if not frappe.db.get_value("Custom Role", dict(report="UAE VAT 201")):
|
||||
frappe.get_doc(
|
||||
dict(
|
||||
doctype="Custom Role",
|
||||
report="UAE VAT 201",
|
||||
roles=[dict(role="Accounts User"), dict(role="Accounts Manager"), dict(role="Auditor")],
|
||||
)
|
||||
).insert()
|
||||
|
||||
|
||||
def add_permissions():
|
||||
"""Add Permissions for UAE VAT Settings and UAE VAT Account."""
|
||||
for doctype in ('UAE VAT Settings', 'UAE VAT Account'):
|
||||
add_permission(doctype, 'All', 0)
|
||||
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
||||
for doctype in ("UAE VAT Settings", "UAE VAT Account"):
|
||||
add_permission(doctype, "All", 0)
|
||||
for role in ("Accounts Manager", "Accounts User", "System Manager"):
|
||||
add_permission(doctype, role, 0)
|
||||
update_permission_property(doctype, role, 0, 'write', 1)
|
||||
update_permission_property(doctype, role, 0, 'create', 1)
|
||||
update_permission_property(doctype, role, 0, "write", 1)
|
||||
update_permission_property(doctype, role, 0, "create", 1)
|
||||
|
||||
|
||||
def create_gratuity_rule():
|
||||
rule_1 = rule_2 = rule_3 = None
|
||||
@@ -160,7 +286,11 @@ def create_gratuity_rule():
|
||||
# Rule Under Limited Contract
|
||||
slabs = get_slab_for_limited_contract()
|
||||
if not frappe.db.exists("Gratuity Rule", "Rule Under Limited Contract (UAE)"):
|
||||
rule_1 = get_gratuity_rule("Rule Under Limited Contract (UAE)", slabs, calculate_gratuity_amount_based_on="Sum of all previous slabs")
|
||||
rule_1 = get_gratuity_rule(
|
||||
"Rule Under Limited Contract (UAE)",
|
||||
slabs,
|
||||
calculate_gratuity_amount_based_on="Sum of all previous slabs",
|
||||
)
|
||||
|
||||
# Rule Under Unlimited Contract on termination
|
||||
slabs = get_slab_for_unlimited_contract_on_termination()
|
||||
@@ -172,7 +302,7 @@ def create_gratuity_rule():
|
||||
if not frappe.db.exists("Gratuity Rule", "Rule Under Unlimited Contract on resignation (UAE)"):
|
||||
rule_3 = get_gratuity_rule("Rule Under Unlimited Contract on resignation (UAE)", slabs)
|
||||
|
||||
#for applicable salary component user need to set this by its own
|
||||
# for applicable salary component user need to set this by its own
|
||||
if rule_1:
|
||||
rule_1.flags.ignore_mandatory = True
|
||||
rule_1.save()
|
||||
@@ -185,61 +315,29 @@ def create_gratuity_rule():
|
||||
|
||||
|
||||
def get_slab_for_limited_contract():
|
||||
return [{
|
||||
"from_year": 0,
|
||||
"to_year":1,
|
||||
"fraction_of_applicable_earnings": 0
|
||||
},
|
||||
{
|
||||
"from_year": 1,
|
||||
"to_year":5,
|
||||
"fraction_of_applicable_earnings": 21/30
|
||||
},
|
||||
{
|
||||
"from_year": 5,
|
||||
"to_year":0,
|
||||
"fraction_of_applicable_earnings": 1
|
||||
}]
|
||||
return [
|
||||
{"from_year": 0, "to_year": 1, "fraction_of_applicable_earnings": 0},
|
||||
{"from_year": 1, "to_year": 5, "fraction_of_applicable_earnings": 21 / 30},
|
||||
{"from_year": 5, "to_year": 0, "fraction_of_applicable_earnings": 1},
|
||||
]
|
||||
|
||||
|
||||
def get_slab_for_unlimited_contract_on_termination():
|
||||
return [{
|
||||
"from_year": 0,
|
||||
"to_year":1,
|
||||
"fraction_of_applicable_earnings": 0
|
||||
},
|
||||
{
|
||||
"from_year": 1,
|
||||
"to_year":5,
|
||||
"fraction_of_applicable_earnings": 21/30
|
||||
},
|
||||
{
|
||||
"from_year": 5,
|
||||
"to_year":0,
|
||||
"fraction_of_applicable_earnings": 1
|
||||
}]
|
||||
return [
|
||||
{"from_year": 0, "to_year": 1, "fraction_of_applicable_earnings": 0},
|
||||
{"from_year": 1, "to_year": 5, "fraction_of_applicable_earnings": 21 / 30},
|
||||
{"from_year": 5, "to_year": 0, "fraction_of_applicable_earnings": 1},
|
||||
]
|
||||
|
||||
|
||||
def get_slab_for_unlimited_contract_on_resignation():
|
||||
fraction_1 = 1/3 * 21/30
|
||||
fraction_2 = 2/3 * 21/30
|
||||
fraction_3 = 21/30
|
||||
fraction_1 = 1 / 3 * 21 / 30
|
||||
fraction_2 = 2 / 3 * 21 / 30
|
||||
fraction_3 = 21 / 30
|
||||
|
||||
return [{
|
||||
"from_year": 0,
|
||||
"to_year":1,
|
||||
"fraction_of_applicable_earnings": 0
|
||||
},
|
||||
{
|
||||
"from_year": 1,
|
||||
"to_year":3,
|
||||
"fraction_of_applicable_earnings": fraction_1
|
||||
},
|
||||
{
|
||||
"from_year": 3,
|
||||
"to_year":5,
|
||||
"fraction_of_applicable_earnings": fraction_2
|
||||
},
|
||||
{
|
||||
"from_year": 5,
|
||||
"to_year":0,
|
||||
"fraction_of_applicable_earnings": fraction_3
|
||||
}]
|
||||
return [
|
||||
{"from_year": 0, "to_year": 1, "fraction_of_applicable_earnings": 0},
|
||||
{"from_year": 1, "to_year": 3, "fraction_of_applicable_earnings": fraction_1},
|
||||
{"from_year": 3, "to_year": 5, "fraction_of_applicable_earnings": fraction_2},
|
||||
{"from_year": 5, "to_year": 0, "fraction_of_applicable_earnings": fraction_3},
|
||||
]
|
||||
|
||||
@@ -7,7 +7,8 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax
|
||||
|
||||
|
||||
def update_itemised_tax_data(doc):
|
||||
if not doc.taxes: return
|
||||
if not doc.taxes:
|
||||
return
|
||||
|
||||
itemised_tax = get_itemised_tax(doc.taxes)
|
||||
|
||||
@@ -24,37 +25,39 @@ def update_itemised_tax_data(doc):
|
||||
for account, rate in item_tax_rate.items():
|
||||
tax_rate += rate
|
||||
elif row.item_code and itemised_tax.get(row.item_code):
|
||||
tax_rate = sum([tax.get('tax_rate', 0) for d, tax in itemised_tax.get(row.item_code).items()])
|
||||
tax_rate = sum([tax.get("tax_rate", 0) for d, tax in itemised_tax.get(row.item_code).items()])
|
||||
|
||||
meta = frappe.get_meta(row.doctype)
|
||||
|
||||
if meta.has_field("tax_rate"):
|
||||
row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
|
||||
row.tax_amount = flt((row.net_amount * tax_rate) / 100, row.precision("net_amount"))
|
||||
row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount"))
|
||||
|
||||
row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
|
||||
row.tax_amount = flt((row.net_amount * tax_rate) / 100, row.precision("net_amount"))
|
||||
row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount"))
|
||||
|
||||
def get_account_currency(account):
|
||||
"""Helper function to get account currency."""
|
||||
if not account:
|
||||
return
|
||||
|
||||
def generator():
|
||||
account_currency, company = frappe.get_cached_value(
|
||||
"Account",
|
||||
account,
|
||||
["account_currency",
|
||||
"company"]
|
||||
"Account", account, ["account_currency", "company"]
|
||||
)
|
||||
if not account_currency:
|
||||
account_currency = frappe.get_cached_value('Company', company, "default_currency")
|
||||
account_currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||
|
||||
return account_currency
|
||||
|
||||
return frappe.local_cache("account_currency", account, generator)
|
||||
|
||||
|
||||
def get_tax_accounts(company):
|
||||
"""Get the list of tax accounts for a specific company."""
|
||||
tax_accounts_dict = frappe._dict()
|
||||
tax_accounts_list = frappe.get_all("UAE VAT Account",
|
||||
filters={"parent": company},
|
||||
fields=["Account"]
|
||||
)
|
||||
tax_accounts_list = frappe.get_all(
|
||||
"UAE VAT Account", filters={"parent": company}, fields=["Account"]
|
||||
)
|
||||
|
||||
if not tax_accounts_list and not frappe.flags.in_test:
|
||||
frappe.throw(_('Please set Vat Accounts for Company: "{0}" in UAE VAT Settings').format(company))
|
||||
@@ -64,23 +67,24 @@ def get_tax_accounts(company):
|
||||
|
||||
return tax_accounts_dict
|
||||
|
||||
|
||||
def update_grand_total_for_rcm(doc, method):
|
||||
"""If the Reverse Charge is Applicable subtract the tax amount from the grand total and update in the form."""
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
|
||||
if country != 'United Arab Emirates':
|
||||
if country != "United Arab Emirates":
|
||||
return
|
||||
|
||||
if not doc.total_taxes_and_charges:
|
||||
return
|
||||
|
||||
if doc.reverse_charge == 'Y':
|
||||
if doc.reverse_charge == "Y":
|
||||
tax_accounts = get_tax_accounts(doc.company)
|
||||
|
||||
base_vat_tax = 0
|
||||
vat_tax = 0
|
||||
|
||||
for tax in doc.get('taxes'):
|
||||
for tax in doc.get("taxes"):
|
||||
if tax.category not in ("Total", "Valuation and Total"):
|
||||
continue
|
||||
|
||||
@@ -95,6 +99,7 @@ def update_grand_total_for_rcm(doc, method):
|
||||
|
||||
update_totals(vat_tax, base_vat_tax, doc)
|
||||
|
||||
|
||||
def update_totals(vat_tax, base_vat_tax, doc):
|
||||
"""Update the grand total values in the form."""
|
||||
doc.base_grand_total -= base_vat_tax
|
||||
@@ -106,56 +111,67 @@ def update_totals(vat_tax, base_vat_tax, doc):
|
||||
doc.outstanding_amount = doc.grand_total
|
||||
|
||||
else:
|
||||
doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total,
|
||||
doc.currency, doc.precision("rounded_total"))
|
||||
doc.rounding_adjustment = flt(doc.rounded_total - doc.grand_total,
|
||||
doc.precision("rounding_adjustment"))
|
||||
doc.rounded_total = round_based_on_smallest_currency_fraction(
|
||||
doc.grand_total, doc.currency, doc.precision("rounded_total")
|
||||
)
|
||||
doc.rounding_adjustment = flt(
|
||||
doc.rounded_total - doc.grand_total, doc.precision("rounding_adjustment")
|
||||
)
|
||||
doc.outstanding_amount = doc.rounded_total or doc.grand_total
|
||||
|
||||
doc.in_words = money_in_words(doc.grand_total, doc.currency)
|
||||
doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company))
|
||||
doc.base_in_words = money_in_words(
|
||||
doc.base_grand_total, erpnext.get_company_currency(doc.company)
|
||||
)
|
||||
doc.set_payment_schedule()
|
||||
|
||||
|
||||
def make_regional_gl_entries(gl_entries, doc):
|
||||
"""Hooked to make_regional_gl_entries in Purchase Invoice.It appends the region specific general ledger entries to the list of GL Entries."""
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
|
||||
if country != 'United Arab Emirates':
|
||||
if country != "United Arab Emirates":
|
||||
return gl_entries
|
||||
|
||||
if doc.reverse_charge == 'Y':
|
||||
if doc.reverse_charge == "Y":
|
||||
tax_accounts = get_tax_accounts(doc.company)
|
||||
for tax in doc.get('taxes'):
|
||||
for tax in doc.get("taxes"):
|
||||
if tax.category not in ("Total", "Valuation and Total"):
|
||||
continue
|
||||
gl_entries = make_gl_entry(tax, gl_entries, doc, tax_accounts)
|
||||
return gl_entries
|
||||
|
||||
|
||||
def make_gl_entry(tax, gl_entries, doc, tax_accounts):
|
||||
dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
|
||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts:
|
||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in tax_accounts:
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
|
||||
gl_entries.append(doc.get_gl_dict({
|
||||
"account": tax.account_head,
|
||||
"cost_center": tax.cost_center,
|
||||
"posting_date": doc.posting_date,
|
||||
"against": doc.supplier,
|
||||
dr_or_cr: tax.base_tax_amount_after_discount_amount,
|
||||
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
|
||||
if account_currency==doc.company_currency \
|
||||
else tax.tax_amount_after_discount_amount
|
||||
}, account_currency, item=tax
|
||||
))
|
||||
gl_entries.append(
|
||||
doc.get_gl_dict(
|
||||
{
|
||||
"account": tax.account_head,
|
||||
"cost_center": tax.cost_center,
|
||||
"posting_date": doc.posting_date,
|
||||
"against": doc.supplier,
|
||||
dr_or_cr: tax.base_tax_amount_after_discount_amount,
|
||||
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount
|
||||
if account_currency == doc.company_currency
|
||||
else tax.tax_amount_after_discount_amount,
|
||||
},
|
||||
account_currency,
|
||||
item=tax,
|
||||
)
|
||||
)
|
||||
return gl_entries
|
||||
|
||||
|
||||
def validate_returns(doc, method):
|
||||
"""Standard Rated expenses should not be set when Reverse Charge Applicable is set."""
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
if country != 'United Arab Emirates':
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
if country != "United Arab Emirates":
|
||||
return
|
||||
if doc.reverse_charge == 'Y' and flt(doc.recoverable_standard_rated_expenses) != 0:
|
||||
frappe.throw(_(
|
||||
"Recoverable Standard Rated expenses should not be set when Reverse Charge Applicable is Y"
|
||||
))
|
||||
if doc.reverse_charge == "Y" and flt(doc.recoverable_standard_rated_expenses) != 0:
|
||||
frappe.throw(
|
||||
_("Recoverable Standard Rated expenses should not be set when Reverse Charge Applicable is Y")
|
||||
)
|
||||
|
||||
@@ -7,40 +7,64 @@ import json
|
||||
from frappe.permissions import add_permission, update_permission_property
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
|
||||
def setup(company=None, patch=True):
|
||||
# Company independent fixtures should be called only once at the first company setup
|
||||
if frappe.db.count('Company', {'country': 'United States'}) <=1:
|
||||
if frappe.db.count("Company", {"country": "United States"}) <= 1:
|
||||
setup_company_independent_fixtures(patch=patch)
|
||||
|
||||
|
||||
def setup_company_independent_fixtures(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_print_formats()
|
||||
|
||||
|
||||
def make_custom_fields(update=True):
|
||||
custom_fields = {
|
||||
'Supplier': [
|
||||
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
|
||||
label='Is IRS 1099 reporting required for supplier?')
|
||||
"Supplier": [
|
||||
dict(
|
||||
fieldname="irs_1099",
|
||||
fieldtype="Check",
|
||||
insert_after="tax_id",
|
||||
label="Is IRS 1099 reporting required for supplier?",
|
||||
)
|
||||
],
|
||||
'Sales Order': [
|
||||
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
|
||||
label='Is customer exempted from sales tax?')
|
||||
"Sales Order": [
|
||||
dict(
|
||||
fieldname="exempt_from_sales_tax",
|
||||
fieldtype="Check",
|
||||
insert_after="taxes_and_charges",
|
||||
label="Is customer exempted from sales tax?",
|
||||
)
|
||||
],
|
||||
'Sales Invoice': [
|
||||
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_section',
|
||||
label='Is customer exempted from sales tax?')
|
||||
"Sales Invoice": [
|
||||
dict(
|
||||
fieldname="exempt_from_sales_tax",
|
||||
fieldtype="Check",
|
||||
insert_after="taxes_section",
|
||||
label="Is customer exempted from sales tax?",
|
||||
)
|
||||
],
|
||||
'Customer': [
|
||||
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='represents_company',
|
||||
label='Is customer exempted from sales tax?')
|
||||
"Customer": [
|
||||
dict(
|
||||
fieldname="exempt_from_sales_tax",
|
||||
fieldtype="Check",
|
||||
insert_after="represents_company",
|
||||
label="Is customer exempted from sales tax?",
|
||||
)
|
||||
],
|
||||
"Quotation": [
|
||||
dict(
|
||||
fieldname="exempt_from_sales_tax",
|
||||
fieldtype="Check",
|
||||
insert_after="taxes_and_charges",
|
||||
label="Is customer exempted from sales tax?",
|
||||
)
|
||||
],
|
||||
'Quotation': [
|
||||
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
|
||||
label='Is customer exempted from sales tax?')
|
||||
]
|
||||
}
|
||||
create_custom_fields(custom_fields, update=update)
|
||||
|
||||
|
||||
def add_print_formats():
|
||||
frappe.reload_doc("regional", "print_format", "irs_1099_form")
|
||||
frappe.db.set_value("Print Format", "IRS 1099 Form", "disabled", 0)
|
||||
|
||||
@@ -9,49 +9,51 @@ from erpnext.regional.report.irs_1099.irs_1099 import execute as execute_1099_re
|
||||
|
||||
|
||||
class TestUnitedStates(unittest.TestCase):
|
||||
def test_irs_1099_custom_field(self):
|
||||
def test_irs_1099_custom_field(self):
|
||||
|
||||
if not frappe.db.exists("Supplier", "_US 1099 Test Supplier"):
|
||||
doc = frappe.new_doc("Supplier")
|
||||
doc.supplier_name = "_US 1099 Test Supplier"
|
||||
doc.supplier_group = "Services"
|
||||
doc.supplier_type = "Company"
|
||||
doc.country = "United States"
|
||||
doc.tax_id = "04-1234567"
|
||||
doc.irs_1099 = 1
|
||||
doc.save()
|
||||
frappe.db.commit()
|
||||
supplier = frappe.get_doc('Supplier', "_US 1099 Test Supplier")
|
||||
self.assertEqual(supplier.irs_1099, 1)
|
||||
if not frappe.db.exists("Supplier", "_US 1099 Test Supplier"):
|
||||
doc = frappe.new_doc("Supplier")
|
||||
doc.supplier_name = "_US 1099 Test Supplier"
|
||||
doc.supplier_group = "Services"
|
||||
doc.supplier_type = "Company"
|
||||
doc.country = "United States"
|
||||
doc.tax_id = "04-1234567"
|
||||
doc.irs_1099 = 1
|
||||
doc.save()
|
||||
frappe.db.commit()
|
||||
supplier = frappe.get_doc("Supplier", "_US 1099 Test Supplier")
|
||||
self.assertEqual(supplier.irs_1099, 1)
|
||||
|
||||
def test_irs_1099_report(self):
|
||||
make_payment_entry_to_irs_1099_supplier()
|
||||
filters = frappe._dict({"fiscal_year": "_Test Fiscal Year 2016", "company": "_Test Company 1"})
|
||||
columns, data = execute_1099_report(filters)
|
||||
expected_row = {'supplier': '_US 1099 Test Supplier',
|
||||
'supplier_group': 'Services',
|
||||
'payments': 100.0,
|
||||
'tax_id': '04-1234567'}
|
||||
self.assertEqual(data[0], expected_row)
|
||||
def test_irs_1099_report(self):
|
||||
make_payment_entry_to_irs_1099_supplier()
|
||||
filters = frappe._dict({"fiscal_year": "_Test Fiscal Year 2016", "company": "_Test Company 1"})
|
||||
columns, data = execute_1099_report(filters)
|
||||
expected_row = {
|
||||
"supplier": "_US 1099 Test Supplier",
|
||||
"supplier_group": "Services",
|
||||
"payments": 100.0,
|
||||
"tax_id": "04-1234567",
|
||||
}
|
||||
self.assertEqual(data[0], expected_row)
|
||||
|
||||
|
||||
def make_payment_entry_to_irs_1099_supplier():
|
||||
|
||||
frappe.db.sql("delete from `tabGL Entry` where party='_US 1099 Test Supplier'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where against='_US 1099 Test Supplier'")
|
||||
frappe.db.sql("delete from `tabPayment Entry` where party='_US 1099 Test Supplier'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where party='_US 1099 Test Supplier'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where against='_US 1099 Test Supplier'")
|
||||
frappe.db.sql("delete from `tabPayment Entry` where party='_US 1099 Test Supplier'")
|
||||
|
||||
pe = frappe.new_doc("Payment Entry")
|
||||
pe.payment_type = "Pay"
|
||||
pe.company = "_Test Company 1"
|
||||
pe.posting_date = "2016-01-10"
|
||||
pe.paid_from = "_Test Bank USD - _TC1"
|
||||
pe.paid_to = "_Test Payable USD - _TC1"
|
||||
pe.paid_amount = 100
|
||||
pe.received_amount = 100
|
||||
pe.reference_no = "For IRS 1099 testing"
|
||||
pe.reference_date = "2016-01-10"
|
||||
pe.party_type = "Supplier"
|
||||
pe.party = "_US 1099 Test Supplier"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
pe = frappe.new_doc("Payment Entry")
|
||||
pe.payment_type = "Pay"
|
||||
pe.company = "_Test Company 1"
|
||||
pe.posting_date = "2016-01-10"
|
||||
pe.paid_from = "_Test Bank USD - _TC1"
|
||||
pe.paid_to = "_Test Payable USD - _TC1"
|
||||
pe.paid_amount = 100
|
||||
pe.received_amount = 100
|
||||
pe.reference_no = "For IRS 1099 testing"
|
||||
pe.reference_date = "2016-01-10"
|
||||
pe.party_type = "Supplier"
|
||||
pe.party = "_US 1099 Test Supplier"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
Reference in New Issue
Block a user