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.model.fields as fields
import facho.fe.form as form import facho.fe.form as form
from facho import fe from facho import fe
from .common import *
from . import dian
from datetime import date, datetime from datetime import date, datetime
from collections import defaultdict from collections import defaultdict
from copy import copy from copy import copy
import hashlib 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): class PartyTaxScheme(model.Model):
__name__ = 'PartyTaxScheme' __name__ = 'PartyTaxScheme'
@ -139,10 +99,10 @@ class TaxCategory(model.Model):
class TaxSubTotal(model.Model): class TaxSubTotal(model.Model):
__name__ = 'TaxSubTotal' __name__ = 'TaxSubTotal'
taxable_amount = fields.Many2One(Amount, name='TaxableAmount', default=0.00) taxable_amount = fields.Many2One(Amount, name='TaxableAmount', namespace='cbc', default=0.00)
tax_amount = fields.Many2One(Amount, name='TaxAmount', default=0.00) tax_amount = fields.Many2One(Amount, name='TaxAmount', namespace='cbc', default=0.00)
tax_percent = fields.Many2One(Percent) tax_percent = fields.Many2One(Percent, namespace='cbc')
tax_category = fields.Many2One(TaxCategory) tax_category = fields.Many2One(TaxCategory, namespace='cac')
percent = fields.Virtual(setter='set_category', getter='get_category') percent = fields.Virtual(setter='set_category', getter='get_category')
scheme = 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): def update_payable_amount(self, name, value):
self.payable_amount = self.tax_inclusive_amount + self.charge_total_amount 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): class Invoice(model.Model):
__name__ = 'Invoice' __name__ = 'Invoice'
__namespace__ = { __namespace__ = {
@ -277,18 +259,25 @@ class Invoice(model.Model):
'xades': 'http://uri.etsi.org/01903/v1.3.2#', 'xades': 'http://uri.etsi.org/01903/v1.3.2#',
'ds': 'http://www.w3.org/2000/09/xmldsig#' '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') id = fields.Many2One(ID, namespace='cbc')
issue = fields.Virtual(setter='set_issue') issue = fields.Virtual(setter='set_issue')
issue_date = fields.Many2One(Date, name='IssueDate', namespace='cbc') issue_date = fields.Many2One(Date, name='IssueDate', namespace='cbc')
issue_time = fields.Many2One(Time, name='IssueTime', 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') supplier = fields.Many2One(AccountingSupplierParty, namespace='cac')
customer = fields.Many2One(AccountingCustomerParty, namespace='cac') customer = fields.Many2One(AccountingCustomerParty, namespace='cac')
lines = fields.One2Many(InvoiceLine, namespace='cac')
legal_monetary_total = fields.Many2One(LegalMonetaryTotal, namespace='cac') legal_monetary_total = fields.Many2One(LegalMonetaryTotal, namespace='cac')
lines = fields.One2Many(InvoiceLine, namespace='cac')
taxtotal_01 = fields.Many2One(TaxTotal) taxtotal_01 = fields.Many2One(TaxTotal)
taxtotal_04 = fields.Many2One(TaxTotal) taxtotal_04 = fields.Many2One(TaxTotal)
@ -359,3 +348,6 @@ class Invoice(model.Model):
raise ValueError('expected type datetime') raise ValueError('expected type datetime')
self.issue_date = value.date() self.issue_date = value.date()
self.issue_time = value 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 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: if hasattr(v, 'default') and v.default is not None:
setattr(obj, key, v.default) setattr(obj, key, v.default)
if hasattr(v, 'create') and v.create == True:
setattr(obj, key, '')
# register callbacks for changes # register callbacks for changes
(fun, on_change_fields) = on_change_fields_for_function() (fun, on_change_fields) = on_change_fields_for_function()
for field in on_change_fields: 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 from collections import defaultdict
class Many2One(Field): 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.model = model
self.setter = setter self.setter = setter
self.namespace = namespace self.namespace = namespace
@ -10,7 +10,8 @@ class Many2One(Field):
self.default = default self.default = default
self.virtual = virtual self.virtual = virtual
self.relations = defaultdict(dict) self.relations = defaultdict(dict)
self.create = create
def __get__(self, inst, cls): def __get__(self, inst, cls):
if inst is None: if inst is None:
return self 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 party.location = 99
assert '<Party><PhysicalLocation ID="99"/></Party>' == party.to_xml() 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(): def test_many2one_auto_create():
class TaxAmount(facho.model.Model): class TaxAmount(facho.model.Model):
__name__ = 'TaxAmount' __name__ = 'TaxAmount'

View File

@ -13,8 +13,15 @@ import facho.fe.model as model
import facho.fe.form as form import facho.fe.form as form
from facho import fe from facho import fe
def _test_simple_invoice(): def test_simple_invoice():
invoice = model.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.id = '323200000129'
invoice.issue = datetime.strptime('2019-01-16 10:53:10-05:00', '%Y-%m-%d %H:%M:%S%z') 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.supplier.party.id = '700085371'