generacion de cufe desde invoice

FossilOrigin-Name: d3494f20063452571b1e86d505f211e61fdf435aa43b870408136e3e9302bc17
This commit is contained in:
bit4bit 2021-07-31 17:09:42 +00:00
parent a1a9746353
commit 69a74c0714
8 changed files with 233 additions and 45 deletions

View File

@ -54,8 +54,8 @@ class AmountCollection(Collection):
return total return total
class Amount: class Amount:
def __init__(self, amount: int or float or str or Amount, currency: Currency = Currency('COP')): def __init__(self, amount: int or float or str or Amount, currency: Currency = Currency('COP'), precision = DECIMAL_PRECISION):
self.precision = precision
#DIAN 1.7.-2020: 1.2.3.1 #DIAN 1.7.-2020: 1.2.3.1
if isinstance(amount, Amount): if isinstance(amount, Amount):
if amount < Amount(0.0): if amount < Amount(0.0):
@ -67,7 +67,7 @@ class Amount:
if float(amount) < 0: if float(amount) < 0:
raise ValueError('amount must be positive >= 0') raise ValueError('amount must be positive >= 0')
self.amount = Decimal(amount, decimal.Context(prec=DECIMAL_PRECISION, self.amount = Decimal(amount, decimal.Context(prec=self.precision,
#DIAN 1.7.-2020: 1.2.1.1 #DIAN 1.7.-2020: 1.2.1.1
rounding=decimal.ROUND_HALF_EVEN )) rounding=decimal.ROUND_HALF_EVEN ))
self.currency = currency self.currency = currency
@ -87,18 +87,21 @@ class Amount:
def __lt__(self, other): def __lt__(self, other):
if not self.is_same_currency(other): if not self.is_same_currency(other):
raise AmountCurrencyError() raise AmountCurrencyError()
return round(self.amount, DECIMAL_PRECISION) < round(other, 2) return round(self.amount, self.precision) < round(other, 2)
def __eq__(self, other): def __eq__(self, other):
if not self.is_same_currency(other): if not self.is_same_currency(other):
raise AmountCurrencyError() raise AmountCurrencyError()
return round(self.amount, DECIMAL_PRECISION) == round(other.amount, DECIMAL_PRECISION) return round(self.amount, self.precision) == round(other.amount, self.precision)
def _cast(self, val): def _cast(self, val):
if type(val) in [int, float]: if type(val) in [int, float]:
return self.fromNumber(val) return self.fromNumber(val)
if isinstance(val, Amount): if isinstance(val, Amount):
return val return val
if isinstance(val, Decimal):
return self.fromNumber(float(val))
raise TypeError("cant cast %s to amount" % (type(val))) raise TypeError("cant cast %s to amount" % (type(val)))
def __add__(self, rother): def __add__(self, rother):

View File

