diff --git a/recurring_consignment/demo/account_account.xml b/recurring_consignment/demo/account_account.xml index 1c9b91fd..db6ca9de 100644 --- a/recurring_consignment/demo/account_account.xml +++ b/recurring_consignment/demo/account_account.xml @@ -6,6 +6,9 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). --> + + + Demo Account Receivable 411 @@ -38,6 +41,27 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + Cash Suspense Account + 5xx + asset_current + + + + + Extra Expense + 658 + expense + + + + + Extra Revenue + 758 + income + + + Commission Account 706 @@ -52,4 +76,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + + + diff --git a/recurring_consignment/demo/account_journal.xml b/recurring_consignment/demo/account_journal.xml index 108ea38d..944fbf9e 100644 --- a/recurring_consignment/demo/account_journal.xml +++ b/recurring_consignment/demo/account_journal.xml @@ -4,20 +4,32 @@ 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). --> - + + - Demo Sale Journal For Consignor + Demo Sale Journal sale - SJ-CONS + SALE + + Demo Cash Journal + cash + CASH + + + + + + + Demo Misc Journal For Consignor general - MC-CONS + MISC diff --git a/recurring_consignment/demo/product_pricelist.xml b/recurring_consignment/demo/product_pricelist.xml index 8726e37e..e23f2a5a 100644 --- a/recurring_consignment/demo/product_pricelist.xml +++ b/recurring_consignment/demo/product_pricelist.xml @@ -8,7 +8,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). My Pricelist (-10%) - + + @@ -21,7 +22,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). My Pricelist (-50%) - + + diff --git a/recurring_consignment/models/account_move.py b/recurring_consignment/models/account_move.py index e9882108..7f7941ab 100644 --- a/recurring_consignment/models/account_move.py +++ b/recurring_consignment/models/account_move.py @@ -155,7 +155,7 @@ def _get_commission_information_product_detail_grouped(self): # Get related invoice lines com_invoice_lines = self.mapped( "invoice_line_ids.consignment_invoice_line_ids" - ).filtered(lambda x: x.display_type == "product") + ).filtered(lambda x: x.display_type == "product" and x.product_id) for com_invoice_line in com_invoice_lines: key = ( diff --git a/recurring_consignment/models/res_partner.py b/recurring_consignment/models/res_partner.py index b54de746..0087f1bc 100644 --- a/recurring_consignment/models/res_partner.py +++ b/recurring_consignment/models/res_partner.py @@ -3,7 +3,7 @@ # 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 +from odoo.exceptions import UserError class ResPartner(models.Model): @@ -35,7 +35,7 @@ def _check_is_consignor_consignment_account_id(self): if partner.is_consignor: if not partner.consignment_account_id: raise UserError( - _("A Consignor must have a 'Consignment Account'" " defined.") + _("A Consignor must have a 'Consignment Account' defined.") ) else: if ( @@ -48,15 +48,3 @@ def _check_is_consignor_consignment_account_id(self): " Commission' neither 'Consignment Account' defined." ) ) - - # TODO prevent check and uncheck. (only via wizard is possible) - # # Overload Section - # def write(self, vals): - # if not vals.get("is_consignor", True) and any(self.mapped("is_consignor")): - # raise UserError( - # _( - # "You can not unset consignor setting on partner.\n" - # " Please create a new one if you want to do so." - # ) - # ) - # return super().write(vals) diff --git a/recurring_consignment/tests/test_make_commissions.py b/recurring_consignment/tests/test_make_commissions.py index c992e62e..0a0d9418 100644 --- a/recurring_consignment/tests/test_make_commissions.py +++ b/recurring_consignment/tests/test_make_commissions.py @@ -9,7 +9,7 @@ @tagged("post_install", "-at_install") -class TestCreateConsignors(TransactionCase): +class TestMakeCommissions(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -73,13 +73,13 @@ def test_30_commission_workflow(self): self.assertEqual( len(commission_invoice.invoice_line_ids), 1, - "Two commission lines should be generated", + "One commission line should be generated", ) self.assertEqual( len(lines_20), 1, "One 20% commission line should be generated" ) - # Check line #2 details (Tax Excl) + # Check line details (Tax Excl) line_20 = lines_20[0] self.assertEqual(line_20.quantity, 1, "Incorrect Commission Quantity.") self.assertEqual( diff --git a/recurring_consignment_pos/__manifest__.py b/recurring_consignment_pos/__manifest__.py index d9c36ef1..674db3ee 100644 --- a/recurring_consignment_pos/__manifest__.py +++ b/recurring_consignment_pos/__manifest__.py @@ -11,12 +11,16 @@ "website": "https://github.com/grap/grap-odoo-business", "license": "AGPL-3", "depends": [ - "recurring_consignment", + # Odoo "point_of_sale", + # GRAP + "recurring_consignment", ], - "data": [ - "views/view_account_invoice.xml", - "views/templates.xml", + "data": ["views/view_account_move.xml"], + "demo": [ + "demo/pos_payment_method.xml", + "demo/pos_config.xml", + "demo/product_product.xml", ], "installable": True, "auto_install": True, diff --git a/recurring_consignment_pos/demo/pos_config.xml b/recurring_consignment_pos/demo/pos_config.xml new file mode 100644 index 00000000..591292c7 --- /dev/null +++ b/recurring_consignment_pos/demo/pos_config.xml @@ -0,0 +1,19 @@ + + + + + + Pos Config + + + + + + + + + diff --git a/recurring_consignment_pos/demo/pos_payment_method.xml b/recurring_consignment_pos/demo/pos_payment_method.xml new file mode 100644 index 00000000..71453d9e --- /dev/null +++ b/recurring_consignment_pos/demo/pos_payment_method.xml @@ -0,0 +1,15 @@ + + + + + + Cash Method + + + + + diff --git a/recurring_consignment_pos/demo/product_product.xml b/recurring_consignment_pos/demo/product_product.xml new file mode 100644 index 00000000..5cc7d9ce --- /dev/null +++ b/recurring_consignment_pos/demo/product_product.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + My Consigned Product E (VAT 20% - Consignor 1) + + + + + + + + diff --git a/recurring_consignment_pos/models/__init__.py b/recurring_consignment_pos/models/__init__.py index e4b9bbb2..5570f965 100644 --- a/recurring_consignment_pos/models/__init__.py +++ b/recurring_consignment_pos/models/__init__.py @@ -1,3 +1,2 @@ -from . import account_invoice -from . import pos_order +from . import account_move from . import product_template diff --git a/recurring_consignment_pos/models/account_invoice.py b/recurring_consignment_pos/models/account_move.py similarity index 79% rename from recurring_consignment_pos/models/account_invoice.py rename to recurring_consignment_pos/models/account_move.py index df1164d5..62e9acdd 100644 --- a/recurring_consignment_pos/models/account_invoice.py +++ b/recurring_consignment_pos/models/account_move.py @@ -2,11 +2,11 @@ # @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, models +from odoo import models -class AccountInvoice(models.Model): - _inherit = "account.invoice" +class AccountMove(models.Model): + _inherit = "account.move" # View Section def button_commission_view_pos_order_lines(self): @@ -17,13 +17,32 @@ def button_commission_view_pos_order_lines(self): return action_data # Private Section + def _get_commission_information_product_detail_grouped(self): + groups = super()._get_commission_information_product_detail_grouped() + + # Get related pos order lines + com_order_lines = self._get_commission_related_pos_order_lines() + for com_order_line in com_order_lines: + key = ( + com_order_line.product_id, + com_order_line.price_unit, + com_order_line.discount, + ) + groups.setdefault(key, {"quantity": 0, "total_vat_excl": 0}) + groups[key] = { + "quantity": groups[key]["quantity"] + com_order_line.qty, + "total_vat_excl": groups[key]["total_vat_excl"] + + com_order_line.price_subtotal, + } + return groups + def _get_commission_related_pos_order_lines(self): PosOrder = self.env["pos.order"] PosOrderLine = self.env["pos.order.line"] ProductProduct = self.env["product.product"] # Get Account Move - moves = self.mapped("consignment_line_ids.move_id") + moves = self.mapped("invoice_line_ids.consignment_invoice_line_ids.move_id") # Get Product ids consignor_products = ProductProduct.with_context(active_test=False).search( @@ -31,7 +50,7 @@ def _get_commission_related_pos_order_lines(self): ) # Get related pos orders - com_orders = PosOrder.search([("account_move", "in", moves.ids)]) + com_orders = PosOrder.search([("session_move_id", "in", moves.ids)]) # We add pos.order.line sales, that are not invoiced # because the lines are still include in the module @@ -44,29 +63,3 @@ def _get_commission_related_pos_order_lines(self): ("product_id", "in", consignor_products.ids), ] ) - - def _get_commission_information_product_detail_grouped(self): - groups = super().get_commission_information_product_detail_grouped() - - # Get related pos order lines - com_order_lines = self._get_commission_related_pos_order_lines() - - for com_order_line in com_order_lines: - key = ( - com_order_line.product_id.id, - com_order_line.price_unit, - com_order_line.discount, - ) - groups.setdefault( - key, - { - "quantity": 0, - "total_vat_excl": 0, - }, - ) - groups[key] = { - "quantity": groups[key]["quantity"] + com_order_line.qty, - "total_vat_excl": groups[key]["total_vat_excl"] - + com_order_line.price_subtotal, - } - return groups diff --git a/recurring_consignment_pos/models/pos_order.py b/recurring_consignment_pos/models/pos_order.py deleted file mode 100644 index 8f648027..00000000 --- a/recurring_consignment_pos/models/pos_order.py +++ /dev/null @@ -1,15 +0,0 @@ -# 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 import _, api, models -from odoo.exceptions import UserError - - -class PosOrder(models.Model): - _inherit = "pos.order" - - @api.constrains("partner_id") - def _check_partner_id_recurring_consignment(self): - if self.partner_id.is_consignor: - raise UserError(_("You can not make PoS Orders to consignors")) diff --git a/recurring_consignment_pos/models/product_template.py b/recurring_consignment_pos/models/product_template.py index 1f6ad04a..e39d7302 100644 --- a/recurring_consignment_pos/models/product_template.py +++ b/recurring_consignment_pos/models/product_template.py @@ -2,8 +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 _, api, models -from odoo.exceptions import Warning as UserError +from odoo import _, models +from odoo.exceptions import ValidationError class ProductTemplate(models.Model): @@ -11,23 +11,30 @@ class ProductTemplate(models.Model): # Constrains Section def _check_consignor_changes(self, vals): - super()._check_consignor_changes(vals) + res = super()._check_consignor_changes(vals) + PosSession = self.env["pos.session"] + if PosSession.search([("state", "!=", "closed")]): + raise ValidationError( + _( + "You can not change the value of the field" + " 'Consignor' because a Pos Session is opened." + " Please make such changement when sessions" + " are closed." + ) + ) + PosOrderLine = self.env["pos.order.line"] - if vals.get("consignor_partner_id", False): - for template in self: - product_ids = template.product_variant_ids.ids - if template.consignor_partner_id.id != vals.get( - "consignor_partner_id", False - ): - order_lines = PosOrderLine.search( - [("product_id", "in", product_ids)] + for template in self: + order_lines = PosOrderLine.search( + [("product_id", "in", template.product_variant_ids.ids)] + ) + if len(order_lines): + raise ValidationError( + _( + "You can not change the value of the field" + " 'Consignor' because the product is associated" + " to one or more PoS Order Lines. You should" + " disable the product and create a new one." ) - if len(order_lines): - raise UserError( - _( - "You can not change the value of the field" - " 'Consignor' because the product is associated" - " to one or more PoS Order Lines. You should" - " disable the product and create a new one." - ) - ) + ) + return res diff --git a/recurring_consignment_pos/static/src/js/recurring_consignment.js b/recurring_consignment_pos/static/src/js/recurring_consignment.js deleted file mode 100644 index 5fe3a035..00000000 --- a/recurring_consignment_pos/static/src/js/recurring_consignment.js +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2014 - 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.define('recurring_consignment_pos.recurring_consignment', function (require) { - 'use strict'; - - var models = require('point_of_sale.models'); - - var PosModel_super = models.PosModel.prototype; - - models.PosModel = models.PosModel.extend({ - load_server_data: function () { - var self = this; - _.each(self.models, function (item) { - if (item.model === 'res.partner') { - item.domain.push(['is_consignor', '=', false]); - } - }); - return PosModel_super.load_server_data.apply(this, arguments); - }, - }); - -}); diff --git a/recurring_consignment_pos/tests/__init__.py b/recurring_consignment_pos/tests/__init__.py new file mode 100644 index 00000000..53a55243 --- /dev/null +++ b/recurring_consignment_pos/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_abstract +from . import test_product_settings +from . import test_make_commissions diff --git a/recurring_consignment_pos/tests/test_abstract.py b/recurring_consignment_pos/tests/test_abstract.py new file mode 100644 index 00000000..f038501f --- /dev/null +++ b/recurring_consignment_pos/tests/test_abstract.py @@ -0,0 +1,77 @@ +# Copyright (C) 2024 - 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 Command +from odoo.tests.common import TransactionCase, tagged + + +@tagged("post_install", "-at_install") +class TestAbstract(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.PosOrder = cls.env["pos.order"] + cls.env.user = cls.env.ref("base.user_admin") + cls.env.user.company_id = cls.env.ref("recurring_consignment.company") + cls.env.company = cls.env.ref("recurring_consignment.company") + cls.config = cls.env.ref("recurring_consignment_pos.pos_config") + cls.product_E = cls.env.ref( + "recurring_consignment_pos.consigned_product_consignor_1_vat_20_E" + ) + cls.cash_pm1 = cls.env.ref("recurring_consignment_pos.payment_method_cash") + cls.sales_account = cls.env.ref( + "recurring_consignment.account_income_commission" + ) + + def _make_pos_order(self, product=False, close_session=False): + self.config.open_ui() + self.pos_session = self.config.current_session_id + + line_vals = { + "id": 1, + "qty": 1, + "price_unit": 1, + "price_subtotal": 1, + "price_subtotal_incl": 1.20, + "discount": 0, + "product_id": product.id, + "tax_ids": [[6, False, product.taxes_id.ids]], + "full_product_name": product.name, + } + statement_vals = { + "name": "2024-06-26 23:28:47", + "payment_method_id": 1, + "amount": 1.20, + } + + order_data = { + "id": "00003-001-0001", + "data": { + "sequence_number": 1, + "name": "Order 00099-099-0099", + "pos_session_id": self.pos_session.id, + "creation_date": "2024-06-26T23:28:47.947Z", + "access_token": "a89e1005-6f4c-4aa6-9b0f-9bc27a5379ff", + "user_id": self.env.user.id, + "pricelist_id": 1, + "fiscal_position_id": False, + "partner_id": False, + "amount_paid": 1.20, + "amount_total": 1.20, + "amount_tax": 0.20, + "amount_return": 0, + "lines": [Command.create(line_vals)], + "statement_ids": [Command.create(statement_vals)], + }, + "to_invoice": False, + } + + orders = self.PosOrder.create_from_ui([order_data]) + + self.assertEqual(len(orders), 1) + + if close_session: + self.pos_session.close_session_from_ui() + + return self.pos_session, orders diff --git a/recurring_consignment_pos/tests/test_make_commissions.py b/recurring_consignment_pos/tests/test_make_commissions.py new file mode 100644 index 00000000..1e334d5d --- /dev/null +++ b/recurring_consignment_pos/tests/test_make_commissions.py @@ -0,0 +1,62 @@ +# Copyright (C) 2024 - 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 tagged + +from odoo.addons.recurring_consignment.tests.test_make_commissions import ( + TestMakeCommissions, +) + +from .test_abstract import TestAbstract + + +@tagged("post_install", "-at_install") +class TestMakeCommissionPointOfSale(TestAbstract, TestMakeCommissions): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_50_commission_workflow_point_of_sale(self): + self._make_pos_order(product=self.product_E, close_session=True) + commission_invoices = self._make_commission(self.consignor_1) + + self.assertEqual( + len(commission_invoices), 1, "It should generate one invoice commission" + ) + + commission_invoice = commission_invoices[0] + + self.assertEqual(commission_invoice.state, "draft") + + lines_20 = commission_invoice.invoice_line_ids.filtered( + lambda x: x.product_id.id == self.commission_product_vat_20.id + ) + # check invoice lines generated + self.assertEqual( + len(commission_invoice.invoice_line_ids), + 1, + "One commission line should be generated", + ) + self.assertEqual( + len(lines_20), 1, "One 20% commission line should be generated" + ) + + # Check line #2 details (Tax Excl) + line_20 = lines_20[0] + self.assertEqual(line_20.quantity, 1, "Incorrect Commission Quantity.") + self.assertEqual( + line_20.price_unit, 0.2, "Incorrect Commission Price Unit, awaiting 1 * 0.2" + ) + self.assertEqual( + line_20.tax_ids.ids, + [self.vat_20_exclude.id], + "Incorrect Commission Tax.", + ) + + self.IrActionsReport._render_qweb_pdf( + "account.account_invoices", commission_invoices.ids + ) + + commission_invoice.action_post() + self.assertEqual(commission_invoice.payment_state, "paid") diff --git a/recurring_consignment_pos/tests/test_product_settings.py b/recurring_consignment_pos/tests/test_product_settings.py new file mode 100644 index 00000000..b6c1f09f --- /dev/null +++ b/recurring_consignment_pos/tests/test_product_settings.py @@ -0,0 +1,64 @@ +# 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). + +from odoo.exceptions import ValidationError +from odoo.tests.common import tagged + +from odoo.addons.recurring_consignment.tests.test_product_settings import ( + TestProductSettings, +) + +from .test_abstract import TestAbstract + + +@tagged("post_install", "-at_install") +class TestProductSettingsPointOfSale(TestAbstract, TestProductSettings): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.product_D = cls.env.ref( + "recurring_consignment.consigned_product_consignor_1_vat_20_D" + ) + + def test_51_change_consignor_no_pos_order(self): + """Test if it's possible to change a consignor for a product + that has not been saled in a pos order""" + self.product_E.product_tmpl_id.write( + { + "consignor_partner_id": self.consignor_2.id, + "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id, + } + ) + + def test_52_change_consignor_openened_session(self): + """Test if it's impossible to change a consignor for a product + if a session is opened""" + self._make_pos_order(product=self.product_D, close_session=False) + with self.assertRaises(ValidationError): + new_vals = { + "consignor_partner_id": self.consignor_2.id, + "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id, + } + self.product_E.product_tmpl_id.write(new_vals) + + def test_53_change_consignor_closed_session(self): + """Test if it's possible to change a consignor for a product + if a session is closed and the product has not been sold""" + self._make_pos_order(product=self.product_D, close_session=True) + new_vals = { + "consignor_partner_id": self.consignor_2.id, + "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id, + } + self.product_E.product_tmpl_id.write(new_vals) + + def test_54_change_consignor_with_pos_order(self): + """Test if it's impossible to change a consignor for a product + that has been saled in a pos order""" + self._make_pos_order(product=self.product_E, close_session=True) + with self.assertRaises(ValidationError): + new_vals = { + "consignor_partner_id": self.consignor_2.id, + "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id, + } + self.product_E.product_tmpl_id.write(new_vals) diff --git a/recurring_consignment_pos/views/templates.xml b/recurring_consignment_pos/views/templates.xml deleted file mode 100644 index 4f22968b..00000000 --- a/recurring_consignment_pos/views/templates.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - diff --git a/recurring_consignment_pos/views/view_account_invoice.xml b/recurring_consignment_pos/views/view_account_move.xml similarity index 57% rename from recurring_consignment_pos/views/view_account_invoice.xml rename to recurring_consignment_pos/views/view_account_move.xml index 5432f5cc..ee399703 100644 --- a/recurring_consignment_pos/views/view_account_invoice.xml +++ b/recurring_consignment_pos/views/view_account_move.xml @@ -6,15 +6,13 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). --> - - account.invoice - + + account.move + - -