Browse Source

Merge 484d5a61b2 into 45a15ac568

pull/476/merge
Quentin Dupont 4 years ago
committed by GitHub
parent
commit
e081b38f6a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 114
      pos_multiple_control/README.rst
  2. 2
      pos_multiple_control/__init__.py
  3. 33
      pos_multiple_control/__manifest__.py
  4. 21
      pos_multiple_control/demo/account_account.xml
  5. 26
      pos_multiple_control/demo/account_journal.xml
  6. 22
      pos_multiple_control/demo/pos_config.xml
  7. 23
      pos_multiple_control/demo/pos_move_reason.xml
  8. 20
      pos_multiple_control/demo/res_groups.xml
  9. 497
      pos_multiple_control/i18n/fr.po
  10. 4
      pos_multiple_control/models/__init__.py
  11. 144
      pos_multiple_control/models/account_bank_statement.py
  12. 24
      pos_multiple_control/models/account_journal.py
  13. 49
      pos_multiple_control/models/pos_config.py
  14. 168
      pos_multiple_control/models/pos_session.py
  15. 25
      pos_multiple_control/readme/CONFIGURE.rst
  16. 3
      pos_multiple_control/readme/CONTRIBUTORS.rst
  17. 27
      pos_multiple_control/readme/DESCRIPTION.rst
  18. BIN
      pos_multiple_control/static/description/account_journal_config.png
  19. BIN
      pos_multiple_control/static/description/change_starting_balance.gif
  20. BIN
      pos_multiple_control/static/description/end_session_balance_automatic_solve.gif
  21. BIN
      pos_multiple_control/static/description/icon.png
  22. 469
      pos_multiple_control/static/description/index.html
  23. BIN
      pos_multiple_control/static/description/open_new_session.png
  24. BIN
      pos_multiple_control/static/description/pos_session.png
  25. BIN
      pos_multiple_control/static/description/pos_session_config.png
  26. 1
      pos_multiple_control/tests/__init__.py
  27. 170
      pos_multiple_control/tests/test_pos_multiple_control.py
  28. 31
      pos_multiple_control/views/view_account_journal.xml
  29. 45
      pos_multiple_control/views/view_pos_config.xml
  30. 135
      pos_multiple_control/views/view_pos_session.xml
  31. 1
      pos_multiple_control/wizard/__init__.py
  32. 153
      pos_multiple_control/wizard/wizard_pos_update_statement_balance.py
  33. 38
      pos_multiple_control/wizard/wizard_pos_update_statement_balance.xml

114
pos_multiple_control/README.rst

@ -0,0 +1,114 @@
=====================================
Point Of Sale - Multiple Cash Control
=====================================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-quentinDupont%2Fgrap--odoo--incubator-lightgray.png?logo=github
:target: https://github.com/quentinDupont/grap-odoo-incubator/tree/12.0_pos_multiple_control/pos_multiple_control
:alt: quentinDupont/grap-odoo-incubator
|badge1| |badge2| |badge3|
This module extends the functionality of the point of sale by allowing a
better control at the closing of the session.
* Show differences for all statements chosen (see config part)
* Allow user to control each statement. (not only the cash statement, by
default) and change his starting and ending balance
.. figure:: https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/change_starting_balance.gif
* Quickly solve differences (we can set an absolute limit),
thanks to pos_move_reason
See this gif where where we set ending balance with difference, solve it and
close session :
.. figure:: https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/end_session_balance_automatic_solve.gif
As the verification is more complete, allow the user to reopen a new session,
if the first one is in a closed state.
.. figure:: https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/open_new_session.png
Extra checks are done, to prevent user errors:
* It is not possible to click on the button 'Close Session' if there are some
draft orders.
**Table of contents**
.. contents::
:local:
Configuration
=============
To configure this module, you need to:
**Configure the journals** you want to control in your point of sale (POS)
* Go to Invoicing / Configuration / Journals / Journals
* Check the box 'POS Journal Control' if you want to enable this feature for this journal
* Unchecked journal will be in grey
.. figure:: https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/account_journal_config.png
**Configure your POS configuration**
* Go to Point of Sale / Configuration / Point of Sale / <your pos>
* Choose your Payments methods (1)
* Check Cash Control (2)
* (optional) Choose a "pos move reason" to ausolve difference (3) (see pos_move_reason module for more details)
* (optional) Choose a limit to allow or not the user to autosolve control difference in pos - Set 0 if you don't want any limit. (4)
.. figure:: https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/pos_session_config.png
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/quentinDupont/grap-odoo-incubator/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/quentinDupont/grap-odoo-incubator/issues/new?body=module:%20pos_multiple_control%0Aversion:%2012.0_pos_multiple_control%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
~~~~~~~
* GRAP
Contributors
~~~~~~~~~~~~
* Sylvain LE GAL <https://twitter.com/legalsylvain>
* Julien WESTE
* Quentin DUPONT <https://twitter.com/pondupont>
Maintainers
~~~~~~~~~~~
This module is part of the `quentinDupont/grap-odoo-incubator <https://github.com/quentinDupont/grap-odoo-incubator/tree/12.0_pos_multiple_control/pos_multiple_control>`_ project on GitHub.
You are welcome to contribute.

2
pos_multiple_control/__init__.py

@ -0,0 +1,2 @@
from . import wizard
from . import models

33
pos_multiple_control/__manifest__.py

@ -0,0 +1,33 @@
# Copyright (C) 2013 - Today: GRAP (http://www.grap.coop)
# @author Julien WESTE
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# @author: Quentin DUPONT (https://twitter.com/pondupont)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "Point Of Sale - Multiple Cash Control",
"summary": "Allow user to control each statement and add extra checks",
"version": "12.0.1.0.0",
"category": "Point of Sale",
"author": "GRAP, Odoo Community Association (OCA)",
"website": "http://www.grap.coop",
"license": "AGPL-3",
"depends": [
"point_of_sale",
"pos_cash_move_reason"
],
"data": [
"views/view_account_journal.xml",
"views/view_pos_config.xml",
"views/view_pos_session.xml",
"wizard/wizard_pos_update_statement_balance.xml"
],
'demo': [
'demo/res_groups.xml',
'demo/account_account.xml',
'demo/account_journal.xml',
'demo/pos_move_reason.xml',
'demo/pos_config.xml',
],
"installable": True,
}

