Compare commits
10 Commits
8f07d1044b
...
5e4fddae80
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e4fddae80 | ||
|
|
08c514519b | ||
|
|
948a3425c1 | ||
|
|
ddf0778731 | ||
|
|
932dcff807 | ||
|
|
0fb063da14 | ||
|
|
393f035871 | ||
|
|
4fdd967523 | ||
|
|
fafb9e7d2a | ||
|
|
5170cf70b2 |
@ -6,21 +6,38 @@ It adds a wizard for cashbox counting after balance_start an balance_end_real fi
|
||||
|
||||
It is based on modules:
|
||||
- [account_statement_base from OCA](https://github.com/OCA/account-reconcile)
|
||||
- [account_statement_import_sheet_file](https://github.com/OCA/bank-statement-import)
|
||||
- [account_cash_deposit](https://github.com/OCA/account-financial-tools)
|
||||
|
||||
|
||||
## How do we work with it:
|
||||
|
||||
1. Each day, users create operations they assume (like receive donation, etc.)
|
||||
2. If time is short, they can also declare it on a simple solo statement line
|
||||
3. At the end of the week, Odoo generates a statement with missing lines (i.e. lines with payment suspense accounts)
|
||||
4. the accountant verifies cashbox balance and validate loss/profit
|
||||
|
||||
1. Each day, users create operations like receive donation, pay expenses, etc. The counterpart is their own property_account_payable_id (467 or 425)
|
||||
2. When the accountant gives/receive money to/from user, the accountant registers the cash-out/deposit to/from user (i.e. user's property_account_payable_id vs cash_account_id)
|
||||
3. The accountant can then:
|
||||
1. Generates a cash statement
|
||||
2. Verify cashbox and generate loss/profit move
|
||||
|
||||
## Whats Needs to Be Done:
|
||||
|
||||
- add validate cashbox and entry lines creation
|
||||
- allow user to add statement lines 'on-the-fly'
|
||||
- allow user to create Donation / Expense / Cash-In / Cash-Out operations
|
||||
- create an action triggered by a button leading to a wizard that auto-generates cash statement from lines with payment suspense account.
|
||||
- [x] add validate cashbox and entry lines creation | 16.0.0.0.1
|
||||
- [x] loss/profit validation | 16.0.0.0.2
|
||||
- [x] use predefined cash units | 16.0.0.0.3
|
||||
- [x] sequencing on statement gen | 16.0.0.0.4
|
||||
- [x] add button on payment to access statement if is_matched |16.0.0.0.5
|
||||
- [x] add orphan statement lines in the next gen statement | 16.0.0.0.6
|
||||
- [x] add partner in deposit and move | 16.0.0.0.7
|
||||
- [x] use suspense account | 16.0.0.0.7
|
||||
- [x] add button in dashboard to create a deposit / order | 16.0.0.0.8
|
||||
- [x] add some defaults (date, default_lines) at deposit creation | 16.0.0.0.8
|
||||
- [] add default cash units for cash order
|
||||
- [] choose beetween bank and cash journal for a deposit
|
||||
- [] add a button to put directly the cash order from bank to cash
|
||||
- [] add cash deposits / cash outs to statement
|
||||
- [] check all reconciled before closing loss/profit
|
||||
- [] What to do when statement line generated has no label
|
||||
- [] report cashbox to next statement
|
||||
- [] compute cashbox from cash entries
|
||||
|
||||
## Security ToDo
|
||||
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
{
|
||||
'name': "Gn Cash",
|
||||
'version': '16.0.0.0.1',
|
||||
'version': '16.0.0.0.8',
|
||||
'author': 'Garage Numérique',
|
||||
'category': 'Accounting',
|
||||
'description': """
|
||||
This module revivals cash statements from odoo 15.
|
||||
""",
|
||||
'depends': ['account_reconcile_oca', 'account_statement_impot_sheet_file'],
|
||||
'depends': ['account_reconcile_oca', 'account_statement_import_sheet_file', 'account_cash_deposit'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/cash_statement_views.xml',
|
||||
'data/cash_statement_validate.xml',
|
||||
'views/delete_views.xml',
|
||||
'views/account_cash_statement_cashbox_views.xml',
|
||||
'views/account_journal_ConfigForm_view.xml',
|
||||
'views/account_cash_statement_views.xml',
|
||||
'views/account_journal_dashboard_view.xml',
|
||||
'views/account_payment_views.xml',
|
||||
'views/account_cash_deposit_views.xml',
|
||||
],
|
||||
'translate': True,
|
||||
'installable': True,
|
||||
|
||||
24
gn_cash/data/cash_statement_validate.xml
Normal file
24
gn_cash/data/cash_statement_validate.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="gn_cash.action_validate_cash_statement" model="ir.actions.server">
|
||||
<field name="name">Validate Cash Statement</field>
|
||||
<field name="model_id" ref="model_account_bank_statement"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
<![CDATA[
|
||||
statement_id = env.context.get('statement_id')
|
||||
action = env['account.bank.statement'].browse(statement_id)._create_closing_difference_line_values()
|
||||
action_window = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Bank Statements',
|
||||
'res_model': 'account.bank.statement',
|
||||
'view_mode': 'form',
|
||||
'res_id': statement_id,
|
||||
'target': 'current',
|
||||
}
|
||||
action = action_window
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import cash_statement, account_journal_dashboard
|
||||
from . import cash_statement, account_journal_dashboard, account_payment, account_cash_deposit
|
||||
@ -1,9 +1,62 @@
|
||||
from odoo import models, api, _, fields
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class AccountCashDeposit(models.Model):
|
||||
_inherit = 'account.cash.deposit'
|
||||
|
||||
def _prepare_account_move(self, vals):
|
||||
move_vals = self.super()._prepare_account_move(vals)
|
||||
if move_vals['line_ids'] and self.partner_id and self.partner_id.:
|
||||
partner_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
domain=[('is_company','=', False), '|', ('parent_id', '=', False), ('parent_id.is_company', '=', True)],
|
||||
string='Partner',
|
||||
compute='_compute_partner_id', store=True, readonly=False, precompute=True,
|
||||
ondelete='restrict',
|
||||
)
|
||||
|
||||
operation_type = fields.Selection(
|
||||
[
|
||||
("deposit", "Cash Deposit"),
|
||||
("order", "Cash Order"),
|
||||
],
|
||||
required=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def _compute_partner_id(self):
|
||||
for deposit in self:
|
||||
deposit.ensure_one()
|
||||
if not deposit.partner_id:
|
||||
deposit.write({'partner_id': self.env.user.partner_id.id})
|
||||
|
||||
|
||||
def _prepare_account_move(self, vals):
|
||||
move_vals = super()._prepare_account_move(vals)
|
||||
if move_vals['line_ids']:
|
||||
'''
|
||||
change move if deposit partner is defined
|
||||
'''
|
||||
if self.partner_id:
|
||||
for line_tuple in move_vals["line_ids"]:
|
||||
'''
|
||||
change transfer_account_id to partner_account_payable_id
|
||||
'''
|
||||
if line_tuple[2]['account_id'] == self.company_id.transfer_account_id.id:
|
||||
line_tuple[2]['account_id'] = self.partner_id.property_account_payable_id.id
|
||||
line_tuple[2]['partner_id'] = self.partner_id.id
|
||||
break
|
||||
'''
|
||||
Use suspense account for reconciliation with statement
|
||||
'''
|
||||
journal = self.env['account.journal'].browse(move_vals['journal_id'])
|
||||
if not journal.suspense_account_id:
|
||||
raise UserError(_("Suspense Account for Journal is not defined"))
|
||||
for line_tuple in move_vals['line_ids']:
|
||||
if line_tuple[2]['account_id'] == journal.default_account_id.id:
|
||||
line_tuple[2]['account_id'] = journal.suspense_account_id.id
|
||||
break
|
||||
|
||||
return move_vals
|
||||
|
||||
|
||||
|
||||
for line_values in move_vals['lines_ids']:
|
||||
if
|
||||
|
||||
@ -1,16 +1,47 @@
|
||||
from odoo import models, api, _, fields
|
||||
from odoo.exceptions import UserError
|
||||
import itertools
|
||||
from datetime import datetime
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
from datetime import date
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
_inherit = "account.journal"
|
||||
|
||||
def generate_statement_line_values_from_payment(self, sequence, payment_id):
|
||||
def cash_in_or_out(self):
|
||||
for journal in self:
|
||||
journal.ensure_one()
|
||||
|
||||
# Create deposit
|
||||
deposit = self.env['account.cash.deposit'].create({
|
||||
'operation_type': 'order',
|
||||
'date': fields.Date.today(),
|
||||
'cash_journal_id': journal.id,
|
||||
'bank_journal_id': journal.id,
|
||||
'currency_id': self.env.company.currency_id.id
|
||||
})
|
||||
|
||||
# Add default lines
|
||||
cash_units = self.env["cash.unit"].search(
|
||||
[
|
||||
("auto_create", "in", ("both", 'deposit')),
|
||||
("currency_id", "=", deposit.currency_id.id),
|
||||
]
|
||||
)
|
||||
deposit.line_ids = [(0, 0, {
|
||||
"cash_unit_id": cunit.id,
|
||||
"qty": 0,
|
||||
"currency_id": deposit.currency_id.id}) for cunit in cash_units ]
|
||||
|
||||
# Call form view
|
||||
action = self.env['ir.actions.act_window']._for_xml_id('gn_cash.account_cash_inorout_action')
|
||||
action['res_id'] = deposit.id
|
||||
return action
|
||||
|
||||
|
||||
def generate_statement_line_from_payment(self, payment_id):
|
||||
payment = self.env['account.payment'].browse(payment_id)
|
||||
stmt_line_values = {
|
||||
'sequence': sequence,
|
||||
'date': payment.date,
|
||||
'move_type': payment.move_type,
|
||||
'journal_id': self.id,
|
||||
@ -18,38 +49,91 @@ class AccountJournal(models.Model):
|
||||
'partner_id': payment.partner_id.id,
|
||||
'amount': payment.amount if payment.payment_type == 'inbound' else -payment.amount
|
||||
}
|
||||
return stmt_line_values, sequence + 1
|
||||
return stmt_line_values
|
||||
|
||||
|
||||
def create_cash_statement(self):
|
||||
self.ensure_one()
|
||||
context = dict(self.env.context or {})
|
||||
|
||||
if context.get('journal_type', False) == 'cash':
|
||||
#context['is_payment'] = True
|
||||
#new_stmt = self.env['account.bank.statement'].write({'line_ids': None})
|
||||
#_logger.warning("new statement id: %s", new_stmt.id)
|
||||
|
||||
_logger.warning("Date used for payment_ids search in create_cash statement: %s", fields.Date.context_today(self))
|
||||
payment_ids = self.env['account.payment'].search([
|
||||
'''
|
||||
Search for payments
|
||||
'''
|
||||
payment_lines = []
|
||||
payment_values = self.env['account.payment'].search([
|
||||
('journal_id', '=', self.id),
|
||||
('is_matched', '=', False),
|
||||
('date', '<=', fields.Date.context_today(self)),
|
||||
('state', '=', 'posted'),
|
||||
])
|
||||
sequence = 1
|
||||
stmt_lines_to_create = []
|
||||
for payment in payment_ids:
|
||||
_logger.warning("payment id to treat: %s (%s)", payment.id, payment.ref)
|
||||
stmt_line_values, sequence = self.generate_statement_line_values_from_payment(sequence, payment.id)
|
||||
stmt_lines_to_create.append(stmt_line_values)
|
||||
_logger.warning("stmt_lines_to_create: %s", stmt_lines_to_create)
|
||||
last_line = self.env['account.bank.statement.line'].search([
|
||||
('journal_id', '=', self.id),
|
||||
]).sorted(key=lambda r: (r.date, r.id)).read(['date', 'id', 'amount', 'ref', 'partner_id', 'payment_type'])
|
||||
|
||||
payment_ids = [pay['id'] for pay in payment_values]
|
||||
for payment_id in payment_ids:
|
||||
stmt_line_values = self.generate_statement_line_from_payment(payment_id)
|
||||
stmt_line = self.env['account.bank.statement.line'].create(stmt_line_values)
|
||||
payment_lines.extend(stmt_line.read(['date', 'id']))
|
||||
|
||||
|
||||
payment_lines_iter_by_date = itertools.groupby(payment_lines, lambda l: l['date'])
|
||||
|
||||
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
|
||||
'''
|
||||
orphan_lines = self.env['account.bank.statement.line'].search([
|
||||
('statement_id', '=', False),
|
||||
('state', '=', 'posted'),
|
||||
], limit=1)
|
||||
stmt_vals = {
|
||||
'name': f"Registre de caisse généré manuellement - {fields.Datetime.now()}",
|
||||
'date': fields.Date.context_today(self),
|
||||
'balance_start': last_line.statement_id.balance_end
|
||||
}
|
||||
stmt_vals['line_ids'] = [[0, False, line] for line in stmt_lines_to_create]
|
||||
new_stmt = self.env['account.bank.statement'].create(stmt_vals)
|
||||
('journal_id', '=', self.id),
|
||||
('date', '<=', fields.Date.context_today(self)),
|
||||
]).sorted(key=lambda r: (r.date, r.id)).read(['date', 'id'])
|
||||
|
||||
orphan_lines_iter_by_date = itertools.groupby(orphan_lines, lambda l: l['date'])
|
||||
orphan_lines_by_date = [{'date': iter_date, 'lines': sorted([line['id'] for line in iter_lines])} for iter_date, iter_lines in orphan_lines_iter_by_date]
|
||||
|
||||
|
||||
'''
|
||||
Collect lines by date
|
||||
'''
|
||||
stmt_line_ids_to_set = []
|
||||
dates_sorted = sorted(set([group['date'] for group in orphan_lines_by_date if len(orphan_lines_by_date) > 0]))
|
||||
for date in dates_sorted:
|
||||
orphan_lines_for_date = [group['lines'] for group in orphan_lines_by_date if len(orphan_lines_by_date) > 0 and group['date'] == date]
|
||||
for lines_grouped in orphan_lines_for_date:
|
||||
stmt_line_ids_to_set.extend([line_id for line_id in lines_grouped])
|
||||
#_logger.warning("STMT_LINES_TO_UPDATE : %s " % stmt_line_ids_to_update)
|
||||
|
||||
if len(stmt_line_ids_to_set) > 0:
|
||||
'''
|
||||
Create statement
|
||||
'''
|
||||
stmt_vals = {
|
||||
'name': f"Registre de caisse généré manuellement - {fields.Datetime.now()}",
|
||||
'date': fields.Date.context_today(self),
|
||||
'line_ids': [(6, 0, stmt_line_ids_to_set)],
|
||||
}
|
||||
|
||||
try:
|
||||
new_stmt = self.env['account.bank.statement'].create(stmt_vals)
|
||||
except Exception as e:
|
||||
_logger.error("Error creating bank statement: %s" % str(e))
|
||||
raise
|
||||
|
||||
'''
|
||||
Add lines to statement
|
||||
'''
|
||||
sequence = 1
|
||||
for line_to_set in stmt_line_ids_to_set:
|
||||
new_stmt.write({'line_ids': [(1, line_to_set, {'sequence': sequence})]})
|
||||
sequence += 1
|
||||
|
||||
'''
|
||||
return form view for statement
|
||||
'''
|
||||
action = self.env['ir.actions.act_window']._for_xml_id('gn_cash.action_cash_statement_tree')
|
||||
action['views'] = [[self.env.ref('gn_cash.view_cash_statement_form').id, "form"]]
|
||||
action['res_id'] = new_stmt.id
|
||||
return action
|
||||
else:
|
||||
raise ValidationError("No bank statement to generate")
|
||||
47
gn_cash/models/account_payment.py
Normal file
47
gn_cash/models/account_payment.py
Normal file
@ -0,0 +1,47 @@
|
||||
from odoo import models, api, _, fields
|
||||
from odoo.exceptions import ValidationError
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = "account.payment"
|
||||
|
||||
reconciled_statement_ids = fields.Many2many(
|
||||
comodel_name='account.bank.statement',
|
||||
string="Reconciled Statements",
|
||||
compute='_compute_stat_statement_button',
|
||||
help="Statements matched to this payment",
|
||||
)
|
||||
reconciled_statement_count = fields.Integer(
|
||||
string="# Reconciled Statement",
|
||||
compute="_compute_stat_statement_button",
|
||||
)
|
||||
|
||||
@api.depends('reconciled_statement_line_ids')
|
||||
def _compute_stat_statement_button(self):
|
||||
for pay in self:
|
||||
#statement_ids = { line.statement_id.id for line in pay.reconciled_statement_line_ids }
|
||||
statement_ids = {line.statement_id.id for line in pay.reconciled_statement_line_ids if line.statement_id}
|
||||
pay.reconciled_statement_ids = [(6, 0, list(statement_ids))]
|
||||
pay.reconciled_statement_count = len(statement_ids)
|
||||
|
||||
|
||||
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
|
||||
@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import math
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import float_is_zero
|
||||
from odoo.tools.misc import formatLang, format_date, str2bool
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.exceptions import UserError, ValidationError, RedirectWarning
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountCashboxLine(models.Model):
|
||||
@ -15,18 +13,51 @@ class AccountCashboxLine(models.Model):
|
||||
_rec_name = 'coin_value'
|
||||
_order = 'coin_value'
|
||||
|
||||
@api.depends('coin_value', 'number')
|
||||
def _sub_total(self):
|
||||
""" Calculates Sub total"""
|
||||
for cashbox_line in self:
|
||||
cashbox_line.subtotal = cashbox_line.coin_value * cashbox_line.number
|
||||
cash_unit_id = fields.Many2one(
|
||||
"cash.unit", required=True, domain="[('currency_id', '=', currency_id)]"
|
||||
)
|
||||
tree_order = fields.Float(related="cash_unit_id.tree_order", store=True)
|
||||
|
||||
coin_value = fields.Float(string='Coin/Bill Value', required=True, digits=0)
|
||||
number = fields.Integer(string='#Coins/Bills', help='Opening Unit Numbers')
|
||||
subtotal = fields.Float(compute='_sub_total', string='Subtotal', digits=0, readonly=True)
|
||||
cashbox_id = fields.Many2one('gn_cash.account.bank.statement.cashbox', string="Cashbox")
|
||||
currency_id = fields.Many2one('res.currency', related='cashbox_id.currency_id')
|
||||
|
||||
qty = fields.Integer(string='#Coins/Bills', help='Opening Unit Numbers')
|
||||
_sql_constraints = [
|
||||
("qty_positive", "CHECK(qty >= 0)", "The quantity must be positive or null."),
|
||||
(
|
||||
"cash_unit_unique",
|
||||
"unique(cash_unit_id, cashbox_id)",
|
||||
"A line already exists for this cash unit.",
|
||||
),
|
||||
]
|
||||
coin_value = fields.Float(string='Coin/Bill Value', required=False, digits=0)
|
||||
|
||||
subtotal = fields.Float(compute='_compute_subtotal', string='Subtotal', digits=0, readonly=True)
|
||||
|
||||
@api.constrains("currency_id", "cash_unit_id")
|
||||
def _check_lines(self):
|
||||
for line in self:
|
||||
if (
|
||||
line.currency_id
|
||||
and line.cash_unit_id
|
||||
and line.currency_id != line.cash_unit_id.currency_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"You must delete cash lines that are linked to a currency "
|
||||
"other than %s."
|
||||
)
|
||||
% line.currency_id.name
|
||||
)
|
||||
|
||||
@api.depends("cash_unit_id", "qty")
|
||||
def _compute_subtotal(self):
|
||||
for line in self:
|
||||
subtotal = 0
|
||||
if line.cash_unit_id:
|
||||
subtotal = line.cash_unit_id.total_value * line.qty
|
||||
line.subtotal = subtotal
|
||||
|
||||
class AccountBankStmtCashWizard(models.Model):
|
||||
"""
|
||||
Account Bank Statement popup that allows entering cash details.
|
||||
@ -35,7 +66,7 @@ class AccountBankStmtCashWizard(models.Model):
|
||||
_description = 'Bank Statement Cashbox'
|
||||
_rec_name = 'id'
|
||||
|
||||
cashbox_lines_ids = fields.One2many('gn_cash.account.cashbox.line', 'cashbox_id', string='Cashbox Lines')
|
||||
cashbox_line_ids = fields.One2many('gn_cash.account.cashbox.line', 'cashbox_id', string='Cashbox Lines')
|
||||
start_bank_stmt_ids = fields.One2many('account.bank.statement', 'cashbox_start_id')
|
||||
end_bank_stmt_ids = fields.One2many('account.bank.statement', 'cashbox_end_id')
|
||||
total = fields.Float(compute='_compute_total')
|
||||
@ -50,10 +81,10 @@ class AccountBankStmtCashWizard(models.Model):
|
||||
if cashbox.start_bank_stmt_ids:
|
||||
cashbox.currency_id = cashbox.start_bank_stmt_ids[0].currency_id
|
||||
|
||||
@api.depends('cashbox_lines_ids', 'cashbox_lines_ids.coin_value', 'cashbox_lines_ids.number')
|
||||
@api.depends('cashbox_line_ids', 'cashbox_line_ids.cash_unit_id', 'cashbox_line_ids.qty')
|
||||
def _compute_total(self):
|
||||
for cashbox in self:
|
||||
cashbox.total = sum([line.subtotal for line in cashbox.cashbox_lines_ids])
|
||||
cashbox.total = sum([line.subtotal for line in cashbox.cashbox_line_ids])
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
@ -87,54 +118,19 @@ class AccountBankStmtCashWizard(models.Model):
|
||||
def _check_closing(self):
|
||||
self.ensure_one()
|
||||
context = dict(self.env.context or {})
|
||||
stmt = self.end_bank_stmt_ids
|
||||
difference = stmt.currency_id.compare_amounts(stmt.balance_end_real, stmt.balance_end)
|
||||
return self.env['ir.actions.act_window']._for_xml_id('gn_cash.action_view_account_bnk_stmt_check')
|
||||
stmt = self.end_bank_stmt_ids[0]
|
||||
difference = stmt.currency_id.round(stmt.balance_end_real - stmt.balance_end)
|
||||
|
||||
if difference != 0:
|
||||
return difference
|
||||
|
||||
def _validate_cashbox(self):
|
||||
for cashbox in self:
|
||||
context = dict(self.env.context or {})
|
||||
if cashbox.start_bank_stmt_ids:
|
||||
cashbox.start_bank_stmt_ids.write({'balance_start': cashbox.total})
|
||||
cashbox.start_bank_stmt_ids[0].write({'balance_start': cashbox.total})
|
||||
if cashbox.end_bank_stmt_ids:
|
||||
cashbox.end_bank_stmt_ids.write({'balance_end_real': cashbox.total})
|
||||
|
||||
if cashbox.end_bank_stmt_ids.balance_end_real != cashbox.end_bank_stmt_ids.balance_end:
|
||||
cashbox._check_closing(context)
|
||||
# action = {
|
||||
# 'name': _('Cash Difference'),
|
||||
# 'view_mode': 'form',
|
||||
# 'res_model': 'gn_cash.account.bank.statement.closebalance',
|
||||
# 'view_id': self.env.ref('gn_cash.action_view_account_bnk_stmt_check').id,
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# 'res_id': cashbox_id,
|
||||
# 'context': context,
|
||||
# 'target': 'new'
|
||||
# }
|
||||
# return action
|
||||
#return self.env['ir.actions.act_window']._for_xml_id('gn_cash.action_view_account_bnk_stmt_check')
|
||||
|
||||
class AccountBankStmtCloseCheck(models.TransientModel):
|
||||
"""
|
||||
Account Bank Statement wizard that check that closing balance is correct.
|
||||
"""
|
||||
_name = 'gn_cash.account.bank.statement.closebalance'
|
||||
_description = 'Bank Statement Closing Balance'
|
||||
|
||||
def validate(self):
|
||||
_logger.warning("context in gn_cash.account.bank.statement.closebalance: %s", dict(self.env.context) or {})
|
||||
bnk_stmt_id = self.env.context.get('active_id', False)
|
||||
stmt = self.env['account.bank.statement'].browse(bnk_stmt_id)
|
||||
if not stmt.is_complete and difference > 0:
|
||||
difference = stmt.currency_id.compare_amounts(stmt.balance_end, stmt.balance_end_real)
|
||||
message = "We found difference + {difference} in the statement computation, compared to your computation.",
|
||||
|
||||
values = self.env['account.bank.statement'].browse(bnk_stmt_id)._check_cash_balance_end_real_same_as_computed()
|
||||
if values:
|
||||
return self.env['ir.actions.act_window']._for_xml_id('account.action_view_account_bnk_stmt_check')
|
||||
else:
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
cashbox.end_bank_stmt_ids[0].write({'balance_end_real': cashbox.total})
|
||||
|
||||
class BankStatement(models.Model):
|
||||
_inherit = 'account.bank.statement'
|
||||
@ -142,43 +138,47 @@ class BankStatement(models.Model):
|
||||
cashbox_start_id = fields.Many2one('gn_cash.account.bank.statement.cashbox', string="Starting Cashbox")
|
||||
cashbox_end_id = fields.Many2one('gn_cash.account.bank.statement.cashbox', string="Ending Cashbox")
|
||||
|
||||
def _check_cash_balance_end_real_same_as_computed(self):
|
||||
def _create_closing_difference_line_values(self):
|
||||
""" Check the balance_end_real (encoded manually by the user) is equals to the balance_end (computed by odoo).
|
||||
For a cash statement, if there is a difference, the different is set automatically to a profit/loss account.
|
||||
"""
|
||||
for statement in self.filtered(lambda stmt: stmt.journal_type == 'cash'):
|
||||
if not statement.currency_id.is_zero(statement.difference):
|
||||
st_line_vals = {
|
||||
'statement_id': statement.id,
|
||||
'journal_id': statement.journal_id.id,
|
||||
'amount': statement.difference,
|
||||
'date': statement.date,
|
||||
}
|
||||
for statement in self.filtered(lambda stmt: stmt.journal_id.type == 'cash'):
|
||||
if not statement.is_complete:
|
||||
difference = statement.currency_id.round(statement.balance_end_real - statement.balance_end)
|
||||
if difference != 0:
|
||||
last_sequence = statement.line_ids.sorted(key=lambda r: r.sequence, reverse=True)[0].sequence
|
||||
st_line_vals = {
|
||||
'statement_id': statement.id,
|
||||
'journal_id': statement.journal_id.id,
|
||||
'amount': difference,
|
||||
'date': statement.date,
|
||||
'sequence': last_sequence + 1
|
||||
}
|
||||
|
||||
if difference < 0:
|
||||
if not statement.journal_id.loss_account_id:
|
||||
raise UserError(_(
|
||||
"Please go on the %s journal and define a Loss Account. "
|
||||
"This account will be used to record cash difference.",
|
||||
statement.journal_id.name
|
||||
))
|
||||
|
||||
if statement.currency_id.compare_amounts(statement.difference, 0.0) < 0.0:
|
||||
if not statement.journal_id.loss_account_id:
|
||||
raise UserError(_(
|
||||
"Please go on the %s journal and define a Loss Account. "
|
||||
"This account will be used to record cash difference.",
|
||||
statement.journal_id.name
|
||||
))
|
||||
st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Loss)")
|
||||
st_line_vals['counterpart_account_id'] = statement.journal_id.loss_account_id.id
|
||||
else:
|
||||
# statement.difference > 0.0
|
||||
if not statement.journal_id.profit_account_id:
|
||||
raise UserError(_(
|
||||
"Please go on the %s journal and define a Profit Account. "
|
||||
"This account will be used to record cash difference.",
|
||||
statement.journal_id.name
|
||||
))
|
||||
|
||||
st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Loss)")
|
||||
st_line_vals['counterpart_account_id'] = statement.journal_id.loss_account_id.id
|
||||
else:
|
||||
# statement.difference > 0.0
|
||||
if not statement.journal_id.profit_account_id:
|
||||
raise UserError(_(
|
||||
"Please go on the %s journal and define a Profit Account. "
|
||||
"This account will be used to record cash difference.",
|
||||
statement.journal_id.name
|
||||
))
|
||||
st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Profit)")
|
||||
st_line_vals['counterpart_account_id'] = statement.journal_id.profit_account_id.id
|
||||
|
||||
st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Profit)")
|
||||
st_line_vals['counterpart_account_id'] = statement.journal_id.profit_account_id.id
|
||||
|
||||
self.env['account.bank.statement.line'].create(st_line_vals)
|
||||
return True
|
||||
self.env['account.bank.statement.line'].create(st_line_vals)
|
||||
|
||||
|
||||
def open_cashbox_id(self):
|
||||
self.ensure_one()
|
||||
@ -203,4 +203,18 @@ class BankStatement(models.Model):
|
||||
'target': 'new'
|
||||
}
|
||||
|
||||
return action
|
||||
return action
|
||||
|
||||
def button_validate(self):
|
||||
for statement in self:
|
||||
if not statement.is_complete:
|
||||
difference = statement.cashbox_end_id._check_closing()
|
||||
difference = statement.currency_id.format(difference)
|
||||
if difference:
|
||||
action_id = self.env.ref('gn_cash.action_validate_cash_statement').id
|
||||
raise RedirectWarning(
|
||||
_("If you continue you will create move with difference %s, on statement %s, " % (difference, statement.id)),
|
||||
action_id,
|
||||
"Proceed",
|
||||
additional_context={'statement_id': statement.id},
|
||||
)
|
||||
25
gn_cash/views/account_cash_deposit_views.xml
Normal file
25
gn_cash/views/account_cash_deposit_views.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!--
|
||||
Copyright 2024 Le Garage Numérique contact@legaragenumerique.fr Akretion France (http://www.akretion.com/)
|
||||
@author: Florian ROGER <makayabou@gmail.com>
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
||||
-->
|
||||
<odoo>
|
||||
<record id="gn_cash.account_cash_deposit_form" model="ir.ui.view">
|
||||
<field name="name">AccountCashDepositFormView</field>
|
||||
<field name="model">account.cash.deposit</field>
|
||||
<field name="inherit_id" ref="account_cash_deposit.account_cash_deposit_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='order_date']" position="before">
|
||||
<field name="operation_type" widget="radio" options="{'horizontal': True}" on_change="1" modifiers="{'readonly': [('state', '!=', 'draft')], 'required': True"/>
|
||||
<field name="partner_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_cash.account_cash_inorout_action" model="ir.actions.act_window">
|
||||
<field name="name">Cash Deposit</field>
|
||||
<field name="res_model">account.cash.deposit</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="gn_cash.account_cash_deposit_form"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@ -10,14 +10,21 @@
|
||||
<field name="start_bank_stmt_ids" invisible="1"/>
|
||||
<field name="end_bank_stmt_ids" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="cashbox_lines_ids" nolabel="1" context="{'default_currency_id': currency_id}">
|
||||
<tree editable="bottom">
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="number"/>
|
||||
<field name="coin_value" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||
<field name="subtotal" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||
</tree>
|
||||
</field>
|
||||
<group name="lines">
|
||||
<field
|
||||
name="cashbox_line_ids"
|
||||
nolabel="1"
|
||||
colspan="2"
|
||||
context="{'default_currency_id': currency_id}"
|
||||
>
|
||||
<tree editable="bottom">
|
||||
<field name="cash_unit_id" />
|
||||
<field name="qty" />
|
||||
<field name="subtotal" sum="1" />
|
||||
<field name="currency_id" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<div>
|
||||
<group>
|
||||
<group class="oe_subtotal_footer oe_right" cols="6">
|
||||
@ -46,28 +53,5 @@
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_cash.view_account_bnk_stmt_check" model="ir.ui.view">
|
||||
<field name="name">gn_cash.account.bnk_stmt_check.form</field>
|
||||
<field name="model">gn_cash.account.bank.statement.closebalance</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<div>
|
||||
<p>The closing balance is different than the computed one!</p>
|
||||
<p>Confirming this will create automatically a journal entry with the difference in the profit/loss account set on the cash journal.</p>
|
||||
<footer>
|
||||
<button string="Confirm" name="validate" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="gn_cash.action_view_account_bnk_stmt_check" model="ir.actions.act_window">
|
||||
<field name="name">Check Closing Balance</field>
|
||||
<field name="res_model">gn_cash.account.bank.statement.closebalance</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_account_bnk_stmt_check"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@ -24,6 +24,12 @@
|
||||
<field name="inherit_id" ref="account_statement_base.view_bank_statement_form"/>
|
||||
<field name="priority">100</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@role='alert']" position="before">
|
||||
<header>
|
||||
<field name="is_complete" invisible="1"/>
|
||||
<button name="button_validate" invisible="context.get('journal_type', False) != 'cash'" attrs="{'invisible': [('is_complete', '=', True)]}" string="Clôturer" type="object" class="oe_highlight" />
|
||||
</header>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='balance_start']" position="after" >
|
||||
<button name="open_cashbox_id" invisible="context.get('journal_type', False) != 'cash'" string="→ Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'start'}"/>
|
||||
</xpath>
|
||||
|
||||
@ -10,20 +10,11 @@
|
||||
<xpath expr='//div[@name="bank_customer_payment"]' position='before'>
|
||||
<div t-if="journal_type == 'cash'" name="new_statement">
|
||||
<a role="menuitem" context="{'journal_type': 'cash'}" type="object" name="create_cash_statement">Nouveau Relevé</a>
|
||||
<a role="menuitem" context="{'journal_type': 'cash'}" type="object" name="cash_in_or_out">Cash In/Out</a>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr='//a[@name="create_cash_statement"]/..'
|
||||
position='before'
|
||||
>
|
||||
<button
|
||||
name="create_cash_statement"
|
||||
type="object"
|
||||
class="btn btn-primary"
|
||||
groups="account.group_account_user"
|
||||
>
|
||||
<span>Generate Statement</span>
|
||||
</button>
|
||||
<xpath expr='//button[@name="create_cash_statement"]' position='replace'>
|
||||
<button t-if="dashboard.number_to_reconcile == 0" type="object" name="create_cash_statement" context="{'journal_type': 'cash'}" class="btn btn-primary" groups="account.group_account_invoice">Générer le relevé</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
22
gn_cash/views/account_payment_views.xml
Normal file
22
gn_cash/views/account_payment_views.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="gn_cash.view_account_payment_form" model="ir.ui.view">
|
||||
<field name="name">gn_cash.account.payment.form</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_form"/>
|
||||
<field name="model">account.payment</field>
|
||||
<field name="priority">100</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@name='button_open_statement_lines']" position="before">
|
||||
<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_statement_count"/>
|
||||
<span> Relevé</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<delete model="ir.ui.view" id="gn_cash.account_journal_dashboard_kanban_view"/>
|
||||
<!--<delete model="ir.ui.view" id="gn_cash.account_journal_dashboard_kanban_view"/>-->
|
||||
<delete model="ir.ui.view" id="gn_cash.view_account_journal_form"/>
|
||||
</data>
|
||||
</odoo>
|
||||
Loading…
x
Reference in New Issue
Block a user