From dc8a04f6e7256517d2ff5be4e0161eeac5bf01ca Mon Sep 17 00:00:00 2001 From: Corrin Lakeland Date: Sat, 1 Aug 2026 22:04:45 +1200 Subject: [PATCH 1/3] fix: stop the Finish tab prescribing business process it invented MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An audit of user-facing strings in the last release found the Finish Job copy asserting processes KAN-323 never specified. Corrections, per the office's actual rules: - Over-invoiced is a diagnostic, not advice: a credit note is only one possible resolution — the job value being out of date is another — so the warning now names the fact and both likely causes, prescribing neither. - The load-error panel no longer instructs staff not to quote the customer; the T&M "get them right before invoicing" paragraph is deleted outright (the ticket forbids the checklist area nagging staff). - Timesheets only exist to invoice on a T&M job, so a quoted job is no longer asked "Have you collected the timesheet entries?". - "Has the job been released?" becomes "Has the job been handed over?", matching the field's meaning (collection, delivery, or on-site install). KAN-323 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf --- frontend/src/components/job/JobFinishTab.vue | 31 +++++++++---------- .../__tests__/JobFinishTab.checklist.test.ts | 28 ++++++----------- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/job/JobFinishTab.vue b/frontend/src/components/job/JobFinishTab.vue index 6dcd10913..7f2f17154 100644 --- a/frontend/src/components/job/JobFinishTab.vue +++ b/frontend/src/components/job/JobFinishTab.vue @@ -32,9 +32,7 @@ class="bg-white rounded-lg border border-red-200 p-6 text-center" >

Could not load this job's financials.

-

- Reload the page. Do not quote the customer a figure until it loads. -

+

Reload the page.

@@ -375,14 +367,21 @@ const checklist = reactive({ }) const savingChecklistKey = ref(null) -const checklistItems: Array<{ key: ChecklistItemKey; label: string }> = [ +const allChecklistItems: Array<{ key: ChecklistItemKey; label: string }> = [ { key: 'foreman_signed_off', label: 'Has the foreman signed off the job?' }, { key: 'timesheets_collected', label: 'Have you collected the timesheet entries?' }, { key: 'materials_checked', label: 'Have you checked the materials on the job?' }, { key: 'customer_called', label: 'Have you called the customer?' }, - { key: 'released', label: 'Has the job been released?' }, + { key: 'released', label: 'Has the job been handed over?' }, ] +// Timesheets only exist to invoice on a T&M job; a quoted job is never asked. +const checklistItems = computed(() => + props.pricingMethodology === 'time_materials' + ? allChecklistItems + : allChecklistItems.filter((item) => item.key !== 'timesheets_collected'), +) + const jobValueLabel = computed(() => props.pricingMethodology === 'fixed_price' ? 'Quote total (excl GST)' diff --git a/frontend/src/components/job/__tests__/JobFinishTab.checklist.test.ts b/frontend/src/components/job/__tests__/JobFinishTab.checklist.test.ts index c91bb1450..499392bac 100644 --- a/frontend/src/components/job/__tests__/JobFinishTab.checklist.test.ts +++ b/frontend/src/components/job/__tests__/JobFinishTab.checklist.test.ts @@ -60,33 +60,23 @@ describe('JobFinishTab completion checklist', () => { afterEach(resetFinishTab) - it('asks the same five questions whatever the pricing methodology', async () => { - for (const methodology of ['fixed_price', 'time_materials']) { - const wrapper = mountTab(methodology) - await flushPromises() - - for (const key of ITEMS) { - expect(item(wrapper, key).exists(), `${methodology}/${key}`).toBe(true) - } - - resetFinishTab() - } - }) - - it('warns that time and materials are what a T&M customer pays', async () => { + it('asks all five questions on a T&M job', async () => { const wrapper = mountTab('time_materials') await flushPromises() - expect(wrapper.find('[data-automation-id="JobFinishTab-tm-urgency"]').text()).toContain( - 'before invoicing', - ) + for (const key of ITEMS) { + expect(item(wrapper, key).exists(), key).toBe(true) + } }) - it('does not show the T&M urgency note on a quoted job', async () => { + it('does not ask about timesheets on a quoted job', async () => { const wrapper = mountTab('fixed_price') await flushPromises() - expect(wrapper.find('[data-automation-id="JobFinishTab-tm-urgency"]').exists()).toBe(false) + expect(item(wrapper, 'timesheets_collected').exists()).toBe(false) + for (const key of ITEMS.filter((k) => k !== 'timesheets_collected')) { + expect(item(wrapper, key).exists(), key).toBe(true) + } }) it('reflects ticks already recorded on the job', async () => { From 9ded41f64f01eed4bbe29f17fd11176f1a88822a Mon Sep 17 00:00:00 2001 From: Corrin Lakeland Date: Sat, 1 Aug 2026 22:15:37 +1200 Subject: [PATCH 2/3] fix: drop "Never blocks invoicing" from the checklist subtitle True, but not something the office needs telling; the behaviour is pinned by a test, not by copy. The subtitle keeps "Recorded against the job." so staff know ticks persist. KAN-323 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf --- frontend/src/components/job/JobFinishTab.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/job/JobFinishTab.vue b/frontend/src/components/job/JobFinishTab.vue index 7f2f17154..c34a87c7d 100644 --- a/frontend/src/components/job/JobFinishTab.vue +++ b/frontend/src/components/job/JobFinishTab.vue @@ -119,7 +119,7 @@ class="bg-white rounded-xl border border-slate-200 p-4" >

Completion checklist

-

Recorded against the job. Never blocks invoicing.

+

Recorded against the job.