# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import functools import os from decimal import Decimal import genshi import genshi.template # XXX fix: https://genshi.edgewall.org/ticket/582 from genshi.template.astutil import ASTCodeGenerator, ASTTransformer from trytond.model import Model from trytond.pool import Pool from trytond.rpc import RPC from trytond.tools import cached_property, slugify from trytond.transaction import Transaction if not hasattr(ASTCodeGenerator, 'visit_NameConstant'): def visit_NameConstant(self, node): if node.value is None: self._write('None') elif node.value is True: self._write('True') elif node.value is False: self._write('False') else: raise Exception("Unknown NameConstant %r" % (node.value,)) ASTCodeGenerator.visit_NameConstant = visit_NameConstant if not hasattr(ASTTransformer, 'visit_NameConstant'): # Re-use visit_Name because _clone is deleted ASTTransformer.visit_NameConstant = ASTTransformer.visit_Name loader = genshi.template.TemplateLoader( os.path.join(os.path.dirname(__file__), 'template'), auto_reload=True) def has_goods_assets(func): @functools.wraps(func) def wrapper(self): if any(l.product.type in {'goods', 'assets'} for l in self.invoice.lines if l.product): return func(self) return wrapper def remove_comment(stream): for kind, data, pos in stream: if kind is genshi.core.COMMENT: continue yield kind, data, pos class Invoice(Model): __name__ = 'edocument.uncefact.invoice' __slots__ = ('invoice',) @classmethod def __setup__(cls): super().__setup__() cls.__rpc__.update({ 'render': RPC(instantiate=0), }) def __init__(self, invoice): pool = Pool() Invoice = pool.get('account.invoice') super().__init__() if int(invoice) >= 0: invoice = Invoice(int(invoice)) with Transaction().set_context(language=invoice.party_lang): self.invoice = invoice.__class__(int(invoice)) else: self.invoice = invoice def render(self, template): if self.invoice.state not in {'posted', 'paid'}: raise ValueError("Invoice must be posted") tmpl = self._get_template(template) if not tmpl: raise NotImplementedError return (tmpl.generate( this=self, Decimal=Decimal) .filter(remove_comment) .render() .encode('utf-8')) def _get_template(self, version): return loader.load(os.path.join(version, 'CrossIndustryInvoice.xml')) @property def filename(self): return f'{slugify(self.invoice.rec_name)}.xml' @cached_property def type_code(self): if self.invoice.type == 'out': if all(l.amount < 0 for l in self.lines): return '381' else: return '380' else: if all(l.amount < 0 for l in self.lines): return '261' else: return '389' @cached_property def type_sign(self): "The sign of the quantity depending of the type code" if self.type_code in {'381', '261'}: return -1 return 1 @cached_property def lines(self): return [l for l in self.invoice.lines if l.type == 'line'] @cached_property def seller_trade_party(self): if self.invoice.type == 'out': return self.invoice.company.party else: return self.invoice.party @cached_property def seller_trade_address(self): if self.invoice.type == 'out': return self.invoice.company.party.address_get('invoice') else: return self.invoice.invoice_address @cached_property def seller_trade_tax_identifier(self): if self.invoice.type == 'out': return self.invoice.tax_identifier else: return self.invoice.party_tax_identifier @cached_property def buyer_trade_party(self): if self.invoice.type == 'out': return self.invoice.party else: return self.invoice.company.party @cached_property def buyer_trade_address(self): if self.invoice.type == 'out': return self.invoice.invoice_address else: return None @cached_property def buyer_trade_tax_identifier(self): if self.invoice.type == 'out': return self.invoice.party_tax_identifier else: return self.invoice.tax_identifier @cached_property @has_goods_assets def ship_to_trade_party(self): if self.invoice.type == 'out': if getattr(self.invoice, 'sales', None): sale = self.invoice.sales[0] # XXX if sale.shipment_party != self.buyer_trade_party: return sale.shipment_party else: if getattr(self.invoice, 'purchases', None): purchase = self.invoice.purchases[0] # XXX address = purchase.warehouse.address if (address and address.party != self.buyer_trade_party): return address.party @cached_property @has_goods_assets def ship_to_trade_address(self): if self.invoice.type == 'out': if getattr(self.invoice, 'sales', None): sale = self.invoice.sales[0] # XXX if sale.shipment_party != self.buyer_trade_party: return sale.shipment_address else: if getattr(self.invoice, 'purchases', None): purchase = self.invoice.purchases[0] # XXX address = purchase.warehouse.address if (address and address.party != self.buyer_trade_party): return address @cached_property @has_goods_assets def ship_from_trade_party(self): if self.invoice.type == 'out': if getattr(self.invoice, 'sales', None): sale = self.invoice.sales[0] # XXX address = sale.warehouse.address if address and address.party != self.seller_trade_party: return address.shipment_party @cached_property @has_goods_assets def ship_from_trade_address(self): if self.invoice.type == 'out': if getattr(self.invoice, 'sales', None): sale = self.invoice.sales[0] # XXX address = sale.warehouse.address if address and address.party != self.seller_trade_party: return address @cached_property def payment_reference(self): return self.invoice.number @classmethod def party_legal_ids(cls, party, address): return []