import { SmrtObjectOptions } from '@happyvertical/smrt-core'; import { ServiceTimeEntry, ServiceTimeEntryCollection } from '../models/service-time-entry.js'; import { SupportCase } from '../models/support-case.js'; import { SupportCompensationPlanCollection } from '../models/support-compensation-plan.js'; import { SupportCharge, SupportChargeCollection, SupportCompensation, SupportCompensationCollection } from '../models/support-settlement.js'; import { SupportPrincipal } from '../permissions.js'; import { TimeApprovalPolicy, TimeEntryApprovalPath, TimeEntryEvidence } from '../types.js'; import { SupportCaseService } from './support-case-service.js'; /** Thrown when an approval-path or tenant gate refuses the acting principal. */ export declare class TimeEntryApprovalDeniedError extends Error { constructor(reason: string); } /** Options for {@link TimeEntryApprovalService.create}. */ export interface TimeEntryApprovalServiceOptions extends SmrtObjectOptions { /** Share an existing case facade (otherwise one is created internally). */ caseService?: SupportCaseService; } /** * The plan terms governing one entry's approval and pricing, resolved from * the case's frozen `planSnapshot` first, then the live plan row, else * zero-rate defaults (operator approval, nothing charged). */ export interface ResolvedPlanTerms { policy: TimeApprovalPolicy; currency: string; includedMinutes: number; overageHourlyRate: number; onCallHourlyRate: number; planId: string | null; planKey: string; } /** Input for {@link TimeEntryApprovalService.approve}. */ export interface ApproveTimeEntryInput { /** Acting principal; optional only for the `automatic` path. */ principal?: SupportPrincipal; note?: string; /** Approval instant (defaults to now); also the derivation timestamp. */ at?: Date; } /** Result of {@link TimeEntryApprovalService.approve}. */ export interface ApproveTimeEntryResult { entry: ServiceTimeEntry; charge: SupportCharge; compensation: SupportCompensation; path: TimeEntryApprovalPath; } /** Input for {@link TimeEntryApprovalService.reject}. */ export interface RejectTimeEntryInput { principal: SupportPrincipal; reason: string; } /** Input for {@link TimeEntryApprovalService.correct}. */ export interface CorrectTimeEntryInput { principal: SupportPrincipal; patch: Partial<{ durationSeconds: number; startedAt: Date; endedAt: Date; description: string; evidence: TimeEntryEvidence[]; }>; note?: string; } /** Result of {@link TimeEntryApprovalService.correct}. */ export interface CorrectTimeEntryResult { /** The superseded entry, now `corrected` with its snapshot intact. */ original: ServiceTimeEntry; /** The new `draft` entry carrying the patch (flows submit → approve). */ correction: ServiceTimeEntry; } /** Charge math derived from the plan terms before any row is written. */ interface ChargeDerivation { billableSeconds: number; includedSecondsBefore: number; includedSecondsApplied: number; /** Minor units per hour — see {@link SupportPlan.overageHourlyRate}. */ hourlyRate: number; rateSource: 'overage' | 'on_call' | 'none'; /** Integer minor units (#2401). */ amount: number; } /** * The approval gate. Construct with {@link TimeEntryApprovalService.create}. */ export declare class TimeEntryApprovalService { readonly entries: ServiceTimeEntryCollection; readonly charges: SupportChargeCollection; readonly compensations: SupportCompensationCollection; readonly compensationPlans: SupportCompensationPlanCollection; readonly caseService: SupportCaseService; private readonly approvalMutex; protected constructor(collections: { entries: ServiceTimeEntryCollection; charges: SupportChargeCollection; compensations: SupportCompensationCollection; compensationPlans: SupportCompensationPlanCollection; caseService: SupportCaseService; }); static create(options: TimeEntryApprovalServiceOptions): Promise; /** Load an entry or throw a descriptive error. */ getEntry(entryRef: ServiceTimeEntry | string): Promise; /** * Resolve the plan terms governing an entry: the case's frozen * `planSnapshot` wins field-by-field (history never rewritten by plan * edits), the live plan row fills gaps, and with no case or plan at all the * terms fall back to zero rates under operator approval. */ resolvePlanTerms(entry: ServiceTimeEntry, supportCase?: SupportCase | null): Promise; /** * Approve a `submitted` entry: gate the acting principal by the plan's * approval policy, freeze the entry, and derive its charge and compensation * snapshots. Throws {@link TimeEntryApprovalDeniedError} when the gate * refuses. */ approve(entryRef: ServiceTimeEntry | string, input?: ApproveTimeEntryInput): Promise; private approveExclusive; /** * Reject a `submitted` entry with a reason (requires the * `support.approve-time-entry` split). */ reject(entryRef: ServiceTimeEntry | string, input: RejectTimeEntryInput): Promise; /** * Correct an `approved` entry (requires the `support.approve-time-entry` * split): a NEW `draft` entry copies the original's context, participant, * and source with the patch applied and links back via `correctionOfId`; * the original flips to `corrected` with its frozen fields and snapshots * untouched, and its charge/compensation rows are marked `corrected`. The * correction then flows submit → approve normally, deriving fresh * snapshots. */ correct(entryRef: ServiceTimeEntry | string, input: CorrectTimeEntryInput): Promise; /** * Determine the approval path for an entry under a policy, or throw * {@link TimeEntryApprovalDeniedError} when the acting principal does not * satisfy it. */ protected determinePath(entry: ServiceTimeEntry, policy: TimeApprovalPolicy, /** Integer minor units, same unit as `policy.thresholdAmount` (#2401). */ chargeAmount: number, principal: SupportPrincipal | undefined, supportCase: SupportCase | null): TimeEntryApprovalPath; /** * Charge math from the Managed Support Plan terms: remaining included time * (per case — period windows are #1925's scope) absorbs first, the * remainder meters at the overage (or on-call) hourly rate. */ protected deriveCharge(entry: ServiceTimeEntry, terms: ResolvedPlanTerms): Promise; /** * Provider earning from the Support Compensation Plan effective at the work * instant (`endedAt`, falling back to the approval instant). No specialist * or no plan resolves to a zero-amount row so the entry still settles. */ protected deriveCompensation(entry: ServiceTimeEntry, at: Date): Promise; /** * Idempotent write of the entry's single charge row: an approval retry * after a partial failure refreshes the existing row instead of colliding * on the `time_entry_id` unique key. */ protected upsertChargeRow(entry: ServiceTimeEntry, fields: Record): Promise; /** Idempotent write of the entry's single compensation row (see above). */ protected upsertCompensationRow(entry: ServiceTimeEntry, fields: Record): Promise; /** Cross-tenant acts are refused when both sides carry a tenant. */ protected assertTenantMatch(entry: ServiceTimeEntry, principal: SupportPrincipal | undefined): void; /** Require the operator permission split for a privileged act. */ protected assertOperator(principal: SupportPrincipal | undefined, act: string): void; } export default TimeEntryApprovalService; //# sourceMappingURL=time-entry-approval-service.d.ts.map