Compare commits

...

10 Commits

Author SHA1 Message Date
makayabou
5e4fddae80 add cash order - deposit creation from dashboard 2024-08-08 13:38:14 +02:00
makayabou
08c514519b account move from deposit uses partner and suspense account 2024-08-07 02:53:15 +02:00
makayabou
948a3425c1 add partner field in cash deposit model and form 2024-08-06 23:55:42 +02:00
makayabou
ddf0778731 add orphan cash statement lines in statement 2024-08-01 00:55:05 +02:00
makayabou
932dcff807 access statement from payment 2024-07-30 00:04:21 +02:00
makayabou
0fb063da14 sequencing ok 2024-07-29 21:50:07 +02:00
makayabou
393f035871 use cash units 2024-07-28 23:52:39 +02:00
makayabou
4fdd967523 loss or profit move generation with only RedirectWarning 2024-07-28 23:52:05 +02:00
makayabou
fafb9e7d2a loss or profit move generation with confirmation wizard 2024-07-28 14:26:26 +02:00
makayabou
5170cf70b2 cash statement form with cashboxes, auto-generation and file sheet import 2024-07-24 16:47:20 +02:00
14 changed files with 451 additions and 180 deletions

View File

@ -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

View File

@ -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,

View 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>

View File

@ -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

View File

@ -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

View File

@ -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")

View 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

View File

@ -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},
)

View 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>

View File

@ -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>

View File

@ -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="&#8594; Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'start'}"/>
</xpath>

View File

@ -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>

View 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>

View File

@ -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>