@ -1,8 +1,12 @@
import facho.model as model 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 datetime import date, datetime from datetime import date, datetime
from collections import defaultdict
from copy import copy from copy import copy
import hashlib
class Name(model.Model): class Name(model.Model):
__name__ = 'Name' __name__ = 'Name'
@ -16,6 +20,9 @@ class Date(model.Model):
if isinstance(value, date): if isinstance(value, date):
return value.isoformat() return value.isoformat()
def __str__(self):
return str(self._value)
class Time(model.Model): class Time(model.Model):
__name__ = 'Time' __name__ = 'Time'
@ -23,7 +30,10 @@ class Time(model.Model):
if isinstance(value, str): if isinstance(value, str):
return value return value
if isinstance(value, date): if isinstance(value, date):
return value.strftime('%H:%M%S-05:00') return value.strftime('%H:%M:%S-05:00')
def __str__(self):
return str(self._value)
class InvoicePeriod(model.Model): class InvoicePeriod(model.Model):
__name__ = 'InvoicePeriod' __name__ = 'InvoicePeriod'
@ -35,6 +45,9 @@ class InvoicePeriod(model.Model):
class ID(model.Model): class ID(model.Model):
__name__ = 'ID' __name__ = 'ID'
def __str__(self):
return str(self._value)
class Party(model.Model): class Party(model.Model):
__name__ = 'Party' __name__ = 'Party'
@ -63,22 +76,36 @@ class Quantity(model.Model):
def __mul__(self, other): def __mul__(self, other):
return form.Amount(self.value) * other.value return form.Amount(self.value) * other.value
def __add__(self, other):
return form.Amount(self.value) + other.value
class Amount(model.Model): class Amount(model.Model):
__name__ = 'Amount' __name__ = 'Amount'
currency = fields.Attribute('currencyID', default='COP') currency = fields.Attribute('currencyID', default='COP')
value = fields.Virtual(default=form.Amount(0), update_internal=True) value = fields.Amount(name='amount', default=0.00, precision=2)
def __default_set__(self, value): def __default_set__(self, value):
self.value = value self.value = value
return value return value
def __default__get__(self, value):
return 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): class Price(model.Model):
__name__ = 'Price' __name__ = 'Price'
amount = fields.Many2One(Amount, name='PriceAmount') amount = fields.Many2One(Amount, name='PriceAmount')
value = fields.Virtual(default=form.Amount(0)) value = fields.Amount(0.0)
def __default_set__(self, value): def __default_set__(self, value):
self.amount = value self.amount = value
@ -101,32 +128,40 @@ class TaxScheme(model.Model):
class TaxCategory(model.Model): class TaxCategory(model.Model):
__name__ = 'TaxCategory' __name__ = 'TaxCategory'
percent = fields.Many2One(Percent, default='19.0') percent = fields.Many2One(Percent)
tax_scheme = fields.Many2One(TaxScheme) tax_scheme = fields.Many2One(TaxScheme)
class TaxSubTotal(model.Model): class TaxSubTotal(model.Model):
__name__ = 'TaxSubTotal' __name__ = 'TaxSubTotal'
taxable_amount = fields.Many2One(Amount, name='TaxableAmount') taxable_amount = fields.Many2One(Amount, name='TaxableAmount', default=0.00)
tax_amount = fields.Many2One(Amount, name='TaxAmount') tax_amount = fields.Many2One(Amount, name='TaxAmount', default=0.00)
tax_percent = fields.Many2One(Percent)
tax_category = fields.Many2One(TaxCategory) tax_category = fields.Many2One(TaxCategory)
percent = fields.Virtual(setter='set_category') percent = fields.Virtual(setter='set_category', getter='get_category')
scheme = fields.Virtual(setter='set_category') scheme = fields.Virtual(setter='set_category', getter='get_category')
def set_category(self, name, value): def set_category(self, name, value):
if name == 'percent': if name == 'percent':
self.tax_category.percent = value self.tax_category.percent = value
# TODO(bit4bit) hacer variable # TODO(bit4bit) debe variar en conjunto?
self.tax_category.tax_scheme.id = '01' self.tax_percent = value
self.tax_category.tax_scheme.name = 'IVA'
elif name == 'scheme': elif name == 'scheme':
self.tax_category.tax_scheme.id = value self.tax_category.tax_scheme.id = value
return value
def get_category(self, name, value):
if name == 'percent':
return value
elif name == 'scheme':
return self.tax_category.tax_scheme
class TaxTotal(model.Model): class TaxTotal(model.Model):
__name__ = 'TaxTotal' __name__ = 'TaxTotal'
tax_amount = fields.Many2One(Amount, name='TaxAmount') tax_amount = fields.Many2One(Amount, name='TaxAmount', default=0.00)
subtotals = fields.One2Many(TaxSubTotal) subtotals = fields.One2Many(TaxSubTotal)
@ -142,6 +177,17 @@ class AllowanceCharge(model.Model):
def isDiscount(self): def isDiscount(self):
return self.is_discount == True return self.is_discount == True
class TaxScheme:
pass
class TaxIva(TaxScheme):
def __init__(self, percent):
self.scheme = '01'
self.percent = percent
def calculate(self, amount):
return form.Amount(amount) * form.Amount(self.percent / 100)
class InvoiceLine(model.Model): class InvoiceLine(model.Model):
__name__ = 'InvoiceLine' __name__ = 'InvoiceLine'
@ -150,6 +196,26 @@ class InvoiceLine(model.Model):
price = fields.Many2One(Price) price = fields.Many2One(Price)
amount = fields.Many2One(Amount, name='LineExtensionAmount') amount = fields.Many2One(Amount, name='LineExtensionAmount')
allowance_charge = fields.One2Many(AllowanceCharge) allowance_charge = fields.One2Many(AllowanceCharge)
tax_amount = fields.Virtual(getter='get_tax_amount')
def __setup__(self):
self._taxs = defaultdict(list)
self._subtotals = {
'01': self.taxtotal.subtotals.create()
}
self._subtotals['01'].scheme = '01'
def get_tax_amount(self, name, value):
total = form.Amount(0)
for (scheme, subtotal) in self._subtotals.items():
total += subtotal.tax_amount.value
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']) @fields.on_change(['price', 'quantity'])
def update_amount(self, name, value): def update_amount(self, name, value):
@ -165,19 +231,38 @@ class InvoiceLine(model.Model):
total = self.quantity * self.price total = self.quantity * self.price
self.amount = total + charge - discount self.amount = total + charge - discount
for (scheme, subtotal) in self._subtotals.items():
subtotal.tax_amount.value = 0
for (scheme, taxes) in self._taxs.items():
for tax in taxes:
self._subtotals[scheme].tax_amount += tax.calculate(self.amount.value)
class LegalMonetaryTotal(model.Model): class LegalMonetaryTotal(model.Model):
__name__ = 'LegalMonetaryTotal' __name__ = 'LegalMonetaryTotal'
line_extension_amount = fields.Many2One(Amount, name='LineExtensionAmount', default=form.Amount(0)) line_extension_amount = fields.Many2One(Amount, name='LineExtensionAmount', default=0)
tax_exclusive_amount = fields.Many2One(Amount, name='TaxExclusiveAmount')
tax_inclusive_amount = fields.Many2One(Amount, name='TaxInclusiveAmount') tax_exclusive_amount = fields.Many2One(Amount, name='TaxExclusiveAmount', default=form.Amount(0))
charge_total_amount = fields.Many2One(Amount, name='ChargeTotalAmount') tax_inclusive_amount = fields.Many2One(Amount, name='TaxInclusiveAmount', default=form.Amount(0))
payable_amount = fields.Many2One(Amount, name='PayableAmount') charge_total_amount = fields.Many2One(Amount, name='ChargeTotalAmount', default=form.Amount(0))
payable_amount = fields.Many2One(Amount, name='PayableAmount', default=form.Amount(0))
@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)
class Invoice(model.Model): class Invoice(model.Model):
__name__ = 'Invoice' __name__ = 'Invoice'
technical = fields.Many2One(Technical, virtual=True)
id = fields.Many2One(ID) id = fields.Many2One(ID)
issue = fields.Virtual(setter='set_issue') issue = fields.Virtual(setter='set_issue')
issue_date = fields.Many2One(Date, name='IssueDate') issue_date = fields.Many2One(Date, name='IssueDate')
@ -190,15 +275,82 @@ class Invoice(model.Model):
lines = fields.One2Many(InvoiceLine) lines = fields.One2Many(InvoiceLine)
legal_monetary_total = fields.Many2One(LegalMonetaryTotal) legal_monetary_total = fields.Many2One(LegalMonetaryTotal)
cufe = fields.Virtual() taxtotal_01 = fields.Many2One(TaxTotal)
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
self._subtotal_01 = self.taxtotal_01.subtotals.create()
self._subtotal_01.scheme = '01'
self._subtotal_01.percent = 19.0
self._subtotal_04 = self.taxtotal_04.subtotals.create()
self._subtotal_04.scheme = '04'
self._subtotal_03 = self.taxtotal_03.subtotals.create()
self._subtotal_03.scheme = '03'
def calculate_cufe(self, name, value):
valor_bruto = self.legal_monetary_total.line_extension_amount.value
valor_total_pagar = self.legal_monetary_total.payable_amount.value
valor_impuesto_01 = form.Amount(0.0)
valor_impuesto_04 = form.Amount(0.0)
valor_impuesto_03 = form.Amount(0.0)
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
elif subtotal.scheme.id == '04':
valor_impuesto_04 += subtotal.tax_amount.value
elif subtotal.scheme.id == '03':
valor_impuesto_03 += subtotal.tax_amount.value
pattern = [
'%s' % str(self.id),
'%s' % str(self.issue_date),
'%s' % str(self.issue_time),
valor_bruto.truncate_as_string(2),
'01', valor_impuesto_01.truncate_as_string(2),
'04', valor_impuesto_04.truncate_as_string(2),
'03', valor_impuesto_03.truncate_as_string(2),
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)
]
cufe = "".join(pattern)
h = hashlib.sha384()
h.update(cufe.encode('utf-8'))
return h.hexdigest()
@fields.on_change(['lines']) @fields.on_change(['lines'])
def update_legal_monetary_total(self, name, value): 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
for line in self.lines: for line in self.lines:
self.legal_monetary_total.line_extension_amount.value += line.amount.value 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)))
def set_issue(self, name, value): def set_issue(self, name, value):
if not isinstance(value, datetime): if not isinstance(value, datetime):
raise ValueError('expected type datetime') raise ValueError('expected type datetime')
self.issue_date = value self.issue_date = value.date()
self.issue_time = value self.issue_time = value