21
pos_multiple_control/demo/account_account.xml

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2018 - Today: GRAP (http://www.grap.coop)
@author: Quentin DUPONT (https://twitter.com/pondupont)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="register_error_income" model="account.account">
<field name="code">7582</field>
<field name="name">Cash Register Error Income</field>
<field name="user_type_id" ref="account.data_account_type_revenue"/>
</record>
<record id="register_error_expense" model="account.account">
<field name="code">6582</field>
<field name="name">Cash Register Error Expense</field>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
</record>
</odoo>

26
pos_multiple_control/demo/account_journal.xml

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="check_journal" model="account.journal">
<field name="code">CHK-C</field>
<field name="type">bank</field>
<field name="name">Check Journal (With Control)</field>
<field name="journal_user" eval="True" />
<field name="pos_control" eval="True" />
</record>
<record id="cash_journal" model="account.journal">
<field name="code">CSH-C</field>
<field name="type">cash</field>
<field name="name">Cash Journal (With Control)</field>
<field name="journal_user" eval="True" />
<field name="pos_control" eval="True" />
</record>
</odoo>

22
pos_multiple_control/demo/pos_config.xml

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
@author Quentin DUPONT (https://twitter.com/pondupont)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="pos_config_control" model="pos.config">
<field name="name">Point Of Sale (With Mutiple Controls)</field>
<field name="picking_type_id" ref="point_of_sale.picking_type_posout" />
<field name="journal_ids" eval="[
(4, ref('check_journal')),
(4, ref('cash_journal'))]" />
<field name="cash_control" eval="True" />
<field name="autosolve_pos_move_reason" eval="cash_register_error" />
</record>
</odoo>

23
pos_multiple_control/demo/pos_move_reason.xml

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
@author Quentin DUPONT (https://twitter.com/pondupont)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record model="pos.move.reason" id="cash_register_error">
<field name="name">Cash register error</field>
<field name="is_income_reason" eval="True" />
<field name="is_expense_reason" eval="True"/>
<field name="income_account_id" ref="register_error_income"/>
<field name="expense_account_id" ref="register_error_expense"/>
<field name="journal_ids" model="account.journal" search="[
('type', '=', 'cash'),
('company_id', '=', obj().env.ref('base.main_company').id),
]"/>
<field name="company_id" ref="base.main_company"/>
</record>
</odoo>

20
pos_multiple_control/demo/res_groups.xml

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2015 - Today: GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="account.group_account_manager" model="res.groups">
<field name="users" eval="[
(4, ref('base.user_root'))]" />
</record>
<record id="point_of_sale.group_pos_user" model="res.groups">
<field name="users" eval="[
(4, ref('base.user_demo'))]" />
</record>
</odoo>

497
pos_multiple_control/i18n/fr.po

