# Copyright 2026 Criptomart # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import date, timedelta import odoo from odoo.tests.common import TransactionCase @odoo.tests.tagged("post_install", "-at_install") class TestPosDashboardCooperative(TransactionCase): """Validate the extra dimensions added by pos_dashboard_cooperative on top of point_of_sale.report.pos.order: - time dimensions (date_day, hour_of_day, day_of_week) - product category hierarchy (categ_root_id, categ_parent_id) - cooperative member historic flag (is_member_at_date) These tests use a minimal hand-built pos.session to stay independent from any existing data in the database and from the heavy TestPoSCommon setup machinery, which requires a clean accounting setup. """ @classmethod def setUpClass(cls): super().setUpClass() cls.company = cls.env.ref("base.main_company") # Two-level category hierarchy: family -> subfamily -> leaf cls.family_root = cls.env["product.category"].create( {"name": "DASHBOARD-FAMILY-ROOT"} ) cls.subfamily = cls.env["product.category"].create( {"name": "DASHBOARD-SUBFAMILY", "parent_id": cls.family_root.id} ) cls.categ_leaf = cls.env["product.category"].create( {"name": "DASHBOARD-LEAF", "parent_id": cls.subfamily.id} ) # Product sold at the POS in every test. cls.product = cls.env["product.product"].create( { "name": "DASHBOARD-PRODUCT", "type": "consu", "available_in_pos": True, "list_price": 10.0, "standard_price": 6.0, "categ_id": cls.categ_leaf.id, } ) # Share product needed by cooperator.share.line. cls.share_product = cls.env["product.product"].create( { "name": "DASHBOARD-SHARE", "is_share": True, "type": "service", "list_price": 25.0, } ) # Two partners: one cooperative member (share dated in the past) # and one non-member. cls.member_partner = cls.env["res.partner"].create( {"name": "DASHBOARD-MEMBER"} ) cls.non_member_partner = cls.env["res.partner"].create( {"name": "DASHBOARD-NON-MEMBER"} ) cls.env["share.line"].create( { "partner_id": cls.member_partner.id, "share_product_id": cls.share_product.id, "share_number": 1, "share_unit_price": 25.0, "effective_date": date.today() - timedelta(days=365), "company_id": cls.company.id, } ) # Reuse an existing POS config to avoid bootstrapping payment # methods and journals from scratch. Any installed config is # acceptable since we never go through the POS UI. cls.pos_config = cls.env["pos.config"].search( [("company_id", "=", cls.company.id)], limit=1 ) assert cls.pos_config, "A pos.config in the main company is required." if not cls.pos_config.current_session_id: cls.pos_config.open_ui() cls.pos_session = cls.pos_config.current_session_id # ------------------------------------------------------------------ # Helpers # ------------------------------------------------------------------ def _create_pos_order(self, partner): return self.env["pos.order"].create( { "session_id": self.pos_session.id, "partner_id": partner.id, "company_id": self.company.id, "lines": [ ( 0, 0, { "name": "DASHBOARD-OL", "product_id": self.product.id, "price_unit": 10.0, "discount": 0, "qty": 1.0, "price_subtotal": 10.0, "price_subtotal_incl": 10.0, "total_cost": 6.0, }, ) ], "amount_total": 10.0, "amount_tax": 0.0, "amount_paid": 0.0, "amount_return": 0.0, } ) def _reports_for(self, partner): return ( self.env["report.pos.order"] .sudo() .search( [ ("product_id", "=", self.product.id), ("partner_id", "=", partner.id), ], order="id", ) ) # ------------------------------------------------------------------ # Tests # ------------------------------------------------------------------ def test_member_flag_for_member_partner(self): """Partner with a share line predating the order is flagged as member.""" self._create_pos_order(self.member_partner) reports = self._reports_for(self.member_partner) self.assertTrue(reports) self.assertTrue(reports[0].is_member_at_date) def test_member_flag_for_non_member_partner(self): """Partner without any share line is not flagged as member.""" self._create_pos_order(self.non_member_partner) reports = self._reports_for(self.non_member_partner) self.assertTrue(reports) self.assertFalse(reports[0].is_member_at_date) def test_member_flag_is_historic(self): """Share line dated AFTER the order date does not mark the partner as member for that order.""" future_member = self.env["res.partner"].create( {"name": "DASHBOARD-FUTURE-MEMBER"} ) self._create_pos_order(future_member) self.env["share.line"].create( { "partner_id": future_member.id, "share_product_id": self.share_product.id, "share_number": 1, "share_unit_price": 25.0, "effective_date": date.today() + timedelta(days=30), "company_id": self.company.id, } ) reports = self._reports_for(future_member) self.assertTrue(reports) self.assertFalse( reports[0].is_member_at_date, "Member flag must reflect the situation at order date.", ) def test_category_hierarchy_is_resolved(self): """categ_root_id and categ_parent_id reflect the product category ancestors derived from parent_path.""" self._create_pos_order(self.member_partner) reports = self._reports_for(self.member_partner) self.assertTrue(reports) report = reports[0] self.assertEqual(report.categ_root_id, self.family_root) self.assertEqual(report.categ_parent_id, self.subfamily) def test_time_dimensions_are_populated(self): """date_day matches order date; hour_of_day / day_of_week are in valid ranges.""" self._create_pos_order(self.member_partner) reports = self._reports_for(self.member_partner) self.assertTrue(reports) report = reports[0] self.assertIsNotNone(report.date_day) self.assertGreaterEqual(report.hour_of_day, 0) self.assertLess(report.hour_of_day, 24) self.assertGreaterEqual(report.day_of_week, 1) self.assertLessEqual(report.day_of_week, 7) def test_read_group_by_member_flag(self): """Aggregations grouped by the member flag return one row per member status when both kinds of partners have purchased.""" self._create_pos_order(self.member_partner) self._create_pos_order(self.non_member_partner) results = self.env["report.pos.order"].read_group( [("product_id", "=", self.product.id)], ["price_total", "is_member_at_date"], ["is_member_at_date"], ) statuses = {r["is_member_at_date"] for r in results} self.assertIn(True, statuses) self.assertIn(False, statuses)