Merge branch 'main' into 7.0
This commit is contained in:
commit
d442390154
12 changed files with 319 additions and 17 deletions
|
@ -5,12 +5,17 @@
|
|||
|
||||
from trytond.pool import Pool
|
||||
from .edocument import XRechnung, FacturX
|
||||
from .bank import AccountNumber
|
||||
from .party import PartyConfiguration, Party
|
||||
from .configuration import Configuration, BankEdocumentRel
|
||||
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
AccountNumber,
|
||||
XRechnung,
|
||||
Configuration,
|
||||
BankEdocumentRel,
|
||||
FacturX,
|
||||
Party,
|
||||
PartyConfiguration,
|
||||
|
|
97
bank.py
Normal file
97
bank.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the edocument-module for Tryton from m-ds.de.
|
||||
# The COPYRIGHT file at the top level of this repository contains the
|
||||
# full copyright notices and license terms.
|
||||
|
||||
|
||||
from sql.conditionals import Case
|
||||
from trytond.pool import PoolMeta, Pool
|
||||
from trytond.model import fields
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
DEF_NONE = None
|
||||
|
||||
|
||||
class AccountNumber(metaclass=PoolMeta):
|
||||
__name__ = 'bank.account.number'
|
||||
|
||||
company_owned = fields.Function(fields.Boolean(
|
||||
string='Number belongs to Company',
|
||||
readonly=True),
|
||||
'get_company_owned',
|
||||
searcher='searcher_company_owned')
|
||||
|
||||
@classmethod
|
||||
def get_company_owned_sql(cls):
|
||||
""" get sql to search for bank acconts owned by company-party
|
||||
"""
|
||||
pool = Pool()
|
||||
Account = pool.get('bank.account')
|
||||
Number = pool.get('bank.account.number')
|
||||
Owners = pool.get('bank.account-party.party')
|
||||
Company = pool.get('company.company')
|
||||
context = Transaction().context
|
||||
|
||||
tab_acc = Account.__table__()
|
||||
tab_owner = Owners.__table__()
|
||||
tab_num = Number.__table__()
|
||||
|
||||
company_id = context.get('company', -1)
|
||||
party_id = -1
|
||||
if company_id and company_id > 0:
|
||||
party_id = Company(company_id).party.id
|
||||
|
||||
query = tab_num.join(
|
||||
tab_acc,
|
||||
condition=tab_num.account == tab_acc.id,
|
||||
).join(
|
||||
tab_owner,
|
||||
condition=tab_owner.account == tab_acc.id,
|
||||
).select(
|
||||
tab_num.id.as_('number'),
|
||||
Case(
|
||||
(tab_owner.owner == party_id, True),
|
||||
else_=False,
|
||||
).as_('owned'))
|
||||
return (tab_num, query)
|
||||
|
||||
@classmethod
|
||||
def searcher_company_owned(cls, name, clause):
|
||||
""" search in owned by party
|
||||
|
||||
Args:
|
||||
name (str): field name
|
||||
clause (list): search domain
|
||||
|
||||
Returns:
|
||||
list: updated search domain
|
||||
"""
|
||||
Operator = fields.SQL_OPERATORS[clause[1]]
|
||||
(tab_num, query) = cls.get_company_owned_sql()
|
||||
|
||||
query = query.select(
|
||||
query.number,
|
||||
where=Operator(query.owned, clause[2]))
|
||||
return [('id', 'in', query)]
|
||||
|
||||
@classmethod
|
||||
def get_company_owned(cls, records, names):
|
||||
""" get list of bank account numbers owned by company
|
||||
"""
|
||||
cursor = Transaction().connection.cursor()
|
||||
|
||||
result = {x: {y.id: False for y in records} for x in names}
|
||||
|
||||
(tab_num, query) = cls.get_company_owned_sql()
|
||||
query.where = tab_num.id.in_([x.id for x in records])
|
||||
cursor.execute(*query)
|
||||
lines = cursor.fetchall()
|
||||
|
||||
for line in lines:
|
||||
values = {'company_owned': line[1]}
|
||||
|
||||
for name in names:
|
||||
result[name][line[0]] = values.get(name)
|
||||
return result
|
||||
|
||||
# end AccountNumber
|
35
configuration.py
Normal file
35
configuration.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the edocument-module for Tryton from m-ds.de.
|
||||
# The COPYRIGHT file at the top level of this repository contains the
|
||||
# full copyright notices and license terms.
|
||||
|
||||
|
||||
from trytond.pool import PoolMeta
|
||||
from trytond.model import ModelSQL, fields
|
||||
|
||||
|
||||
class Configuration(metaclass=PoolMeta):
|
||||
__name__ = 'account.configuration'
|
||||
|
||||
edocument_bank = fields.Many2Many(
|
||||
string='Bank accounts',
|
||||
relation_name='edocument_xrechnung.bank_rel',
|
||||
origin='config', target='bankaccount',
|
||||
filter=[('company_owned', '=', True)],
|
||||
help='The bank accounts listed here are output in the invoice XML.')
|
||||
|
||||
# end Configuration
|
||||
|
||||
|
||||
class BankEdocumentRel(ModelSQL):
|
||||
'Bank - eDocument - Relation'
|
||||
__name__ = 'edocument_xrechnung.bank_rel'
|
||||
|
||||
bankaccount = fields.Many2One(
|
||||
string='Account', model_name='bank.account.number',
|
||||
required=True, ondelete='CASCADE')
|
||||
config = fields.Many2One(
|
||||
string='Configuration', model_name='account.configuration',
|
||||
required=True, ondelete='CASCADE')
|
||||
|
||||
# end BankEdocumentRel
|
15
configuration.xml
Normal file
15
configuration.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the edocument-module for Tryton from m-ds.de.
|
||||
The COPYRIGHT file at the top level of this repository contains the
|
||||
full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="configuration_view_form">
|
||||
<field name="model">account.configuration</field>
|
||||
<field name="inherit" ref="account.configuration_view_form"/>
|
||||
<field name="name">configuration_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
|
@ -6,3 +6,6 @@ https://github.com/itplr-kosit
|
|||
|
||||
validator:
|
||||
- https://erechnungsvalidator.service-bw.de/
|
||||
|
||||
überweisungsdaten
|
||||
https://portal3.gefeg.com/projectdata/invoice/deliverables/installed/publishingproject/zugferd%202.0.1%20-%20facturx%201.03/en%2016931%20%E2%80%93%20facturx%201.03%20%E2%80%93%20zugferd%202.0.1%20-%20basic.scm/html/de/021.htm?https://portal3.gefeg.com/projectdata/invoice/deliverables/installed/publishingproject/zugferd%202.0.1%20-%20facturx%201.03/en%2016931%20%E2%80%93%20facturx%201.03%20%E2%80%93%20zugferd%202.0.1%20-%20basic.scm/html/de/02134.htm
|
||||
|
|
28
locale/de.po
28
locale/de.po
|
@ -113,3 +113,31 @@ msgstr "Allgemeine indirekte Steuer der kanarischen Inseln"
|
|||
msgctxt "selection:account.tax,xrtax_category:"
|
||||
msgid "Tax on production; services and imports in Ceuta and Melilla"
|
||||
msgstr "Steuer für Produktion; Dienstleistungen und Einfuhr in Ceuta und Melilla"
|
||||
|
||||
|
||||
#########################
|
||||
# account.configuration #
|
||||
#########################
|
||||
msgctxt "field:account.configuration,edocument_bank:"
|
||||
msgid "Bank accounts"
|
||||
msgstr "Bankkonten"
|
||||
|
||||
msgctxt "help:account.configuration,edocument_bank:"
|
||||
msgid "The bank accounts listed here are output in the invoice XML."
|
||||
msgstr "Die hier aufgeführten Bankkonten werden in der Rechnungs-XML ausgegeben."
|
||||
|
||||
|
||||
################################
|
||||
# edocument_xrechnung.bank_rel #
|
||||
################################
|
||||
msgctxt "model:edocument_xrechnung.bank_rel,name:"
|
||||
msgid "Bank - eDocument - Relation"
|
||||
msgstr "Bank - eDocument - Verknüpfung"
|
||||
|
||||
msgctxt "field:edocument_xrechnung.bank_rel,bankaccount:"
|
||||
msgid "Account"
|
||||
msgstr "Konto"
|
||||
|
||||
msgctxt "field:edocument_xrechnung.bank_rel,config:"
|
||||
msgid "Configuration"
|
||||
msgstr "Konfiguration"
|
||||
|
|
20
locale/en.po
20
locale/en.po
|
@ -98,3 +98,23 @@ msgctxt "selection:account.tax,xrtax_category:"
|
|||
msgid "Tax on production; services and imports in Ceuta and Melilla"
|
||||
msgstr "Tax on production; services and imports in Ceuta and Melilla"
|
||||
|
||||
msgctxt "field:account.configuration,edocument_bank:"
|
||||
msgid "Bank accounts"
|
||||
msgstr "Bank accounts"
|
||||
|
||||
msgctxt "help:account.configuration,edocument_bank:"
|
||||
msgid "The bank accounts listed here are output in the invoice XML."
|
||||
msgstr "The bank accounts listed here are output in the invoice XML."
|
||||
|
||||
msgctxt "model:edocument_xrechnung.bank_rel,name:"
|
||||
msgid "Bank - eDocument - Relation"
|
||||
msgstr "Bank - eDocument - Relation"
|
||||
|
||||
msgctxt "field:edocument_xrechnung.bank_rel,bankaccount:"
|
||||
msgid "Account"
|
||||
msgstr "Account"
|
||||
|
||||
msgctxt "field:edocument_xrechnung.bank_rel,config:"
|
||||
msgid "Configuration"
|
||||
msgstr "Configuration"
|
||||
|
||||
|
|
22
mixin.py
22
mixin.py
|
@ -9,6 +9,7 @@ import html
|
|||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
from trytond.tools import cached_property
|
||||
from trytond.pool import Pool
|
||||
|
||||
|
||||
class EdocumentMixin(object):
|
||||
|
@ -16,6 +17,25 @@ class EdocumentMixin(object):
|
|||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def company_bank_accounts(self):
|
||||
""" get leist of bank account numbers, defined in config
|
||||
|
||||
Returns:
|
||||
list: records of model bank.account.number
|
||||
"""
|
||||
Configuration = Pool().get('account.configuration')
|
||||
|
||||
result = []
|
||||
cfg1 = Configuration.get_singleton()
|
||||
if cfg1 and cfg1.edocument_bank:
|
||||
result.extend(list(cfg1.edocument_bank))
|
||||
else:
|
||||
result.extend([
|
||||
y
|
||||
for x in self.invoice.company.party.bank_accounts
|
||||
for y in x.numbers])
|
||||
return result
|
||||
|
||||
@cached_property
|
||||
def seller_trade_address(self):
|
||||
""" get address of seller, throw exception if incomplete
|
||||
|
@ -151,6 +171,6 @@ class EdocumentMixin(object):
|
|||
""" replace critical chars
|
||||
"""
|
||||
if text:
|
||||
return html.quote(text)
|
||||
return html.escape(text)
|
||||
|
||||
# end EdocumentMixin
|
||||
|
|
|
@ -12,7 +12,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<udt:DateTimeString format="102">${value.strftime('%Y%m%d')}</udt:DateTimeString>
|
||||
</py:def>
|
||||
<py:def function="TradeParty(party, address=None, tax_identifier=None)">
|
||||
<ram:Name>${party.name}</ram:Name>
|
||||
<ram:Name>${this.quote_text(party.name)}</ram:Name>
|
||||
<ram:SpecifiedLegalOrganization>
|
||||
<py:for each="id, attrs in this.party_legal_ids(party, address)">
|
||||
<ram:ID py:attrs="attrs">${id}</ram:ID>
|
||||
|
@ -26,13 +26,13 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<py:def function="TradeAddress(address)">
|
||||
<ram:PostcodeCode py:if="address.postal_code">${address.postal_code}</ram:PostcodeCode>
|
||||
<py:with vars="lines = (address.street or '').splitlines()">
|
||||
<ram:LineOne py:if="len(lines) > 0">${lines[0]}</ram:LineOne>
|
||||
<ram:LineTwo py:if="len(lines) > 1">${lines[1]}</ram:LineTwo>
|
||||
<ram:LineThree py:if="len(lines) > 2">${lines[2]}</ram:LineThree>
|
||||
<ram:LineOne py:if="len(lines) > 0">${this.quote_text(lines[0])}</ram:LineOne>
|
||||
<ram:LineTwo py:if="len(lines) > 1">${this.quote_text(lines[1])}</ram:LineTwo>
|
||||
<ram:LineThree py:if="len(lines) > 2">${(lines[2])}</ram:LineThree>
|
||||
</py:with>
|
||||
<ram:CityName py:if="address.city">${address.city}</ram:CityName>
|
||||
<ram:CityName py:if="address.city">${(address.city)}</ram:CityName>
|
||||
<ram:CountryID py:if="address.country">${address.country.code}</ram:CountryID>
|
||||
<ram:CountrySubDivisionName py:if="address.subdivision">${address.subdivision.name}</ram:CountrySubDivisionName>
|
||||
<ram:CountrySubDivisionName py:if="address.subdivision">${this.quote_text(address.subdivision.name)}</ram:CountrySubDivisionName>
|
||||
</py:def>
|
||||
<py:def function="TradeTax(tax, amount=None, base=None)">
|
||||
<ram:ApplicableTradeTax>
|
||||
|
@ -50,8 +50,8 @@ this repository contains the full copyright notices and license terms. -->
|
|||
</ram:GuidelineSpecifiedDocumentContextParameter>
|
||||
</rsm:ExchangedDocumentContext>
|
||||
<rsm:ExchangedDocument>
|
||||
<ram:ID>${this.invoice.number}</ram:ID>
|
||||
<ram:Name py:if="this.invoice.description">${this.invoice.description}</ram:Name>
|
||||
<ram:ID>${this.quote_text(this.invoice.number)}</ram:ID>
|
||||
<ram:Name py:if="this.invoice.description">${this.quote_text(this.invoice.description)}</ram:Name>
|
||||
<ram:TypeCode>${this.type_code}</ram:TypeCode>
|
||||
<ram:IssueDateTime>
|
||||
${DateTime(this.invoice.invoice_date)}
|
||||
|
@ -71,8 +71,8 @@ this repository contains the full copyright notices and license terms. -->
|
|||
</ram:AssociatedDocumentLineDocument>
|
||||
<ram:SpecifiedTradeProduct>
|
||||
<ram:ID py:if="line.product and line.product.code">${line.product.code}</ram:ID>
|
||||
<ram:Name>${line.product.name if line.product else ''}</ram:Name>
|
||||
<ram:Description py:if="line.description">${line.description}</ram:Description>
|
||||
<ram:Name>${this.quote_text(line.product.name if line.product else '')}</ram:Name>
|
||||
<ram:Description py:if="line.description">${this.quote_text(line.description)}</ram:Description>
|
||||
</ram:SpecifiedTradeProduct>
|
||||
<ram:SpecifiedLineTradeAgreement>
|
||||
<ram:NetPriceProductTradePrice>
|
||||
|
@ -99,10 +99,10 @@ this repository contains the full copyright notices and license terms. -->
|
|||
${TradeParty(this.buyer_trade_party, this.buyer_trade_address, this.buyer_trade_tax_identifier)}
|
||||
</ram:BuyerTradeParty>
|
||||
<ram:SellerOrderReferencedDocument py:if="this.invoice.type == 'in'">
|
||||
<ram:IssuerAssignedID>${this.invoice.reference}</ram:IssuerAssignedID>
|
||||
<ram:IssuerAssignedID>${this.quote_text(this.invoice.reference)}</ram:IssuerAssignedID>
|
||||
</ram:SellerOrderReferencedDocument>
|
||||
<ram:BuyerOrderReferencedDocument py:if="this.invoice.type == 'out'">
|
||||
<ram:IssuerAssignedID>${this.invoice.reference}</ram:IssuerAssignedID>
|
||||
<ram:IssuerAssignedID>${this.quote_text(this.invoice.reference)}</ram:IssuerAssignedID>
|
||||
</ram:BuyerOrderReferencedDocument>
|
||||
</ram:ApplicableHeaderTradeAgreement>
|
||||
<ram:ApplicableHeaderTradeDelivery>
|
||||
|
@ -114,11 +114,26 @@ this repository contains the full copyright notices and license terms. -->
|
|||
</ram:ShipFromTradeParty>
|
||||
</ram:ApplicableHeaderTradeDelivery>
|
||||
<ram:ApplicableHeaderTradeSettlement>
|
||||
<ram:PaymentReference>${this.payment_reference}</ram:PaymentReference>
|
||||
<ram:PaymentReference>${this.quote_text(this.payment_reference)}</ram:PaymentReference>
|
||||
<ram:InvoiceCurrencyCode>${this.invoice.currency.code}</ram:InvoiceCurrencyCode>
|
||||
<ram:SpecifiedTradeSettlementPaymentMeans>
|
||||
<ram:TypeCode>1</ram:TypeCode> <!-- Instrument not defined -->
|
||||
</ram:SpecifiedTradeSettlementPaymentMeans>
|
||||
<py:if test="len(this.company_bank_accounts()) == 0">
|
||||
<ram:SpecifiedTradeSettlementPaymentMeans>
|
||||
<ram:TypeCode>1</ram:TypeCode>
|
||||
</ram:SpecifiedTradeSettlementPaymentMeans>
|
||||
</py:if>
|
||||
<py:for each="banknumber in this.company_bank_accounts()">
|
||||
<ram:SpecifiedTradeSettlementPaymentMeans>
|
||||
<ram:TypeCode>30</ram:TypeCode>
|
||||
<ram:Information>Wire transfer</ram:Information>
|
||||
<ram:PayeePartyCreditorFinancialAccount>
|
||||
<ram:IBANID>${banknumber.number_compact}</ram:IBANID>
|
||||
<ram:AccountName>${this.quote_text(banknumber.account.bank.party.rec_name)}</ram:AccountName>
|
||||
</ram:PayeePartyCreditorFinancialAccount>
|
||||
<ram:PayeeSpecifiedCreditorFinancialInstitution>
|
||||
<ram:BICID>${banknumber.account.bank.bic}</ram:BICID>
|
||||
</ram:PayeeSpecifiedCreditorFinancialInstitution>
|
||||
</ram:SpecifiedTradeSettlementPaymentMeans>
|
||||
</py:for>
|
||||
<py:for each="tax in this.invoice.taxes">
|
||||
${TradeTax(tax.tax, tax.amount, tax.base)}
|
||||
</py:for>
|
||||
|
|
|
@ -11,6 +11,7 @@ from datetime import date
|
|||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond.modules.edocument_uncefact.tests.test_module import get_invoice
|
||||
from trytond.modules.company.tests import create_company, set_company
|
||||
from trytond.exceptions import UserError
|
||||
|
||||
|
||||
|
@ -18,6 +19,53 @@ class EdocTestCase(ModuleTestCase):
|
|||
'Test e-rechnung module'
|
||||
module = 'edocument_xrechnung'
|
||||
|
||||
@with_transaction()
|
||||
def test_xrechn_bank_account_owned(self):
|
||||
""" check field 'company_owned' on bank.account.number
|
||||
"""
|
||||
pool = Pool()
|
||||
BankAccount = pool.get('bank.account')
|
||||
AccountNumber = pool.get('bank.account.number')
|
||||
Bank = pool.get('bank')
|
||||
Party = pool.get('party.party')
|
||||
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
bank_party, = Party.create([{
|
||||
'name': 'Bank 123',
|
||||
'addresses': [('create', [{}])]}])
|
||||
customer_party, = Party.create([{
|
||||
'name': 'Someone',
|
||||
'addresses': [('create', [{}])]}])
|
||||
bank, = Bank.create([{'party': bank_party.id}])
|
||||
|
||||
acc_company, acc_other, = BankAccount.create([
|
||||
{
|
||||
'bank': bank.id,
|
||||
'owners': [('add', [company.party.id])],
|
||||
'numbers': [('create', [
|
||||
{'type': 'iban', 'number': 'DE02300209000106531065'}])]
|
||||
}, {
|
||||
'bank': bank.id,
|
||||
'owners': [('add', [customer_party.id])],
|
||||
'numbers': [('create', [
|
||||
{'type': 'iban', 'number': 'DE02200505501015871393'}])]
|
||||
}])
|
||||
self.assertEqual(len(acc_company.numbers), 1)
|
||||
self.assertEqual(acc_company.numbers[0].company_owned, True)
|
||||
self.assertEqual(len(acc_other.numbers), 1)
|
||||
self.assertEqual(acc_other.numbers[0].company_owned, False)
|
||||
|
||||
company_numbers = AccountNumber.search(
|
||||
[('company_owned', '=', True)])
|
||||
self.assertEqual(len(company_numbers), 1)
|
||||
self.assertEqual(company_numbers[0].id, acc_company.numbers[0].id)
|
||||
|
||||
other_numbers = AccountNumber.search(
|
||||
[('company_owned', '=', False)])
|
||||
self.assertEqual(len(other_numbers), 1)
|
||||
self.assertEqual(other_numbers[0].id, acc_other.numbers[0].id)
|
||||
|
||||
@with_transaction()
|
||||
def test_xrechn_check_validator(self):
|
||||
""" check validation of optional route-id
|
||||
|
|
|
@ -7,4 +7,5 @@ depends:
|
|||
account_invoice
|
||||
xml:
|
||||
message.xml
|
||||
configuration.xml
|
||||
party.xml
|
||||
|
|
15
view/configuration_form.xml
Normal file
15
view/configuration_form.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the account-datev-module from m-ds for Tryton.
|
||||
The COPYRIGHT file at the top level of this repository contains the
|
||||
full copyright notices and license terms. -->
|
||||
<data>
|
||||
|
||||
<xpath expr="/form/separator[@id='currency_exchange']" position="before">
|
||||
|
||||
<separator id="edocument" colspan="4" string="eDocument"/>
|
||||
<field name="edocument_bank" colspan="2" height="200"/>
|
||||
<newline/>
|
||||
|
||||
</xpath>
|
||||
|
||||
</data>
|
Loading…
Reference in a new issue