View File

@ -25,7 +25,7 @@ class ModelBase(object, metaclass=ModelMeta):
obj._order_fields = [] obj._order_fields = []
def on_change_fields_for_function(): def on_change_fields_for_function():
# se recorre arbol buscando el primero # se recorre arbol de herencia buscando attributo on_changes
for parent_cls in type(obj).__mro__: for parent_cls in type(obj).__mro__:
for parent_attr in dir(parent_cls): for parent_attr in dir(parent_cls):
parent_meth = getattr(parent_cls, parent_attr, None) parent_meth = getattr(parent_cls, parent_attr, None)
@ -114,6 +114,10 @@ class ModelBase(object, metaclass=ModelMeta):
for name in ordered_fields.keys(): for name in ordered_fields.keys():
value = self._fields[name] value = self._fields[name]
# al ser virtual no adicinamos al arbol xml
if hasattr(value, 'virtual') and value.virtual:
continue
if hasattr(value, 'to_xml'): if hasattr(value, 'to_xml'):
content += value.to_xml() content += value.to_xml()
elif isinstance(value, str): elif isinstance(value, str):
@ -143,6 +147,12 @@ class Model(ModelBase):
""" """
return value return value
def __default_get__(self, name, value):
"""
Retorno de valor por defecto
"""
return value
def __setup__(self): def __setup__(self):
""" """
Inicializar modelo Inicializar modelo

