Merge branch 'main' into 6.0
This commit is contained in:
commit
d83663b8fb
5 changed files with 200 additions and 132 deletions
|
@ -1,6 +1,7 @@
|
||||||
Copyright (C) 2015-2023 Cédric Krier.
|
Copyright (C) 2015-2023 Cédric Krier.
|
||||||
Copyright (C) 2015-2023 B2CK SPRL.
|
Copyright (C) 2015-2023 B2CK SPRL.
|
||||||
Copyright (C) 2021-2023 martin-data services.
|
Copyright (C) 2021-2024 martin-data services.
|
||||||
|
Copyright (C) 2024 Mathias Behrle
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|
18
mixin.py
18
mixin.py
|
@ -100,21 +100,29 @@ class EdocumentMixin(object):
|
||||||
""" get tax of invoice-line,
|
""" get tax of invoice-line,
|
||||||
fire exception if no/multiple taxes exists
|
fire exception if no/multiple taxes exists
|
||||||
"""
|
"""
|
||||||
if len(line.invoice_taxes) != 1:
|
Tax = Pool().get('account.tax')
|
||||||
|
|
||||||
|
if len(line.taxes) != 1:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'edocument_xrechnung.msg_linetax_invalid_number',
|
'edocument_xrechnung.msg_linetax_invalid_number',
|
||||||
linename=line.rec_name,
|
linename=line.rec_name,
|
||||||
numtax=len(line.invoice_taxes)))
|
numtax=len(line.taxes)))
|
||||||
|
|
||||||
|
taxlines = Tax.compute(
|
||||||
|
line.taxes, Decimal('1'), 1.0,
|
||||||
|
line.invoice.accounting_date or line.invoice.invoice_date)
|
||||||
|
assert len(taxlines) == 1
|
||||||
|
tax = taxlines[0]['tax']
|
||||||
|
|
||||||
allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B']
|
allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B']
|
||||||
unece_category_code = self.get_category_code(line.invoice_taxes[0].tax)
|
unece_category_code = self.get_category_code(tax)
|
||||||
if unece_category_code not in allowed_cat:
|
if unece_category_code not in allowed_cat:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'edocument_xrechnung.msg_linetax_invalid_catcode',
|
'edocument_xrechnung.msg_linetax_invalid_catcode',
|
||||||
taxname=line.invoice_taxes[0].tax.rec_name,
|
taxname=tax.rec_name,
|
||||||
allowed=', '.join(allowed_cat)))
|
allowed=', '.join(allowed_cat)))
|
||||||
|
|
||||||
return line.invoice_taxes[0].tax
|
return tax
|
||||||
|
|
||||||
def taxident_data(self, tax_identifier):
|
def taxident_data(self, tax_identifier):
|
||||||
""" get tax-scheme-id and codes
|
""" get tax-scheme-id and codes
|
||||||
|
|
|
@ -83,9 +83,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||||
<ram:BilledQuantity py:attrs="{'unitCode': line.unit.unece_code} if line.unit and line.unit.unece_code else {}">${line.quantity * this.type_sign}</ram:BilledQuantity>
|
<ram:BilledQuantity py:attrs="{'unitCode': line.unit.unece_code} if line.unit and line.unit.unece_code else {}">${line.quantity * this.type_sign}</ram:BilledQuantity>
|
||||||
</ram:SpecifiedLineTradeDelivery>
|
</ram:SpecifiedLineTradeDelivery>
|
||||||
<ram:SpecifiedLineTradeSettlement>
|
<ram:SpecifiedLineTradeSettlement>
|
||||||
<py:for each="tax in line.invoice_taxes">
|
${TradeTax(this.invoice_line_tax(line))}
|
||||||
${TradeTax(tax.tax)}
|
|
||||||
</py:for>
|
|
||||||
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${line.amount}</ram:LineTotalAmount>
|
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${line.amount}</ram:LineTotalAmount>
|
||||||
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
|
|
@ -5,21 +5,159 @@
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
import os
|
import os
|
||||||
from unittest.mock import Mock
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
from trytond.modules.edocument_uncefact.tests.test_edocument_uncefact import \
|
|
||||||
get_invoice
|
|
||||||
from trytond.modules.company.tests import create_company, set_company
|
from trytond.modules.company.tests import create_company, set_company
|
||||||
|
from trytond.modules.account.tests import create_chart, get_fiscalyear
|
||||||
from trytond.exceptions import UserError
|
from trytond.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
def set_invoice_sequences(fiscalyear):
|
||||||
|
pool = Pool()
|
||||||
|
Sequence = pool.get('ir.sequence.strict')
|
||||||
|
SequenceType = pool.get('ir.sequence.type')
|
||||||
|
InvoiceSequence = pool.get('account.fiscalyear.invoice_sequence')
|
||||||
|
ModelData = pool.get('ir.model.data')
|
||||||
|
|
||||||
|
sequence = Sequence(
|
||||||
|
name=fiscalyear.name,
|
||||||
|
sequence_type=SequenceType(ModelData.get_id(
|
||||||
|
'account_invoice', 'sequence_type_account_invoice')),
|
||||||
|
company=fiscalyear.company)
|
||||||
|
sequence.save()
|
||||||
|
fiscalyear.invoice_sequences = []
|
||||||
|
invoice_sequence = InvoiceSequence()
|
||||||
|
invoice_sequence.fiscalyear = fiscalyear
|
||||||
|
invoice_sequence.in_invoice_sequence = sequence
|
||||||
|
invoice_sequence.in_credit_note_sequence = sequence
|
||||||
|
invoice_sequence.out_invoice_sequence = sequence
|
||||||
|
invoice_sequence.out_credit_note_sequence = sequence
|
||||||
|
invoice_sequence.save()
|
||||||
|
return fiscalyear
|
||||||
|
|
||||||
|
|
||||||
class EdocTestCase(ModuleTestCase):
|
class EdocTestCase(ModuleTestCase):
|
||||||
'Test e-rechnung module'
|
'Test e-rechnung module'
|
||||||
module = 'edocument_xrechnung'
|
module = 'edocument_xrechnung'
|
||||||
|
|
||||||
|
def prep_fiscalyear(self, company1):
|
||||||
|
""" prepare fiscal year, sequences...
|
||||||
|
"""
|
||||||
|
pool = Pool()
|
||||||
|
FiscalYear = pool.get('account.fiscalyear')
|
||||||
|
|
||||||
|
fisc_year = get_fiscalyear(company1, today=date(2024, 1, 15))
|
||||||
|
set_invoice_sequences(fisc_year)
|
||||||
|
self.assertEqual(len(fisc_year.invoice_sequences), 1)
|
||||||
|
FiscalYear.create_period([fisc_year])
|
||||||
|
|
||||||
|
def prep_company(self):
|
||||||
|
""" create company, add country and bank-account
|
||||||
|
"""
|
||||||
|
pool = Pool()
|
||||||
|
Country = pool.get('country.country')
|
||||||
|
Party = pool.get('party.party')
|
||||||
|
Bank = pool.get('bank')
|
||||||
|
BankAccount = pool.get('bank.account')
|
||||||
|
|
||||||
|
country_de, = Country.create([{
|
||||||
|
'name': 'Germany',
|
||||||
|
'code': 'DE',
|
||||||
|
'code3': 'DEU'}])
|
||||||
|
|
||||||
|
company = create_company('m-ds')
|
||||||
|
Party.write(*[[company.party], {
|
||||||
|
'addresses': [('write', [company.party.addresses[0]], {
|
||||||
|
'country': country_de.id})]}])
|
||||||
|
|
||||||
|
bank_party, = Party.create([{
|
||||||
|
'name': 'Bank 123',
|
||||||
|
'addresses': [('create', [{}])]}])
|
||||||
|
bank, = Bank.create([{'party': bank_party.id}])
|
||||||
|
BankAccount.create([{
|
||||||
|
'bank': bank.id,
|
||||||
|
'owners': [('add', [company.party.id])],
|
||||||
|
'numbers': [('create', [{
|
||||||
|
'type': 'iban',
|
||||||
|
'number': 'DE02300209000106531065'}])]}])
|
||||||
|
return company
|
||||||
|
|
||||||
|
def prep_invoice(self, credit_note=False):
|
||||||
|
""" add invoice
|
||||||
|
"""
|
||||||
|
pool = Pool()
|
||||||
|
Invoice = pool.get('account.invoice')
|
||||||
|
Taxes = pool.get('account.tax')
|
||||||
|
Account = pool.get('account.account')
|
||||||
|
Journal = pool.get('account.journal')
|
||||||
|
Currency = pool.get('currency.currency')
|
||||||
|
Uom = pool.get('product.uom')
|
||||||
|
Country = pool.get('country.country')
|
||||||
|
Party = pool.get('party.party')
|
||||||
|
|
||||||
|
country_de, = Country.search([('code', '=', 'DE')])
|
||||||
|
customer, = Party.create([{
|
||||||
|
'name': 'Customer',
|
||||||
|
'identifiers': [('create', [{
|
||||||
|
'type': 'edoc_route_id', 'code': 'xrechn-route-id-123'}])],
|
||||||
|
'addresses': [('create', [{
|
||||||
|
'invoice': True,
|
||||||
|
'street': 'Customer Street 1',
|
||||||
|
'postal_code': '12345',
|
||||||
|
'city': 'Usertown',
|
||||||
|
'country': country_de.id,
|
||||||
|
}])],
|
||||||
|
}])
|
||||||
|
|
||||||
|
currency1, = Currency.search([('code', '=', 'usd')])
|
||||||
|
|
||||||
|
tax, = Taxes.search([('name', '=', '20% VAT')])
|
||||||
|
Taxes.write(*[
|
||||||
|
[tax],
|
||||||
|
{'unece_code': 'GST', 'unece_category_code': 'S',
|
||||||
|
'legal_notice': 'Legal Notice'}])
|
||||||
|
|
||||||
|
account_lst = Account.search([
|
||||||
|
('name', 'in', ['Main Revenue', 'Main Receivable'])
|
||||||
|
], order=[('name', 'ASC')])
|
||||||
|
self.assertEqual(len(account_lst), 2)
|
||||||
|
self.assertEqual(account_lst[0].name, 'Main Receivable')
|
||||||
|
|
||||||
|
journ_lst = Journal.search([('name', '=', 'Revenue')])
|
||||||
|
self.assertEqual(len(journ_lst), 1)
|
||||||
|
|
||||||
|
to_create_invoice = [{
|
||||||
|
'type': 'out',
|
||||||
|
'description': 'description of invoice',
|
||||||
|
'comment': 'note line 1\nnote line 2',
|
||||||
|
'invoice_date': date(2024, 7, 1),
|
||||||
|
'party': customer.id,
|
||||||
|
'invoice_address': customer.addresses[0].id,
|
||||||
|
'account': account_lst[0].id,
|
||||||
|
'journal': journ_lst[0].id,
|
||||||
|
'currency': currency1.id,
|
||||||
|
'lines': [('create', [{
|
||||||
|
'type': 'line',
|
||||||
|
'quantity': 2.0 if not credit_note else -2.0,
|
||||||
|
'description': 'Product 1',
|
||||||
|
'unit': Uom.search([('symbol', '=', 'u')])[0].id,
|
||||||
|
'unit_price': Decimal('50.0'),
|
||||||
|
'taxes': [('add', [tax.id])],
|
||||||
|
'account': account_lst[1].id,
|
||||||
|
'currency': currency1.id,
|
||||||
|
}])],
|
||||||
|
}]
|
||||||
|
inv_lst, = Invoice.create(to_create_invoice)
|
||||||
|
inv_lst.on_change_lines()
|
||||||
|
inv_lst.save()
|
||||||
|
Invoice.validate_invoice([inv_lst])
|
||||||
|
Invoice.post([inv_lst])
|
||||||
|
self.assertEqual(inv_lst.currency.code, 'usd')
|
||||||
|
self.assertEqual(len(inv_lst.move.lines), 3)
|
||||||
|
return inv_lst
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_xrechn_bank_account_owned(self):
|
def test_xrechn_bank_account_owned(self):
|
||||||
""" check field 'company_owned' on bank.account.number
|
""" check field 'company_owned' on bank.account.number
|
||||||
|
@ -101,45 +239,24 @@ class EdocTestCase(ModuleTestCase):
|
||||||
"""
|
"""
|
||||||
pool = Pool()
|
pool = Pool()
|
||||||
Template = pool.get('edocument.facturxext.invoice')
|
Template = pool.get('edocument.facturxext.invoice')
|
||||||
Identifier = pool.get('party.identifier')
|
|
||||||
Party = pool.get('party.party')
|
|
||||||
Bank = pool.get('bank')
|
|
||||||
BankAccount = pool.get('bank.account')
|
|
||||||
BankNumber = pool.get('bank.account.number')
|
|
||||||
|
|
||||||
invoice = get_invoice()
|
company = self.prep_company()
|
||||||
invoice.payment_term_date = date.today()
|
with set_company(company):
|
||||||
invoice.party.get_xrechnung_route_id = Mock(
|
create_chart(company=company, tax=True)
|
||||||
return_value='xrechn-route-id-123')
|
self.prep_fiscalyear(company)
|
||||||
invoice.company.party.bank_accounts = [
|
invoice = self.prep_invoice()
|
||||||
Mock(
|
|
||||||
spec=BankAccount,
|
|
||||||
currency=invoice.currency,
|
|
||||||
bank=Mock(spec=Bank, party=Mock(spec=Party, name='Bank')),
|
|
||||||
owners=[invoice.company.party],
|
|
||||||
numbers=[Mock(spec=BankNumber, type='other', number='123456')],
|
|
||||||
)]
|
|
||||||
invoice.description = 'description of invoice'
|
|
||||||
invoice.comment = 'note line 1\nnote line 2'
|
|
||||||
invoice.taxes[0].tax.rate = Decimal('0.1')
|
|
||||||
invoice.identifiers = [
|
|
||||||
Mock(
|
|
||||||
spec=Identifier,
|
|
||||||
type='edoc_route_id',
|
|
||||||
code='xrechn-route-id-123')
|
|
||||||
]
|
|
||||||
|
|
||||||
template = Template(invoice)
|
template = Template(invoice)
|
||||||
|
|
||||||
schema_file = os.path.join(
|
schema_file = os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
'Factur-X_1.07.2_EXTENDED',
|
'Factur-X_1.07.2_EXTENDED',
|
||||||
'Factur-X_1.07.2_EXTENDED.xsd')
|
'Factur-X_1.07.2_EXTENDED.xsd')
|
||||||
|
|
||||||
invoice_string = template.render('Factur-X-1.07.2-extended')
|
invoice_string = template.render('Factur-X-1.07.2-extended')
|
||||||
invoice_xml = etree.fromstring(invoice_string)
|
invoice_xml = etree.fromstring(invoice_string)
|
||||||
schema = etree.XMLSchema(etree.parse(schema_file))
|
schema = etree.XMLSchema(etree.parse(schema_file))
|
||||||
schema.assertValid(invoice_xml)
|
schema.assertValid(invoice_xml)
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_xrechn_export_xml_invoice(self):
|
def test_xrechn_export_xml_invoice(self):
|
||||||
|
@ -147,45 +264,24 @@ class EdocTestCase(ModuleTestCase):
|
||||||
"""
|
"""
|
||||||
pool = Pool()
|
pool = Pool()
|
||||||
Template = pool.get('edocument.xrechnung.invoice')
|
Template = pool.get('edocument.xrechnung.invoice')
|
||||||
Identifier = pool.get('party.identifier')
|
|
||||||
Party = pool.get('party.party')
|
|
||||||
Bank = pool.get('bank')
|
|
||||||
BankAccount = pool.get('bank.account')
|
|
||||||
BankNumber = pool.get('bank.account.number')
|
|
||||||
|
|
||||||
invoice = get_invoice()
|
company = self.prep_company()
|
||||||
invoice.payment_term_date = date.today()
|
with set_company(company):
|
||||||
invoice.party.get_xrechnung_route_id = Mock(
|
create_chart(company=company, tax=True)
|
||||||
return_value='xrechn-route-id-123')
|
self.prep_fiscalyear(company)
|
||||||
invoice.company.party.bank_accounts = [
|
invoice = self.prep_invoice()
|
||||||
Mock(
|
|
||||||
spec=BankAccount,
|
|
||||||
currency=invoice.currency,
|
|
||||||
bank=Mock(spec=Bank, party=Mock(spec=Party, name='Bank')),
|
|
||||||
owners=[invoice.company.party],
|
|
||||||
numbers=[Mock(spec=BankNumber, type='other', number='123456')],
|
|
||||||
)]
|
|
||||||
invoice.description = 'description of invoice'
|
|
||||||
invoice.comment = 'note line 1\nnote line 2'
|
|
||||||
invoice.taxes[0].tax.rate = Decimal('0.1')
|
|
||||||
invoice.identifiers = [
|
|
||||||
Mock(
|
|
||||||
spec=Identifier,
|
|
||||||
type='edoc_route_id',
|
|
||||||
code='xrechn-route-id-123')
|
|
||||||
]
|
|
||||||
|
|
||||||
template = Template(invoice)
|
template = Template(invoice)
|
||||||
|
|
||||||
schema_file = os.path.join(
|
schema_file = os.path.join(
|
||||||
os.path.dirname(__file__), 'os-UBL-2.1',
|
os.path.dirname(__file__), 'os-UBL-2.1',
|
||||||
'xsd', 'maindoc', 'UBL-Invoice-2.1.xsd')
|
'xsd', 'maindoc', 'UBL-Invoice-2.1.xsd')
|
||||||
|
|
||||||
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
|
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
|
||||||
invoice_string = template.render(x)
|
invoice_string = template.render(x)
|
||||||
invoice_xml = etree.fromstring(invoice_string)
|
invoice_xml = etree.fromstring(invoice_string)
|
||||||
schema = etree.XMLSchema(etree.parse(schema_file))
|
schema = etree.XMLSchema(etree.parse(schema_file))
|
||||||
schema.assertValid(invoice_xml)
|
schema.assertValid(invoice_xml)
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_xrechn_export_xml_creditnote(self):
|
def test_xrechn_export_xml_creditnote(self):
|
||||||
|
@ -193,59 +289,24 @@ class EdocTestCase(ModuleTestCase):
|
||||||
"""
|
"""
|
||||||
pool = Pool()
|
pool = Pool()
|
||||||
Template = pool.get('edocument.xrechnung.invoice')
|
Template = pool.get('edocument.xrechnung.invoice')
|
||||||
Identifier = pool.get('party.identifier')
|
|
||||||
Party = pool.get('party.party')
|
|
||||||
Bank = pool.get('bank')
|
|
||||||
BankAccount = pool.get('bank.account')
|
|
||||||
BankNumber = pool.get('bank.account.number')
|
|
||||||
|
|
||||||
invoice = get_invoice()
|
company = self.prep_company()
|
||||||
|
with set_company(company):
|
||||||
|
create_chart(company=company, tax=True)
|
||||||
|
self.prep_fiscalyear(company)
|
||||||
|
invoice = self.prep_invoice(credit_note=True)
|
||||||
|
|
||||||
# credit note
|
template = Template(invoice)
|
||||||
invoice.lines[0].quantity = -1
|
|
||||||
invoice.lines[0].amount = Decimal('-100.0')
|
|
||||||
invoice.taxes[0].base = Decimal('-100.0')
|
|
||||||
invoice.taxes[0].amount = Decimal('-10.0')
|
|
||||||
invoice.untaxed_amount = Decimal('-100.0')
|
|
||||||
invoice.tax_amount = Decimal('-10.0')
|
|
||||||
invoice.total_amount = Decimal('-110.0')
|
|
||||||
invoice.lines_to_pay[0].debit = Decimal('-110.0')
|
|
||||||
|
|
||||||
invoice.party.get_xrechnung_route_id = Mock(
|
schema_file = os.path.join(
|
||||||
return_value='xrechn-route-id-123')
|
os.path.dirname(__file__), 'os-UBL-2.1',
|
||||||
invoice.company.party.bank_accounts = [
|
'xsd', 'maindoc', 'UBL-CreditNote-2.1.xsd')
|
||||||
Mock(
|
|
||||||
spec=BankAccount,
|
|
||||||
currency=invoice.currency,
|
|
||||||
bank=Mock(spec=Bank, party=Mock(spec=Party, name='Bank')),
|
|
||||||
owners=[invoice.company.party],
|
|
||||||
numbers=[Mock(spec=BankNumber, type='other', number='123456')],
|
|
||||||
)]
|
|
||||||
invoice.description = 'description of invoice'
|
|
||||||
invoice.comment = 'note line 1\nnote line 2'
|
|
||||||
invoice.taxes[0].tax.rate = Decimal('0.1')
|
|
||||||
invoice.identifiers = [
|
|
||||||
Mock(
|
|
||||||
spec=Identifier,
|
|
||||||
type='edoc_route_id',
|
|
||||||
code='xrechn-route-id-123')
|
|
||||||
]
|
|
||||||
|
|
||||||
template = Template(invoice)
|
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
|
||||||
|
invoice_string = template.render(x)
|
||||||
schema_file = os.path.join(
|
invoice_xml = etree.fromstring(invoice_string)
|
||||||
os.path.dirname(__file__), 'os-UBL-2.1',
|
schema = etree.XMLSchema(etree.parse(schema_file))
|
||||||
'xsd', 'maindoc', 'UBL-CreditNote-2.1.xsd')
|
schema.assertValid(invoice_xml)
|
||||||
|
|
||||||
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
|
|
||||||
invoice_string = template.render(x)
|
|
||||||
invoice_xml = etree.fromstring(invoice_string)
|
|
||||||
schema = etree.XMLSchema(etree.parse(schema_file))
|
|
||||||
schema.assertValid(invoice_xml)
|
|
||||||
|
|
||||||
# invoice_string = template.render('XRechnung-2.2')
|
|
||||||
# with open('xrechnung-test-creditnote.xml', 'wt') as fhdl:
|
|
||||||
# fhdl.write(invoice_string.decode('utf8'))
|
|
||||||
|
|
||||||
# end EdocTestCase
|
# end EdocTestCase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
full copyright notices and license terms. -->
|
full copyright notices and license terms. -->
|
||||||
<data>
|
<data>
|
||||||
|
|
||||||
<xpath expr="/form/separator[@id='move']" position="after">
|
<xpath expr="/form/separator[@id='move']" position="before">
|
||||||
|
|
||||||
<separator id="edocument" colspan="4" string="eDocument"/>
|
<separator id="edocument" colspan="4" string="eDocument"/>
|
||||||
<field name="edocument_bank" colspan="2" height="200"/>
|
<field name="edocument_bank" colspan="2" height="200"/>
|
||||||
|
|
Loading…
Reference in a new issue