@ -0,0 +1,497 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * pos_multiple_control
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-09 15:56+0000\n"
"PO-Revision-Date: 2020-04-09 15:56+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "$ 3.12"
msgstr "3,12 $"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "$ 4.40"
msgstr "4,40 $"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "$ 4.50"
msgstr "4,50 $"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "$ 8.50"
msgstr "8,50 $"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "+ Transactions"
msgstr ""
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "<span class=\"o_stat_text\">Set Opening Balance</span>"
msgstr "<span class=\"o_stat_text\">Réglez le solde d'ouverture</span>"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "<span class=\"pos-change_amount\">$ 0.86</span>"
msgstr "<span class=\"pos-change_amount\"/>"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "<span class=\"pos-change_title\">Change</span>"
msgstr "<span class=\"pos-change_title\"/>"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "<span class=\"total-amount-formatting\">TOTAL</span>"
msgstr ""
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "<span id=\"total-amount\" class=\"pos_total-amount\">$ 469.14</span>"
msgstr "<span class=\"pos_total-amount\" id=\"total-amount\"/>"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "<span>$ 470.00</span>"
msgstr "<span/>"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "<span>Cash (USD):</span>"
msgstr "<span/>"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_wizard_pos_update_bank_statement_balance_form
msgid "Apply"
msgstr "Appliquer"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_config__autosolve_limit
msgid "Autosolve limit"
msgstr "Limite de résolution automatique"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_config__autosolve_pos_move_reason
msgid "Autosolve pos move reason"
msgstr "Raison de résolution automatique"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__balance_end_real
msgid "Balance End Real"
msgstr "Solde réel de fermeture"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__balance_start_real
msgid "Balance Start Real"
msgstr "Solde réel d'ouverture"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__balance_moment
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__balance_moment
msgid "Balance moment"
msgstr "Moment de balance"
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_account_bank_statement
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__statement_ids
msgid "Bank Statement"
msgstr "Relevé bancaire"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_wizard_pos_update_bank_statement_balance_form
msgid "Cancel"
msgstr "Annuler"
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_cash_box_in
msgid "Cash Box In"
msgstr ""
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_cash_box_out
msgid "Cash Box Out"
msgstr ""
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__balance_end
msgid "Computed Balance"
msgstr "Solde calculé"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_account_bank_statement__control_difference
msgid "Control Difference"
msgstr "Différence"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__control_statement_ids
msgid "Control statements"
msgstr "Déclarations de contrôle"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_account_bank_statement__control_balance
msgid "Controled Balance"
msgstr "Solde contrôlé"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__create_uid
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__create_uid
msgid "Created by"
msgstr "Créé par"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__create_date
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__create_date
msgid "Created on"
msgstr "Créé le"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__currency_id
msgid "Currency"
msgstr "Devise"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__session_id
msgid "Current Session"
msgstr "Session en cours"
#. module: pos_multiple_control
#: selection:wizard.pos.update.bank.statement.balance,balance_moment:0
msgid "Default"
msgstr "Par défaut"
#. module: pos_multiple_control
#: model:product.template,name:pos_multiple_control.demo_product_cash_box
msgid "Demo Product Cash box product"
msgstr "Produit de démonstration Produit de caisse"
#. module: pos_multiple_control
#: model:product.template,name:pos_multiple_control.demo_product_not_cash_box
msgid "Demo Product Not Cash box product"
msgstr "Produit de démonstration Pas un produit de caisse"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "Desk Organizer"
msgstr ""
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__display_name
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__display_name
msgid "Display Name"
msgstr "Nom affiché"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_account_bank_statement__display_autosolve
msgid "Display autosolve"
msgstr "Afficher résolution automatique"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Do you really want to solve this difference automatically ?"
msgstr "Voulez-vous vraiment solder cette différence automatiquement ?"
#. module: pos_multiple_control
#: selection:wizard.pos.update.bank.statement.balance,balance_moment:0
msgid "Ending balance"
msgstr "Solde de fermeture"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_wizard_pos_update_bank_statement_balance_form
msgid "Here you can change the"
msgstr "Ici vous pouvez changer le"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__id
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__id
msgid "ID"
msgstr ""
#. module: pos_multiple_control
#: model:ir.model.fields,help:pos_multiple_control.field_account_journal__pos_control
msgid "If you want this journal to be controled at closing of point of sale, check this option"
msgstr "Si vous souhaitez que ce journal soit contrôlé à la fermeture du point de vente, cochez cette option"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__item_ids
msgid "Items"
msgstr "Articles"
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_account_journal
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__journal_id
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__journal_id
msgid "Journal"
msgstr ""
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance____last_update
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line____last_update
msgid "Last Modified on"
msgstr "Dernière modification le"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__write_uid
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__write_uid
msgid "Last Updated by"
msgstr "Dernière mise à jour par"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance__write_date
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour le"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "Led Lamp"
msgstr "Lampe Led"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_config_form
msgid "Limit to display autosolve button"
msgstr "Limite d'affichage du bouton de résolution automatique"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "Monitor Stand"
msgstr ""
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_config_kanban
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "New Session"
msgstr "Nouvelle session"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "Odoo Logo"
msgstr "Logo Odoo"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__control_register_balance_start
msgid "Opening Balances"
msgstr "Solde d'ouverture"
#. module: pos_multiple_control
#: code:addons/pos_multiple_control/wizard/wizard_pos_update_statement_balance.py:85
#, python-format
msgid "Out"
msgstr "Sortie"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_account_journal__pos_control
msgid "POS Journal Control"
msgstr "Journal de contrôle du point de vente"
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_wizard_pos_update_bank_statement_balance
msgid "POS Update Bank Statement Balance"
msgstr "Mise à jour du solde de relevé de banque"
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_wizard_pos_update_bank_statement_balance_line
msgid "POS Update Bank Statement Balance Line"
msgstr "Mise à jour des lignes de solde de relevé de banque"
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_pos_config
msgid "Point of Sale Configuration"
msgstr "Paramétrage du point de vente"
#. module: pos_multiple_control
#: model:ir.model,name:pos_multiple_control.model_pos_session
msgid "Point of Sale Session"
msgstr "Session du point de vente"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_account_bank_statement__is_pos_control
msgid "Pos control Bank statement"
msgstr "Contrôle des point de vente Relevé bancaire"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_account_bank_statement__pos_session_state
msgid "Pos session state"
msgstr "État de la session de vente"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "Price"
msgstr "Prix"
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "Quantity"
msgstr "Quantité"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Real Closing Balance"
msgstr "Solde de clôture réel"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__control_register_balance
msgid "Real Closing Balances"
msgstr "Réel solde de clôture"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_cash_box_out__ref
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__name
msgid "Reference"
msgstr "Référence"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Set your ending balance"
msgstr "Définissez votre solde de clôture"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Set your starting balance"
msgstr "Définissez votre solde d'ouverture"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Solve difference automatically with a product choosen in pos config"
msgstr "Résoudre la différence automatiquement avec un produit choisi dans la configuration du point de vente"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__balance_start
msgid "Starting Balance"
msgstr "Solde initial"
#. module: pos_multiple_control
#: selection:wizard.pos.update.bank.statement.balance,balance_moment:0
msgid "Starting balance"
msgstr "Solde initial"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__statement_id
msgid "Statement"
msgstr "Relevé"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_wizard_pos_update_bank_statement_balance_form
msgid "Statements"
msgstr "Relevés"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__summary_statement_ids
msgid "Summary statements"
msgstr "Résumé de relevés"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__control_register_balance_end
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Theoretical Closing Balances"
msgstr "Solde de fermeture théorique"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Total"
msgstr ""
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__control_register_difference
msgid "Total difference"
msgstr "Différence total"
#. module: pos_multiple_control
#: model:ir.model.fields,help:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__total_entry_encoding
msgid "Total of transaction lines."
msgstr "Total des lignes de transaction. "
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_pos_session__control_register_total_entry_encoding
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_pos_session_form
msgid "Transactions"
msgstr ""
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__total_entry_encoding
msgid "Transactions Subtotal"
msgstr "Sous-total des transactions"
#. module: pos_multiple_control
#: model:product.template,uom_name:pos_multiple_control.demo_product_cash_box
#: model:product.template,uom_name:pos_multiple_control.demo_product_not_cash_box
msgid "Unit(s)"
msgstr "Unité(s)"
#. module: pos_multiple_control
#: model_terms:ir.ui.view,arch_db:pos_multiple_control.view_wizard_pos_update_bank_statement_balance_form
msgid "Update Balance"
msgstr "Mettre à jour le solde"
#. module: pos_multiple_control
#: model:ir.actions.act_window,name:pos_multiple_control.action_wizard_pos_update_bank_statement_balance
msgid "Update Balances"
msgstr "Mettre à jour les soldes"
#. module: pos_multiple_control
#: code:addons/pos_multiple_control/models/account_bank_statement.py:123
#, python-format
msgid "We can't autosolve this difference. \n"
"You need to configure the Point Of Sale config and choose a pos move reason for autosolving this difference."
msgstr "On ne peut pas résoudre automatiquement cette différence. \n"
"Vous devez paramétrer la configuration de caisse et choisir un article spécial pour résoudre automatiquement cette différence."
#. module: pos_multiple_control
#: model_terms:pos.config,customer_facing_display_html:pos_multiple_control.pos_config_control
msgid "Whiteboard Pen"
msgstr "Marqueur pour Tableau Blanc"
#. module: pos_multiple_control
#: model:ir.model.fields,field_description:pos_multiple_control.field_wizard_pos_update_bank_statement_balance_line__wiz_id
msgid "Wiz"
msgstr ""
#. module: pos_multiple_control
#: code:addons/pos_multiple_control/models/pos_session.py:125
#, python-format
msgid "You can not close this session because the journal %s (from %s) has a not null difference: %s%s \n"
" You have to change his starting or ending balance"
msgstr "Vous ne pouvez pas fermer cette session car le relevé %s (de %s) a une différence non nulle : %s%s \n"
"Vous devez modifier son solde de départ ou de fin."
#. module: pos_multiple_control
#: code:addons/pos_multiple_control/models/pos_session.py:110
#, python-format
msgid "You can not end this session because there are some draft orders: \n"
"\n"
"- %s"
msgstr "Vous ne pouvez pas fermer cette session car le relevé %s a une différence non nulle : \n"
"\n"
"- %s"
#. module: pos_multiple_control
#: code:addons/pos_multiple_control/models/pos_box.py:55
#, python-format
msgid "You cannot put/take money in/out for the statement %s which is closed"
msgstr "Vous ne pouvez pas prendre ou retirer de l'argent sur le relevé %s car celui ci est fermé"
#. module: pos_multiple_control
#: code:addons/pos_multiple_control/wizard/wizard_pos_update_statement_balance.py:67
#, python-format
msgid "You cannot start the closing balance for multiple POS sessions"
msgstr "Vous ne pouvez changer en même temps le solde de plusieurs sessions de vente"
#. module: pos_multiple_control
#: model:product.template,weight_uom_name:pos_multiple_control.demo_product_cash_box
#: model:product.template,weight_uom_name:pos_multiple_control.demo_product_not_cash_box
msgid "kg"
msgstr ""

4
pos_multiple_control/models/__init__.py

