Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
94.29% |
33 / 35 |
|
66.67% |
2 / 3 |
CRAP | |
0.00% |
0 / 1 |
| PrefixMetadata | |
94.12% |
32 / 34 |
|
66.67% |
2 / 3 |
20.08 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| fromArray | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
9 | |||
| shouldReset | |
83.33% |
10 / 12 |
|
0.00% |
0 / 1 |
10.46 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | /** @license For full copyright and license information, please see the LICENSE.md file. */ |
| 6 | |
| 7 | namespace App\Core\Engine\Domain\Model; |
| 8 | |
| 9 | defined('AMMONLY_APP') || exit('Direct script access is forbidden.'); |
| 10 | |
| 11 | use DateTimeImmutable; |
| 12 | |
| 13 | /** |
| 14 | * Prefix Metadata Value Object. |
| 15 | * |
| 16 | * Immutable representation of a record prefix configuration rule from a_core_prefix_records. |
| 17 | * Encapsulates sequence parameters, template pattern, reset strategy, and padding rules. |
| 18 | * |
| 19 | * @package App\Core\Engine\Domain\Model |
| 20 | */ |
| 21 | final readonly class PrefixMetadata |
| 22 | { |
| 23 | /** |
| 24 | * PrefixMetadata constructor. |
| 25 | * |
| 26 | * @param int $id Prefix primary key. |
| 27 | * @param int $moduleId Target module ID. |
| 28 | * @param int $fieldId Target field ID within the module. |
| 29 | * @param string $name Descriptive configuration label. |
| 30 | * @param string $prefix Static prefix text. |
| 31 | * @param string $postfix Static postfix text. |
| 32 | * @param string|null $pattern Dynamic template pattern (e.g. {PREFIX}{YYYY}{NUMBER}{POSTFIX}). |
| 33 | * @param int $leadingZeros Number of digits with zero-padding (0 for no padding). |
| 34 | * @param int $startNumber Initial starting sequence number. |
| 35 | * @param int $currentNumber Current sequence number. |
| 36 | * @param int $step Increment step per record (default 1). |
| 37 | * @param string $resetFrequency Reset policy: 'never', 'yearly', 'monthly', 'daily'. |
| 38 | * @param string|null $lastResetDate Timestamp of the last sequence increment/reset. |
| 39 | * @param int|null $picklistFieldId Optional picklist field ID for dictionary-scoped numbering. |
| 40 | * @param bool $isActive Whether the prefix rule is active. |
| 41 | * @param string|null $description Optional administrator notes. |
| 42 | */ |
| 43 | public function __construct( |
| 44 | public int $id, |
| 45 | public int $moduleId, |
| 46 | public int $fieldId, |
| 47 | public string $name, |
| 48 | public string $prefix = '', |
| 49 | public string $postfix = '', |
| 50 | public ?string $pattern = null, |
| 51 | public int $leadingZeros = 0, |
| 52 | public int $startNumber = 1, |
| 53 | public int $currentNumber = 1, |
| 54 | public int $step = 1, |
| 55 | public string $resetFrequency = 'never', |
| 56 | public ?string $lastResetDate = null, |
| 57 | public ?int $picklistFieldId = null, |
| 58 | public bool $isActive = true, |
| 59 | public ?string $description = null, |
| 60 | ) { |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * Hydrates a PrefixMetadata instance from a database associative array. |
| 65 | * |
| 66 | * @param array<string, mixed> $data Raw database row. |
| 67 | * @return self Hydrated metadata value object. |
| 68 | */ |
| 69 | public static function fromArray(array $data): self |
| 70 | { |
| 71 | return new self( |
| 72 | id: (int) ($data['id'] ?? 0), |
| 73 | moduleId: (int) ($data['module_id'] ?? 0), |
| 74 | fieldId: (int) ($data['field_id'] ?? 0), |
| 75 | name: (string) ($data['name'] ?? ''), |
| 76 | prefix: (string) ($data['prefix'] ?? ''), |
| 77 | postfix: (string) ($data['postfix'] ?? ''), |
| 78 | pattern: isset($data['pattern']) && $data['pattern'] !== '' ? (string) $data['pattern'] : null, |
| 79 | leadingZeros: (int) ($data['leading_zeros'] ?? 0), |
| 80 | startNumber: (int) ($data['start_number'] ?? 1), |
| 81 | currentNumber: (int) ($data['current_number'] ?? 1), |
| 82 | step: max(1, (int) ($data['step'] ?? 1)), |
| 83 | resetFrequency: (string) ($data['reset_frequency'] ?? 'never'), |
| 84 | lastResetDate: isset($data['last_reset_date']) && $data['last_reset_date'] !== '' |
| 85 | ? (string) $data['last_reset_date'] : null, |
| 86 | picklistFieldId: isset($data['picklist_field_id']) && (int) $data['picklist_field_id'] > 0 |
| 87 | ? (int) $data['picklist_field_id'] : null, |
| 88 | isActive: (bool) ($data['is_active'] ?? true), |
| 89 | description: isset($data['description']) && $data['description'] !== '' |
| 90 | ? (string) $data['description'] : null, |
| 91 | ); |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Checks whether the sequence should be reset based on the current timestamp. |
| 96 | * |
| 97 | * @param DateTimeImmutable $now Current point in time. |
| 98 | * @return bool True if a reset cycle has elapsed. |
| 99 | */ |
| 100 | public function shouldReset(DateTimeImmutable $now): bool |
| 101 | { |
| 102 | if ($this->resetFrequency === 'never' || $this->lastResetDate === null || $this->lastResetDate === '') { |
| 103 | return false; |
| 104 | } |
| 105 | |
| 106 | $last = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $this->lastResetDate) |
| 107 | ?: DateTimeImmutable::createFromFormat('Y-m-d', substr($this->lastResetDate, 0, 10)); |
| 108 | |
| 109 | if ($last === false) { |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | return match ($this->resetFrequency) { |
| 114 | 'yearly' => $last->format('Y') !== $now->format('Y'), |
| 115 | 'monthly' => $last->format('Y-m') !== $now->format('Y-m'), |
| 116 | 'daily' => $last->format('Y-m-d') !== $now->format('Y-m-d'), |
| 117 | default => false, |
| 118 | }; |
| 119 | } |
| 120 | } |