diff --git a/facho/fe/model/__init__.py b/facho/fe/model/__init__.py index 7b714e1..5de325e 100644 --- a/facho/fe/model/__init__.py +++ b/facho/fe/model/__init__.py @@ -45,6 +45,9 @@ class InvoicePeriod(model.Model): class ID(model.Model): __name__ = 'ID' + def __default_get__(self, name, value): + return self._value + def __str__(self): return str(self._value) @@ -67,17 +70,16 @@ class Quantity(model.Model): __name__ = 'Quantity' code = fields.Attribute('unitCode', default='NAR') - value = fields.Virtual(default=0, update_internal=True) + + def __setup__(self): + self.value = 0 def __default_set__(self, value): self.value = value return value - def __mul__(self, other): - return form.Amount(self.value) * other.value - - def __add__(self, other): - return form.Amount(self.value) + other.value + def __default_get__(self, name, value): + return self.value class Amount(model.Model): __name__ = 'Amount' @@ -89,32 +91,23 @@ class Amount(model.Model): self.value = value return value - def __default__get__(self, value): - return value + def __default_get__(self, name, value): + return self.value def __str__(self): return str(self.value) - def __add__(self, other): - if isinstance(other, form.Amount): - return self.value + other - - return self.value + other.value - class Price(model.Model): __name__ = 'Price' amount = fields.Many2One(Amount, name='PriceAmount') - value = fields.Amount(0.0) def __default_set__(self, value): self.amount = value - self.value = value return value - def __mul__(self, other): - return self.value * other.value - + def __default_get__(self, name, value): + return self.amount class Percent(model.Model): __name__ = 'Percent' @@ -177,16 +170,18 @@ class AllowanceCharge(model.Model): def isDiscount(self): return self.is_discount == True -class TaxScheme: - pass +class Taxes: + class Scheme: + def __init__(self, scheme): + self.scheme = scheme -class TaxIva(TaxScheme): - def __init__(self, percent): - self.scheme = '01' - self.percent = percent + class Iva(Scheme): + def __init__(self, percent): + super().__init__('01') + self.percent = percent - def calculate(self, amount): - return form.Amount(amount) * form.Amount(self.percent / 100) + def calculate(self, amount): + return form.Amount(amount) * form.Amount(self.percent / 100) class InvoiceLine(model.Model): __name__ = 'InvoiceLine' @@ -200,23 +195,28 @@ class InvoiceLine(model.Model): def __setup__(self): self._taxs = defaultdict(list) - self._subtotals = { - '01': self.taxtotal.subtotals.create() - } - self._subtotals['01'].scheme = '01' + self._subtotals = {} + + def add_tax(self, tax): + if not isinstance(tax, Taxes.Scheme): + raise ValueError('tax expected TaxIva') + + # inicialiamos subtotal para impuesto + if not tax.scheme in self._subtotals: + subtotal = self.taxtotal.subtotals.create() + subtotal.scheme = tax.scheme + + self._subtotals[tax.scheme] = subtotal + + self._taxs[tax.scheme].append(tax) def get_tax_amount(self, name, value): total = form.Amount(0) for (scheme, subtotal) in self._subtotals.items(): - total += subtotal.tax_amount.value + total += subtotal.tax_amount + return total - def add_tax(self, tax): - if not isinstance(tax, TaxScheme): - raise ValueError('tax expected TaxScheme') - - self._taxs[tax.scheme].append(tax) - @fields.on_change(['price', 'quantity']) def update_amount(self, name, value): charge = form.AmountCollection(self.allowance_charge)\ @@ -229,14 +229,15 @@ class InvoiceLine(model.Model): .map(lambda charge: charge.amount)\ .sum() - total = self.quantity * self.price + total = form.Amount(self.quantity) * form.Amount(self.price) self.amount = total + charge - discount + for (scheme, subtotal) in self._subtotals.items(): - subtotal.tax_amount.value = 0 + subtotal.tax_amount = 0 for (scheme, taxes) in self._taxs.items(): for tax in taxes: - self._subtotals[scheme].tax_amount += tax.calculate(self.amount.value) + self._subtotals[scheme].tax_amount += tax.calculate(self.amount) class LegalMonetaryTotal(model.Model): __name__ = 'LegalMonetaryTotal' @@ -250,19 +251,11 @@ class LegalMonetaryTotal(model.Model): @fields.on_change(['tax_inclusive_amount', 'charge_total']) def update_payable_amount(self, name, value): - self.payable_amount = self.tax_inclusive_amount.value + self.charge_total_amount.value - -class Technical(model.Model): - __name__ = 'Technical' - - token = fields.Virtual(default='') - environment = fields.Virtual(default=fe.AMBIENTE_PRODUCCION) + self.payable_amount = self.tax_inclusive_amount + self.charge_total_amount class Invoice(model.Model): __name__ = 'Invoice' - technical = fields.Many2One(Technical, virtual=True) - id = fields.Many2One(ID) issue = fields.Virtual(setter='set_issue') issue_date = fields.Many2One(Date, name='IssueDate') @@ -279,12 +272,6 @@ class Invoice(model.Model): taxtotal_04 = fields.Many2One(TaxTotal) taxtotal_03 = fields.Many2One(TaxTotal) - cufe = fields.Virtual(getter='calculate_cufe') - - _subtotal_01 = fields.Virtual() - _subtotal_04 = fields.Virtual() - _subtotal_03 = fields.Virtual() - def __setup__(self): # Se requieren minimo estos impuestos para # validar el cufe @@ -298,10 +285,10 @@ class Invoice(model.Model): self._subtotal_03 = self.taxtotal_03.subtotals.create() self._subtotal_03.scheme = '03' - def calculate_cufe(self, name, value): + def cufe(self, token, environment): - valor_bruto = self.legal_monetary_total.line_extension_amount.value - valor_total_pagar = self.legal_monetary_total.payable_amount.value + valor_bruto = self.legal_monetary_total.line_extension_amount + valor_total_pagar = self.legal_monetary_total.payable_amount valor_impuesto_01 = form.Amount(0.0) valor_impuesto_04 = form.Amount(0.0) @@ -309,15 +296,12 @@ class Invoice(model.Model): for line in self.lines: for subtotal in line.taxtotal.subtotals: - scheme_id = subtotal.scheme - if str(subtotal.scheme.id) == '01': - valor_impuesto_01 += subtotal.tax_amount.value + if subtotal.scheme.id == '01': + valor_impuesto_01 += subtotal.tax_amount elif subtotal.scheme.id == '04': - valor_impuesto_04 += subtotal.tax_amount.value + valor_impuesto_04 += subtotal.tax_amount elif subtotal.scheme.id == '03': - valor_impuesto_03 += subtotal.tax_amount.value - - + valor_impuesto_03 += subtotal.tax_amount pattern = [ '%s' % str(self.id), @@ -330,8 +314,8 @@ class Invoice(model.Model): valor_total_pagar.truncate_as_string(2), str(self.supplier.party.id), str(self.customer.party.id), - str(self.technical.token), - str(self.technical.environment) + str(token), + str(environment) ] cufe = "".join(pattern) @@ -341,13 +325,12 @@ class Invoice(model.Model): @fields.on_change(['lines']) def update_legal_monetary_total(self, name, value): - self.legal_monetary_total.line_extension_amount.value = 0 - self.legal_monetary_total.tax_inclusive_amount.value = 0 + self.legal_monetary_total.line_extension_amount = 0 + self.legal_monetary_total.tax_inclusive_amount = 0 for line in self.lines: - self.legal_monetary_total.line_extension_amount.value += line.amount.value - self.legal_monetary_total.tax_inclusive_amount += line.amount.value + line.tax_amount - print("update legal monetary %s" % (str(line.amount.value))) + self.legal_monetary_total.line_extension_amount += line.amount + self.legal_monetary_total.tax_inclusive_amount += line.amount + line.tax_amount def set_issue(self, name, value): if not isinstance(value, datetime): diff --git a/facho/model/__init__.py b/facho/model/__init__.py index d9b9ca7..acf29bc 100644 --- a/facho/model/__init__.py +++ b/facho/model/__init__.py @@ -52,6 +52,7 @@ class ModelBase(object, metaclass=ModelMeta): obj._on_change_fields[field].append(fun) + # post inicializacion del objeto obj.__setup__() return obj @@ -76,17 +77,17 @@ class ModelBase(object, metaclass=ModelMeta): if default is not None: self._value = default - def _hook_before_xml(self): - self.__before_xml__() - for field in self._fields.values(): - if hasattr(field, '__before_xml__'): - field.__before_xml__() - def to_xml(self): """ Genera xml del modelo y sus relaciones """ - self._hook_before_xml() + def _hook_before_xml(): + self.__before_xml__() + for field in self._fields.values(): + if hasattr(field, '__before_xml__'): + field.__before_xml__() + + _hook_before_xml() tag = self.__name__ ns = '' @@ -149,6 +150,7 @@ class Model(ModelBase): def __default_get__(self, name, value): """ + Al obtener el valor atraves de una relacion (age = person.age) Retorno de valor por defecto """ return value diff --git a/facho/model/fields/many2one.py b/facho/model/fields/many2one.py index c22bd3d..d39bf36 100644 --- a/facho/model/fields/many2one.py +++ b/facho/model/fields/many2one.py @@ -25,8 +25,10 @@ class Many2One(Field): # se puede obtener directamente un valor indicado por el modelo if hasattr(value, '__default_get__'): return value.__default_get__(self.name, value) - else: + elif hasattr(inst, '__default_get__'): return inst.__default_get__(self.name, value) + else: + return value def __set__(self, inst, value): assert self.name is not None diff --git a/facho/model/fields/one2many.py b/facho/model/fields/one2many.py index 13b2e51..2c2dca7 100644 --- a/facho/model/fields/one2many.py +++ b/facho/model/fields/one2many.py @@ -13,12 +13,21 @@ class _RelationProxy(): if (name in self.__dict__): return self.__dict__[name] - return getattr(self.__dict__['_obj'], name) + rel = getattr(self.__dict__['_obj'], name) + if hasattr(rel, '__default_get__'): + return rel.__default_get__(name, rel) + + return rel def __setattr__(self, attr, value): # TODO(bit4bit) hacemos proxy al sistema de notificacion de cambios - # algo burdo, se usa __dict__ para saltarnos el __getattr__ y generar un fallo por recursion - response = setattr(self._obj, attr, value) + # algo burdo, se usa __dict__ para saltarnos el __getattr__ y evitar un fallo por recursion + rel = getattr(self.__dict__['_obj'], attr) + if hasattr(rel, '__default_set__'): + response = setattr(self._obj, attr, rel.__default_set__(value)) + else: + response = setattr(self._obj, attr, value) + for fun in self.__dict__['_inst']._on_change_fields[self.__dict__['_attribute']]: fun(self.__dict__['_inst'], self.__dict__['_attribute'], value) return response diff --git a/tests/test_model_invoice.py b/tests/test_model_invoice.py index c99cdb8..4b18980 100644 --- a/tests/test_model_invoice.py +++ b/tests/test_model_invoice.py @@ -29,16 +29,17 @@ def _test_simple_invoice(): def test_simple_invoice_cufe(): + token = '693ff6f2a553c3646a063436fd4dd9ded0311471' + environment = fe.AMBIENTE_PRODUCCION + invoice = model.Invoice() - invoice.technical.token = '693ff6f2a553c3646a063436fd4dd9ded0311471' - invoice.technical.environment = fe.AMBIENTE_PRODUCCION invoice.id = '323200000129' invoice.issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z') invoice.supplier.party.id = '700085371' invoice.customer.party.id = '800199436' line = invoice.lines.create() - line.add_tax(model.TaxIva(19.0)) + line.add_tax(model.Taxes.Iva(19.0)) # TODO(bit4bit) acoplamiento temporal # se debe crear primero el subotatl @@ -46,4 +47,4 @@ def test_simple_invoice_cufe(): line.quantity = 1 line.price = 1_500_000 - assert invoice.cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' + assert invoice.cufe(token, environment) == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'