report deposits at statement generation

16.0
Florian Roger 4 weeks ago
parent 04f70460ab
commit fbbbbb0b5f

@ -43,12 +43,13 @@ It is based on modules:
- [x] verbose message for missing fields (could be highlighted?) | 16.0.0.0.15
- [x] forbid coin_amount to be defined in order/deposit from/to bank | 16.0.0.16
- [x] add a button to put directly the cash order from bank to cash | 16.0.0.17
- [] add cash deposits / cash outs to statement
- [x] add cash deposits / cash outs to statement | 16.0.0.18
- [] check all reconciled before closing loss/profit
- [] What to do when statement line generated has no label
- [] better handling of line_ids creation in deposit wizard
- [] report cashbox to next statement
- [] compute cashbox from cash entries
- [] upgrade _get_journal_dashboard_outstanding_payments for deposits being also counted for journal kanban card in accounting dashboard
## Security ToDo

@ -1,6 +1,6 @@
{
'name': "Gn Cash",
'version': '16.0.0.0.17',
'version': '16.0.0.0.18',
'author': 'Garage Numérique',
'category': 'Accounting',
'description': """

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import cash_statement, account_cash_deposit, account_journal_dashboard, account_payment
from . import cash_statement, account_journal_dashboard, account_cash_deposit, account_payment

@ -60,6 +60,115 @@ class AccountCashDeposit(models.Model):
states={"draft": [("readonly", "=", False)]},
)
'''
Reconciliation
'''
is_matched = fields.Boolean(string="Is Matched With a Bank Statement", store=True,
compute='_compute_reconciliation')
is_reconcile = fields.Boolean(
compute="_compute_reconciliation", store=True, string="Reconciled"
)
reconciled_statement_line_ids = fields.Many2many(
comodel_name='account.bank.statement.line',
string="Reconciled Statement Lines",
compute='_compute_reconciliation',
help="Statements lines matched to this deposit",
)
reconciled_statement_lines_count = fields.Integer(
string="# Reconciled Statement Lines",
compute="_compute_reconciliation",
)
reconciled_statement_ids = fields.Many2many(
comodel_name='account.bank.statement',
string="Reconciled Statements",
compute='_compute_reconciliation',
help="Statements matched to this payment",
)
reconciled_statements_count = fields.Integer(
string="# Reconciled Statement",
compute="_compute_reconciliation",
)
@api.depends("move_id.line_ids.reconciled", "company_id")
def _compute_reconciliation(self):
for deposit in self:
match_counterpart = False
match_statement = False
reconciled_lines = self.env['account.move.line']
if deposit.move_id:
for line in deposit.move_id.line_ids:
if line.reconciled:
if line.account_id.id == deposit.move_id.journal_id.order_credit_account_id.id \
or deposit.move_id.journal_id.deposit_debit_account_id.id:
match_statement = True
elif line.account_id.id == deposit.partner_id.property_account_payable_id.id:
match_counterpart = True
elif line.account_id.id != self.company_id.transfer_account_id.id:
raise UserError("A line from the deposit %s is incorrect" % deposit.name)
if match_statement == True:
for partial in line.matched_credit_ids | line.matched_debit_ids:
reconciled_lines |= partial.debit_move_id | partial.credit_move_id
deposit.reconciled_statement_line_ids = [(6, 0, reconciled_lines.mapped('statement_line_id').ids)]
deposit.reconciled_statement_lines_count = len(deposit.reconciled_statement_line_ids)
deposit.is_matched = match_statement
deposit.is_reconcile = match_counterpart
statement_ids = {line.statement_id.id for line in reconciled_lines}
deposit.reconciled_statement_ids = [(6, 0, list(statement_ids))]
deposit.reconciled_statements_count = len(statement_ids)
def button_open_statement_lines(self):
''' Redirect the user to the statement line(s) reconciled to this payment.
:return: An action
'''
self.ensure_one()
action = {
'name': _("Matched Transactions"),
'type': 'ir.actions.act_window',
'res_model': 'account.bank.statement.line',
'context': {'create': False},
}
if len(self.reconciled_statement_line_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': self.reconciled_statement_line_ids.id,
})
else:
action.update({
'view_mode': 'list,form',
'domain': [('id', 'in', self.reconciled_statement_line_ids.ids)],
})
return action
def button_open_statements(self):
self.ensure_one()
action = {
'name': _("Statements"),
'type': 'ir.actions.act_window',
'res_model': 'account.bank.statement',
'context': {'create': False, 'journal_type': 'cash'},
}
if len(self.reconciled_statement_ids) == 1:
action.update({
'views': [[self.env.ref('gn_cash.view_cash_statement_form').id, "form"]],
'res_id': self.reconciled_statement_ids.id,
})
else:
action.update({
'views': [[False, "list"], [self.env.ref('gn_cash.view_cash_statement_form').id, "form"]],
'domain': [('id', 'in', self.reconciled_statement_ids.ids)],
})
return action
'''
End of fields and methods related to statements
'''
def _prepare_account_move(self, vals):
move_vals = super()._prepare_account_move(vals)
if move_vals['line_ids']:
@ -67,15 +176,27 @@ class AccountCashDeposit(models.Model):
Inverse debit / credit and use suspense_account_id for further reconciliation with statement
'''
journal = self.env['account.journal'].browse(move_vals['journal_id'])
debit_account_id = journal.deposit_debit_account_id.id
credit_account_id = journal.order_credit_account_id.id
if not (debit_account_id or credit_account_id):
raise UserError("Default debit and credit account must be defined in journal settings")
for line_tuple in move_vals["line_ids"]:
if line_tuple[2]['account_id'] == self.company_id.transfer_account_id.id:
line_tuple[2]['account_id'] = journal.suspense_account_id.id
if line_tuple[2]['amount_currency'] > 0:
line_tuple[2]['account_id'] = debit_account_id
else:
line_tuple[2]['account_id'] = credit_account_id
if self.partner_id:
line_tuple[2]['partner_id'] = self.partner_id.id
else:
if self.partner_id:
line_tuple[2]['account_id'] = self.partner_id.property_account_payable_id.id
line_tuple[2]['partner_id'] = self.partner_id.id
else:
line_tuple[2]['account_id'] = self.company_id.transfer_account_id.id
return move_vals
def confirm_order_and_receive(self):
@ -95,7 +216,6 @@ class AccountCashDeposit(models.Model):
'qty': line['qty'],
'cash_unit_id': line['cash_unit_id'].id,
} for line in self.line_ids]
_logger.warning("lines are : %s" % lines)
counterpart_vals = {
'operation_type': 'order' if self.operation_type == 'deposit' else 'deposit',
'date': self.date,
@ -122,7 +242,6 @@ class AccountCashDeposit(models.Model):
super().validate(force_date=force_date)
counterpart_move = self.move_id
origin_move = self.env['account.move'].browse(self.env.context.get('is_counterpart_of')) if self.env.context.get('is_counterpart_of') else False
_logger.warning("context in validate deposit : %s" % dict(self.env.context))
if origin_move:
lines_to_reconcile = [line for line in (counterpart_move.line_ids + origin_move.line_ids) if line.account_id.id == self.company_id.transfer_account_id.id]
if len(lines_to_reconcile) == 2:
@ -144,7 +263,6 @@ class AccountCashOrderReception(models.TransientModel):
def run(self):
super().run()
_logger.warning("context in run: %s " % self.env.context)
context = dict(self.env.context)
if not self.order_id.partner_id and not self.order_id.is_reconcile:
return {

@ -8,6 +8,18 @@ _logger = logging.getLogger(__name__)
class AccountJournal(models.Model):
_inherit = "account.journal"
deposit_debit_account_id = fields.Many2one(
comodel_name='account.account', check_company=True,
string='Default account for cash deposit',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), \
('account_type', '=', 'asset_current'), ('reconcile', '=', True)]")
order_credit_account_id = fields.Many2one(
comodel_name='account.account', check_company=True,
string='Default account for cash order',
domain="[('deprecated', '=', False), ('company_id', '=', company_id), \
('account_type', '=', 'asset_current'), ('reconcile', '=', True)]")
def cash_in_or_out(self):
for journal in self:
journal.ensure_one()
@ -38,6 +50,17 @@ class AccountJournal(models.Model):
}
return stmt_line_values
def generate_statement_line_from_deposit(self, deposit_id):
deposit = self.env['account.cash.deposit'].browse(deposit_id)
stmt_line_values = {
'date': deposit.date,
'move_type': deposit.move_id.move_type,
'journal_id': deposit.move_id.journal_id.id,
'payment_ref': deposit.name,
'partner_id': deposit.partner_id.id,
'amount': deposit.total_amount * (deposit.operation_type == 'deposit' and 1 or -1)
}
return stmt_line_values
def create_cash_statement(self):
self.ensure_one()
@ -62,9 +85,23 @@ class AccountJournal(models.Model):
payment_lines.extend(stmt_line.read(['date', 'id']))
payment_lines_iter_by_date = itertools.groupby(payment_lines, lambda l: l['date'])
'''
Search for cash orders / cash deposits
'''
deposit_lines = []
deposit_values = self.env['account.cash.deposit'].search([
('cash_journal_id', '=', self.id),
('is_matched', '=', False),
('date', '<=', fields.Date.context_today(self)),
('state', '=', 'done'),
]).sorted(key=lambda r: (r.date, r.id)).read(['date', 'id', 'total_amount', 'name', 'partner_id', 'operation_type'])
deposit_ids = [deposit['id'] for deposit in deposit_values]
for deposit_id in deposit_ids:
stmt_line_values = self.generate_statement_line_from_deposit(deposit_id)
stmt_line = self.env['account.bank.statement.line'].create(stmt_line_values)
deposit_lines.extend(stmt_line.read(['date', 'id']))
payment_lines_by_date = [{'date': iter_date, 'lines': sorted([line['id'] for line in iter_lines])} for iter_date, iter_lines in payment_lines_iter_by_date]
'''
Search for orphan statement lines
@ -107,7 +144,7 @@ class AccountJournal(models.Model):
raise
'''
Add lines to statement
Resquence lines in statement
'''
sequence = 1
for line_to_set in stmt_line_ids_to_set:

@ -10,10 +10,10 @@
<xpath expr="header" position="replace">
<header>
<field name="id" invisible="1"/>
<!-- Create counterpart -->
<field name="cash_journal_type" invisible="0"/>
<field name="counterpart_journal_type" invisible="0"/>
<field name="is_reconcile" invisible="1"/>
<field name="is_matched" invisible="1"/>
<field name="partner_id" invisible="1"/>
<button
name="create_counterpart_deposit"
@ -65,6 +65,24 @@
/>
</header>
</xpath>
<xpath expr="//div[@class='oe_title']" position="before">
<div class="oe_button_box" name="button_box">
<button name="button_open_statement_lines" type="object" class="oe_stat_button" icon="fa-bars" attrs="{'invisible': [('is_matched','=', False)]}">
<div class="o_form_field o_stat_info">
<field name="reconciled_statement_lines_count"/>
<span> Ligne de relevé</span>
</div>
</button>
</div>
<div class="oe_button_box" name="button_box">
<button name="button_open_statements" type="object" class="oe_stat_button" icon="fa-bars" attrs="{'invisible': [('is_matched','=', False)]}">
<div class="o_form_field o_stat_info">
<field name="reconciled_statements_count"/>
<span> Relevé</span>
</div>
</button>
</div>
</xpath>
<xpath expr="//field[@name='cash_journal_id']" position="attributes">
<attribute name="string">Journal de destination</attribute>
</xpath>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="gn_cash.view_account_journal_form" model="ir.ui.view">
<field name="name">gn_cash.account.journal.form</field>
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="priority">200</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='suspense_account_id']" position="after">
<field name='deposit_debit_account_id'
attrs="{'invisible': [('type', 'not in', ('bank', 'cash'))]}"/>
<field name='order_credit_account_id'
attrs="{'invisible': [('type', 'not in', ('bank', 'cash'))]}"/>
</xpath>
<xpath expr="//page[@name='advanced_settings']/group" position="inside">
<group string="Statement Import Map for Cash" attrs="{'invisible': [('type','!=', 'cash')]}">
<field name="bank_statements_source" attrs="{'required': [('type', '=', 'cash')]}" widget="radio" groups="account.group_account_readonly"/>
<field name="default_sheet_mapping_id" />
</group>
</xpath>
</field>
</record>
</data>
</odoo>
Loading…
Cancel
Save