Add tax identifiers to company
This commit is contained in:
@@ -1383,9 +1383,12 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
|
||||
"Returns the field name of invoice_sequence to use"
|
||||
return f'{self.type}_{self.sequence_type}_sequence'
|
||||
|
||||
def get_tax_identifier(self):
|
||||
def get_tax_identifier(self, pattern=None):
|
||||
"Return the default computed tax identifier"
|
||||
return self.company.party.tax_identifier
|
||||
pattern = pattern.copy() if pattern is not None else {}
|
||||
if self.invoice_address and self.invoice_address.country:
|
||||
pattern.setdefault('country', self.invoice_address.country.id)
|
||||
return self.company.get_tax_identifier(pattern)
|
||||
|
||||
@property
|
||||
def invoice_report_versioned(self):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
* Add tax identifiers to company
|
||||
|
||||
Version 7.4.0 - 2024-11-04
|
||||
--------------------------
|
||||
|
||||
@@ -12,6 +12,7 @@ __all__ = ['register', 'CompanyReport']
|
||||
def register():
|
||||
Pool.register(
|
||||
company.Company,
|
||||
company.CompanyTaxIdentifier,
|
||||
company.CompanyLogoCache,
|
||||
company.Employee,
|
||||
company.CompanyConfigStart,
|
||||
|
||||
@@ -12,10 +12,11 @@ except ImportError:
|
||||
PIL = None
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.model import ModelSQL, ModelView, Unique, fields
|
||||
from trytond.model import (
|
||||
MatchMixin, ModelSQL, ModelView, Unique, fields, sequence_ordered)
|
||||
from trytond.model.exceptions import SQLConstraintError
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, If
|
||||
from trytond.pyson import Bool, Eval, If
|
||||
from trytond.report import Report
|
||||
from trytond.tools import timezone as tz
|
||||
from trytond.transaction import Transaction
|
||||
@@ -60,6 +61,10 @@ class Company(ModelSQL, ModelView):
|
||||
help="Used to compute the today date.")
|
||||
employees = fields.One2Many('company.employee', 'company', 'Employees',
|
||||
help="Add employees to the company.")
|
||||
tax_identifiers = fields.One2Many(
|
||||
'company.company.tax_identifier', 'company', "Tax Identifiers",
|
||||
help="Uses the first identifier that matches the criteria.\n"
|
||||
"If none match, uses the party's tax identifier.")
|
||||
|
||||
@property
|
||||
def header_used(self):
|
||||
@@ -195,6 +200,80 @@ class Company(ModelSQL, ModelView):
|
||||
caches = [c for r in companies for c in r.logo_cache]
|
||||
Cache.delete(caches)
|
||||
|
||||
def get_tax_identifier(self, pattern=None):
|
||||
if pattern is None:
|
||||
pattern = {}
|
||||
for record in self.tax_identifiers:
|
||||
if record.match(pattern):
|
||||
return record.tax_identifier
|
||||
return self.party.tax_identifier
|
||||
|
||||
|
||||
class CompanyTaxIdentifier(
|
||||
MatchMixin, sequence_ordered(), ModelSQL, ModelView):
|
||||
__name__ = 'company.company.tax_identifier'
|
||||
|
||||
company = fields.Many2One(
|
||||
'company.company', "Company", required=True, ondelete='CASCADE')
|
||||
company_party = fields.Function(
|
||||
fields.Many2One(
|
||||
'party.party', "Company Party",
|
||||
context={
|
||||
'company': Eval('company', None),
|
||||
}),
|
||||
'on_change_with_company_party')
|
||||
|
||||
country = fields.Many2One(
|
||||
'country.country', "Country", ondelete='RESTRICT',
|
||||
states={
|
||||
'invisible': Bool(Eval('organization')),
|
||||
},
|
||||
help="Applies only to addresses in this country.")
|
||||
organization = fields.Many2One(
|
||||
'country.organization', "Organization",
|
||||
states={
|
||||
'invisible': Bool(Eval('country')),
|
||||
},
|
||||
help="Applies only to addresses in this organization.")
|
||||
|
||||
tax_identifier = fields.Many2One(
|
||||
'party.identifier', "Tax Identifier",
|
||||
help="The identifier used for tax report.")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
pool = Pool()
|
||||
Party = pool.get('party.party')
|
||||
super().__setup__()
|
||||
cls.__access__.add('company')
|
||||
tax_identifier_types = Party.tax_identifier_types()
|
||||
cls.tax_identifier.domain = [
|
||||
('party', '=', Eval('company_party', -1)),
|
||||
('type', 'in', tax_identifier_types),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def default_company(cls):
|
||||
return Transaction().context.get('company')
|
||||
|
||||
@fields.depends('company', '_parent_company.party')
|
||||
def on_change_with_company_party(self, name=None):
|
||||
if self.company:
|
||||
return self.company.party
|
||||
|
||||
def match(self, pattern, match_none=False):
|
||||
if 'country' in pattern:
|
||||
pattern = pattern.copy()
|
||||
country = pattern.pop('country')
|
||||
if country is not None or match_none:
|
||||
if self.country and self.country.id != country:
|
||||
return False
|
||||
if (self.organization
|
||||
and country not in [
|
||||
c.id for c in self.organization.countries]):
|
||||
return False
|
||||
return super().match(pattern, match_none=match_none)
|
||||
|
||||
|
||||
class CompanyLogoCache(ModelSQL):
|
||||
__name__ = 'company.company.logo.cache'
|
||||
|
||||
@@ -96,6 +96,18 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="perm_delete" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="company_tax_identifier_view_form">
|
||||
<field name="model">company.company.tax_identifier</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">company_tax_identifier_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="company_tax_identifier_view_list">
|
||||
<field name="model">company.company.tax_identifier</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">company_tax_identifier_list</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="user_view_form">
|
||||
<field name="model">res.user</field>
|
||||
<field name="inherit" ref="res.user_view_form"/>
|
||||
|
||||
@@ -432,5 +432,68 @@ class CompanyTestCase(
|
||||
|
||||
datetime.now.assert_called_with(tz.ZoneInfo('CET'))
|
||||
|
||||
@with_transaction()
|
||||
def test_company_tax_identifier(self):
|
||||
"Test company tax identifier"
|
||||
pool = Pool()
|
||||
Country = pool.get('country.country')
|
||||
Organization = pool.get('country.organization')
|
||||
Identifier = pool.get('party.identifier')
|
||||
TaxIdentifier = pool.get('company.company.tax_identifier')
|
||||
|
||||
company = create_company()
|
||||
|
||||
fr_vat = Identifier(
|
||||
party=company.party, type='fr_vat', code="FR23334175221")
|
||||
fr_vat.save()
|
||||
be_vat = Identifier(
|
||||
party=company.party, type='be_vat', code="BE403019261")
|
||||
be_vat.save()
|
||||
|
||||
belgium = Country(name="Belgium")
|
||||
belgium.save()
|
||||
france = Country(name="France")
|
||||
france.save()
|
||||
germany = Country(name="Germany")
|
||||
germany.save()
|
||||
switzerland = Country(name="Switzerland")
|
||||
switzerland.save()
|
||||
europe = Organization(
|
||||
name="Europe",
|
||||
members=[
|
||||
{'country': belgium},
|
||||
{'country': france},
|
||||
{'country': germany},
|
||||
])
|
||||
europe.save()
|
||||
|
||||
TaxIdentifier.create([{
|
||||
'company': company,
|
||||
'country': france,
|
||||
'tax_identifier': fr_vat,
|
||||
}, {
|
||||
'company': company,
|
||||
'country': belgium,
|
||||
'tax_identifier': be_vat,
|
||||
}, {
|
||||
'company': company,
|
||||
'organization': europe,
|
||||
'tax_identifier': be_vat,
|
||||
}])
|
||||
|
||||
self.assertEqual(company.party.tax_identifier, fr_vat)
|
||||
self.assertEqual(company.get_tax_identifier({
|
||||
'country': france.id,
|
||||
}), fr_vat)
|
||||
self.assertEqual(company.get_tax_identifier({
|
||||
'country': belgium.id,
|
||||
}), be_vat)
|
||||
self.assertEqual(company.get_tax_identifier({
|
||||
'country': germany.id,
|
||||
}), be_vat)
|
||||
self.assertEqual(company.get_tax_identifier({
|
||||
'country': switzerland,
|
||||
}), fr_vat)
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
|
||||
@@ -12,6 +12,9 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<page name="employees" col="1">
|
||||
<field name="employees"/>
|
||||
</page>
|
||||
<page name="tax_identifiers">
|
||||
<field name="tax_identifiers" colspan="4"/>
|
||||
</page>
|
||||
<page string="Reports" col="1" id="reports">
|
||||
<separator name="logo"/>
|
||||
<field name="logo" widget="image" width="800" height="400"/>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form cursor="tax_identifier">
|
||||
<label name="company"/>
|
||||
<field name="company"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
|
||||
<label name="tax_identifier"/>
|
||||
<field name="tax_identifier" colspan="3"/>
|
||||
|
||||
<separator id="pattern" colspan="4"/>
|
||||
|
||||
<label name="country"/>
|
||||
<field name="country"/>
|
||||
<label name="organization"/>
|
||||
<field name="organization"/>
|
||||
</form>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree sequence="sequence">
|
||||
<field name="company" expand="1"/>
|
||||
<field name="country" expand="1"/>
|
||||
<field name="organization" expand="1"/>
|
||||
<field name="tax_identifier" expand="2"/>
|
||||
</tree>
|
||||
@@ -64,6 +64,17 @@ class Invoice(metaclass=PoolMeta):
|
||||
return [('lines.origin.sale' + clause[0][len(name):],
|
||||
*clause[1:3], 'sale.line', *clause[3:])]
|
||||
|
||||
def get_tax_identifier(self, pattern=None):
|
||||
if self.sales:
|
||||
pattern = pattern.copy() if pattern is not None else {}
|
||||
shipment_countries = {
|
||||
s.shipment_address.country for s in self.sales
|
||||
if s.shipment_address and s.shipment_address.country}
|
||||
if len(shipment_countries) == 1:
|
||||
shipment_country, = shipment_countries
|
||||
pattern.setdefault('country', shipment_country.id)
|
||||
return super().get_tax_identifier(pattern=pattern)
|
||||
|
||||
@classmethod
|
||||
@process_sale
|
||||
def delete(cls, invoices):
|
||||
|
||||
Reference in New Issue
Block a user