@ -0,0 +1,4 @@
from . import account_bank_statement
from . import account_journal
from . import pos_session
from . import pos_config

144
pos_multiple_control/models/account_bank_statement.py

@ -0,0 +1,144 @@
# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# @author Quentin DUPONT (quentin.dupont@grap.coop)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import Warning as UserError
import odoo.addons.decimal_precision as dp
class AccountBankStatement(models.Model):
_inherit = "account.bank.statement"
# Columns Section
control_balance = fields.Float(
string="Controled Balance",
compute="_compute_control_balance",
digits=dp.get_precision('Account'),
)
control_difference = fields.Float(
string="Control Difference",
compute="_compute_control_difference",
digits=dp.get_precision('Account'),
)
is_pos_control = fields.Boolean(
string="Pos control Bank statement",
compute="_compute_is_pos_control", store=True,
)
pos_session_state = fields.Char(
string="Pos session state", compute="_compute_pos_session_state"
)
display_autosolve = fields.Boolean(
string="Display autosolve", compute="_compute_display_autosolve"
)
# Compute Section
@api.multi
@api.depends("line_ids")
def _compute_control_balance(self):
for statement in self:
statement.control_balance = sum(
statement.mapped("balance_end_real")
)
@api.multi
@api.depends("control_balance", "total_entry_encoding", "balance_end_real")
def _compute_control_difference(self):
for statement in self:
statement.control_difference = (
+ statement.balance_end_real
- statement.balance_start
- statement.total_entry_encoding
)
@api.multi
@api.depends("journal_id.pos_control", "pos_session_state",
"balance_start")
def _compute_is_pos_control(self):
for statement in self:
journal = statement.journal_id
statement.is_pos_control = journal.pos_control
@api.multi
@api.depends("pos_session_id.state")
def _compute_pos_session_state(self):
for statement in self:
statement.pos_session_state = statement.pos_session_id.state
# Display button autosolve with some conditions
@api.multi
def _compute_display_autosolve(self):
for statement in self:
if not statement.journal_id.pos_control:
statement.display_autosolve = False
else:
if statement.pos_session_id.config_id.autosolve_limit:
difference_with_limit = (
abs(statement.control_difference)
- statement.pos_session_id.config_id.autosolve_limit
)
else:
difference_with_limit = -1
statement.display_autosolve = (
statement.pos_session_state not in
["closed"]
and difference_with_limit < 0
and abs(round(statement.control_difference, 3)) != 0
)
@api.multi
@api.depends("pos_session_state")
def automatic_solve(self):
self.WizardReason = self.env['wizard.pos.move.reason']
for statement in self:
pos_move_reason = statement.\
pos_session_id.config_id.autosolve_pos_move_reason
if pos_move_reason:
cb_pos_move_reason_id = pos_move_reason.id
cb_difference = statement.control_difference
cb_journal_id = statement.journal_id.id
if cb_difference < 0:
default_move_type = "expense"
else:
default_move_type = "income"
wizard = self.WizardReason.with_context(
active_id=statement.pos_session_id.id,
default_move_type=default_move_type).create({
'move_reason_id': cb_pos_move_reason_id,
'journal_id': cb_journal_id,
'statement_id': statement.id,
'amount': abs(cb_difference),
'name': 'Automatic solve',
})
wizard.apply()
else:
raise UserError(
_(
"We can't autosolve this difference. \nYou need to "
"configure the Point Of Sale config and choose a "
"pos move reason for autosolving this difference."
)
)
def open_cashbox_starting_balance(self):
return self.open_cashbox_balance('starting')
def open_cashbox_ending_balance(self):
return self.open_cashbox_balance('ending')
def open_cashbox_balance(self, balance_moment):
action = self.env.ref(
"pos_multiple_control."
"action_wizard_pos_update_bank_statement_balance").read()[0]
action['context'] = {'balance_moment': balance_moment,
'active_id': [self.id],
'active_pos_id': [self.pos_session_id.id],
'active_model': 'pos.session'}
return action

24
pos_multiple_control/models/account_journal.py

@ -0,0 +1,24 @@
# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class AccountJournal(models.Model):
_inherit = "account.journal"
pos_control = fields.Boolean(
string="POS Journal Control",
help="If you want this journal"
" to be controled at closing of point of sale, check this option",
default=False
)
@api.onchange("type")
def onchange_type(self):
for journal in self:
if journal.type in ["bank", "cash"]:
journal.pos_control = True
else:
journal.pos_control = False

49
pos_multiple_control/models/pos_config.py

@ -0,0 +1,49 @@
# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
# @author Quentin DUPONT (quentin.dupont@grap.coop)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
class PosConfig(models.Model):
_inherit = "pos.config"
# Columns Section
autosolve_pos_move_reason = fields.Many2one(
string="Autosolve pos move reason",
description="Product used to autosolve control difference",
comodel_name="pos.move.reason",
domain="['|', \
('is_income_reason', '=', True), ('is_expense_reason', '=', True)]",
default="",
)
autosolve_limit = fields.Float(
string="Autosolve limit",
description="Limit for autosolving bank statement", default=20
)
@api.multi
def open_new_session(self, openui):
self.ensure_one()
# Check if some opening / opened session exists
session_obj = self.env['pos.session']
sessions = session_obj.search([
('user_id', '=', self.env.uid),
('config_id', '=', self.id),
('state', 'in', ['opened', 'opening_control']),
], limit=1)
if sessions:
# An opening / opened session exists
session = sessions[0]
else:
# Create a session
session = session_obj.create({
'user_id': self.env.uid,
'config_id': self.id,
})
if session.state == 'opening_control' or openui is False:
return self._open_session(session.id)
return self.open_ui()

168
pos_multiple_control/models/pos_session.py

