Skip to content

Q2 Plan — roots_hemms_spare_parts (Final 1.0)

Plan version: Final 1.1 — Q2.1–Q2.8 committed 2026-05-22 Approved: 2026-05-22 Module ID: roots_hemms_spare_parts Layer: 3 (Hospital lens) — wraps OCA Layer 2 module maintenance_stock Status: Q2.1–Q2.8 ✅ committed | Q2.9–Q2.11 pending

0. Executive Summary

What we build: A hospital-flavoured spare-parts module on top of OCA maintenance_stock that (a) lets each maintenance.equipment carry a filterable spare-parts template, (b) auto-creates a draft stock.picking when PM cron generates a request, and © gives technicians a dedicated "My Open Requests" view to track their pending pickings with a running Stock Request number.

Why: OCA maintenance_stock provides the picking↔request link but deliberately leaves three gaps that matter for hospital UX — no consumption template, no auto-picking, no maintenance-flavoured tracking. We fill all three at Layer 3.

Effort: 11 commits / ~3 dev sessions, 26 tests target, 5-page docs.

Keystone wins (impact-ranked):

  1. Auto-draft picking on PM cron (Q2.6) — technician clicks Validate, not New
  2. My Open Requests view (Q2.8) — self-service tracking with Running No SR/2026/00001
  3. PM-kind filter on template (Q2.4) — only relevant spares per maintenance kind

License: LGPL-3 over AGPL-3 dependency — compatible at distribution.

Status of open questions: None remaining (all locked in §2).


1. Purpose

Hospital BME technicians need to know which spare parts a given piece of equipment may require during repair/PM — and then track each request to completion — without memorising SKUs or asking senior engineers. The module gives equipment master data a configurable spare-part template that auto-flows into a draft picking when a maintenance request is opened, with a dedicated tracking view to follow up.

Scope — in

  • Vendor OCA maintenance_stock (Layer 2 prerequisite)
  • New model hemms.equipment.spare.line (o2m on maintenance.equipment)
  • roots_is_critical_spare + roots_name_th on product.template
  • Bilingual name_get so EN and TH names both searchable
  • Inline qty_available + critical badge in template view
  • Auto-propagate equipment criticality red → line roots_is_critical
  • Low-stock activity for critical spares
  • Auto-create draft picking on PM cron, filtered by request's kind
  • Draft picking remains fully editable (add / remove / change qty) until validated — confirmed in Q2.6 behaviour
  • Spare summary stat button on equipment form
  • Stock Request running no. roots_request_no via ir.sequence code hemms.stock.request (prefix configurable through UI, default SR/%(year)s/ padding 5 → SR/2026/00001)
  • Stock Request tracking views (List / Form / Kanban) with computed open/pending fields — default landing = "My Open Requests"
  • "Pending Items" stat button on maintenance.request form

Scope — out (deferred)

  • ❌ "Used last time" pin (F4) — Q3
  • ❌ Single-page move-line embed in request form (S5) — Q3
  • ❌ "Copy from similar equipment" wizard (S6) — Q3
  • ❌ Compatibility guard via product brand m2m (S4) — Q3
  • ❌ Auto-reserve stock on stage transition (A2) — Q3
  • ❌ Auto-return picking on request cancel (A3) — Q3
  • ❌ Track "manual vs template" line origin — Q3 analytics
  • ❌ Qty auto-learn (A5), ward cost notification (A7) — Q4+
  • ❌ Multi-warehouse routing, PO automation

2. Design decisions (locked)

# Decision Choice
D1 Template on equipment vs category Equipment (category copy = Q3 wizard)
D2 Auto-picking default Opt-in per equipment, default True when allow_consumptions=True
D3 Qty-over-default approval in Q2 No — revisit Q3 after demo feedback
D4 Field naming roots_* prefix on inherited models; bare names on new hemms.*
D5 License LGPL-3 (roots) over AGPL-3 (maintenance_stock)
D6 Stock Request sequence format SR/%(year)s/00001 (year-based, padding 5) — admin-editable through Settings, NOT hardcoded in Python; lives in ir.sequence data
D7 Default landing for tracking menu My Open Requests (state ∉ done/cancel AND user_id=uid OR create_uid=uid)
D8 Draft picking editability Fully editable (add / remove / change qty) until Validate — relies on default Odoo stock behaviour

3. Data model

maintenance.equipment (inherit)
  ├─ roots_auto_generate_picking (Boolean, default by allow_consumptions)
  ├─ roots_spare_line_ids (o2m → hemms.equipment.spare.line)
  └─ roots_spare_line_count (Integer, computed)

hemms.equipment.spare.line (NEW)
  ├─ equipment_id (m2o → maintenance.equipment, ondelete=cascade)
  ├─ product_id (m2o → product.product, ondelete=restrict)
  ├─ default_qty (Float, default 1.0)
  ├─ kind_ids (m2m → maintenance.kind)         ⭐ filter axis
  ├─ position_note (Char)
  ├─ roots_is_critical (Boolean, computed+stored, writable)
  ├─ qty_available (Float, related, readonly)
  └─ sequence (Integer)

