oc-facho/facho/fe/form.py
bit4bit@riseup.net 52632babb2 se extrae generacion de CUFE a DianXMLExtensionCUFE
FossilOrigin-Name: 964ad19875edfc7f6990f795396eeeb4c66809eefe93303b1479d6c3ad2e2484
2020-05-28 00:49:14 +00:00

257 lines
10 KiB
Python

# This file is part of facho. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import hashlib
from functools import reduce
import copy
from dataclasses import dataclass
from datetime import datetime
from .data import dian
from . import fe
@dataclass
class Party:
name: str
ident: str
responsability_code: str
organization_code: str
phone: str = ''
address: str = ''
email: str = ''
legal_name: str = ''
legal_company_ident: str = ''
legal_address: str = ''
@dataclass
class TaxSubTotal:
percent: float
tax_scheme_ident: str = '01'
tax_amount: float = 0.0
taxable_amount: float = 0.0
def calculate(self, invline):
self.tax_amount = invline.total_amount * (self.percent / 100)
self.taxable_amount = invline.total_amount
@dataclass
class TaxTotal:
subtotals: list
tax_amount: float = 0.0
taxable_amount: float = 0.0
def calculate(self, invline):
for subtax in self.subtotals:
subtax.calculate(invline)
self.tax_amount += subtax.tax_amount
self.taxable_amount += subtax.taxable_amount
@dataclass
class InvoiceLine:
# RESOLUCION 0004: pagina 155
quantity: int
description: str
item_ident: int
price_amount: float
tax: TaxTotal
@property
def total_amount(self):
return self.quantity * self.price_amount
@property
def total_tax_inclusive_amount(self):
return self.tax.taxable_amount + self.tax.tax_amount
@property
def total_tax_exclusive_amount(self):
return self.tax.taxable_amount
def calculate(self):
self.tax.calculate(self)
@dataclass
class LegalMonetaryTotal:
line_extension_amount: float = 0.0
tax_exclusive_amount: float = 0.0
tax_inclusive_amount: float = 0.0
charge_total_amount: float = 0.0
payable_amount: float = 0.0
class Invoice:
def __init__(self):
self.invoice_period_start = None
self.invoice_period_end = None
self.invoice_issue = None
self.invoice_ident = None
self.invoice_legal_monetary_total = LegalMonetaryTotal(0, 0, 0, 0, 0)
self.invoice_customer = None
self.invoice_supplier = None
self.invoice_lines = []
def set_period(self, startdate, enddate):
self.invoice_period_start = startdate
self.invoice_period_end = enddate
def set_issue(self, dtime: datetime):
self.invoice_issue = dtime
def set_ident(self, ident: str):
self.invoice_ident = ident
def set_supplier(self, party: Party):
self.invoice_supplier = party
def set_customer(self, party: Party):
self.invoice_customer = party
def add_invoice_line(self, line: InvoiceLine):
self.invoice_lines.append(line)
def validate(self, validator):
validator.validate_customer(self.invoice_customer)
validator.validate_supplier(self.invoice_supplier)
for invline in self.invoice_lines:
validator.validate_invoice_line(self, invline)
def _calculate_legal_monetary_total(self):
for invline in self.invoice_lines:
self.invoice_legal_monetary_total.line_extension_amount += invline.total_amount
self.invoice_legal_monetary_total.tax_exclusive_amount += invline.total_tax_exclusive_amount
self.invoice_legal_monetary_total.tax_inclusive_amount += invline.total_tax_inclusive_amount
self.invoice_legal_monetary_total.charge_total_amount += invline.total_amount
#self.invoice_legal_monetary_total.payable_amount = self.invoice_legal_monetary_total.tax_exclusive_amount \
# + self.invoice_legal_monetary_total.line_extension_amount \
# + self.invoice_legal_monetary_total.tax_inclusive_amount
self.invoice_legal_monetary_total.payable_amount = self.invoice_legal_monetary_total.tax_inclusive_amount
def calculate(self):
for invline in self.invoice_lines:
invline.calculate()
self._calculate_legal_monetary_total()
class DianResolucion0001Validator:
def __init__(self):
self.errors = []
def _validate_party(self, party):
try:
dian.TipoResponsabilidad[party.responsability_code]
except KeyError:
self.errors.append(('responsability_code', 'not found'))
try:
dian.TipoOrganizacion[party.organization_code]
except KeyError:
self.errors.append(('organization_code', 'not found'))
def validate_customer(self, customer):
self._validate_party(customer)
def validate_supplier(self, supplier):
self._validate_party(supplier)
def validate_invoice_line(self, invoice, line):
pass
def valid(self):
return not self.errors
class DIANInvoiceXML(fe.FeXML):
def __init__(self, invoice):
super().__init__('Invoice', 'http://www.dian.gov.co/contratos/facturaelectronica/v1')
self.attach_invoice(invoice)
def attach_invoice(self, invoice):
"""adiciona etiquetas a FEXML y retorna FEXML
en caso de fallar validacion retorna None"""
fexml = self
invoice.calculate()
fexml.set_element('/fe:Invoice/cbc:ID', invoice.invoice_ident)
fexml.set_element('/fe:Invoice/cbc:IssueDate', self.issue_date(invoice.invoice_issue))
fexml.set_element('/fe:Invoice/cbc:IssueTime', self.issue_time(invoice.invoice_issue))
fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:StartDate', invoice.invoice_period_start.strftime('%Y-%m-%d'))
fexml.set_element('/fe:Invoice/cac:InvoicePeriod/cbc:EndDate', invoice.invoice_period_end.strftime('%Y-%m-%d'))
fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines))
fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/cac:PartyIdentification/cbc:ID',
invoice.invoice_supplier.ident)
fexml.set_element('/Invoice/cac:AccountingSupplierParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_supplier.ident)
fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyTaxScheme/cbc:TaxLevelCode',
invoice.invoice_supplier.responsability_code)
fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/cbc:AdditionalAccountID',
invoice.invoice_supplier.organization_code)
fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/cac:PartyName/cbc:Name',
invoice.invoice_supplier.name)
fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_supplier.legal_name)
fexml.set_element('/fe:Invoice/fe:AccountingSupplierParty/fe:Party/fe:PhysicalLocation/fe:Address/cac:AddressLine/cbc:Line',
invoice.invoice_supplier.address)
fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/cac:PartyIdentification/cbc:ID',
invoice.invoice_customer.ident)
fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyTaxScheme/cbc:TaxLevelCode',
invoice.invoice_customer.responsability_code)
fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/cbc:AdditionalAccountID',
invoice.invoice_customer.organization_code)
fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/cac:PartyName/cbc:Name',
invoice.invoice_customer.name)
fexml.set_element('/fe:Invoice/cac:AccountingCustomerParty/cac:Party/cac:PartyTaxScheme/cbc:CompanyID',
invoice.invoice_customer.ident)
fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PartyLegalEntity/cbc:RegistrationName',
invoice.invoice_customer.legal_name)
fexml.set_element('/fe:Invoice/fe:AccountingCustomerParty/fe:Party/fe:PhysicalLocation/fe:Address/cac:AddressLine/cbc:Line',
invoice.invoice_customer.address)
fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:LineExtensionAmount',
invoice.invoice_legal_monetary_total.line_extension_amount,
currencyID='COP')
fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:TaxExclusiveAmount',
invoice.invoice_legal_monetary_total.tax_exclusive_amount,
currencyID='COP')
fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:TaxInclusiveAmount',
invoice.invoice_legal_monetary_total.tax_inclusive_amount,
currencyID='COP')
fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:ChargeTotalAmount',
invoice.invoice_legal_monetary_total.charge_total_amount,
currencyID='COP')
fexml.set_element('/fe:Invoice/fe:LegalMonetaryTotal/cbc:PayableAmount',
invoice.invoice_legal_monetary_total.payable_amount,
currencyID='COP')
fexml.set_element('/fe:Invoice/cbc:LineCountNumeric', len(invoice.invoice_lines))
next_append = False
for index, invoice_line in enumerate(invoice.invoice_lines):
line = fexml.fragment('/fe:Invoice/fe:InvoiceLine', append=next_append)
next_append = True
line.set_element('/fe:InvoiceLine/cbc:ID', index + 1)
line.set_element('/fe:InvoiceLine/cbc:InvoicedQuantity', invoice_line.quantity, unitCode = 'NAR')
line.set_element('/fe:InvoiceLine/cbc:LineExtensionAmount', invoice_line.total_amount, currencyID="COP")
line.set_element('/fe:InvoiceLine/fe:Price/cbc:PriceAmount', invoice_line.price_amount, currencyID="COP")
line.set_element('/fe:InvoiceLine/fe:Item/cbc:Description', invoice_line.description)
return fexml
def issue_time(self, datetime_):
return datetime_.strftime('%H:%M:%S%z')
def issue_date(self, datetime_):
return datetime_.strftime('%Y-%m-%d')