diff --git a/facho/fe/model/__init__.py b/facho/fe/model/__init__.py
index 504c644..5a1c546 100644
--- a/facho/fe/model/__init__.py
+++ b/facho/fe/model/__init__.py
@@ -2,54 +2,14 @@ import facho.model as model
import facho.model.fields as fields
import facho.fe.form as form
from facho import fe
+from .common import *
+from . import dian
from datetime import date, datetime
from collections import defaultdict
from copy import copy
import hashlib
-class Name(model.Model):
- __name__ = 'Name'
-
-class Date(model.Model):
- __name__ = 'Date'
-
- def __default_set__(self, value):
- if isinstance(value, str):
- return value
- if isinstance(value, date):
- return value.isoformat()
-
- def __str__(self):
- return str(self._value)
-
-class Time(model.Model):
- __name__ = 'Time'
-
- def __default_set__(self, value):
- if isinstance(value, str):
- return value
- if isinstance(value, date):
- return value.strftime('%H:%M:%S-05:00')
-
- def __str__(self):
- return str(self._value)
-
-class InvoicePeriod(model.Model):
- __name__ = 'InvoicePeriod'
-
- start_date = fields.Many2One(Date, name='StartDate', namespace='cbc')
-
- end_date = fields.Many2One(Date, name='EndDate', namespace='cbc')
-
-class ID(model.Model):
- __name__ = 'ID'
-
- def __default_get__(self, name, value):
- return self._value
-
- def __str__(self):
- return str(self._value)
class PartyTaxScheme(model.Model):
__name__ = 'PartyTaxScheme'
@@ -139,10 +99,10 @@ class TaxCategory(model.Model):
class TaxSubTotal(model.Model):
__name__ = 'TaxSubTotal'
- taxable_amount = fields.Many2One(Amount, name='TaxableAmount', default=0.00)
- tax_amount = fields.Many2One(Amount, name='TaxAmount', default=0.00)
- tax_percent = fields.Many2One(Percent)
- tax_category = fields.Many2One(TaxCategory)
+ taxable_amount = fields.Many2One(Amount, name='TaxableAmount', namespace='cbc', default=0.00)
+ tax_amount = fields.Many2One(Amount, name='TaxAmount', namespace='cbc', default=0.00)
+ tax_percent = fields.Many2One(Percent, namespace='cbc')
+ tax_category = fields.Many2One(TaxCategory, namespace='cac')
percent = fields.Virtual(setter='set_category', getter='get_category')
scheme = fields.Virtual(setter='set_category', getter='get_category')
@@ -266,6 +226,28 @@ class LegalMonetaryTotal(model.Model):
def update_payable_amount(self, name, value):
self.payable_amount = self.tax_inclusive_amount + self.charge_total_amount
+
+class DIANExtension(model.Model):
+ __name__ = 'UBLExtension'
+
+ _content = fields.Many2One(Element, name='ExtensionContent', namespace='ext')
+
+ dian = fields.Many2One(dian.DianExtensions, name='DianExtensions', namespace='sts')
+
+ def __default_get__(self, name, value):
+ return self.dian
+
+class UBLExtension(model.Model):
+ __name__ = 'UBLExtension'
+
+ content = fields.Many2One(Element, name='ExtensionContent', namespace='ext', default='')
+
+class UBLExtensions(model.Model):
+ __name__ = 'UBLExtensions'
+
+ dian = fields.Many2One(DIANExtension, namespace='ext', create=True)
+ extension = fields.Many2One(UBLExtension, namespace='ext', create=True)
+
class Invoice(model.Model):
__name__ = 'Invoice'
__namespace__ = {
@@ -277,18 +259,25 @@ class Invoice(model.Model):
'xades': 'http://uri.etsi.org/01903/v1.3.2#',
'ds': 'http://www.w3.org/2000/09/xmldsig#'
}
+
+
+ _ubl_extensions = fields.Many2One(UBLExtensions, namespace='ext')
+ dian = fields.Virtual(getter='get_dian_extension')
+
+ profile_id = fields.Many2One(Element, name='ProfileID', namespace='cbc', default='DIAN 2.1')
+ profile_execute_id = fields.Many2One(Element, name='ProfileExecuteID', namespace='cbc', default='2')
id = fields.Many2One(ID, namespace='cbc')
issue = fields.Virtual(setter='set_issue')
issue_date = fields.Many2One(Date, name='IssueDate', namespace='cbc')
issue_time = fields.Many2One(Time, name='IssueTime', namespace='cbc')
- period = fields.Many2One(InvoicePeriod, namespace='cac')
+ period = fields.Many2One(Period, name='InvoicePeriod', namespace='cac')
supplier = fields.Many2One(AccountingSupplierParty, namespace='cac')
customer = fields.Many2One(AccountingCustomerParty, namespace='cac')
- lines = fields.One2Many(InvoiceLine, namespace='cac')
legal_monetary_total = fields.Many2One(LegalMonetaryTotal, namespace='cac')
+ lines = fields.One2Many(InvoiceLine, namespace='cac')
taxtotal_01 = fields.Many2One(TaxTotal)
taxtotal_04 = fields.Many2One(TaxTotal)
@@ -359,3 +348,6 @@ class Invoice(model.Model):
raise ValueError('expected type datetime')
self.issue_date = value.date()
self.issue_time = value
+
+ def get_dian_extension(self, name, _value):
+ return self._ubl_extensions.dian
diff --git a/facho/fe/model/common.py b/facho/fe/model/common.py
new file mode 100644
index 0000000..6234537
--- /dev/null
+++ b/facho/fe/model/common.py
@@ -0,0 +1,55 @@
+import facho.model as model
+import facho.model.fields as fields
+
+from datetime import date, datetime
+
+__all__ = ['Element', 'Name', 'Date', 'Time', 'Period', 'ID']
+
+class Element(model.Model):
+ """
+ Lo usuamos para elementos que solo manejan contenido
+ """
+ __name__ = 'Element'
+
+class Name(model.Model):
+ __name__ = 'Name'
+
+class Date(model.Model):
+ __name__ = 'Date'
+
+ def __default_set__(self, value):
+ if isinstance(value, str):
+ return value
+ if isinstance(value, date):
+ return value.isoformat()
+
+ def __str__(self):
+ return str(self._value)
+
+class Time(model.Model):
+ __name__ = 'Time'
+
+ def __default_set__(self, value):
+ if isinstance(value, str):
+ return value
+ if isinstance(value, date):
+ return value.strftime('%H:%M:%S-05:00')
+
+ def __str__(self):
+ return str(self._value)
+
+class Period(model.Model):
+ __name__ = 'Period'
+
+ start_date = fields.Many2One(Date, name='StartDate', namespace='cbc')
+
+ end_date = fields.Many2One(Date, name='EndDate', namespace='cbc')
+
+class ID(model.Model):
+ __name__ = 'ID'
+
+ def __default_get__(self, name, value):
+ return self._value
+
+ def __str__(self):
+ return str(self._value)
diff --git a/facho/fe/model/dian.py b/facho/fe/model/dian.py
new file mode 100644
index 0000000..721f5ee
--- /dev/null
+++ b/facho/fe/model/dian.py
@@ -0,0 +1,37 @@
+import facho.model as model
+import facho.model.fields as fields
+from .common import *
+
+class SoftwareProvider(model.Model):
+ __name__ = 'SoftwareProvider'
+
+ provider_id = fields.Many2One(Element, name='ProviderID', namespace='sts')
+ software_id = fields.Many2One(Element, name='SoftwareID', namespace='sts')
+
+class InvoiceSource(model.Model):
+ __name__ = 'InvoiceSource'
+
+ identification_code = fields.Many2One(Element, name='IdentificationCode', namespace='sts', default='CO')
+
+class AuthorizedInvoices(model.Model):
+ __name__ = 'AuthorizedInvoices'
+
+ prefix = fields.Many2One(Element, name='Prefix', namespace='sts')
+ from_range = fields.Many2One(Element, name='From', namespace='sts')
+ to_range = fields.Many2One(Element, name='To', namespace='sts')
+
+class InvoiceControl(model.Model):
+ __name__ = 'InvoiceControl'
+
+ authorization = fields.Many2One(Element, name='InvoiceAuthorization', namespace='sts')
+ period = fields.Many2One(Period, name='AuthorizationPeriod', namespace='sts')
+ invoices = fields.Many2One(AuthorizedInvoices, namespace='sts')
+
+class DianExtensions(model.Model):
+ __name__ = 'DianExtensions'
+
+ software_security_code = fields.Many2One(Element, name='SoftwareSecurityCode', namespace='sts')
+ software_provider = fields.Many2One(SoftwareProvider, namespace='sts')
+ source = fields.Many2One(InvoiceSource, namespace='sts')
+ control = fields.Many2One(InvoiceControl, namespace='sts')
+
diff --git a/facho/model/__init__.py b/facho/model/__init__.py
index acf29bc..0839644 100644
--- a/facho/model/__init__.py
+++ b/facho/model/__init__.py
@@ -45,7 +45,9 @@ class ModelBase(object, metaclass=ModelMeta):
if isinstance(v, fields.Attribute) or isinstance(v, fields.Many2One) or isinstance(v, fields.Function) or isinstance(v, fields.Amount):
if hasattr(v, 'default') and v.default is not None:
setattr(obj, key, v.default)
-
+ if hasattr(v, 'create') and v.create == True:
+ setattr(obj, key, '')
+
# register callbacks for changes
(fun, on_change_fields) = on_change_fields_for_function()
for field in on_change_fields:
diff --git a/facho/model/fields/amount.py b/facho/model/fields/amount.py
new file mode 100644
index 0000000..b756e0a
--- /dev/null
+++ b/facho/model/fields/amount.py
@@ -0,0 +1,31 @@
+from .field import Field
+from collections import defaultdict
+import facho.fe.form as form
+
+class Amount(Field):
+ def __init__(self, name=None, default=None, precision=6):
+ self.field_name = name
+ self.values = {}
+ self.default = default
+ self.precision = precision
+
+ def __get__(self, model, cls):
+ if model is None:
+ return self
+ assert self.name is not None
+
+ self.__init_value(model)
+ model._set_field(self.name, self)
+ return self.values[model]
+
+ def __set__(self, model, value):
+ assert self.name is not None
+ self.__init_value(model)
+ model._set_field(self.name, self)
+ self.values[model] = form.Amount(value, precision=self.precision)
+
+ self._changed_field(model, self.name, value)
+
+ def __init_value(self, model):
+ if model not in self.values:
+ self.values[model] = form.Amount(self.default or 0)
diff --git a/facho/model/fields/many2one.py b/facho/model/fields/many2one.py
index d39bf36..7201d87 100644
--- a/facho/model/fields/many2one.py
+++ b/facho/model/fields/many2one.py
@@ -2,7 +2,7 @@ from .field import Field
from collections import defaultdict
class Many2One(Field):
- def __init__(self, model, name=None, setter=None, namespace=None, default=None, virtual=False):
+ def __init__(self, model, name=None, setter=None, namespace=None, default=None, virtual=False, create=False):
self.model = model
self.setter = setter
self.namespace = namespace
@@ -10,7 +10,8 @@ class Many2One(Field):
self.default = default
self.virtual = virtual
self.relations = defaultdict(dict)
-
+ self.create = create
+
def __get__(self, inst, cls):
if inst is None:
return self
diff --git a/facho/model/fields/virtual.py b/facho/model/fields/virtual.py
new file mode 100644
index 0000000..4fe4e02
--- /dev/null
+++ b/facho/model/fields/virtual.py
@@ -0,0 +1,45 @@
+from .field import Field
+
+# Un campo virtual
+# no participa del renderizado
+# pero puede interactura con este
+class Virtual(Field):
+ def __init__(self,
+ setter=None,
+ getter='bob',
+ default=None,
+ update_internal=False):
+ self.default = default
+ self.setter = setter
+ self.getter = getter
+ self.values = {}
+ self.update_internal = update_internal
+ self.virtual = True
+
+ def __get__(self, inst, cls):
+ if inst is None:
+ return self
+ assert self.name is not None
+
+ value = self.default
+ try:
+ value = self.values[inst]
+ except KeyError:
+ pass
+
+ try:
+ self.values[inst] = getattr(inst, self.getter)(self.name, value)
+ except AttributeError:
+ self.values[inst] = value
+
+ return self.values[inst]
+
+ def __set__(self, inst, value):
+ if self.update_internal:
+ inst._value = value
+
+ if self.setter is None:
+ self.values[inst] = value
+ else:
+ self.values[inst] = self._call(inst, self.setter, self.name, value)
+ self._changed_field(inst, self.name, value)
diff --git a/tests/test_model.py b/tests/test_model.py
index 0ceebec..831460e 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -80,6 +80,35 @@ def test_many2one_with_custom_setter():
party.location = 99
assert '' == party.to_xml()
+def test_many2one_always_create():
+ class Name(facho.model.Model):
+ __name__ = 'Name'
+
+ class Person(facho.model.Model):
+ __name__ = 'Person'
+
+ name = fields.Many2One(Name, default='facho')
+
+ person = Person()
+ assert 'facho' == person.to_xml()
+
+def test_many2one_nested_always_create():
+ class Name(facho.model.Model):
+ __name__ = 'Name'
+
+ class Contact(facho.model.Model):
+ __name__ = 'Contact'
+
+ name = fields.Many2One(Name, default='facho')
+
+ class Person(facho.model.Model):
+ __name__ = 'Person'
+
+ contact = fields.Many2One(Contact, create=True)
+
+ person = Person()
+ assert 'facho' == person.to_xml()
+
def test_many2one_auto_create():
class TaxAmount(facho.model.Model):
__name__ = 'TaxAmount'
diff --git a/tests/test_model_invoice.py b/tests/test_model_invoice.py
index 4b18980..01d94cc 100644
--- a/tests/test_model_invoice.py
+++ b/tests/test_model_invoice.py
@@ -13,8 +13,15 @@ import facho.fe.model as model
import facho.fe.form as form
from facho import fe
-def _test_simple_invoice():
+def test_simple_invoice():
invoice = model.Invoice()
+ invoice.dian.software_security_code = '12345'
+ invoice.dian.software_provider.provider_id = 'provider-id'
+ invoice.dian.software_provider.software_id = 'facho'
+ invoice.dian.control.prefix = 'SETP'
+ invoice.dian.control.from_range = '1000'
+ invoice.dian.control.to_range = '1000'
+
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'