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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
125 lines
3.6 KiB
TypeScript
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,
|
|
};
|
|
}
|