add spreadsheet_dashboard_pos_cooperative & pos_dashboard_cooperative
This commit is contained in:
parent
86101218e9
commit
5247938b86
34 changed files with 7974 additions and 0 deletions
215
pos_dashboard_cooperative/tests/test_pos_dashboard.py
Normal file
215
pos_dashboard_cooperative/tests/test_pos_dashboard.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
# 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue