mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 21:15:41 +00:00
add reusable per-user contract signatures with profile prefill and approval
This commit is contained in:
@@ -23,6 +23,7 @@ import { ContractViewDto } from './dto/contract-view.dto';
|
||||
import { SignContractDto } from './dto/sign-contract.dto';
|
||||
import { ContractSignerRole } from './entities/booking-contract-signature.entity';
|
||||
import { BookingBatchService } from '../train-scheduling/booking-batch.service';
|
||||
import { SignaturesService } from '../signatures/signatures.service';
|
||||
|
||||
@Injectable()
|
||||
export class BookingContractService {
|
||||
@@ -38,6 +39,7 @@ export class BookingContractService {
|
||||
private readonly pdfService: ContractPdfService,
|
||||
@Inject(forwardRef(() => BookingBatchService))
|
||||
private readonly bookingBatchService: BookingBatchService,
|
||||
private readonly signaturesService: SignaturesService,
|
||||
) {}
|
||||
|
||||
buildContractSummary(booking: Booking): string {
|
||||
@@ -75,10 +77,16 @@ export class BookingContractService {
|
||||
return { summary };
|
||||
}
|
||||
|
||||
async getContractView(bookingId: string): Promise<ContractViewDto> {
|
||||
async getContractView(
|
||||
bookingId: string,
|
||||
viewerUserId?: string,
|
||||
): Promise<ContractViewDto> {
|
||||
const { view } = await this.viewModelBuilder.build(bookingId);
|
||||
await this.inlineSignatureImages(view.signatures);
|
||||
const html = this.renderer.render(view);
|
||||
const savedSignature = viewerUserId
|
||||
? ((await this.signaturesService.getForUser(viewerUserId)) ?? undefined)
|
||||
: undefined;
|
||||
return {
|
||||
bookingId: view.bookingId,
|
||||
reference: view.reference,
|
||||
@@ -90,6 +98,7 @@ export class BookingContractService {
|
||||
canSignStaff: view.canSignStaff,
|
||||
hasContractDocument: view.hasContractDocument,
|
||||
signatures: view.signatures,
|
||||
savedSignature,
|
||||
pricingSchedule: view.pricing as unknown as Record<string, unknown>,
|
||||
};
|
||||
}
|
||||
@@ -194,6 +203,23 @@ export class BookingContractService {
|
||||
ipAddress: options.ipAddress ?? null,
|
||||
});
|
||||
|
||||
// Persist the just-used signature to the signer's reusable profile so they
|
||||
// don't have to redraw it on the next contract. Best-effort: a failure here
|
||||
// must never block contract execution.
|
||||
if (options.signerUserId) {
|
||||
try {
|
||||
await this.signaturesService.upsertForUser({
|
||||
userId: options.signerUserId,
|
||||
signerDisplayName: dto.signerDisplayName,
|
||||
signatureImageBase64: dto.signatureImageBase64,
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`Could not save reusable signature for user ${options.signerUserId}: ${err}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const updates: Record<string, unknown> = {};
|
||||
|
||||
if (role === 'CUSTOMER') {
|
||||
|
||||
@@ -342,8 +342,12 @@ export class BookingsController {
|
||||
@Get(':id/contract/view')
|
||||
@ApiOkResponse({ type: ContractViewDto })
|
||||
@ApiOperation({ summary: 'Contract HTML view for portal and backoffice' })
|
||||
getContractView(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.contractService.getContractView(id);
|
||||
getContractView(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Request() req: { user?: { id?: string; sub?: string } },
|
||||
) {
|
||||
const userId = req.user?.id ?? req.user?.sub;
|
||||
return this.contractService.getContractView(id, userId);
|
||||
}
|
||||
|
||||
@Get(':id/contract/document')
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CompaniesModule } from '../companies/companies.module';
|
||||
import { FilesModule } from '../files/files.module';
|
||||
import { MinioModule } from '../minio/minio.module';
|
||||
import { RuleEngineModule } from '../rule-engine/rule-engine.module';
|
||||
import { SignaturesModule } from '../signatures/signatures.module';
|
||||
import { BookingContractService } from './booking-contract.service';
|
||||
import { BookingPaymentService } from './booking-payment.service';
|
||||
import { BookingPricingService } from './booking-pricing.service';
|
||||
@@ -50,6 +51,7 @@ import { CbeExchangeService } from '../cbe-exchange/cbe-exchange.service';
|
||||
CompaniesModule,
|
||||
// CustomersModule,
|
||||
RuleEngineModule,
|
||||
SignaturesModule,
|
||||
],
|
||||
controllers: [BookingsController, PayController],
|
||||
providers: [
|
||||
|
||||
@@ -14,6 +14,14 @@ export class ContractSignatureDto {
|
||||
signatureImageUrl?: string | null;
|
||||
}
|
||||
|
||||
export class SavedSignatureViewDto {
|
||||
@ApiProperty()
|
||||
signerDisplayName!: string;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
signatureImageUrl?: string | null;
|
||||
}
|
||||
|
||||
export class ContractViewDto {
|
||||
@ApiProperty()
|
||||
bookingId!: string;
|
||||
@@ -45,6 +53,9 @@ export class ContractViewDto {
|
||||
@ApiProperty({ type: [ContractSignatureDto] })
|
||||
signatures!: ContractSignatureDto[];
|
||||
|
||||
@ApiPropertyOptional({ type: SavedSignatureViewDto })
|
||||
savedSignature?: SavedSignatureViewDto;
|
||||
|
||||
@ApiPropertyOptional()
|
||||
pricingSchedule?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user