Encapsulates different methods to be able to perform a better inheritance in other modules
This commit is contained in:
parent
12084c44a3
commit
4dada49b1c
62
invoice.py
62
invoice.py
|
@ -18,6 +18,7 @@ from trytond.pool import Pool, PoolMeta
|
|||
from trytond.pyson import Bool, Eval
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.wizard import Wizard, StateView, StateTransition, Button
|
||||
from trytond import backend
|
||||
|
||||
__all__ = ['Invoice', 'InvoiceLine', 'CreditInvoiceStart', 'CreditInvoice',
|
||||
'GenerateFacturaeStart', 'GenerateFacturae']
|
||||
|
@ -107,6 +108,9 @@ FACe_REQUIRED_FIELDS = ['facturae_person_type', 'facturae_residence_type']
|
|||
_slugify_strip_re = re.compile(r'[^\w\s-]')
|
||||
_slugify_hyphenate_re = re.compile(r'[-\s]+')
|
||||
|
||||
DEFAULT_FACTURAE_TEMPLATE = 'template_facturae_3.2.1.xml'
|
||||
DEFAULT_FACTURAE_SCHEMA = 'Facturaev3_2_1-offline.xsd'
|
||||
|
||||
|
||||
def slugify(value):
|
||||
if not isinstance(value, unicode):
|
||||
|
@ -258,20 +262,37 @@ class Invoice:
|
|||
|
||||
@classmethod
|
||||
def generate_facturae_default(cls, invoices, certificate_password):
|
||||
to_save = []
|
||||
to_write = ([],)
|
||||
for invoice in invoices:
|
||||
if invoice.invoice_facturae:
|
||||
continue
|
||||
facturae_content = invoice.get_facturae()
|
||||
invoice._validate_facturae(facturae_content)
|
||||
invoice.invoice_facturae = invoice._sign_facturae(
|
||||
facturae_content, certificate_password)
|
||||
to_save.append(invoice)
|
||||
if to_save:
|
||||
cls.save(to_save)
|
||||
if backend.name() != 'sqlite':
|
||||
invoice_facturae = invoice._sign_facturae(
|
||||
facturae_content, certificate_password)
|
||||
else:
|
||||
invoice_facturae = facturae_content
|
||||
to_write[0].append(invoice)
|
||||
to_write += ({'invoice_facturae': invoice_facturae},)
|
||||
if to_write:
|
||||
cls.write(*to_write)
|
||||
|
||||
def get_facturae(self):
|
||||
"""Return the content of factura-e XML file"""
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader(module_path()),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
template = DEFAULT_FACTURAE_TEMPLATE
|
||||
return self._get_jinja_template(jinja_env, template).render(
|
||||
self._get_content_to_render(), ).encode('utf-8')
|
||||
|
||||
def _get_jinja_template(self, jinja_env, template):
|
||||
return jinja_env.get_template(template)
|
||||
|
||||
def _get_content_to_render(self):
|
||||
"""Return the content to render in factura-e XML file"""
|
||||
pool = Pool()
|
||||
Currency = pool.get('currency.currency')
|
||||
Date = pool.get('ir.date')
|
||||
|
@ -306,6 +327,7 @@ class Invoice:
|
|||
or len(self.company.party.vat_code) > 30):
|
||||
self.raise_user_error('company_vat_identifier',
|
||||
(self.company.party.rec_name,))
|
||||
|
||||
if (not self.company.party.addresses
|
||||
or not self.company.party.addresses[0].street
|
||||
or not self.company.party.addresses[0].zip
|
||||
|
@ -398,34 +420,29 @@ class Invoice:
|
|||
'invoice': self.rec_name,
|
||||
})
|
||||
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader(module_path()),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
jinja_template = jinja_env.get_template('template_facturae_3.2.1.xml')
|
||||
|
||||
return jinja_template.render({
|
||||
return {
|
||||
'invoice': self,
|
||||
'Decimal': Decimal,
|
||||
'Currency': Currency,
|
||||
'euro': euro,
|
||||
'exchange_rate': exchange_rate,
|
||||
'exchange_rate_date': exchange_rate_date,
|
||||
'UOM_CODE2TYPE': UOM_CODE2TYPE,
|
||||
}, ).encode('utf-8')
|
||||
}
|
||||
|
||||
def _validate_facturae(self, xml_string):
|
||||
def _validate_facturae(self, xml_string, schema_file_path=None):
|
||||
"""
|
||||
Inspired by https://github.com/pedrobaeza/l10n-spain/blob/d01d049934db55130471e284012be7c860d987eb/l10n_es_facturae/wizard/create_facturae.py
|
||||
"""
|
||||
logger = logging.getLogger('account_invoice_facturae')
|
||||
|
||||
schema_file_path = os.path.join(
|
||||
module_path(),
|
||||
'Facturaev3_2_1-offline.xsd')
|
||||
if not schema_file_path:
|
||||
schema_file_path = os.path.join(
|
||||
module_path(),
|
||||
DEFAULT_FACTURAE_SCHEMA)
|
||||
with open(schema_file_path) as schema_file:
|
||||
facturae_schema = etree.XMLSchema(file=schema_file)
|
||||
logger.debug("Schema Facturaev3_2_1-offline.xsd loaded")
|
||||
logger.debug("%s loaded" % schema_file_path)
|
||||
|
||||
try:
|
||||
facturae_schema.assertValid(etree.fromstring(xml_string))
|
||||
|
@ -435,7 +452,8 @@ class Invoice:
|
|||
logger.warning("Error validating generated Factura-e file",
|
||||
exc_info=True)
|
||||
logger.debug(xml_string)
|
||||
self.raise_user_error('invalid_factura_xml_file', (self.rec_name, e))
|
||||
self.raise_user_error('invalid_factura_xml_file',
|
||||
(self.rec_name, e))
|
||||
return True
|
||||
|
||||
def _sign_facturae(self, xml_string, certificate_password):
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
<field name="wiz_name">account.invoice.generate_facturae</field>
|
||||
<field name="model">account.invoice</field>
|
||||
</record>
|
||||
|
||||
<!--<record model="ir.action.keyword" id="generate_signed_facturae_keyword">
|
||||
<field name="keyword">form_action</field>
|
||||
<field name="model">account.invoice,-1</field>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<EquivalentInEuros>{{ Currency.compute(invoice.currency, invoice.total_amount, euro) }}</EquivalentInEuros>
|
||||
{% endif %}
|
||||
</TotalExecutableAmount>
|
||||
<InvoiceCurrencyCode>{{ invoice.currency.code }}</InvoiceCurrencyCode>
|
||||
<InvoiceCurrencyCode>{{ invoice.currency.code.upper() }}</InvoiceCurrencyCode>
|
||||
</Batch>
|
||||
{# FactoryAssignmentData optional: not supported (factoring not supported) #}
|
||||
</FileHeader>
|
||||
|
@ -158,11 +158,11 @@
|
|||
{# OperationDate required only if is different to IssueDate, but we consider OperatinDate==invoice_date: not supported #}
|
||||
{# PlaceOfIssue optional: not supported #}
|
||||
{# InvoicingPeriod required only for Recapitulativas or temporary service: not supported #}
|
||||
<InvoiceCurrencyCode>{{ invoice.currency.code }}</InvoiceCurrencyCode>
|
||||
<InvoiceCurrencyCode>{{ invoice.currency.code.upper() }}</InvoiceCurrencyCode>
|
||||
{% if invoice.currency != euro %}
|
||||
<ExchangeRateDetails>
|
||||
<ExchangeRate>{{ exchange_rate_date }}</ExchangeRate>
|
||||
<ExchangeRateDate>{{ exchange_rate_date.isoformat() }}</ExchangeRateDate>
|
||||
<ExchangeRate>{{ exchange_rate }}</ExchangeRate>
|
||||
<ExchangeRateDate>{{ exchange_rate_date }}</ExchangeRateDate>
|
||||
</ExchangeRateDetails>
|
||||
{% endif %}
|
||||
<TaxCurrencyCode>EUR</TaxCurrencyCode>
|
||||
|
@ -255,7 +255,7 @@
|
|||
- TransactionDate
|
||||
- Extensions
|
||||
#}
|
||||
<ItemDescription>{{ line.description[:2500] }}</ItemDescription>
|
||||
<ItemDescription>{{ line.description and line.description[:2500] or '' }}</ItemDescription>
|
||||
<Quantity>{{ line.quantity }}</Quantity>
|
||||
<UnitOfMeasure>{{ UOM_CODE2TYPE.get(line.unit.symbol, '05') if line.unit else '05' }}</UnitOfMeasure>
|
||||
<UnitPriceWithoutTax>{{ line.unit_price }}</UnitPriceWithoutTax>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
{% else %}
|
||||
<OverseasAddress>
|
||||
<Address>{{ address.street[:80] }}</Address>
|
||||
<PostCodeAndTown>{{ (', '.join(address.zip, address.city))[:50] }}</PostCode>
|
||||
<PostCodeAndTown>{{ (', '.join([address.zip, address.city]))[:50] }}</PostCodeAndTown>
|
||||
<Province>{{ address.subdivision.name[:20] }}</Province>
|
||||
<CountryCode>{{ address.country.code3 }}</CountryCode>
|
||||
</OverseasAddress>
|
||||
|
|
|
@ -6,12 +6,12 @@ import unittest
|
|||
from decimal import Decimal
|
||||
import trytond.tests.test_tryton
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.modules.account.tests import get_fiscalyear
|
||||
from trytond.modules.account_es.tests import create_chart
|
||||
from trytond.modules.account.tests import get_fiscalyear, create_chart
|
||||
from trytond.modules.company.tests import create_company, set_company
|
||||
from trytond.modules.account_invoice.tests import set_invoice_sequences
|
||||
|
||||
from trytond.modules.currency.tests import create_currency, add_currency_rate
|
||||
CURRENT_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
|
@ -22,31 +22,49 @@ class TestAccountInvoiceFacturaeCase(ModuleTestCase):
|
|||
@with_transaction()
|
||||
def test_invoice_generation(self):
|
||||
'Test invoice generation'
|
||||
|
||||
pool = Pool()
|
||||
Account = pool.get('account.account')
|
||||
FiscalYear = pool.get('account.fiscalyear')
|
||||
GenerateSignedFacturae = pool.get('account.invoice.generate_facturae',
|
||||
type='wizard')
|
||||
Invoice = pool.get('account.invoice')
|
||||
InvoiceLine = pool.get('account.invoice.line')
|
||||
ModelData = pool.get('ir.model.data')
|
||||
Party = pool.get('party.party')
|
||||
PaymentTerm = pool.get('account.invoice.payment_term')
|
||||
ProductUom = pool.get('product.uom')
|
||||
ProductTemplate = pool.get('product.template')
|
||||
Product = pool.get('product.product')
|
||||
Tax = pool.get('account.tax')
|
||||
Address = pool.get('party.address')
|
||||
PartyIdentifier = pool.get('party.identifier')
|
||||
Country = pool.get('country.country')
|
||||
Subdivision = pool.get('country.subdivision')
|
||||
PaymentType = pool.get('account.payment.type')
|
||||
|
||||
revenue_template_id = ModelData.get_id('account_es', 'pgc_7000_child')
|
||||
expense_template_id = ModelData.get_id('account_es', 'pgc_600_child')
|
||||
vat21_template_id = ModelData.get_id('account_es', 'iva_rep_21')
|
||||
country = Country(name='Country', code='ES', code3='ESP')
|
||||
country.save()
|
||||
subdivision = Subdivision(name='Subdivision', country=country,
|
||||
code='SUB', type='area')
|
||||
subdivision.save()
|
||||
|
||||
company = create_company()
|
||||
currency = create_currency('EUR')
|
||||
add_currency_rate(currency, Decimal(1.0))
|
||||
|
||||
tax_identifier = PartyIdentifier()
|
||||
tax_identifier.type = 'eu_vat'
|
||||
tax_identifier.code = 'BE0897290877'
|
||||
company.header = 'Report Header'
|
||||
company.party.identifiers = [tax_identifier]
|
||||
company.party.facturae_person_type = 'J'
|
||||
company.party.facturae_residence_type = 'R'
|
||||
company.party.name = 'Seller'
|
||||
company.party.save()
|
||||
company.save()
|
||||
|
||||
# Save certificate into company
|
||||
with open(os.path.join(
|
||||
CURRENT_PATH, 'certificate.pfx'), 'rb') as cert_file:
|
||||
company.facturae_certificate = cert_file.read()
|
||||
company.save()
|
||||
|
||||
payment_term, = PaymentTerm.create([{
|
||||
'name': '20 days, 40 days',
|
||||
|
@ -79,13 +97,55 @@ class TestAccountInvoiceFacturaeCase(ModuleTestCase):
|
|||
fiscalyear.save()
|
||||
FiscalYear.create_period([fiscalyear])
|
||||
|
||||
revenue, = Account.search([('template', '=', revenue_template_id)])
|
||||
expense, = Account.search([('template', '=', expense_template_id)])
|
||||
vat21, = Tax.search([('template', '=', vat21_template_id)])
|
||||
payment_receivable, = PaymentType.create([{
|
||||
'name': 'Payment Receivable',
|
||||
'kind': 'receivable',
|
||||
'company': company.id,
|
||||
'facturae_type': '01',
|
||||
}])
|
||||
revenue, = Account.search([('kind', '=', 'revenue')])
|
||||
expense, = Account.search([('kind', '=', 'expense')])
|
||||
tax_account, = Account.search([
|
||||
('name', '=', 'Main Tax'),
|
||||
])
|
||||
with Transaction().set_user(0):
|
||||
vat21 = Tax()
|
||||
vat21.name = vat21.description = '21% VAT'
|
||||
vat21.type = 'percentage'
|
||||
vat21.rate = Decimal('0.21')
|
||||
vat21.invoice_account = tax_account
|
||||
vat21.report_type = '05'
|
||||
vat21.credit_note_account = tax_account
|
||||
|
||||
party = Party(name='Party')
|
||||
vat21.save()
|
||||
|
||||
company_address, = company.party.addresses
|
||||
company_address.street = 'street'
|
||||
company_address.zip = '08201'
|
||||
company_address.city = 'City'
|
||||
company_address.subdivision = subdivision
|
||||
company_address.country = country
|
||||
company_address.save()
|
||||
party = Party(name='Buyer')
|
||||
party.facturae_person_type = 'J'
|
||||
party.facturae_residence_type = 'R'
|
||||
tax_identifier = PartyIdentifier()
|
||||
tax_identifier.type = 'eu_vat'
|
||||
tax_identifier.code = 'BE0897290877'
|
||||
party.identifiers = [tax_identifier]
|
||||
party.save()
|
||||
|
||||
address_dict = {
|
||||
'party': party.id,
|
||||
'street': 'St sample, 15',
|
||||
'city': 'City',
|
||||
'zip': '08201',
|
||||
'subdivision': subdivision.id,
|
||||
'country': country.id,
|
||||
}
|
||||
|
||||
address, = Address.create([address_dict])
|
||||
|
||||
term, = PaymentTerm.create([{
|
||||
'name': 'Payment term',
|
||||
'lines': [
|
||||
|
@ -116,43 +176,41 @@ class TestAccountInvoiceFacturaeCase(ModuleTestCase):
|
|||
product.template = template
|
||||
product.save()
|
||||
|
||||
invoice = Invoice()
|
||||
invoice.type = 'out'
|
||||
invoice.on_change_type()
|
||||
invoice.party = party
|
||||
invoice.on_change_party()
|
||||
invoice.payment_term = term
|
||||
with Transaction().set_user(0):
|
||||
invoice = Invoice()
|
||||
invoice.type = 'out'
|
||||
invoice.on_change_type()
|
||||
invoice.party = party
|
||||
invoice.on_change_party()
|
||||
invoice.payment_type = payment_receivable
|
||||
invoice.payment_term = term
|
||||
invoice.currency = currency
|
||||
invoice.company = company
|
||||
|
||||
line1 = InvoiceLine()
|
||||
line1.product = product
|
||||
line1.on_change_product()
|
||||
line1.on_change_account()
|
||||
line1.quantity = 5
|
||||
line1.unit_price = Decimal('40')
|
||||
line1 = InvoiceLine()
|
||||
line1.account = revenue
|
||||
line1.product = product
|
||||
line1.on_change_product()
|
||||
line1.on_change_account()
|
||||
line1.description = 'TestLine2'
|
||||
line1.quantity = 5
|
||||
line1.unit_price = Decimal('40')
|
||||
|
||||
line2 = InvoiceLine()
|
||||
line2.account = revenue
|
||||
line2.on_change_account()
|
||||
line2.description = 'Test'
|
||||
line2.quantity = 1
|
||||
line2.unit_price = Decimal(20)
|
||||
line2 = InvoiceLine()
|
||||
line2.account = revenue
|
||||
line2.product = product
|
||||
line2.on_change_product()
|
||||
line2.on_change_account()
|
||||
line2.description = 'TestLine2'
|
||||
line2.quantity = 1
|
||||
line2.unit_price = Decimal(20)
|
||||
|
||||
invoice.lines = [line1, line2]
|
||||
invoice.on_change_lines()
|
||||
invoice.save()
|
||||
# invoice.untaxed_amount == Decimal('220.00')
|
||||
# invoice.tax_amount == Decimal('20.00')
|
||||
# invoice.total_amount == Decimal('240.00')
|
||||
invoice.lines = [line1, line2]
|
||||
invoice.on_change_lines()
|
||||
invoice.save()
|
||||
|
||||
Invoice.post([invoice])
|
||||
|
||||
session_id, _, _ = GenerateSignedFacturae.create()
|
||||
generate_signed_facturae = GenerateSignedFacturae(session_id)
|
||||
generate_signed_facturae.account.certificate_password = (
|
||||
'privatepassword')
|
||||
generate_signed_facturae.transition_generate()
|
||||
|
||||
self.assertIsNotNone(invoice.invoice_facturae)
|
||||
Invoice.generate_facturae_default([invoice], 'privatepassword')
|
||||
|
||||
|
||||
def suite():
|
||||
|
|
Loading…
Reference in New Issue