167 lines
8.2 KiB
Python
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)
|