Files
2026-06-15 10:18:46 +02:00

8.2 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
12-offer-composition-drag-drop-csv-import 03 api
drizzle
postgres
zod
server-actions
next.js
phase provides
12-offer-composition-drag-drop-csv-import (Plan 01) Additive offer_macros/offer_micros columns (category, ticket, is_archived, transformation-promise fields, tier_letter, public_price) and offer_tier_services junction table, applied to prod via Plan 02
getOfferEditorData(macroId) — full editor data shape (macro fields, A/B/C tiers with assignedServiceIds + computed servicesTotal, category-filtered availableServices, tipoTags/obiettivoTags)
getOfferListCards() — list-page cards with category + archive status
getOfferFieldOptions() — categoria/ticket/tipo/obiettivo select pools
saveOfferEditor(macroId, payload) — single-call persistence of macro scalars, per-tier upsert + offer_tier_services replace, and Tipo/Obiettivo tag replace
toggleOfferArchived, addOfferTag, removeOfferTag, renameOfferOption, createOfferMacro server actions
12-04-offer-list-page
12-05-offer-editor-page
added patterns
Polymorphic tags table reused for offer_macros Tipo/Obiettivo dimensions via entity_type = 'offer_macros.tipo' | 'offer_macros.obiettivo', mirroring the Phase 11 services/leads D-06 pattern
Computed servicesTotal via coalesce(sum(unit_price::numeric), 0) joined through offer_tier_services -> services, mirroring getCatalogWithMicros's cumulMap pattern
Delete-then-reinsert for many-to-many replacement (offer_tier_services per tier, Tipo/Obiettivo tags per macro), mirroring updateMicroOfferServices
Zod enum tier_letter validation (A/B/C) as defense-in-depth alongside the Plan 01 DB CHECK constraint
created modified
scripts/verify-12-03-queries.ts
scripts/verify-12-03-actions.ts
src/lib/offer-queries.ts
src/app/admin/offers/actions.ts
No test runner configured (no scripts.test in package.json) — wrote typecheck-only verification scripts documenting the 14 spec'd test cases (5 query + 9 action), per plan's explicit fallback; not executed against prod DB
saveOfferEditor uses sequential awaits (no transaction wrapper) per macro update -> tier upserts -> tag replace, matching the existing per-statement pattern in this codebase (no interactive tx helper elsewhere)
OFFER_TIPO_ENTITY/OFFER_OBIETTIVO_ENTITY constants ('offer_macros.tipo'/'offer_macros.obiettivo') as the canonical entity_type values for offer-level tag dimensions — reuse in Plans 04/05 UI
OFFER-11
OFFER-15
OFFER-16
OFFER-17
OFFER-18
12min 2026-06-15

Phase 12 Plan 03: Offer Editor Query Layer & Server Actions Summary

Query layer (getOfferEditorData, getOfferListCards, getOfferFieldOptions) and server actions (saveOfferEditor + archive/tag/rename/create actions) giving Plans 04/05 a complete, type-safe, admin-only data contract for the offer editor.

Performance

  • Duration: 12 min
  • Started: 2026-06-15T08:15:00Z (approx)
  • Completed: 2026-06-15T08:17:07Z
  • Tasks: 2 completed
  • Files modified: 4 (2 source, 2 new verification scripts)

Accomplishments

  • Added getOfferListCards() to src/lib/offer-queries.ts: returns { id, internal_name, description, category, is_archived } per offer_macros row, ordered by sort_order, including archived offers (client-side filter per UI-SPEC)
  • Added getOfferEditorData(macroId): returns { macro, tiers, availableServices, tipoTags, obiettivoTags } — tiers ordered A→B→C via SQL CASE expression, each with assignedServiceIds and a computed servicesTotal (coalesce(sum(unit_price::numeric), 0) joined through offer_tier_services), available services pre-filtered by services.category === macro.category (D-4, best-effort when category unset)
  • Added getOfferFieldOptions(): { categoria, ticket, tipo, obiettivo } distinct-value pools, mirroring getCatalogFieldOptions's null-filter + alphabetical sort pattern
  • Added saveOfferEditor(macroId, payload) to src/app/admin/offers/actions.ts: Zod-validates the full editor payload (tier_letter restricted to ["A","B","C"]), updates offer_macros scalars, upserts each tier (update if id present, insert + .returning({id}) if not), delete-then-reinserts offer_tier_services per tier, and delete-then-reinserts Tipo/Obiettivo tags
  • Added toggleOfferArchived, addOfferTag/removeOfferTag (tipo/obiettivo dimensions), renameOfferOption (categoria/ticket/tipo/obiettivo), and createOfferMacro (Plan 04 "+ Nuova Offerta" target, defaults public_name to internal_name)
  • All new actions call requireAdmin() first; existing exports in both files left untouched