@ -0,0 +1,168 @@
# Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.exceptions import Warning as UserError
class PosSession(models.Model):
_inherit = "pos.session"
# Columns Section
statement_ids = fields.One2many(readonly=False)
control_statement_ids = fields.One2many(
string="Control statements",
comodel_name="account.bank.statement",
related="statement_ids",
)
summary_statement_ids = fields.One2many(
string="Summary statements",
comodel_name="account.bank.statement",
related="statement_ids",
)
control_register_balance_start = fields.Float(
compute='_compute_control_register_balance_start',
string='Opening Balances')
control_register_total_entry_encoding = fields.Float(
compute='_compute_control_register_total_entry_encoding',
string='Transactions')
control_register_balance_end = fields.Float(
compute='_compute_control_register_balance_end',
string='Theoretical Closing Balances')
control_register_balance = fields.Float(
compute='_compute_control_register_balance',
string='Real Closing Balances')
control_register_difference = fields.Float(
compute='_compute_control_register_difference',
string='Total difference')
# Compute Section
@api.multi
@api.depends('statement_ids.is_pos_control', 'statement_ids.balance_start')
def _compute_control_register_balance_start(self):
for session in self:
session.control_register_balance_start = sum(
session.statement_ids.filtered(
lambda x: x.is_pos_control).mapped('balance_start'))
@api.multi
@api.depends(
'statement_ids.is_pos_control', 'statement_ids.total_entry_encoding')
def _compute_control_register_total_entry_encoding(self):
for session in self:
session.control_register_total_entry_encoding = sum(
session.statement_ids.filtered(
lambda x: x.is_pos_control).mapped('total_entry_encoding'))
@api.multi
@api.depends(
'statement_ids.is_pos_control', 'statement_ids.balance_end_real')
def _compute_control_register_balance_end(self):
for session in self:
session.control_register_balance_end = sum(
session.statement_ids.filtered(
lambda x: x.is_pos_control).mapped('balance_end'))
@api.multi
@api.depends(
'statement_ids.is_pos_control', 'statement_ids.control_balance')
def _compute_control_register_balance(self):
for session in self:
session.control_register_balance = sum(
session.statement_ids.filtered(
lambda x: x.is_pos_control).mapped('control_balance'))
@api.multi
@api.depends(
'statement_ids.is_pos_control', 'statement_ids.control_difference')
def _compute_control_register_difference(self):
for session in self:
session.control_register_difference = sum(
session.statement_ids.filtered(
lambda x: x.is_pos_control).mapped('control_difference'))
# Model
@api.multi
def open_cashbox_opening(self):
return super(PosSession, self).open_cashbox()
@api.multi
def action_pos_session_new_session(self):
return self.config_id.open_new_session(False)
@api.multi
def wkf_action_closing_control(self):
for session in self:
draft_orders = session.order_ids.filtered(
lambda x: x.state == "draft"
)
if len(draft_orders):
raise UserError(
_(
"You can not end this session because there are some"
" draft orders: \n\n- %s"
)
% ("\n- ".join([x.name for x in draft_orders]))
)
return super(PosSession, self).wkf_action_closing_control()
@api.multi
def action_pos_session_validate(self):
for session in self:
for statement in session.statement_ids:
if statement.journal_id.pos_control is True:
if abs(statement.control_difference) > 0.001:
raise UserError(
_(
"You can not close this session because the "
"journal %s (from %s) has a not null "
"difference: %s%s \n You have to change his "
"starting or ending balance"
)
% (statement.journal_id.name, statement.name,
str(round(statement.control_difference, 3)),
statement.currency_id.symbol)
)
return super(PosSession, self).action_pos_session_validate()
# Constraints
@api.multi
@api.constrains('user_id', 'state')
def _check_unicity(self):
for session in self:
domain = [
("state", "in", ["opening_control", "opened"]),
("user_id", "=", session.user_id.id),
]
if self.search_count(domain) > 1:
raise ValidationError(
_(
"You cannot create two active sessions "
"with the same responsible!"
)
)
@api.multi
@api.constrains('config_id', 'state')
def _check_pos_config(self):
for session in self:
domain = [
("state", "in", ["opening_control", "opened"]),
("config_id", "=", session.config_id.id),
]
if self.search_count(domain) > 1:
raise ValidationError(
_(
"You cannot create two active sessions related"
" to the same point of sale!"
)
)

25
pos_multiple_control/readme/CONFIGURE.rst

@ -0,0 +1,25 @@
To configure this module, you need to:
**Configure the journals** you want to control in your point of sale (POS)
* Go to Invoicing / Configuration / Journals / Journals
* Check the box 'POS Journal Control' if you want to enable this feature for this journal
* Unchecked journal will be in grey
.. figure:: ../static/description/account_journal_config.png
**Configure your POS configuration**
* Go to Point of Sale / Configuration / Point of Sale / <your pos>
* Choose your Payments methods (1)
* Check Cash Control (2)
* (optional) Choose a "pos move reason" to ausolve difference (3) (see pos_move_reason module for more details)
* (optional) Choose a limit to allow or not the user to autosolve control difference in pos - Set 0 if you don't want any limit. (4)
.. figure:: ../static/description/pos_session_config.png

3
pos_multiple_control/readme/CONTRIBUTORS.rst

@ -0,0 +1,3 @@
* Sylvain LE GAL <https://twitter.com/legalsylvain>
* Julien WESTE
* Quentin DUPONT <https://twitter.com/pondupont>

27
pos_multiple_control/readme/DESCRIPTION.rst

@ -0,0 +1,27 @@
This module extends the functionality of the point of sale by allowing a
better control at the closing of the session.
* Show differences for all statements chosen (see config part)
* Allow user to control each statement. (not only the cash statement, by
default) and change his starting and ending balance
.. figure:: ../static/description/change_starting_balance.gif
* Quickly solve differences (we can set an absolute limit),
thanks to pos_move_reason
See this gif where where we set ending balance with difference, solve it and
close session :
.. figure:: ../static/description/end_session_balance_automatic_solve.gif
As the verification is more complete, allow the user to reopen a new session,
if the first one is in a closed state.
.. figure:: ../static/description/open_new_session.png
Extra checks are done, to prevent user errors:
* It is not possible to click on the button 'Close Session' if there are some
draft orders.

BIN
pos_multiple_control/static/description/account_journal_config.png

After

Width: 425  |  Height: 369  |  Size: 17 KiB

BIN
pos_multiple_control/static/description/change_starting_balance.gif

After

Width: 1129  |  Height: 610  |  Size: 317 KiB

BIN
pos_multiple_control/static/description/end_session_balance_automatic_solve.gif

After

Width: 1174  |  Height: 624  |  Size: 1.1 MiB

BIN
pos_multiple_control/static/description/icon.png

After

Width: 164  |  Height: 164  |  Size: 12 KiB

469
pos_multiple_control/static/description/index.html

