Initial commit
This commit is contained in:
205
Fusion Accounting/models/edi_format.py
Normal file
205
Fusion Accounting/models/edi_format.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
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,
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user