diff --git a/api/src/services/notifications.ts b/api/src/services/notifications.ts index f01cde9b..8b73997f 100644 --- a/api/src/services/notifications.ts +++ b/api/src/services/notifications.ts @@ -192,7 +192,7 @@ export const NOTIFICATION_KINDS = { }), work_order_renewed: (p) => ({ title: `Work Order ${p.code} is renewed as ${p.nextCode}`, - body: `Work Order ${p.code} on ${p.projectTitle} continues as ${p.nextCode} for ${p.period}, with the same terms.`, + body: `Work Order ${p.code} on ${p.projectTitle} continues as ${p.nextCode} for ${p.period}, with the same terms, once it's signed.`, }), work_order_renewal_needs_approval: (p) => ({ title: `Renewal of ${p.code} needs your decision`, diff --git a/api/src/services/work-orders/documents.ts b/api/src/services/work-orders/documents.ts index dfacae73..4bb021d5 100644 --- a/api/src/services/work-orders/documents.ts +++ b/api/src/services/work-orders/documents.ts @@ -68,7 +68,7 @@ export function documentFields(input: { repos: input.repos.join(", "), auto_renew: box(input.autoRenew), auto_renew_text: input.autoRenew - ? "This work order renews monthly, with the same terms, while the base metrics are met; otherwise the representative decides whether it renews." + ? "When the base metrics are met, a work order for the next month with the same terms is sent for signature; otherwise the representative decides whether to send one. Each renewal starts only once both parties sign it." : "", contractor_email: input.contractorEmail ?? "", representative_email: input.representativeEmail ?? "", diff --git a/api/src/services/work-orders/service.ts b/api/src/services/work-orders/service.ts index 0e15c119..27b70c0a 100644 --- a/api/src/services/work-orders/service.ts +++ b/api/src/services/work-orders/service.ts @@ -901,7 +901,7 @@ export function createWorkOrdersService(deps: WorkOrderDeps) { ["Work period", `${longDate(row.periodStart)} to ${longDate(row.periodEnd)}`], [ "Budget cap", - `${display(row.budgetAmount, row.tokenId)}${row.tokenId === USD ? ", paid in NEAR at the price when each payment is made" : ""}`, + `${display(row.budgetAmount, row.tokenId)}${row.tokenId === USD ? ", paid in NEAR at the price when each payment is started" : ""}`, ], ["Payout account (NEAR)", row.builderNearAccount], ["GitHub account", row.githubLogin ?? ""], @@ -911,13 +911,17 @@ export function createWorkOrdersService(deps: WorkOrderDeps) { [ "Monthly renewal", row.autoRenew - ? "Renews monthly, with the same terms, while the base metrics are met; otherwise the representative decides." + ? "A work order for the next month with the same terms is sent for signature when the base metrics are met; otherwise the representative decides." : "No", ], [ "How work counts", "Work counts once the repository owner approves or merges it on GitHub. The budget is a maximum, not a guarantee; each payment follows an approved compensation request.", ], + [ + "Governing agreement", + "This work order is governed by and incorporated into the services agreement between the builder and the Agency, which controls in a conflict.", + ], ]; } @@ -943,8 +947,9 @@ export function createWorkOrdersService(deps: WorkOrderDeps) { pdf = await termsPdf({ title: `Work Order ${row.code}`, rows: await termsRows(row), - footer: - "Signed electronically in MultiAgency: each party reviews this document and signs it with their typed name; the builder signs in with the NEAR account above.", + footer: deps.esign + ? "Signed electronically through Documenso: the Agency representative signs first, then the builder, each from their own signing link." + : "Signed electronically in MultiAgency: each party reviews this document and signs it with their typed name; the builder signs in with the NEAR account above.", }); } const sha256 = sha256Bytes(pdf); diff --git a/api/tests/integration/work-orders.test.ts b/api/tests/integration/work-orders.test.ts index c27ce0d4..47d491af 100644 --- a/api/tests/integration/work-orders.test.ts +++ b/api/tests/integration/work-orders.test.ts @@ -1380,6 +1380,8 @@ describe("work orders", () => { "carol-gh", REPO, "Rita Rep", + "Governing agreement", + "typed name", ]) { expect(text).toContain(part); } @@ -1419,6 +1421,9 @@ describe("work orders", () => { const pages = (await PDFDocument.load(doc.pdf)).getPageCount(); expect(doc.signers[0]?.signature.page).toBe(pages); expect(doc.signers[1]?.signature.y).toBeGreaterThan(doc.signers[0]?.signature.y ?? 100); + const text = await pdfText(doc.pdf.slice()); + expect(text).toContain("through Documenso"); + expect(text).not.toContain("typed name"); const offeredDoc = await service.document(await studio(), { id: created.id }); expect(offeredDoc.sha256).toBe(createHash("sha256").update(doc.pdf).digest("hex")); expect(events.map((e) => e.type)).not.toContain("work_order.offered"); diff --git a/ui/src/components/work-orders/renewal-panel.tsx b/ui/src/components/work-orders/renewal-panel.tsx index 6946e9a8..24535c01 100644 --- a/ui/src/components/work-orders/renewal-panel.tsx +++ b/ui/src/components/work-orders/renewal-panel.tsx @@ -45,8 +45,8 @@ export function RenewalPanel({

Monthly renewal

- Checked after {workOrder.periodEnd} + {workOrder.graceDays} grace days. Renews by itself - if every base metric is met; otherwise you decide. + Checked after {workOrder.periodEnd} + {workOrder.graceDays} grace days. If every base + metric is met, next month's work order is sent for signature; otherwise you decide. @@ -152,8 +152,8 @@ function DecideRenewal({ workOrder }: { workOrder: WorkOrder }) { Renewal needs your decision - Renew with the same terms, or let {workOrder.code} end here. The builder is told either - way. + Renew to send next month's work order for signature, or let {workOrder.code} end here. The + builder is told either way. Renew monthly when the base metrics are met - Each period is measured at its end. All met: next month's work order starts by - itself. Any missed: you decide. Part of the accepted terms. + Each period is measured at its end. All met: next month's work order is sent for + signature. Any missed: you decide. It starts once the representative and the + builder sign it.