Skip to content

feat(invoicing): TAM-6897: fixed-price medications spec and test cases#10109

Open
tcaiger wants to merge 3 commits into
mainfrom
feat/tam-6897-fixed-price-medications
Open

feat(invoicing): TAM-6897: fixed-price medications spec and test cases#10109
tcaiger wants to merge 3 commits into
mainfrom
feat/tam-6897-fixed-price-medications

Conversation

@tcaiger

@tcaiger tcaiger commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Changes

Adds the spec and verification checklist for fixed-price medications (TAM-6897): flag a medication in a price list so it is charged as a flat fee regardless of dispensed quantity, instead of price × quantity.

This PR is docs onlydocs/specs/invoicing/fixed-price-medications.md (acceptance criteria + technical design) and docs/specs/invoicing/fixed-price-medications-test-cases.md (test checklist). It captures the resolved import/export syntax (per-cell f marker, :fixed column default), the pricing/discount/insurance behaviour, and scope boundaries ahead of implementation. No code changes.

Auto-Deploy

  • Deploy
Options
  • Artillery load test
  • Seed from closest snapshot
  • Generate fake data
  • More data (20Gi)
  • No facility servers (central-only)
  • No sync (facility tasks scaled to zero)
  • AMD64 architecture (default is arm64)
  • Skip mobile build
  • Always build mobile
  • Stay up for 8 hours
  • Stay up for 24 hours
  • Stay up (no TTL)
  • Build images only (don't deploy)
  • Pause this deploy

Tests

  • Run E2E tests
  • Run DAST scan

Review Hero

  • Run Review Hero
  • Auto-fix review suggestions Wait for Review Hero to finish, resolve any comments you disagree with or want to fix manually, then check this to auto-fix the rest.
  • Auto-fix CI failures Check this to auto-fix lint errors, test failures, and other CI issues.
  • Auto-merge upstream Check this to merge the base branch into this PR, with AI conflict resolution if needed.
  • Save suppressions Check this to capture 👎 reactions on Review Hero comments as suppression rules in .github/review-hero/suppressions.yml. Also runs automatically at the end of any auto-fix run.

Remember to...

  • ...write or update tests
  • ...add UI screenshots and testing notes to the Linear issue
  • ...add any manual upgrade steps to the Linear issue
  • ...update the config reference, settings reference, or any relevant runbook(s)
  • ...call out additions or changes to config files for the deployment team to take note of

@review-hero

review-hero Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦸 Review Hero Summary
2 agents reviewed this PR | 1 failed | 0 critical | 0 suggestions | 0 nitpicks | Filtering: consensus 3 voters, 3 below threshold

No issues found. Looks good!

Below consensus threshold (3 unique issues not confirmed by majority)
Location Agent Severity Comment
docs/specs/invoicing/fixed-price-medications-test-cases.md:22 Bugs & Correctness suggestion The test case 'A medication line with isFixedPrice = true but no invoicePriceListItem price falls through to 0' is self-contradictory. isFixedPrice lives on InvoicePriceListItem, so if ther...
docs/specs/invoicing/fixed-price-medications-test-cases.md:27 Bugs & Correctness suggestion There is no test case for a fixed-amount discount that exceeds the fixed fee (e.g. a $5 fixed-amount discount on a $2.00 fixed line). The existing getInvoiceItemTotalDiscountedPrice (invoiceItem.ts...
docs/specs/invoicing/fixed-price-medications.md:95 Bugs & Correctness suggestion The technical design says the check is 'gated on ... invoicePriceListItem.isFixedPrice', but the existing Product type in packages/utils/src/invoice/types.ts has no category field and invoicePric...

@tcaiger tcaiger requested a review from a team as a code owner June 22, 2026 03:23
@review-hero

review-hero Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦸 Review Hero (could not post inline comments — showing here instead)

packages/central-server/app/admin/referenceDataImporter/invoicePriceListItemLoaderFactory.js:52

[BES Requirements] suggestion

The couldNotFindParentId message is now dead code — the refactored ProductMatrixByCodeLoaderFactory filters unresolved columns during initialisation and never reaches a "could not find parent ID" path at row-processing time. Remove it to avoid confusion.


packages/facility-server/app/routes/apiv1/invoice/invoices.js:57

[BES Requirements] suggestion

The response shape of the /price-list-item endpoint changed: previously it returned the Sequelize model instance (with id and price), now it returns a hand-built { price, isFixedPrice, category }. If any other consumer (e.g. mobile, tests, external integrations) relied on additional fields from the model instance, this is a breaking change. Consider whether this endpoint has other callers beyond InvoiceItem.jsx.


docs/specs/invoicing/fixed-price-medications-plan.md:7

[BES Requirements] suggestion

Plan says "no TypeORM migration" because invoicing has no mobile models. However, the test-cases doc (line 77) says "The isFixedPrice column is added on InvoicePriceListItem (server) with a matching mobile migration" — these contradict each other. The plan is correct (no mobile model), so the test-case doc line should be updated to remove the mobile migration reference to avoid confusion.


packages/central-server/app/admin/referenceDataImporter/invoicePriceListItemLoaderFactory.js:37

[BES Requirements] suggestion

The HIDDEN comparison changed from value === HIDDEN (exact match) to raw.toLowerCase() === HIDDEN (case-insensitive after trim). This subtly changes behaviour: previously Hidden or HIDDEN (uppercase) would NOT match; now they do. If this is intentional, the corresponding export and test should verify the round-trip with mixed-case hidden values. If unintentional, keep the original exact match to avoid accepting unexpected input.


packages/facility-server/app/routes/apiv1/invoice/invoices.js:56

[Design & Architecture] suggestion

The /price-list-item endpoint now does an extra InvoiceProduct.findByPk query to return the product's category, mixing product concerns into what was a price-list-item lookup. The frontend already has a product object when it selects a product (it picked the product to trigger this call) — if category were eagerly loaded on the product the form already holds, this extra round-trip and coupling would be unnecessary. As more product fields are needed for client-side calc, this endpoint risks becoming a catch-all. Consider loading the category on the product at form init instead.


packages/utils/src/invoice/invoiceItem.ts:24

[Design & Architecture] suggestion

isFixedPriceItem silently returns false when product.category is not loaded, causing a fixed-price medication to be charged per-unit (e.g. $60 instead of $2). The optional-chaining fallback is correct for genuinely absent data (ad-hoc items), but indistinguishable from a missing eager-load — a future attributes restriction on the InvoiceProduct include would silently break billing with no type or runtime error. Consider a diagnostic: when product.invoicePriceListItem?.isFixedPrice is truthy but product.category is undefined, that's almost certainly a loading bug, not a real non-drug product — logging a warning there would catch misconfigured includes before they reach production invoices.


packages/central-server/app/admin/referenceDataImporter/ProductMatrixByCodeLoaderFactory.js:41

[Design & Architecture] suggestion

The refactoring from codes[] + parentIdCache to columns[] silently changes error behaviour for downstream consumers: previously an unresolved column header produced a per-row couldNotFindParentId error on every data row (visible in the test change from errored: 2 to errored: 1); now only the header-level error fires and rows for that column are skipped. This is better UX, but it's a behavioural change to a generic factory also used by non-invoice importers — confirm the other consumer (InvoiceInsurancePlanItem or similar) doesn't rely on the per-row error count or message for its own validation/reporting.

@review-hero

review-hero Bot commented Jun 22, 2026

Copy link
Copy Markdown

🦸 Review Hero Summary
12 agents reviewed this PR | 0 critical | 7 suggestions | 0 nitpicks | Filtering: consensus 3 voters, 9 below threshold, 1 suppressed

Below consensus threshold (9 unique issues not confirmed by majority)
Location Agent Severity Comment
docs/specs/invoicing/fixed-price-medications-plan.md:1 Design & Architecture nitpick This implementation plan contains ephemeral content — task checkboxes, Status: Implemented on feat/tam-6897-..., follow-up items, and branch references — that will be stale the moment the PR merg...
packages/central-server/app/admin/exporter/modelExporters/InvoicePriceListItemExporter.js:19 Bugs & Correctness critical If isFixedPrice is true but price is null (the column is nullable), the template literal ${FIXED_PREFIX}${price} produces the string "fnull", which will fail validation on re-import ("fnull...
packages/central-server/app/admin/referenceDataImporter/invoicePriceListItemLoaderFactory.js:37 BES Requirements nitpick let isFixedPrice and let numericPart are reassigned through a conditional — per project style (let is a smell), consider extracting a small function that returns `{ isFixedPrice, numericPart ...
packages/central-server/app/admin/referenceDataImporter/invoicePriceListItemLoaderFactory.js:41 Bugs & Correctness suggestion Number(numericPart) accepts "Infinity" and "-Infinity" as valid (non-NaN) values, so a cell like fInfinity would pass validation and store Infinity as a price. Add `Number.isFinite(parsedVa...
packages/central-server/app/admin/referenceDataImporter/ProductMatrixByCodeLoaderFactory.js:73 Bugs & Correctness suggestion Duplicate-column detection checks raw header strings, not resolved parent codes. Two different headers that resolve to the same parent (e.g. KOSRAE as a literal match and KOSRAE:fixed after tok...
packages/facility-server/app/routes/apiv1/invoice/invoices.js:434 Integration tests suggestion The finalisation route now snapshots isFixedPriceFinal via isFixedPriceItem(item), but the existing integration test at Invoice.test.js:136 ('should freeze invoice item product details when f...
packages/utils/src/invoice/invoiceItem.ts:18 Design & Architecture nitpick The 5-line JSDoc on isFixedPriceItem restates the spec and the code itself. Per project coding rules ("Default to writing no comments… Only add one when the WHY is non-obvious"), the finalisation...
packages/utils/src/invoice/invoiceItem.ts:25 Bugs & Correctness critical isFixedPriceItem doesn't protect finalised per-unit lines. When isFixedPriceFinal is false (item was per-unit at finalisation), the function falls through to check the live `product.invoi...
packages/utils/src/invoice/invoiceItem.ts:37 BES Requirements nitpick Project convention prefers ?? over || (see important-project-rules.md). Since this line was rewritten, invoiceItem.quantity ?? 0 would be more consistent (and the || 0 on price at line ...
Local fix prompt (copy to your coding agent)

Fix these issues identified on the pull request. One commit per issue fixed.


packages/central-server/app/admin/referenceDataImporter/invoicePriceListItemLoaderFactory.js:52: The couldNotFindParentId message is now dead code — the refactored ProductMatrixByCodeLoaderFactory filters unresolved columns during initialisation and never reaches a "could not find parent ID" path at row-processing time. Remove it to avoid confusion.


packages/facility-server/app/routes/apiv1/invoice/invoices.js:57: The response shape of the /price-list-item endpoint changed: previously it returned the Sequelize model instance (with id and price), now it returns a hand-built { price, isFixedPrice, category }. If any other consumer (e.g. mobile, tests, external integrations) relied on additional fields from the model instance, this is a breaking change. Consider whether this endpoint has other callers beyond InvoiceItem.jsx.


docs/specs/invoicing/fixed-price-medications-plan.md:7: Plan says "no TypeORM migration" because invoicing has no mobile models. However, the test-cases doc (line 77) says "The isFixedPrice column is added on InvoicePriceListItem (server) with a matching mobile migration" — these contradict each other. The plan is correct (no mobile model), so the test-case doc line should be updated to remove the mobile migration reference to avoid confusion.


packages/central-server/app/admin/referenceDataImporter/invoicePriceListItemLoaderFactory.js:37: The HIDDEN comparison changed from value === HIDDEN (exact match) to raw.toLowerCase() === HIDDEN (case-insensitive after trim). This subtly changes behaviour: previously Hidden or HIDDEN (uppercase) would NOT match; now they do. If this is intentional, the corresponding export and test should verify the round-trip with mixed-case hidden values. If unintentional, keep the original exact match to avoid accepting unexpected input.


packages/facility-server/app/routes/apiv1/invoice/invoices.js:56: The /price-list-item endpoint now does an extra InvoiceProduct.findByPk query to return the product's category, mixing product concerns into what was a price-list-item lookup. The frontend already has a product object when it selects a product (it picked the product to trigger this call) — if category were eagerly loaded on the product the form already holds, this extra round-trip and coupling would be unnecessary. As more product fields are needed for client-side calc, this endpoint risks becoming a catch-all. Consider loading the category on the product at form init instead.


packages/utils/src/invoice/invoiceItem.ts:24: isFixedPriceItem silently returns false when product.category is not loaded, causing a fixed-price medication to be charged per-unit (e.g. $60 instead of $2). The optional-chaining fallback is correct for genuinely absent data (ad-hoc items), but indistinguishable from a missing eager-load — a future attributes restriction on the InvoiceProduct include would silently break billing with no type or runtime error. Consider a diagnostic: when product.invoicePriceListItem?.isFixedPrice is truthy but product.category is undefined, that's almost certainly a loading bug, not a real non-drug product — logging a warning there would catch misconfigured includes before they reach production invoices.


packages/central-server/app/admin/referenceDataImporter/ProductMatrixByCodeLoaderFactory.js:41: The refactoring from codes[] + parentIdCache to columns[] silently changes error behaviour for downstream consumers: previously an unresolved column header produced a per-row couldNotFindParentId error on every data row (visible in the test change from errored: 2 to errored: 1); now only the header-level error fires and rows for that column are skipped. This is better UX, but it's a behavioural change to a generic factory also used by non-invoice importers — confirm the other consumer (InvoiceInsurancePlanItem or similar) doesn't rely on the per-row error count or message for its own validation/reporting.

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

🍹 up on tamanu-on-k8s/bes/tamanu-on-k8s/feat-tam-6897-fixed-price-medications

Pulumi report
   Updating (feat-tam-6897-fixed-price-medications)

View Live: https://app.pulumi.com/bes/tamanu-on-k8s/feat-tam-6897-fixed-price-medications/updates/10

Downloading plugin random-4.19.0: starting
Downloading plugin random-4.19.0: done
Installing plugin random-4.19.0: starting
Installing plugin random-4.19.0: done

@ Updating....
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running 
@ Updating....
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read pulumi:pulumi:StackReference bes/k8s-core/tamanu-internal-main
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read pulumi:pulumi:StackReference bes/k8s-core/tamanu-internal-main
@ Updating....
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read kubernetes:core/v1:Namespace tamanu-feat-tam-6897-fixed-price-medications
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read pulumi:pulumi:StackReference bes/core/tamanu-internal
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read pulumi:pulumi:StackReference bes/core/tamanu-internal
@ Updating......
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read kubernetes:core/v1:Namespace tamanu-feat-tam-6897-fixed-price-medications
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Waiting for central-db...
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Waiting for facility-1-db...
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Waiting for facility-2-db...
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read kubernetes:core/v1:ConfigMap actual-provisioning
~  kubernetes:apps/v1:Deployment central-web updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment patient-portal-web updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment facility-1-web updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment facility-2-web updating (0s) [diff: ~spec]
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (0s) 
~  kubernetes:batch/v1:CronJob ttl-hibernate updating (0s) [diff: ~spec]
@ Updating....
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running read kubernetes:core/v1:ConfigMap actual-provisioning
++ kubernetes:batch/v1:Job central-migrator creating replacement (0s) [diff: ~spec]
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (0s) 
@ Updating....
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Secret facility-2-db-superuser not found or not ready: Error: HTTP-Code: 404
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Message: Unknown API Status Code!
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Body: "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"secrets \\\"facility-2-db-superuser\\\" not found\",\"reason\":\"NotFound\",\"details\":{\"name\":\"facility-2-db-superuser\",\"kind\":\"secrets\"},\"code\":404}
"
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Headers: {"audit-id":"2bad491b-ccf5-4b6c-be7a-6edfaba8c350","cache-control":"no-cache, private","connection":"close","content-length":"220","content-type":"application/json","date":"Fri, 26 Jun 2026 00:42:00 GMT","x-kubernetes-pf-flowschema-uid":"3fb296fc-e46b-45d1-9306-057e37ddd229","x-kubernetes-pf-prioritylevel-uid":"feccf24d-a074-4fa8-aa6f-db82477fc2f5"}
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Secret central-db-superuser not found or not ready: Error: HTTP-Code: 404
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Message: Unknown API Status Code!
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Body: "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"secrets \\\"central-db-superuser\\\" not found\",\"reason\":\"NotFound\",\"details\":{\"name\":\"central-db-superuser\",\"kind\":\"secrets\"},\"code\":404}
"
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Headers: {"audit-id":"9c6a79f1-4de3-470a-91b0-f09f875c3a78","cache-control":"no-cache, private","connection":"close","content-length":"214","content-type":"application/json","date":"Fri, 26 Jun 2026 00:42:00 GMT","x-kubernetes-pf-flowschema-uid":"3fb296fc-e46b-45d1-9306-057e37ddd229","x-kubernetes-pf-prioritylevel-uid":"feccf24d-a074-4fa8-aa6f-db82477fc2f5"}
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Secret facility-1-db-superuser not found or not ready: Error: HTTP-Code: 404
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Message: Unknown API Status Code!
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Body: "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"secrets \\\"facility-1-db-superuser\\\" not found\",\"reason\":\"NotFound\",\"details\":{\"name\":\"facility-1-db-superuser\",\"kind\":\"secrets\"},\"code\":404}
"
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications running Headers: {"audit-id":"e19db195-3a66-48dc-a535-d137b3fc9188","cache-control":"no-cache, private","connection":"close","content-length":"220","content-type":"application/json","date":"Fri, 26 Jun 2026 00:42:00 GMT","x-kubernetes-pf-flowschema-uid":"3fb296fc-e46b-45d1-9306-057e37ddd229","x-kubernetes-pf-prioritylevel-uid":"feccf24d-a074-4fa8-aa6f-db82477fc2f5"}
++ kubernetes:batch/v1:Job central-migrator creating replacement (0s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (0s) [diff: ~spec]
~  kubernetes:batch/v1:CronJob ttl-hibernate updating (1s) [diff: ~spec]; 
~  kubernetes:batch/v1:CronJob ttl-hibernate updated (1s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (0s) [diff: ~spec]
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (0s) [diff: ~spec]; 
@ Updating....
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (0s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-2-migrator-ee12a8de" to start
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (2s) Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/ttl-wake-1782448916-f9fb7cdb" to start
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (2s) Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/ttl-wake-1782448916-f9fb7cdb" to succeed (Active: 1 | Succeeded: 0 | Failed: 0)
++ kubernetes:batch/v1:Job central-migrator creating replacement (1s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-migrator-493ac599" to start
++ kubernetes:batch/v1:Job central-migrator creating replacement (1s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-migrator-493ac599" to succeed (Active: 1 | Succeeded: 0 | Failed: 0)
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (0s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-2-migrator-ee12a8de" to succeed (Active: 1 | Succeeded: 0 | Failed: 0)
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (0s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (0s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-1-migrator-c0626c82" to start
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (0s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-1-migrator-c0626c82" to succeed (Active: 1 | Succeeded: 0 | Failed: 0)
~  kubernetes:apps/v1:Deployment facility-2-web updating (3s) [diff: ~spec]; Waiting for app ReplicaSet to be available (0/1 Pods available)
~  kubernetes:apps/v1:Deployment facility-1-web updating (3s) [diff: ~spec]; Waiting for app ReplicaSet to be available (0/1 Pods available)
~  kubernetes:apps/v1:Deployment central-web updating (3s) [diff: ~spec]; Waiting for app ReplicaSet to be available (0/1 Pods available)
@ Updating....
~  kubernetes:apps/v1:Deployment patient-portal-web updating (3s) [diff: ~spec]; Waiting for app ReplicaSet to be available (0/1 Pods available)
@ Updating.....
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (5s) warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/ttl-wake-1782448916-f9fb7cdb-qngbk]: Container "wake-cnpg" completed with exit code 0
@ Updating.....
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (7s) Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/ttl-wake-1782448916-f9fb7cdb" to succeed (Active: 0 | Succeeded: 0 | Failed: 0)
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (8s) Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/ttl-wake-1782448916-f9fb7cdb" to succeed (Active: 0 | Succeeded: 1 | Failed: 0)
+  kubernetes:batch/v1:Job ttl-wake-1782448916 creating (8s) 
+  kubernetes:batch/v1:Job ttl-wake-1782448916 created (8s) 
@ Updating........
~  kubernetes:apps/v1:Deployment facility-1-web updating (13s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/facility-1-web-aaad49e7-54f6dcf857-l28vj]: containers with unready status: [http]
~  kubernetes:apps/v1:Deployment central-web updating (13s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/central-web-879aef0e-6b98b7975-dtwk5]: containers with unready status: [http]
@ Updating....
~  kubernetes:apps/v1:Deployment patient-portal-web updating (13s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/patient-portal-web-66484ffc-8549c88cc4-524km]: containers with unready status: [http]
@ Updating......
~  kubernetes:apps/v1:Deployment facility-1-web updating (16s) [diff: ~spec]; Waiting for app ReplicaSet to be available (1/2 Pods available)
~  kubernetes:apps/v1:Deployment patient-portal-web updating (16s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment patient-portal-web updating (16s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment patient-portal-web updated (16s) [diff: ~spec]; 
@ Updating........
~  kubernetes:apps/v1:Deployment facility-2-web updating (21s) [diff: ~spec]; Waiting for app ReplicaSet to be available (1/2 Pods available)
~  kubernetes:apps/v1:Deployment central-web updating (21s) [diff: ~spec]; Waiting for app ReplicaSet to be available (1/2 Pods available)
@ Updating....
~  kubernetes:apps/v1:Deployment facility-2-web updating (23s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/facility-2-web-d37cf97e-7f664b5b8b-9zkq4]: containers with unready status: [http]
~  kubernetes:apps/v1:Deployment facility-1-web updating (23s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/facility-1-web-aaad49e7-54f6dcf857-krl4j]: containers with unready status: [http]
~  kubernetes:apps/v1:Deployment central-web updating (23s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/central-web-879aef0e-6b98b7975-qxksz]: containers with unready status: [http]
@ Updating...........
~  kubernetes:apps/v1:Deployment facility-1-web updating (30s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-1-web updating (30s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-1-web updated (30s) [diff: ~spec]; 
@ Updating....
~  kubernetes:apps/v1:Deployment facility-2-web updating (31s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-2-web updating (31s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-2-web updated (31s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-web updating (32s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment central-web updating (32s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-web updated (32s) [diff: ~spec]; 
@ Updating......
++ kubernetes:batch/v1:Job central-migrator creating replacement (34s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/central-migrator-493ac599-544tx]: Container "migrator" completed with exit code 0
@ Updating....
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (34s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/facility-2-migrator-ee12a8de-bljzc]: Container "migrator" completed with exit code 0
@ Updating....
++ kubernetes:batch/v1:Job central-migrator creating replacement (36s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-migrator-493ac599" to succeed (Active: 0 | Succeeded: 0 | Failed: 0)
++ kubernetes:batch/v1:Job central-migrator creating replacement (36s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-migrator-493ac599" to succeed (Active: 0 | Succeeded: 1 | Failed: 0)
++ kubernetes:batch/v1:Job central-migrator creating replacement (36s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job central-migrator created replacement (36s) [diff: ~spec]; 
@ Updating....
+- kubernetes:batch/v1:Job central-migrator replacing (0s) [diff: ~spec]; 
+- kubernetes:batch/v1:Job central-migrator replaced (0.00s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job central-provisioner creating replacement (0s) [diff: ~spec]
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (35s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/facility-1-migrator-c0626c82-vts68]: Container "migrator" completed with exit code 0
++ kubernetes:batch/v1:Job central-provisioner creating replacement (0s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job central-provisioner creating replacement (0s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-provisioner-1bde14f6" to start
@ Updating....
++ kubernetes:batch/v1:Job central-provisioner creating replacement (0s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-provisioner-1bde14f6" to succeed (Active: 1 | Succeeded: 0 | Failed: 0)
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (36s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-2-migrator-ee12a8de" to succeed (Active: 0 | Succeeded: 0 | Failed: 0)
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (36s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-2-migrator-ee12a8de" to succeed (Active: 0 | Succeeded: 1 | Failed: 0)
++ kubernetes:batch/v1:Job facility-2-migrator creating replacement (36s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job facility-2-migrator created replacement (36s) [diff: ~spec]; 
+- kubernetes:batch/v1:Job facility-2-migrator replacing (0s) [diff: ~spec]; 
+- kubernetes:batch/v1:Job facility-2-migrator replaced (0.00s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-2-tasks updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment facility-2-sync updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment facility-2-api updating (0s) [diff: ~spec]
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (37s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-1-migrator-c0626c82" to succeed (Active: 0 | Succeeded: 0 | Failed: 0)
@ Updating....
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (37s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/facility-1-migrator-c0626c82" to succeed (Active: 0 | Succeeded: 1 | Failed: 0)
++ kubernetes:batch/v1:Job facility-1-migrator creating replacement (37s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job facility-1-migrator created replacement (37s) [diff: ~spec]; 
+- kubernetes:batch/v1:Job facility-1-migrator replacing (0s) [diff: ~spec]; 
+- kubernetes:batch/v1:Job facility-1-migrator replaced (0.00s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-1-tasks updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment facility-1-api updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment facility-1-sync updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment facility-2-tasks updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-2-tasks updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-2-tasks updated (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-2-sync updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-2-sync updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-2-sync updated (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-2-api updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-2-api updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-2-api updated (1s) [diff: ~spec]; 
@ Updating....
~  kubernetes:apps/v1:Deployment facility-1-tasks updating (0s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-1-tasks updating (0s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-1-tasks updated (0.93s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-1-sync updating (1s) [diff: ~spec]; warning: Replicas scaled to 0 for Deployment "facility-1-sync"
~  kubernetes:apps/v1:Deployment facility-1-sync updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-1-sync updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-1-sync updated (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-1-api updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment facility-1-api updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment facility-1-api updated (1s) [diff: ~spec]; 
@ Updating.........
++ kubernetes:batch/v1:Job central-provisioner creating replacement (9s) [diff: ~spec]; warning: [Pod tamanu-feat-tam-6897-fixed-price-medications/central-provisioner-1bde14f6-5vpr4]: Container "provisioner" completed with exit code 0
@ Updating.....
++ kubernetes:batch/v1:Job central-provisioner creating replacement (11s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-provisioner-1bde14f6" to succeed (Active: 0 | Succeeded: 0 | Failed: 0)
++ kubernetes:batch/v1:Job central-provisioner creating replacement (11s) [diff: ~spec]; Waiting for Job "tamanu-feat-tam-6897-fixed-price-medications/central-provisioner-1bde14f6" to succeed (Active: 0 | Succeeded: 1 | Failed: 0)
++ kubernetes:batch/v1:Job central-provisioner creating replacement (11s) [diff: ~spec]; 
++ kubernetes:batch/v1:Job central-provisioner created replacement (11s) [diff: ~spec]; 
@ Updating....
+- kubernetes:batch/v1:Job central-provisioner replacing (0s) [diff: ~spec]; 
+- kubernetes:batch/v1:Job central-provisioner replaced (0.00s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-fhir-refresh updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment central-tasks updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment central-api updating (0s) [diff: ~spec]
~  kubernetes:apps/v1:Deployment central-fhir-resolver updating (0s) [diff: ~spec]
@ Updating....
~  kubernetes:apps/v1:Deployment central-tasks updating (0s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment central-tasks updating (0s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-tasks updated (0.99s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-fhir-resolver updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment central-fhir-resolver updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-fhir-resolver updated (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-api updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment central-api updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-fhir-refresh updating (1s) [diff: ~spec]; Deployment initialization complete
~  kubernetes:apps/v1:Deployment central-fhir-refresh updating (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-api updated (1s) [diff: ~spec]; 
~  kubernetes:apps/v1:Deployment central-fhir-refresh updated (1s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job central-provisioner deleting original (0s) [diff: ~spec]; 
@ Updating....
-- kubernetes:batch/v1:Job central-provisioner deleting original (0s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job central-provisioner deleted original (0.55s) [diff: ~spec]; 
-  kubernetes:batch/v1:Job ttl-wake-1782442931 deleting (0s) 
-- kubernetes:batch/v1:Job facility-1-migrator deleting original (0s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job facility-2-migrator deleting original (0s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job central-migrator deleting original (0s) [diff: ~spec]; 
@ Updating....
-  kubernetes:batch/v1:Job ttl-wake-1782442931 deleted (0.37s) 
-- kubernetes:batch/v1:Job facility-2-migrator deleting original (0s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job facility-2-migrator deleted original (0.79s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job facility-1-migrator deleting original (0s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job facility-1-migrator deleted original (0.81s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job central-migrator deleting original (1s) [diff: ~spec]; 
-- kubernetes:batch/v1:Job central-migrator deleted original (1s) [diff: ~spec]; 
   pulumi:pulumi:Stack tamanu-on-k8s-feat-tam-6897-fixed-price-medications  15 messages
Diagnostics:
 pulumi:pulumi:Stack (tamanu-on-k8s-feat-tam-6897-fixed-price-medications):
   Waiting for central-db...
   Waiting for facility-1-db...
   Waiting for facility-2-db...

   Secret facility-2-db-superuser not found or not ready: Error: HTTP-Code: 404
   Message: Unknown API Status Code!
   Body: "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"secrets \\\"facility-2-db-superuser\\\" not found\",\"reason\":\"NotFound\",\"details\":{\"name\":\"facility-2-db-superuser\",\"kind\":\"secrets\"},\"code\":404}
"
   Headers: {"audit-id":"2bad491b-ccf5-4b6c-be7a-6edfaba8c350","cache-control":"no-cache, private","connection":"close","content-length":"220","content-type":"application/json","date":"Fri, 26 Jun 2026 00:42:00 GMT","x-kubernetes-pf-flowschema-uid":"3fb296fc-e46b-45d1-9306-057e37ddd229","x-kubernetes-pf-prioritylevel-uid":"feccf24d-a074-4fa8-aa6f-db82477fc2f5"}
   Secret central-db-superuser not found or not ready: Error: HTTP-Code: 404
   Message: Unknown API Status Code!
   Body: "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"secrets \\\"central-db-superuser\\\" not found\",\"reason\":\"NotFound\",\"details\":{\"name\":\"central-db-superuser\",\"kind\":\"secrets\"},\"code\":404}
"
   Headers: {"audit-id":"9c6a79f1-4de3-470a-91b0-f09f875c3a78","cache-control":"no-cache, private","connection":"close","content-length":"214","content-type":"application/json","date":"Fri, 26 Jun 2026 00:42:00 GMT","x-kubernetes-pf-flowschema-uid":"3fb296fc-e46b-45d1-9306-057e37ddd229","x-kubernetes-pf-prioritylevel-uid":"feccf24d-a074-4fa8-aa6f-db82477fc2f5"}
   Secret facility-1-db-superuser not found or not ready: Error: HTTP-Code: 404
   Message: Unknown API Status Code!
   Body: "{\"kind\":\"Status\",\"apiVersion\":\"v1\",\"metadata\":{},\"status\":\"Failure\",\"message\":\"secrets \\\"facility-1-db-superuser\\\" not found\",\"reason\":\"NotFound\",\"details\":{\"name\":\"facility-1-db-superuser\",\"kind\":\"secrets\"},\"code\":404}
"
   Headers: {"audit-id":"e19db195-3a66-48dc-a535-d137b3fc9188","cache-control":"no-cache, private","connection":"close","content-length":"220","content-type":"application/json","date":"Fri, 26 Jun 2026 00:42:00 GMT","x-kubernetes-pf-flowschema-uid":"3fb296fc-e46b-45d1-9306-057e37ddd229","x-kubernetes-pf-prioritylevel-uid":"feccf24d-a074-4fa8-aa6f-db82477fc2f5"}

Outputs:
   urls: {
       Central      : "https://central.feat-tam-6897-fixed-price-medications.cd.tamanu.app"
       Facility- 1  : "https://facility-1.feat-tam-6897-fixed-price-medications.cd.tamanu.app"
       Facility- 2  : "https://facility-2.feat-tam-6897-fixed-price-medications.cd.tamanu.app"
       PatientPortal: "https://portal.feat-tam-6897-fixed-price-medications.cd.tamanu.app"
   }

Resources:
   + 1 created
   ~ 15 updated
   - 1 deleted
   +-4 replaced
   21 changes. 73 unchanged

Duration: 1m0s

   

@tcaiger tcaiger force-pushed the epic-fsm-invoicing branch from 4890222 to a6afe59 Compare June 23, 2026 22:09
@tcaiger tcaiger changed the base branch from epic-fsm-invoicing to main June 23, 2026 22:10
…s of quantity

Flag a medication on a price list so it is charged as a flat fee (price x 1)
instead of price x quantity. Per-unit stays the default; only medications
honour the flag.

- Schema: is_fixed_price on invoice_price_list_items; is_fixed_price_final
  snapshot on invoice_items (set at finalisation so a finalised line stays
  fixed/per-unit independent of later price-list edits).
- Calc: single quantity-to-1 gate in getInvoiceItemTotalPrice via
  isFixedPriceItem; discount, insurance coverage, net cost and totals all
  inherit it. Finalised lines key off priceFinal so they can't be flipped.
- Import: per-cell `f` prefix (f2.00) and a code-first `:fixed` column default
  in the price-list matrix loader; literal headers always resolve first.
- Export: re-emits the per-cell `f` marker for a lossless round-trip.
- API: price-list-item endpoint returns isFixedPrice + product category so the
  form applies fixed pricing before save.
- dbt source models updated; specs under specs/invoicing/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tcaiger tcaiger force-pushed the feat/tam-6897-fixed-price-medications branch from bfa141a to fb6cba6 Compare June 25, 2026 22:16
tcaiger and others added 2 commits June 26, 2026 10:45
Temporary copy of the xlsx ESM fs-binding fix so reference-data export/import
works locally on this branch. The real fix is in its own PR (#10145); drop this
commit once that merges to main and reaches this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…at import

Reverses the earlier "store the flag on any product" behaviour. The importer now
checks the product category:
- an explicit `f`-prefixed cell on a non-medication is an import error
- a `:fixed` column default silently skips non-medication rows (imported as a
  plain per-unit price)

Threads the product into the matrix loader's valueExtractor and lets it return a
specific error message. Updates spec and import test cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant