Files
dh-inspeccion-v2/web-v2/src/lib/api.ts
T
admin 4c5742c548 F1.2 · Seguimiento administrativo por Acta
Plazo único por Acta, respuestas de empresa con PDF, fecha comprometida, estados administrativos, cola y calendario por Acta.
2026-09-05 16:47:46 -03:00

3132 lines
106 KiB
TypeScript

const API_BASE = '/api/v3';
export class ApiError extends Error {
constructor(
public readonly status: number,
public readonly code: string,
message: string,
public readonly requestId?: string,
) {
super(message);
this.name = 'ApiError';
}
}
export interface AuthUser {
id: string;
username: string;
firstName: string;
lastName: string;
email: string | null;
mustChangePassword: boolean;
roles: string[];
permissions: string[];
}
export interface AuthResponse {
user: AuthUser;
csrfToken: string;
accessExpiresInSeconds: number;
}
export interface HealthResponse {
status: string;
service: string;
api: string;
version: string;
phase: string;
database: string;
time: string;
latencyMs: number;
}
export interface DashboardSummary {
counts: {
totalAssets: number;
assetsNeedValidation: number;
assetsWithoutGeometry: number;
plannedInspections: number;
activeUsers: number;
inactiveUsers: number;
activeSessions: number;
openFindings: number;
awaitingCompanyResponse: number;
overdueCompanyResponses: number;
companyResponsesDueNext7Days: number;
awaitingVerificationSchedule: number;
overdueControls: number;
controlsNext30Days: number;
};
recentAudit: Array<{
id: string;
occurredAt: string;
actorUsername: string | null;
action: string;
entityType: string | null;
entityId: string | null;
}>;
upcomingControls: Array<{
id: string;
code: string;
title: string;
nextControlOn: string;
actId: string;
actCode: string;
visitId: string;
visitCode: string;
assetId: string;
assetCode: string;
assetName: string;
}>;
generatedAt: string;
}
export interface RoleSummary { id: string; code: string; name: string }
export interface AdministrativeUser {
id: string;
username: string;
email: string | null;
firstName: string;
lastName: string;
status: 'ACTIVE' | 'INACTIVE';
mustChangePassword: boolean;
failedLoginAttempts: number;
lockedUntil: string | null;
lastLoginAt: string | null;
passwordChangedAt: string | null;
createdAt: string;
updatedAt: string;
roles: RoleSummary[];
}
export interface Permission { id: string; code: string; description: string }
export interface AdministrativeRole {
id: string;
code: string;
name: string;
description: string;
isSystem: boolean;
createdAt: string;
updatedAt: string;
userCount: number;
permissions: Permission[];
}
export interface AuditEventSummary {
id: string;
occurredAt: string;
actorUserId: string | null;
actorUsername: string | null;
action: string;
entityType: string | null;
entityId: string | null;
requestId: string | null;
source: 'WEB' | 'ANDROID' | 'SYSTEM' | 'IMPORT';
ip: string | null;
hasDetails: boolean;
}
export interface AuditEventDetail extends AuditEventSummary {
userAgent: string | null;
beforeData: Record<string, unknown> | null;
afterData: Record<string, unknown> | null;
metadata: Record<string, unknown> | null;
}
export interface PageMeta { page: number; pageSize: number; total: number; totalPages: number }
interface Page<T> { data: T[]; meta: PageMeta }
export type AssetInformationStatus =
| 'DRAFT' | 'PENDING_SURVEY' | 'SURVEYED' | 'VALIDATED'
| 'OBSERVED' | 'OUTDATED' | 'INACTIVE';
export type AssetOperationalStatus =
| 'UNKNOWN' | 'IN_SERVICE' | 'TEMPORARILY_OUT_OF_SERVICE'
| 'OUT_OF_SERVICE' | 'DECOMMISSIONED' | 'ABANDONED';
export type AssetDataOrigin =
| 'MANUAL' | 'FIELD_SURVEY' | 'PROVIDED_DOCUMENT' | 'IMPORT' | 'SYSTEM';
export type AssetTypeOperationalRole = 'GENERIC' | 'AREA' | 'COMPANY';
export interface AssetProvenance {
assetId: string;
origin: AssetDataOrigin;
sourceName: string | null;
sourceReference: string | null;
observedAt: string | null;
notes: string | null;
verifiedAt: string | null;
verifiedBy: string | null;
verifiedByUsername: string | null;
updatedAt: string;
updatedBy: string | null;
updatedByUsername: string | null;
}
export type AssetAttributeDataType =
| 'TEXT' | 'NUMBER' | 'BOOLEAN' | 'DATE' | 'DATETIME' | 'SELECT';
export interface AssetAttributeDefinition {
id: string;
code: string;
name: string;
dataType: AssetAttributeDataType;
isRequired: boolean;
isActive: boolean;
unit: string | null;
options: string[] | null;
sortOrder: number;
}
export interface AssetTypeSummary {
id: string;
code: string;
name: string;
isActive: boolean;
operationalRole: AssetTypeOperationalRole;
}
export interface AssetType extends AssetTypeSummary {
description: string;
canBeRoot: boolean;
createdAt: string;
updatedAt: string;
assetCount: number;
allowedParentTypes: AssetTypeSummary[];
attributes: AssetAttributeDefinition[];
}
export interface MasterBootstrapStatus {
presetCode: string;
presetName: string;
typeCount: number;
canApply: boolean;
reason: string | null;
types: Array<{
code: string;
name: string;
operationalRole: AssetTypeOperationalRole;
parentCodes: string[];
attributeCount: number;
}>;
}
export interface MasterBootstrapResult {
presetCode: string;
presetName: string;
createdTypeCount: number;
createdAttributeCount: number;
createdParentRuleCount: number;
data: AssetType[];
}
export interface MasterEnrichmentStatus {
presetCode: string;
presetName: string;
typeCount: number;
canApply: boolean;
complete: boolean;
reason: string | null;
missingTypeCodes: string[];
missingAttributeCount: number;
missingParentRuleCount: number;
}
export interface AssetListItem {
id: string;
code: string;
name: string;
commonName: string | null;
type: { id: string; code: string; name: string };
parent: { id: string; code: string; name: string } | null;
operationalArea: { id: string; code: string; name: string } | null;
operatorCompany: { id: string; code: string; name: string } | null;
informationStatus: AssetInformationStatus;
operationalStatus: AssetOperationalStatus;
childrenCount: number;
hasGeometry: boolean;
geometryType: AssetGeometryType | null;
mediaCount: number;
dataOrigin: AssetDataOrigin;
provenanceVerified: boolean;
currentVersion: number;
updatedAt: string;
}
export interface AssetLineageItem {
id: string;
code: string;
name: string;
commonName: string | null;
type: { id: string; code: string; name: string };
}
export interface AssetDetail extends AssetListItem {
description: string | null;
createdAt: string;
createdBy: string | null;
updatedBy: string | null;
attributes: Array<{
definitionId: string;
code: string;
name: string;
dataType: AssetAttributeDataType;
isRequired: boolean;
unit: string | null;
options: string[] | null;
sortOrder: number;
value: unknown;
}>;
}
export type AssetDossierTimelineKind =
| 'INVENTORY_CHANGE' | 'INSPECTION' | 'ACT' | 'FINDING' | 'FINDING_CLOSED'
| 'COMMUNICATION' | 'PHOTO' | 'DOCUMENT' | 'REPORT' | 'SOURCE_DOCUMENT' | 'VERIFICATION';
export interface AssetDossierTimelineEvent {
id: string;
kind: AssetDossierTimelineKind;
occurredAt: string;
title: string;
description: string | null;
href?: string;
meta?: Record<string, unknown>;
}
export interface AssetDossier {
asset: { id: string; code: string; name: string; commonName: string | null };
counters: {
inspections: number; acts: number; findings: number; openFindings: number; closedFindings: number;
evidence: number; documents: number; photos: number; reports: number; verifications: number;
};
visits: Array<{ id: string; code: string; title: string; status: InspectionVisitStatus; plannedStartAt: string | null; actualStartedAt: string | null; actualClosedAt: string | null; createdAt: string }>;
acts: Array<{ id: string; visitId: string; code: string; status: InspectionActStatus; occurredAt: string; title: string; summary: string; closedAt: string | null; currentVersion: number; visitCode: string; visitTitle: string }>;
findings: Array<{ id: string; actId: string; code: string; status: InspectionFindingStatus; title: string; description: string; correctionDueOn: string | null; companyResponseReceivedOn: string | null; nextControlOn: string | null; closedAt: string | null; closureNotes: string | null; createdAt: string; updatedAt: string; actCode: string; actOccurredAt: string; visitId: string; visitCode: string; visitTitle: string }>;
evidence: Array<{ id: string; findingId: string; communicationId: string | null; kind: InspectionEvidenceKind; purpose: InspectionEvidencePurpose; originalName: string; title: string | null; description: string | null; capturedAt: string | null; createdAt: string; findingCode: string; findingTitle: string }>;
communications: Array<{ id: string; findingId: string; direction: InspectionFindingCommunication['direction']; channel: InspectionFindingCommunication['channel']; type: InspectionFindingCommunication['type']; occurredAt: string; subject: string; details: string | null; contactName: string | null; createdAt: string; findingCode: string; findingTitle: string }>;
verificationResults: Array<{ id: string; findingId: string; findingCode: string; findingTitle: string; visitId: string; visitCode: string; visitTitle: string; visitStatus: InspectionVisitStatus; targetControlOn: string | null; outcome: InspectionVerificationOutcome | null; resultNotes: string | null; verifiedAt: string | null; resultRecordedAt: string | null; rescheduledControlOn: string | null; evidenceCount: number }>;
documents: Array<{ id: string; documentType: SourceDocumentType; documentNumber: string | null; title: string; issuer: string | null; documentDate: string | null; externalReference: string | null; relationType: AssetSourceDocumentRelationType; notes: string | null; linkedAt: string }>;
inspectionReports: Array<{ id: string; code: string; status: InspectionReportStatus; pdfStatus: InspectionReportPdfStatus; title: string; generatedAt: string; frozenSha256: string; actId: string; actCode: string; visitId: string; visitCode: string }>;
reports: Array<{ id: string; documentType: SourceDocumentType; documentNumber: string | null; title: string; issuer: string | null; documentDate: string | null; externalReference: string | null; relationType: AssetSourceDocumentRelationType; notes: string | null; linkedAt: string }>;
media: Array<{ id: string; kind: AssetMediaKind; originalName: string; title: string | null; description: string | null; capturedAt: string | null; createdAt: string; source: 'WEB' | 'ANDROID' | 'IMPORT' }>;
versions: Array<{ id: string; versionNumber: number; changeType: AssetVersionChangeType; changedFields: string[]; occurredAt: string; actorUsername: string | null; source: string }>;
timeline: AssetDossierTimelineEvent[];
}
export interface AssetContextHistoryItem {
id: string;
assetId: string;
validFrom: string;
validUntil: string | null;
changeReason: string;
endReason: string | null;
assetVersionNumber: number;
source: string;
requestId: string | null;
createdAt: string;
parent: { id: string; code: string; name: string } | null;
operationalArea: { id: string; code: string; name: string } | null;
operatorCompany: { id: string; code: string; name: string } | null;
creator: { id: string; username: string; firstName: string; lastName: string } | null;
isCurrent: boolean;
}
export type FieldDiscoveryStatus = 'PENDING' | 'APPROVED' | 'MATCHED' | 'REJECTED';
export interface FieldDiscovery {
id: string;
status: FieldDiscoveryStatus;
discoveryNotes: string | null;
observedAt: string;
reviewedAt: string | null;
reviewNotes: string | null;
createdAt: string;
asset: {
id: string; code: string; name: string; commonName: string | null; informationStatus: AssetInformationStatus;
typeId: string; typeName: string; parentId: string | null; operationalAreaId: string | null; operatorCompanyId: string | null;
};
visit: { id: string; code: string; title: string; status: InspectionVisitStatus };
creator: { id: string; username: string; firstName: string; lastName: string };
reviewer: { id: string; username: string; firstName: string; lastName: string } | null;
matchedAsset: { id: string; code: string; name: string; commonName: string | null } | null;
}
export interface OperationalAssetSummary {
id: string;
code: string;
name: string;
commonName: string | null;
typeName: string;
}
export type AreaOrganizationRole = 'OPERATOR' | 'TECHNICAL_OPERATOR' | 'CONCESSIONAIRE' | 'PERMIT_HOLDER' | 'PARTICIPANT' | 'OTHER';
export interface AreaCompanyRelation {
id: string;
area: OperationalAssetSummary;
company: OperationalAssetSummary;
relationRole: AreaOrganizationRole;
participationPercent: number | null;
legalInstrument: string | null;
sourceDocumentId: string | null;
validFrom: string;
validUntil: string | null;
startReason: string;
endReason: string | null;
createdBy: { id: string; username: string } | null;
endedBy: { id: string; username: string } | null;
createdAt: string;
updatedAt: string;
active: boolean;
assignedAssetCount: number;
}
export type OrganizationKind = 'COMPANY' | 'UTE' | 'PUBLIC_ENTITY' | 'OTHER';
export type SourceDocumentType = 'NOTE' | 'TECHNICAL_REPORT' | 'INSPECTION_ACT' | 'INVENTORY' | 'RESOLUTION' | 'DECREE' | 'CONTRACT' | 'SPREADSHEET' | 'OTHER';
export type AssetSourceDocumentRelationType = 'SOURCE' | 'MENTIONS' | 'VALIDATES' | 'SUPERSEDES' | 'OTHER';
export type OrganizationMembershipRole = 'MEMBER' | 'LEAD_MEMBER' | 'OTHER';
export type AreaLegalRightType = 'EXPLOITATION_CONCESSION' | 'EXPLORATION_PERMIT' | 'TRANSPORT_CONCESSION' | 'OTHER';
export type AreaLegalRightStatus = 'ACTIVE' | 'EXPIRED' | 'REVOKED' | 'PENDING';
export type AreaLegalRightOrganizationRole = 'HOLDER' | 'PARTICIPANT' | 'OPERATOR' | 'OTHER';
export interface SourceDocumentSummary {
id: string; documentType: SourceDocumentType; documentNumber: string | null; title: string;
issuer: string | null; documentDate: string | null; externalReference: string | null; notes: string | null;
createdAt?: string; updatedAt?: string;
}
export interface AssetRegistry {
organizationProfile: null | { assetId: string; organizationKind: OrganizationKind; legalName: string | null; taxId: string | null; notificationEmail: string | null; notes: string | null; createdAt: string; updatedAt: string };
organizationMemberships: Array<{ id: string; parent: { id: string; code: string; name: string }; member: { id: string; code: string; name: string }; role: OrganizationMembershipRole; participationPercent: number | null; validFrom: string; validUntil: string | null; sourceDocumentId: string | null; notes: string | null; endReason: string | null }>;
externalIdentifiers: Array<{ id: string; namespace: string; value: string; validFrom: string; validUntil: string | null; sourceDocumentId: string | null; notes: string | null; endReason: string | null }>;
sourceDocuments: Array<SourceDocumentSummary & { linkId: string; relationType: AssetSourceDocumentRelationType; linkNotes: string | null }>;
legalRights: Array<{ id: string; rightType: AreaLegalRightType; name: string; instrumentNumber: string | null; validFrom: string | null; validUntil: string | null; status: AreaLegalRightStatus; sourceDocumentId: string | null; notes: string | null; organizations: Array<{ id: string; organizationId: string; organizationName: string; role: AreaLegalRightOrganizationRole; participationPercent: number | null; validFrom: string; validUntil: string | null; notes: string | null; endReason: string | null }> }>;
}
export type AssetImportBatchStatus = 'ANALYZED' | 'REVIEW_REQUIRED' | 'CANCELLED' | 'FAILED';
export type AssetImportRowStatus = 'READY' | 'WARNING' | 'CONFLICT' | 'IGNORED';
export type AssetImportProfileCode = 'MENDOZA_INVENTORY_V1' | 'MENDOZA_YACIMIENTOS_V1' | 'UNKNOWN';
export interface AssetImportBatch {
id: string;
originalName: string;
mimeType: string;
sizeBytes: number;
sha256: string;
profileCode: AssetImportProfileCode;
profileConfidence: number;
worksheetName: string | null;
headerRow: number | null;
totalRows: number;
readyRows: number;
warningRows: number;
conflictRows: number;
ignoredRows: number;
status: AssetImportBatchStatus;
sourceDocumentId: string | null;
sourceLabel: string | null;
notes: string | null;
analysis: Record<string, unknown>;
uploadedBy: string | null;
uploadedByUsername: string | null;
analyzedAt: string;
createdAt: string;
updatedAt: string;
}
export interface AssetImportBatchDetail extends AssetImportBatch {
topIssues: Array<{ code: string; count: number }>;
duplicateFiles: Array<{ id: string; originalName: string; createdAt: string; status: AssetImportBatchStatus }>;
}
export interface AssetImportRow {
id: string;
worksheetName: string;
rowNumber: number;
status: AssetImportRowStatus;
suggestedAction: 'CREATE' | 'MATCH' | 'REVIEW' | 'IGNORE';
rawData: Record<string, unknown>;
normalizedData: Record<string, unknown>;
issues: string[];
fingerprint: string;
matchedAssetId: string | null;
importedAssetId: string | null;
createdAt: string;
updatedAt: string;
}
export type AssetImportPlanStatus = 'REVIEW_REQUIRED' | 'READY' | 'APPLIED' | 'ROLLED_BACK' | 'SUPERSEDED' | 'FAILED';
export type AssetImportPlanEntityKind = 'DEPARTMENT' | 'ORGANIZATION' | 'AREA' | 'AREA_DEPARTMENT_RELATION' | 'FIELD' | 'OPERATOR_RELATION' | 'LEGAL_RIGHT' | 'LEGAL_RIGHT_ORGANIZATION' | 'INSTALLATION' | 'LOCAL_STRUCTURE' | 'TECHNICAL_ASSET';
export type AssetImportPlanAction = 'CREATE' | 'MATCH' | 'REVIEW' | 'IGNORE';
export type AssetImportPlanItemStatus = 'PLANNED' | 'MATCHED' | 'REVIEW' | 'IGNORED' | 'APPLIED' | 'ROLLED_BACK' | 'FAILED';
export interface AssetImportPlanItem {
id: string;
planId: string;
itemOrder: number;
entityKey: string;
entityKind: AssetImportPlanEntityKind;
action: AssetImportPlanAction;
status: AssetImportPlanItemStatus;
assetTypeCode: string | null;
displayName: string;
generatedCode: string | null;
parentEntityKey: string | null;
matchedAssetId: string | null;
appliedAssetId: string | null;
appliedObjectId: string | null;
payload: Record<string, unknown>;
sourceRowNumbers: number[];
reviewCodes: string[];
resolutionNote: string | null;
resolvedBy: string | null;
resolvedAt: string | null;
}
export interface AssetImportPlan {
id: string;
batchId: string;
revision: number;
status: AssetImportPlanStatus;
externalIdNamespace: string | null;
operatorAssetId: string | null;
summary: Record<string, unknown>;
planHash: string;
generatedBy: string | null;
generatedAt: string;
appliedBy: string | null;
appliedAt: string | null;
applicationSummary: Record<string, unknown> | null;
rolledBackBy: string | null;
rolledBackAt: string | null;
rollbackSummary: Record<string, unknown> | null;
supersededAt: string | null;
createdAt: string;
updatedAt: string;
masterStateStale: boolean;
masterStateHashAvailable: boolean;
masterStateCheckedAt: string;
previewItems: AssetImportPlanItem[];
reviewItems: AssetImportPlanItem[];
}
export interface AssetImportReviewItem extends AssetImportPlanItem {
batchId: string;
batchName: string;
sourceLabel: string | null;
profileCode: AssetImportProfileCode;
planRevision: number;
planStatus: AssetImportPlanStatus;
dependency: boolean;
}
export interface AssetImportOrganizationOption {
id: string;
code: string;
name: string;
legalName: string | null;
}
export type Position = [number, number];
export type AssetGeometryType = 'POINT' | 'LINESTRING' | 'POLYGON';
export type GeoJsonGeometry =
| { type: 'POINT'; coordinates: Position }
| { type: 'LINESTRING'; coordinates: Position[] }
| { type: 'POLYGON'; coordinates: Position[][] };
export interface AssetGeometry {
assetId: string;
geometry: GeoJsonGeometry;
geometryType: AssetGeometryType;
source: 'WEB' | 'ANDROID' | 'IMPORT' | 'SURVEY';
accuracyM: number | null;
capturedAt: string | null;
deviceLabel: string | null;
createdAt: string;
updatedAt: string;
updatedBy: string | null;
}
export interface MapAssetProperties {
id: string;
code: string;
name: string;
commonName?: string | null;
typeId: string;
typeCode: string;
typeName: string;
parentId: string | null;
parentName: string | null;
informationStatus: AssetInformationStatus;
geometryType: AssetGeometryType;
accuracyM: number | null;
capturedAt: string | null;
updatedAt: string;
}
export interface MapAssetFeature {
type: 'Feature';
id: string;
geometry: GeoJsonGeometry;
properties: MapAssetProperties;
}
export interface MapAssetFeatureCollection {
type: 'FeatureCollection';
features: MapAssetFeature[];
meta: { count: number; truncated: boolean };
}
export type AssetMediaKind = 'PHOTO' | 'DOCUMENT';
export interface AssetMedia {
id: string;
assetId: string;
kind: AssetMediaKind;
originalName: string;
mimeType: 'image/jpeg' | 'image/png' | 'image/webp' | 'application/pdf';
sizeBytes: number;
sha256: string;
title: string | null;
description: string | null;
capturedAt: string | null;
latitude: number | null;
longitude: number | null;
accuracyM: number | null;
source: 'WEB' | 'ANDROID' | 'IMPORT';
uploadedBy: string | null;
uploadedByUsername: string | null;
createdAt: string;
updatedAt: string;
}
export type AssetVersionChangeType =
| 'BASELINE'
| 'CREATED'
| 'UPDATED'
| 'CONTEXT_CHANGED'
| 'STATUS_CHANGED'
| 'OPERATIONAL_STATUS_CHANGED'
| 'REGISTRY_UPDATED'
| 'GEOMETRY_UPDATED'
| 'GEOMETRY_REMOVED'
| 'MEDIA_UPLOADED'
| 'MEDIA_UPDATED'
| 'MEDIA_REMOVED'
| 'PROVENANCE_BASELINE'
| 'PROVENANCE_UPDATED'
| 'PROVENANCE_VERIFIED';
export interface AssetVersionSnapshot {
id: string;
code: string;
name: string;
description: string | null;
type: { id: string; code: string; name: string; operationalRole?: AssetTypeOperationalRole };
parent: { id: string; code: string; name: string } | null;
operationalArea?: { id: string; code: string; name: string } | null;
operatorCompany?: { id: string; code: string; name: string } | null;
informationStatus: AssetInformationStatus;
operationalStatus: AssetOperationalStatus;
organizationProfile?: Record<string, unknown> | null;
organizationMemberships?: Array<Record<string, unknown>>;
externalIdentifiers?: Array<Record<string, unknown>>;
sourceDocuments?: Array<Record<string, unknown>>;
legalRights?: Array<Record<string, unknown>>;
attributes: Array<{
definitionId: string;
code: string;
name: string;
dataType: AssetAttributeDataType;
isRequired: boolean;
unit: string | null;
options: string[] | null;
sortOrder: number;
value: unknown;
}>;
geometry: AssetGeometry | null;
media?: AssetMedia[];
provenance?: {
origin: AssetDataOrigin;
sourceName: string | null;
sourceReference: string | null;
observedAt: string | null;
notes: string | null;
verifiedAt: string | null;
verifiedBy: string | null;
updatedAt: string;
updatedBy: string | null;
};
createdAt: string;
updatedAt: string;
createdBy: string | null;
updatedBy: string | null;
currentVersion: number;
}
export interface AssetVersionSummary {
id: string;
assetId: string;
assetCode: string;
assetName: string;
typeId: string;
typeName: string;
informationStatus: AssetInformationStatus;
operationalStatus: AssetOperationalStatus;
versionNumber: number;
changeType: AssetVersionChangeType;
changedFields: string[];
occurredAt: string;
actorUserId: string | null;
actorUsername: string | null;
source: 'WEB' | 'ANDROID' | 'SYSTEM' | 'IMPORT';
requestId: string | null;
isCurrent: boolean;
}
export interface AssetVersionDetail extends AssetVersionSummary {
snapshot: AssetVersionSnapshot;
}
export interface TemporalAssetSummary extends AssetVersionSummary {
effectiveUntil: string | null;
}
export interface TemporalAssetDetail extends TemporalAssetSummary {
snapshot: AssetVersionSnapshot;
asOf: string;
}
export type SurveyCampaignStatus =
| 'DRAFT' | 'PLANNED' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED';
export type SurveyTargetStatus =
| 'PENDING' | 'IN_PROGRESS' | 'SUBMITTED' | 'COMPLETED' | 'SKIPPED';
export interface SurveyPerson {
id: string;
username: string;
firstName: string;
lastName: string;
}
export interface SurveyCampaignListItem {
id: string;
code: string;
name: string;
description: string | null;
status: SurveyCampaignStatus;
plannedStartAt: string | null;
plannedEndAt: string | null;
scopeAsset: { id: string; code: string; name: string } | null;
coordinator: SurveyPerson | null;
targetCount: number;
pendingCount: number;
inProgressCount: number;
submittedCount: number;
completedCount: number;
skippedCount: number;
createdAt: string;
updatedAt: string;
}
export interface SurveyTarget {
id: string;
campaignId: string;
asset: { id: string; code: string; name: string; typeName: string };
assignedUser: SurveyPerson | null;
status: SurveyTargetStatus;
dueAt: string | null;
instructions: string | null;
createdAt: string;
updatedAt: string;
}
export interface SurveyCampaign extends SurveyCampaignListItem {
targets: SurveyTarget[];
}
export type SurveyReportOutcome = 'CONFIRMED' | 'CHANGES_RECORDED' | 'NOT_LOCATED';
export type SurveyReportStatus = 'DRAFT' | 'SUBMITTED' | 'APPROVED' | 'REJECTED';
export type SurveyReportVersionEvent = 'SUBMITTED' | 'APPROVED' | 'REJECTED';
export interface SurveyTargetReport {
id: string;
targetId: string;
outcome: SurveyReportOutcome | null;
status: SurveyReportStatus;
observedAt: string | null;
latitude: number | null;
longitude: number | null;
accuracyM: number | null;
notes: string | null;
assetVersionAtSubmission: number | null;
submittedAt: string | null;
submittedBy: SurveyPerson | null;
reviewedAt: string | null;
reviewedBy: SurveyPerson | null;
reviewNotes: string | null;
selectedMediaIds: string[];
createdAt: string;
updatedAt: string;
}
export interface SurveyReportMedia extends AssetMedia {
kind: 'PHOTO';
selected: boolean;
}
export interface SurveyReportVersion {
id: string;
versionNumber: number;
event: SurveyReportVersionEvent;
snapshot: Record<string, unknown>;
actorUserId: string | null;
actorUsername: string | null;
createdAt: string;
}
export interface SurveyExecution {
target: {
id: string;
status: SurveyTargetStatus;
dueAt: string | null;
instructions: string | null;
assignedUser: SurveyPerson | null;
};
campaign: {
id: string;
code: string;
name: string;
status: SurveyCampaignStatus;
};
asset: {
id: string;
code: string;
name: string;
typeName: string;
informationStatus: AssetInformationStatus;
currentVersion: number;
};
report: SurveyTargetReport | null;
availableMedia: SurveyReportMedia[];
versions: SurveyReportVersion[];
}
export type InspectionVisitStatus =
| 'DRAFT' | 'PLANNED' | 'IN_PROGRESS' | 'CLOSED' | 'CANCELLED';
export interface InspectionPerson {
id: string;
username: string;
firstName: string;
lastName: string;
}
export interface InspectionAssetSummary {
id: string;
code: string;
name: string;
commonName?: string | null;
typeName: string;
operatorCompany?: { id: string; code: string; name: string } | null;
operationalArea?: { id: string; code: string; name: string } | null;
}
export interface InspectionPlanningContextAsset {
id: string;
code: string;
name: string;
}
export type InspectionVisitAssetPlanningSource = 'LEGACY' | 'AUTOMATIC' | 'PREVENTIVE' | 'VERIFICATION';
export type InspectionChecklistItemKind = 'ANTECEDENT' | 'COMPANY_OVERDUE' | 'VERIFICATION_OVERDUE' | 'UPCOMING_CONTROL';
export interface InspectionPlannedAsset extends InspectionAssetSummary {
included: boolean;
planningSource: InspectionVisitAssetPlanningSource;
exclusionReason: string | null;
excludedAt: string | null;
excludedBy: InspectionPerson | null;
}
export interface InspectionVisitChecklistItem {
id: string;
findingId: string;
findingCode: string;
findingTitle: string;
findingStatus: InspectionFindingStatus;
severity: number | null;
itemKind: InspectionChecklistItemKind;
referenceOn: string | null;
asset: InspectionAssetSummary;
assetIncluded: boolean;
}
export interface InspectionVisitChecklistSummary {
generation: number;
generatedAt: string | null;
stale: boolean;
antecedents: number;
companyOverdue: number;
verificationOverdue: number;
upcomingControls: number;
actionableAssets: number;
excludedAssets: number;
items: InspectionVisitChecklistItem[];
}
export interface InspectionVisitListItem {
id: string;
code: string;
title: string;
objective: string | null;
status: InspectionVisitStatus;
scopeAsset: InspectionAssetSummary | null;
operationalArea: InspectionPlanningContextAsset | null;
operatorCompany: InspectionPlanningContextAsset | null;
leadInspector: InspectionPerson | null;
plannedStartAt: string | null;
plannedEndAt: string | null;
actualStartedAt: string | null;
actualClosedAt: string | null;
instructions: string | null;
cancellationReason: string | null;
checklistGeneration: number;
checklistGeneratedAt: string | null;
assetCount: number;
memberCount: number;
createdAt: string;
updatedAt: string;
}
export type InspectionVerificationOutcome = 'RESOLVED' | 'NOT_RESOLVED' | 'REQUIRES_NEW_DATE';
export interface InspectionVerificationFindingSummary {
id: string;
code: string;
title: string;
status: InspectionFindingStatus;
nextControlOn: string | null;
assetId: string;
assetCode: string;
assetName: string;
targetControlOn: string | null;
outcome: InspectionVerificationOutcome | null;
resultNotes: string | null;
verifiedAt: string | null;
resultRecordedAt: string | null;
rescheduledControlOn: string | null;
verificationEvidenceCount: number;
}
export interface InspectionVisit extends InspectionVisitListItem {
assets: InspectionAssetSummary[];
planningAssets: InspectionPlannedAsset[];
team: InspectionPerson[];
verificationFindings: InspectionVerificationFindingSummary[];
checklist: InspectionVisitChecklistSummary;
}
export type InspectionActStatus =
| 'DRAFT' | 'READY' | 'CLOSED' | 'CANCELLED' | 'RECTIFIED';
export type InspectionActVersionEvent =
| 'CREATED' | 'UPDATED' | 'READY' | 'REOPENED' | 'CLOSED' | 'CANCELLED';
export interface InspectionActListItem {
id: string;
visitId: string;
visit: {
id: string;
code: string;
title: string;
status: InspectionVisitStatus;
actualStartedAt: string | null;
};
actYear: number;
actNumber: number;
code: string;
status: InspectionActStatus;
occurredAt: string;
title: string;
summary: string;
observations: string | null;
currentVersion: number;
cancellationReason: string | null;
closedAt: string | null;
closedBy: string | null;
closureSha256: string | null;
assetCount: number;
findingCount: number;
companies: Array<{ id: string; code: string; name: string }>;
areas: Array<{ id: string; code: string; name: string }>;
report: null | {
id: string;
code: string;
status: InspectionReportStatus;
pdfStatus: InspectionReportPdfStatus;
generatedAt: string;
};
createdBy: InspectionPerson | null;
updatedBy: InspectionPerson | null;
createdAt: string;
updatedAt: string;
}
export interface InspectionActVersion {
id: string;
versionNumber: number;
event: InspectionActVersionEvent;
snapshot: Record<string, unknown>;
actorUserId: string | null;
actorUsername: string | null;
createdAt: string;
}
export interface InspectionAct extends InspectionActListItem {
assets: InspectionAssetSummary[];
versions: InspectionActVersion[];
}
export type InspectionReportStatus = 'FROZEN' | 'CANCELLED';
export type InspectionReportPdfStatus = 'PENDING' | 'READY' | 'FAILED';
export type InspectionReportWordStatus = 'PENDING' | 'READY' | 'FAILED';
export type InspectionReportReviewStatus = 'PENDING_REVIEW' | 'APPROVED' | 'SIGNED';
export interface InspectionReportListItem {
id: string;
visitId: string;
actId: string;
reportYear: number;
reportNumber: number;
code: string;
status: InspectionReportStatus;
pdfStatus: InspectionReportPdfStatus;
wordStatus: InspectionReportWordStatus;
wordGeneratedAt: string | null;
reviewStatus: InspectionReportReviewStatus;
currentRevisionNumber: number;
approvedAt: string | null;
signedAt: string | null;
title: string;
actVersion: number;
actClosureSha256: string;
frozenSha256: string;
generatedAt: string;
generatedBy: InspectionPerson;
act: {
id: string;
code: string;
title: string;
status: InspectionActStatus;
occurredAt: string;
closedAt: string | null;
};
visit: {
id: string;
code: string;
title: string;
status: InspectionVisitStatus;
};
companies: Array<{ id: string; code: string; name: string }>;
areas: Array<{ id: string; code: string; name: string }>;
findingCount: number;
}
export interface InspectionReport extends InspectionReportListItem {
frozenSnapshot: Record<string, unknown>;
}
export type InspectionReportRevisionSource = 'AUTO' | 'DIRECTOR_UPLOAD';
export interface InspectionReportRevision {
id: string;
reportId: string;
revisionNumber: number;
source: InspectionReportRevisionSource;
originalName: string;
mimeType: string;
sizeBytes: number;
sha256: string;
changeSummary: string | null;
createdAt: string;
createdBy: InspectionPerson;
}
export interface InspectionReportSignature {
id: string;
revisionId: string;
signedAt: string;
confirmationText: string;
signatureSha256: string;
signedBy: InspectionPerson;
}
export interface InspectionReportReview {
reportId: string;
reportCode: string;
reviewStatus: InspectionReportReviewStatus;
currentRevisionNumber: number;
approvedRevisionId: string | null;
approvedAt: string | null;
reviewNote: string | null;
approvedBy: InspectionPerson | null;
signature: InspectionReportSignature | null;
revisions: InspectionReportRevision[];
}
export interface PendingInspectionReportItem {
actId: string;
visitId: string;
actCode: string;
actTitle: string;
actYear: number;
occurredAt: string;
closedAt: string;
closureSha256: string;
visitCode: string;
visitTitle: string;
companies: Array<{ id: string; code: string; name: string }>;
areas: Array<{ id: string; code: string; name: string }>;
findingCount: number;
}
export type InspectionResponsibleAttendanceStatus = 'PRESENT' | 'ABSENT';
export type InspectionResponsibleDocumentType = 'DNI' | 'CUIL' | 'PASSPORT' | 'OTHER';
export type InspectionActSignatureStatus = 'SIGNED' | 'REFUSED' | 'ABSENT';
export type InspectionActSignerType = 'INSPECTOR' | 'COMPANY_RESPONSIBLE';
export type InspectionActUploadMode = 'IMMEDIATE' | 'DEFERRED';
export interface InspectionActResponsible {
actId: string;
attendanceStatus: InspectionResponsibleAttendanceStatus;
fullName: string | null;
documentType: InspectionResponsibleDocumentType | null;
documentNumber: string | null;
position: string | null;
email: string | null;
phone: string | null;
absenceReason: string | null;
updatedBy: string | null;
createdAt: string;
updatedAt: string;
}
export interface InspectionActSignature {
id: string;
actId: string;
signerType: InspectionActSignerType;
signerUserId: string | null;
signerName: string;
documentType: InspectionResponsibleDocumentType | null;
documentNumber: string | null;
position: string | null;
status: InspectionActSignatureStatus;
reason: string | null;
companyManifestation: 'CONFORMITY' | 'DISSENT' | null;
companyStatement: string | null;
mimeType: string | null;
sizeBytes: number | null;
imageSha256: string | null;
consentText: string | null;
consentVersion: string | null;
consentAcceptedAt: string | null;
clientSignedAt: string | null;
signedAt: string | null;
latitude: number | null;
longitude: number | null;
accuracyM: number | null;
deviceLabel: string | null;
source: 'WEB' | 'ANDROID';
preparedSha256: string;
signaturePayloadSha256: string;
uploadedBy: string;
uploadedByUsername: string;
createdAt: string;
}
export interface InspectionClosure {
act: {
id: string;
code: string;
status: InspectionActStatus;
visitId: string;
currentVersion: number;
closedAt: string | null;
closedBy: string | null;
closureSha256: string | null;
};
visit: {
id: string;
code: string;
status: InspectionVisitStatus;
actualClosedAt: string | null;
};
responsible: InspectionActResponsible | null;
closure: null | {
schemaVersion: string;
preparedSha256: string;
preparedAt: string;
preparedBy: string;
finalSha256: string | null;
deviceClosedAt: string | null;
serverClosedAt: string | null;
uploadMode: InspectionActUploadMode | null;
closedBy: string | null;
isCurrent: boolean;
};
signatures: InspectionActSignature[];
consents: {
version: string;
inspector: string;
company: string;
};
}
export interface FindingCategory {
id: string;
code: string;
name: string;
sortOrder: number;
}
export interface FindingCatalogItem {
id: string;
categoryId: string;
categoryName: string;
code: string;
sourceNumber: number;
title: string;
legalBasis: string | null;
glossary: string | null;
suggestedSeverity: number | null;
revision: number;
}
export interface FindingCatalog {
categories: FindingCategory[];
items: FindingCatalogItem[];
}
export interface FindingAdminCategory extends FindingCategory {
isActive: boolean;
itemCount: number;
activeItemCount: number;
createdAt: string;
updatedAt: string;
}
export interface FindingAdminCatalogItem extends FindingCatalogItem {
categoryCode: string;
categoryActive: boolean;
importNote: string | null;
isActive: boolean;
usageCount: number;
createdAt: string;
updatedAt: string;
}
export interface FindingAdminCatalog {
categories: FindingAdminCategory[];
items: FindingAdminCatalogItem[];
}
export interface FindingCatalogSelectionItem {
id: string;
categoryId: string;
categoryName: string;
code: string;
sourceNumber: number;
title: string;
suggestedSeverity: number | null;
enabled: boolean;
}
export interface FindingCatalogAssetTypeSelection {
assetType: { id: string; code: string; name: string; operationalRole: AssetTypeOperationalRole };
configured: boolean;
reason: string | null;
items: FindingCatalogSelectionItem[];
}
export interface FindingCatalogAssetSelection {
asset: {
id: string;
code: string;
name: string;
assetTypeId: string;
assetTypeCode: string;
assetTypeName: string;
};
typeConfigured: boolean;
typeReason: string | null;
items: Array<FindingCatalogSelectionItem & {
typeDefaultEnabled: boolean;
assetOverride: boolean | null;
}>;
}
export interface FindingCatalogApplicable extends FindingCatalog {
asset: FindingCatalogAssetSelection['asset'];
typeConfigured: boolean;
configurationReason: string | null;
other: { enabled: true; code: 'OTHER'; label: string; help: string };
}
export type FindingCatalogProposalStatus = 'PENDING' | 'MATCHED' | 'REJECTED';
export interface FindingCatalogProposal {
id: string;
findingId: string;
findingCode: string;
assetId: string;
assetCode: string;
assetName: string;
assetTypeId: string;
assetTypeCode: string;
assetTypeName: string;
proposedTitle: string;
proposedLegalBasis: string | null;
proposedSeverity: number | null;
description: string;
status: FindingCatalogProposalStatus;
resolvedCatalogItemId: string | null;
resolvedCatalogCode: string | null;
resolvedCatalogTitle: string | null;
officeNotes: string | null;
reviewedBy: string | null;
reviewedByUsername: string | null;
reviewedAt: string | null;
createdAt: string;
updatedAt: string;
}
export type InspectionFindingWorkflow =
| 'ALL'
| 'OPEN'
| 'WAITING_COMPANY'
| 'COMPANY_OVERDUE'
| 'TO_SCHEDULE_VERIFICATION'
| 'TO_VERIFY'
| 'VERIFICATION_OVERDUE'
| 'READY_TO_CLOSE'
| 'CLOSED';
export interface InspectionFindingWorkflowCounters {
open: number;
waitingCompany: number;
companyOverdue: number;
companyDueNext7Days: number;
awaitingVerificationSchedule: number;
toVerify: number;
verificationOverdue: number;
verificationNext30Days: number;
readyToClose: number;
closed: number;
}
export interface InspectionFindingGlobalPage extends Page<InspectionFinding> {
counters: InspectionFindingWorkflowCounters;
}
export type VerificationPlanningStatus = 'ALL' | 'UNPLANNED' | 'PLANNED';
export interface VerificationPlanningItem {
id: string;
code: string;
title: string;
status: 'OPEN';
nextControlOn: string;
companyResponseReceivedOn: string;
asset: { id: string; code: string; name: string; typeName: string };
company: { id: string; code: string; name: string } | null;
area: { id: string; code: string; name: string } | null;
act: { id: string; code: string };
sourceVisit: { id: string; code: string; title: string };
verificationVisit: null | {
id: string;
code: string;
title: string;
status: InspectionVisitStatus;
plannedStartAt: string | null;
plannedEndAt: string | null;
};
}
export interface VerificationPlanningCounters {
eligible: number;
unplanned: number;
overdueUnplanned: number;
dueNext30Days: number;
planned: number;
}
export interface VerificationPlanningPage extends Page<VerificationPlanningItem> {
counters: VerificationPlanningCounters;
}
export interface PlannedVerificationVisitResult {
visit: {
id: string;
code: string;
title: string;
status: InspectionVisitStatus;
plannedStartAt: string | null;
plannedEndAt: string | null;
};
company: { id: string; code: string; name: string };
area: { id: string; code: string; name: string };
findingCount: number;
assetCount: number;
}
export type InspectionFindingStatus = 'OPEN' | 'CLOSED' | 'VOIDED';
export type InspectionFindingVerificationEventType =
| 'CONTROL_DATE_DEFINED'
| 'CONTROL_DATE_CHANGED'
| 'CONTROL_DATE_CLEARED'
| 'VISIT_PLANNED'
| 'RESULT_RECORDED';
export interface InspectionFindingVerificationEvent {
id: string;
eventType: InspectionFindingVerificationEventType;
occurredAt: string;
targetControlOn: string | null;
previousControlOn: string | null;
nextControlOn: string | null;
outcome: InspectionVerificationOutcome | null;
notes: string | null;
actorUserId: string | null;
actorUsername: string | null;
verificationVisit: null | {
id: string;
code: string;
title: string;
status: InspectionVisitStatus;
plannedStartAt: string | null;
};
evidenceCount: number;
}
export type InspectionFindingVersionEvent = 'CREATED' | 'UPDATED' | 'FOLLOW_UP_UPDATED' | 'VERIFICATION_RECORDED';
export type InspectionFindingOfficeStage =
| 'WAITING_COMPANY'
| 'COMPANY_OVERDUE'
| 'DEFINE_VERIFICATION'
| 'PLAN_VERIFICATION'
| 'VERIFICATION_PLANNED'
| 'RESCHEDULE_VERIFICATION'
| 'READY_TO_CLOSE'
| 'CLOSED';
export interface InspectionFindingOfficeWorkflow {
stage: InspectionFindingOfficeStage;
label: string;
action: string;
dueOn: string | null;
overdue: boolean;
}
export interface InspectionFinding {
id: string;
actId: string;
assetId: string;
catalogItemId: string | null;
findingNumber: number;
code: string;
status: InspectionFindingStatus;
title: string;
description: string;
legalBasis: string | null;
glossary: string | null;
catalogRevision: number | null;
suggestedSeverity: number | null;
severity: number | null;
correctionDueOn: string | null;
responseDueBasis: 'FINDING_DATE' | 'REPORT_NOTIFICATION' | null;
responseDueDays: number | null;
responseDueBaseOn: string | null;
reportNotifiedOn: string | null;
companyResponse: string | null;
companyResponseReceivedOn: string | null;
companyCommittedCorrectionOn: string | null;
nextControlOn: string | null;
currentVersion: number;
closedAt: string | null;
closureNotes: string | null;
asset: InspectionAssetSummary;
catalog: {
id: string;
code: string;
categoryId: string;
categoryName: string;
sourceNumber: number;
revision: number;
suggestedSeverity: number | null;
} | null;
document: {
actId: string;
actCode: string;
actStatus: InspectionActStatus;
visitId: string;
visitCode: string;
visitTitle: string;
visitStatus: InspectionVisitStatus;
};
verificationVisit: null | {
id: string;
code: string;
title: string;
status: InspectionVisitStatus;
plannedStartAt: string | null;
};
latestVerification: null | {
visitId: string;
visitCode: string;
visitStatus: InspectionVisitStatus;
targetControlOn: string | null;
outcome: InspectionVerificationOutcome | null;
resultNotes: string | null;
verifiedAt: string | null;
resultRecordedAt: string | null;
rescheduledControlOn: string | null;
evidenceCount: number;
};
verificationHistory?: InspectionFindingVerificationEvent[];
createdAt: string;
updatedAt: string;
officeWorkflow?: InspectionFindingOfficeWorkflow;
versions?: Array<{
id: string;
versionNumber: number;
event: InspectionFindingVersionEvent;
snapshot: Record<string, unknown>;
actorUserId: string | null;
actorUsername: string | null;
createdAt: string;
}>;
}
export type InspectionEvidenceKind = 'PHOTO' | 'DOCUMENT';
export type InspectionEvidencePurpose =
| 'OBSERVATION'
| 'VERIFICATION'
| 'COMPANY_RESPONSE'
| 'COMMUNICATION_ATTACHMENT'
| 'OTHER_DOCUMENT';
export interface InspectionFindingCommunication {
id: string;
findingId: string;
direction: 'INBOUND' | 'OUTBOUND' | 'INTERNAL';
channel: 'EMAIL' | 'IN_PERSON' | 'PHONE' | 'LETTER' | 'SYSTEM' | 'OTHER';
type: 'COMPANY_RESPONSE' | 'AUTHORITY_NOTICE' | 'FOLLOW_UP' | 'OTHER';
occurredAt: string;
subject: string;
details: string | null;
contactName: string | null;
contactEmail: string | null;
createdBy: string | null;
createdByUsername: string | null;
attachmentCount: number;
createdAt: string;
}
export interface InspectionFindingEvidence {
id: string;
findingId: string;
communicationId: string | null;
verificationVisitId: string | null;
communication: {
id: string;
type: InspectionFindingCommunication['type'];
direction: InspectionFindingCommunication['direction'];
subject: string;
} | null;
kind: InspectionEvidenceKind;
purpose: InspectionEvidencePurpose;
originalName: string;
mimeType: string;
sizeBytes: number;
sha256: string;
title: string | null;
description: string | null;
capturedAt: string | null;
latitude: number | null;
longitude: number | null;
accuracyM: number | null;
deviceLabel: string | null;
source: 'WEB' | 'ANDROID' | 'IMPORT';
uploadedBy: string | null;
uploadedByUsername: string | null;
createdAt: string;
}
let csrfToken: string | null = null;
let refreshPromise: Promise<void> | null = null;
function readCsrfCookie(): string | null {
if (typeof document === 'undefined') return null;
const cookies = document.cookie.split(';').map((item) => item.trim());
const exact = cookies.find((item) => item.startsWith('dhv2_csrf='));
const fallback = cookies.find((item) => item.split('=', 1)[0]?.endsWith('_csrf'));
const raw = exact ?? fallback;
if (!raw) return null;
const separator = raw.indexOf('=');
return separator >= 0 ? decodeURIComponent(raw.slice(separator + 1)) : null;
}
function isUnsafe(method: string): boolean {
return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase());
}
async function decodeResponse<T>(response: Response): Promise<T> {
const body = await response.json().catch(() => null) as {
code?: string;
message?: string;
requestId?: string;
} | null;
if (!response.ok) {
throw new ApiError(
response.status,
body?.code ?? 'REQUEST_ERROR',
body?.message ?? `La solicitud falló con HTTP ${response.status}`,
body?.requestId,
);
}
return body as T;
}
async function rawRequest<T>(path: string, init: RequestInit = {}): Promise<T> {
const method = (init.method ?? 'GET').toUpperCase();
const headers = new Headers(init.headers);
headers.set('Accept', 'application/json');
if (init.body && !(init.body instanceof FormData)) {
headers.set('Content-Type', 'application/json');
}
if (isUnsafe(method) && path !== '/auth/login') {
const token = csrfToken ?? readCsrfCookie();
if (token) headers.set('x-csrf-token', token);
}
const response = await fetch(`${API_BASE}${path}`, {
...init,
method,
headers,
credentials: 'include',
cache: 'no-store',
});
return decodeResponse<T>(response);
}
async function refreshAccess(): Promise<void> {
if (!refreshPromise) {
refreshPromise = rawRequest<AuthResponse>('/auth/refresh', { method: 'POST' })
.then((response) => { csrfToken = response.csrfToken; })
.finally(() => { refreshPromise = null; });
}
return refreshPromise;
}
export async function apiRequest<T>(
path: string,
init: RequestInit = {},
retrySession = true,
): Promise<T> {
try {
return await rawRequest<T>(path, init);
} catch (error) {
if (
retrySession && error instanceof ApiError && error.status === 401 &&
path !== '/auth/login' && path !== '/auth/refresh'
) {
try {
await refreshAccess();
return await rawRequest<T>(path, init);
} catch (refreshError) {
csrfToken = null;
window.dispatchEvent(new Event('dhv2:unauthorized'));
throw refreshError;
}
}
throw error;
}
}
async function rawBlobRequest(path: string): Promise<Blob> {
const response = await fetch(`${API_BASE}${path}`, {
method: 'GET',
headers: { Accept: '*/*' },
credentials: 'include',
cache: 'no-store',
});
if (!response.ok) return decodeResponse<never>(response);
return response.blob();
}
async function apiBlobRequest(path: string): Promise<Blob> {
try {
return await rawBlobRequest(path);
} catch (error) {
if (error instanceof ApiError && error.status === 401) {
try {
await refreshAccess();
return await rawBlobRequest(path);
} catch (refreshError) {
csrfToken = null;
window.dispatchEvent(new Event('dhv2:unauthorized'));
throw refreshError;
}
}
throw error;
}
}
export function getHealth() { return apiRequest<HealthResponse>('/health', {}, false); }
export async function login(identifier: string, password: string) {
const response = await apiRequest<AuthResponse>('/auth/login', {
method: 'POST',
body: JSON.stringify({ identifier, password, deviceLabel: 'Panel web' }),
}, false);
csrfToken = response.csrfToken;
return response;
}
export async function getMe() {
const response = await apiRequest<{ user: AuthUser }>('/auth/me');
return response.user;
}
export async function logout() {
await apiRequest<{ status: string }>('/auth/logout', { method: 'POST' });
csrfToken = null;
}
export function changePassword(currentPassword: string, newPassword: string) {
return apiRequest<{ status: string; mustChangePassword: false }>('/auth/change-password', {
method: 'POST', body: JSON.stringify({ currentPassword, newPassword }),
});
}
export function getDashboardSummary() { return apiRequest<DashboardSummary>('/dashboard/summary'); }
export function listUsers(params: {
page?: number; pageSize?: number; search?: string; status?: 'ACTIVE' | 'INACTIVE' | '';
} = {}) {
const query = new URLSearchParams();
query.set('page', String(params.page ?? 1));
query.set('pageSize', String(params.pageSize ?? 20));
if (params.search) query.set('search', params.search);
if (params.status) query.set('status', params.status);
return apiRequest<Page<AdministrativeUser>>(`/users?${query}`);
}
export function getUser(id: string) { return apiRequest<AdministrativeUser>(`/users/${id}`); }
export function createUser(input: {
username: string; email?: string; firstName: string; lastName: string;
password: string; mustChangePassword: boolean; roleIds: string[];
}) {
return apiRequest<AdministrativeUser>('/users', {
method: 'POST', body: JSON.stringify(input),
});
}
export function updateUser(id: string, input: {
username?: string; email?: string | null; firstName?: string; lastName?: string;
}) {
return apiRequest<AdministrativeUser>(`/users/${id}`, {
method: 'PATCH', body: JSON.stringify(input),
});
}
export function resetUserPassword(id: string, password: string, mustChangePassword = true) {
return apiRequest<AdministrativeUser>(`/users/${id}/reset-password`, {
method: 'POST',
body: JSON.stringify({ password, mustChangePassword }),
});
}
export function updateUserStatus(id: string, status: 'ACTIVE' | 'INACTIVE') {
return apiRequest<AdministrativeUser>(`/users/${id}/status`, {
method: 'PATCH', body: JSON.stringify({ status }),
});
}
export function replaceUserRoles(id: string, roleIds: string[]) {
return apiRequest<AdministrativeUser>(`/users/${id}/roles`, {
method: 'PUT', body: JSON.stringify({ roleIds }),
});
}
export async function listRoles() {
return (await apiRequest<{ data: AdministrativeRole[] }>('/roles')).data;
}
export async function listPermissions() {
return (await apiRequest<{ data: Permission[] }>('/roles/permissions')).data;
}
export function createRole(input: {
code: string; name: string; description: string; permissionIds: string[];
}) {
return apiRequest<AdministrativeRole>('/roles', {
method: 'POST', body: JSON.stringify(input),
});
}
export function updateRole(id: string, input: { name: string; description: string }) {
return apiRequest<AdministrativeRole>(`/roles/${id}`, {
method: 'PATCH', body: JSON.stringify(input),
});
}
export function replaceRolePermissions(id: string, permissionIds: string[]) {
return apiRequest<AdministrativeRole>(`/roles/${id}/permissions`, {
method: 'PUT', body: JSON.stringify({ permissionIds }),
});
}
export function listAudit(params: Record<string, string | number | undefined>) {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== '') query.set(key, String(value));
});
return apiRequest<Page<AuditEventSummary>>(`/audit?${query}`);
}
export function getAuditEvent(id: string) {
return apiRequest<AuditEventDetail>(`/audit/${id}`);
}
export async function listAssetTypes() {
return (await apiRequest<{ data: AssetType[] }>('/asset-types')).data;
}
export function getMasterBootstrapStatus() {
return apiRequest<MasterBootstrapStatus>('/asset-types/bootstrap-status');
}
export function bootstrapMasterDefaults() {
return apiRequest<MasterBootstrapResult>('/asset-types/bootstrap-defaults', {
method: 'POST',
});
}
export function getMasterEnrichmentStatus() {
return apiRequest<MasterEnrichmentStatus>('/asset-types/enrichment-status');
}
export function enrichMasterDefaults() {
return apiRequest<MasterBootstrapResult>('/asset-types/enrich-defaults', {
method: 'POST',
});
}
export async function listOperationalAreas(parentId?: string) {
const query = parentId ? `?${new URLSearchParams({ parentId })}` : '';
return (await apiRequest<{ data: OperationalAssetSummary[] }>(`/asset-operational-relations/areas${query}`)).data;
}
export async function listOperationalCompanies() {
return (await apiRequest<{ data: OperationalAssetSummary[] }>('/asset-operational-relations/companies')).data;
}
export async function listCompaniesForArea(areaId: string) {
return (await apiRequest<{ data: OperationalAssetSummary[] }>(`/asset-operational-relations/areas/${areaId}/companies`)).data;
}
export async function listAreasForCompany(companyId: string) {
return (await apiRequest<{ data: OperationalAssetSummary[] }>(`/asset-operational-relations/companies/${companyId}/areas`)).data;
}
export async function listAreaCompanyRelations(params: {
areaId?: string;
companyId?: string;
includeHistory?: boolean;
} = {}) {
const query = new URLSearchParams();
if (params.areaId) query.set('areaId', params.areaId);
if (params.companyId) query.set('companyId', params.companyId);
if (params.includeHistory) query.set('includeHistory', 'true');
const suffix = query.size ? `?${query}` : '';
return (await apiRequest<{ data: AreaCompanyRelation[] }>(`/asset-operational-relations${suffix}`)).data;
}
export function createAreaCompanyRelation(input: { areaId: string; companyId: string; relationRole?: AreaOrganizationRole; participationPercent?: number | null; legalInstrument?: string | null; sourceDocumentId?: string | null; reason: string }) {
return apiRequest<AreaCompanyRelation>('/asset-operational-relations', {
method: 'POST', body: JSON.stringify(input),
});
}
export function endAreaCompanyRelation(id: string, reason: string) {
return apiRequest<AreaCompanyRelation>(`/asset-operational-relations/${id}/end`, {
method: 'POST', body: JSON.stringify({ reason }),
});
}
export function createAssetType(input: {
code: string;
name: string;
description: string;
canBeRoot: boolean;
operationalRole: AssetTypeOperationalRole;
allowedParentTypeIds: string[];
}) {
return apiRequest<AssetType>('/asset-types', {
method: 'POST', body: JSON.stringify(input),
});
}
export function updateAssetType(id: string, input: {
name?: string;
description?: string;
canBeRoot?: boolean;
isActive?: boolean;
operationalRole?: AssetTypeOperationalRole;
allowedParentTypeIds?: string[];
}) {
return apiRequest<AssetType>(`/asset-types/${id}`, {
method: 'PATCH', body: JSON.stringify(input),
});
}
export function createAssetAttribute(typeId: string, input: {
code: string;
name: string;
dataType: AssetAttributeDataType;
isRequired: boolean;
unit?: string | null;
options?: string[];
sortOrder: number;
}) {
return apiRequest<AssetType>(`/asset-types/${typeId}/attributes`, {
method: 'POST', body: JSON.stringify(input),
});
}
export function updateAssetAttribute(
typeId: string,
attributeId: string,
input: {
name?: string;
dataType?: AssetAttributeDataType;
isRequired?: boolean;
isActive?: boolean;
unit?: string | null;
options?: string[] | null;
sortOrder?: number;
},
) {
return apiRequest<AssetType>(`/asset-types/${typeId}/attributes/${attributeId}`, {
method: 'PATCH', body: JSON.stringify(input),
});
}
export function listAssets(params: {
page?: number;
pageSize?: number;
search?: string;
typeId?: string;
status?: AssetInformationStatus | '';
operationalStatus?: AssetOperationalStatus | '';
needsValidation?: boolean;
hasGeometry?: boolean;
parentId?: string;
operationalAreaId?: string;
operatorCompanyId?: string;
} = {}) {
const query = new URLSearchParams();
query.set('page', String(params.page ?? 1));
query.set('pageSize', String(params.pageSize ?? 25));
if (params.search) query.set('search', params.search);
if (params.typeId) query.set('typeId', params.typeId);
if (params.status) query.set('status', params.status);
if (params.operationalStatus) query.set('operationalStatus', params.operationalStatus);
if (params.needsValidation !== undefined) query.set('needsValidation', String(params.needsValidation));
if (params.hasGeometry !== undefined) query.set('hasGeometry', String(params.hasGeometry));
if (params.parentId) query.set('parentId', params.parentId);
if (params.operationalAreaId) query.set('operationalAreaId', params.operationalAreaId);
if (params.operatorCompanyId) query.set('operatorCompanyId', params.operatorCompanyId);
return apiRequest<Page<AssetListItem>>(`/assets?${query}`);
}
export async function listAssetTree(params: {
search?: string;
typeId?: string;
status?: AssetInformationStatus | '';
operationalStatus?: AssetOperationalStatus | '';
operationalAreaId?: string;
operatorCompanyId?: string;
needsValidation?: boolean;
hasGeometry?: boolean;
} = {}) {
const query = new URLSearchParams();
if (params.search) query.set('search', params.search);
if (params.typeId) query.set('typeId', params.typeId);
if (params.status) query.set('status', params.status);
if (params.operationalStatus) query.set('operationalStatus', params.operationalStatus);
if (params.operationalAreaId) query.set('operationalAreaId', params.operationalAreaId);
if (params.operatorCompanyId) query.set('operatorCompanyId', params.operatorCompanyId);
if (params.needsValidation !== undefined) query.set('needsValidation', String(params.needsValidation));
if (params.hasGeometry !== undefined) query.set('hasGeometry', String(params.hasGeometry));
const suffix = query.toString() ? `?${query}` : '';
return apiRequest<{ data: AssetListItem[]; meta: { count: number; truncated: boolean } }>(`/assets/tree${suffix}`);
}
export async function listAssetTreeChildren(params: {
parentId?: string; search?: string; typeId?: string; status?: AssetInformationStatus | '';
operationalStatus?: AssetOperationalStatus | ''; operationalAreaId?: string; operatorCompanyId?: string;
needsValidation?: boolean; hasGeometry?: boolean; limit?: number;
} = {}) {
const query = new URLSearchParams();
if (params.parentId) query.set('parentId', params.parentId);
if (params.search) query.set('search', params.search);
if (params.typeId) query.set('typeId', params.typeId);
if (params.status) query.set('status', params.status);
if (params.operationalStatus) query.set('operationalStatus', params.operationalStatus);
if (params.operationalAreaId) query.set('operationalAreaId', params.operationalAreaId);
if (params.operatorCompanyId) query.set('operatorCompanyId', params.operatorCompanyId);
if (params.needsValidation !== undefined) query.set('needsValidation', String(params.needsValidation));
if (params.hasGeometry !== undefined) query.set('hasGeometry', String(params.hasGeometry));
query.set('limit', String(params.limit ?? 100));
return apiRequest<{ data: AssetListItem[]; meta: { count: number; hasMore: boolean; parentId: string | null } }>(`/assets/tree-children?${query}`);
}
export async function listAssetParentOptions(
childTypeId: string,
assetId?: string,
search?: string,
) {
const query = new URLSearchParams({ childTypeId });
if (assetId) query.set('assetId', assetId);
if (search) query.set('search', search);
return (await apiRequest<{ data: AssetListItem[] }>(`/assets/parent-options?${query}`)).data;
}
export function getAsset(id: string) {
return apiRequest<AssetDetail>(`/assets/${id}`);
}
export function getAssetDossier(id: string) {
return apiRequest<AssetDossier>(`/assets/${id}/dossier`);
}
export async function getAssetLineage(id: string) {
return (await apiRequest<{ data: AssetLineageItem[] }>(`/assets/${id}/lineage`)).data;
}
export function createAsset(input: {
code: string;
name: string;
commonName?: string | null;
typeId: string;
parentId?: string | null;
operationalAreaId?: string | null;
operatorCompanyId?: string | null;
description?: string | null;
informationStatus?: AssetInformationStatus;
attributes: Record<string, unknown>;
}) {
return apiRequest<AssetDetail>('/assets', {
method: 'POST', body: JSON.stringify(input),
});
}
export function updateAsset(id: string, input: {
typeId?: string;
code?: string;
name?: string;
commonName?: string | null;
parentId?: string | null;
operationalAreaId?: string | null;
operatorCompanyId?: string | null;
description?: string | null;
attributes?: Record<string, unknown>;
}) {
return apiRequest<AssetDetail>(`/assets/${id}`, {
method: 'PATCH', body: JSON.stringify(input),
});
}
export function updateAssetInformationStatus(
id: string,
informationStatus: AssetInformationStatus,
) {
return apiRequest<AssetDetail>(`/assets/${id}/information-status`, {
method: 'PATCH', body: JSON.stringify({ informationStatus }),
});
}
export function updateAssetOperationalStatus(id: string, status: AssetOperationalStatus) {
return apiRequest<AssetDetail>(`/assets/${id}/operational-status`, {
method: 'PATCH', body: JSON.stringify({ status }),
});
}
export async function listAssetContextHistory(assetId: string) {
return (await apiRequest<{ data: AssetContextHistoryItem[] }>(`/assets/${assetId}/context-history`)).data;
}
export function changeAssetContext(assetId: string, input: {
parentId?: string | null;
operationalAreaId?: string | null;
operatorCompanyId?: string | null;
effectiveAt?: string;
reason: string;
}) {
return apiRequest<AssetDetail>(`/assets/${assetId}/context`, {
method: 'POST', body: JSON.stringify(input),
});
}
export function listFieldDiscoveries(params: { page?: number; pageSize?: number; status?: FieldDiscoveryStatus; search?: string } = {}) {
const query = new URLSearchParams();
query.set('page', String(params.page ?? 1));
query.set('pageSize', String(params.pageSize ?? 25));
if (params.status) query.set('status', params.status);
if (params.search) query.set('search', params.search);
return apiRequest<Page<FieldDiscovery>>(`/assets/field-discoveries?${query}`);
}
export function approveFieldDiscovery(id: string, notes?: string | null) {
return apiRequest<FieldDiscovery>(`/assets/field-discoveries/${id}/approve`, { method: 'POST', body: JSON.stringify({ notes: notes || null }) });
}
export function rejectFieldDiscovery(id: string, reason: string) {
return apiRequest<FieldDiscovery>(`/assets/field-discoveries/${id}/reject`, { method: 'POST', body: JSON.stringify({ reason }) });
}
export function matchFieldDiscovery(id: string, matchedAssetId: string, reason: string) {
return apiRequest<FieldDiscovery>(`/assets/field-discoveries/${id}/match`, { method: 'POST', body: JSON.stringify({ matchedAssetId, reason }) });
}
export function getAssetRegistry(assetId: string) {
return apiRequest<AssetRegistry>(`/assets/${assetId}/registry`);
}
export async function listSourceDocuments(search = '') {
const suffix = search.trim() ? `?search=${encodeURIComponent(search.trim())}` : '';
return (await apiRequest<{ data: SourceDocumentSummary[] }>(`/source-documents${suffix}`)).data;
}
export function createSourceDocument(input: { documentType: SourceDocumentType; documentNumber?: string | null; title: string; issuer?: string | null; documentDate?: string | null; externalReference?: string | null; notes?: string | null }) {
return apiRequest<SourceDocumentSummary>('/source-documents', { method: 'POST', body: JSON.stringify(input) });
}
export function linkAssetSourceDocument(assetId: string, documentId: string, input: { relationType: AssetSourceDocumentRelationType; notes?: string | null }) {
return apiRequest(`/assets/${assetId}/source-documents/${documentId}`, { method: 'POST', body: JSON.stringify(input) });
}
export function addAssetExternalIdentifier(assetId: string, input: { namespace: string; value: string; validFrom?: string | null; sourceDocumentId?: string | null; notes?: string | null }) {
return apiRequest(`/assets/${assetId}/external-identifiers`, { method: 'POST', body: JSON.stringify(input) });
}
export function endAssetExternalIdentifier(id: string, reason: string) {
return apiRequest(`/asset-external-identifiers/${id}/end`, { method: 'POST', body: JSON.stringify({ reason }) });
}
export function upsertOrganizationProfile(assetId: string, input: { organizationKind: OrganizationKind; legalName?: string | null; taxId?: string | null; notificationEmail?: string | null; notes?: string | null }) {
return apiRequest(`/assets/${assetId}/organization-profile`, { method: 'PATCH', body: JSON.stringify(input) });
}
export function addOrganizationMembership(organizationId: string, input: { memberOrganizationId: string; role: OrganizationMembershipRole; participationPercent?: number | null; validFrom?: string | null; sourceDocumentId?: string | null; notes?: string | null }) {
return apiRequest(`/organizations/${organizationId}/memberships`, { method: 'POST', body: JSON.stringify(input) });
}
export function endOrganizationMembership(id: string, reason: string) {
return apiRequest(`/organization-memberships/${id}/end`, { method: 'POST', body: JSON.stringify({ reason }) });
}
export function createAreaLegalRight(areaId: string, input: { rightType: AreaLegalRightType; name: string; instrumentNumber?: string | null; validFrom?: string | null; validUntil?: string | null; status?: AreaLegalRightStatus; sourceDocumentId?: string | null; notes?: string | null }) {
return apiRequest(`/areas/${areaId}/legal-rights`, { method: 'POST', body: JSON.stringify(input) });
}
export function addAreaLegalRightOrganization(rightId: string, input: { organizationId: string; role: AreaLegalRightOrganizationRole; participationPercent?: number | null; validFrom?: string | null; notes?: string | null }) {
return apiRequest(`/area-legal-rights/${rightId}/organizations`, { method: 'POST', body: JSON.stringify(input) });
}
export function endAreaLegalRightOrganization(id: string, reason: string) {
return apiRequest(`/area-legal-right-organizations/${id}/end`, { method: 'POST', body: JSON.stringify({ reason }) });
}
export async function getAssetGeometry(assetId: string) {
return (await apiRequest<{ data: AssetGeometry | null }>(`/assets/${assetId}/geometry`)).data;
}
export function upsertAssetGeometry(assetId: string, input: {
geometry: GeoJsonGeometry;
accuracyM?: number | null;
capturedAt?: string | null;
deviceLabel?: string | null;
}) {
return apiRequest<AssetGeometry>(`/assets/${assetId}/geometry`, {
method: 'PUT', body: JSON.stringify(input),
});
}
export function removeAssetGeometry(assetId: string) {
return apiRequest<{ status: 'removed' | 'absent' }>(`/assets/${assetId}/geometry`, {
method: 'DELETE',
});
}
export function getMapAssets(params: {
bbox?: string;
typeId?: string;
status?: AssetInformationStatus | '';
geometryType?: AssetGeometryType | '';
} = {}) {
const query = new URLSearchParams();
if (params.bbox) query.set('bbox', params.bbox);
if (params.typeId) query.set('typeId', params.typeId);
if (params.status) query.set('status', params.status);
if (params.geometryType) query.set('geometryType', params.geometryType);
const suffix = query.size ? `?${query}` : '';
return apiRequest<MapAssetFeatureCollection>(`/map/assets${suffix}`);
}
export function listAssetVersions(params: {
page?: number;
pageSize?: number;
search?: string;
typeId?: string;
status?: AssetInformationStatus | '';
changeType?: AssetVersionChangeType | '';
from?: string;
to?: string;
} = {}) {
const query = new URLSearchParams();
query.set('page', String(params.page ?? 1));
query.set('pageSize', String(params.pageSize ?? 25));
if (params.search) query.set('search', params.search);
if (params.typeId) query.set('typeId', params.typeId);
if (params.status) query.set('status', params.status);
if (params.changeType) query.set('changeType', params.changeType);
if (params.from) query.set('from', params.from);
if (params.to) query.set('to', params.to);
return apiRequest<Page<AssetVersionSummary>>(`/asset-versions?${query}`);
}
export function listAssetVersionTimeline(
assetId: string,
params: { page?: number; pageSize?: number } = {},
) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 10),
});
return apiRequest<Page<AssetVersionSummary>>(`/assets/${assetId}/versions?${query}`);
}
export function getAssetVersion(assetId: string, versionNumber: number) {
return apiRequest<AssetVersionDetail>(`/assets/${assetId}/versions/${versionNumber}`);
}
export async function listAssetMedia(assetId: string) {
return (await apiRequest<{ data: AssetMedia[] }>(`/assets/${assetId}/media`)).data;
}
export function uploadAssetMedia(assetId: string, input: {
file: File;
kind: AssetMediaKind;
title?: string;
description?: string;
capturedAt?: string;
latitude?: number;
longitude?: number;
accuracyM?: number;
}) {
const body = new FormData();
body.append('file', input.file);
body.append('kind', input.kind);
if (input.title) body.append('title', input.title);
if (input.description) body.append('description', input.description);
if (input.capturedAt) body.append('capturedAt', input.capturedAt);
if (input.latitude !== undefined) body.append('latitude', String(input.latitude));
if (input.longitude !== undefined) body.append('longitude', String(input.longitude));
if (input.accuracyM !== undefined) body.append('accuracyM', String(input.accuracyM));
return apiRequest<AssetMedia>(`/assets/${assetId}/media`, {
method: 'POST',
body,
});
}
export function updateAssetMedia(mediaId: string, input: {
title?: string | null;
description?: string | null;
capturedAt?: string | null;
latitude?: number | null;
longitude?: number | null;
accuracyM?: number | null;
}) {
return apiRequest<AssetMedia>(`/asset-media/${mediaId}`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export function removeAssetMedia(mediaId: string) {
return apiRequest<{ status: 'removed' }>(`/asset-media/${mediaId}`, {
method: 'DELETE',
});
}
export function getAssetMediaBlob(mediaId: string, download = false) {
return apiBlobRequest(`/asset-media/${mediaId}/content${download ? '?download=1' : ''}`);
}
export function getAssetProvenance(assetId: string) {
return apiRequest<AssetProvenance>(`/assets/${assetId}/provenance`);
}
export function updateAssetProvenance(assetId: string, input: {
origin: AssetDataOrigin;
sourceName?: string | null;
sourceReference?: string | null;
observedAt?: string | null;
notes?: string | null;
}) {
return apiRequest<AssetProvenance>(`/assets/${assetId}/provenance`, {
method: 'PUT',
body: JSON.stringify(input),
});
}
export function verifyAssetProvenance(assetId: string) {
return apiRequest<AssetProvenance>(`/assets/${assetId}/provenance/verify`, {
method: 'POST',
});
}
export function listTemporalAssets(params: {
at: string;
page?: number;
pageSize?: number;
search?: string;
typeId?: string;
status?: AssetInformationStatus | '';
}) {
const query = new URLSearchParams({
at: params.at,
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.typeId) query.set('typeId', params.typeId);
if (params.status) query.set('status', params.status);
return apiRequest<Page<TemporalAssetSummary> & { asOf: string }>(
`/temporal-assets?${query}`,
);
}
export function getTemporalAsset(assetId: string, at: string) {
const query = new URLSearchParams({ at });
return apiRequest<TemporalAssetDetail>(`/temporal-assets/${assetId}?${query}`);
}
export function listSurveyCampaigns(params: {
page?: number;
pageSize?: number;
search?: string;
status?: SurveyCampaignStatus | '';
coordinatorUserId?: string;
} = {}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.status) query.set('status', params.status);
if (params.coordinatorUserId) query.set('coordinatorUserId', params.coordinatorUserId);
return apiRequest<Page<SurveyCampaignListItem>>(`/survey-campaigns?${query}`);
}
export function getSurveyCampaign(id: string) {
return apiRequest<SurveyCampaign>(`/survey-campaigns/${id}`);
}
export async function listSurveyAssignees() {
return (await apiRequest<{ data: SurveyPerson[] }>('/survey-campaigns/assignees')).data;
}
export function createSurveyCampaign(input: {
code: string;
name: string;
description?: string | null;
plannedStartAt?: string | null;
plannedEndAt?: string | null;
scopeAssetId?: string | null;
coordinatorUserId?: string | null;
}) {
return apiRequest<SurveyCampaign>('/survey-campaigns', {
method: 'POST',
body: JSON.stringify(input),
});
}
export function updateSurveyCampaign(id: string, input: {
code?: string;
name?: string;
description?: string | null;
plannedStartAt?: string | null;
plannedEndAt?: string | null;
scopeAssetId?: string | null;
coordinatorUserId?: string | null;
}) {
return apiRequest<SurveyCampaign>(`/survey-campaigns/${id}`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export function updateSurveyCampaignStatus(id: string, status: SurveyCampaignStatus) {
return apiRequest<SurveyCampaign>(`/survey-campaigns/${id}/status`, {
method: 'PUT',
body: JSON.stringify({ status }),
});
}
export function addSurveyTarget(campaignId: string, input: {
assetId: string;
assignedUserId?: string | null;
dueAt?: string | null;
instructions?: string | null;
}) {
return apiRequest<SurveyCampaign>(`/survey-campaigns/${campaignId}/targets`, {
method: 'POST',
body: JSON.stringify(input),
});
}
export function updateSurveyTarget(id: string, input: {
dueAt?: string | null;
instructions?: string | null;
}) {
return apiRequest<SurveyCampaign>(`/survey-campaign-targets/${id}`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export function assignSurveyTarget(id: string, assignedUserId: string | null) {
return apiRequest<SurveyCampaign>(`/survey-campaign-targets/${id}/assignment`, {
method: 'PUT',
body: JSON.stringify({ assignedUserId }),
});
}
export function updateSurveyTargetStatus(id: string, status: SurveyTargetStatus) {
return apiRequest<SurveyCampaign>(`/survey-campaign-targets/${id}/status`, {
method: 'PUT',
body: JSON.stringify({ status }),
});
}
export function getSurveyExecution(targetId: string) {
return apiRequest<SurveyExecution>(`/survey-campaign-targets/${targetId}/report`);
}
export function saveSurveyReport(targetId: string, input: {
outcome: SurveyReportOutcome | null;
observedAt: string | null;
latitude: number | null;
longitude: number | null;
accuracyM: number | null;
notes: string | null;
mediaIds: string[];
}) {
return apiRequest<SurveyExecution>(`/survey-campaign-targets/${targetId}/report`, {
method: 'PUT',
body: JSON.stringify(input),
});
}
export function submitSurveyReport(targetId: string) {
return apiRequest<SurveyExecution>(`/survey-campaign-targets/${targetId}/report/submit`, {
method: 'POST',
});
}
export function reviewSurveyReport(
targetId: string,
decision: 'APPROVE' | 'REJECT',
notes: string | null,
) {
return apiRequest<SurveyExecution>(`/survey-campaign-targets/${targetId}/report/review`, {
method: 'POST',
body: JSON.stringify({ decision, notes }),
});
}
export function listInspectionVisits(params: {
page?: number;
pageSize?: number;
search?: string;
status?: InspectionVisitStatus | '';
companyId?: string;
areaId?: string;
inspectorId?: string;
dateFrom?: string;
dateTo?: string;
}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.status) query.set('status', params.status);
if (params.companyId) query.set('companyId', params.companyId);
if (params.areaId) query.set('areaId', params.areaId);
if (params.inspectorId) query.set('inspectorId', params.inspectorId);
if (params.dateFrom) query.set('dateFrom', params.dateFrom);
if (params.dateTo) query.set('dateTo', params.dateTo);
return apiRequest<Page<InspectionVisitListItem>>(`/inspection-visits?${query}`);
}
export function getInspectionVisit(id: string) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}`);
}
export async function listInspectionAssignees() {
return (await apiRequest<{ data: InspectionPerson[] }>('/inspection-visits/assignees')).data;
}
export async function listInspectionPlanningAreas() {
return (await apiRequest<{ data: InspectionPlanningContextAsset[] }>('/inspection-visits/planning-context/areas')).data;
}
export async function listInspectionPlanningOperators(areaId: string) {
return (await apiRequest<{ data: InspectionPlanningContextAsset[] }>(`/inspection-visits/planning-context/areas/${areaId}/operators`)).data;
}
export function createInspectionVisit(input: {
operationalAreaId: string;
operatorCompanyId: string;
plannedStartAt: string;
leadInspectorUserId: string;
}) {
return apiRequest<InspectionVisit>('/inspection-visits', {
method: 'POST',
body: JSON.stringify(input),
});
}
export function updateInspectionVisit(id: string, input: {
objective?: string | null;
scopeAssetId?: string | null;
operationalAreaId?: string | null;
operatorCompanyId?: string | null;
plannedStartAt?: string | null;
instructions?: string | null;
}) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export function replaceInspectionVisitAssets(id: string, assetIds: string[]) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}/assets`, {
method: 'PUT',
body: JSON.stringify({ assetIds }),
});
}
export function generateInspectionVisitChecklist(id: string) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}/checklist/generate`, {
method: 'POST',
});
}
export function excludeInspectionVisitAsset(id: string, assetId: string, reason: string) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}/assets/${assetId}/exclude`, {
method: 'POST',
body: JSON.stringify({ reason }),
});
}
export function includeInspectionVisitAsset(id: string, assetId: string) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}/assets/${assetId}/include`, {
method: 'POST',
});
}
export function replaceInspectionVisitTeam(
id: string,
leadInspectorUserId: string | null,
memberUserIds: string[],
) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}/team`, {
method: 'PUT',
body: JSON.stringify({ leadInspectorUserId, memberUserIds }),
});
}
export function updateInspectionVisitStatus(
id: string,
status: InspectionVisitStatus,
reason?: string | null,
) {
return apiRequest<InspectionVisit>(`/inspection-visits/${id}/status`, {
method: 'PUT',
body: JSON.stringify({ status, reason: reason ?? null }),
});
}
export function listInspectionActs(visitId: string, params: {
page?: number;
pageSize?: number;
search?: string;
status?: InspectionActStatus | '';
} = {}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.status) query.set('status', params.status);
return apiRequest<Page<InspectionActListItem>>(
`/inspection-visits/${visitId}/acts?${query}`,
);
}
export function listInspectionActsGlobal(params: {
page?: number;
pageSize?: number;
search?: string;
status?: InspectionActStatus | '';
year?: number | '';
companyId?: string;
areaId?: string;
inspectorId?: string;
dateFrom?: string;
dateTo?: string;
} = {}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.status) query.set('status', params.status);
if (params.year) query.set('year', String(params.year));
if (params.companyId) query.set('companyId', params.companyId);
if (params.areaId) query.set('areaId', params.areaId);
if (params.inspectorId) query.set('inspectorId', params.inspectorId);
if (params.dateFrom) query.set('dateFrom', params.dateFrom);
if (params.dateTo) query.set('dateTo', params.dateTo);
return apiRequest<Page<InspectionActListItem>>(`/inspection-acts?${query}`);
}
export function listInspectionReports(params: {
page?: number;
pageSize?: number;
search?: string;
year?: number | '';
companyId?: string;
areaId?: string;
inspectorId?: string;
dateFrom?: string;
dateTo?: string;
} = {}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.year) query.set('year', String(params.year));
if (params.companyId) query.set('companyId', params.companyId);
if (params.areaId) query.set('areaId', params.areaId);
if (params.inspectorId) query.set('inspectorId', params.inspectorId);
if (params.dateFrom) query.set('dateFrom', params.dateFrom);
if (params.dateTo) query.set('dateTo', params.dateTo);
return apiRequest<Page<InspectionReportListItem>>(`/inspection-reports?${query}`);
}
export function listPendingInspectionReports(params: {
page?: number;
pageSize?: number;
search?: string;
year?: number | '';
companyId?: string;
areaId?: string;
inspectorId?: string;
dateFrom?: string;
dateTo?: string;
} = {}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.year) query.set('year', String(params.year));
if (params.companyId) query.set('companyId', params.companyId);
if (params.areaId) query.set('areaId', params.areaId);
if (params.inspectorId) query.set('inspectorId', params.inspectorId);
if (params.dateFrom) query.set('dateFrom', params.dateFrom);
if (params.dateTo) query.set('dateTo', params.dateTo);
return apiRequest<Page<PendingInspectionReportItem>>(`/inspection-reports/pending?${query}`);
}
export function getInspectionReport(id: string) {
return apiRequest<InspectionReport>(`/inspection-reports/${id}`);
}
export function getInspectionReportReview(id: string) {
return apiRequest<InspectionReportReview>(`/inspection-reports/${id}/review`);
}
export function uploadInspectionReportRevision(id: string, file: File, changeSummary: string) {
const form = new FormData();
form.append('file', file);
form.append('changeSummary', changeSummary);
return apiRequest<InspectionReportReview>(`/inspection-reports/${id}/review/revisions`, { method: 'POST', body: form });
}
export function approveInspectionReport(id: string, note?: string) {
return apiRequest<InspectionReportReview>(`/inspection-reports/${id}/review/approve`, { method: 'POST', body: JSON.stringify({ note: note?.trim() || undefined }) });
}
export function signFinalInspectionReport(id: string) {
return apiRequest<InspectionReportReview>(`/inspection-reports/${id}/review/sign-final`, { method: 'POST', body: JSON.stringify({ confirmation: true }) });
}
export function inspectionReportRevisionUrl(revisionId: string) {
return `${API_BASE}/inspection-report-revisions/${revisionId}/content`;
}
export function getInspectionAct(id: string) {
return apiRequest<InspectionAct>(`/inspection-acts/${id}`);
}
export function getInspectionClosure(actId: string) {
return apiRequest<InspectionClosure>(`/inspection-acts/${actId}/closure`);
}
export function getInspectionSignatureBlob(signatureId: string) {
return apiBlobRequest(`/inspection-act-signatures/${signatureId}/content`);
}
export function getFindingCatalog(params: { categoryId?: string; search?: string } = {}) {
const query = new URLSearchParams();
if (params.categoryId) query.set('categoryId', params.categoryId);
if (params.search) query.set('search', params.search);
return apiRequest<FindingCatalog>(`/finding-catalog${query.size ? `?${query}` : ''}`);
}
export function getFindingCatalogAdmin() {
return apiRequest<FindingAdminCatalog>('/finding-catalog/admin');
}
export function getApplicableFindingCatalog(assetId: string, params: { categoryId?: string; search?: string } = {}) {
const query = new URLSearchParams();
if (params.categoryId) query.set('categoryId', params.categoryId);
if (params.search) query.set('search', params.search);
return apiRequest<FindingCatalogApplicable>(`/finding-catalog/applicable/${assetId}${query.size ? `?${query}` : ''}`);
}
export function getFindingCatalogAssetTypeSelection(assetTypeId: string) {
return apiRequest<FindingCatalogAssetTypeSelection>(`/finding-catalog/asset-types/${assetTypeId}/selection`);
}
export function replaceFindingCatalogAssetTypeSelection(assetTypeId: string, input: { enabledItemIds: string[]; reason: string }) {
return apiRequest<FindingCatalogAssetTypeSelection>(`/finding-catalog/asset-types/${assetTypeId}/selection`, {
method: 'PUT',
body: JSON.stringify(input),
});
}
export function getFindingCatalogAssetSelection(assetId: string) {
return apiRequest<FindingCatalogAssetSelection>(`/finding-catalog/assets/${assetId}/selection`);
}
export function replaceFindingCatalogAssetSelection(assetId: string, input: { enabledItemIds: string[]; reason: string }) {
return apiRequest<FindingCatalogAssetSelection>(`/finding-catalog/assets/${assetId}/selection`, {
method: 'PUT',
body: JSON.stringify(input),
});
}
export function listFindingCatalogProposals(params: { status?: FindingCatalogProposalStatus; assetTypeId?: string } = {}) {
const query = new URLSearchParams();
if (params.status) query.set('status', params.status);
if (params.assetTypeId) query.set('assetTypeId', params.assetTypeId);
return apiRequest<FindingCatalogProposal[]>(`/finding-catalog/proposals${query.size ? `?${query}` : ''}`);
}
export function reviewFindingCatalogProposal(id: string, input: {
decision: 'MATCH' | 'REJECT';
catalogItemId?: string;
notes?: string | null;
}) {
return apiRequest<FindingCatalogProposal>(`/finding-catalog/proposals/${id}/review`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export function createFindingCategory(input: {
code: string;
name: string;
sortOrder: number;
}) {
return apiRequest<FindingAdminCategory>('/finding-catalog/categories', {
method: 'POST',
body: JSON.stringify(input),
});
}
export function updateFindingCategory(id: string, input: {
name?: string;
sortOrder?: number;
isActive?: boolean;
}) {
return apiRequest<FindingAdminCategory>(`/finding-catalog/categories/${id}`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export interface FindingCatalogItemInput {
categoryId: string;
sourceNumber: number;
title: string;
legalBasis?: string | null;
glossary?: string | null;
importNote?: string | null;
suggestedSeverity?: number | null;
}
export function createFindingCatalogItem(input: FindingCatalogItemInput & { code: string }) {
return apiRequest<FindingAdminCatalogItem>('/finding-catalog/items', {
method: 'POST',
body: JSON.stringify(input),
});
}
export function updateFindingCatalogItem(id: string, input: FindingCatalogItemInput & {
isActive: boolean;
}) {
return apiRequest<FindingAdminCatalogItem>(`/finding-catalog/items/${id}`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export async function listInspectionFindings(actId: string) {
return (await apiRequest<{ data: InspectionFinding[] }>(
`/inspection-acts/${actId}/findings`,
)).data;
}
export function listInspectionFindingsGlobal(params: {
page?: number;
pageSize?: number;
search?: string;
workflow?: InspectionFindingWorkflow;
companyId?: string;
areaId?: string;
inspectorId?: string;
dateFrom?: string;
dateTo?: string;
} = {}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 25),
});
if (params.search) query.set('search', params.search);
if (params.workflow) query.set('workflow', params.workflow);
if (params.companyId) query.set('companyId', params.companyId);
if (params.areaId) query.set('areaId', params.areaId);
if (params.inspectorId) query.set('inspectorId', params.inspectorId);
if (params.dateFrom) query.set('dateFrom', params.dateFrom);
if (params.dateTo) query.set('dateTo', params.dateTo);
return apiRequest<InspectionFindingGlobalPage>(`/inspection-findings?${query}`);
}
export function getInspectionFinding(id: string) {
return apiRequest<InspectionFinding>(`/inspection-findings/${id}`);
}
export function getInspectionFindingVerificationHistory(id: string) {
return apiRequest<InspectionFindingVerificationEvent[]>(`/inspection-findings/${id}/verification-history`);
}
export function listVerificationPlanning(params: {
page?: number;
pageSize?: number;
search?: string;
planningStatus?: VerificationPlanningStatus;
companyId?: string;
areaId?: string;
dueFrom?: string;
dueTo?: string;
} = {}) {
const query = new URLSearchParams({
page: String(params.page ?? 1),
pageSize: String(params.pageSize ?? 50),
});
if (params.search) query.set('search', params.search);
if (params.planningStatus) query.set('planningStatus', params.planningStatus);
if (params.companyId) query.set('companyId', params.companyId);
if (params.areaId) query.set('areaId', params.areaId);
if (params.dueFrom) query.set('dueFrom', params.dueFrom);
if (params.dueTo) query.set('dueTo', params.dueTo);
return apiRequest<VerificationPlanningPage>(`/inspection-verifications?${query}`);
}
export function planVerificationVisit(input: {
findingIds: string[];
plannedStartAt: string;
notes?: string | null;
}) {
return apiRequest<PlannedVerificationVisitResult>('/inspection-verifications/plan', {
method: 'POST',
body: JSON.stringify(input),
});
}
export function recordVerificationResult(visitId: string, findingId: string, input: {
outcome: InspectionVerificationOutcome;
notes: string;
verifiedAt?: string;
nextControlOn?: string;
}) {
return apiRequest<{
findingId: string;
visitId: string;
targetControlOn: string | null;
outcome: InspectionVerificationOutcome;
notes: string;
verifiedAt: string;
recordedAt: string;
rescheduledControlOn: string | null;
nextControlOn: string | null;
findingStatus: InspectionFindingStatus;
visitStatus: InspectionVisitStatus;
}>(`/inspection-verifications/visits/${visitId}/findings/${findingId}/result`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export function closeInspectionFinding(id: string, closureNotes: string) {
return apiRequest<InspectionFinding>(`/inspection-findings/${id}/close`, {
method: 'PATCH',
body: JSON.stringify({ closureNotes }),
});
}
export function updateInspectionFindingFollowUp(id: string, input: {
responseDueBasis?: 'FINDING_DATE' | 'REPORT_NOTIFICATION' | null;
responseDueDays?: number | null;
reportNotifiedOn?: string | null;
companyResponse?: string | null;
companyResponseReceivedOn?: string | null;
companyCommittedCorrectionOn?: string | null;
nextControlOn?: string | null;
}) {
return apiRequest<InspectionFinding>(`/inspection-findings/${id}/follow-up`, {
method: 'PATCH',
body: JSON.stringify(input),
});
}
export async function listInspectionFindingEvidence(findingId: string) {
return (await apiRequest<{ data: InspectionFindingEvidence[] }>(
`/inspection-findings/${findingId}/evidence`,
)).data;
}
export function uploadInspectionFindingEvidence(findingId: string, input: {
file: File;
kind: InspectionEvidenceKind;
purpose: InspectionEvidencePurpose;
communicationId?: string;
verificationVisitId?: string;
title?: string;
description?: string;
capturedAt?: string;
latitude?: number;
longitude?: number;
accuracyM?: number;
deviceLabel?: string;
}) {
const body = new FormData();
body.append('file', input.file);
body.append('kind', input.kind);
body.append('purpose', input.purpose);
if (input.communicationId) body.append('communicationId', input.communicationId);
if (input.verificationVisitId) body.append('verificationVisitId', input.verificationVisitId);
if (input.title) body.append('title', input.title);
if (input.description) body.append('description', input.description);
if (input.capturedAt) body.append('capturedAt', input.capturedAt);
if (input.latitude !== undefined) body.append('latitude', String(input.latitude));
if (input.longitude !== undefined) body.append('longitude', String(input.longitude));
if (input.accuracyM !== undefined) body.append('accuracyM', String(input.accuracyM));
if (input.deviceLabel) body.append('deviceLabel', input.deviceLabel);
return apiRequest<InspectionFindingEvidence>(
`/inspection-findings/${findingId}/evidence`,
{ method: 'POST', body },
);
}
export function getInspectionFindingEvidenceBlob(evidenceId: string, download = false) {
return apiBlobRequest(
`/inspection-finding-evidence/${evidenceId}/content${download ? '?download=1' : ''}`,
);
}
export async function listInspectionFindingCommunications(findingId: string) {
return (await apiRequest<{ data: InspectionFindingCommunication[] }>(
`/inspection-findings/${findingId}/communications`,
)).data;
}
export function createInspectionFindingCommunication(findingId: string, input: {
direction: InspectionFindingCommunication['direction'];
channel: InspectionFindingCommunication['channel'];
type: InspectionFindingCommunication['type'];
occurredAt: string;
subject: string;
details?: string | null;
contactName?: string | null;
contactEmail?: string | null;
}) {
return apiRequest<InspectionFindingCommunication>(
`/inspection-findings/${findingId}/communications`,
{ method: 'POST', body: JSON.stringify(input) },
);
}
export function listAssetImportBatches(params: { page?: number; pageSize?: number; status?: AssetImportBatchStatus | '' } = {}) {
const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 20) });
if (params.status) query.set('status', params.status);
return apiRequest<Page<AssetImportBatch>>(`/asset-imports?${query}`);
}
export function listAssetImportReviews(params: { page?: number; pageSize?: number; kind?: 'ALL' | 'DIRECT' | 'DEPENDENCY' } = {}) {
const query = new URLSearchParams();
if (params.page) query.set('page', String(params.page));
if (params.pageSize) query.set('pageSize', String(params.pageSize));
if (params.kind && params.kind !== 'ALL') query.set('kind', params.kind);
const suffix = query.toString() ? `?${query}` : '';
return apiRequest<{ data: AssetImportReviewItem[]; meta: { page: number; pageSize: number; total: number; totalPages: number } }>(`/asset-imports/reviews${suffix}`);
}
export function getAssetImportBatch(id: string) {
return apiRequest<AssetImportBatchDetail>(`/asset-imports/${id}`);
}
export function listAssetImportRows(id: string, params: { page?: number; pageSize?: number; status?: AssetImportRowStatus | ''; search?: string } = {}) {
const query = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 50) });
if (params.status) query.set('status', params.status);
if (params.search) query.set('search', params.search);
return apiRequest<Page<AssetImportRow>>(`/asset-imports/${id}/rows?${query}`);
}
export function uploadAssetImport(input: { file: File; sourceLabel?: string; notes?: string; profileCode?: Exclude<AssetImportProfileCode, 'UNKNOWN'> }) {
const body = new FormData();
body.append('file', input.file);
if (input.sourceLabel?.trim()) body.append('sourceLabel', input.sourceLabel.trim());
if (input.notes?.trim()) body.append('notes', input.notes.trim());
if (input.profileCode) body.append('profileCode', input.profileCode);
return apiRequest<AssetImportBatch>('/asset-imports', { method: 'POST', body });
}
export function reconcileAssetImport(id: string) {
return apiRequest<AssetImportBatch>(`/asset-imports/${id}/reconcile`, { method: 'POST' });
}
export function listAssetImportOrganizations(search?: string) {
const query = new URLSearchParams();
if (search?.trim()) query.set('search', search.trim());
const suffix = query.toString() ? `?${query}` : '';
return apiRequest<{ data: AssetImportOrganizationOption[] }>(`/asset-imports/context/organizations${suffix}`);
}
export function getAssetImportPlan(id: string) {
return apiRequest<AssetImportPlan | null>(`/asset-imports/${id}/plan`);
}
export function generateAssetImportPlan(id: string, input: { operatorAssetId?: string; externalIdNamespace?: string }) {
return apiRequest<AssetImportPlan>(`/asset-imports/${id}/plan`, { method: 'POST', body: JSON.stringify(input) });
}
export function listAssetImportPlanItems(id: string, input: { entityKind?: AssetImportPlanEntityKind; action?: AssetImportPlanAction; page?: number; pageSize?: number } = {}) {
const query = new URLSearchParams();
if (input.entityKind) query.set('entityKind', input.entityKind);
if (input.action) query.set('action', input.action);
if (input.page) query.set('page', String(input.page));
if (input.pageSize) query.set('pageSize', String(input.pageSize));
const suffix = query.toString() ? `?${query}` : '';
return apiRequest<{ data: AssetImportPlanItem[]; meta: { page: number; pageSize: number; total: number; totalPages: number } }>(`/asset-imports/${id}/plan/items${suffix}`);
}
export function resolveAssetImportPlanItem(id: string, itemId: string, input: { action: 'CREATE' | 'MATCH' | 'IGNORE'; matchedAssetId?: string; reason: string }) {
return apiRequest<AssetImportPlan>(`/asset-imports/${id}/plan/items/${itemId}/resolve`, { method: 'POST', body: JSON.stringify(input) });
}
export function applySafeAssetImportPlan(id: string, planHash: string) {
return apiRequest<AssetImportPlan>(`/asset-imports/${id}/apply-safe`, { method: 'POST', body: JSON.stringify({ planHash }) });
}
export function applyAssetImportPlan(id: string, planHash: string) {
return apiRequest<AssetImportPlan>(`/asset-imports/${id}/apply`, { method: 'POST', body: JSON.stringify({ planHash }) });
}
export function rollbackAssetImportPlan(id: string, reason: string) {
return apiRequest<AssetImportPlan>(`/asset-imports/${id}/rollback`, { method: 'POST', body: JSON.stringify({ reason }) });
}
export function cancelAssetImport(id: string, reason: string) {
return apiRequest<AssetImportBatch>(`/asset-imports/${id}/cancel`, { method: 'POST', body: JSON.stringify({ reason }) });
}
export function inspectionReportWordUrl(id: string) {
return `${API_BASE}/inspection-reports/${id}/word`;
}
export function generateInspectionReportWord(id: string) {
return apiRequest<InspectionReport>(`/inspection-reports/${id}/word`, { method: 'POST' });
}
export interface DocumentDeliverySettings { officeEmail:string|null; directorEmail:string|null; updatedAt:string; smtpConfigured:boolean; mailFrom:string|null; }
export interface DocumentDeliveryItem { id:string; actId:string; reportId:string|null; documentKind:'ACT_PDF'|'REPORT_WORD'; recipientKind:'COMPANY'|'OFFICE'|'DIRECTOR'; recipientAssetId:string|null; recipientAssetName:string|null; recipientEmail:string|null; status:'PENDING'|'WAITING_RECIPIENT'|'WAITING_TRANSPORT'|'WAITING_ARTIFACT'|'SENT'|'FAILED'; attempts:number; lastAttemptAt:string|null; sentAt:string|null; providerMessageId:string|null; lastError:string|null; createdAt:string; actCode:string; reportCode:string|null; }
export function getDocumentDeliverySettings(){return apiRequest<DocumentDeliverySettings>('/document-delivery/settings');}
export function updateDocumentDeliverySettings(input:{officeEmail?:string|null;directorEmail?:string|null}){return apiRequest<DocumentDeliverySettings>('/document-delivery/settings',{method:'PATCH',body:JSON.stringify(input)});}
export function listDocumentDeliveries(){return apiRequest<{data:DocumentDeliveryItem[]}>('/document-delivery/outbox');}
export function retryDocumentDelivery(id:string){return apiRequest<DocumentDeliveryItem>(`/document-delivery/outbox/${id}/retry`,{method:'POST'});}
export function retryPendingDocumentDeliveries(){return apiRequest<{processed:number}>('/document-delivery/outbox/retry-pending',{method:'POST'});}
export type ActAdministrationState = 'NEW' | 'WAITING_RESPONSE' | 'DUE_SOON' | 'OVERDUE' | 'RESPONSE_RECEIVED' | 'VERIFICATION_PENDING' | 'COMMITMENT_OVERDUE' | 'REGULARIZED';
export interface ActAdministrationQueueItem {
actId: string; actCode: string; actStatus: string; occurredAt: string; closedAt: string | null;
visitId: string; visitCode: string; areaId: string | null; companyId: string | null; areaName: string | null; companyName: string | null;
responseDueOn: string | null; deadlineReason: string | null; responseReceivedOn: string | null; committedCorrectionOn: string | null;
findingCount: number; openFindingCount: number; scheduledControlCount: number; adminState: ActAdministrationState;
}
export interface ActAdministrationQueuePage { data: ActAdministrationQueueItem[]; counters: Partial<Record<ActAdministrationState, number>>; meta: PageMeta }
export interface ActAdministrationDetail {
act: ActAdministrationQueueItem;
deadlines: Array<{ id:string; responseDueOn:string; reason:string; createdAt:string }>;
responses: Array<{ id:string; receivedOn:string; details:string|null; committedCorrectionOn:string|null; contactName:string|null; contactEmail:string|null; originalName:string|null; sizeBytes:number|null; sha256:string|null; createdAt:string }>;
findings: Array<{ id:string; code:string; title:string; status:string; nextControlOn:string|null }>;
}
export function listActAdministrationQueue(params: { state?: ActAdministrationState | 'ALL'; areaId?: string; companyId?: string; page?: number; pageSize?: number } = {}) {
const q = new URLSearchParams({ page: String(params.page ?? 1), pageSize: String(params.pageSize ?? 25) });
if (params.state && params.state !== 'ALL') q.set('state', params.state);
if (params.areaId) q.set('areaId', params.areaId);
if (params.companyId) q.set('companyId', params.companyId);
return apiRequest<ActAdministrationQueuePage>(`/act-administration/queue?${q}`);
}
export function getActAdministration(actId: string) { return apiRequest<ActAdministrationDetail>(`/inspection-acts/${actId}/administration`); }
export function setActResponseDeadline(actId: string, input: { responseDueOn: string; reason: string }) { return apiRequest(`/inspection-acts/${actId}/administration/deadline`, { method: 'PATCH', body: JSON.stringify(input) }); }
export function addActCompanyResponse(actId: string, input: { receivedOn: string; details?: string; committedCorrectionOn?: string; contactName?: string; contactEmail?: string; file?: File }) {
const body = new FormData(); body.append('receivedOn', input.receivedOn);
if (input.details) body.append('details', input.details); if (input.committedCorrectionOn) body.append('committedCorrectionOn', input.committedCorrectionOn);
if (input.contactName) body.append('contactName', input.contactName); if (input.contactEmail) body.append('contactEmail', input.contactEmail); if (input.file) body.append('file', input.file);
return apiRequest(`/inspection-acts/${actId}/administration/responses`, { method: 'POST', body });
}
export function actCompanyResponseContentUrl(responseId: string) { return `${API_BASE}/act-company-responses/${responseId}/content`; }
export function listActAdministrationCalendar(from?: string, to?: string) { const q=new URLSearchParams(); if(from)q.set('from',from); if(to)q.set('to',to); return apiRequest<{from:string;to:string;data:Array<{type:'RESPONSE_DUE'|'COMMITMENT_DUE';date:string;actId:string;actCode:string;areaName:string|null;companyName:string|null;state:ActAdministrationState}>}>(`/act-administration/calendar${q.size?`?${q}`:''}`); }