@ -0,0 +1,469 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
<title>Point Of Sale - Multiple Cash Control</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: grey; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="point-of-sale-multiple-cash-control">
<h1 class="title">Point Of Sale - Multiple Cash Control</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/quentinDupont/grap-odoo-incubator/tree/12.0_pos_multiple_control/pos_multiple_control"><img alt="quentinDupont/grap-odoo-incubator" src="https://img.shields.io/badge/github-quentinDupont%2Fgrap--odoo--incubator-lightgray.png?logo=github" /></a></p>
<p>This module extends the functionality of the point of sale by allowing a
better control at the closing of the session.</p>
<ul class="simple">
<li>Show differences for all statements chosen (see config part)</li>
<li>Allow user to control each statement. (not only the cash statement, by
default) and change his starting and ending balance</li>
</ul>
<div class="figure">
<img alt="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/change_starting_balance.gif" src="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/change_starting_balance.gif" />
</div>
<ul class="simple">
<li>Quickly solve differences (we can set an absolute limit),
thanks to pos_move_reason</li>
</ul>
<p>See this gif where where we set ending balance with difference, solve it and
close session :</p>
<div class="figure">
<img alt="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/end_session_balance_automatic_solve.gif" src="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/end_session_balance_automatic_solve.gif" />
</div>
<p>As the verification is more complete, allow the user to reopen a new session,
if the first one is in a closed state.</p>
<div class="figure">
<img alt="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/open_new_session.png" src="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/open_new_session.png" />
</div>
<p>Extra checks are done, to prevent user errors:</p>
<ul class="simple">
<li>It is not possible to click on the button ‘Close Session’ if there are some
draft orders.</li>
</ul>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="id1">Configuration</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id5">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#id1">Configuration</a></h1>
<p>To configure this module, you need to:</p>
<p><strong>Configure the journals</strong> you want to control in your point of sale (POS)</p>
<ul class="simple">
<li>Go to Invoicing / Configuration / Journals / Journals</li>
<li>Check the box ‘POS Journal Control’ if you want to enable this feature for this journal</li>
<li>Unchecked journal will be in grey</li>
</ul>
<div class="figure">
<img alt="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/account_journal_config.png" src="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/account_journal_config.png" />
</div>
<p><strong>Configure your POS configuration</strong></p>
<ul class="simple">
<li>Go to Point of Sale / Configuration / Point of Sale / &lt;your pos&gt;</li>
<li>Choose your Payments methods (1)</li>
<li>Check Cash Control (2)</li>
<li>(optional) Choose a “pos move reason” to ausolve difference (3) (see pos_move_reason module for more details)</li>
<li>(optional) Choose a limit to allow or not the user to autosolve control difference in pos - Set 0 if you don’t want any limit. (4)</li>
</ul>
<div class="figure">
<img alt="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/pos_session_config.png" src="https://raw.githubusercontent.com/quentinDupont/grap-odoo-incubator/12.0_pos_multiple_control/pos_multiple_control/static/description/pos_session_config.png" />
</div>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/quentinDupont/grap-odoo-incubator/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/quentinDupont/grap-odoo-incubator/issues/new?body=module:%20pos_multiple_control%0Aversion:%2012.0_pos_multiple_control%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#id3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id4">Authors</a></h2>
<ul class="simple">
<li>GRAP</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id5">Contributors</a></h2>
<ul class="simple">
<li>Sylvain LE GAL &lt;<a class="reference external" href="https://twitter.com/legalsylvain">https://twitter.com/legalsylvain</a>&gt;</li>
<li>Julien WESTE</li>
<li>Quentin DUPONT &lt;<a class="reference external" href="https://twitter.com/pondupont">https://twitter.com/pondupont</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id6">Maintainers</a></h2>
<p>This module is part of the <a class="reference external" href="https://github.com/quentinDupont/grap-odoo-incubator/tree/12.0_pos_multiple_control/pos_multiple_control">quentinDupont/grap-odoo-incubator</a> project on GitHub.</p>
<p>You are welcome to contribute.</p>
</div>
</div>
</div>
</body>
</html>

BIN
pos_multiple_control/static/description/open_new_session.png

After

Width: 584  |  Height: 222  |  Size: 13 KiB

BIN
pos_multiple_control/static/description/pos_session.png

After

Width: 1132  |  Height: 515  |  Size: 78 KiB

BIN
pos_multiple_control/static/description/pos_session_config.png

After

Width: 1082  |  Height: 375  |  Size: 39 KiB

1
pos_multiple_control/tests/__init__.py

@ -0,0 +1 @@
from . import test_pos_multiple_control

170
pos_multiple_control/tests/test_pos_multiple_control.py

@ -0,0 +1,170 @@
# Copyright (C) 2018 - Today: GRAP (http://www.grap.coop)
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
from odoo.exceptions import Warning as UserError
class TestMultipleControl(TransactionCase):
"""Tests for 'Point of Sale - Multiple Control' Module"""
def setUp(self):
super(TestMultipleControl, self).setUp()
self.session_obj = self.env["pos.session"]
self.order_obj = self.env["pos.order"]
self.payment_obj = self.env["pos.make.payment"]
self.partner = self.env.ref("base.partner_demo_portal")
self.product = self.env.ref("product.product_product_3")
self.pos_move_reason = self.env.ref(
"pos_multiple_control.cash_register_error"
)
self.pos_config = self.env.ref(
"pos_multiple_control.pos_config_control"
)
self.check_journal = self.env.ref("pos_multiple_control.check_journal")
self.cash_journal = self.env.ref("pos_multiple_control.cash_journal")
def _order(self, session, price, journal):
# I create a new PoS order with 2 lines
order = self.order_obj.create({
'session_id': session.id,
'partner_id': self.partner.id,
'pricelist_id': self.partner.property_product_pricelist.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product.id,
'price_unit': price,
'qty': 1.0,
# 'tax_ids': [(6, 0, self.product.taxes_id.ids)],
'price_subtotal': price,
'price_subtotal_incl': price,
})],
'amount_total': price,
'amount_tax': 0.0,
'amount_paid': 0.0,
'amount_return': 0.0,
})
return order
def _order_and_pay(self, session, price, journal):
order = self._order(session, price, journal)
context_make_payment = {
"active_ids": [order.id],
"active_id": order.id
}
self.pos_make_payment = self.payment_obj.with_context(
context_make_payment).create({'amount': price})
# I click on the validate button to register the payment.
context_payment = {'active_id': order.id}
self.pos_make_payment.with_context(context_payment).check()
return order
# Test Section
def test_01_two_opening_session(self):
# I create new session
self.session_obj.create({"config_id": self.pos_config.id})
# I Try to create a new session
with self.assertRaises(ValidationError):
self.session_obj.create({"config_id": self.pos_config.id})
def test_02_opening_and_opened_session(self):
# I create new session and open it
session = self.session_obj.create({"config_id": self.pos_config.id})
self.pos_config.open_session_cb()
self.pos_config._open_session(session.id)
# I Try to create a new session
with self.assertRaises(ValidationError):
self.session_obj.create({"config_id": self.pos_config.id})
def test_03_check_close_session_with_draft_order(self):
# I create new session and open it
session = self.session_obj.create({"config_id": self.pos_config.id})
self.pos_config.open_session_cb()
self.pos_config._open_session(session.id)
# Create a Draft order, and try to close the session
self._order(session, 1, self.check_journal)
with self.assertRaises(UserError):
session.wkf_action_closing_control()
def test_04_check_bank_statement_control(self):
# I create new session and open it
session = self.session_obj.create({"config_id": self.pos_config.id})
# Make 2 Sales of 1100 and check transactions and theoritical balance
self.pos_config.open_session_cb()
self.pos_config._open_session(session.id)
self._order_and_pay(session, 100, self.check_journal)
self._order_and_pay(session, 1000, self.check_journal)
self.assertEqual(
session.control_register_total_entry_encoding,
1100,
"Incorrect transactions total",
)
self.assertEqual(
session.control_register_balance_end,
session.control_register_balance_start + 1100,
"Incorrect theoritical ending balance",
)
with self.assertRaises(UserError):
session.action_pos_session_validate()
def test_05_check_autosolve(self):
# I create new session and open it
self.pos_config.write(
{
"autosolve_pos_move_reason": self.pos_move_reason.id,
"autosolve_limit": 20,
}
)
session = self.session_obj.create({"config_id": self.pos_config.id})
# Make sales and autosolve
self.pos_config.open_session_cb()
self.pos_config._open_session(session.id)
sale = self._order_and_pay(session, 18, self.check_journal)
sale.statement_ids[0].statement_id.automatic_solve()
self.assertEqual(
session.summary_statement_ids[1].control_difference,
0,
"Incorrect transactions total",
)
def test_06_check_display_button(self):
# I create new session and open it
self.pos_config.write(
{
"autosolve_pos_move_reason": self.pos_move_reason.id,
"autosolve_limit": 30,
}
)
session = self.session_obj.create({"config_id": self.pos_config.id})
# Make sales too important
self.pos_config.open_session_cb()
self.pos_config._open_session(session.id)
sale = self._order_and_pay(session, 31, self.check_journal)
self.assertEqual(
sale.statement_ids[0].statement_id.display_autosolve,
False,
"Autosolve button should be hidden",
)
# Autosolve and second sales
sale.statement_ids[0].statement_id.automatic_solve()
sale2 = self._order_and_pay(session, 29, self.check_journal)
sale2.statement_ids[0].statement_id._compute_display_autosolve()
self.assertEqual(
sale2.statement_ids[0].statement_id.display_autosolve,
True,
"Autosolve button should not be hidden",
)