View File

@ -1,6 +1,7 @@
class Field: class Field:
def __set_name__(self, owner, name): def __set_name__(self, owner, name, virtual=False):
self.name = name self.name = name
self.virtual = virtual
def __get__(self, inst, cls): def __get__(self, inst, cls):
if inst is None: if inst is None:

View File

@ -1,22 +1,37 @@
from .field import Field from .field import Field
from collections import defaultdict
class Many2One(Field): class Many2One(Field):
def __init__(self, model, name=None, setter=None, namespace=None, default=None): def __init__(self, model, name=None, setter=None, namespace=None, default=None, virtual=False):
self.model = model self.model = model
self.setter = setter self.setter = setter
self.namespace = namespace self.namespace = namespace
self.field_name = name self.field_name = name
self.default = default self.default = default
self.virtual = virtual
self.relations = defaultdict(dict)
def __get__(self, inst, cls): def __get__(self, inst, cls):
if inst is None: if inst is None:
return self return self
assert self.name is not None assert self.name is not None
return self._create_model(inst, name=self.field_name)
if self.name in self.relations:
value = self.relations[inst][self.name]
else:
value = self._create_model(inst, name=self.field_name)
self.relations[inst][self.name] = value
# se puede obtener directamente un valor indicado por el modelo
if hasattr(value, '__default_get__'):
return value.__default_get__(self.name, value)
else:
return inst.__default_get__(self.name, value)
def __set__(self, inst, value): def __set__(self, inst, value):
assert self.name is not None assert self.name is not None
inst_model = self._create_model(inst, name=self.field_name, model=self.model) inst_model = self._create_model(inst, name=self.field_name, model=self.model)
self.relations[inst][self.name] = inst_model
# si hay setter manual se ejecuta # si hay setter manual se ejecuta
# de lo contrario se asigna como texto del elemento # de lo contrario se asigna como texto del elemento

