206 lines
6.7 KiB
Python
206 lines
6.7 KiB
Python
"""
|
|
Fusion Accounting - EDI Format Registry
|
|
|
|
Provides a configuration model for registering electronic document
|
|
interchange formats. Each format record carries a unique code and
|
|
delegates the actual XML generation / parsing to dedicated generator
|
|
classes (e.g. ``FusionUBLGenerator``, ``FusionCIIGenerator``).
|
|
|
|
Administrators may restrict a format to customer invoices, vendor bills,
|
|
or allow it for both through the ``applicable_to`` field.
|
|
|
|
Original implementation by Nexa Systems Inc.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.exceptions import UserError
|
|
|
|
_log = logging.getLogger(__name__)
|
|
|
|
|
|
class FusionEDIFormat(models.Model):
|
|
"""
|
|
Registry entry for a supported EDI format.
|
|
|
|
This model acts as a strategy-pattern dispatcher: the ``code`` field
|
|
selects the concrete generator/parser to invoke when creating or
|
|
reading an electronic document. Format records are typically seeded
|
|
via XML data files and should not be deleted while EDI documents
|
|
reference them.
|
|
"""
|
|
|
|
_name = "fusion.edi.format"
|
|
_description = "Fusion EDI Format"
|
|
_order = "sequence, name"
|
|
_rec_name = "name"
|
|
|
|
# ------------------------------------------------------------------
|
|
# Fields
|
|
# ------------------------------------------------------------------
|
|
name = fields.Char(
|
|
string="Format Name",
|
|
required=True,
|
|
help="Human-readable name shown in selection lists.",
|
|
)
|
|
code = fields.Char(
|
|
string="Code",
|
|
required=True,
|
|
help=(
|
|
"Unique technical identifier used to dispatch generation / "
|
|
"parsing logic. Examples: 'ubl_21', 'cii', 'facturx'."
|
|
),
|
|
)
|
|
description = fields.Text(
|
|
string="Description",
|
|
help="Optional notes about the format, its version, or usage.",
|
|
)
|
|
applicable_to = fields.Selection(
|
|
selection=[
|
|
("invoices", "Customer Invoices / Credit Notes"),
|
|
("bills", "Vendor Bills"),
|
|
("both", "Both"),
|
|
],
|
|
string="Applicable To",
|
|
default="both",
|
|
required=True,
|
|
help="Restricts this format to customer-side, vendor-side, or both.",
|
|
)
|
|
active = fields.Boolean(
|
|
string="Active",
|
|
default=True,
|
|
help="Inactive formats are hidden from selection lists.",
|
|
)
|
|
sequence = fields.Integer(
|
|
string="Sequence",
|
|
default=10,
|
|
help="Controls display ordering in lists and dropdowns.",
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Constraints
|
|
# ------------------------------------------------------------------
|
|
_sql_constraints = [
|
|
(
|
|
"code_unique",
|
|
"UNIQUE(code)",
|
|
"Each EDI format must have a unique code.",
|
|
),
|
|
]
|
|
|
|
# ------------------------------------------------------------------
|
|
# Generation / Parsing Dispatch
|
|
# ------------------------------------------------------------------
|
|
def generate_document(self, move):
|
|
"""Generate an electronic document for the given journal entry.
|
|
|
|
Dispatches to the appropriate generator based on ``self.code``.
|
|
|
|
Args:
|
|
move: An ``account.move`` recordset (single record).
|
|
|
|
Returns:
|
|
bytes: The XML payload of the generated document.
|
|
|
|
Raises:
|
|
UserError: When no generator is registered for this format
|
|
code or the move type is incompatible.
|
|
"""
|
|
self.ensure_one()
|
|
move.ensure_one()
|
|
self._check_applicability(move)
|
|
|
|
generator_map = self._get_generator_map()
|
|
generator_method = generator_map.get(self.code)
|
|
if not generator_method:
|
|
raise UserError(
|
|
_("No generator is registered for EDI format '%s'.", self.code)
|
|
)
|
|
return generator_method(move)
|
|
|
|
def parse_document(self, xml_bytes):
|
|
"""Parse an incoming EDI XML document and return invoice data.
|
|
|
|
Dispatches to the appropriate parser based on ``self.code``.
|
|
|
|
Args:
|
|
xml_bytes (bytes): Raw XML content to parse.
|
|
|
|
Returns:
|
|
dict: A dictionary of invoice field values ready for
|
|
``account.move.create()``.
|
|
|
|
Raises:
|
|
UserError: When no parser is registered for this format code.
|
|
"""
|
|
self.ensure_one()
|
|
parser_map = self._get_parser_map()
|
|
parser_method = parser_map.get(self.code)
|
|
if not parser_method:
|
|
raise UserError(
|
|
_("No parser is registered for EDI format '%s'.", self.code)
|
|
)
|
|
return parser_method(xml_bytes)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Internal dispatch helpers
|
|
# ------------------------------------------------------------------
|
|
def _get_generator_map(self):
|
|
"""Return a mapping of format codes to generator callables.
|
|
|
|
Each callable accepts a single ``account.move`` record and
|
|
returns ``bytes`` (the XML payload).
|
|
"""
|
|
ubl = self.env["fusion.ubl.generator"]
|
|
cii = self.env["fusion.cii.generator"]
|
|
return {
|
|
"ubl_21": ubl.generate_ubl_invoice,
|
|
"cii": cii.generate_cii_invoice,
|
|
"facturx": cii.generate_cii_invoice,
|
|
}
|
|
|
|
def _get_parser_map(self):
|
|
"""Return a mapping of format codes to parser callables.
|
|
|
|
Each callable accepts ``bytes`` (raw XML) and returns a ``dict``
|
|
of invoice values.
|
|
"""
|
|
ubl = self.env["fusion.ubl.generator"]
|
|
cii = self.env["fusion.cii.generator"]
|
|
return {
|
|
"ubl_21": ubl.parse_ubl_invoice,
|
|
"cii": cii.parse_cii_invoice,
|
|
"facturx": cii.parse_cii_invoice,
|
|
}
|
|
|
|
def _check_applicability(self, move):
|
|
"""Verify that this format is applicable to the given move type.
|
|
|
|
Raises:
|
|
UserError: When the format/move combination is invalid.
|
|
"""
|
|
self.ensure_one()
|
|
if self.applicable_to == "invoices" and move.move_type in (
|
|
"in_invoice",
|
|
"in_refund",
|
|
):
|
|
raise UserError(
|
|
_(
|
|
"EDI format '%s' is restricted to customer invoices / "
|
|
"credit notes and cannot be used for vendor bills.",
|
|
self.name,
|
|
)
|
|
)
|
|
if self.applicable_to == "bills" and move.move_type in (
|
|
"out_invoice",
|
|
"out_refund",
|
|
):
|
|
raise UserError(
|
|
_(
|
|
"EDI format '%s' is restricted to vendor bills and "
|
|
"cannot be used for customer invoices.",
|
|
self.name,
|
|
)
|
|
)
|