product.template (inherit)
  ├─ roots_is_critical_spare (Boolean)
  └─ roots_name_th (Char)

stock.picking (inherit — Stock Request tracking)
  ├─ roots_request_no (Char, readonly, copy=False, index=True, tracking=True)
  ├─ roots_is_open (Boolean, computed+stored)
  ├─ roots_pending_line_count (Integer, computed)
  ├─ roots_qty_progress (Float, computed)        sum(quantity)/sum(uom_qty)*100
  ├─ roots_pending_qty_total (Float, computed)
  └─ roots_scheduled_overdue (Boolean, computed)

ir.sequence (data record, NOT hardcoded)
  ├─ code   : "hemms.stock.request"
  ├─ name   : "HEMMS Stock Request"
  ├─ prefix : "SR/%(year)s/"
  ├─ padding: 5
  └─ company_id: False

4. Development Plan (11 steps)

# Step Commit prefix Key deliverable
Q2.1 ✅ Vendor OCA maintenance_stock 18.0 → addons/oca/ 3650615 Installs cleanly
Q2.2 ✅ Module skeleton (manifest + empty models + security) 8332871 Installs; no features
Q2.3 ✅ roots_is_critical_spare + roots_name_th on product.template + bilingual _compute_display_name + EN/TH _name_search ca55074 "แบตเตอรี่" matches "Battery"
Q2.4 ✅ New model hemms.equipment.spare.line + o2m on equipment + inline view (qty_available, critical badge, kind tags) 5910394 Admin sets template; technician sees filtered list per kind
Q2.5 ✅ ir.cron for daily critical-spare low-stock activity f1340c3 Critical badge + activity flow
Q2.6 ✅ maintenance.request.create() override → auto-create draft stock.picking from template filtered by maintenance_kind_id ad0805d PM request opens with draft picking pre-filled; lines remain editable
Q2.7 ✅ Spare summary stat buttons on equipment form ("N Spare Parts · N Critical Low") c2dc019 One-click drill from equipment
Q2.8 ✅ ⭐ Stock Request trackingir.sequence + roots_request_no + computed pending fields + List/Kanban views + search filters + "Pending SR" stat button on request form + menu 3f36d84 Technician opens "Stock Requests" menu, sees SR/2026/00001, pending count per picking
Q2.9 Tests (9 unit + 17 integration = 26) test(spare): add unit + integration tests [Q2.9] Green CI
Q2.10 Module docs (5 pages) under docs/modules/spare-parts/ docs(spare): add module documentation [Q2.10] mkdocs serve clean
Q2.11 Demo data — ventilator/MRI/defibrillator with realistic spare templates chore(spare): demo data for 3 critical equipment classes [Q2.11] Smoke test passes on fresh DB

Dependencies: maintenance_stock (OCA — to vendor), roots_hemms_pm, roots_hemms_asset_master, roots_hemms_criticality, stock, mail.

5. View specifications (Q2.8 detail)

5.1 Menu structure

Maintenance
└─ Operations
   └─ Stock Requests (ใบเบิก)
      ├─ My Open Requests          ← default landing (D7)
      ├─ All My Requests
      ├─ Team Open
      └─ Backorders
└─ Configuration
   └─ Stock Request Sequence       ← ir.sequence shortcut

5.2 List view (default = My Open Requests)

Column Source
Request No. roots_request_no
Equipment equipment_id (with photo)
For Request maintenance_request_id
State state (badge)
Progress roots_qty_progress (progress bar widget)
Pending Items roots_pending_line_count
Scheduled scheduled_date (red if overdue)
  • Default filter: state ∉ done/cancel AND mine
  • Default group by: State
  • Decoration: row in red when roots_scheduled_overdue=True

5.3 Form view — "Pending Items" notebook tab

Column Source
Product move.product_id
Ordered move.product_uom_qty
Received move.quantity
Pending product_uom_qty - quantity
Available product.qty_available (red badge if 0)
Critical product.roots_is_critical_spare
  • Filter: shows only moves where state ∉ (done, cancel)
  • Header shows roots_request_no prominently + state badge + progress bar

5.4 Kanban view

  • Columns by state: Draft / Waiting / Ready / Partial / Done (Partial column hidden by default)
  • Card: equipment thumbnail, request no, item count, progress bar, overdue badge

5.5 Search filters

  • ☑ Mine (user_id=uid OR create_uid=uid)
  • ☑ Open Only (state ∉ done, cancel)
  • ☐ Overdue (scheduled_date < today AND roots_is_open)
  • ☐ Backorders (backorder_id != False)
  • ☐ Has Critical Spare (mapped("move_ids.product_id.roots_is_critical_spare") contains True)
  • ☐ Scheduled This Week
  • Group by: State / Equipment / Kind / Scheduled Date

5.6 "Pending Items" stat button on maintenance.request

Adds a 4th stat button beside OCA's 3 (Picking list / Stock Moves / Product Moves):

[ Picking list ]  [ Stock Moves ]  [ Product Moves ]  [ Pending Items ⭐ ]
       3                8                              5

