F4 inventory: include function in temporal snapshots
This commit is contained in:
@@ -41,14 +41,14 @@ export interface AssetVersionDetail extends AssetVersionSummary {
|
|||||||
function assetNotFound(): NotFoundException {
|
function assetNotFound(): NotFoundException {
|
||||||
return new NotFoundException({
|
return new NotFoundException({
|
||||||
code: 'ASSET_NOT_FOUND',
|
code: 'ASSET_NOT_FOUND',
|
||||||
message: 'Activo no encontrado',
|
message: 'Inventario no encontrado',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function versionNotFound(): NotFoundException {
|
function versionNotFound(): NotFoundException {
|
||||||
return new NotFoundException({
|
return new NotFoundException({
|
||||||
code: 'ASSET_VERSION_NOT_FOUND',
|
code: 'ASSET_VERSION_NOT_FOUND',
|
||||||
message: 'Versión de activo no encontrada',
|
message: 'Versión de Inventario no encontrada',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ export class AssetHistoryService {
|
|||||||
|
|
||||||
const versionNumber = Number(versionRow.current_version);
|
const versionNumber = Number(versionRow.current_version);
|
||||||
if (!Number.isInteger(versionNumber) || versionNumber < 1) {
|
if (!Number.isInteger(versionNumber) || versionNumber < 1) {
|
||||||
throw new Error(`Versión de activo inválida después de incrementar: ${versionRow.current_version}`);
|
throw new Error(`Versión de Inventario inválida después de incrementar: ${versionRow.current_version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const snapshot = await this.loadCurrentSnapshot(manager, assetId);
|
const snapshot = await this.loadCurrentSnapshot(manager, assetId);
|
||||||
@@ -101,17 +101,8 @@ export class AssetHistoryService {
|
|||||||
asset_id, version_number, change_type, changed_fields, snapshot,
|
asset_id, version_number, change_type, changed_fields, snapshot,
|
||||||
actor_user_id, actor_username, source, request_id
|
actor_user_id, actor_username, source, request_id
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||||
[
|
[assetId, versionNumber, changeType, changedFields, snapshot,
|
||||||
assetId,
|
principal.userId, principal.username, source, request.requestId],
|
||||||
versionNumber,
|
|
||||||
changeType,
|
|
||||||
changedFields,
|
|
||||||
snapshot,
|
|
||||||
principal.userId,
|
|
||||||
principal.username,
|
|
||||||
source,
|
|
||||||
request.requestId,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
return versionNumber;
|
return versionNumber;
|
||||||
}
|
}
|
||||||
@@ -123,38 +114,21 @@ export class AssetHistoryService {
|
|||||||
parameters.push(value);
|
parameters.push(value);
|
||||||
return `$${parameters.length}`;
|
return `$${parameters.length}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (query.search?.trim()) {
|
if (query.search?.trim()) {
|
||||||
const search = add(`%${query.search.trim()}%`);
|
const search = add(`%${query.search.trim()}%`);
|
||||||
conditions.push(`(
|
conditions.push(`(version.snapshot->>'code' ILIKE ${search} OR version.snapshot->>'name' ILIKE ${search} OR version.actor_username ILIKE ${search})`);
|
||||||
version.snapshot->>'code' ILIKE ${search}
|
|
||||||
OR version.snapshot->>'name' ILIKE ${search}
|
|
||||||
OR version.actor_username ILIKE ${search}
|
|
||||||
)`);
|
|
||||||
}
|
|
||||||
if (query.typeId) {
|
|
||||||
conditions.push(`version.snapshot #>> '{type,id}' = ${add(query.typeId)}`);
|
|
||||||
}
|
|
||||||
if (query.status) {
|
|
||||||
conditions.push(`version.snapshot->>'informationStatus' = ${add(query.status)}`);
|
|
||||||
}
|
|
||||||
if (query.changeType) {
|
|
||||||
conditions.push(`version.change_type = ${add(query.changeType)}`);
|
|
||||||
}
|
}
|
||||||
|
if (query.typeId) conditions.push(`version.snapshot #>> '{type,id}' = ${add(query.typeId)}`);
|
||||||
|
if (query.status) conditions.push(`version.snapshot->>'informationStatus' = ${add(query.status)}`);
|
||||||
|
if (query.changeType) conditions.push(`version.change_type = ${add(query.changeType)}`);
|
||||||
if (query.from) conditions.push(`version.occurred_at >= ${add(new Date(query.from))}`);
|
if (query.from) conditions.push(`version.occurred_at >= ${add(new Date(query.from))}`);
|
||||||
if (query.to) conditions.push(`version.occurred_at <= ${add(new Date(query.to))}`);
|
if (query.to) conditions.push(`version.occurred_at <= ${add(new Date(query.to))}`);
|
||||||
|
|
||||||
return this.listWithConditions(query.page, query.pageSize, conditions, parameters);
|
return this.listWithConditions(query.page, query.pageSize, conditions, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
async listForAsset(assetId: string, query: AssetVersionPageQueryDto) {
|
async listForAsset(assetId: string, query: AssetVersionPageQueryDto) {
|
||||||
await this.requireAsset(assetId);
|
await this.requireAsset(assetId);
|
||||||
return this.listWithConditions(
|
return this.listWithConditions(query.page, query.pageSize, ['version.asset_id = $1'], [assetId]);
|
||||||
query.page,
|
|
||||||
query.pageSize,
|
|
||||||
['version.asset_id = $1'],
|
|
||||||
[assetId],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getVersion(assetId: string, versionNumber: number): Promise<AssetVersionDetail> {
|
async getVersion(assetId: string, versionNumber: number): Promise<AssetVersionDetail> {
|
||||||
@@ -170,17 +144,9 @@ export class AssetHistoryService {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async listWithConditions(
|
private async listWithConditions(page: number, pageSize: number, conditions: string[], parameters: unknown[]) {
|
||||||
page: number,
|
|
||||||
pageSize: number,
|
|
||||||
conditions: string[],
|
|
||||||
parameters: unknown[],
|
|
||||||
) {
|
|
||||||
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
||||||
const [countRow] = (await this.dataSource.query(
|
const [countRow] = (await this.dataSource.query(`SELECT COUNT(*)::integer AS total FROM asset_versions version ${where}`, parameters)) as Array<{ total: number }>;
|
||||||
`SELECT COUNT(*)::integer AS total FROM asset_versions version ${where}`,
|
|
||||||
parameters,
|
|
||||||
)) as Array<{ total: number }>;
|
|
||||||
const total = Number(countRow?.total ?? 0);
|
const total = Number(countRow?.total ?? 0);
|
||||||
const paginated = [...parameters, pageSize, (page - 1) * pageSize];
|
const paginated = [...parameters, pageSize, (page - 1) * pageSize];
|
||||||
const limit = `$${parameters.length + 1}`;
|
const limit = `$${parameters.length + 1}`;
|
||||||
@@ -194,51 +160,29 @@ export class AssetHistoryService {
|
|||||||
LIMIT ${limit} OFFSET ${offset}`,
|
LIMIT ${limit} OFFSET ${offset}`,
|
||||||
paginated,
|
paginated,
|
||||||
)) as AssetVersionSummary[];
|
)) as AssetVersionSummary[];
|
||||||
|
return { data, meta: { page, pageSize, total, totalPages: total === 0 ? 0 : Math.ceil(total / pageSize) } };
|
||||||
return {
|
|
||||||
data,
|
|
||||||
meta: {
|
|
||||||
page,
|
|
||||||
pageSize,
|
|
||||||
total,
|
|
||||||
totalPages: total === 0 ? 0 : Math.ceil(total / pageSize),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectSummary(): string {
|
private selectSummary(): string {
|
||||||
return `SELECT
|
return `SELECT
|
||||||
version.id,
|
version.id, version.asset_id AS "assetId",
|
||||||
version.asset_id AS "assetId",
|
version.snapshot->>'code' AS "assetCode", version.snapshot->>'name' AS "assetName",
|
||||||
version.snapshot->>'code' AS "assetCode",
|
version.snapshot #>> '{type,id}' AS "typeId", version.snapshot #>> '{type,name}' AS "typeName",
|
||||||
version.snapshot->>'name' AS "assetName",
|
|
||||||
version.snapshot #>> '{type,id}' AS "typeId",
|
|
||||||
version.snapshot #>> '{type,name}' AS "typeName",
|
|
||||||
version.snapshot->>'informationStatus' AS "informationStatus",
|
version.snapshot->>'informationStatus' AS "informationStatus",
|
||||||
version.snapshot->>'operationalStatus' AS "operationalStatus",
|
version.snapshot->>'operationalStatus' AS "operationalStatus",
|
||||||
version.version_number AS "versionNumber",
|
version.version_number AS "versionNumber", version.change_type AS "changeType",
|
||||||
version.change_type AS "changeType",
|
version.changed_fields AS "changedFields", version.occurred_at AS "occurredAt",
|
||||||
version.changed_fields AS "changedFields",
|
version.actor_user_id AS "actorUserId", version.actor_username AS "actorUsername",
|
||||||
version.occurred_at AS "occurredAt",
|
version.source, version.request_id AS "requestId",
|
||||||
version.actor_user_id AS "actorUserId",
|
|
||||||
version.actor_username AS "actorUsername",
|
|
||||||
version.source,
|
|
||||||
version.request_id AS "requestId",
|
|
||||||
(version.version_number = current_asset.current_version) AS "isCurrent"`;
|
(version.version_number = current_asset.current_version) AS "isCurrent"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async requireAsset(assetId: string): Promise<void> {
|
private async requireAsset(assetId: string): Promise<void> {
|
||||||
const [row] = (await this.dataSource.query(
|
const [row] = (await this.dataSource.query('SELECT 1 FROM assets WHERE id = $1', [assetId])) as unknown[];
|
||||||
'SELECT 1 FROM assets WHERE id = $1',
|
|
||||||
[assetId],
|
|
||||||
)) as unknown[];
|
|
||||||
if (!row) throw assetNotFound();
|
if (!row) throw assetNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadCurrentSnapshot(
|
private async loadCurrentSnapshot(manager: EntityManager, assetId: string): Promise<Record<string, unknown>> {
|
||||||
manager: EntityManager,
|
|
||||||
assetId: string,
|
|
||||||
): Promise<Record<string, unknown>> {
|
|
||||||
const [row] = (await manager.query(
|
const [row] = (await manager.query(
|
||||||
`SELECT JSONB_BUILD_OBJECT(
|
`SELECT JSONB_BUILD_OBJECT(
|
||||||
'id', asset.id,
|
'id', asset.id,
|
||||||
@@ -246,82 +190,49 @@ export class AssetHistoryService {
|
|||||||
'name', asset.name,
|
'name', asset.name,
|
||||||
'commonName', asset.common_name,
|
'commonName', asset.common_name,
|
||||||
'description', asset.description,
|
'description', asset.description,
|
||||||
'type', JSONB_BUILD_OBJECT(
|
'type', JSONB_BUILD_OBJECT('id', asset_type.id,'code', asset_type.code,'name', asset_type.name,'operationalRole', asset_type.operational_role),
|
||||||
'id', asset_type.id,
|
'parent', CASE WHEN parent.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT('id', parent.id,'code', parent.code,'name', parent.name) END,
|
||||||
'code', asset_type.code,
|
'operationalArea', CASE WHEN operational_area.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT('id', operational_area.id,'code', operational_area.code,'name', operational_area.name) END,
|
||||||
'name', asset_type.name,
|
'operatorCompany', CASE WHEN operator_company.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT('id', operator_company.id,'code', operator_company.code,'name', operator_company.name) END,
|
||||||
'operationalRole', asset_type.operational_role
|
|
||||||
),
|
|
||||||
'parent', CASE WHEN parent.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
|
||||||
'id', parent.id,
|
|
||||||
'code', parent.code,
|
|
||||||
'name', parent.name
|
|
||||||
) END,
|
|
||||||
'operationalArea', CASE WHEN operational_area.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
|
||||||
'id', operational_area.id,
|
|
||||||
'code', operational_area.code,
|
|
||||||
'name', operational_area.name
|
|
||||||
) END,
|
|
||||||
'operatorCompany', CASE WHEN operator_company.id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
|
||||||
'id', operator_company.id,
|
|
||||||
'code', operator_company.code,
|
|
||||||
'name', operator_company.name
|
|
||||||
) END,
|
|
||||||
'informationStatus', asset.information_status,
|
'informationStatus', asset.information_status,
|
||||||
'operationalStatus', asset.operational_status,
|
'operationalStatus', asset.operational_status,
|
||||||
|
'currentFunction', (
|
||||||
|
SELECT JSONB_BUILD_OBJECT(
|
||||||
|
'assignmentId', assignment.id,
|
||||||
|
'id', fn.id,
|
||||||
|
'code', fn.code,
|
||||||
|
'name', fn.name,
|
||||||
|
'validFrom', assignment.valid_from,
|
||||||
|
'reason', assignment.change_reason
|
||||||
|
)
|
||||||
|
FROM inventory_function_assignments assignment
|
||||||
|
JOIN inventory_functions fn ON fn.id=assignment.function_id
|
||||||
|
WHERE assignment.asset_id=asset.id AND assignment.valid_until IS NULL
|
||||||
|
ORDER BY assignment.valid_from DESC LIMIT 1
|
||||||
|
),
|
||||||
'attributes', COALESCE((
|
'attributes', COALESCE((
|
||||||
SELECT JSONB_AGG(JSONB_BUILD_OBJECT(
|
SELECT JSONB_AGG(JSONB_BUILD_OBJECT(
|
||||||
'definitionId', definition.id,
|
'definitionId', definition.id,'code', definition.code,'name', definition.name,
|
||||||
'code', definition.code,
|
'dataType', definition.data_type,'isRequired', definition.is_required,
|
||||||
'name', definition.name,
|
'unit', definition.unit,'options', definition.options,'sortOrder', definition.sort_order,'value', value.value
|
||||||
'dataType', definition.data_type,
|
|
||||||
'isRequired', definition.is_required,
|
|
||||||
'unit', definition.unit,
|
|
||||||
'options', definition.options,
|
|
||||||
'sortOrder', definition.sort_order,
|
|
||||||
'value', value.value
|
|
||||||
) ORDER BY definition.sort_order, definition.name)
|
) ORDER BY definition.sort_order, definition.name)
|
||||||
FROM asset_attribute_definitions definition
|
FROM asset_attribute_definitions definition
|
||||||
LEFT JOIN asset_attribute_values value
|
LEFT JOIN asset_attribute_values value ON value.definition_id = definition.id AND value.asset_id = asset.id
|
||||||
ON value.definition_id = definition.id
|
WHERE definition.asset_type_id = asset.asset_type_id AND definition.is_active = true
|
||||||
AND value.asset_id = asset.id
|
|
||||||
WHERE definition.asset_type_id = asset.asset_type_id
|
|
||||||
AND definition.is_active = true
|
|
||||||
), '[]'::jsonb),
|
), '[]'::jsonb),
|
||||||
'geometry', CASE WHEN geometry.asset_id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
'geometry', CASE WHEN geometry.asset_id IS NULL THEN NULL ELSE JSONB_BUILD_OBJECT(
|
||||||
'assetId', geometry.asset_id,
|
'assetId', geometry.asset_id,'geometry', ST_AsGeoJSON(geometry.geometry)::jsonb,'geometryType', geometry.geometry_type,
|
||||||
'geometry', ST_AsGeoJSON(geometry.geometry)::jsonb,
|
'source', geometry.source,'accuracyM', geometry.accuracy_m::double precision,'capturedAt', geometry.captured_at,
|
||||||
'geometryType', geometry.geometry_type,
|
'deviceLabel', geometry.device_label,'createdAt', geometry.created_at,'updatedAt', geometry.updated_at,'updatedBy', geometry.updated_by
|
||||||
'source', geometry.source,
|
|
||||||
'accuracyM', geometry.accuracy_m::double precision,
|
|
||||||
'capturedAt', geometry.captured_at,
|
|
||||||
'deviceLabel', geometry.device_label,
|
|
||||||
'createdAt', geometry.created_at,
|
|
||||||
'updatedAt', geometry.updated_at,
|
|
||||||
'updatedBy', geometry.updated_by
|
|
||||||
) END,
|
) END,
|
||||||
'media', COALESCE((
|
'media', COALESCE((
|
||||||
SELECT JSONB_AGG(JSONB_BUILD_OBJECT(
|
SELECT JSONB_AGG(JSONB_BUILD_OBJECT(
|
||||||
'id', media.id,
|
'id', media.id,'kind', media.kind,'originalName', media.original_name,'mimeType', media.mime_type,
|
||||||
'kind', media.kind,
|
'sizeBytes', media.size_bytes,'sha256', media.sha256,'title', media.title,'description', media.description,
|
||||||
'originalName', media.original_name,
|
'capturedAt', media.captured_at,'latitude', media.latitude,'longitude', media.longitude,'accuracyM', media.accuracy_m,
|
||||||
'mimeType', media.mime_type,
|
'source', media.source,'uploadedBy', media.uploaded_by,'createdAt', media.created_at,'updatedAt', media.updated_at
|
||||||
'sizeBytes', media.size_bytes,
|
|
||||||
'sha256', media.sha256,
|
|
||||||
'title', media.title,
|
|
||||||
'description', media.description,
|
|
||||||
'capturedAt', media.captured_at,
|
|
||||||
'latitude', media.latitude,
|
|
||||||
'longitude', media.longitude,
|
|
||||||
'accuracyM', media.accuracy_m,
|
|
||||||
'source', media.source,
|
|
||||||
'uploadedBy', media.uploaded_by,
|
|
||||||
'createdAt', media.created_at,
|
|
||||||
'updatedAt', media.updated_at
|
|
||||||
) ORDER BY media.created_at, media.id)
|
) ORDER BY media.created_at, media.id)
|
||||||
FROM asset_media media
|
FROM asset_media media WHERE media.asset_id = asset.id AND media.deleted_at IS NULL
|
||||||
WHERE media.asset_id = asset.id
|
|
||||||
AND media.deleted_at IS NULL
|
|
||||||
), '[]'::jsonb),
|
), '[]'::jsonb),
|
||||||
'organizationProfile', (SELECT TO_JSONB(profile) - 'created_at' - 'updated_at' FROM organization_profiles profile WHERE profile.asset_id=asset.id),
|
'organizationProfile', (SELECT TO_JSONB(profile) - 'created_at' - 'updated_at' FROM organization_profiles profile WHERE profile.asset_id=asset.id),
|
||||||
'organizationMemberships', COALESCE((
|
'organizationMemberships', COALESCE((
|
||||||
@@ -337,22 +248,8 @@ export class AssetHistoryService {
|
|||||||
'legalRights', COALESCE((
|
'legalRights', COALESCE((
|
||||||
SELECT JSONB_AGG(JSONB_BUILD_OBJECT('id',r.id,'rightType',r.right_type,'name',r.name,'instrumentNumber',r.instrument_number,'validFrom',r.valid_from,'validUntil',r.valid_until,'status',r.status,'sourceDocumentId',r.source_document_id,'notes',r.notes,'organizations',COALESCE((SELECT JSONB_AGG(JSONB_BUILD_OBJECT('id',o.id,'organizationId',o.organization_id,'role',o.role,'participationPercent',o.participation_percent::double precision,'validFrom',o.valid_from,'validUntil',o.valid_until,'notes',o.notes,'endReason',o.end_reason) ORDER BY o.valid_until NULLS FIRST,o.valid_from DESC) FROM area_legal_right_organizations o WHERE o.right_id=r.id),'[]'::jsonb)) ORDER BY r.valid_until DESC NULLS FIRST,r.valid_from DESC NULLS LAST) FROM area_legal_rights r WHERE r.area_id=asset.id
|
SELECT JSONB_AGG(JSONB_BUILD_OBJECT('id',r.id,'rightType',r.right_type,'name',r.name,'instrumentNumber',r.instrument_number,'validFrom',r.valid_from,'validUntil',r.valid_until,'status',r.status,'sourceDocumentId',r.source_document_id,'notes',r.notes,'organizations',COALESCE((SELECT JSONB_AGG(JSONB_BUILD_OBJECT('id',o.id,'organizationId',o.organization_id,'role',o.role,'participationPercent',o.participation_percent::double precision,'validFrom',o.valid_from,'validUntil',o.valid_until,'notes',o.notes,'endReason',o.end_reason) ORDER BY o.valid_until NULLS FIRST,o.valid_from DESC) FROM area_legal_right_organizations o WHERE o.right_id=r.id),'[]'::jsonb)) ORDER BY r.valid_until DESC NULLS FIRST,r.valid_from DESC NULLS LAST) FROM area_legal_rights r WHERE r.area_id=asset.id
|
||||||
),'[]'::jsonb),
|
),'[]'::jsonb),
|
||||||
'provenance', JSONB_BUILD_OBJECT(
|
'provenance', JSONB_BUILD_OBJECT('origin', asset.data_origin,'sourceName', asset.source_name,'sourceReference', asset.source_reference,'observedAt', asset.source_observed_at,'notes', asset.source_notes,'verifiedAt', asset.provenance_verified_at,'verifiedBy', asset.provenance_verified_by,'updatedAt', asset.provenance_updated_at,'updatedBy', asset.provenance_updated_by),
|
||||||
'origin', asset.data_origin,
|
'createdAt', asset.created_at,'updatedAt', asset.updated_at,'createdBy', asset.created_by,'updatedBy', asset.updated_by,'currentVersion', asset.current_version
|
||||||
'sourceName', asset.source_name,
|
|
||||||
'sourceReference', asset.source_reference,
|
|
||||||
'observedAt', asset.source_observed_at,
|
|
||||||
'notes', asset.source_notes,
|
|
||||||
'verifiedAt', asset.provenance_verified_at,
|
|
||||||
'verifiedBy', asset.provenance_verified_by,
|
|
||||||
'updatedAt', asset.provenance_updated_at,
|
|
||||||
'updatedBy', asset.provenance_updated_by
|
|
||||||
),
|
|
||||||
'createdAt', asset.created_at,
|
|
||||||
'updatedAt', asset.updated_at,
|
|
||||||
'createdBy', asset.created_by,
|
|
||||||
'updatedBy', asset.updated_by,
|
|
||||||
'currentVersion', asset.current_version
|
|
||||||
) AS snapshot
|
) AS snapshot
|
||||||
FROM assets asset
|
FROM assets asset
|
||||||
INNER JOIN asset_types asset_type ON asset_type.id = asset.asset_type_id
|
INNER JOIN asset_types asset_type ON asset_type.id = asset.asset_type_id
|
||||||
|
|||||||
Reference in New Issue
Block a user