Jordi Ballester Alomar
4 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 377 additions and 0 deletions
-
2pos_statement_closing_balance/__init__.py
-
22pos_statement_closing_balance/__manifest__.py
-
2pos_statement_closing_balance/models/__init__.py
-
14pos_statement_closing_balance/models/account_journal.py
-
34pos_statement_closing_balance/models/pos_session.py
-
4pos_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
-
84pos_statement_closing_balance/tests/test_pos_statement_closing_balance.py
-
14pos_statement_closing_balance/views/account_journal_views.xml
-
19pos_statement_closing_balance/views/pos_session_views.xml
-
1pos_statement_closing_balance/wizards/__init__.py
-
128pos_statement_closing_balance/wizards/pos_update_statement_closing_balance.py
-
34pos_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_move_reason', |
|||
], |
|||
'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,14 @@ |
|||
# 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') |
|||
pos_move_reason_id = fields.Many2one( |
|||
string='Default reason POS adjustments', |
|||
comodel_name='pos.move.reason', |
|||
) |
@ -0,0 +1,34 @@ |
|||
# Copyright 2020 ForgeFlow, S.L. |
|||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). |
|||
from odoo import api, models, _ |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
|
|||
class PosSession(models.Model): |
|||
_inherit = "pos.session" |
|||
|
|||
@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,4 @@ |
|||
* 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, and enter the default POS |
|||
move reason to be used in the adjustment. |
@ -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,84 @@ |
|||
# 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"}) |
|||
self.wizard_reason = self.env['wizard.pos.move.reason'] |
|||
account = self.env['account.account'].create({ |
|||
'code': '9999', |
|||
'name': 'Test', |
|||
'user_type_id': self.env.ref( |
|||
"account.data_account_type_liquidity").id |
|||
}) |
|||
self.bank_journal = self.env["account.journal"].create({ |
|||
"name": "Test bank", |
|||
"code": "TB1", |
|||
"type": "bank", |
|||
"pos_control_ending_balance": True, |
|||
}) |
|||
self.pos_move_reason = self.env['pos.move.reason'].create({ |
|||
'name': 'Bank closing reason', |
|||
'is_income_reason': True, |
|||
'is_expense_reason': True, |
|||
'expense_account_id': account.id, |
|||
'income_account_id': account.id, |
|||
'journal_ids': self.bank_journal.ids, |
|||
'company_id': self.env.ref('base.main_company').id, |
|||
}) |
|||
self.bank_journal.pos_move_reason_id = self.pos_move_reason |
|||
self.pos_config.journal_ids += self.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.wizard_reason.with_context( |
|||
active_id=self.session.id, |
|||
default_move_type='income').create({ |
|||
'move_reason_id': self.pos_move_reason.id, |
|||
'journal_id': self.bank_journal.id, |
|||
'statement_id': self.session.statement_ids.filtered( |
|||
lambda s: s.journal_id == self.bank_journal).id, |
|||
'amount': 10, |
|||
'name': 'Test Bank Deposit', |
|||
}) |
|||
wizard.apply() |
|||
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,14 @@ |
|||
<?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" attrs="{'invisible':[('journal_user', '=', False)]}"/> |
|||
<field name="pos_move_reason_id" attrs="{'invisible':[('pos_control_ending_balance', '=', False)]}"/> |
|||
</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,128 @@ |
|||
# 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, |
|||
"pos_move_reason_id": statement.journal_id.pos_move_reason_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.filtered( |
|||
lambda s: s.journal_id.pos_control_ending_balance): |
|||
items.append([0, 0, self._prepare_item(session, statement)]) |
|||
res["session_id"] = session.id |
|||
res["item_ids"] = items |
|||
return res |
|||
|
|||
@api.model |
|||
def _prepare_wizard_pos_move_reason(self, item): |
|||
return { |
|||
'move_reason_id': |
|||
item.statement_id.journal_id.pos_move_reason_id.id, |
|||
'amount': abs(item.difference), |
|||
'journal_id': item.journal_id.id, |
|||
'name': _("Ending balance adjustment") |
|||
} |
|||
|
|||
@api.multi |
|||
def action_confirm(self): |
|||
self.ensure_one() |
|||
for item in self.item_ids: |
|||
if item.difference: |
|||
if item.difference > 0.0: |
|||
default_move_type = "income" |
|||
else: |
|||
default_move_type = "expense" |
|||
wizard = ( |
|||
self.env["wizard.pos.move.reason"] |
|||
.with_context( |
|||
active_model="pos.session", |
|||
active_ids=self.session_id.ids, |
|||
active_id=self.session_id.id, |
|||
default_move_type=default_move_type, |
|||
).create(self._prepare_wizard_pos_move_reason(item)) |
|||
) |
|||
wizard.apply() |
|||
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' |
|||
) |
|||
pos_move_reason_id = fields.Many2one( |
|||
comodel_name='pos.move.reason', |
|||
string='Reason', |
|||
) |
|||
|
|||
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,34 @@ |
|||
<?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"/> |
|||
<field name="pos_move_reason_id" invisible="1"/> |
|||
</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