mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
add company stamp upload functionality for contract signing
- Introduced StampUpload component for uploading company stamp images. - Integrated stamp upload in contract signing modal, supporting PNG and JPG formats. - Implemented validation for file type and size (max 5 MB). - Added visual feedback for drag-and-drop functionality. - Updated contract-related pages to handle duplicate contract alerts and pricing notices. - Enhanced contract expiry management with a nightly sweep service. - Added unit tests for new features and updated existing tests for contract handling.
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
import { ContractRateSnapshot } from './entities/contract-rate-snapshot.entity';
|
||||
import { ContractReviewNote, ContractReviewNoteType } from './entities/contract-review-note.entity';
|
||||
import { ContractSignature, ContractSignerRole } from './entities/contract-signature.entity';
|
||||
import { TERMINAL_CONTRACT_STATUSES } from './utils/contract-expiry.util';
|
||||
|
||||
export interface ContractListFilterOptions {
|
||||
statuses?: string[];
|
||||
@@ -66,6 +67,46 @@ export class ContractsRepository extends BaseRepository<Contract> {
|
||||
return Number(row?.max ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-terminal contracts for the same company + service type, with routes
|
||||
* loaded — candidates for the duplicate-contract check on create(). Terminal
|
||||
* filtering happens in JS via isEffectivelyExpired (also covers the
|
||||
* date-passed-but-not-yet-cron-flipped case).
|
||||
*/
|
||||
async findDuplicateCandidates(
|
||||
companyId: string,
|
||||
serviceTypeId: string,
|
||||
): Promise<Contract[]> {
|
||||
return this.repository
|
||||
.createQueryBuilder('contract')
|
||||
.leftJoinAndSelect('contract.routes', 'routes')
|
||||
.where('contract.deleted_at IS NULL')
|
||||
.andWhere('contract.company_id = :companyId', { companyId })
|
||||
.andWhere('contract.service_type_id = :serviceTypeId', { serviceTypeId })
|
||||
.andWhere('contract.status NOT IN (:...terminal)', {
|
||||
terminal: TERMINAL_CONTRACT_STATUSES,
|
||||
})
|
||||
.getMany();
|
||||
}
|
||||
|
||||
/**
|
||||
* Nightly expiry sweep: flips lapsed contracts to EXPIRED. Returns the
|
||||
* number of rows updated (for cron logging).
|
||||
*/
|
||||
async expireLapsedContracts(): Promise<number> {
|
||||
const result = await this.repository
|
||||
.createQueryBuilder()
|
||||
.update(Contract)
|
||||
.set({ status: 'EXPIRED' })
|
||||
.where('deleted_at IS NULL')
|
||||
.andWhere('status NOT IN (:...terminal)', { terminal: TERMINAL_CONTRACT_STATUSES })
|
||||
.andWhere('contract_valid_until IS NOT NULL AND contract_valid_until < :now', {
|
||||
now: new Date(),
|
||||
})
|
||||
.execute();
|
||||
return result.affected ?? 0;
|
||||
}
|
||||
|
||||
/** Find a contract by ID with all child collections, service type, company and files. */
|
||||
async findByIdWithRelations(id: string): Promise<Contract | null> {
|
||||
if (!id) return null;
|
||||
@@ -434,7 +475,7 @@ export class ContractsRepository extends BaseRepository<Contract> {
|
||||
findSignatures(contractId: string): Promise<ContractSignature[]> {
|
||||
return this.dataSource.getRepository(ContractSignature).find({
|
||||
where: { contractId },
|
||||
relations: ['signatureFile'],
|
||||
relations: ['signatureFile', 'stampFile'],
|
||||
order: { signedAt: 'ASC' },
|
||||
});
|
||||
}
|
||||
@@ -445,7 +486,7 @@ export class ContractsRepository extends BaseRepository<Contract> {
|
||||
): Promise<ContractSignature | null> {
|
||||
return this.dataSource.getRepository(ContractSignature).findOne({
|
||||
where: { contractId, role },
|
||||
relations: ['signatureFile'],
|
||||
relations: ['signatureFile', 'stampFile'],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user