import { SmrtClassOptions } from '@happyvertical/smrt-core'; import { CommissionPlanCollection } from '../../commissions/index.js'; import { AttributionPolicyCollection } from '../collections/AttributionPolicyCollection.js'; import { ReferralAgreementCollection } from '../collections/ReferralAgreementCollection.js'; import { ReferralCollection } from '../collections/ReferralCollection.js'; import { ReferralTermSnapshotCollection } from '../collections/ReferralTermSnapshotCollection.js'; import { ReferrerCollection } from '../collections/ReferrerCollection.js'; import { Referral } from '../models/Referral.js'; import { ReferralTermSnapshot } from '../models/ReferralTermSnapshot.js'; /** Collaborators for {@link ReferralQualificationService}. */ export interface ReferralQualificationServiceDeps { referrals: ReferralCollection; agreements: ReferralAgreementCollection; /** The commissions module's plan collection (same package). */ plans: CommissionPlanCollection; policies: AttributionPolicyCollection; snapshots: ReferralTermSnapshotCollection; referrers: ReferrerCollection; } /** Why {@link ReferralQualificationService.qualify} refused. */ export type QualificationRefusalReason = 'not_attributed' | 'existing_client_ineligible' | 'self_referral_ineligible' | 'no_active_agreement' | 'no_active_plan'; /** Input for {@link ReferralQualificationService.qualify}. */ export interface QualifyReferralInput { referralId: string; /** Clock override for deterministic tests. */ now?: Date; /** Re-check flag: the prospect is already a client. */ isExistingClient?: boolean; /** Re-check flag: the prospect's own profile id (self-referral gate). */ subjectProfileId?: string; } /** Result of {@link ReferralQualificationService.qualify}. */ export interface QualifyReferralResult { /** Whether the referral is (now or already) qualified. */ qualified: boolean; /** `true` only when THIS call minted the snapshot. */ created: boolean; /** The governing snapshot (`null` on refusal). */ snapshot: ReferralTermSnapshot | null; /** The (re)loaded referral (`null` only when the id is unknown). */ referral: Referral | null; /** Why qualification was refused, when it was. */ reason?: QualificationRefusalReason; } /** Input for {@link ReferralQualificationService.requalify}. */ export interface RequalifyReferralInput { referralId: string; /** Non-empty rationale recorded in the referral metadata. Required. */ reason: string; /** Clock override for deterministic tests. */ now?: Date; } /** Result of {@link ReferralQualificationService.requalify}. */ export interface RequalifyReferralResult { /** Whether a new snapshot now governs the referral. */ requalified: boolean; /** The NEW governing snapshot (`null` on refusal). */ snapshot: ReferralTermSnapshot | null; /** The previously governing snapshot's id (kept as history). */ previousSnapshotId: string; /** Why requalification was refused, when it was. */ reason?: 'no_active_agreement' | 'no_active_plan'; } export declare class ReferralQualificationService { private readonly deps; constructor(deps: ReferralQualificationServiceDeps); static create(classOptions?: SmrtClassOptions): Promise; /** * Qualify an attributed referral. * * Flow: * 1. **Status** — the referral must be `attributed` * (`reason: 'not_attributed'` otherwise). Already `qualified` → * IDEMPOTENT: the existing snapshot returns with `created: false`. * 2. **Eligibility re-check** — the attribution-time policy version's * flags are re-applied against the caller's current knowledge: * `isExistingClient` (`existing_client_ineligible`) and * `subjectProfileId` vs the referrer's profile * (`self_referral_ineligible`). Refusals do NOT change the referral's * status — disqualification stays an explicit caller decision. * 3. **Agreement** — the ACTIVE ReferralAgreement for * (referrerId, programId) effective at `now` * (`reason: 'no_active_agreement'` when none). * 4. **Plan** — the agreement's pinned `commissionPlanVersion`, or the * latest active version when the pin is `0`; the resolved version must * be ACTIVE (`reason: 'no_active_plan'` otherwise). * 5. **Snapshot** — mint the immutable ReferralTermSnapshot (frozen * component copy, currency, clearingDays, approvalMode, agreement + * plan + policy version refs), point `referral.snapshotId` at it, and * transition `attributed → qualified` (stamping `qualifiedAt`). */ qualify(input: QualifyReferralInput): Promise; /** * Explicitly re-snapshot a QUALIFIED referral under the CURRENT terms — * the "amendment explicitly applied" path: after a plan/agreement * amendment, earnings keep flowing through the OLD snapshot until an * operator invokes this. * * Creates a NEW snapshot from the currently active agreement/plan, * repoints `referral.snapshotId`, leaves the old snapshot row untouched * (history — commissions it produced still reference it), and appends * `{ at, reason, fromSnapshotId, toSnapshotId }` to the referral * metadata's `requalifications` array. * * Throws on misuse (unknown referral, non-qualified status, empty * reason); refuses with a typed reason when no active agreement/plan * currently governs. */ requalify(input: RequalifyReferralInput): Promise; private requireReferral; /** * The exact policy version pinned on the referral, when it exists — * resolved in the REFERRAL's tenant lane (global fallback): background * qualification runs without ambient tenant context, and another * tenant's same-keyed policy must never supply the eligibility flags. */ private findPolicyVersion; /** * The CommissionPlan an agreement binds: the pinned version when * `commissionPlanVersion > 0`, else the latest active version. Either * way the resolved row must be ACTIVE — superseded/retired terms cannot * govern a NEW qualification (an amendment should be activated, or the * agreement re-pinned, first). */ private resolvePlan; /** * Mint the immutable snapshot: agreement/plan/policy refs plus the FROZEN * component copy and the calculation inputs. `planVersion` records the * RESOLVED version — this is where an unpinned (`0`) agreement version * gets pinned. */ private mintSnapshot; } export default ReferralQualificationService; //# sourceMappingURL=ReferralQualificationService.d.ts.map