Jordi Ballester Alomar
5 years ago
committed by
Adrià Gil Sorribes
16 changed files with 358 additions and 0 deletions
-
2pos_statement_closing_balance/__init__.py
-
22pos_statement_closing_balance/__manifest__.py
-
2pos_statement_closing_balance/models/__init__.py
-
10pos_statement_closing_balance/models/account_journal.py
-
48pos_statement_closing_balance/models/pos_session.py
-
3pos_statement_closing_balance/readme/CONFIGURATION.rst
-
3pos_statement_closing_balance/readme/CONTRIBUTORS.rst
-
8pos_statement_closing_balance/readme/DESCRIPTION.rst
-
7pos_statement_closing_balance/readme/USAGE.rst
-
1pos_statement_closing_balance/tests/__init__.py
-
65pos_statement_closing_balance/tests/test_pos_statement_closing_balance.py
-
13pos_statement_closing_balance/views/account_journal_views.xml
-
19pos_statement_closing_balance/views/pos_session_views.xml
-
1pos_statement_closing_balance/wizards/__init__.py
-
121pos_statement_closing_balance/wizards/pos_update_statement_closing_balance.py
-
33pos_statement_closing_balance/wizards/pos_update_statement_closing_balance.xml
@ -0,0 +1,2 @@ |
|||||
|
from . import wizards |
||||
|
from . import models |
@ -0,0 +1,22 @@ |
|||||
|
# Copyright 2020 ForgeFlow, S.L. |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
|
||||
|
{ |
||||
|
'name': 'POS Statement Closing Balance', |
||||
|
'version': '12.0.1.0.1', |
||||
|
'category': 'Point Of Sale', |
||||
|
'summary': 'Allows to set a closing balance in your statements and ' |
||||
|
'auto-post the difference between theoretical and actual.', |
||||
|
'author': 'ForgeFlow, Odoo Community Association (OCA)', |
||||
|
'website': 'http://www.github.com/OCA/pos', |
||||
|
'license': 'AGPL-3', |
||||
|
'depends': [ |
||||
|
'pos_cash_box_journal', |
||||
|
], |
||||
|
'data': [ |
||||
|
'wizards/pos_update_statement_closing_balance.xml', |
||||
|
'views/pos_session_views.xml', |
||||
|
'views/account_journal_views.xml', |
||||
|
], |
||||
|
'installable': True, |
||||
|
} |
@ -0,0 +1,2 @@ |
|||||
|
from . import pos_session |
||||
|
from . import account_journal |
@ -0,0 +1,10 @@ |
|||||
|
# Copyright 2020 ForgeFlow, S.L. |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class AccountJournal(models.Model): |
||||
|
_inherit = 'account.journal' |
||||
|
|
||||
|
pos_control_ending_balance = fields.Boolean( |
||||
|
'Control ending balance in POS') |
@ -0,0 +1,48 @@ |
|||||
|
# Copyright 2020 ForgeFlow, S.L. |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
from odoo import api, fields, models, _ |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
|
||||
|
class PosSession(models.Model): |
||||
|
_inherit = "pos.session" |
||||
|
|
||||
|
ending_balances_to_update = fields.Boolean( |
||||
|
compute='_compute_ending_balances_to_update') |
||||
|
|
||||
|
@api.multi |
||||
|
def _compute_ending_balances_to_update(self): |
||||
|
for rec in self: |
||||
|
rec.ending_balances_to_update = False |
||||
|
for statement in rec.statement_ids: |
||||
|
journal = statement.journal_id |
||||
|
if journal.pos_control_ending_balance and \ |
||||
|
journal.type != 'cash' or (journal.type == 'cash' and |
||||
|
not rec.cash_control): |
||||
|
rec.ending_balances_to_update = True |
||||
|
|
||||
|
@api.multi |
||||
|
def button_update_statement_ending_balance(self): |
||||
|
self.ensure_one() |
||||
|
action = self.env.ref( |
||||
|
"pos_statement_closing_balance." |
||||
|
"action_pos_update_bank_statement_closing_balance") |
||||
|
result = action.read()[0] |
||||
|
return result |
||||
|
|
||||
|
@api.multi |
||||
|
def _check_pos_session_bank_balance(self): |
||||
|
for session in self: |
||||
|
for statement in session.statement_ids: |
||||
|
if statement.journal_id.pos_control_ending_balance and \ |
||||
|
(statement != session.cash_register_id) and ( |
||||
|
statement.balance_end != statement.balance_end_real): |
||||
|
raise ValidationError(_( |
||||
|
'Mismatch in the closing balance ' |
||||
|
'for a non-cash statement.')) |
||||
|
return True |
||||
|
|
||||
|
@api.multi |
||||
|
def action_pos_session_closing_control(self): |
||||
|
self._check_pos_session_bank_balance() |
||||
|
return super(PosSession, self).action_pos_session_closing_control() |
@ -0,0 +1,3 @@ |
|||||
|
* Go to *Invoicing > Configuration > Journals* and set, for each Journal |
||||
|
that will be used in the POS, the flag 'Control ending balance in POS' if |
||||
|
you expect users to enter the ending balance. |
@ -0,0 +1,3 @@ |
|||||
|
* ForgeFlow |
||||
|
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com> |
||||
|
|
@ -0,0 +1,8 @@ |
|||||
|
This module extends the functionality of point of sale to allow users to input |
||||
|
the ending balances of the payment methods when they close a POS Session. |
||||
|
The system will then post the difference to the 'Liquidity Transfers' account. |
||||
|
|
||||
|
Currently it's only possible to define the ending balance for the cash |
||||
|
if you configure "Cash Control" in the POS Configuration. But that setting |
||||
|
will not allow users to enter the ending balances for credit card transactions |
||||
|
, for example. |
@ -0,0 +1,7 @@ |
|||||
|
* Go to *Point of Sale*, then to a session and go to *Settings*. |
||||
|
* Define the payment methods that you want to use. |
||||
|
* Start the Session and create some POS orders. |
||||
|
* Go back to the POS Session and press "Close". |
||||
|
* Press the button "Update Ending Balances" in the POS Session, and enter |
||||
|
the final balance in the column "Balance End Real". |
||||
|
|
@ -0,0 +1 @@ |
|||||
|
from . import test_pos_statement_closing_balance |
@ -0,0 +1,65 @@ |
|||||
|
# Copyright 2020 ForgeFlow, S.L. |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
from odoo.tests.common import TransactionCase |
||||
|
from odoo.exceptions import ValidationError |
||||
|
|
||||
|
|
||||
|
class TestPosStatementClosingBalance(TransactionCase): |
||||
|
def setUp(self): |
||||
|
super(TestPosStatementClosingBalance, self).setUp() |
||||
|
self.pos_config = self.env["pos.config"].create({"name": "PoS config"}) |
||||
|
bank_journal = self.env["account.journal"].create({ |
||||
|
"name": "Test bank", |
||||
|
"code": "TB1", |
||||
|
"type": "bank", |
||||
|
"pos_control_ending_balance": True, |
||||
|
}) |
||||
|
self.pos_config.journal_ids += bank_journal |
||||
|
self.pos_config.open_session_cb() |
||||
|
self.session = self.pos_config.current_session_id |
||||
|
self.session.action_pos_session_open() |
||||
|
|
||||
|
def test_wizard(self): |
||||
|
journal = self.session.journal_ids.filtered(lambda j: j.code == 'TB1') |
||||
|
wizard = ( |
||||
|
self.env["cash.box.journal.in"] |
||||
|
.with_context( |
||||
|
active_model="pos.session", active_ids=self.session.ids |
||||
|
) |
||||
|
.create({"amount": 10, "name": "Out"}) |
||||
|
) |
||||
|
wizard.journal_id = journal |
||||
|
wizard.run() |
||||
|
self.assertEqual( |
||||
|
self.session.statement_ids.filtered( |
||||
|
lambda r: r.journal_id.id == journal.id |
||||
|
).difference, |
||||
|
-10.0, |
||||
|
) |
||||
|
with self.assertRaises(ValidationError): |
||||
|
self.session.action_pos_session_closing_control() |
||||
|
|
||||
|
wizard = ( |
||||
|
self.env["pos.update.bank.statement.closing.balance"] |
||||
|
.with_context( |
||||
|
active_model="pos.session", active_ids=self.session.ids |
||||
|
) |
||||
|
.create({}) |
||||
|
) |
||||
|
for item in wizard.item_ids: |
||||
|
item.balance_end_real = 2.0 |
||||
|
wizard.action_confirm() |
||||
|
self.assertEqual( |
||||
|
self.session.statement_ids.filtered( |
||||
|
lambda r: r.journal_id.id == journal.id |
||||
|
).balance_end, |
||||
|
2.0, |
||||
|
) |
||||
|
self.assertEqual( |
||||
|
self.session.statement_ids.filtered( |
||||
|
lambda r: r.journal_id.id == journal.id |
||||
|
).difference, |
||||
|
0, |
||||
|
) |
||||
|
self.session.action_pos_session_closing_control() |
||||
|
self.assertEqual(self.session.state, 'closed') |
@ -0,0 +1,13 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<record id="view_account_journal_form" model="ir.ui.view"> |
||||
|
<field name="name">account.journal.form</field> |
||||
|
<field name="model">account.journal</field> |
||||
|
<field name="inherit_id" ref="account.view_account_journal_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="type" position="after"> |
||||
|
<field name="pos_control_ending_balance"/> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,19 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<record id="view_pos_session_form" model="ir.ui.view"> |
||||
|
<field name="name">pos.session.form.view</field> |
||||
|
<field name="model">pos.session</field> |
||||
|
<field name="inherit_id" ref="point_of_sale.view_pos_session_form"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<button name="open_cashbox" position="after"> |
||||
|
<button name="button_update_statement_ending_balance" |
||||
|
class="oe_stat_button" |
||||
|
attrs="{'invisible':[('state', '=', 'closed')]}" |
||||
|
icon="fa-money" |
||||
|
type="object" context="{'balance': 'end'}"> |
||||
|
<span class="o_stat_text">Update Ending Balances</span> |
||||
|
</button> |
||||
|
</button> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1 @@ |
|||||
|
from . import pos_update_statement_closing_balance |
@ -0,0 +1,121 @@ |
|||||
|
# Copyright 2020 ForgeFlow, S.L. |
||||
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
||||
|
from odoo import api, fields, models, _ |
||||
|
from odoo.exceptions import UserError |
||||
|
|
||||
|
|
||||
|
class POSBankStatementUpdateClosingBalance(models.TransientModel): |
||||
|
_name = "pos.update.bank.statement.closing.balance" |
||||
|
_description = 'POS Update Bank Statement Closing Balance' |
||||
|
|
||||
|
session_id = fields.Many2one( |
||||
|
comodel_name='pos.session', |
||||
|
) |
||||
|
item_ids = fields.One2many( |
||||
|
comodel_name="pos.update.bank.statement.closing.balance.line", |
||||
|
inverse_name="wiz_id", |
||||
|
string="Items", |
||||
|
) |
||||
|
|
||||
|
@api.model |
||||
|
def _prepare_item(self, session, statement): |
||||
|
return { |
||||
|
"statement_id": statement.id, |
||||
|
"name": statement.name, |
||||
|
"journal_id": statement.journal_id.id, |
||||
|
"balance_start": statement.balance_start, |
||||
|
"total_entry_encoding": statement.total_entry_encoding, |
||||
|
"currency_id": statement.currency_id.id, |
||||
|
} |
||||
|
|
||||
|
@api.model |
||||
|
def default_get(self, flds): |
||||
|
res = super().default_get(flds) |
||||
|
session_obj = self.env["pos.session"] |
||||
|
active_ids = self.env.context["active_ids"] or [] |
||||
|
active_model = self.env.context["active_model"] |
||||
|
|
||||
|
if not active_ids: |
||||
|
return res |
||||
|
assert active_model == "pos.session", \ |
||||
|
"Bad context propagation" |
||||
|
|
||||
|
items = [] |
||||
|
if len(active_ids) > 1: |
||||
|
raise UserError(_('You cannot start the closing ' |
||||
|
'balance for multiple POS sessions')) |
||||
|
session = session_obj.browse(active_ids[0]) |
||||
|
for statement in session.statement_ids: |
||||
|
if statement.journal_id.type != 'cash' or \ |
||||
|
(statement.journal_id.type == 'cash' and |
||||
|
not session.cash_control): |
||||
|
items.append([0, 0, self._prepare_item(session, statement)]) |
||||
|
res["session_id"] = session.id |
||||
|
res["item_ids"] = items |
||||
|
return res |
||||
|
|
||||
|
@api.model |
||||
|
def _prepare_cash_box_journal(self, item): |
||||
|
return { |
||||
|
'amount': abs(item.difference), |
||||
|
'name': _('Out'), |
||||
|
"journal_id": item.journal_id.id, |
||||
|
} |
||||
|
|
||||
|
@api.multi |
||||
|
def action_confirm(self): |
||||
|
self.ensure_one() |
||||
|
for item in self.item_ids: |
||||
|
if item.difference: |
||||
|
if item.difference > 0.0: |
||||
|
model = "cash.box.journal.in" |
||||
|
else: |
||||
|
model = "cash.box.journal.out" |
||||
|
wizard = ( |
||||
|
self.env[model] |
||||
|
.with_context( |
||||
|
active_model="pos.session", |
||||
|
active_ids=self.session_id.ids |
||||
|
).create(self._prepare_cash_box_journal(item)) |
||||
|
) |
||||
|
wizard.run() |
||||
|
item.statement_id.balance_end_real = item.balance_end_real |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
class BankStatementLineUpdateEndingBalanceLine(models.TransientModel): |
||||
|
_name = "pos.update.bank.statement.closing.balance.line" |
||||
|
_description = 'POS Update Bank Statement Closing Balance Line' |
||||
|
|
||||
|
wiz_id = fields.Many2one( |
||||
|
comodel_name='pos.update.bank.statement.closing.balance', |
||||
|
required=True, |
||||
|
) |
||||
|
statement_id = fields.Many2one( |
||||
|
comodel_name='account.bank.statement', |
||||
|
) |
||||
|
name = fields.Char( |
||||
|
related='statement_id.name' |
||||
|
) |
||||
|
journal_id = fields.Many2one( |
||||
|
comodel_name='account.journal', |
||||
|
related='statement_id.journal_id', |
||||
|
) |
||||
|
balance_start = fields.Monetary( |
||||
|
related='statement_id.balance_start', |
||||
|
) |
||||
|
total_entry_encoding = fields.Monetary( |
||||
|
related='statement_id.total_entry_encoding', |
||||
|
) |
||||
|
balance_end = fields.Monetary(compute='_compute_balance_end') |
||||
|
balance_end_real = fields.Monetary(default=0.0) |
||||
|
difference = fields.Monetary(compute='_compute_balance_end') |
||||
|
currency_id = fields.Many2one( |
||||
|
comodel_name='res.currency', |
||||
|
related='statement_id.currency_id' |
||||
|
) |
||||
|
|
||||
|
def _compute_balance_end(self): |
||||
|
for rec in self: |
||||
|
rec.balance_end = rec.balance_start + rec.total_entry_encoding |
||||
|
rec.difference = rec.balance_end_real - rec.balance_end |
@ -0,0 +1,33 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<odoo> |
||||
|
<record id="pos_update_bank_statement_closing_balance_form" model="ir.ui.view"> |
||||
|
<field name="name">pos.update.bank.statement.closing.balance.form</field> |
||||
|
<field name="model">pos.update.bank.statement.closing.balance</field> |
||||
|
<field name="type">form</field> |
||||
|
<field name="arch" type="xml"> |
||||
|
<form string="Update Ending Balance"> |
||||
|
<field name="item_ids" nolabel="1"> |
||||
|
<tree string="Statements" create="0" editable="top"> |
||||
|
<field name="name"/> |
||||
|
<field name="journal_id"/> |
||||
|
<field name="balance_start"/> |
||||
|
<field name="total_entry_encoding"/> |
||||
|
<field name="balance_end_real"/> |
||||
|
<field name="currency_id" groups="base.group_multi_currency"/> |
||||
|
</tree> |
||||
|
</field> |
||||
|
<footer> |
||||
|
<button name="action_confirm" string="Confirm" type="object" class="btn-primary"/> |
||||
|
<button special="cancel" string="Cancel" class="btn-default"/> |
||||
|
</footer> |
||||
|
</form> |
||||
|
</field> |
||||
|
</record> |
||||
|
|
||||
|
<record model="ir.actions.act_window" id="action_pos_update_bank_statement_closing_balance"> |
||||
|
<field name="name">Update Ending Balances</field> |
||||
|
<field name="res_model">pos.update.bank.statement.closing.balance</field> |
||||
|
<field name="view_mode">form</field> |
||||
|
<field name="target">new</field> |
||||
|
</record> |
||||
|
</odoo> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue