Back to Index Section
Open Wireframe

Climacert‑X — BoQ Pricing & Lifecycle (Super Admin + Tenant) — Consolidated Spec v1.0

1) Goal & Preconditions

Goal (Outcome): Enable Super Admins to manage the price catalog (devices, gateways, connectivity, software, installation, delivery, certification) per region/country, including tax rules and effective versioning. Compute an AI BoQ from intake answers; when the user logs in, persist the BoQ under their account with totals, device counts, target certification, and payment split (OTC vs recurring). Support multi‑facility / multi‑region pricing in one BoQ (apply per‑facility region pricing) and 30‑day validity; allow Redo BoQ after expiry or if details change.

Preconditions:

  • Intake captures facility country/city and (optionally) postal code → resolves to Region (see Region Resolution).
  • Price catalog exists with at least one active PriceList for the resolved region(s).
  • Authentication available (OTP + password) to reveal priced BoQ & checkout.
  • Currency per region is configured; FX source configured for display conversions (if needed).

2) User Flow

Mermaid placeholder:

flowchart TD
  A[Intake answers] --> B{Resolve Regions}
  B -->|GCC| C[Select GCC PriceList]
  B -->|UK| D[Select UK PriceList]
  C --> E[Compute per-facility BoQ]
  D --> E[Compute per-facility BoQ]
  E --> F[BoQ teaser]
  F -->|Login| G[BoQ priced_active]
  G --> H{Within 30 days?}
  H -->|Yes| I[Checkout Split]
  H -->|No| J[priced_expired → Redo]

3) Functional Requirements

3.1 Region Resolution

Input: facility.country, facility.city, optional postalCode.

Rule: Map to Region via precedence: explicit country rule → sub‑region (e.g., GCC vs Non‑GCC) → default fallback.

Examples: AE/SA/QA/OM/BH/KW → GCC, GB → UK, US → USA, DE/FR/… → Europe, SG → SE Asia. If a tenant has multiple facilities in different regions, compute per‑facility line items using each facility’s region.

3.2 Price Catalog (Super Admin)

Entities:

PriceList: { priceListId, name, region, currency, taxPolicyId, effectiveFrom, effectiveTo?, isActive }
SKU: { sku, family, label, unit, meta } (families: device, gateway, connectivity, software, license, installation, delivery, certification)
PriceItem: { priceListId, sku, unitPrice, minQty?, maxQty?, tiering?, discountPct?, taxClass, notes }
TaxPolicy: { taxPolicyId, name, region, taxClasses:[{taxClass, ratePct, inclusive:boolean}] }

CRUD & Versioning:

  • Create/Update PriceList per region; multiple active lists allowed if effective ranges don’t overlap.
  • Price changes do not retro‑edit existing BoQs; BoQs store a snapshot { priceListId, version }.
  • Audit trail: who/when/what changed; export/import CSV.

Taxes:

  • Support inclusive (VAT‑inclusive prices) vs exclusive (VAT added at calc time).
  • TaxClass per SKU (e.g., hardware_standard=5%, software=0%, delivery=5%, certification=0%).
  • Multi‑tax support (VAT + ecoFee) via array if required.

3.3 BoQ Computation

Inputs from intake: facility count, floors, area/floor, freezers, fridges, pools, waterTanks, targetLevel, facility type & location.

Formula Families (configurable Rules): Min devices per area, Cold Storage, Pools → Sigfox, Water Quality, Gateways (LoRa), etc. Admin‑editable without code.

Pricing per Facility Region: per facility resolve region → select active PriceList → compute quantities by family → price line items → apply tax policy. Delivery per device, connectivity per gateway/year, software/licenses per device/pool, installation per facility, certification by type/target.

Discounts: Annual Plan Discount (per region) on recurring totals; optional per‑SKU discount at PriceItem.

3.4 BoQ Output & Persistence

Header: boqId, tenantId, version, createdAt, validUntil(+30 days), priceListId(s), currency per region.
Customer: billTo/shipTo from intake (editable after login before checkout).
Items: [ { sku, label, productCode?, region, facilityId?, qty, unitPrice, discountPct, taxPct, taxAmount, subtotal, type: 'otc'|'annual_recurring' } ].
Totals: { otcTotal, recurringAnnualTotal, recurringMonthlyEquiv, taxTotal, grandTotal }.
Metadata: counts (devices/gateways), targetLevel, facility types, terms, notes.
Visibility: anonymous teaser vs logged‑in full priced + PDF.

3.5 BoQ Lifecycle

States: draft_teaser → priced_active → priced_expired → ordered → paid_otc → cancelled
ValidUntil: createdAt + 30 days (cron → priced_expired)
Redo: recompute with new boqId; keep history; one active BoQ per facility set.

3.6 Checkout Split & Orders

OTC now: hardware, installation, delivery (and setup fees). Recurring later: certification (monthly/annual with discount). Save payment method via SetupIntent. Order links { orderId, boqId, otcAmount, plan, facilities[] }.

3.7 Multi‑Facility & Multi‑Region Rules

Group lines per facility & region; summarize per tenant. PDF sections per facility with region badge; taxes per region. Currency: prefer native; optionally display tenant currency using FX snapshot at BoQ creation.

4) Non‑Functional Requirements

  • Performance: /boq/compute < 2s p95 for ≤ 20 facilities; PDF render < 3s p95.
  • Reliability: idempotency keys; retry with exponential backoff.
  • Security: RBAC; tenant‑scoped snapshots; signed PDF URLs.
  • Auditability: PriceList versions, BoQ snapshots, and tax policy recorded in metadata.

5) Data & Validation Cheatsheet

Region & Catalog
- region: enum { GCC, Europe, SE_Asia, Australia, USA, South_America, UK, … }
- priceList.region: required; currency: ISO 4217 (AED, EUR, USD, GBP)
- taxPolicy: per region; taxClass per SKU family
BoQ
- validUntil: required; now() ≤ validUntil for checkout eligibility
- facility.country, city: required; postalCode optional
- targetLevel: enum { Silver, Gold, Platinum }
Numbers
- unitPrice ≥ 0; qty ≥ 0; taxPct 0–100; discountPct 0–100

6) API Stubs (Shapes)

Super Admin — Price Catalog
POST /v1/admin/pricelists { name, region, currency, taxPolicyId, effectiveFrom, effectiveTo?, isActive } → 201 { priceListId }
POST /v1/admin/pricelists/{id}/items { sku, unitPrice, taxClass, discountPct?, tiering? }
POST /v1/admin/tax-policies { name, region, classes:[{taxClass, ratePct, inclusive}] }
GET  /v1/admin/pricelists?region=GCC&active=true
PUT  /v1/admin/pricelists/{id} (no retro changes to issued BoQs)

Tenant — Intake/BoQ/Checkout
POST /v1/intake …
POST /v1/boq/compute { tenantId, intakeId } → 200 { boqId, priced:false, teaser:{ deviceCounts:[…], highlights:[…] } }
GET  /v1/boq/{boqId} → { priced payload }
POST /v1/checkout { tenantId, boqId, plan:'monthly'|'annual', paymentProvider:'stripe' } → { clientSecret, setupClientSecret, orderId }

BoQ Lifecycle
GET  /v1/boq → list for tenant
POST /v1/boq/{id}/redo → recompute
Background job: boq_expire → marks state=priced_expired at validUntil

7) UX Copy & Errors (EN/AR)

BoQ gate (EN): “Your BoQ is ready. Create an account to view pricing and download the PDF.”
بوابة عرض التسعير (AR): «عرض BoQ جاهز. أنشئ حسابًا لعرض الأسعار وتنزيل الملف.»

Validity (EN): “This BoQ is valid until {date}. Prices and taxes are based on {region}.”
الصلاحية (AR): «هذا العرض صالح حتى {date}. الأسعار والضرائب حسب {region}.»

Expired (EN): “BoQ expired. Redo to refresh prices and taxes.”
منتهي (AR): «انتهت صلاحية العرض. أعد الحساب لتحديث الأسعار والضرائب.»

Multi‑region notice (EN): “Multiple facility regions detected; taxes calculated per region.”
متعدد المناطق (AR): «تم اكتشاف مناطق متعددة للمرافق؛ الضرائب محسوبة لكل منطقة على حدة.»

Errors:

  • EN: “No active price list for region {region}. Contact support.”
    AR: «لا توجد قائمة أسعار فعّالة للمنطقة {region}. يرجى التواصل مع الدعم.»
  • EN: “Tax policy missing for region {region}.”
    AR: «سياسة الضرائب مفقودة للمنطقة {region}.»

8) Edge Cases

  • No active PriceList for region → block pricing; allow teaser only; surface admin alert.
  • Mixed currencies → PDF shows per‑region subtotals in native currency + optional grand total in tenant currency with FX snapshot.
  • Intake change after BoQ issued → mark current BoQ superseded; prompt Redo.
  • Tax inclusive lists → back‑calculate net & tax; show both.
  • Commercial Buildings → floors × 0.5 × facilities for LoRa gateways when facilityType==Commercial.

9) Acceptance Criteria (G/W/T)

Region pricing — Given facility country=UAE, When BoQ is computed, Then items price from GCC price list and VAT per GCC tax policy are applied.
Multi‑region — Given facilities in UAE & UK, When BoQ computed, Then two regional sections are produced with correct currencies/taxes and a tenant grand total.
Lifecycle — Given BoQ created on Sep 1, When Sep 31 passes, Then state becomes priced_expired and checkout is blocked until Redo.
Snapshot — Given PriceList updated on Sep 10, When user opens BoQ created on Sep 5, Then BoQ still shows old prices (snapshot).
Checkout split — Given priced_active BoQ, When checkout completes, Then OTC captured and subscription remains pending_activation until onboarding AND tenant confirmation.

10) Analytics (events)

boq_teaser_viewed { intakeId, deviceCounts }
boq_priced_viewed { boqId, regions:[…], otc, recurringAnnual }
boq_expired { boqId }
boq_redo_requested { oldBoqId, newBoqId }
checkout_succeeded { orderId, otcAmount, plan }
catalog_price_updated { priceListId, userId }

11) API Shapes — Price & Tax Examples (JSON)

{
  "priceList": {
    "priceListId": "pl_gcc_2025_09",
    "name": "GCC Master (2025‑09)",
    "region": "GCC",
    "currency": "AED",
    "taxPolicyId": "tax_gcc_v1",
    "effectiveFrom": "2025-09-01",
    "isActive": true
  },
  "taxPolicy": {
    "taxPolicyId": "tax_gcc_v1",
    "region": "GCC",
    "classes": [
      { "taxClass": "hardware_standard", "ratePct": 5, "inclusive": false },
      { "taxClass": "software", "ratePct": 0, "inclusive": false },
      { "taxClass": "delivery", "ratePct": 5, "inclusive": false }
    ]
  }
}