Merge remote-tracking branch 'upstream/develop' into remove-india

This commit is contained in:
Sagar Vora
2022-06-19 22:57:22 +05:30
2017 changed files with 248759 additions and 94060 deletions

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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");
}
});

View File

@@ -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
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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"
}

View File

@@ -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

View File

@@ -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('&amp', '&', args.supplier)
new_supplier.supplier_name = re.sub("&amp", "&", 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:

View File

@@ -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()

View File

@@ -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"

View File

@@ -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()

View File

@@ -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

View File

@@ -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'

View File

@@ -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 darte, dantiquariato o da collezione (art.36, DL 41/95)",
"RF15-Agenzie di vendite allasta di oggetti darte, 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 darte, dantiquariato o da collezione (art.36, DL 41/95)",
"RF15-Agenzie di vendite allasta di oggetti darte, 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",
}

View File

@@ -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)

View File

@@ -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("&", "&amp;")
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)

View File

@@ -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);
});
}
};

View File

@@ -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": []
}

View File

@@ -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)
},
])

View File

@@ -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))

View File

@@ -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)

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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},
]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -1,2 +1,2 @@
def setup(company=None, patch=True):
pass
pass

View File

@@ -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},
]

View File

@@ -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")
)

View File

@@ -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)

View File

@@ -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()