se adiciona extensions para la dian

FossilOrigin-Name: f5521ddbfb903915de88a26ba5197b67efa1ebfd66337061ee9e3653c59dd217
This commit is contained in:
bit4bit 2021-08-08 22:00:08 +00:00
parent 088fa9e6e0
commit 64b312a432
9 changed files with 251 additions and 52 deletions

View File

@ -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

55
facho/fe/model/common.py Normal file
View File

@ -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)

37
facho/fe/model/dian.py Normal file
View File

@ -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')

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -80,6 +80,35 @@ def test_many2one_with_custom_setter():
party.location = 99
assert '<Party><PhysicalLocation ID="99"/></Party>' == 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 '<Person><Name>facho</Name></Person>' == 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 '<Person><Contact><Name>facho</Name></Contact></Person>' == person.to_xml()
def test_many2one_auto_create():
class TaxAmount(facho.model.Model):
__name__ = 'TaxAmount'

View File

@ -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'