Files

167 lines
8.2 KiB
Python

import qrcode
import uuid
import io
import base64
from odoo import fields, models, _
from odoo.tools.float_utils import float_repr, float_is_zero
from odoo.addons.l10n_at_pos.models.fiskaly_client import FiskalyClient
class PosOrder(models.Model):
_inherit = 'pos.order'
l10n_at_pos_order_receipt_id = fields.Char(string="Receipt UUID", readonly=True, copy=False, help="Fiskaly receipt identifier for this order")
l10n_at_pos_order_receipt_number = fields.Integer(string="Fiskaly Receipt Number", readonly=True, copy=False, help="Fiskaly receipt number for this order")
l10n_at_pos_order_receipt_qr_data = fields.Char(string="Encrypted QRdata", readonly=True, copy=False, help="Fiskaly receipt signature QRdata for this order")
is_fiskaly_order_receipt_signed = fields.Boolean(string="Receipt Signed?", readonly=True, copy=False, help="Fiskaly receipt signature status for this order")
def _generate_qr_code_image(self, qr_data):
qr = qrcode.make(qr_data)
temp = io.BytesIO()
qr.save(temp, format="PNG")
return base64.b64encode(temp.getvalue()).decode("utf-8")
def _l10n_at_amounts_per_vat(self):
mapped_tax_groups = {
"20%": "STANDARD",
"10%": "REDUCED_1",
"13%": "REDUCED_2",
"0%": "ZERO",
}
result = []
for line in self.lines:
tax = line.tax_ids[0] if line.tax_ids else None
tax_group_name = tax.tax_group_id.name if tax else "0%"
vat_rate = mapped_tax_groups.get(tax_group_name, "SPECIAL")
percentage = tax.amount if tax else 0
vat_entry = {
"vat_rate": vat_rate,
"percentage": percentage,
"incl_vat": line.price_subtotal_incl,
"excl_vat": line.price_subtotal,
"vat": line.price_subtotal_incl - line.price_subtotal
}
if vat_rate == "SPECIAL":
existing_entry = next(
(entry for entry in result if entry["vat_rate"] == "SPECIAL" and float_is_zero(entry["percentage"] - vat_entry["percentage"], precision_digits=2)),
None,
)
if existing_entry:
existing_entry["incl_vat"] += vat_entry["incl_vat"]
existing_entry["excl_vat"] += vat_entry["excl_vat"]
existing_entry["vat"] += vat_entry["vat"]
else:
result.append(vat_entry)
else:
if existing_entry := next((entry for entry in result if entry["vat_rate"] == vat_rate), None):
existing_entry["incl_vat"] += vat_entry["incl_vat"]
existing_entry["excl_vat"] += vat_entry["excl_vat"]
existing_entry["vat"] += vat_entry["vat"]
else:
result.append(vat_entry)
# Converting amounts in "^-?\\d+\\.\\d{2}$" format
for entry_dict in result:
for key, value in entry_dict.items():
if isinstance(value, (int, float)):
entry_dict[key] = float_repr(value, 2)
return result
def action_prepare_receipts_queue(self):
# Filter orders to include only those with 'l10n_at_pos_order_receipt_id', this ensures we process only orders created when Fiskaly was enabled,
# particularly for configurations where Fiskaly is active, this approach is crucial in a multi-company setup.
to_sign_orders = self.filtered(
lambda order: not order.is_fiskaly_order_receipt_signed
and order.l10n_at_pos_order_receipt_id and order.state != 'cancel'
)
if not to_sign_orders:
return self.config_id.company_id._notify("info", _("No unsigned orders are found for the sessions synced with fiskaly of this company among the selected orders."))
to_sign_orders.config_id.company_id._verify_required_fields()
for order in to_sign_orders:
order.sign_order_receipt()
def sign_order_receipt(self, receipt_type="NORMAL"):
self.ensure_one()
company = self.company_id
buyer = self.partner_id
if not self.l10n_at_pos_order_receipt_id:
self.l10n_at_pos_order_receipt_id = str(uuid.uuid4())
self.env.cr.commit()
vat_details = self._l10n_at_amounts_per_vat()
receipt_data = {
"receipt_type": receipt_type,
"schema": {
"ekabs_v0": {
"head": {
"id": str(self.id),
"number": str(self.pos_reference),
"date": self.date_order.strftime("%Y-%m-%dT%H:%M:%SZ"),
"seller": {
"name": company.name,
"tax_number": company.vat,
"tax_exemption": company.l10n_at_pos_is_tax_exempted,
"address": {
"street": (company.street or "") + (company.street2 or ""),
"postal_code": company.zip or "",
"city": company.city or "",
"country_code": "AUT",
}
},
"buyer": {
"name": buyer.name or "",
"address": {
"street": (buyer.street or "") + (buyer.street2 or ""),
"postal_code": buyer.zip or "",
"city": buyer.city or "",
"country_code": "AUT",
}
}
},
"data": {
"currency": self.currency_id.name,
"full_amount_incl_vat": float_repr(self.amount_total, 2),
"payment_types": [
{
"name": payment.payment_method_id.name,
"amount": float_repr(payment.amount, 2),
}
for payment in self.payment_ids
],
"vat_amounts": vat_details,
"lines": [
{
"text": line.full_product_name,
"additional_text": (
(f"Refunded Line of receipt id : {line.refunded_orderline_id.order_id.l10n_at_pos_order_receipt_id}" if line.refunded_orderline_id else "") +
", ".join(attr.display_name for attr in line.attribute_value_ids)
),
"vat_amounts": [
{
"percentage": float_repr(line.tax_ids[0].amount if line.tax_ids else 0, 2),
"incl_vat": float_repr(line.price_subtotal_incl, 2)
}
],
"item": {
"number": line.product_id.id,
"quantity": line.qty,
"price_per_unit": float_repr(line.price_unit, 2),
},
}
for line in self.lines
]
}
}
}
}
response = FiskalyClient(self.company_id, self.company_id.l10n_at_fiskaly_api_key, self.company_id.l10n_at_fiskaly_api_secret).sign_order(self.config_id.l10n_at_cash_regid, self.company_id.l10n_at_fiskaly_access_token, self.l10n_at_pos_order_receipt_id, receipt_data)
self.l10n_at_pos_order_receipt_qr_data = self._generate_qr_code_image(response.get("qr_code_data"))
self.l10n_at_pos_order_receipt_number = int(response.get("receipt_number"))
self.is_fiskaly_order_receipt_signed = bool(response.get("signed"))
return (self.is_fiskaly_order_receipt_signed, self.l10n_at_pos_order_receipt_number, self.l10n_at_pos_order_receipt_qr_data)