import { Injectable } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { DatabaseService } from '../../../infrastructure/database/database.service';
import {
assertExternalIntegrationEnum,
assertExternalIntegrationRecord,
assertExternalIntegrationScope,
assertExternalIntegrationText,
assertOptionalExternalIntegrationText,
} from './external-integration-input-validation';
@Injectable()
export class ExternalConflictService {
constructor(private readonly database:DatabaseService) {}
async create(input:unknown) {
assertExternalIntegrationScope(input, 'external_integration.conflict_input_invalid', { locationRequired: false });
assertExternalIntegrationText(input.connectionId, 200, 'external_integration.conflict_input_invalid');
assertExternalIntegrationEnum(input.conflictType, [
'mapping_missing', 'mapping_ambiguous', 'version_conflict', 'ownership_conflict', 'unsupported_state',
'tax_mapping_missing', 'tender_mapping_missing', 'product_mapping_missing', 'supplier_mapping_missing',
'provider_rejected',
], 'external_integration.conflict_input_invalid');
const severity = input.severity ?? 'high';
assertExternalIntegrationEnum(severity, ['medium', 'high', 'critical'], 'external_integration.conflict_input_invalid');
assertOptionalExternalIntegrationText(input.localEntityType, 100, 'external_integration.conflict_input_invalid');
assertOptionalExternalIntegrationText(input.localEntityId, 200, 'external_integration.conflict_input_invalid');
assertOptionalExternalIntegrationText(input.externalEntityType, 100, 'external_integration.conflict_input_invalid');
assertOptionalExternalIntegrationText(input.externalId, 500, 'external_integration.conflict_input_invalid');
assertExternalIntegrationText(input.summary, 2000, 'external_integration.conflict_input_invalid');
const localSnapshot = input.localSnapshot ?? {};
const externalSnapshot = input.externalSnapshot ?? {};
assertExternalIntegrationRecord(localSnapshot, 'external_integration.conflict_input_invalid');
assertExternalIntegrationRecord(externalSnapshot, 'external_integration.conflict_input_invalid');
const id=randomUUID();
await this.database.query(
`insert into external_integration_conflicts(
id,organization_id,location_id,integration_connection_id,conflict_type,severity,
local_entity_type,local_entity_id,external_entity_type,external_id,
local_snapshot_json,external_snapshot_json,summary
) values($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12::jsonb,$13)`,
[
id,input.organizationId,input.locationId??null,input.connectionId,input.conflictType,
severity,input.localEntityType??null,input.localEntityId??null,
input.externalEntityType??null,input.externalId??null,
JSON.stringify(localSnapshot),JSON.stringify(externalSnapshot),
input.summary,
],
);
return {id};
}
async resolve(input:{organizationId:string;locationId:string;conflictId:string;action:string;userId?:string;note?:string;result?:unknown}) {
await this.database.transaction(async(tx)=>{
const rows=await tx.query(
`update external_integration_conflicts
set status=$1,resolved_at=now(),resolved_by_user_id=$2
where id=$3 and organization_id=$4 and location_id=$5 and status='open'`,
[input.action==='ignore'?'ignored':'resolved',input.userId??null,input.conflictId,input.organizationId,input.locationId],
);
if(!rows[0]) throw new Error('external_integration.conflict_not_found');
await tx.query(
`insert into external_integration_conflict_resolutions(
id,organization_id,conflict_id,action,actor_user_id,note,result_json
) values($1,$2,$3,$4,$5,$6,$7::jsonb)`,
[
randomUUID(),input.organizationId,input.conflictId,input.action,
input.userId??null,input.note??null,JSON.stringify(input.result??{}),
],
);
});
return {resolved:true};
}
}
