PSA/shared/workflow/streams/domainEventBuilders/contractEventBuilders.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

125 lines
3.6 KiB
TypeScript

const MS_PER_DAY = 24 * 60 * 60 * 1000;
export const DEFAULT_CONTRACT_RENEWAL_UPCOMING_WINDOW_DAYS = 90;
function toUtcMidnightDate(value: string | Date): Date {
if (value instanceof Date) {
return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()));
}
const trimmed = value.trim();
const dateOnly = trimmed.includes('T') ? trimmed.slice(0, 10) : trimmed;
return new Date(`${dateOnly}T00:00:00.000Z`);
}
export function computeContractRenewalUpcoming(params: {
renewalAt: string;
decisionDueAt?: string;
renewalCycleKey?: string;
now?: string | Date;
windowDays?: number;
}): {
renewalAt: string;
decisionDueDate: string;
daysUntilRenewal: number;
daysUntilDecisionDue: number;
renewalCycleKey?: string;
} | null {
const windowDays = params.windowDays ?? DEFAULT_CONTRACT_RENEWAL_UPCOMING_WINDOW_DAYS;
if (!Number.isInteger(windowDays) || windowDays < 0) return null;
const renewalDate = toUtcMidnightDate(params.renewalAt);
if (Number.isNaN(renewalDate.getTime())) return null;
const decisionDueDateRaw = params.decisionDueAt ?? params.renewalAt;
const decisionDueDate = toUtcMidnightDate(decisionDueDateRaw);
if (Number.isNaN(decisionDueDate.getTime())) return null;
const nowDate = toUtcMidnightDate(params.now ?? new Date());
const daysUntilDecisionDue = Math.round((decisionDueDate.getTime() - nowDate.getTime()) / MS_PER_DAY);
const daysUntilRenewal = Math.round((renewalDate.getTime() - nowDate.getTime()) / MS_PER_DAY);
if (!Number.isFinite(daysUntilDecisionDue) || daysUntilDecisionDue < 0) return null;
if (!Number.isFinite(daysUntilRenewal)) return null;
if (daysUntilDecisionDue > windowDays) return null;
return {
renewalAt: params.renewalAt,
decisionDueDate: decisionDueDateRaw,
daysUntilRenewal,
daysUntilDecisionDue,
renewalCycleKey: params.renewalCycleKey,
};
}
export function buildContractCreatedPayload(params: {
contractId: string;
clientId: string;
createdByUserId?: string;
createdAt?: string;
startDate?: string;
endDate?: string | null;
status?: string;
}) {
return {
contractId: params.contractId,
clientId: params.clientId,
createdByUserId: params.createdByUserId,
createdAt: params.createdAt,
startDate: params.startDate,
endDate: params.endDate ?? undefined,
status: params.status,
};
}
export function buildContractUpdatedPayload(params: {
contractId: string;
clientId: string;
updatedAt?: string;
updatedFields?: string[];
changes?: Record<string, { previous: unknown; new: unknown }>;
}) {
return {
contractId: params.contractId,
clientId: params.clientId,
updatedAt: params.updatedAt,
updatedFields: params.updatedFields,
changes: params.changes,
};
}
export function buildContractStatusChangedPayload(params: {
contractId: string;
clientId: string;
previousStatus: string;
newStatus: string;
changedAt?: string;
}) {
return {
contractId: params.contractId,
clientId: params.clientId,
previousStatus: params.previousStatus,
newStatus: params.newStatus,
changedAt: params.changedAt,
};
}
export function buildContractRenewalUpcomingPayload(params: {
contractId: string;
clientId: string;
renewalAt: string;
decisionDueDate?: string;
daysUntilRenewal: number;
daysUntilDecisionDue?: number;
renewalCycleKey?: string;
}) {
return {
contractId: params.contractId,
clientId: params.clientId,
renewalAt: params.renewalAt,
decisionDueDate: params.decisionDueDate,
daysUntilRenewal: params.daysUntilRenewal,
daysUntilDecisionDue: params.daysUntilDecisionDue,
renewalCycleKey: params.renewalCycleKey,
};
}