Skip to content

Commit

Permalink
AttributesCollector:Add attributes to Person on finalize (#289)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis authored Feb 11, 2025
1 parent 5cd5867 commit 4f9d8af
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 25 deletions.
151 changes: 130 additions & 21 deletions app/plugins/CoreEnroller/src/Model/Table/AttributeCollectorsTable.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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;
}
}
Loading

0 comments on commit 4f9d8af

Please sign in to comment.