View File

@ -1,4 +1,5 @@
from .field import Field from .field import Field
from collections import defaultdict
# TODO(bit4bit) lograr que isinstance se aplique # TODO(bit4bit) lograr que isinstance se aplique
# al objeto envuelto # al objeto envuelto
@ -51,7 +52,7 @@ class One2Many(Field):
self.field_name = name self.field_name = name
self.namespace = namespace self.namespace = namespace
self.default = default self.default = default
self.relation = None self.relation = {}
def __get__(self, inst, cls): def __get__(self, inst, cls):
assert self.name is not None assert self.name is not None
@ -59,8 +60,8 @@ class One2Many(Field):
def creator(attribute): def creator(attribute):
return self._create_model(inst, name=self.field_name, model=self.model, attribute=attribute) return self._create_model(inst, name=self.field_name, model=self.model, attribute=attribute)
if self.relation: if inst in self.relation:
return self.relation return self.relation[inst]
else: else:
self.relation = _Relation(creator, inst, self.name) self.relation[inst] = _Relation(creator, inst, self.name)
return self.relation return self.relation[inst]

View File

@ -494,7 +494,7 @@ def test_field_amount():
class Line(facho.model.Model): class Line(facho.model.Model):
__name__ = 'Line' __name__ = 'Line'
amount = fields.Amount(name='Amount', precision=0) amount = fields.Amount(name='Amount', precision=1)
amount_as_attribute = fields.Attribute('amount') amount_as_attribute = fields.Attribute('amount')
@fields.on_change(['amount']) @fields.on_change(['amount'])
@ -504,7 +504,7 @@ def test_field_amount():
line = Line() line = Line()
line.amount = 33 line.amount = 33
assert '<Line amount="33"/>' == line.to_xml() assert '<Line amount="33.0"/>' == line.to_xml()
def test_model_setup(): def test_model_setup():

View File

