$data */ public static function save(Model $record, array $data, int $actor, ?int $expectedVersion = null): void { DB::transaction(function () use ($record, $data, $actor, $expectedVersion) { $created = ! $record->exists; if (! $created) { $locked = $record->newQuery()->where($record->getKeyName(), $record->getKey())->lockForUpdate()->firstOrFail(); $record->setRawAttributes($locked->getAttributes(), true); if ($expectedVersion !== null && (int) $locked->getAttribute('version_no') !== $expectedVersion) { throw ValidationException::withMessages(['version' => 'Este registo foi alterado por outra operação. Compare os dados actuais antes de continuar; as suas alterações foram conservadas.']); } } $versionedCrm = in_array($record->getTable(), ['investors', 'contacts', 'leads', 'tasks'], true); // Exclude free text and personal data from the immutable event payload. $fields = ['primary_territory_id', 'territory_version', 'version_no', 'status', 'is_active', 'is_featured', 'sort_order', 'sector_id', 'maturity_level', 'estimated_investment_amount', 'estimated_investment_currency', 'owner_user_id', 'assignee_user_id', 'due_at', 'cluster_id', 'project_id', 'corridor_id', 'dossier_id', 'interest_request_id', 'publication_status', 'editor_id', 'reviewer_id', 'role', 'stage', 'value_chain_id', 'from_value_chain_id', 'to_value_chain_id', 'link_type', 'deleted_at']; $before = array_intersect_key($record->getAttributes(), array_flip($fields)); $record->fill($data); if ($versionedCrm && ! $created && ! $record->isDirty()) { return; } if ($versionedCrm && ! array_key_exists('version_no', $data)) { $record->setAttribute('version_no', $created ? ($record->getAttribute('version_no') ?? 1) : (int) $record->getOriginal('version_no') + 1); } $changed = array_keys($record->getDirty()); $record->save(); if (! $changed) { return; } DB::table('audit_events')->insert([ 'source_system' => 'portal-management', 'actor_user_id' => $actor, 'entity_type' => $record->getTable(), 'entity_id' => Str::isUuid((string) $record->getKey()) ? $record->getKey() : null, 'action' => $created ? 'created' : 'updated', 'before_data' => json_encode(['record_id' => $record->getKey(), 'values' => $before], JSON_THROW_ON_ERROR), 'after_data' => json_encode(['record_id' => $record->getKey(), 'changed_fields' => $changed, 'values' => array_intersect_key($record->getAttributes(), array_flip($fields))], JSON_THROW_ON_ERROR), ]); }); } }