Add receivable/payable to company

This commit is contained in:
Cédric Krier
2023-04-07 18:24:09 +02:00
parent d547689e0e
commit 84d83120c5
8 changed files with 243 additions and 26 deletions
+1
View File
@@ -1,3 +1,4 @@
* Add receivable/payable to company
Version 6.8.0 - 2023-05-01
--------------------------
+122
View File
@@ -1,14 +1,136 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from decimal import Decimal
from sql import Literal, Null
from sql.aggregate import Sum
from sql.conditionals import Coalesce
from trytond.i18n import gettext
from trytond.model import fields
from trytond.model.exceptions import AccessError
from trytond.modules.currency.fields import Monetary
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice, reduce_ids
from trytond.transaction import Transaction
class Company(metaclass=PoolMeta):
__name__ = 'company.company'
receivable = fields.Function(Monetary(
"Receivable", currency='currency', digits='currency'),
'get_receivable_payable', searcher='search_receivable_payable')
payable = fields.Function(Monetary(
"Payable", currency='currency', digits='currency'),
'get_receivable_payable', searcher='search_receivable_payable')
receivable_today = fields.Function(Monetary(
"Receivable Today", currency='currency', digits='currency'),
'get_receivable_payable', searcher='search_receivable_payable')
payable_today = fields.Function(Monetary(
"Payable Today", currency='currency', digits='currency'),
'get_receivable_payable', searcher='search_receivable_payable')
@classmethod
def get_receivable_payable(cls, companies, names):
amounts = {}
pool = Pool()
MoveLine = pool.get('account.move.line')
Account = pool.get('account.account')
AccountType = pool.get('account.account.type')
Date = pool.get('ir.date')
cursor = Transaction().connection.cursor()
line = MoveLine.__table__()
account = Account.__table__()
account_type = AccountType.__table__()
for name in names:
assert name in {
'receivable', 'payable', 'receivable_today', 'payable_today'}
amounts[name] = dict.fromkeys(map(int, companies), Decimal(0))
exp = {
c.id: Decimal(str(10.0 ** -c.currency.digits)) for c in companies}
today = Date.today()
amount = Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0))
for name in names:
if name.endswith('_today'):
code = name[:-len('_today')]
today_where = (
(line.maturity_date <= today)
| (line.maturity_date == Null))
else:
code = name
today_where = Literal(True)
for sub_companies in grouped_slice(companies):
sub_ids = [p.id for p in sub_companies]
company_where = reduce_ids(account.company, sub_ids)
cursor.execute(*line
.join(account,
condition=account.id == line.account)
.join(account_type,
condition=account.type == account_type.id)
.select(account.company, amount,
where=(getattr(account_type, code)
& (line.reconciliation == Null)
& company_where
& today_where),
group_by=account.company))
for company_id, value in cursor:
# SQLite uses float for SUM
if not isinstance(value, Decimal):
value = Decimal(str(value))
amounts[name][company_id] = value.quantize(exp[company_id])
return amounts
@classmethod
def search_receivable_payable(cls, name, clause):
pool = Pool()
MoveLine = pool.get('account.move.line')
Account = pool.get('account.account')
AccountType = pool.get('account.account.type')
Date = pool.get('ir.date')
line = MoveLine.__table__()
account = Account.__table__()
account_type = AccountType.__table__()
assert name in {
'receivable', 'payable', 'receivable_today', 'payable_today'}
_, operator, value = clause
today = Date.today()
if name.endswith('_today'):
code = name[:-len('_today')]
today_query = ((line.maturity_date <= today)
| (line.maturity_date == Null))
else:
code = name
today_query = Literal(True)
Operator = fields.SQL_OPERATORS[operator]
# Need to cast numeric for sqlite
cast_ = MoveLine.debit.sql_cast
amount = cast_(Sum(Coalesce(line.debit, 0) - Coalesce(line.credit, 0)))
if operator in {'in', 'not in'}:
value = [cast_(Literal(Decimal(v or 0))) for v in value]
else:
value = cast_(Literal(Decimal(value or 0)))
query = (line
.join(account, condition=account.id == line.account)
.join(account_type, condition=account.type == account_type.id)
.select(account.company,
where=(getattr(account_type, code)
& (line.reconciliation == Null)
& today_query),
group_by=account.company,
having=Operator(amount, value)))
return [('id', 'in', query)]
@classmethod
def write(cls, *args):
pool = Pool()
+58
View File
@@ -0,0 +1,58 @@
<?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. -->
<tryton>
<data>
<record model="ir.ui.view" id="company_view_list">
<field name="model">company.company</field>
<field name="inherit" ref="company.company_view_list"/>
<field name="name">company_list</field>
</record>
<record model="ir.ui.view" id="company_view_form">
<field name="model">company.company</field>
<field name="inherit" ref="company.company_view_form"/>
<field name="name">company_form</field>
</record>
<record model="ir.model.field.access" id="access_company_receivable">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'receivable')]"/>
<field name="perm_read" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_company_receivable_account">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'receivable')]"/>
<field name="group" ref="group_account"/>
<field name="perm_read" eval="True"/>
</record>
<record model="ir.model.field.access" id="access_company_payable">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'payable')]"/>
<field name="perm_read" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_company_payable_account">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'payable')]"/>
<field name="group" ref="group_account"/>
<field name="perm_read" eval="True"/>
</record>
<record model="ir.model.field.access" id="access_company_receivable_today">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'receivable_today')]"/>
<field name="perm_read" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_company_receivable_today_account">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'receivable_today')]"/>
<field name="group" ref="group_account"/>
<field name="perm_read" eval="True"/>
</record>
<record model="ir.model.field.access" id="access_company_payable_today">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'payable_today')]"/>
<field name="perm_read" eval="False"/>
</record>
<record model="ir.model.field.access" id="access_company_payable_today_account">
<field name="field" search="[('model.model', '=', 'company.company'), ('name', '=', 'payable_today')]"/>
<field name="group" ref="group_account"/>
<field name="perm_read" eval="True"/>
</record>
</data>
</tryton>
+27 -19
View File
@@ -1330,6 +1330,7 @@ class AccountTestCase(
def test_receivable_payable(self):
'Test party receivable payable'
pool = Pool()
Company = pool.get('company.company')
Party = pool.get('party.party')
FiscalYear = pool.get('account.fiscalyear')
Journal = pool.get('account.journal')
@@ -1399,27 +1400,34 @@ class AccountTestCase(
def check_fields():
party_test = Party(party.id)
company_test = Company(company.id)
for field, value in [('receivable', Decimal('300')),
('receivable_today', Decimal('100')),
('payable', Decimal('90')),
('payable_today', Decimal('30')),
for record, Model in [
(party_test, Party),
(company_test, Company),
]:
msg = 'field: %s, value: %s' % (field, value)
self.assertEqual(
getattr(party_test, field), value, msg=msg)
self.assertEqual(
Party.search([(field, '=', value)]),
[party_test], msg=msg)
self.assertEqual(
Party.search([(field, 'in', [value])]),
[party_test], msg=msg)
self.assertEqual(
Party.search([(field, '!=', value)]),
[], msg=msg)
self.assertEqual(
Party.search([(field, 'not in', [value])]),
[], msg=msg)
for field, value in [
('receivable', Decimal('300')),
('receivable_today', Decimal('100')),
('payable', Decimal('90')),
('payable_today', Decimal('30')),
]:
with self.subTest(
record=record, field=field, value=value):
self.assertEqual(
getattr(record, field), value)
self.assertEqual(
Model.search([(field, '=', value)]),
[record])
self.assertEqual(
Model.search([(field, 'in', [value])]),
[record])
self.assertEqual(
Model.search([(field, '!=', value)]),
[])
self.assertEqual(
Model.search([(field, 'not in', [value])]),
[])
check_fields()
close_fiscalyear(fiscalyear)
+1
View File
@@ -9,6 +9,7 @@ depends:
xml:
account.xml
configuration.xml
company.xml
fiscalyear.xml
period.xml
journal.xml
+19
View File
@@ -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. -->
<data>
<xpath expr="/form/notebook" position="inside">
<page string="Accounting" id="accounting">
<separator string="Account" colspan="4" id="account"/>
<label name="receivable_today"/>
<field name="receivable_today"/>
<label name="payable_today"/>
<field name="payable_today"/>
<label name="receivable"/>
<field name="receivable"/>
<label name="payable"/>
<field name="payable"/>
</page>
</xpath>
</data>
+9
View File
@@ -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. -->
<data>
<xpath expr="/tree" position="inside">
<field name="receivable_today" optional="0"/>
<field name="payable_today" optional="0"/>
</xpath>
</data>
@@ -2,12 +2,11 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook" position="inside">
<page string="Invoice" id="invoice">
<label name="purchase_taxes_expense"/>
<field name="purchase_taxes_expense"/>
<label name="cancel_invoice_out"/>
<field name="cancel_invoice_out"/>
</page>
<xpath expr="//page[@id='accounting']" position="inside">
<separator string="Invoice" colspan="4" id="invoice"/>
<label name="purchase_taxes_expense"/>
<field name="purchase_taxes_expense"/>
<label name="cancel_invoice_out"/>
<field name="cancel_invoice_out"/>
</xpath>
</data>