@ -11,8 +11,9 @@ import pytest
import facho.fe.model as model import facho.fe.model as model
import facho.fe.form as form import facho.fe.form as form
from facho import fe
def test_simple_invoice(): def _test_simple_invoice():
invoice = model.Invoice() invoice = model.Invoice()
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')
@ -24,20 +25,25 @@ def test_simple_invoice():
line.price = form.Amount(5_000) line.price = form.Amount(5_000)
subtotal = line.taxtotal.subtotals.create() subtotal = line.taxtotal.subtotals.create()
subtotal.percent = 19.0 subtotal.percent = 19.0
assert '<Invoice><ID>323200000129</ID><IssueDate>2019-01-16T10:53:10-05:00</IssueDate><IssueTime>10:5310-05:00</IssueTime><AccountingSupplierParty><Party><ID>700085371</ID></Party></AccountingSupplierParty><AccountingCustomerParty><Party><ID>800199436</ID></Party></AccountingCustomerParty><InvoiceLine><InvoicedQuantity unitCode="NAR">1</InvoicedQuantity><TaxTotal><TaxSubTotal><TaxCategory><Percent>19.0</Percent><TaxScheme><ID>01</ID><Name>IVA</Name></TaxScheme></TaxCategory></TaxSubTotal></TaxTotal><Price><PriceAmount currencyID="COP">5000.0</PriceAmount>5000.0</Price><LineExtensionAmount currencyID="COP">5000.0</LineExtensionAmount></InvoiceLine><LegalMonetaryTotal><LineExtensionAmount currencyID="COP">35000.0</LineExtensionAmount></LegalMonetaryTotal></Invoice>' == invoice.to_xml() assert '<Invoice><ID>323200000129</ID><IssueDate>2019-01-16T10:53:10-05:00</IssueDate><IssueTime>10:5310-05:00</IssueTime><AccountingSupplierParty><Party><ID>700085371</ID></Party></AccountingSupplierParty><AccountingCustomerParty><Party><ID>800199436</ID></Party></AccountingCustomerParty><InvoiceLine><InvoicedQuantity unitCode="NAR">1</InvoicedQuantity><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><Percent>19.0</Percent><TaxCategory><Percent>19.0</Percent></TaxCategory></TaxSubTotal></TaxTotal><Price><PriceAmount currencyID="COP">5000.0</PriceAmount>5000.0</Price><LineExtensionAmount currencyID="COP">5000.0</LineExtensionAmount></InvoiceLine><LegalMonetaryTotal><LineExtensionAmount currencyID="COP">0.0</LineExtensionAmount></LegalMonetaryTotal><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><Percent>19.0</Percent><TaxCategory><Percent>19.0</Percent><TaxScheme><ID>01</ID></TaxScheme></TaxCategory></TaxSubTotal></TaxTotal><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><TaxCategory><TaxScheme><ID>04</ID></TaxScheme></TaxCategory></TaxSubTotal></TaxTotal><TaxTotal><TaxAmount currencyID="COP">0.0</TaxAmount><TaxSubTotal><TaxableAmount currencyID="COP">0.0</TaxableAmount><TaxAmount currencyID="COP">0.0</TaxAmount><TaxCategory><TaxScheme><ID>03</ID></TaxScheme></TaxCategory></TaxSubTotal></TaxTotal></Invoice>' == invoice.to_xml()
def _test_simple_invoice_cufe():
def test_simple_invoice_cufe():
invoice = model.Invoice() invoice = model.Invoice()
invoice.technical.token = '693ff6f2a553c3646a063436fd4dd9ded0311471'
invoice.technical.environment = fe.AMBIENTE_PRODUCCION
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'
invoice.customer.party.id = '800199436' invoice.customer.party.id = '800199436'
line = invoice.lines.create() line = invoice.lines.create()
line.add_tax(model.TaxIva(19.0))
# TODO(bit4bit) acoplamiento temporal
# se debe crear primero el subotatl
# para poder calcularse al cambiar el precio
line.quantity = 1 line.quantity = 1
line.price = 1_500_000 line.price = 1_500_000
line_subtotal = line.taxtotal.subtotals.create()
line_subtotal.percent = 19.0
line.subtotal.scheme = '01'
assert invoice.cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4' assert invoice.cufe == '8bb918b19ba22a694f1da11c643b5e9de39adf60311cf179179e9b33381030bcd4c3c3f156c506ed5908f9276f5bd9b4'