mod(regional,italy): Updates to Italian Localization

fixes:
removed cleanup_files from italy/utils
removed extract_doc_number from italy/utils
added country filter to italian localization patch
replaced dict assignment with dot operator in prepare_invoice
correcions in e-invoice xml following changes in prepare_invoice
added setup_report in patch
added section for e-invoicing custom fields in company
added Italy in test_company
This commit is contained in:
Gaurav 2019-02-13 16:46:24 +05:30
parent a4aa80fe05
commit f1e28e0e8d
14 changed files with 747 additions and 16 deletions

View File

@ -24,6 +24,7 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
from erpnext.accounts.deferred_revenue import validate_service_stop_date
from erpnext.controllers.accounts_controller import on_submit_regional, on_cancel_regional
from erpnext.healthcare.utils import manage_invoice_submit_cancel
@ -198,6 +199,8 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_submit")
on_submit_regional(self)
def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice."))
@ -253,6 +256,8 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_cancel")
on_cancel_regional(self)
def update_status_updater_args(self):
if cint(self.update_stock):
self.status_updater.extend([{

View File

@ -94,6 +94,8 @@ class AccountsController(TransactionBase):
if self.is_return:
self.validate_qty()
validate_regional(self)
def validate_invoice_documents_schedule(self):
self.validate_payment_schedule_dates()
self.set_due_date()
@ -1132,3 +1134,15 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name):
p_doctype.update_blanket_order()
p_doctype.update_billing_percentage()
p_doctype.set_status()
@erpnext.allow_regional
def validate_regional(doc):
pass
@erpnext.allow_regional
def on_submit_regional(doc):
pass
@erpnext.allow_regional
def on_cancel_regional(doc):
pass

View File

@ -301,5 +301,11 @@ regional_overrides = {
},
'Saudi Arabia': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data'
},
'Italy': {
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data',
'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate',
'erpnext.controllers.accounts_controller.on_submit_regional': 'erpnext.regional.italy.utils.sales_invoice_on_submit',
'erpnext.controllers.accounts_controller.on_cancel_regional': 'erpnext.regional.italy.utils.sales_invoice_on_cancel'
}
}

View File

@ -585,4 +585,5 @@ erpnext.patches.v11_0.renamed_from_to_fields_in_project
erpnext.patches.v11_0.add_permissions_in_gst_settings
erpnext.patches.v11_1.setup_guardian_role
execute:frappe.delete_doc('DocType', 'Notification Control')
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
erpnext.patches.v11_0.make_italian_localization_fields

View File

@ -0,0 +1,14 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from erpnext.regional.italy.setup import make_custom_fields, setup_report
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'Italy'})
if not company:
return
make_custom_fields()
setup_report()

View File

@ -30,3 +30,34 @@ tax_exemption_reasons = [
"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"
]
vat_collectability_options = [
"I-Immediata",
"D-Differita",
"S-Scissione dei Pagamenti"
]

View File

