Unique_Attribute合併至 main
| @@ -46,5 +46,11 @@ node_modules/ | |||||
| npm-debug.log* | npm-debug.log* | ||||
| yarn-error.log* | yarn-error.log* | ||||
| .claude/ | |||||
| .abacusai/ | |||||
| .claude/* | |||||
| .abacusai/* | |||||
| graphify-out/* | |||||
| .graphify_ast_extract.py | |||||
| .graphify_ast.json | |||||
| .graphify_detect.json | |||||
| .graphify_python | |||||
| .graphify_uncached.txt | |||||
| @@ -91,6 +91,21 @@ class CustomerController extends Controller | |||||
| ]); | ]); | ||||
| } | } | ||||
| $encodedValues = json_encode($form['attribute_values'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); | |||||
| $duplicate = $this->repo()->findDuplicate((int) $form['customer_type_id'], $encodedValues); | |||||
| if ($duplicate !== null) { | |||||
| $model->form = $form; | |||||
| $model->errors['_duplicate'] = [ | |||||
| 'A customer with these exact values already exists: <a href="/customers/' . (int) $duplicate['id'] . '/edit">Customer #' . (int) $duplicate['id'] . ' (' . htmlspecialchars((string) $duplicate['customer_type_name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . ')</a>.', | |||||
| ]; | |||||
| return $this->view('customers.create', [ | |||||
| 'model' => $model, | |||||
| 'pageTitle' => $model->title, | |||||
| ]); | |||||
| } | |||||
| $customer = new Customer(); | $customer = new Customer(); | ||||
| $customer->customerTypeId = (int) $form['customer_type_id']; | $customer->customerTypeId = (int) $form['customer_type_id']; | ||||
| $customer->attributeValues = $form['attribute_values']; | $customer->attributeValues = $form['attribute_values']; | ||||
| @@ -159,6 +174,21 @@ class CustomerController extends Controller | |||||
| ]); | ]); | ||||
| } | } | ||||
| $encodedValues = json_encode($form['attribute_values'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); | |||||
| $duplicate = $this->repo()->findDuplicate((int) $form['customer_type_id'], $encodedValues, (int) $id); | |||||
| if ($duplicate !== null) { | |||||
| $model->form = $form; | |||||
| $model->errors['_duplicate'] = [ | |||||
| 'These values are identical to an existing customer: <a href="/customers/' . (int) $duplicate['id'] . '/edit">Customer #' . (int) $duplicate['id'] . ' (' . htmlspecialchars((string) $duplicate['customer_type_name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . ')</a>.', | |||||
| ]; | |||||
| return $this->view('customers.edit', [ | |||||
| 'model' => $model, | |||||
| 'pageTitle' => $model->title, | |||||
| ]); | |||||
| } | |||||
| $customer = new Customer(); | $customer = new Customer(); | ||||
| $customer->id = (int) $id; | $customer->id = (int) $id; | ||||
| $customer->customerTypeId = (int) $form['customer_type_id']; | $customer->customerTypeId = (int) $form['customer_type_id']; | ||||
| @@ -88,6 +88,24 @@ class CustomerRepository extends Repository | |||||
| ); | ); | ||||
| } | } | ||||
| /** Returns an existing customer with the same type and attribute values, excluding $excludeId (use for update). */ | |||||
| public function findDuplicate(int $typeId, string $attributeValuesJson, int $excludeId = 0): ?array | |||||
| { | |||||
| $sql = 'SELECT TOP (1) c.id, ct.name AS customer_type_name | |||||
| FROM customer c | |||||
| INNER JOIN customer_type ct ON c.customer_type_id = ct.id | |||||
| WHERE c.customer_type_id = :type_id | |||||
| AND c.attribute_values = :attribute_values'; | |||||
| $params = ['type_id' => $typeId, 'attribute_values' => $attributeValuesJson]; | |||||
| if ($excludeId > 0) { | |||||
| $sql .= ' AND c.id != :exclude_id'; | |||||
| $params['exclude_id'] = $excludeId; | |||||
| } | |||||
| return $this->database->first($sql, $params); | |||||
| } | |||||
| /** Used after INSERT to recover the generated id for audit logging. */ | /** Used after INSERT to recover the generated id for audit logging. */ | ||||
| public function findLatestByType(int $typeId): ?array | public function findLatestByType(int $typeId): ?array | ||||
| { | { | ||||
| @@ -26,6 +26,10 @@ window.__initialCtVals = <?= json_encode($model->form['attribute_values'], JSON_ | |||||
| <div class="alert alert-error"><?= e($model->errors['_token'][0]) ?></div> | <div class="alert alert-error"><?= e($model->errors['_token'][0]) ?></div> | ||||
| <?php endif; ?> | <?php endif; ?> | ||||
| <?php if (isset($model->errors['_duplicate'])): ?> | |||||
| <div class="alert alert-error"><?= $model->errors['_duplicate'][0] ?></div> | |||||
| <?php endif; ?> | |||||
| <form method="post" action="/customers" class="ct-form" novalidate> | <form method="post" action="/customers" class="ct-form" novalidate> | ||||
| <?= csrf_field() ?> | <?= csrf_field() ?> | ||||
| @@ -27,6 +27,10 @@ window.__initialCtVals = <?= json_encode($model->form['attribute_values'], JSON_ | |||||
| <div class="alert alert-error"><?= e($model->errors['_token'][0]) ?></div> | <div class="alert alert-error"><?= e($model->errors['_token'][0]) ?></div> | ||||
| <?php endif; ?> | <?php endif; ?> | ||||
| <?php if (isset($model->errors['_duplicate'])): ?> | |||||
| <div class="alert alert-error"><?= $model->errors['_duplicate'][0] ?></div> | |||||
| <?php endif; ?> | |||||
| <form method="post" action="/customers/<?= e((string) $customerId) ?>/update" class="ct-form" novalidate> | <form method="post" action="/customers/<?= e((string) $customerId) ?>/update" class="ct-form" novalidate> | ||||
| <?= csrf_field() ?> | <?= csrf_field() ?> | ||||
Powered by TurnKey Linux.