Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 77 additions & 173 deletions migrations/db/20260603120000_add_anr_supervisors.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,195 +53,57 @@ public function up(): void
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;'
);

$this->table('instances_risks')
->addColumn('risk_owner_supervisor_id', 'integer', ['null' => true, 'signed' => false, 'after' => 'risk_owner_id'])
->addColumn(
'residual_acceptance_use_risk_owner',
'boolean',
['default' => false, 'after' => 'residual_risk_decision']
)
->addColumn(
'residual_acceptance_approver_supervisor_id',
'integer',
['null' => true, 'signed' => false, 'after' => 'residual_acceptance_use_risk_owner']
)
->addColumn(
'residual_acceptance_performed_by_name',
'string',
['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_approver_supervisor_id']
)
->addColumn(
'residual_acceptance_performed_by_email',
'string',
['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_performed_by_name']
)
->addColumn(
'residual_acceptance_performed_on_behalf',
'boolean',
['default' => false, 'after' => 'residual_acceptance_performed_by_email']
)
->addColumn(
'residual_risk_decided_by_supervisor_id',
'integer',
['null' => true, 'signed' => false, 'after' => 'residual_acceptance_performed_on_behalf']
)
->addColumn(
'residual_risk_decided_by_user_id',
'integer',
['null' => true, 'signed' => false, 'after' => 'residual_risk_decided_by_supervisor_id']
)
->addColumn(
'residual_risk_decided_at',
'datetime',
['null' => true, 'after' => 'residual_risk_decided_by_user_id']
)
->addIndex(['risk_owner_supervisor_id'], ['name' => 'risk_owner_supervisor_id'])
->addIndex(
['residual_acceptance_approver_supervisor_id'],
['name' => 'residual_acceptance_approver_supervisor_id']
)
->addIndex(
['residual_risk_decided_by_supervisor_id'],
['name' => 'residual_risk_decided_by_supervisor_id']
)
->addIndex(['residual_risk_decided_by_user_id'], ['name' => 'residual_risk_decided_by_user_id'])
->addForeignKey(
'risk_owner_supervisor_id',
'anr_supervisors',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->addForeignKey(
'residual_acceptance_approver_supervisor_id',
'anr_supervisors',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->addForeignKey(
'residual_risk_decided_by_supervisor_id',
'anr_supervisors',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->addForeignKey(
'residual_risk_decided_by_user_id',
'users',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->removeColumn('residual_risk_approved_at')
->removeColumn('residual_risk_approved_by')
->update();
$this->updateRiskTable(
'instances_risks',
'',
[
'risk_owner_supervisor_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'risk_owner_id']],
'residual_acceptance_use_risk_owner' => ['boolean', ['default' => false, 'after' => 'residual_risk_decision']],
'residual_acceptance_approver_supervisor_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'residual_acceptance_use_risk_owner']],
'residual_acceptance_performed_by_name' => ['string', ['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_approver_supervisor_id']],
'residual_acceptance_performed_by_email' => ['string', ['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_performed_by_name']],
'residual_acceptance_performed_on_behalf' => ['boolean', ['default' => false, 'after' => 'residual_acceptance_performed_by_email']],
'residual_risk_decided_by_supervisor_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'residual_acceptance_performed_on_behalf']],
'residual_risk_decided_by_user_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'residual_risk_decided_by_supervisor_id']],
'residual_risk_decided_at' => ['datetime', ['null' => true, 'after' => 'residual_risk_decided_by_user_id']],
]
);

$this->table('instances_risks_op')
->addColumn('risk_owner_supervisor_id', 'integer', ['null' => true, 'signed' => false, 'after' => 'risk_owner_id'])
->addColumn(
'residual_acceptance_use_risk_owner',
'boolean',
['default' => false, 'after' => 'residual_risk_decision']
)
->addColumn(
'residual_acceptance_approver_supervisor_id',
'integer',
['null' => true, 'signed' => false, 'after' => 'residual_acceptance_use_risk_owner']
)
->addColumn(
'residual_acceptance_performed_by_name',
'string',
['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_approver_supervisor_id']
)
->addColumn(
'residual_acceptance_performed_by_email',
'string',
['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_performed_by_name']
)
->addColumn(
'residual_acceptance_performed_on_behalf',
'boolean',
['default' => false, 'after' => 'residual_acceptance_performed_by_email']
)
->addColumn(
'residual_risk_decided_by_supervisor_id',
'integer',
['null' => true, 'signed' => false, 'after' => 'residual_acceptance_performed_on_behalf']
)
->addColumn(
'residual_risk_decided_by_user_id',
'integer',
['null' => true, 'signed' => false, 'after' => 'residual_risk_decided_by_supervisor_id']
)
->addColumn(
'residual_risk_decided_at',
'datetime',
['null' => true, 'after' => 'residual_risk_decided_by_user_id']
)
->addIndex(['risk_owner_supervisor_id'], ['name' => 'op_risk_owner_supervisor_id'])
->addIndex(
['residual_acceptance_approver_supervisor_id'],
['name' => 'op_residual_acceptance_approver_supervisor_id']
)
->addIndex(
['residual_risk_decided_by_supervisor_id'],
['name' => 'op_residual_risk_decided_by_supervisor_id']
)
->addIndex(
['residual_risk_decided_by_user_id'],
['name' => 'op_residual_risk_decided_by_user_id']
)
->addForeignKey(
'risk_owner_supervisor_id',
'anr_supervisors',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->addForeignKey(
'residual_acceptance_approver_supervisor_id',
'anr_supervisors',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->addForeignKey(
'residual_risk_decided_by_supervisor_id',
'anr_supervisors',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->addForeignKey(
'residual_risk_decided_by_user_id',
'users',
'id',
['delete' => 'SET_NULL', 'update' => 'RESTRICT']
)
->removeColumn('residual_risk_approved_at')
->removeColumn('residual_risk_approved_by')
->update();
$this->updateRiskTable(
'instances_risks_op',
'op_',
[
'risk_owner_supervisor_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'risk_owner_id']],
'residual_acceptance_use_risk_owner' => ['boolean', ['default' => false, 'after' => 'residual_risk_decision']],
'residual_acceptance_approver_supervisor_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'residual_acceptance_use_risk_owner']],
'residual_acceptance_performed_by_name' => ['string', ['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_approver_supervisor_id']],
'residual_acceptance_performed_by_email' => ['string', ['limit' => 255, 'null' => true, 'after' => 'residual_acceptance_performed_by_name']],
'residual_acceptance_performed_on_behalf' => ['boolean', ['default' => false, 'after' => 'residual_acceptance_performed_by_email']],
'residual_risk_decided_by_supervisor_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'residual_acceptance_performed_on_behalf']],
'residual_risk_decided_by_user_id' => ['integer', ['null' => true, 'signed' => false, 'after' => 'residual_risk_decided_by_supervisor_id']],
'residual_risk_decided_at' => ['datetime', ['null' => true, 'after' => 'residual_risk_decided_by_user_id']],
]
);

$this->execute(
'INSERT INTO anr_supervisors (anr_id, name, email, linked_user_id, is_active, creator, created_at)
SELECT iro.anr_id, iro.name, NULL, NULL, 1, iro.creator, COALESCE(iro.created_at, NOW())
FROM instance_risk_owners iro
LEFT JOIN anr_supervisors s
ON s.anr_id = iro.anr_id
AND LOWER(TRIM(s.name)) = LOWER(TRIM(iro.name))
WHERE s.id IS NULL;'
FROM instance_risk_owners iro;'
);

$this->execute(
"INSERT INTO anr_supervisor_roles (anr_supervisor_id, role, creator, created_at)
SELECT s.id, 'risk_owner', COALESCE(s.creator, 'System'), COALESCE(s.created_at, NOW())
FROM anr_supervisors s
LEFT JOIN anr_supervisor_roles sr
ON sr.anr_supervisor_id = s.id
AND sr.role = 'risk_owner'
WHERE sr.id IS NULL;"
FROM anr_supervisors s;"
);

$this->execute(
'UPDATE instances_risks ir
INNER JOIN instance_risk_owners iro ON iro.id = ir.risk_owner_id
INNER JOIN anr_supervisors s
ON s.anr_id = iro.anr_id
AND LOWER(TRIM(s.name)) = LOWER(TRIM(iro.name))
AND CONVERT(s.name USING utf8mb4) COLLATE utf8mb4_unicode_ci
= CONVERT(iro.name USING utf8mb4) COLLATE utf8mb4_unicode_ci
SET ir.risk_owner_supervisor_id = s.id
WHERE ir.risk_owner_id IS NOT NULL;'
);
Expand All @@ -251,12 +113,54 @@ public function up(): void
INNER JOIN instance_risk_owners iro ON iro.id = iropr.risk_owner_id
INNER JOIN anr_supervisors s
ON s.anr_id = iro.anr_id
AND LOWER(TRIM(s.name)) = LOWER(TRIM(iro.name))
AND CONVERT(s.name USING utf8mb4) COLLATE utf8mb4_unicode_ci
= CONVERT(iro.name USING utf8mb4) COLLATE utf8mb4_unicode_ci
SET iropr.risk_owner_supervisor_id = s.id
WHERE iropr.risk_owner_id IS NOT NULL;'
);
}

/**
* Adds the supervisor-related schema only where a previous deployment has not already created it.
*
* @param array<string, array{0: string, 1: array<string, mixed>}> $columns
*/
private function updateRiskTable(string $tableName, string $indexPrefix, array $columns): void
{
$table = $this->table($tableName);
foreach ($columns as $name => [$type, $options]) {
if (!$table->hasColumn($name)) {
$table->addColumn($name, $type, $options);
}
}

foreach (['residual_risk_approved_at', 'residual_risk_approved_by'] as $column) {
if ($table->hasColumn($column)) {
$table->removeColumn($column);
}
}

$table->update();
$table = $this->table($tableName);
$foreignKeys = [
'risk_owner_supervisor_id' => 'anr_supervisors',
'residual_acceptance_approver_supervisor_id' => 'anr_supervisors',
'residual_risk_decided_by_supervisor_id' => 'anr_supervisors',
'residual_risk_decided_by_user_id' => 'users',
];

foreach ($foreignKeys as $column => $referenceTable) {
if (!$table->hasIndex([$column])) {
$table->addIndex([$column], ['name' => $indexPrefix . $column]);
}
if (!$table->hasForeignKey([$column])) {
$table->addForeignKey($column, $referenceTable, 'id', ['delete' => 'SET_NULL', 'update' => 'RESTRICT']);
}
}

$table->update();
}

public function down(): void
{
$this->table('instances_risks')
Expand Down
39 changes: 28 additions & 11 deletions src/Import/Processor/ObjectImportProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ public function processObjectData(
if (isset($objectData['label'])) {
$objectData['label' . $anr->getLanguage()] = $objectData['label'];
}
$objectData[$nameFiledKey] = $this->prepareUniqueObjectName($objectData[$nameFiledKey]);
$objectData[$nameFiledKey] = $this->prepareUniqueObjectName(
$objectData[$nameFiledKey],
$objectCategory?->getId()
);

$object = $this->anrObjectService
->createMonarcObject($anr, $asset, $objectCategory, $rolfTag, $objectData, false);
Expand All @@ -111,11 +114,14 @@ public function processObjectData(
$objectData[$nameFiledKey] . $asset->getUuid() . $object->getScope() . $objectCategory?->getId()
);
$this->importCacheHelper
->addItemToArrayCache('objects_names', $objectData[$nameFiledKey], $objectData[$nameFiledKey]);
} elseif ($this->importCacheHelper->isItemInArrayCache(
'processed_objects_by_current_uuids',
$object->getUuid()
)) {
->addItemToArrayCache(
'objects_names_by_category',
$objectData[$nameFiledKey],
$this->getObjectNameCacheKey($objectData[$nameFiledKey], $objectCategory?->getId())
);
} elseif (
$this->importCacheHelper->isItemInArrayCache('processed_objects_by_current_uuids', $object->getUuid())
) {
$this->importCacheHelper->addItemToArrayCache('processed_objects_by_old_uuids', $object, $currentObjectUuid);

return $object;
Expand Down Expand Up @@ -190,9 +196,12 @@ private function getObjectFromCacheByParams(
. $object->getCategory()?->getId()
);
$this->importCacheHelper->addItemToArrayCache(
'objects_names',
'objects_names_by_category',
$object->getName($languageIndex),
$object->getName($languageIndex)
$this->getObjectNameCacheKey(
$object->getName($languageIndex),
$object->getCategory()?->getId()
)
);
}
}
Expand All @@ -203,21 +212,29 @@ private function getObjectFromCacheByParams(
);
}

private function prepareUniqueObjectName(string $objectName, int $index = 1): string
private function prepareUniqueObjectName(string $objectName, ?int $categoryId, int $index = 1): string
{
if ($this->importCacheHelper->isItemInArrayCache('objects_names', $objectName)) {
if ($this->importCacheHelper->isItemInArrayCache(
'objects_names_by_category',
$this->getObjectNameCacheKey($objectName, $categoryId)
)) {
if (str_contains($objectName, ' - Imp. #')) {
$objectName = preg_replace('/#\d+/', '#' . $index, $objectName);
} else {
$objectName .= ' - Imp. #' . $index;
}

return $this->prepareUniqueObjectName($objectName, $index + 1);
return $this->prepareUniqueObjectName($objectName, $categoryId, $index + 1);
}

return $objectName;
}

private function getObjectNameCacheKey(string $objectName, ?int $categoryId): string
{
return ($categoryId ?? 'root') . ':' . $objectName;
}

/** Merges the amvs (information risks) of the existing object. */
private function mergeAssetInformationRisks(Entity\MonarcObject $object, array $informationRisksData): void
{
Expand Down
Loading