@ -0,0 +1,208 @@
{%- macro format_float(value) -%}
{{ "%.2f" % value|abs }}
{%- endmacro -%}
{%- macro render_address(address) %}
<Indirizzo>{{ address.address_line1 }}</Indirizzo>
<CAP>{{ address.pincode }}</CAP>
<Comune>{{ address.city }}</Comune>
{%- if address.state %}
<Provincia>{{ address.state }}</Provincia>
{%- endif %}
<Nazione>{{ address.country_code|upper }}</Nazione>
{%- endmacro %}
{%- macro render_discount_or_margin(item) -%}
{%- if item.discount_percentage > 0.0 or item.margin_type %}
<ScontoMaggiorazione>
{%- if item.discount_percentage > 0.0 %}
<Tipo>SC</Tipo>
<Percentuale>{{ format_float(item.discount_percentage) }}</Percentuale>
{%- endif %}
{%- if item.margin_rate_or_amount > 0.0 -%}
<Tipo>MG</Tipo>
{%- if item.margin_type == "Percentage" -%}
<Percentuale>{{ format_float(item.margin_rate_or_amount) }}</Percentuale>
{%- elif item.margin_type == "Amount" -%}
<Importo>{{ format_float(item.margin_rate_or_amount) }}</Importo>
{%- endif -%}
{%- endif %}
</ScontoMaggiorazione>
{%- endif -%}
{%- endmacro -%}
<?xml version='1.0' encoding='UTF-8'?>
<p:FatturaElettronica xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:p="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
versione="{{ doc.transmission_format_code }}"
xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1.2 http://www.fatturapa.gov.it/export/fatturazione/sdi/fatturapa/v1.2/Schema_del_file_xml_FatturaPA_versione_1.2.xsd">
<FatturaElettronicaHeader>
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>{{ doc.company_address_data.country_code|upper or "IT" }}</IdPaese>
<IdCodice>{{ doc.company_fiscal_code or doc.company_tax_id | replace("IT","") }}</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>{{ doc.progressive_number }}</ProgressivoInvio>
<FormatoTrasmissione>{{ doc.transmission_format_code }}</FormatoTrasmissione>
<CodiceDestinatario>{{ doc.customer_data.recipient_code }}</CodiceDestinatario>
{% if doc.company_data.phone or doc.company_data.email -%}
<ContattiTrasmittente>
{% if doc.company_data.phone -%}<Telefono>{{ doc.company_data.phone }}</Telefono>{%- endif %}
{% if doc.company_data.email -%}<Email>{{ doc.company_data.email }}</Email>{%- endif %}
</ContattiTrasmittente>
{% endif -%}
</DatiTrasmissione>
<CedentePrestatore>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>{{ doc.company_address_data.country_code|upper or "IT"}}</IdPaese>
<IdCodice>{{ doc.company_tax_id | replace("IT","") }}</IdCodice>
</IdFiscaleIVA>
{%- if doc.company_fiscal_code %}
<CodiceFiscale>{{ doc.company_fiscal_code }}</CodiceFiscale>
{%- endif %}
<Anagrafica>
<Denominazione>{{ doc.company }}</Denominazione>
</Anagrafica>
<RegimeFiscale>{{ doc.company_fiscal_regime.split("-")[0] }}</RegimeFiscale>
</DatiAnagrafici>
<Sede>
{{ render_address(doc.company_address_data) }}
</Sede>
{%- if doc.company_data.registration_number %}
<IscrizioneREA>
<Ufficio>{{ doc.company_data.registrar_office_province }}</Ufficio>
<NumeroREA>{{ doc.company_data.registration_number }}</NumeroREA>
{%- if doc.company_data.share_capital_amount %}
<CapitaleSociale>{{ format_float(doc.company_data.share_capital_amount) }}</CapitaleSociale>
{%- endif %}
{%- if doc.company_data.no_of_members %}
<SocioUnico>{{ doc.company_data.no_of_members.split("-")[0] }}</SocioUnico>
{%- endif %}
{%- if doc.company_data.liquidation_state %}
<StatoLiquidazione>{{ doc.company_data.liquidation_state.split("-")[0] }}</StatoLiquidazione>
{%- endif %}
</IscrizioneREA>
{%- endif %}
</CedentePrestatore>
<CessionarioCommittente>
<DatiAnagrafici>
{%- if doc.customer_data.customer_type == "Individual" %}
<CodiceFiscale>{{ doc.customer_data.fiscal_code }}</CodiceFiscale>
<Anagrafica>
<Nome>{{ doc.customer_data.first_name }}</Nome>
<Cognome>{{ doc.customer_data.last_name }}</Cognome>
</Anagrafica>
{%- else %}
{%- if doc.customer_data.is_public_administration %}
<CodiceFiscale>{{ doc.customer_data.fiscal_code }}</CodiceFiscale>
{%- else %}
<IdFiscaleIVA>
<IdPaese>{{ doc.customer_address_data.country_code|upper or "IT" }}</IdPaese>
<IdCodice>{{ doc.tax_id | replace("IT","") }}</IdCodice>
</IdFiscaleIVA>
{%- endif %}
<Anagrafica>
<Denominazione>{{ doc.customer_name }}</Denominazione>
</Anagrafica>
{%- endif %}
</DatiAnagrafici>
{%- if doc.customer_address_data %}
<Sede>
{{ render_address(doc.customer_address_data) }}
</Sede>
{%- endif %}
</CessionarioCommittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody>
<DatiGenerali>
<DatiGeneraliDocumento>
<TipoDocumento>{{ doc.type_of_document }}</TipoDocumento>
<Divisa>EUR</Divisa>
<Data>{{ doc.posting_date }}</Data>
<Numero>{{ doc.unamended_name }}</Numero>
{%- if doc.stamp_duty %}
<DatiBollo>
<BolloVirtuale>SI</BolloVirtuale>
<ImportoBollo>{{ format_float(doc.stamp_duty) }}</ImportoBollo>
</DatiBollo>
{%- endif %}
<ImportoTotaleDocumento>{{ format_float(doc.grand_total) }}</ImportoTotaleDocumento>
<Causale>VENDITA</Causale>
</DatiGeneraliDocumento>
{%- if doc.po_no %}
<DatiOrdineAcquisto>
<IdDocumento>{{ doc.po_no }}</IdDocumento>
{%- if doc.po_date %}
<Data>{{ doc.po_date }}</Data>
{%- endif %}
</DatiOrdineAcquisto>
{%- endif %}
{%- if doc.is_return and doc.return_against_unamended %}
<DatiFattureCollegate>
<IdDocumento>{{ doc.return_against_unamended }}</IdDocumento>
</DatiFattureCollegate>
{%- endif %}
{%- if doc.shipping_address_data %}
<DatiTrasporto>
<IndirizzoResa>
{{ render_address(doc.shipping_address_data) }}
</IndirizzoResa>
</DatiTrasporto>
{%- endif %}
</DatiGenerali>
<DatiBeniServizi>
{%- for item in doc.items %}
<DettaglioLinee>
<NumeroLinea>{{ item.idx }}</NumeroLinea>
<CodiceArticolo>
<CodiceTipo>CODICE</CodiceTipo>
<CodiceValore>{{ item.item_code }}</CodiceValore>
</CodiceArticolo>
<Descrizione>{{ item.description or item.item_name }}</Descrizione>
<Quantita>{{ format_float(item.qty) }}</Quantita>
<UnitaMisura>{{ item.stock_uom }}</UnitaMisura>
<PrezzoUnitario>{{ format_float(item.price_list_rate or item.rate) }}</PrezzoUnitario>
{{ render_discount_or_margin(item) }}
<PrezzoTotale>{{ format_float(item.amount) }}</PrezzoTotale>
<AliquotaIVA>{{ format_float(item.tax_rate) }}</AliquotaIVA>
{%- if item.tax_exemption_reason %}
<Natura>{{ item.tax_exemption_reason.split("-")[0] }}</Natura>
{%- endif %}
</DettaglioLinee>
{%- endfor %}
{%- for tax, data in doc.tax_data.items() %}
<DatiRiepilogo>
<AliquotaIVA>{{ format_float(tax|flt) }}</AliquotaIVA>
{%- if data.tax_exemption_reason %}
<Natura>{{ data.tax_exemption_reason.split("-")[0] }}</Natura>
{%- endif %}
<ImponibileImporto>{{ format_float(data.taxable_amount) }}</ImponibileImporto>
<Imposta>{{ format_float(data.tax_amount) }}</Imposta>
<EsigibilitaIVA>{{ doc.vat_collectability.split("-")[0] }}</EsigibilitaIVA>
{%- if data.tax_exemption_law %}
<RiferimentoNormativo>{{ data.tax_exemption_law }}</RiferimentoNormativo>
{%- endif %}
</DatiRiepilogo>
{%- endfor %}
</DatiBeniServizi>
{%- if doc.payment_schedule %}
<DatiPagamento>
{%- if payment_schedule|length > 1 %}
<CondizioniPagamento>TP01</CondizioniPagamento>
{%- else %}
<CondizioniPagamento>TP02</CondizioniPagamento>
{%- endif %}
{%- for payment_term in doc.payment_schedule %}
<DettaglioPagamento>
<ModalitaPagamento>{{ payment_term.mode_of_payment_code.split("-")[0] }}</ModalitaPagamento>
<DataScadenzaPagamento>{{ payment_term.due_date }}</DataScadenzaPagamento>
<ImportoPagamento>{{ format_float(payment_term.payment_amount) }}</ImportoPagamento>
{%- if payment_term.bank_account_iban %}<IBAN>{{ payment_term.bank_account_iban }}</IBAN>{%- endif %}
</DettaglioPagamento>
{%- endfor %}
</DatiPagamento>
{%- endif %}
</FatturaElettronicaBody>
</p:FatturaElettronica>

