Add receivable/payable to company
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Add receivable/payable to company
|
||||
|
||||
Version 6.8.0 - 2023-05-01
|
||||
--------------------------
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -9,6 +9,7 @@ depends:
|
||||
xml:
|
||||
account.xml
|
||||
configuration.xml
|
||||
company.xml
|
||||
fiscalyear.xml
|
||||
period.xml
|
||||
journal.xml
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user