diff --git a/pos_cash_move_reason/README.rst b/pos_cash_move_reason/README.rst index a77f4dea43..37c752803e 100644 --- a/pos_cash_move_reason/README.rst +++ b/pos_cash_move_reason/README.rst @@ -33,7 +33,7 @@ POS cash in-out reason |badge1| |badge2| |badge3| |badge4| |badge5| This module allow to define some reasons for the functionality of -"Put Money In" and "Take Money Out" available in point of sale session. +"Cash In / Out" available in point of sale session. So, with this module it is possible to impact directly an expense or income account which is defined on the related reasons and create according @@ -69,14 +69,12 @@ Configuration .. figure:: https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/pos_cash_move_form.png :alt: PoS Move Reason -**Note** - -You should have checked first 'Used in Point of Sale' for the Journals you want -to enable the feature. Usage ===== +**In backoffice** + * Go to your current session * Click on the button "Put Money In" or "Take Money Out" @@ -88,12 +86,35 @@ Usage .. figure:: https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/wizard_pos_move_reason_form.png -* When closing the session, an account move will be created, with two lines, +* An account move will be created, with two lines, one with the default journal account, and one with the expense / income reason account. .. figure:: https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/account_move_form.png +**In POS UI** + +* Click on Cash In / Out + +* Choose Cash In or Cash Out then write the amount + +* Choose a Reason + +* If it has a Journal, choose Journal + +* Confirm and continue sale OR confirm and go to Close PopUp + +.. figure:: https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/pos_cash_move_ui.png + +**In POS UI, from Close PopUp** + +* New button "Cash In / Out" can trigger same workflow + +* You now can see Positive and Negative Move for every Payment method + + +.. figure:: https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/pos_close_popup.png + Known issues / Roadmap ====================== @@ -105,6 +126,10 @@ hide native obsolete actions available on the model ``pos.session`` Changelog ========= +16.0.2.0.0 (2026-05-02) +~~~~~~~~~~~~~~~~~~~~~~~ +* [IMP] This module works directly on POS UI + 16.0.1.0.0 (2025-07-27) ~~~~~~~~~~~~~~~~~~~~~~~ * [MIG] Port module to version 16.0. diff --git a/pos_cash_move_reason/__manifest__.py b/pos_cash_move_reason/__manifest__.py index 6f67f7decd..16acc9844f 100644 --- a/pos_cash_move_reason/__manifest__.py +++ b/pos_cash_move_reason/__manifest__.py @@ -6,12 +6,11 @@ "author": "ACSONE SA/NV," "GRAP," "Odoo Community Association (OCA)", "website": "https://github.com/OCA/pos", "category": "Point Of sale", - "version": "16.0.1.0.0", + "version": "16.0.2.0.0", "license": "AGPL-3", "depends": ["point_of_sale"], "data": [ "security/ir_rule.xml", - "security/res_groups.xml", "security/ir.model.access.csv", "views/view_pos_move_reason.xml", "views/view_pos_session.xml", @@ -22,4 +21,11 @@ "demo/account_journal.xml", "demo/pos_move_reason.xml", ], + "assets": { + "point_of_sale.assets": [ + "pos_cash_move_reason/static/src/css/*.scss", + "pos_cash_move_reason/static/src/xml/**.xml", + "pos_cash_move_reason/static/src/js/**.js", + ], + }, } diff --git a/pos_cash_move_reason/i18n/fr.po b/pos_cash_move_reason/i18n/fr.po index 8d22e4c5de..4305e2bf96 100644 --- a/pos_cash_move_reason/i18n/fr.po +++ b/pos_cash_move_reason/i18n/fr.po @@ -4,18 +4,16 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-08-23 15:07+0000\n" -"PO-Revision-Date: 2019-08-23 17:14+0200\n" -"Last-Translator: <>\n" +"POT-Creation-Date: 2026-04-16 08:10+0000\n" +"PO-Revision-Date: 2026-04-16 08:10+0000\n" +"Last-Translator: \n" "Language-Team: \n" -"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Content-Transfer-Encoding: \n" "Plural-Forms: \n" -"X-Generator: Poedit 2.0.6\n" #. module: pos_cash_move_reason #: model_terms:ir.ui.view,arch_db:pos_cash_move_reason.view_pos_session_form @@ -35,6 +33,13 @@ msgstr "" "Retirer\n" " de l'argent" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml:0 +#, python-format +msgid "Account journal" +msgstr "Journal" + #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__journal_ids #: model:ir.model.fields,field_description:pos_cash_move_reason.field_wizard_pos_move_reason__journal_ids @@ -56,16 +61,32 @@ msgstr "Montant" msgid "Apply" msgstr "Appliquer" -#. module: pos_cash_move_reason -#: model:ir.model.fields,field_description:pos_cash_move_reason.field_wizard_pos_move_reason__statement_id -msgid "Bank Statement" -msgstr "Relevé bancaire" - #. module: pos_cash_move_reason #: model_terms:ir.ui.view,arch_db:pos_cash_move_reason.view_wizard_pos_move_reason_form msgid "Cancel" msgstr "Annuler" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonPatch.esm.js:0 +#, python-format +msgid "Cash in/out of %s is ignored." +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml:0 +#, python-format +msgid "Choose the journal" +msgstr "Choisir le journal" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml:0 +#, python-format +msgid "Choose the reason" +msgstr "Choisir le motif" + #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__company_id msgid "Company" @@ -124,7 +145,7 @@ msgstr "Remplir ce formulaire si vous mettez de l'argent dans la caisse" #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__id #: model:ir.model.fields,field_description:pos_cash_move_reason.field_wizard_pos_move_reason__id msgid "ID" -msgstr "ID" +msgstr "" #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__income_account_id @@ -132,6 +153,7 @@ msgid "Income Account" msgstr "Compte de revenus" #. module: pos_cash_move_reason +#. odoo-python #: code:addons/pos_cash_move_reason/wizard/wizard_pos_move_reason.py:0 #, python-format msgid "Invalid Amount" @@ -150,7 +172,7 @@ msgstr "Est un motif d'entrée" #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_wizard_pos_move_reason__journal_id msgid "Journal" -msgstr "Journal" +msgstr "" #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason____last_update @@ -218,16 +240,55 @@ msgstr "Mettre de l'argent" msgid "Reason" msgstr "Motif" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "Select one Journal before confirming." +msgstr "Choisir un journal pour pouvoir confirmer." + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "Select one Reason before confirming." +msgstr "Choisir un motif pour pouvoir confirmer." + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonPatch.esm.js:0 +#, python-format +msgid "Successfully made a cash %s of %s." +msgstr "Mouvement de %s pour %s réalisé avec succès " + #. module: pos_cash_move_reason #: model:ir.model.fields.selection,name:pos_cash_move_reason.selection__wizard_pos_move_reason__move_type__expense msgid "Take Money Out" msgstr "Retirer de l'argent" +#. module: pos_cash_move_reason +#. odoo-python +#: code:addons/pos_cash_move_reason/models/pos_session.py:0 +#, python-format +msgid "There is no cash payment method for this PoS Session" +msgstr "Il n'y a pas de méthode de paiement de cash pour cette session." + #. module: pos_cash_move_reason #: model:res.groups,name:pos_cash_move_reason.group_pos_old_actions msgid "Use Old PoS 'Put or Take Money' Actions" msgstr "" "Utiliser la fonctionnalité obsolète du PdV Mettre ou Prendre de l'argent" -#~ msgid "Move type" -#~ msgstr "Type de mouvement" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonPatch.esm.js:0 +#, python-format +msgid "in" +msgstr "entrée" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonPatch.esm.js:0 +#, python-format +msgid "out" +msgstr "sortie" \ No newline at end of file diff --git a/pos_cash_move_reason/i18n/pos_cash_move_reason.pot b/pos_cash_move_reason/i18n/pos_cash_move_reason.pot index e99cd6035e..c638677829 100644 --- a/pos_cash_move_reason/i18n/pos_cash_move_reason.pot +++ b/pos_cash_move_reason/i18n/pos_cash_move_reason.pot @@ -1,11 +1,13 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * pos_cash_move_reason +# * pos_cash_move_reason # msgid "" msgstr "" "Project-Id-Version: Odoo Server 16.0\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-05-25 10:28+0000\n" +"PO-Revision-Date: 2026-05-25 10:28+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -13,6 +15,13 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "+ Payments in" +msgstr "" + #. module: pos_cash_move_reason #: model_terms:ir.ui.view,arch_db:pos_cash_move_reason.view_pos_session_form msgid "" @@ -27,6 +36,13 @@ msgid "" " Money Out" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml:0 +#, python-format +msgid "Account journal" +msgstr "" + #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__journal_ids #: model:ir.model.fields,field_description:pos_cash_move_reason.field_wizard_pos_move_reason__journal_ids @@ -43,6 +59,13 @@ msgstr "" msgid "Amount" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "An unknown error prevents us from getting closing information." +msgstr "" + #. module: pos_cash_move_reason #: model_terms:ir.ui.view,arch_db:pos_cash_move_reason.view_wizard_pos_move_reason_form msgid "Apply" @@ -53,11 +76,46 @@ msgstr "" msgid "Cancel" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonMoveReason.esm.js:0 +#, python-format +msgid "Cash in/out of %s is ignored." +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml:0 +#, python-format +msgid "Choose the journal" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml:0 +#, python-format +msgid "Choose the reason" +msgstr "" + #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__company_id msgid "Company" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml:0 +#, python-format +msgid "Confirm and Go to Close Session" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "Counted" +msgstr "" + #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__create_uid #: model:ir.model.fields,field_description:pos_cash_move_reason.field_wizard_pos_move_reason__create_uid @@ -80,6 +138,13 @@ msgstr "" msgid "Describe why you take money from the cash register" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "Difference" +msgstr "" + #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_session__display_move_reason_expense msgid "Display Move Reason Expense" @@ -96,6 +161,13 @@ msgstr "" msgid "Display Name" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "Expected" +msgstr "" + #. module: pos_cash_move_reason #: model:ir.model.fields,field_description:pos_cash_move_reason.field_pos_move_reason__expense_account_id msgid "Expense Account" @@ -180,6 +252,55 @@ msgstr "" msgid "Name" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "Network Error" +msgstr "" + +#. module: pos_cash_move_reason +#: model_terms:ir.ui.view,arch_db:pos_cash_move_reason.view_pos_move_reason_form +msgid "" +"Only journals selected also on PoS session will be available on your PoS " +"session." +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "Open the money details popup" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "Opening" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "Payment Method" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml:0 +#, python-format +msgid "Payments in" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "Please check your internet connection and try again." +msgstr "" + #. module: pos_cash_move_reason #: model:ir.model,name:pos_cash_move_reason.model_pos_move_reason msgid "PoS - Move In / Out Reason" @@ -205,12 +326,65 @@ msgstr "" msgid "Reason" msgstr "" +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "Select one Journal before confirming." +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "Select one Reason before confirming." +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonMoveReason.esm.js:0 +#, python-format +msgid "Successfully made a cash %s of %s." +msgstr "" + #. module: pos_cash_move_reason #: model:ir.model.fields.selection,name:pos_cash_move_reason.selection__wizard_pos_move_reason__move_type__expense msgid "Take Money Out" msgstr "" #. module: pos_cash_move_reason -#: model:res.groups,name:pos_cash_move_reason.group_pos_old_actions -msgid "Use Old PoS 'Put or Take Money' Actions" +#. odoo-python +#: code:addons/pos_cash_move_reason/models/pos_session.py:0 +#, python-format +msgid "There is no cash payment method for this PoS Session" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js:0 +#, python-format +msgid "Unknown Error" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-python +#: code:addons/pos_cash_move_reason/models/pos_session.py:0 +#, python-format +msgid "" +"You don't have the access rights to get the point of sale closing control " +"data." +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonMoveReason.esm.js:0 +#, python-format +msgid "in" +msgstr "" + +#. module: pos_cash_move_reason +#. odoo-javascript +#: code:addons/pos_cash_move_reason/static/src/js/CashMoveButtonMoveReason.esm.js:0 +#, python-format +msgid "out" msgstr "" diff --git a/pos_cash_move_reason/models/pos_session.py b/pos_cash_move_reason/models/pos_session.py index 6cce92d2d0..c847c4e9f8 100644 --- a/pos_cash_move_reason/models/pos_session.py +++ b/pos_cash_move_reason/models/pos_session.py @@ -2,7 +2,8 @@ # @author: Sylvain LE GAL (https://twitter.com/legalsylvain) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo import fields, models +from odoo import _, fields, models +from odoo.exceptions import AccessError, UserError class PosSession(models.Model): @@ -45,3 +46,239 @@ def _button_move_reason(self, move_type): "default_move_type": move_type, } return action + + # Add _get_pos_ui_ and _loader_params_ for each model + # For pos_move_reason + def _get_pos_ui_pos_move_reason(self, params): + return self.env["pos.move.reason"].search_read(**params["search_params"]) + + def _loader_params_pos_move_reason(self): + return { + "search_params": { + "fields": [ + "name", + "journal_ids", + "is_income_reason", + "is_expense_reason", + ], + }, + } + + # For account journal + def _get_pos_ui_account_journal(self, params): + return self.env["account.journal"].search_read(**params["search_params"]) + + def _loader_params_account_journal(self): + return { + "search_params": { + "fields": ["name"], + }, + } + + # Overrid for payment method to add journal_id, + # to filter payment methods with only journal selected on pos session + def _loader_params_pos_payment_method(self): + result = super()._loader_params_pos_payment_method() + result["search_params"]["fields"].append("journal_id") + return result + + def _pos_ui_models_to_load(self): + result = super()._pos_ui_models_to_load() + result.append("pos.move.reason") + result.append("account.journal") + return result + + # Rewrite it to handle several journals for pos_cash_move_reason + # Inspired by core code from Odoo SA + # add "move_reason" and "journal_id" in dict extras + def try_cash_in_out(self, _type, amount, reason, extras): + sign = 1 if _type == "in" else -1 + sessions = self.filtered("cash_journal_id") + if not sessions: + raise UserError(_("There is no cash payment method for this PoS Session")) + + # Handle move_reason + move_reason = self.env["pos.move.reason"].browse(extras["move_reason"]) + journal = self.env["account.journal"].browse(extras["journal_id"]) + + # Handle account + if _type == "in": + account_id = move_reason.income_account_id.id + else: + account_id = move_reason.expense_account_id.id + + self.env["account.bank.statement.line"].sudo().create( + [ + { + "pos_session_id": session.id, + "journal_id": journal.id or session.cash_journal_id.id, + "amount": sign * amount, + "date": fields.Date.context_today(self), + "payment_ref": "-".join( + [move_reason.name, extras["translatedType"], reason] + ), + "counterpart_account_id": account_id, + } + for session in sessions + ] + ) + + message_content = [ + f"Cash {extras['translatedType']}", + f'- Amount: {extras["formattedAmount"]}', + ] + if reason: + message_content.append(f"- Reason: {reason}") + self.message_post(body="
\n".join(message_content)) + + # Rewrite it to handle several journals for pos_cash_move_reason + # Inspired by core code from Odoo SA + # This code change other_payment_methods and adjust default_cash_details + def get_closing_control_data(self): + + if not self.env.user.has_group("point_of_sale.group_pos_user"): + raise AccessError( + _( + "You don't have the access rights to get the " + "point of sale closing control data." + ) + ) + self.ensure_one() + + # ------------------------- + # FROM CORE CODE + # ------------------------- + orders = self.order_ids.filtered( + lambda o: o.state == "paid" or o.state == "invoiced" + ) + payments = orders.payment_ids.filtered( + lambda p: p.payment_method_id.type != "pay_later" + ) + pay_later_payments = orders.payment_ids - payments + cash_payment_method_ids = self.payment_method_ids.filtered( + lambda pm: pm.type == "cash" + ) + default_cash_payment_method_id = ( + cash_payment_method_ids[0] if cash_payment_method_ids else None + ) + total_default_cash_payment_amount = ( + sum( + payments.filtered( + lambda p: p.payment_method_id == default_cash_payment_method_id + ).mapped("amount") + ) + if default_cash_payment_method_id + else 0 + ) + cash_in_out_list = [] + last_session = self.search( + [("config_id", "=", self.config_id.id), ("id", "!=", self.id)], limit=1 + ) + + # New + other_payment_methods_data = [] + all_account_bank_statement_lines = self.sudo().statement_line_ids + + # ------------------------------------- + # LOOP PAYMENTS OF EACH PAYMENT METHODS + # ------------------------------------- + for pm in self.payment_method_ids: + + pm_payments = payments.filtered(lambda p: p.payment_method_id == pm) + payment_amount = sum(pm_payments.mapped("amount")) + payment_count = len(pm_payments) + + # -------------------------- + # TOTAL : PAYMENT + IN / OUT + # -------------------------- + total_amount = payment_amount + + # Init + move_list = [] + move_in_count = 0 + move_out_count = 0 + + statement_lines = all_account_bank_statement_lines.filtered( + lambda l: l.journal_id == pm.journal_id + ) + + for move in statement_lines.sorted("create_date"): + if move.amount > 0: + move_in_count += 1 + name = f"Cash in {move_in_count}" + else: + move_out_count += 1 + name = f"Cash out {move_out_count}" + + # TOTAL : PAYMENT + IN / OUT + total_amount += move.amount + move_list.append( + { + "name": move.payment_ref if move.payment_ref else name, + "amount": move.amount, + } + ) + + # ------------------------------------------- + # CASH SPECIFIC DATA to fit Odoo res needed + # ------------------------------------------- + if pm.id == default_cash_payment_method_id.id: + cash_in_out_list = move_list + continue + else: + + # ------------------------- + # BUILD RESULT + # ------------------------- + other_payment_methods_data.append( + { + "name": pm.name, + "amount": total_amount, + "number": payment_count, + "id": pm.id, + "type": pm.type, + "payments": { + "amount": payment_amount, + "count": payment_count, + }, + "moves": move_list, + } + ) + + # ------------------------- + # FINAL RETURN + # ------------------------- + return { + "orders_details": { + "quantity": len(orders), + "amount": sum(orders.mapped("amount_total")), + }, + "payments_amount": sum(payments.mapped("amount")), + "pay_later_amount": sum(pay_later_payments.mapped("amount")), + "opening_notes": self.opening_notes, + # default_cash_details → add filtered for amount + "default_cash_details": { + "name": default_cash_payment_method_id.name, + "amount": last_session.cash_register_balance_end_real + + total_default_cash_payment_amount + + sum( + self.sudo() + .statement_line_ids.filtered( + lambda l: l.journal_id + == default_cash_payment_method_id.journal_id + ) + .mapped("amount") + ), + "opening": last_session.cash_register_balance_end_real, + "payment_amount": total_default_cash_payment_amount, + "moves": cash_in_out_list, + "id": default_cash_payment_method_id.id, + } + if default_cash_payment_method_id + else None, + "other_payment_methods": other_payment_methods_data, + "is_manager": self.user_has_groups("point_of_sale.group_pos_manager"), + "amount_authorized_diff": self.config_id.amount_authorized_diff + if self.config_id.set_maximum_difference + else None, + } diff --git a/pos_cash_move_reason/readme/CONFIGURE.rst b/pos_cash_move_reason/readme/CONFIGURE.rst index 8f1e74215c..c828879b4b 100644 --- a/pos_cash_move_reason/readme/CONFIGURE.rst +++ b/pos_cash_move_reason/readme/CONFIGURE.rst @@ -11,7 +11,3 @@ .. figure:: ../static/description/pos_cash_move_form.png :alt: PoS Move Reason -**Note** - -You should have checked first 'Used in Point of Sale' for the Journals you want -to enable the feature. diff --git a/pos_cash_move_reason/readme/DESCRIPTION.rst b/pos_cash_move_reason/readme/DESCRIPTION.rst index e59734e83a..28a1a9bc6a 100644 --- a/pos_cash_move_reason/readme/DESCRIPTION.rst +++ b/pos_cash_move_reason/readme/DESCRIPTION.rst @@ -1,5 +1,5 @@ This module allow to define some reasons for the functionality of -"Put Money In" and "Take Money Out" available in point of sale session. +"Cash In / Out" available in point of sale session. So, with this module it is possible to impact directly an expense or income account which is defined on the related reasons and create according diff --git a/pos_cash_move_reason/readme/HISTORY.rst b/pos_cash_move_reason/readme/HISTORY.rst index a1bbfa9b2f..06a5749ceb 100644 --- a/pos_cash_move_reason/readme/HISTORY.rst +++ b/pos_cash_move_reason/readme/HISTORY.rst @@ -1,3 +1,7 @@ +16.0.2.0.0 (2026-05-02) +~~~~~~~~~~~~~~~~~~~~~~~ +* [IMP] This module works directly on POS UI + 16.0.1.0.0 (2025-07-27) ~~~~~~~~~~~~~~~~~~~~~~~ * [MIG] Port module to version 16.0. diff --git a/pos_cash_move_reason/readme/TECHNICAL.rst b/pos_cash_move_reason/readme/TECHNICAL.rst new file mode 100644 index 0000000000..4abf949586 --- /dev/null +++ b/pos_cash_move_reason/readme/TECHNICAL.rst @@ -0,0 +1,5 @@ +Native Odoo only handle Cash Moves, so this module rewrite core functions in core point_of_sale : +- get_closing_control_data() in pos_session.py, add some datas in return key 'other_payment_methods' +- try_cash_in_out() in pos_session.py + +We make the choice to rewrite get_closing_control_data() insted of override because of multiple changes needed. \ No newline at end of file diff --git a/pos_cash_move_reason/readme/USAGE.rst b/pos_cash_move_reason/readme/USAGE.rst index 1b20db38f5..89dbe25180 100644 --- a/pos_cash_move_reason/readme/USAGE.rst +++ b/pos_cash_move_reason/readme/USAGE.rst @@ -1,3 +1,5 @@ +**In backoffice** + * Go to your current session * Click on the button "Put Money In" or "Take Money Out" @@ -9,8 +11,31 @@ .. figure:: ../static/description/wizard_pos_move_reason_form.png -* When closing the session, an account move will be created, with two lines, +* An account move will be created, with two lines, one with the default journal account, and one with the expense / income reason account. .. figure:: ../static/description/account_move_form.png + +**In POS UI** + +* Click on Cash In / Out + +* Choose Cash In or Cash Out then write the amount + +* Choose a Reason + +* If it has a Journal, choose Journal + +* Confirm and continue sale OR confirm and go to Close PopUp + +.. figure:: ../static/description/pos_cash_move_ui.png + +**In POS UI, from Close PopUp** + +* New button "Cash In / Out" can trigger same workflow + +* You now can see Positive and Negative Move for every Payment method + + +.. figure:: ../static/description/pos_close_popup.png diff --git a/pos_cash_move_reason/security/res_groups.xml b/pos_cash_move_reason/security/res_groups.xml deleted file mode 100644 index a3c394470d..0000000000 --- a/pos_cash_move_reason/security/res_groups.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - Use Old PoS 'Put or Take Money' Actions - - diff --git a/pos_cash_move_reason/static/description/index.html b/pos_cash_move_reason/static/description/index.html index 150783e2e1..81b3e0f4eb 100644 --- a/pos_cash_move_reason/static/description/index.html +++ b/pos_cash_move_reason/static/description/index.html @@ -376,7 +376,7 @@

POS cash in-out reason

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

This module allow to define some reasons for the functionality of -“Put Money In” and “Take Money Out” available in point of sale session.

+“Cash In / Out” available in point of sale session.

So, with this module it is possible to impact directly an expense or income account which is defined on the related reasons and create according accounting entries.

@@ -395,17 +395,18 @@

POS cash in-out reason

  • Usage
  • Known issues / Roadmap
  • Changelog
  • -
  • Bug Tracker
  • -
  • Credits @@ -427,12 +428,10 @@

    Configuration

    PoS Move Reason
    -

    Note

    -

    You should have checked first ‘Used in Point of Sale’ for the Journals you want -to enable the feature.

    Usage

    +

    In backoffice

    https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/account_move_form.png
    +

    In POS UI

    + +
    +https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/pos_cash_move_ui.png +
    +

    In POS UI, from Close PopUp

    + +
    +https://raw.githubusercontent.com/OCA/pos/16.0/pos_cash_move_reason/static/description/pos_close_popup.png +

    Known issues / Roadmap

    @@ -466,13 +484,19 @@

    Known issues / Roadmap

    Changelog

    -

    16.0.1.0.0 (2025-07-27)

    +

    16.0.2.0.0 (2026-05-02)

      -
    • [MIG] Port module to version 16.0.
    • +
    • [IMP] This module works directly on POS UI
    -

    12.0.3.0.0 (2019-08-13)

    +

    16.0.1.0.0 (2025-07-27)

    +
      +
    • [MIG] Port module to version 16.0.
    • +
    +
    +
    +

    12.0.3.0.0 (2019-08-13)

    • [MIG] Port module to version 12.0.
    • [REF] Don’t use product.product model for Reasons, because Odoo remove @@ -483,21 +507,21 @@

      12.0.3.0.0 (2019-08-13)

      Instead, use new transient model wizard.pos.move.reason.
    -
    -

    8.0.2.0.0 (2018-06-25)

    +
    +

    8.0.2.0.0 (2018-06-25)

    • [REF] Minor code refactoring.
    -
    -

    8.0.1.0.0 (2017-06-08)

    +
    +

    8.0.1.0.0 (2017-06-08)

    • First Version of the module.
    -

    Bug Tracker

    +

    Bug Tracker

    Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -505,16 +529,16 @@

    Bug Tracker

    Do not contact contributors directly about support or help with technical issues.

    -

    Credits

    +

    Credits

    -

    Authors

    +

    Authors

    • ACSONE SA/NV
    • GRAP
    -

    Contributors

    +

    Contributors

    -

    Maintainers

    +

    Maintainers

    This module is maintained by the OCA.

    Odoo Community Association diff --git a/pos_cash_move_reason/static/description/pos_cash_move_ui.png b/pos_cash_move_reason/static/description/pos_cash_move_ui.png new file mode 100644 index 0000000000..c63bbb608b Binary files /dev/null and b/pos_cash_move_reason/static/description/pos_cash_move_ui.png differ diff --git a/pos_cash_move_reason/static/description/pos_close_popup.png b/pos_cash_move_reason/static/description/pos_close_popup.png new file mode 100644 index 0000000000..39b9233a75 Binary files /dev/null and b/pos_cash_move_reason/static/description/pos_close_popup.png differ diff --git a/pos_cash_move_reason/static/src/css/PosCashMoveReason.scss b/pos_cash_move_reason/static/src/css/PosCashMoveReason.scss new file mode 100644 index 0000000000..205b3ca177 --- /dev/null +++ b/pos_cash_move_reason/static/src/css/PosCashMoveReason.scss @@ -0,0 +1,56 @@ +.cash-move-popup { + top: 50% !important; + max-width: 700px !important; +} + +.cash-move-reason-title { + margin-top: 15px; + margin-bottom: 5px; +} + +.cash-move-button-on-closepopup { + width: auto !important; + + .cash-move-button { + color: #017e84; + white-space: nowrap; + font-size: 14px; + } +} + +// ### Point of Sale - Close Pop Up ### + +.close-pos-popup { + max-height: 900px !important; +} + +.payment-methods-overview { + max-height: 600px !important; +} + +.pos-cash-move-reason-close-popup-table { + th { + background-color: #714b67; + color: white; + height: 40px; + padding-left: 10px; + } + + .tableFixHead { + position: sticky; + top: 0; + z-index: 1; + } + + tr:hover { + background-color: #e964c617; + } + + .highlight { + font-weight: bold; + } + + .new-payment { + border-top: 2px solid black; + } +} diff --git a/pos_cash_move_reason/static/src/js/CashMoveButtonMoveReason.esm.js b/pos_cash_move_reason/static/src/js/CashMoveButtonMoveReason.esm.js new file mode 100644 index 0000000000..a755bd5f39 --- /dev/null +++ b/pos_cash_move_reason/static/src/js/CashMoveButtonMoveReason.esm.js @@ -0,0 +1,80 @@ +/* +Copyright (C) 2026-Today: GRAP (https://www.grap.coop) +@author: Quentin DUPONT +License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +*/ +odoo.define("pos_cash_move_reason.CashMoveButtonPatch", function (require) { + const CashMoveButton = require("point_of_sale.CashMoveButton"); + const Registries = require("point_of_sale.Registries"); + const {_t} = require("web.core"); + const {renderToString} = require("@web/core/utils/render"); + + const TRANSLATED_CASH_MOVE_TYPE = { + in: _t("in"), + out: _t("out"), + }; + + const CashMoveButtonMoveReason = (CashMoveButton) => + class extends CashMoveButton { + /* Rewrite all function to add extra field */ + async onClick() { + const {confirmed, payload} = await this.showPopup("CashMovePopup"); + if (!confirmed) return; + const {type, amount, reason, move_reason, journal_id} = payload; + const translatedType = TRANSLATED_CASH_MOVE_TYPE[type]; + const formattedAmount = this.env.pos.format_currency(amount); + if (!amount) { + return this.showNotification( + _.str.sprintf( + this.env._t("Cash in/out of %s is ignored."), + formattedAmount + ), + 3000 + ); + } + const extras = { + formattedAmount, + translatedType, + move_reason, + journal_id, + }; + this.rpc({ + model: "pos.session", + method: "try_cash_in_out", + args: [[this.env.pos.pos_session.id], type, amount, reason, extras], + }); + if (this.env.proxy.printer) { + const renderedReceipt = renderToString( + "point_of_sale.CashMoveReceipt", + { + _receipt: this._getReceiptInfo({ + ...payload, + translatedType, + formattedAmount, + }), + } + ); + const printResult = await this.env.proxy.printer.print_receipt( + renderedReceipt + ); + if (!printResult.successful) { + this.showPopup("ErrorPopup", { + title: printResult.message.title, + body: printResult.message.body, + }); + } + } + this.showNotification( + _.str.sprintf( + this.env._t("Successfully made a cash %s of %s."), + type, + formattedAmount + ), + 3000 + ); + } + }; + + Registries.Component.extend(CashMoveButton, CashMoveButtonMoveReason); + return CashMoveButton; +}); diff --git a/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js b/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js new file mode 100644 index 0000000000..86816a3c9b --- /dev/null +++ b/pos_cash_move_reason/static/src/js/CashMovePopupPatch.js @@ -0,0 +1,163 @@ +/* +Copyright (C) 2026-Today: GRAP (https://www.grap.coop) +@author: Quentin DUPONT +License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +*/ +odoo.define("pos_cash_move_reason.CashMovePopupPatch", function (require) { + "use strict"; + + const CashMovePopup = require("point_of_sale.CashMovePopup"); + const {patch} = require("web.utils"); + const {useState} = owl; + const Registries = require("point_of_sale.Registries"); + const {isConnectionError} = require("point_of_sale.utils"); + + patch(CashMovePopup.prototype, "pos_cash_move_reason.CashMovePopupPatch", { + setup() { + this._super(...arguments); + this.state = useState({ + ...this.state, + inputMoveReason: "", + inputMoveReasonFiltered: "", + inputMoveReasonJournal: "", + inputMoveReasonJournalFiltered: [], + }); + }, + onClickMoveReason(reason_id) { + this.state.inputMoveReason = reason_id; + this.state.inputMoveReasonJournal = ""; + + /* Get Journals linked to Move Reason*/ + const reason = this.env.pos.pos_move_reason.find((r) => r.id === reason_id); + + this.state.inputMoveReasonJournalFiltered = reason.journal_ids; + }, + onClickMoveReasonJournal(journal_id) { + this.state.inputMoveReasonJournal = journal_id; + }, + + /* + * Core function for choice IN or OUT + * Reset all choice if user change this + */ + onClickButton(type) { + this.state.inputMoveReason = ""; + this.state.inputMoveReasonJournal = ""; + this.state.inputMoveReasonJournalFiltered = []; + return this._super(type); + }, + + /* Filter Move Reasons according to cash IN or OUT*/ + get filteredMoveReasons() { + let res = []; + if (this.state.inputType === "in") { + res = this.env.pos.pos_move_reason.filter( + (reason) => reason.is_income_reason === true + ); + } else if (this.state.inputType === "out") { + res = this.env.pos.pos_move_reason.filter( + (reason) => reason.is_expense_reason === true + ); + } + return res; + }, + + /* Filter Account Jounal according to Move Reason chosen + POS Payment Journals */ + get filteredJournals() { + const journal_ids = this.state.inputMoveReasonJournalFiltered; + + /* Pm = payment_methods ids of THIS session */ + const session_pm_ids = this.env.pos.pos_session.payment_method_ids; + + /* Get payment_methods object (contain account journal) */ + const session_payment_methods = this.env.pos.payment_methods.filter((pm) => + session_pm_ids.includes(pm.id) + ); + + /* Get journal */ + const pos_journal_ids = session_payment_methods + .map((pm) => pm.journal_id[0]) + .filter((id) => id); + + /* Filter journal with move reason journal + session */ + const res = this.env.pos.account_journal.filter( + (j) => journal_ids.includes(j.id) && pos_journal_ids.includes(j.id) + ); + + /* Choose journal if there is just one*/ + if (res.length === 1) { + this.state.inputMoveReasonJournal = res[0].id; + } + + return res; + }, + + /* Return new fields for popup result, to create bank statement with them */ + getPayload() { + const payload = this._super(...arguments); + + return { + ...payload, + move_reason: this.state.inputMoveReason, + journal_id: this.state.inputMoveReasonJournal, + }; + }, + + /* Add new required fields and handle link with Close PopUp*/ + async confirm(goToClosePopup) { + if (this.state.inputMoveReason === "") { + this.state.inputHasError = true; + this.errorMessage = this.env._t("Select one Reason before confirming."); + return false; + } + if ( + this.state.inputMoveReasonJournalFiltered.length > 0 && + this.state.inputMoveReasonJournal === "" + ) { + this.state.inputHasError = true; + this.errorMessage = this.env._t( + "Select one Journal before confirming." + ); + return false; + } + this._super(...arguments); + + /* If option, show ClosePop */ + /* Code in inspired by core code */ + if (goToClosePopup === "goToClosePopup") { + // Wait for less than a second in order to have the new Move Reason in database. + await new Promise((res) => setTimeout(res, 300)); + + try { + const info = await this.env.pos.getClosePosInfo(); + this.showPopup("ClosePosPopup", {info: info, keepBehind: true}); + } catch (e) { + if (isConnectionError(e)) { + this.showPopup("OfflineErrorPopup", { + title: this.env._t("Network Error"), + body: this.env._t( + "Please check your internet connection and try again." + ), + }); + } else { + this.showPopup("ErrorPopup", { + title: this.env._t("Unknown Error"), + body: this.env._t( + "An unknown error prevents us from getting closing information." + ), + }); + } + } + } + + return true; + }, + + cancel() { + this._super(...arguments); + }, + }); + + Registries.Component.add(CashMovePopup); + return CashMovePopup; +}); diff --git a/pos_cash_move_reason/static/src/js/model.esm.js b/pos_cash_move_reason/static/src/js/model.esm.js new file mode 100644 index 0000000000..28e637ee0a --- /dev/null +++ b/pos_cash_move_reason/static/src/js/model.esm.js @@ -0,0 +1,19 @@ +/** @odoo-module **/ +// Copyright (C) 2026 - Today: GRAP (http://www.grap.coop) +// @author: Quentin DUPONT +// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import {PosGlobalState} from "point_of_sale.models"; +import Registries from "point_of_sale.Registries"; + +const PosMoveReasonPosGlobalState = (PosGlobalState) => + class PosMoveReasonPosGlobalState extends PosGlobalState { + /* Add two new models */ + async _processData(loadedData) { + await super._processData(...arguments); + this.pos_move_reason = loadedData["pos.move.reason"]; + this.account_journal = loadedData["account.journal"]; + } + }; + +Registries.Model.extend(PosGlobalState, PosMoveReasonPosGlobalState); diff --git a/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml b/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml new file mode 100644 index 0000000000..3927c5f527 --- /dev/null +++ b/pos_cash_move_reason/static/src/xml/ClosePosPopup.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Payment MethodExpectedCountedDifference
    + + + +
    + +
    +
    +
    Opening +
    +
    +
    + +
    +
    +
    +
    +
    + Payments in +
    +
    +
    + + + + + +
    + + Payments in + + + +
    +
    +
    + +
    +
    + + +
    +
    + + + +
    + +
    +
    +
    + +
    diff --git a/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml b/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml new file mode 100644 index 0000000000..d2af1b8046 --- /dev/null +++ b/pos_cash_move_reason/static/src/xml/PosCashMoveReason.xml @@ -0,0 +1,75 @@ + + + + + + + + + +

    + Choose the reason +

    + +
    + +
    +
    +
    + + + + +

    Choose the journal

    +
    + +

    Account journal

    +
    + +
    + +
    +
    +
    + +
    + + +
    + Confirm and Go to Close Session +
    +
    + +
    + +
    diff --git a/pos_cash_move_reason/views/view_pos_move_reason.xml b/pos_cash_move_reason/views/view_pos_move_reason.xml index 947d7f4189..8e4c38b25c 100644 --- a/pos_cash_move_reason/views/view_pos_move_reason.xml +++ b/pos_cash_move_reason/views/view_pos_move_reason.xml @@ -38,6 +38,12 @@ + + +