View File

@ -5,33 +5,152 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.regional.italy import fiscal_regimes, tax_exemption_reasons
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()
make_custom_fields()
setup_report()
def make_custom_fields(update=True):
fiscal_code_field = dict(fieldname='fiscal_code', label='Fiscal Code', fieldtype='Data', insert_after='tax_id', print_hide=1)
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")
]
custom_fields = {
'Company': [
fiscal_code_field,
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='fiscal_code', print_hide=1,
options="\n".join(map(lambda x: x.decode('utf-8'), fiscal_regimes)))
],
'Customer': [
fiscal_code_field,
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)
fieldtype='Select', insert_after='sb_e_invoicing', print_hide=1,
options="\n".join(map(lambda x: x.decode('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: x.decode('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,
depends_on='eval:doc.charge_type!="Actual" && doc.rate==0.0',
options="\n" + "\n".join(map(lambda x: x.decode('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,
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"')
],
'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: x.decode('utf-8'), tax_exemption_reasons)))
options="\n".join(map(lambda x: x.decode('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: x.decode('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 Account Name',
fieldtype='Data', insert_after='bank_account', print_hide=1,
fetch_from="bank_account.account_name", 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),
],
"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: x.decode('utf-8'), vat_collectability_options)),
fetch_from="company.vat_collectability"),
dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing',
fieldtype='Section Break', insert_after='pos_total_qty', print_hide=1),
dict(fieldname='company_tax_id', label='Company Tax ID',
fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
fetch_from="company.tax_id"),
dict(fieldname='company_fiscal_code', label='Company Fiscal Code',
fieldtype='Data', insert_after='company_tax_id', 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"),
],
'Purchase Invoice Item': invoice_item_fields,
'Sales Order Item': invoice_item_fields,
'Delivery Note Item': invoice_item_fields,
'Sales Invoice 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,
'Address': [
dict(fieldname='country_code', label='Country Code',
fieldtype='Data', insert_after='country', print_hide=1, read_only=1,
fetch_from="country.code")
]
}
create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=update)
def setup_report():
report_name = 'Electronic Invoice Register'
frappe.db.sql(""" update `tabReport` set disabled = 0 where
name = %s """, report_name)
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()

View File

@ -0,0 +1,251 @@
import frappe, json, os
from frappe.utils import flt
from erpnext.controllers.taxes_and_totals import get_itemised_tax
from frappe import _
from frappe.utils.file_manager import save_file, remove_file
from frappe.desk.form.load import get_attachments
def update_itemised_tax_data(doc):
if not doc.taxes: 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()])
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):
saved_xmls = []
invoices = frappe.get_all("Sales Invoice", filters=get_conditions(filters), fields=["*"])
for invoice in invoices:
attachments = get_e_invoice_attachments(invoice)
saved_xmls += [attachment.file_name for attachment in attachments]
zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
download_zip(saved_xmls, zip_filename)
@frappe.whitelist()
def prepare_invoice(invoice, progressive_number):
#set company information
company = frappe.get_doc("Company", invoice.company)
invoice.progressive_number = progressive_number
invoice.unamended_name = get_unamended_name(invoice)
invoice.company_data = company
company_address = frappe.get_doc("Address", invoice.company_address)
invoice.company_address_data = company_address
#Set invoice type
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))
else:
invoice.type_of_document = "TD01" #Sales Invoice (Fattura)
#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
if invoice.shipping_address_name:
invoice.shipping_address_data = frappe.get_doc("Address", invoice.shipping_address_name)
if invoice.customer_data.is_public_administration:
invoice.transmission_format_code = "FPA12"
else:
invoice.transmission_format_code = "FPR12"
tax_data = get_invoice_summary(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)
if stamp_duty_charge_row:
invoice.stamp_duty = stamp_duty_charge_row.tax_amount
for item in invoice.items:
if item.tax_rate == 0.0:
item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"]
return invoice
def get_conditions(filters):
filters = json.loads(filters)
conditions = {"docstatus": 1}
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") and filters.get("to_date"):
conditions["posting_date"] = ("between", [filters.get("from_date"), filters.get("to_date")])
return conditions
#TODO: Use function from frappe once PR #6853 is merged.
def download_zip(files, output_filename):
from zipfile import ZipFile
input_files = [frappe.get_site_path('private', 'files', filename) for filename in files]
output_path = frappe.get_site_path('private', 'files', output_filename)
with ZipFile(output_path, 'w') as output_zip:
for input_file in input_files:
output_zip.write(input_file, arcname=os.path.basename(input_file))
with open(output_path, 'rb') as fileobj:
filedata = fileobj.read()
frappe.local.response.filename = output_filename
frappe.local.response.filecontent = filedata
frappe.local.response.type = "download"
def get_invoice_summary(items, taxes):
summary_data = frappe._dict()
for tax in taxes:
#Include only VAT charges.
if tax.charge_type == "Actual":
continue
#Check item tax rates if tax rate is zero.
if tax.rate == 0:
for item in items:
item_tax_rate = json.loads(item.item_tax_rate)
if tax.account_head in item_tax_rate:
key = str(item_tax_rate[tax.account_head])
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
if key == "0.0":
summary_data[key]["tax_exemption_reason"] = tax.tax_exemption_reason
summary_data[key]["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]:
key = str(tax.rate)
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]])
return summary_data
#Preflight for successful e-invoice export.
def sales_invoice_validate(doc):
#Validate company
if not doc.company_address:
frappe.throw(_("Please set an Address on the Company '%s'" % doc.company), title=_("E-Invoicing Information Missing"))
else:
validate_address(doc.company_address, "Company")
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"))
#Validate customer details
customer_type, is_public_administration = frappe.db.get_value("Customer", doc.customer, ["customer_type", "is_public_administration"])
if customer_type == _("Individual"):
if not doc.customer_fiscal_code:
frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else:
if is_public_administration:
if not doc.customer_fiscal_code:
frappe.throw(_("Please set Fiscal Code for the public administration '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))
else:
if not doc.tax_id:
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"))
else:
validate_address(doc.customer_address, "Customer")
if not len(doc.taxes):
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"))
#Ensure payment details are valid for e-invoice.
def sales_invoice_on_submit(doc):
#Validate payment details
if not len(doc.payment_schedule):
frappe.throw(_("Please set the Payment Schedule"), title=_("E-Invoicing Information Missing"))
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"))
prepare_and_attach_invoice(doc)
def prepare_and_attach_invoice(doc):
progressive_name, progressive_number = get_progressive_name_and_number(doc)
invoice = prepare_invoice(doc, progressive_number)
invoice_xml = frappe.render_template('erpnext/regional/italy/e-invoice.xml', context={"doc": invoice}, is_path=True)
xml_filename = progressive_name + ".xml"
save_file(xml_filename, invoice_xml, dt=doc.doctype, dn=doc.name, is_private=True)
#Delete e-invoice attachment on cancel.
def sales_invoice_on_cancel(doc):
for attachment in get_e_invoice_attachments(doc):
remove_file(attachment.name, attached_to_doctype=doc.doctype, attached_to_name=doc.name)
def get_e_invoice_attachments(invoice):
out = []
attachments = get_attachments(invoice.doctype, invoice.name)
company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id
for attachment in attachments:
if attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"):
out.append(attachment)
return out
def validate_address(address_name, address_context):
pincode, city = frappe.db.get_value("Address", address_name, ["pincode", "city"])
if not pincode:
frappe.throw(_("Please set pin code on %s Address" % address_context), title=_("E-Invoicing Information Missing"))
if not city:
frappe.throw(_("Please set city on %s Address" % address_context), title=_("E-Invoicing Information Missing"))
def get_unamended_name(doc):
attributes = ["naming_series", "amended_from"]
for attribute in attributes:
if not hasattr(doc, attribute):
return doc.name
if doc.amended_from:
return "-".join(doc.name.split("-")[:-1])
else:
return doc.name
def get_progressive_name_and_number(doc):
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

View File

@ -0,0 +1,53 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Electronic Invoice Register"] = {
"filters": [
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
"width": "80"
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.datetime.get_today()
},
{
"fieldname":"customer",
"label": __("Customer"),
"fieldtype": "Link",
"options": "Customer"
},
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company")
},
],
"onload": function(reportview) {
reportview.page.add_inner_button(__("Export E-Invoices"), function() {
//TODO: refactor condition to disallow export if report has no data.
if (!reportview.data.length) {
frappe.msgprint(__("No data to export"));
return
}
var w = window.open(
frappe.urllib.get_full_url(
"/api/method/erpnext.regional.italy.utils.export_invoices?"
+ "filters=" + JSON.stringify(reportview.get_filter_values())
)
);
if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return;
}
})
}
}

View File

@ -0,0 +1,21 @@
{
"add_total_row": 0,
"color": "green",
"creation": "2019-01-13 17:43:21.903589",
"disabled": 1,
"docstatus": 0,
"doctype": "Report",
"icon": "fa fa-file-code-o",
"idx": 0,
"is_standard": "Yes",
"modified": "2019-01-13 19:03:56.187748",
"modified_by": "Administrator",
"module": "Regional",
"name": "Electronic Invoice Register",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Sales Invoice",
"report_name": "Electronic Invoice Register",
"report_type": "Script Report",
"roles": []
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from erpnext.accounts.report.sales_register.sales_register import _execute
def execute(filters=None):
return _execute(filters)

View File

@ -46,7 +46,7 @@ class TestCompany(unittest.TestCase):
def test_coa_based_on_country_template(self):
countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France",
"Guatemala", "Indonesia", "Mexico", "Nicaragua", "Netherlands", "Singapore",
"Guatemala", "Indonesia", "Italy", "Mexico", "Nicaragua", "Netherlands", "Singapore",
"Brazil", "Argentina", "Hungary", "Taiwan"]
for country in countries: