Skip to content

AttributesCollector:Add attributes to Person on finalize #289

Merged
merged 1 commit into from Feb 11, 2025
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
151 changes: 130 additions & 21 deletions app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php
Expand Up @@ -31,12 +31,17 @@

use App\Lib\Enum\PetitionActionEnum;
use App\Lib\Enum\StatusEnum;
use App\Model\Entity\Person;
use App\Model\Entity\PersonRole;
use App\Model\Entity\Petition;
use Cake\Collection\Collection;
use Cake\Database\Connection;
use Cake\Database\Expression\QueryExpression;
use Cake\Datasource\EntityInterface;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use Cake\Validation\Validator;

class AttributeCollectorsTable extends Table {
Expand Down Expand Up @@ -126,49 +131,153 @@ public function initialize(array $config): void {
* @since COmanage Registry v5.1.0
*/

public function finalize(int $id, \App\Model\Entity\Petition $petition) {
public function finalize(int $id, \App\Model\Entity\Petition $petition): bool
{
$cfg = $this->get($id);

if(empty($petition->enrollee_person_id)) {
throw new \InvalidArgumentException(__d('error', 'Petitions.enrollee.notfound', [$petition->id]));
}

// I will get the model from the supported attributes.
$supportedAttributes = $this->EnrollmentAttributes->supportedAttributes();

$People = TableRegistry::getTableLocator()->get('People');

$person = $People->get($petition->enrollee_person_id);
$role = null;

$attributes = $this->EnrollmentAttributes
->PetitionAttributes
->find()
->where(['petition_id' => $petition->id])
->firstOrFail();
->contain(
['EnrollmentAttributes' => ['AttributeTypes']]
)->toArray();

// Get the collection object
$attributesCollection = new Collection($attributes);


/*********** PERSON ROLE ***************/
// Filter the Person Role Attributes and keep the field name
$personRoleAttributes = (new Collection($supportedAttributes))->filter(function($attr, $key) {
return isset($attr['model']) && $attr['model'] == 'PersonRole';
})->toArray();
$personRoleAttributes = array_keys($personRoleAttributes);

// Get all the fields/values required to build the PrersonRole
$fieldsForPersonRole = $attributesCollection->filter(function($attr, $key) use ($personRoleAttributes) {
return in_array($attr['enrollment_attribute']['attribute'], $personRoleAttributes);
})->toArray();

// Start a Transaction
$cxn = $this->getConnection();
$cxn->begin();

// Save the Person Role
try {
$personRoleObj = TableRegistry::getTableLocator()->get('PersonRoles');
$role = $personRoleObj->saveAttributes((int)$person->id, $fieldsForPersonRole);
} catch (\Exception $e) {
$cxn->rollback();
$this->llog('error', __d('error', 'save', [$e->getMessage()]));
throw new \RuntimeException(__d('error', 'save', ['PersonRole']));
}

// XXX Should i save the primary name?
// Since we're not modifying $person, it's a bit clearer if we save each entity
// individually than try to save related
/********* MVEAS **************/
// Filter the MVEAS Attributes and keep the field name
$mveaAttributes = (new Collection($supportedAttributes))->filter(function($attr, $key) {
return isset($attr['mveaModel']);
})->toArray();
$mveaAttributes = array_keys($mveaAttributes);

// MVEAs for Person
$this->handleMveaAttributes(
$person,
$role,
'Person',
$mveaAttributes,
$attributes,
$cxn
);

$Names = TableRegistry::getTableLocator()->get('Names');
// MVEAs for Role
$this->handleMveaAttributes(
$person,
$role,
'PersonRole',
$mveaAttributes,
$attributes,
$cxn
);

// Get the Primary Name
$primaryName = $Names->primaryName($person->id);
// Save the Date Of Birth. This is the only one that is single valued
// and goes under the Person

// XXX enforce CoSettings required/permitted fields here?
$name = [
'person_id' => $person->id,
'primary_name' => true,
'type_id' => $cfg->name_type_id
];
$cxn->commit();

foreach(['honorific', 'given', 'middle', 'family', 'suffix'] as $n) {
if(!empty($attributes->$n)) {
$name[$n] = $attributes->$n;
}
return true;
}

/**
* Handle MVEA (Multi-Valued Enrollment Attributes) for a model.
*
* This method processes and saves Multi-Valued Enrollment Attributes associated
* with a specific model (e.g., Person, PersonRole) for the given person and role.
*
* @param Person|null $person The person entity involved.
* @param PersonRole|null $role The person role entity involved.
* @param string $mveaParent
* @param array $mveaAttributes
* @param array $attributes Collection of petition attributes to filter and process.
* @param Connection $cxn Database connection used for transactions.
* @return void
* @since COmanage Registry v5.1.0
*/
protected function handleMveaAttributes(
Person|null $person,
PersonRole|null $role,
string $mveaParent,
array $mveaAttributes,
array $attributes,
Connection $cxn
): void {
$attributesCollection = new Collection($attributes);
// MVEAs for the requested model
$fieldsForMveaModel = $attributesCollection->filter(function($attr, $key) use ($mveaAttributes, $mveaParent) {
return in_array($attr['enrollment_attribute']['attribute'], $mveaAttributes)
&& $attr['enrollment_attribute']['attribute_mvea_parent'] == $mveaParent;
})->toArray();

if(empty($fieldsForMveaModel)) {
return;
}

$Names->saveOrFail($Names->newEntity($name));
// XXX Should i save the email?
$enrollmentAttributes = Hash::combine(
$fieldsForMveaModel,
'{n}.enrollment_attribute_id', '{n}.enrollment_attribute.attribute',
);

return true;

foreach ($enrollmentAttributes as $attribute_id => $attribute) {
// Get all the fields for this enrollment_attribute_id
$fieldsForAttribute = $attributesCollection->filter(function($attr, $key) use ($attribute_id) {
return $attr['enrollment_attribute_id'] == $attribute_id;
})->toArray();

$supportedAttributes = $this->EnrollmentAttributes->supportedAttributes();
$mveaModel = $supportedAttributes[$attribute]['mveaModel'];

try {
$modelObj = TableRegistry::getTableLocator()->get($mveaModel);
$modelObj->saveAttributes((int)$person->id, $role?->id, $mveaParent, $fieldsForAttribute);
} catch (\Exception $e) {
$cxn->rollback();
$this->llog('error', __d('error', 'save', [$e->getMessage()]));
throw new \RuntimeException(__d('error', 'save', [$mveaModel]));
}
}
}

/**
Expand Down
Expand Up @@ -67,6 +67,11 @@ public function initialize(array $config): void {

// Define associations
$this->belongsTo('CoreEnroller.AttributeCollectors');
$this->belongsTo('AttributeTypes')
->setClassName('Types')
->setForeignKey('attribute_type')
->setProperty('attribute_type');


$this->hasMany('CoreEnroller.PetitionAttributes')
// XXX do we really want to allow deletion once the definition is in use?
Expand Down
Expand Up @@ -163,6 +163,9 @@ if (\in_array($attribute_type, ['adHocAttribute'], true)) {
print $this->element('form/listItem', [
'arguments' => [
'fieldName' => 'attribute_tag',
'fieldOptions' => [
'required' => true
]
]]);
}

Expand Down
39 changes: 39 additions & 0 deletions app/src/Model/Table/AdHocAttributesTable.php
Expand Up @@ -146,4 +146,43 @@ public function validationDefault(Validator $validator): Validator {

return $validator;
}


/**
* Save ad hoc attributes for a person or person role.
*
* @since COmanage Registry v5.1.0
* @param int $personId ID of the person the attributes belong to
* @param int|null $roleId ID of the person role the attributes belong to (or null if not applicable)
* @param string $parentModel Name of the parent model ('Person' or 'PersonRole')
* @param array $fields Array of fields containing the attributes to be saved
* @return bool True on success
* @throws \InvalidArgumentException Thrown if required parameters are missing or invalid
* @throws \Cake\ORM\Exception\PersistenceFailedException If saving the entity fails
*/
public function saveAttributes(int $personId, ?int $roleId, string $parentModel, array $fields): bool
{
foreach ($fields as $idx => $field) {
// Check if this has already been saved
$adhoc = [
'tag' => $field->enrollment_attribute->attribute_tag,
'value' => $field->value
];

if($parentModel === 'Person') {
if(empty($personId)) {
throw new \InvalidArgumentException(__d('error', 'personId'));
}
$adhoc['person_id'] = $personId;
} elseif ($parentModel === 'PersonRole') {
if(empty($roleId)) {
throw new \InvalidArgumentException(__d('error', 'person_role_id'));
}
$adhoc['person_role_id'] = $roleId;
}

$this->saveOrFail($this->newEntity($adhoc));
}
return true;
}
}
50 changes: 49 additions & 1 deletion app/src/Model/Table/AddressesTable.php
Expand Up @@ -29,10 +29,12 @@

namespace App\Model\Table;

use Cake\Collection\Collection;
use Cake\Utility\Hash;
use \App\Lib\Enum\LanguageEnum;
use \Cake\ORM\Table;
use \Cake\ORM\TableRegistry;
use \Cake\Validation\Validator;
use \App\Lib\Enum\LanguageEnum;

class AddressesTable extends Table {
use \App\Lib\Traits\AutoViewVarsTrait;
Expand Down Expand Up @@ -277,4 +279,50 @@ public function validationDefault(Validator $validator): Validator {
public function getPermittedFields(): array {
return $this->permittedFields;
}


/**
* Save attributes based on the provided data.
*
* This method saves the attributes associated with a person or a person role.
*
* @since COmanage Registry v5.1.0
* @param int $personId ID of the person
* @param int|null $roleId ID of the person role (nullable)
* @param string $parentModel Model to associate the attributes with ('Person' or 'PersonRole')
* @param array $fields Array of attribute fields to save
* @return bool True on successful save
* @throws \InvalidArgumentException If required parameters are missing
* @throws \Cake\ORM\Exception\PersistenceFailedException If the entity could not be saved
*/
public function saveAttributes(int $personId, ?int $roleId, string $parentModel, array $fields): bool
{
$fieldType = Hash::extract($fields, '{n}.enrollment_attribute.attribute_type.id');
$fieldTypeId = (new Collection($fieldType))->first();

$address = [
'type_id' => $fieldTypeId
];

if($parentModel === 'Person') {
if(empty($personId)) {
throw new \InvalidArgumentException(__d('error', 'personId'));
}
$address['person_id'] = $personId;
} elseif ($parentModel === 'PersonRole') {
if(empty($roleId)) {
throw new \InvalidArgumentException(__d('error', 'person_role_id'));
}
$address['person_role_id'] = $roleId;
}

// We need to get this from CoSettings??
foreach($fields as $fld) {
$address[$fld->column_name] = $fld->value;
}

$this->saveOrFail($this->newEntity($address));

return true;
}
}
36 changes: 35 additions & 1 deletion app/src/Model/Table/EmailAddressesTable.php
Expand Up @@ -29,10 +29,12 @@

namespace App\Model\Table;

use Cake\Collection\Collection;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use Cake\Validation\Validator;
use \App\Lib\Enum\ActionEnum;
use \App\Lib\Enum\ProvisioningContextEnum;
Expand Down Expand Up @@ -370,6 +372,38 @@ public function validationDefault(Validator $validator): Validator {
]);
$validator->allowEmptyString('source_email_address_id');

return $validator;
return $validator;
}


/**
* Save attributes for a person, possibly tied to a role and parent model.
* Each field is processed to create a new EmailAddress entity and saved.
*
* @since COmanage Registry v5.1.0
* @param int $personId Person ID
* @param int|null $roleId Role ID (if applicable)
* @param string $parentModel Parent model name
* @param array $fields Array of fields containing enrollment attributes
* @return bool True on success
* @throws \Cake\Datasource\Exception\RecordNotFoundException
* @throws \Cake\ORM\Exception\PersistenceFailedException
*/
public function saveAttributes(int $personId, ?int $roleId, string $parentModel, array $fields): bool
{
$fieldType = Hash::extract($fields, '{n}.enrollment_attribute.attribute_type.id');
$fieldTypeId = (new Collection($fieldType))->first();

foreach ($fields as $idx => $field) {
// Check if this has already been saved
$email = [
'person_id' => $personId,
'mail' => $field->value,
'type_id' => $fieldTypeId
];

$this->saveOrFail($this->newEntity($email));
}
return true;
}
}