Merge branch 'dev'

This commit is contained in:
Marshal
2026-08-12 06:44:30 +00:00
94 changed files with 5399 additions and 3583 deletions

View File

@@ -111,11 +111,14 @@ export interface ConsolidationDetails {
splitBilling: { bookingShare: number; partnerShare: number } | null;
}
/**
* No stamp field: the backoffice only ever signs as STAFF, and EDR's seal is
* the ONE global company stamp applied server-side. Customer stamps are posted
* from the portal, not here.
*/
export interface SignContractPayload {
role: "CUSTOMER" | "STAFF";
signatureImageBase64: string;
/** Company stamp/seal image; the API requires one for CUSTOMER and STAFF. */
stampImageBase64?: string;
signerDisplayName: string;
consentText?: string;
}

View File

@@ -118,11 +118,14 @@ export interface ContractView {
} | null;
}
/**
* No stamp field: the backoffice only ever counter-signs as STAFF, and EDR's
* seal is the ONE global company stamp, snapshotted server-side from
* StampSettingsService when the signature is stored.
*/
export interface SignContractPayload {
role: "CUSTOMER" | "STAFF";
signatureImageBase64: string;
/** Company stamp/seal image; required to sign a contract. */
stampImageBase64?: string;
signerDisplayName: string;
consentText?: string;
}

View File

@@ -24,7 +24,7 @@ const cleanParams = (params: object) =>
),
);
/** Lift attributes JSONB into the flat contact/manager fields the UI reads. */
/** Lift attributes JSONB into the flat contact/owner fields the UI reads. */
function mapCompany(dto: Record<string, unknown>): Company {
const attrs = (dto.attributes as Record<string, unknown> | null) ?? {};
return {
@@ -32,9 +32,9 @@ function mapCompany(dto: Record<string, unknown>): Company {
companyProfiles: (dto.companyProfiles as Company["companyProfiles"]) ?? [],
contactPersonName: (attrs.contactPersonName as string | null) ?? null,
contactPersonPhone: (attrs.contactPersonPhone as string | null) ?? null,
generalManagerName: (attrs.generalManagerName as string | null) ?? null,
generalManagerEmail: (attrs.generalManagerEmail as string | null) ?? null,
generalManagerPhone: (attrs.generalManagerPhone as string | null) ?? null,
ownerName: (attrs.ownerName as string | null) ?? null,
ownerEmail: (attrs.ownerEmail as string | null) ?? null,
ownerPhone: (attrs.ownerPhone as string | null) ?? null,
poaName: (attrs.poaName as string | null) ?? null,
poaEmail: (attrs.poaEmail as string | null) ?? null,
poaPhone: (attrs.poaPhone as string | null) ?? null,

View File

@@ -9,12 +9,16 @@ export interface SavedSignature {
stampImageUrl?: string | null;
}
/**
* Signature only. A backoffice employee has no personal stamp — EDR seals with
* the one global company stamp managed under Settings — so the stamp half of
* PUT /me/signature is deliberately not exposed here, even though the shared
* endpoint still accepts it for portal (customer) users.
*/
export interface SaveSignaturePayload {
signerDisplayName: string;
/** Omit to keep the existing saved signature (stamp-only update). */
/** Omit to keep the existing saved signature. */
signatureImageBase64?: string;
/** Omit to keep the existing saved stamp. */
stampImageBase64?: string;
}
export const signaturesService = {

View File

@@ -0,0 +1,31 @@
import { api as client } from "../auth/http";
import { unwrap } from "@/utils/endpoint";
import type { ApiResponse } from "@/types/apiResponse";
const BASE = "/stamp-settings";
/** Company stamp/seal used on generated invoice/receipt PDFs. */
export interface StampSettings {
stampImageUrl: string | null;
updatedById: string | null;
updatedAt: string | null;
}
export const stampSettingsService = {
get: async (): Promise<StampSettings> => {
const response = await client.get<ApiResponse<StampSettings>>(BASE);
return unwrap(response.data);
},
set: async (stampImageBase64: string): Promise<StampSettings> => {
const response = await client.put<ApiResponse<StampSettings>>(BASE, {
stampImageBase64,
});
return unwrap(response.data);
},
clear: async (): Promise<StampSettings> => {
const response = await client.delete<ApiResponse<StampSettings>>(BASE);
return unwrap(response.data);
},
};