Opens a list of stock.move where picking_id.maintenance_request_id=request.id AND state ∉ done, cancel.

6. UX features captured

Feature From analysis In which Q2 step
Equipment spare template (data model A3) Core req Q2.4
Auto-populate picking from template (F1) Friction Q2.6
Pre-filter by PM kind (F2) Friction Q2.4 + Q2.6
Inline qty_available + critical badge (F3) Friction Q2.4 view
Bilingual search (F5) Friction Q2.3
Spare summary widget (S1) Simplify Q2.7
Hide out-of-stock toggle (S3) Simplify Q2.4 view
Auto-create draft picking on PM cron (A1) Automate ⭐ Q2.6
Low-stock activity (A4) Automate Q2.5
Auto-propagate critical (A6) Automate Q2.5
Stock Request running no. (NEW) UX traceability Q2.8
My Open Requests view (NEW) UX self-service Q2.8
Pending Items per picking (NEW) UX visibility Q2.8
Editable draft picking (confirmed) Reality of fieldwork Q2.6 (default Odoo behavior, locked by tests)

7. Test Cases (26)

Unit (test_spare_unit.py) — 9 tests

# Test Verifies
U1 test_critical_spare_default_false Flag default = False
U2 test_bilingual_name_get name_get() returns "EN / TH" when roots_name_th set
U3 test_bilingual_search_by_thai name_search("แบตเตอรี่") finds product whose roots_name_th="แบตเตอรี่"
U4 test_spare_line_default_qty New line defaults qty = 1.0
U5 test_spare_line_critical_propagation Equipment criticality=red → line roots_is_critical=True
U6 test_auto_generate_flag_follows_allow_consumptions allow_consumptions=Trueroots_auto_generate_picking=True
U7 test_request_no_assigned_on_create_with_maintenance_link Picking with maintenance_request_idroots_request_no populated
U8 test_request_no_skipped_for_non_maintenance_picking Picking without maintenance link → roots_request_no empty
U9 test_request_no_uses_ir_sequence_code Sequence code = hemms.stock.request exists; prefix NOT hardcoded in Python

Integration (test_spare_integration.py) — 17 tests

# Test Verifies
I1 test_add_spare_line_to_equipment Admin can add product + qty + kind
I2 test_template_filtered_by_kind_on_request_view Opening request kind=Calibration shows only lines tagged that kind
I3 test_pm_cron_creates_draft_picking_from_template PM cron → request + draft picking from template
I4 test_pm_cron_picking_lines_filtered_by_kind Auto-picking only includes template lines matching kind
I5 test_validating_picking_reduces_qty_available Validate picking → qty_available drops
I6 test_critical_spare_below_min_creates_activity Critical spare < min → activity for BME Manager
I7 test_non_critical_spare_below_min_no_activity Non-critical → no activity
I8 test_equipment_summary_count_excludes_archived_products Stat button count ignores archived
I9 test_corrective_request_does_not_auto_generate Non-PM request → no auto-picking
I10 test_auto_generate_disabled_skips_picking roots_auto_generate_picking=False → no draft
I11 test_technician_can_add_non_template_line_to_draft_picking Adding line outside template, validate succeeds
I12 test_technician_can_remove_template_line_from_draft_picking Removing template line, validate succeeds
I13 test_technician_can_change_qty_on_template_line Qty 1→2, qty_available drops 2 on validate
I14 test_pending_line_count_excludes_done_moves Pending count excludes moves in done/cancel
I15 test_qty_progress_computes_correctly_on_partial_validation Partial validation → progress=50%, state=partial
I16 test_open_filter_excludes_done_and_cancel "Open Only" filter excludes done + cancel
I17 test_my_filter_includes_responsible_and_creator "Mine" filter includes both user_id and create_uid

Acceptance smoke (manual)

  1. Setup ventilator → criticality=red → allow_consumptions=True → add 3 template lines (Filter ×1 kind=Filter Replacement, Battery ×1 kind=Battery, Sensor ×1 kind=Calibration)
  2. Attach maintenance.plan kind=Filter Replacement, interval=3 months
  3. Run PM cron manually
  4. Verify: request in PM Scheduled stage; draft picking attached with roots_request_no=SR/2026/00001 containing only the Filter line
  5. Open "My Open Requests" menu → see SR/2026/00001 with progress 0%
  6. Open picking → "Pending Items" tab → see Filter line with Pending=1
  7. Add an unscheduled Battery line → save → Pending=2
  8. Validate → state=done → row disappears from "My Open Requests"
  9. If Filter qty falls below min → activity on BME Manager

8. Open questions

None. Module name, scope, data model, design decisions (D1–D8), sequence format, and default landing view all confirmed 2026-05-22.

9. Resume checklist

Completed (as of 2026-05-22): Q2.1–Q2.8 all committed on main.

Next session starts at Q2.9:

  1. Read this file (v1.1)
  2. Run tests from §7 — implement tests/test_spare_unit.py + tests/test_spare_integration.py
  3. Commit as test(spare): add unit + integration tests [Q2.9]
  4. Proceed to Q2.10 (docs) and Q2.11 (demo data) to close out Q2