31
pos_multiple_control/views/view_account_journal.xml

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="view_account_journal_form" model="ir.ui.view">
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_form"/>
<field name="arch" type="xml">
<field name="journal_user" position="after">
<field name="pos_control" attrs="{'invisible': [('type', 'not in', ['bank', 'cash'])]}"/>
</field>
</field>
</record>
<record id="view_account_journal_tree" model="ir.ui.view">
<field name="model">account.journal</field>
<field name="inherit_id" ref="account.view_account_journal_tree"/>
<field name="arch" type="xml">
<field name="company_id" position="before">
<field name="journal_user"/>
<field name="pos_control"/>
</field>
</field>
</record>
</odoo>

45
pos_multiple_control/views/view_pos_config.xml

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2019 - Today: GRAP (http://www.grap.coop)
@author Quentin DUPONT (quentin.dupont@grap.coop)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
-->
<odoo>
<record id="view_pos_config_form" model="ir.ui.view">
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.pos_config_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='iface_precompute_cash']/../.." position="after">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="autosolve_pos_move_reason"/>
<field name="autosolve_pos_move_reason"/>
</div>
</div>
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<label for="autosolve_limit"/>
<div class="text-muted">
Limit to display autosolve button
</div>
<field name="autosolve_limit"/>
</div>
</div>
</xpath>
</field>
</record>
<record id="view_pos_config_kanban" model="ir.ui.view">
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.view_pos_config_kanban"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='open_existing_session_cb']" position="after">
<button t-if="_.contains(['closing_control'], record.current_session_state.raw_value)" class="btn btn-primary" name="open_new_session" type="object">New Session</button>
</xpath>
</field>
</record>
</odoo>

135
pos_multiple_control/views/view_pos_session.xml

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2017 - Today: GRAP (http://www.grap.coop)
@author Sylvain LE GAL (https://twitter.com/legalsylvain)
@author Quentin DUPONT (https://twitter.com/pondupont)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
<odoo>
<record id="view_pos_session_form" model="ir.ui.view">
<field name="model">pos.session</field>
<field name="inherit_id" ref="point_of_sale.view_pos_session_form"/>
<field name="arch" type="xml">
<!-- Improve views -->
<field name="config_id" position="attributes">
<attribute name="readonly">1</attribute>
</field>
<!-- Hide obsolete Buttons -->
<!-- Same name button for opening and closing.. -->
<xpath expr="//button[@name='open_cashbox']" position="replace">
<button name="open_cashbox_opening"
class="oe_stat_button"
attrs="{'invisible':['|', ('cash_control', '=', False), ('state', '!=', 'opening_control')]}"
icon="fa-money"
type="object" context="{'balance': 'start'}">
<span class="o_stat_text">Set Opening Balance</span>
</button>
</xpath>
<!-- Hide closing button -->
<xpath expr="//button[@name='open_cashbox']" position="replace">
</xpath>
<!-- Add new session button on closing-->
<xpath expr="//header" position="inside">
<button name="action_pos_session_new_session" type="object"
string="New Session" class="oe_highlight"
attrs="{'invisible' : [('state', '!=', 'closed')]}"/>
</xpath>
<!-- Theoritical Summary -->
<xpath expr="//field[@name='cash_register_balance_start']/.." position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='cash_register_balance_start']/.." position="after">
<group class="oe_subtotal_footer oe_right">
<field name="control_register_balance_start" readonly="1" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<label for="control_register_total_entry_encoding" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="+ Transactions" class="oe_opening_total"/>
<field name="control_register_total_entry_encoding" nolabel="1" attrs="{'invisible' : [('state', '=', 'opening_control')]}" class="oe_pos_total" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<label for="control_register_balance_end" attrs="{'invisible' : [('state', '=', 'opening_control')]}" string="Theoretical Closing Balances" class="oe_opening_total"/>
<field name="control_register_balance_end" nolabel="1" attrs="{'invisible' : [('state', '=', 'opening_control')]}" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
</xpath>
<!-- Real Summary and Differences -->
<xpath expr="//field[@name='cash_register_balance_end_real']/../.." position="replace">
<div>
<group/>
<group class="oe_subtotal_footer oe_right" attrs="{'invisible': ['|', ('cash_control', '=', False), ('state', '=', 'opening_control')]}">
<field name="control_register_balance" string="Real Closing Balance" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<field name="control_register_difference" class="oe_subtotal_footer_separator oe_right oe_pos_total oe_pos_difference" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
</div>
</xpath>
<!-- Summary by Payment Methods -->
<field name="statement_ids" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<field name="statement_ids" position="after">
<field name="summary_statement_ids" nolabel="1" colspan="4"
attrs="{'invisible': [('state', 'in', ['opening_control'])], 'readonly': 1}">
<tree decoration-muted="is_pos_control == False and state != 'closed'" decoration-danger="is_pos_control == True and control_difference != 0">
<field name="is_pos_control" invisible="1"/>
<field name="name" />
<field name="journal_id" />
<field name="balance_start"/>
<button name="open_cashbox_starting_balance"
help="Set your starting balance"
type="object"
class="oe_stat_button" icon="fa-money"
attrs="{'invisible' : ['|', ('is_pos_control','!=', True), ('state', 'in', ['opening_control','closed'])]}">
</button>
<field name="total_entry_encoding" />
<field name="balance_end_real"/>
<button name="open_cashbox_ending_balance"
help="Set your ending balance"
type="object"
class="oe_stat_button" icon="fa-money"
attrs="{'invisible' : ['|', ('is_pos_control','!=', True), ('state', 'in', ['opening_control', 'closed'])]}">
</button>
<field name="control_difference"/>
<field name="currency_id" groups="base.group_multi_currency" />
<field name="state" invisible="1"/>
</tree>
<form>
<group>
<field name="name" />
<field name="journal_id" />
<field name="state" invisible="1" />
</group>
<field name="line_ids" colspan="2">
<tree string="Transactions">
<field name="date"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id" />
<field name="amount" sum="Total"/>
</tree>
</field>
</form>
</field>
</field>
<!-- Automatic solve -->
<field name="control_difference" position="after">
<field name="pos_session_state" invisible="1"/>
<field name="display_autosolve" invisible="1"/>
</field>
<field name="control_difference" position="after">
<button name="automatic_solve"
help="Solve difference automatically with a product choosen in pos config"
type="object"
class="oe_stat_button" icon="fa-cogs"
confirm="Do you really want to solve this difference automatically ?"
attrs="{'invisible':[
('display_autosolve', '!=', True)]}">
</button>
</field>
</field>
</record>
</odoo>

1
pos_multiple_control/wizard/__init__.py

@ -0,0 +1 @@
from . import wizard_pos_update_statement_balance

153
pos_multiple_control/wizard/wizard_pos_update_statement_balance.py

@ -0,0 +1,153 @@
# 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 WizardPOSBankStatementUpdateBalance(models.TransientModel):
_name = "wizard.pos.update.bank.statement.balance"
_description = 'POS Update Bank Statement Balance'
def _default_session_id(self):
return self.env.context.get('active_pos_id', False)
_BALANCE_MOMENT_SELECTION = [
('bydefault', 'Default'),
('starting', 'Starting balance'),
('ending', 'Ending balance'),
]
item_ids = fields.One2many(
comodel_name="wizard.pos.update.bank.statement.balance.line",
inverse_name="wiz_id",
string="Items",
)
balance_moment = fields.Selection(
selection=_BALANCE_MOMENT_SELECTION, string='Balance moment',
default='bydefault')
journal_id = fields.Many2one(
comodel_name='account.journal', string="Journal",
domain="[('id', 'in', journal_ids)]", required=True)
session_id = fields.Many2one(
comodel_name='pos.session', string="Current Session",
default=_default_session_id, required=True, readonly=True)
@api.model
def _prepare_item(self, statement):
return {
"statement_id": statement.id,
"name": statement.name,
"journal_id": statement.journal_id.id,
"balance_start": statement.balance_start,
"balance_end": statement.balance_end,
"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)
# Load objects
session_obj = self.env["pos.session"]
bank_statement_obj = self.env["account.bank.statement"]
# Load context
active_ids = self.env.context["active_id"] or []
active_pos_id = self.env.context["active_pos_id"] or []
active_model = self.env.context["active_model"] or []
balance_moment = self.env.context["balance_moment"] or []
# Check propagation
if not active_pos_id:
return res
assert active_model == "pos.session", \
"Bad context propagation"
if len(active_pos_id) > 1:
raise UserError(_('You cannot start the closing '
'balance for multiple POS sessions'))
# Add bank statement lines
session = session_obj.browse(active_pos_id[0])
bank_statement = bank_statement_obj.browse(active_ids[0])
items = []
items.append([0, 0, self._prepare_item(bank_statement)])
# Give values for wizard
res["session_id"] = session.id
res["item_ids"] = items
res["balance_moment"] = balance_moment
res["journal_id"] = bank_statement.journal_id.id
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()
# record new values from wizard
for item in self.item_ids:
if item.balance_moment == 'starting':
item.statement_id.balance_start = item.balance_start_real
elif item.balance_moment == 'ending':
item.statement_id.balance_end_real = item.balance_end_real
# item.statement_id.balance_end = item.balance_end_real
return True
class WizardPOSBankStatementUpdateBalanceLine(models.TransientModel):
_name = "wizard.pos.update.bank.statement.balance.line"
_description = 'POS Update Bank Statement Balance Line'
wiz_id = fields.Many2one(
comodel_name='wizard.pos.update.bank.statement.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(
string="Starting Balance",
default=0.0,
compute='_compute_balance_start'
)
balance_start_real = fields.Monetary(
default=0.0
)
total_entry_encoding = fields.Monetary(
related='statement_id.total_entry_encoding',
)
balance_end = fields.Monetary(
string="Computed Balance",
default=0.0,
compute='_compute_balance_end'
)
balance_end_real = fields.Monetary(
default=0.0
)
currency_id = fields.Many2one(
comodel_name='res.currency',
related='statement_id.currency_id'
)
balance_moment = fields.Selection(
related='wiz_id.balance_moment',
default='bydefault')
def _compute_balance_start(self):
for rec in self:
rec.balance_start = rec.statement_id.balance_start
def _compute_balance_end(self):
for rec in self:
rec.balance_end = rec.statement_id.balance_end

38
pos_multiple_control/wizard/wizard_pos_update_statement_balance.xml

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_wizard_pos_update_bank_statement_balance_form" model="ir.ui.view">
<field name="model">wizard.pos.update.bank.statement.balance</field>
<field name="arch" type="xml">
<form string="Update Balance">
Here you can change the <b><field name="balance_moment" readonly="1"/></b>
<field name="item_ids" nolabel="1">
<tree string="Statements" create="0" editable="top" limit="1">
<field name="journal_id"/>
<field name="balance_moment" invisible="1"/>
<field name="balance_start"/>
<field name="balance_start_real" invisible="context.get('balance_moment') != 'starting'"/>
<field name="total_entry_encoding" invisible="context.get('balance_moment') != 'ending'"/>
<field name="balance_end" invisible="context.get('balance_moment') != 'ending'"/>
<field name="balance_end_real" invisible="context.get('balance_moment') != 'ending'"/>
<field name="currency_id" groups="base.group_multi_currency"/>
</tree>
</field>
<footer>
<button name="action_confirm" string="Apply" type="object" class="btn-primary"/>
<button class="btn-secondary" special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_wizard_pos_update_bank_statement_balance" model="ir.actions.act_window">
<field name="name">Update Balances</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">wizard.pos.update.bank.statement.balance</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>
Loading…
Cancel
Save