diff --git a/facho/fe/form/__init__.py b/facho/fe/form/__init__.py
index 0aa6318..b12a86a 100644
--- a/facho/fe/form/__init__.py
+++ b/facho/fe/form/__init__.py
@@ -99,7 +99,7 @@ class Amount:
return self.fromNumber(val)
if isinstance(val, Amount):
return val
- raise TypeError("cant cast to amount")
+ raise TypeError("cant cast %s to amount" % (type(val)))
def __add__(self, rother):
other = self._cast(rother)
diff --git a/facho/fe/model/__init__.py b/facho/fe/model/__init__.py
index 74aaf01..e1d895c 100644
--- a/facho/fe/model/__init__.py
+++ b/facho/fe/model/__init__.py
@@ -1,6 +1,8 @@
import facho.model as model
import facho.model.fields as fields
+import facho.fe.form as form
from datetime import date, datetime
+from copy import copy
class Name(model.Model):
__name__ = 'Name'
@@ -48,24 +50,44 @@ class AccountingSupplierParty(model.Model):
party = fields.Many2One(Party)
-class InvoicedQuantity(model.Model):
- __name__ = 'InvoiceQuantity'
+class Quantity(model.Model):
+ __name__ = 'Quantity'
code = fields.Attribute('unitCode', default='NAR')
+ value = fields.Virtual(default=0, update_internal=True)
+
+ def __default_set__(self, value):
+ self.value = value
+ return value
+
+ def __mul__(self, other):
+ return form.Amount(self.value) * other.value
class Amount(model.Model):
__name__ = 'Amount'
currency = fields.Attribute('currencyID', default='COP')
-
+ value = fields.Virtual(default=form.Amount(0), update_internal=True)
+
+ def __default_set__(self, value):
+ self.value = value
+ return value
+
class Price(model.Model):
__name__ = 'Price'
amount = fields.Many2One(Amount, name='PriceAmount')
-
+ value = fields.Virtual(default=form.Amount(0))
+
def __default_set__(self, value):
self.amount = value
+ self.value = value
+ return value
+
+ def __mul__(self, other):
+ return self.value * other.value
+
class Percent(model.Model):
__name__ = 'Percent'
@@ -102,13 +124,52 @@ class TaxTotal(model.Model):
tax_amount = fields.Many2One(Amount, name='TaxAmount')
subtotals = fields.One2Many(TaxSubTotal)
+
+
+class AllowanceCharge(model.Model):
+ __name__ = 'AllowanceCharge'
+
+ amount = fields.Many2One(Amount)
+ is_discount = fields.Virtual(default=False)
+ def isCharge(self):
+ return self.is_discount == False
+
+ def isDiscount(self):
+ return self.is_discount == True
+
class InvoiceLine(model.Model):
__name__ = 'InvoiceLine'
- quantity = fields.Many2One(InvoicedQuantity)
+ quantity = fields.Many2One(Quantity, name='InvoicedQuantity')
taxtotal = fields.Many2One(TaxTotal)
price = fields.Many2One(Price)
+ amount = fields.Many2One(Amount, name='LineExtensionAmount')
+ allowance_charge = fields.One2Many(AllowanceCharge)
+
+ @fields.on_change(['price', 'quantity'])
+ def update_amount(self, name, value):
+ charge = form.AmountCollection(self.allowance_charge)\
+ .filter(lambda charge: charge.isCharge())\
+ .map(lambda charge: charge.amount)\
+ .sum()
+
+ discount = form.AmountCollection(self.allowance_charge)\
+ .filter(lambda charge: charge.isDiscount())\
+ .map(lambda charge: charge.amount)\
+ .sum()
+
+ total = self.quantity * self.price
+ self.amount = total + charge - discount
+
+class LegalMonetaryTotal(model.Model):
+ __name__ = 'LegalMonetaryTotal'
+
+ line_extension_amount = fields.Many2One(Amount, name='LineExtensionAmount', default=form.Amount(0))
+ tax_exclusive_amount = fields.Many2One(Amount, name='TaxExclusiveAmount')
+ tax_inclusive_amount = fields.Many2One(Amount, name='TaxInclusiveAmount')
+ charge_total_amount = fields.Many2One(Amount, name='ChargeTotalAmount')
+ payable_amount = fields.Many2One(Amount, name='PayableAmount')
class Invoice(model.Model):
__name__ = 'Invoice'
@@ -123,7 +184,13 @@ class Invoice(model.Model):
supplier = fields.Many2One(AccountingSupplierParty)
customer = fields.Many2One(AccountingCustomerParty)
lines = fields.One2Many(InvoiceLine)
+ legal_monetary_total = fields.Many2One(LegalMonetaryTotal)
+ @fields.on_change(['lines'])
+ def update_legal_monetary_total(self, name, value):
+ for line in self.lines:
+ self.legal_monetary_total.line_extension_amount.value += line.amount.value
+
def set_issue(self, name, value):
if not isinstance(value, datetime):
raise ValueError('expected type datetime')
diff --git a/facho/model/__init__.py b/facho/model/__init__.py
index 57f7489..0c2f73c 100644
--- a/facho/model/__init__.py
+++ b/facho/model/__init__.py
@@ -19,7 +19,7 @@ class ModelBase(object, metaclass=ModelMeta):
obj = super().__new__(cls, *args, **kwargs)
obj._xml_attributes = {}
obj._fields = {}
- obj._text = ""
+ obj._value = None
obj._namespace_prefix = None
obj._on_change_fields = defaultdict(list)
obj._order_fields = []
@@ -72,7 +72,7 @@ class ModelBase(object, metaclass=ModelMeta):
def _set_content(self, value):
default = self.__default_set__(value)
if default is not None:
- self._text = str(default)
+ self._value = default
def _hook_before_xml(self):
self.__before_xml__()
@@ -116,7 +116,9 @@ class ModelBase(object, metaclass=ModelMeta):
content += value.to_xml()
elif isinstance(value, str):
content += value
- content += self._text
+
+ if self._value is not None:
+ content += str(self._value)
if content == "":
return "<%s%s%s/>" % (ns, tag, attributes)
diff --git a/facho/model/fields/one2many.py b/facho/model/fields/one2many.py
index 112a92f..aeb6547 100644
--- a/facho/model/fields/one2many.py
+++ b/facho/model/fields/one2many.py
@@ -17,9 +17,10 @@ class _RelationProxy():
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)
for fun in self.__dict__['_inst']._on_change_fields[self.__dict__['_attribute']]:
fun(self.__dict__['_inst'], self.__dict__['_attribute'], value)
- return setattr(self._obj, attr, value)
+ return response
class _Relation():
def __init__(self, creator, inst, attribute):
diff --git a/tests/test_model_invoice.py b/tests/test_model_invoice.py
index a28df1c..6472fd0 100644
--- a/tests/test_model_invoice.py
+++ b/tests/test_model_invoice.py
@@ -20,9 +20,21 @@ def test_simple_invoice():
invoice.customer.party.id = '800199436'
line = invoice.lines.create()
- line.quantity = form.Quantity(1, '94')
+ line.quantity = 1
line.price = form.Amount(5_000)
subtotal = line.taxtotal.subtotals.create()
subtotal.percent = 19.0
+ assert '3232000001292019-01-16T10:53:10-05:0010:5310-05:00700085371800199436119.001IVA5000.05000.05000.035000.0' == invoice.to_xml()
- assert '3232000001292019-01-16T10:53:10-05:0010:5310-05:007000853718001994361.019.001IVA5000.0' == invoice.to_xml()
+def _test_simple_invoice_cufe():
+ invoice = model.Invoice()
+ 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.quantity = form.Quantity(1, '94')
+ line.price = form.Amount(1_500_000)
+ subtotal = line.taxtotal.subtotals.create()
+ subtotal.percent = 19.0