feat(externally-signed): mark contract/reservation as signed without file
Step 4 second slice. Adds the "Mark as signed without file" action to
contract + reservation tabs per PRE-DEPLOY-PLAN § 1.5.14.
Service: `markExternallySigned(interestId, portId, docType, reason)`
flips the relevant doc-status column ('contract_doc_status' /
'reservation_doc_status' / 'eoi_doc_status') to 'signed', writes an
audit log entry with `metadata.type='externally_signed'` capturing
the optional reason, and fires the appropriate berth-rule trigger
(eoi_signed / contract_signed) so downstream automation (berth
status flips, notifications) treats it identically to a Documenso-
signed completion.
Route: POST /api/v1/interests/[id]/mark-externally-signed gated on
interests.edit. Validates docType against the canonical 3-value enum.
UI: <MarkExternallySignedDialog> AlertDialog with optional reason
textarea + per-docType copy. Wired into EmptyContractState and
EmptyReservationState empty-state buttons. The action sits alongside
"Upload draft for signing" and "Upload paper-signed copy" as a third
option for reps whose canonical paper lives elsewhere.
EOI not yet wired into a UI surface — the eoi flow already has a
full upload pipeline. Service supports it for completeness.
Followup: quick brochure/PDF download buttons + per-user reminder
digest schedule still pending in Step 4 backlog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { parseBody } from '@/lib/api/route-helpers';
|
||||
import { errorResponse, NotFoundError } from '@/lib/errors';
|
||||
import { markExternallySigned } from '@/lib/services/external-signing.service';
|
||||
|
||||
const bodySchema = z.object({
|
||||
docType: z.enum(['eoi', 'reservation', 'contract']),
|
||||
reason: z.string().trim().max(2000).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/v1/interests/[id]/mark-externally-signed
|
||||
*
|
||||
* Marks the named document type as signed without requiring a file
|
||||
* upload. Sets the relevant `*_doc_status` column to 'signed', writes
|
||||
* an audit log entry capturing the reason, and fires the appropriate
|
||||
* berth-rule trigger (eoi_signed / contract_signed) if any.
|
||||
*/
|
||||
export const POST = withAuth(
|
||||
withPermission('interests', 'edit', async (req, ctx, params) => {
|
||||
try {
|
||||
const interestId = params.id;
|
||||
if (!interestId) throw new NotFoundError('Interest');
|
||||
const input = await parseBody(req, bodySchema);
|
||||
const result = await markExternallySigned(
|
||||
{
|
||||
interestId,
|
||||
portId: ctx.portId,
|
||||
docType: input.docType,
|
||||
reason: input.reason ?? null,
|
||||
},
|
||||
{
|
||||
userId: ctx.userId,
|
||||
portId: ctx.portId,
|
||||
ipAddress: ctx.ipAddress,
|
||||
userAgent: ctx.userAgent,
|
||||
},
|
||||
);
|
||||
return NextResponse.json({ data: result });
|
||||
} catch (error) {
|
||||
return errorResponse(error);
|
||||
}
|
||||
}),
|
||||
);
|
||||
Reference in New Issue
Block a user