feat: bootstrap JOBcard CRM foundation

This commit is contained in:
Marco0300
2026-09-01 18:54:47 +02:00
commit 480494c5ed
31 changed files with 1300 additions and 0 deletions
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/**
* Normalize the fields accepted by a client contact form.
*
* Empty optional values are represented as null and the primary flag is a bool.
*/
function normalize_client_contact(array $input): array
{
$email = trim((string)($input['email'] ?? ''));
$phone = trim((string)($input['phone'] ?? ''));
return [
'name' => trim((string)($input['name'] ?? '')),
'email' => $email === '' ? null : strtolower($email),
'phone' => $phone === '' ? null : $phone,
'is_primary' => normalize_client_contact_primary($input['is_primary'] ?? $input['primary'] ?? false),
];
}
/**
* Validate and normalize a client contact in one reusable operation.
*/
function validate_client_contact(array $input): array
{
$contact = normalize_client_contact($input);
$errors = [];
if ($contact['name'] === '') {
$errors['name'] = 'Contact name is required.';
} elseif (mb_strlen($contact['name']) > 120) {
$errors['name'] = 'Contact name must be 120 characters or fewer.';
}
if ($contact['email'] !== null && filter_var($contact['email'], FILTER_VALIDATE_EMAIL) === false) {
$errors['email'] = 'Contact email must be a valid email address.';
} elseif ($contact['email'] !== null && mb_strlen($contact['email']) > 190) {
$errors['email'] = 'Contact email must be 190 characters or fewer.';
}
if ($contact['phone'] !== null && mb_strlen($contact['phone']) > 60) {
$errors['phone'] = 'Contact phone must be 60 characters or fewer.';
}
if (!is_bool($contact['is_primary'])) {
$errors['is_primary'] = 'Primary contact flag must be boolean.';
$contact['is_primary'] = false;
}
return [...$contact, 'errors' => $errors];
}
function normalize_client_contact_primary(mixed $value): bool|int|string
{
if (is_bool($value)) {
return $value;
}
if (is_int($value) && ($value === 0 || $value === 1)) {
return $value === 1;
}
if (is_string($value)) {
$normalized = strtolower(trim($value));
if (in_array($normalized, ['1', 'true', 'yes', 'on'], true)) {
return true;
}
if (in_array($normalized, ['', '0', 'false', 'no', 'off'], true)) {
return false;
}
}
return $value;
}
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/**
* Return whether a client name already occurs in a list, ignoring case and outer whitespace.
* Existing entries may be strings or rows containing a `name` field.
*/
function client_name_is_duplicate(string $name, array $existingClients): bool
{
$candidate = strtolower(trim($name));
foreach ($existingClients as $existing) {
$existingName = is_array($existing) ? ($existing['name'] ?? '') : $existing;
if (is_string($existingName) && strtolower(trim($existingName)) === $candidate) {
return true;
}
}
return false;
}
+13
View File
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
function validate_client(array $input): array
{
$name = trim((string)($input['name'] ?? ''));
$status = (string)($input['status'] ?? 'active');
$errors = [];
if ($name === '') $errors['name'] = 'Client name is required.';
if (mb_strlen($name) > 190) $errors['name'] = 'Client name must be 190 characters or fewer.';
if (!in_array($status, ['active', 'inactive'], true)) $errors['status'] = 'Invalid client status.';
return ['name' => $name, 'status' => $status, 'errors' => $errors];
}