diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml
index 1241ab7..40b7e2a 100644
--- a/.forgejo/workflows/deploy.yml
+++ b/.forgejo/workflows/deploy.yml
@@ -31,7 +31,13 @@ jobs:
pnpm build # includes prisma generate
pnpm db:migrate:deploy
- pm2 restart ppms --update-env
+ # NOT --update-env: this job runs inside the Forgejo Actions runner, whose
+ # environment includes an ephemeral FORGEJO_TOKEN (the per-job token, revoked
+ # when the job ends). --update-env would inject it into ppms, where it shadows
+ # the real PAT from .env (Next.js does not override an already-set process.env
+ # var) and breaks the Report Issue button once the job token expires. A plain
+ # restart re-execs ppms from the pm2 daemon's clean env, so .env wins.
+ pm2 restart ppms
echo "=== Deployed $TAG ==="
- name: Verify portal responds
diff --git a/App/components/po/po-line-items-editor.tsx b/App/components/po/po-line-items-editor.tsx
index 65f3c93..403ad7a 100644
--- a/App/components/po/po-line-items-editor.tsx
+++ b/App/components/po/po-line-items-editor.tsx
@@ -20,8 +20,11 @@ const UOM_OPTIONS = [
{ value: "mL", label: "mL — Millilitre" },
{ value: "m", label: "m — Metre" },
{ value: "m2", label: "m² — Sq. Metre" },
- { value: "hr", label: "hr — Hour" },
- { value: "day", label: "day — Day" },
+ { value: "hr", label: "hr — Hour" },
+ { value: "day", label: "day — Day" },
+ { value: "week", label: "week — Week" },
+ { value: "month", label: "month — Month" },
+ { value: "year", label: "year — Year" },
{ value: "lump", label: "lump — Lump Sum" },
{ value: "Ltr", label: "Ltr — Litre (alt)" },
];
diff --git a/App/tests/unit/po-line-items-editor.test.tsx b/App/tests/unit/po-line-items-editor.test.tsx
index 1d24a93..53eeb19 100644
--- a/App/tests/unit/po-line-items-editor.test.tsx
+++ b/App/tests/unit/po-line-items-editor.test.tsx
@@ -93,6 +93,25 @@ describe("LineItemsEditor — edit mode", () => {
const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0] as LineItemInput[];
expect(lastCall[0].gstRate).toBeCloseTo(0.05);
});
+
+ it("offers month and year as unit-of-measure options", () => {
+ render();
+ const selects = screen.getAllByRole("combobox") as HTMLSelectElement[];
+ const unitSelect = selects.find((s) => s.value === "pc")!;
+ const values = Array.from(unitSelect.options).map((o) => o.value);
+ expect(values).toContain("month");
+ expect(values).toContain("year");
+ });
+
+ it("calls onChange with the selected duration unit", async () => {
+ const onChange = vi.fn();
+ render();
+ const selects = screen.getAllByRole("combobox") as HTMLSelectElement[];
+ const unitSelect = selects.find((s) => s.value === "pc")!;
+ fireEvent.change(unitSelect, { target: { value: "year" } });
+ const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0] as LineItemInput[];
+ expect(lastCall[0].unit).toBe("year");
+ });
});
// ── Totals calculation (edit mode) ────────────────────────────────────────────