blob: f5b2e2d96b889f2e96d53c4286165a4684a1f3b5 [file] [log] [blame]
Sagar Voraba76f872021-03-29 20:18:45 +05301import io
2import json
Chillar Anand915b3432021-09-02 16:44:59 +05303
Sagar Voraba76f872021-03-29 20:18:45 +05304import frappe
Gauravf1e28e02019-02-13 16:46:24 +05305from frappe import _
Chillar Anand915b3432021-09-02 16:44:59 +05306from frappe.utils import cstr, flt
Saqib6cf92542021-08-24 15:26:44 +05307from frappe.utils.file_manager import remove_file
Chillar Anand915b3432021-09-02 16:44:59 +05308
9from erpnext.controllers.taxes_and_totals import get_itemised_tax
Gaurav3f046132019-02-19 16:28:22 +053010from erpnext.regional.italy import state_codes
Gauravf1e28e02019-02-13 16:46:24 +053011
12
13def update_itemised_tax_data(doc):
Ankush Menat494bd9e2022-03-28 18:52:46 +053014 if not doc.taxes:
15 return
Gauravf1e28e02019-02-13 16:46:24 +053016
Ankush Menat494bd9e2022-03-28 18:52:46 +053017 if doc.doctype == "Purchase Invoice":
18 return
hello@openetech.com70214022019-10-16 23:38:51 +053019
Gauravf1e28e02019-02-13 16:46:24 +053020 itemised_tax = get_itemised_tax(doc.taxes)
21
22 for row in doc.items:
23 tax_rate = 0.0
24 if itemised_tax.get(row.item_code):
Ankush Menat494bd9e2022-03-28 18:52:46 +053025 tax_rate = sum([tax.get("tax_rate", 0) for d, tax in itemised_tax.get(row.item_code).items()])
Gauravf1e28e02019-02-13 16:46:24 +053026
27 row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
28 row.tax_amount = flt((row.net_amount * tax_rate) / 100, row.precision("net_amount"))
29 row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount"))
30
Ankush Menat494bd9e2022-03-28 18:52:46 +053031
Gauravf1e28e02019-02-13 16:46:24 +053032@frappe.whitelist()
33def export_invoices(filters=None):
Ankush Menat494bd9e2022-03-28 18:52:46 +053034 frappe.has_permission("Sales Invoice", throw=True)
Gauravf1e28e02019-02-13 16:46:24 +053035
Sagar Voraba76f872021-03-29 20:18:45 +053036 invoices = frappe.get_all(
Ankush Menat494bd9e2022-03-28 18:52:46 +053037 "Sales Invoice", filters=get_conditions(filters), fields=["name", "company_tax_id"]
Sagar Voraba76f872021-03-29 20:18:45 +053038 )
Gauravf1e28e02019-02-13 16:46:24 +053039
Sagar Voraba76f872021-03-29 20:18:45 +053040 attachments = get_e_invoice_attachments(invoices)
Gauravf1e28e02019-02-13 16:46:24 +053041
Ankush Menat494bd9e2022-03-28 18:52:46 +053042 zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
Gauravf1e28e02019-02-13 16:46:24 +053043
Sagar Voraba76f872021-03-29 20:18:45 +053044 download_zip(attachments, zip_filename)
Gauravf1e28e02019-02-13 16:46:24 +053045
46
Gauravf1e28e02019-02-13 16:46:24 +053047def prepare_invoice(invoice, progressive_number):
Ankush Menat494bd9e2022-03-28 18:52:46 +053048 # set company information
Gauravf1e28e02019-02-13 16:46:24 +053049 company = frappe.get_doc("Company", invoice.company)
50
51 invoice.progressive_number = progressive_number
52 invoice.unamended_name = get_unamended_name(invoice)
53 invoice.company_data = company
54 company_address = frappe.get_doc("Address", invoice.company_address)
55 invoice.company_address_data = company_address
56
Ankush Menat494bd9e2022-03-28 18:52:46 +053057 # Set invoice type
Saqib Ansari032246d2021-04-09 12:08:24 +053058 if not invoice.type_of_document:
59 if invoice.is_return and invoice.return_against:
Ankush Menat494bd9e2022-03-28 18:52:46 +053060 invoice.type_of_document = "TD04" # Credit Note (Nota di Credito)
61 invoice.return_against_unamended = get_unamended_name(
62 frappe.get_doc("Sales Invoice", invoice.return_against)
63 )
Saqib Ansari032246d2021-04-09 12:08:24 +053064 else:
Ankush Menat494bd9e2022-03-28 18:52:46 +053065 invoice.type_of_document = "TD01" # Sales Invoice (Fattura)
Gauravf1e28e02019-02-13 16:46:24 +053066
Ankush Menat494bd9e2022-03-28 18:52:46 +053067 # set customer information
Gauravf1e28e02019-02-13 16:46:24 +053068 invoice.customer_data = frappe.get_doc("Customer", invoice.customer)
69 customer_address = frappe.get_doc("Address", invoice.customer_address)
70 invoice.customer_address_data = customer_address
71
72 if invoice.shipping_address_name:
73 invoice.shipping_address_data = frappe.get_doc("Address", invoice.shipping_address_name)
74
75 if invoice.customer_data.is_public_administration:
76 invoice.transmission_format_code = "FPA12"
77 else:
78 invoice.transmission_format_code = "FPR12"
79
Gaurav2670ad72019-02-19 10:17:17 +053080 invoice.e_invoice_items = [item for item in invoice.items]
81 tax_data = get_invoice_summary(invoice.e_invoice_items, invoice.taxes)
Gauravf1e28e02019-02-13 16:46:24 +053082 invoice.tax_data = tax_data
83
Ankush Menat494bd9e2022-03-28 18:52:46 +053084 # Check if stamp duty (Bollo) of 2 EUR exists.
85 stamp_duty_charge_row = next(
86 (tax for tax in invoice.taxes if tax.charge_type == "Actual" and tax.tax_amount == 2.0), None
87 )
Gauravf1e28e02019-02-13 16:46:24 +053088 if stamp_duty_charge_row:
89 invoice.stamp_duty = stamp_duty_charge_row.tax_amount
90
Gaurav2670ad72019-02-19 10:17:17 +053091 for item in invoice.e_invoice_items:
Nabin Hait34c551d2019-07-03 10:34:31 +053092 if item.tax_rate == 0.0 and item.tax_amount == 0.0 and tax_data.get("0.0"):
Gauravf1e28e02019-02-13 16:46:24 +053093 item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"]
94
Rohit Waghchaure1b7059b2019-03-12 17:44:29 +053095 customer_po_data = {}
96 for d in invoice.e_invoice_items:
Ankush Menat494bd9e2022-03-28 18:52:46 +053097 if d.customer_po_no and d.customer_po_date and d.customer_po_no not in customer_po_data:
Rohit Waghchaure1b7059b2019-03-12 17:44:29 +053098 customer_po_data[d.customer_po_no] = d.customer_po_date
99
100 invoice.customer_po_data = customer_po_data
101
Gauravf1e28e02019-02-13 16:46:24 +0530102 return invoice
103
Ankush Menat494bd9e2022-03-28 18:52:46 +0530104
Gauravf1e28e02019-02-13 16:46:24 +0530105def get_conditions(filters):
106 filters = json.loads(filters)
107
Sagar Voraba76f872021-03-29 20:18:45 +0530108 conditions = {"docstatus": 1, "company_tax_id": ("!=", "")}
Gauravf1e28e02019-02-13 16:46:24 +0530109
Ankush Menat494bd9e2022-03-28 18:52:46 +0530110 if filters.get("company"):
111 conditions["company"] = filters["company"]
112 if filters.get("customer"):
113 conditions["customer"] = filters["customer"]
Gauravf1e28e02019-02-13 16:46:24 +0530114
Ankush Menat494bd9e2022-03-28 18:52:46 +0530115 if filters.get("from_date"):
116 conditions["posting_date"] = (">=", filters["from_date"])
117 if filters.get("to_date"):
118 conditions["posting_date"] = ("<=", filters["to_date"])
Gauravf1e28e02019-02-13 16:46:24 +0530119
120 if filters.get("from_date") and filters.get("to_date"):
121 conditions["posting_date"] = ("between", [filters.get("from_date"), filters.get("to_date")])
122
123 return conditions
124
Sagar Voraba76f872021-03-29 20:18:45 +0530125
Gauravf1e28e02019-02-13 16:46:24 +0530126def download_zip(files, output_filename):
Sagar Voraba76f872021-03-29 20:18:45 +0530127 import zipfile
Gauravf1e28e02019-02-13 16:46:24 +0530128
Sagar Voraba76f872021-03-29 20:18:45 +0530129 zip_stream = io.BytesIO()
Ankush Menat494bd9e2022-03-28 18:52:46 +0530130 with zipfile.ZipFile(zip_stream, "w", zipfile.ZIP_DEFLATED) as zip_file:
Sagar Voraba76f872021-03-29 20:18:45 +0530131 for file in files:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530132 file_path = frappe.utils.get_files_path(file.file_name, is_private=file.is_private)
Gauravf1e28e02019-02-13 16:46:24 +0530133
Sagar Voraba76f872021-03-29 20:18:45 +0530134 zip_file.write(file_path, arcname=file.file_name)
Gauravf1e28e02019-02-13 16:46:24 +0530135
136 frappe.local.response.filename = output_filename
Sagar Voraba76f872021-03-29 20:18:45 +0530137 frappe.local.response.filecontent = zip_stream.getvalue()
Gauravf1e28e02019-02-13 16:46:24 +0530138 frappe.local.response.type = "download"
Sagar Voraba76f872021-03-29 20:18:45 +0530139 zip_stream.close()
Gauravf1e28e02019-02-13 16:46:24 +0530140
Ankush Menat494bd9e2022-03-28 18:52:46 +0530141
Gauravf1e28e02019-02-13 16:46:24 +0530142def get_invoice_summary(items, taxes):
143 summary_data = frappe._dict()
144 for tax in taxes:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530145 # Include only VAT charges.
Gauravf1e28e02019-02-13 16:46:24 +0530146 if tax.charge_type == "Actual":
147 continue
148
Ankush Menat494bd9e2022-03-28 18:52:46 +0530149 # Charges to appear as items in the e-invoice.
Gaurav2670ad72019-02-19 10:17:17 +0530150 if tax.charge_type in ["On Previous Row Total", "On Previous Row Amount"]:
151 reference_row = next((row for row in taxes if row.idx == int(tax.row_id or 0)), None)
152 if reference_row:
153 items.append(
154 frappe._dict(
Ankush Menat494bd9e2022-03-28 18:52:46 +0530155 idx=len(items) + 1,
Gaurav2670ad72019-02-19 10:17:17 +0530156 item_code=reference_row.description,
157 item_name=reference_row.description,
rohitwaghchaure9673d0d2019-03-24 12:19:58 +0530158 description=reference_row.description,
Gaurav2670ad72019-02-19 10:17:17 +0530159 rate=reference_row.tax_amount,
160 qty=1.0,
161 amount=reference_row.tax_amount,
162 stock_uom=frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos"),
163 tax_rate=tax.rate,
164 tax_amount=(reference_row.tax_amount * tax.rate) / 100,
165 net_amount=reference_row.tax_amount,
Rohit Waghchaurec10064a2019-09-23 17:39:55 +0530166 taxable_amount=reference_row.tax_amount,
Rohit Waghchaure58f489f2019-04-01 17:50:31 +0530167 item_tax_rate={tax.account_head: tax.rate},
Ankush Menat494bd9e2022-03-28 18:52:46 +0530168 charges=True,
Gaurav2670ad72019-02-19 10:17:17 +0530169 )
170 )
171
Ankush Menat494bd9e2022-03-28 18:52:46 +0530172 # Check item tax rates if tax rate is zero.
Gauravf1e28e02019-02-13 16:46:24 +0530173 if tax.rate == 0:
174 for item in items:
Rohit Waghchaure58f489f2019-04-01 17:50:31 +0530175 item_tax_rate = item.item_tax_rate
Ankush Menat8fe5feb2021-11-04 19:48:32 +0530176 if isinstance(item.item_tax_rate, str):
Rohit Waghchaure58f489f2019-04-01 17:50:31 +0530177 item_tax_rate = json.loads(item.item_tax_rate)
178
179 if item_tax_rate and tax.account_head in item_tax_rate:
Gaurav2670ad72019-02-19 10:17:17 +0530180 key = cstr(item_tax_rate[tax.account_head])
Rohit Waghchaure58f489f2019-04-01 17:50:31 +0530181 if key not in summary_data:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530182 summary_data.setdefault(
183 key,
184 {
185 "tax_amount": 0.0,
186 "taxable_amount": 0.0,
187 "tax_exemption_reason": "",
188 "tax_exemption_law": "",
189 },
190 )
Rohit Waghchaure58f489f2019-04-01 17:50:31 +0530191
Gauravf1e28e02019-02-13 16:46:24 +0530192 summary_data[key]["tax_amount"] += item.tax_amount
193 summary_data[key]["taxable_amount"] += item.net_amount
194 if key == "0.0":
195 summary_data[key]["tax_exemption_reason"] = tax.tax_exemption_reason
196 summary_data[key]["tax_exemption_law"] = tax.tax_exemption_law
197
Ankush Menat494bd9e2022-03-28 18:52:46 +0530198 if summary_data.get("0.0") and tax.charge_type in [
199 "On Previous Row Total",
200 "On Previous Row Amount",
201 ]:
Rohit Waghchaurec10064a2019-09-23 17:39:55 +0530202 summary_data[key]["taxable_amount"] = tax.total
203
Ankush Menat494bd9e2022-03-28 18:52:46 +0530204 if summary_data == {}: # Implies that Zero VAT has not been set on any item.
205 summary_data.setdefault(
206 "0.0",
207 {
208 "tax_amount": 0.0,
209 "taxable_amount": tax.total,
210 "tax_exemption_reason": tax.tax_exemption_reason,
211 "tax_exemption_law": tax.tax_exemption_law,
212 },
213 )
Gauravf1e28e02019-02-13 16:46:24 +0530214
215 else:
216 item_wise_tax_detail = json.loads(tax.item_wise_tax_detail)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530217 for rate_item in [
218 tax_item for tax_item in item_wise_tax_detail.items() if tax_item[1][0] == tax.rate
219 ]:
Gaurav2670ad72019-02-19 10:17:17 +0530220 key = cstr(tax.rate)
Ankush Menat494bd9e2022-03-28 18:52:46 +0530221 if not summary_data.get(key):
222 summary_data.setdefault(key, {"tax_amount": 0.0, "taxable_amount": 0.0})
Gauravf1e28e02019-02-13 16:46:24 +0530223 summary_data[key]["tax_amount"] += rate_item[1][1]
Ankush Menat494bd9e2022-03-28 18:52:46 +0530224 summary_data[key]["taxable_amount"] += sum(
225 [item.net_amount for item in items if item.item_code == rate_item[0]]
226 )
Gauravf1e28e02019-02-13 16:46:24 +0530227
Gaurav2670ad72019-02-19 10:17:17 +0530228 for item in items:
229 key = cstr(tax.rate)
230 if item.get("charges"):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530231 if not summary_data.get(key):
232 summary_data.setdefault(key, {"taxable_amount": 0.0})
Gaurav2670ad72019-02-19 10:17:17 +0530233 summary_data[key]["taxable_amount"] += item.taxable_amount
234
Gauravf1e28e02019-02-13 16:46:24 +0530235 return summary_data
236
Ankush Menat494bd9e2022-03-28 18:52:46 +0530237
238# Preflight for successful e-invoice export.
Gauravf1e28e02019-02-13 16:46:24 +0530239def sales_invoice_validate(doc):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530240 # Validate company
241 if doc.doctype != "Sales Invoice":
rohitwaghchaureef3f8642019-02-20 15:47:06 +0530242 return
243
Gauravf1e28e02019-02-13 16:46:24 +0530244 if not doc.company_address:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530245 frappe.throw(
246 _("Please set an Address on the Company '%s'" % doc.company),
247 title=_("E-Invoicing Information Missing"),
248 )
Gauravf1e28e02019-02-13 16:46:24 +0530249 else:
Rohit Waghchaure74cfe572019-02-26 20:08:26 +0530250 validate_address(doc.company_address)
Gauravf1e28e02019-02-13 16:46:24 +0530251
Ankush Menat494bd9e2022-03-28 18:52:46 +0530252 company_fiscal_regime = frappe.get_cached_value("Company", doc.company, "fiscal_regime")
Rohit Waghchaure0f98cb82019-02-26 15:01:30 +0530253 if not company_fiscal_regime:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530254 frappe.throw(
255 _("Fiscal Regime is mandatory, kindly set the fiscal regime in the company {0}").format(
256 doc.company
257 )
258 )
Rohit Waghchaure0f98cb82019-02-26 15:01:30 +0530259 else:
260 doc.company_fiscal_regime = company_fiscal_regime
261
Ankush Menat494bd9e2022-03-28 18:52:46 +0530262 doc.company_tax_id = frappe.get_cached_value("Company", doc.company, "tax_id")
263 doc.company_fiscal_code = frappe.get_cached_value("Company", doc.company, "fiscal_code")
Gauravf1e28e02019-02-13 16:46:24 +0530264 if not doc.company_tax_id and not doc.company_fiscal_code:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530265 frappe.throw(
266 _("Please set either the Tax ID or Fiscal Code on Company '%s'" % doc.company),
267 title=_("E-Invoicing Information Missing"),
268 )
Gauravf1e28e02019-02-13 16:46:24 +0530269
Ankush Menat494bd9e2022-03-28 18:52:46 +0530270 # Validate customer details
Gaurav010acf72019-03-28 10:42:23 +0530271 customer = frappe.get_doc("Customer", doc.customer)
272
Rohit Waghchaure68a14562019-05-22 13:17:46 +0530273 if customer.customer_type == "Individual":
Gaurav010acf72019-03-28 10:42:23 +0530274 doc.customer_fiscal_code = customer.fiscal_code
Gauravf1e28e02019-02-13 16:46:24 +0530275 if not doc.customer_fiscal_code:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530276 frappe.throw(
277 _("Please set Fiscal Code for the customer '%s'" % doc.customer),
278 title=_("E-Invoicing Information Missing"),
279 )
Gauravf1e28e02019-02-13 16:46:24 +0530280 else:
Gaurav010acf72019-03-28 10:42:23 +0530281 if customer.is_public_administration:
282 doc.customer_fiscal_code = customer.fiscal_code
Gauravf1e28e02019-02-13 16:46:24 +0530283 if not doc.customer_fiscal_code:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530284 frappe.throw(
285 _("Please set Fiscal Code for the public administration '%s'" % doc.customer),
286 title=_("E-Invoicing Information Missing"),
287 )
Gauravf1e28e02019-02-13 16:46:24 +0530288 else:
Gaurav010acf72019-03-28 10:42:23 +0530289 doc.tax_id = customer.tax_id
Gauravf1e28e02019-02-13 16:46:24 +0530290 if not doc.tax_id:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530291 frappe.throw(
292 _("Please set Tax ID for the customer '%s'" % doc.customer),
293 title=_("E-Invoicing Information Missing"),
294 )
Gauravf1e28e02019-02-13 16:46:24 +0530295
296 if not doc.customer_address:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530297 frappe.throw(_("Please set the Customer Address"), title=_("E-Invoicing Information Missing"))
Gauravf1e28e02019-02-13 16:46:24 +0530298 else:
Rohit Waghchaure74cfe572019-02-26 20:08:26 +0530299 validate_address(doc.customer_address)
Gauravf1e28e02019-02-13 16:46:24 +0530300
301 if not len(doc.taxes):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530302 frappe.throw(
303 _("Please set at least one row in the Taxes and Charges Table"),
304 title=_("E-Invoicing Information Missing"),
305 )
Gauravf1e28e02019-02-13 16:46:24 +0530306 else:
307 for row in doc.taxes:
308 if row.rate == 0 and row.tax_amount == 0 and not row.tax_exemption_reason:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530309 frappe.throw(
310 _("Row {0}: Please set at Tax Exemption Reason in Sales Taxes and Charges").format(row.idx),
311 title=_("E-Invoicing Information Missing"),
312 )
Gauravf1e28e02019-02-13 16:46:24 +0530313
Rohit Waghchaure0f98cb82019-02-26 15:01:30 +0530314 for schedule in doc.payment_schedule:
315 if schedule.mode_of_payment and not schedule.mode_of_payment_code:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530316 schedule.mode_of_payment_code = frappe.get_cached_value(
317 "Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"
318 )
Gauravf1e28e02019-02-13 16:46:24 +0530319
Ankush Menat494bd9e2022-03-28 18:52:46 +0530320
321# Ensure payment details are valid for e-invoice.
rohitwaghchaurec18e9252019-02-20 17:13:15 +0530322def sales_invoice_on_submit(doc, method):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530323 # Validate payment details
324 if get_company_country(doc.company) not in [
325 "Italy",
326 "Italia",
327 "Italian Republic",
328 "Repubblica Italiana",
329 ]:
rohitwaghchaurec18e9252019-02-20 17:13:15 +0530330 return
331
Gauravf1e28e02019-02-13 16:46:24 +0530332 if not len(doc.payment_schedule):
333 frappe.throw(_("Please set the Payment Schedule"), title=_("E-Invoicing Information Missing"))
334 else:
335 for schedule in doc.payment_schedule:
336 if not schedule.mode_of_payment:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530337 frappe.throw(
338 _("Row {0}: Please set the Mode of Payment in Payment Schedule").format(schedule.idx),
339 title=_("E-Invoicing Information Missing"),
340 )
341 elif not frappe.db.get_value(
342 "Mode of Payment", schedule.mode_of_payment, "mode_of_payment_code"
343 ):
344 frappe.throw(
345 _("Row {0}: Please set the correct code on Mode of Payment {1}").format(
346 schedule.idx, schedule.mode_of_payment
347 ),
348 title=_("E-Invoicing Information Missing"),
349 )
Gauravf1e28e02019-02-13 16:46:24 +0530350
351 prepare_and_attach_invoice(doc)
352
Ankush Menat494bd9e2022-03-28 18:52:46 +0530353
Gauravb30a9b12019-03-01 12:33:19 +0530354def prepare_and_attach_invoice(doc, replace=False):
355 progressive_name, progressive_number = get_progressive_name_and_number(doc, replace)
Gauravf1e28e02019-02-13 16:46:24 +0530356
357 invoice = prepare_invoice(doc, progressive_number)
rohitwaghchaurecdcff6c2019-09-09 10:15:01 +0530358 item_meta = frappe.get_meta("Sales Invoice Item")
359
Ankush Menat494bd9e2022-03-28 18:52:46 +0530360 invoice_xml = frappe.render_template(
361 "erpnext/regional/italy/e-invoice.xml",
362 context={"doc": invoice, "item_meta": item_meta},
363 is_path=True,
364 )
rohitwaghchaurecdcff6c2019-09-09 10:15:01 +0530365
Rohit Waghchaure0f98cb82019-02-26 15:01:30 +0530366 invoice_xml = invoice_xml.replace("&", "&amp;")
Gauravf1e28e02019-02-13 16:46:24 +0530367
368 xml_filename = progressive_name + ".xml"
Saurabhacf83792019-02-24 08:57:16 +0530369
Ankush Menat494bd9e2022-03-28 18:52:46 +0530370 _file = frappe.get_doc(
371 {
372 "doctype": "File",
373 "file_name": xml_filename,
374 "attached_to_doctype": doc.doctype,
375 "attached_to_name": doc.name,
376 "is_private": True,
377 "content": invoice_xml,
378 }
379 )
Saurabhacf83792019-02-24 08:57:16 +0530380 _file.save()
Aditya Hase234d3572019-05-01 11:48:10 +0530381 return _file
Gauravb30a9b12019-03-01 12:33:19 +0530382
Ankush Menat494bd9e2022-03-28 18:52:46 +0530383
Gauravb30a9b12019-03-01 12:33:19 +0530384@frappe.whitelist()
385def generate_single_invoice(docname):
386 doc = frappe.get_doc("Sales Invoice", docname)
Sagar Voraba76f872021-03-29 20:18:45 +0530387 frappe.has_permission("Sales Invoice", doc=doc, throw=True)
Rohit Waghchaure1b7059b2019-03-12 17:44:29 +0530388
Gauravb30a9b12019-03-01 12:33:19 +0530389 e_invoice = prepare_and_attach_invoice(doc, True)
Sagar Voraba76f872021-03-29 20:18:45 +0530390 return e_invoice.file_url
Gauravb30a9b12019-03-01 12:33:19 +0530391
Ankush Menat494bd9e2022-03-28 18:52:46 +0530392
Sagar Voraba76f872021-03-29 20:18:45 +0530393# Delete e-invoice attachment on cancel.
rohitwaghchaurec18e9252019-02-20 17:13:15 +0530394def sales_invoice_on_cancel(doc, method):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530395 if get_company_country(doc.company) not in [
396 "Italy",
397 "Italia",
398 "Italian Republic",
399 "Repubblica Italiana",
400 ]:
rohitwaghchaurec18e9252019-02-20 17:13:15 +0530401 return
402
Gauravf1e28e02019-02-13 16:46:24 +0530403 for attachment in get_e_invoice_attachments(doc):
404 remove_file(attachment.name, attached_to_doctype=doc.doctype, attached_to_name=doc.name)
405
Ankush Menat494bd9e2022-03-28 18:52:46 +0530406
rohitwaghchaurec18e9252019-02-20 17:13:15 +0530407def get_company_country(company):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530408 return frappe.get_cached_value("Company", company, "country")
409
rohitwaghchaurec18e9252019-02-20 17:13:15 +0530410
Sagar Voraba76f872021-03-29 20:18:45 +0530411def get_e_invoice_attachments(invoices):
412 if not isinstance(invoices, list):
413 if not invoices.company_tax_id:
414 return
415
416 invoices = [invoices]
417
418 tax_id_map = {
419 invoice.name: (
420 invoice.company_tax_id
421 if invoice.company_tax_id.startswith("IT")
422 else "IT" + invoice.company_tax_id
Ankush Menat494bd9e2022-03-28 18:52:46 +0530423 )
424 for invoice in invoices
Sagar Voraba76f872021-03-29 20:18:45 +0530425 }
426
427 attachments = frappe.get_all(
428 "File",
429 fields=("name", "file_name", "attached_to_name", "is_private"),
Ankush Menat494bd9e2022-03-28 18:52:46 +0530430 filters={"attached_to_name": ("in", tax_id_map), "attached_to_doctype": "Sales Invoice"},
Sagar Voraba76f872021-03-29 20:18:45 +0530431 )
Mangesh-Khairnar359a73e2019-07-04 11:37:20 +0530432
Gauravf1e28e02019-02-13 16:46:24 +0530433 out = []
Gauravf1e28e02019-02-13 16:46:24 +0530434 for attachment in attachments:
Sagar Voraba76f872021-03-29 20:18:45 +0530435 if (
436 attachment.file_name
437 and attachment.file_name.endswith(".xml")
Ankush Menat494bd9e2022-03-28 18:52:46 +0530438 and attachment.file_name.startswith(tax_id_map.get(attachment.attached_to_name))
Sagar Voraba76f872021-03-29 20:18:45 +0530439 ):
Gauravf1e28e02019-02-13 16:46:24 +0530440 out.append(attachment)
441
442 return out
443
Ankush Menat494bd9e2022-03-28 18:52:46 +0530444
Rohit Waghchaure74cfe572019-02-26 20:08:26 +0530445def validate_address(address_name):
446 fields = ["pincode", "city", "country_code"]
447 data = frappe.get_cached_value("Address", address_name, fields, as_dict=1) or {}
Gauravf1e28e02019-02-13 16:46:24 +0530448
Rohit Waghchaure74cfe572019-02-26 20:08:26 +0530449 for field in fields:
450 if not data.get(field):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530451 frappe.throw(
452 _("Please set {0} for address {1}").format(field.replace("-", ""), address_name),
453 title=_("E-Invoicing Information Missing"),
454 )
455
Gauravf1e28e02019-02-13 16:46:24 +0530456
457def get_unamended_name(doc):
458 attributes = ["naming_series", "amended_from"]
459 for attribute in attributes:
460 if not hasattr(doc, attribute):
461 return doc.name
462
463 if doc.amended_from:
464 return "-".join(doc.name.split("-")[:-1])
465 else:
466 return doc.name
467
Ankush Menat494bd9e2022-03-28 18:52:46 +0530468
Gauravb30a9b12019-03-01 12:33:19 +0530469def get_progressive_name_and_number(doc, replace=False):
470 if replace:
471 for attachment in get_e_invoice_attachments(doc):
472 remove_file(attachment.name, attached_to_doctype=doc.doctype, attached_to_name=doc.name)
473 filename = attachment.file_name.split(".xml")[0]
474 return filename, filename.split("_")[1]
475
Ankush Menat494bd9e2022-03-28 18:52:46 +0530476 company_tax_id = (
477 doc.company_tax_id if doc.company_tax_id.startswith("IT") else "IT" + doc.company_tax_id
478 )
Gauravf1e28e02019-02-13 16:46:24 +0530479 progressive_name = frappe.model.naming.make_autoname(company_tax_id + "_.#####")
480 progressive_number = progressive_name.split("_")[1]
481
Gaurav3f046132019-02-19 16:28:22 +0530482 return progressive_name, progressive_number
483
Ankush Menat494bd9e2022-03-28 18:52:46 +0530484
Gaurav Naik3bf0acb2019-02-20 12:08:53 +0530485def set_state_code(doc, method):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530486 if doc.get("country_code"):
Rohit Waghchaure74cfe572019-02-26 20:08:26 +0530487 doc.country_code = doc.country_code.upper()
488
Ankush Menat494bd9e2022-03-28 18:52:46 +0530489 if not doc.get("state"):
deepeshgarg0071915e152019-02-21 17:55:57 +0530490 return
491
Ankush Menat494bd9e2022-03-28 18:52:46 +0530492 if not (
493 hasattr(doc, "state_code")
494 and doc.country in ["Italy", "Italia", "Italian Republic", "Repubblica Italiana"]
495 ):
Gaurav3f046132019-02-19 16:28:22 +0530496 return
497
Ankush Menat494bd9e2022-03-28 18:52:46 +0530498 state_codes_lower = {key.lower(): value for key, value in state_codes.items()}
Rohit Waghchaure4ef924d2019-03-01 16:24:54 +0530499
Ankush Menat494bd9e2022-03-28 18:52:46 +0530500 state = doc.get("state", "").lower()
Rohit Waghchaure4ef924d2019-03-01 16:24:54 +0530501 if state_codes_lower.get(state):
502 doc.state_code = state_codes_lower.get(state)