Task Commits

Each task was committed atomically:

  1. Task 1: Query layer — getOfferEditorData, getOfferListCards, getOfferFieldOptions - 0d742f2 (feat)
  2. Task 2: Server actions — saveOfferEditor, toggleOfferArchived, offer tag CRUD, createOfferMacro - a372c61 (feat)

Plan metadata: (this commit, follows)

Files Created/Modified

  • src/lib/offer-queries.ts - Added getOfferListCards, getOfferEditorData, getOfferFieldOptions + OfferListCard/OfferTierData/OfferEditorData/OfferFieldOptions types; imports offer_tier_services, services, tags, and from drizzle-orm
  • src/app/admin/offers/actions.ts - Added saveOfferEditor, toggleOfferArchived, addOfferTag, removeOfferTag, renameOfferOption, createOfferMacro + SaveOfferEditorPayload/tierSchema/saveOfferEditorSchema types; imports offer_tier_services, tags, and, inArray
  • scripts/verify-12-03-queries.ts - Typecheck-only documentation of the 5 query-layer test cases (Tests 1-5 from Task 1's <behavior>)
  • scripts/verify-12-03-actions.ts - Typecheck-only documentation of the 9 server-action test cases (Tests 1-9 from Task 2's <behavior>)

Decisions Made

  • No test runner configured in this repo (package.json has no scripts.test), and .env.local's DATABASE_URL points at production per project memory — followed the plan's explicit fallback and wrote scripts/verify-12-03-queries.ts / scripts/verify-12-03-actions.ts as typecheck-only documentation of all 14 spec'd test cases, not executed against the live DB
  • saveOfferEditor uses sequential awaits (macro update → per-tier upsert/replace → tag replace) with no transaction wrapper, matching the existing per-statement pattern used by updateMicroOfferServices and the rest of this codebase

Deviations from Plan

None - plan executed exactly as written. Both tasks implemented the documented function signatures, return shapes, and validation rules verbatim; existing exports in offer-queries.ts and actions.ts were not modified.

Issues Encountered

None.

User Setup Required

None - no external service configuration required. This plan is pure source-code (query/action functions); migration 0008 was already live on production (verified per Plan 02), and no data-mutating scripts were run.

Next Phase Readiness

  • Plan 04 (offer list page) can call getOfferListCards() and createOfferMacro for the "+ Nuova Offerta" button
  • Plan 05 (offer editor page) can call getOfferEditorData(macroId) to populate the full editor (macro fields, tier matrix with live totals, Tipo/Obiettivo tags, category-filtered service catalog) and persist all of it via a single saveOfferEditor(macroId, payload) call; archive toggling via toggleOfferArchived; tag/option CRUD via addOfferTag/removeOfferTag/renameOfferOption
  • npx tsc --noEmit passes with zero errors across the full project
  • No legacy offer_micro_services/offer_services/existing-export code paths were touched

Phase: 12-offer-composition-drag-drop-csv-import Completed: 2026-06-15

Self-Check: PASSED

All created/modified files verified present on disk:

  • FOUND: src/lib/offer-queries.ts
  • FOUND: src/app/admin/offers/actions.ts
  • FOUND: scripts/verify-12-03-queries.ts
  • FOUND: scripts/verify-12-03-actions.ts
  • FOUND: .planning/phases/12-offer-composition-drag-drop-csv-import/12-03-SUMMARY.md

All task commits verified present in git log: