From 8c4c8a68f19d35e641ad9c9d37d5c2fbed4e9537 Mon Sep 17 00:00:00 2001 From: dcovington Date: Mon, 18 May 2026 19:29:07 +0000 Subject: [PATCH] customer_api_test (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit coerce api_lookup customer attrs to text when imported into job type When a customer_lookup attribute imports fields from a customer type, any api_lookup fields are flattened to plain text — the value comes from the selected customer record, so the lookup UI is not needed add customer API endpoints and customer_lookup job attribute - Add GET /api/customers, /api/customers/{id}, /api/customer-types endpoints - Add allByTypeWithType() and include api_match_field in customer queries - Add customer_lookup attribute type to job types: stores customer_type_id and imports the customer type's attributes as real fields at design time - Job form: customer_lookup renders a searchable dropdown that fetches /api/customers?customer_type_id=N and auto-fills all matching attribute values when a customer is selected customer-api working now Co-authored-by: Daniel Covington --- app/Controllers/CustomerApiController.php | 76 +++++++++++ app/Controllers/JobTypeController.php | 32 +++-- app/Repositories/CustomerRepository.php | 23 +++- app/Views/job-types/create.php | 6 +- app/Views/job-types/edit.php | 6 +- app/Views/jobs/create.php | 42 +++++- app/Views/jobs/edit.php | 42 +++++- ...1_add_api_match_field_to_customer_type.php | 32 +++++ public/js/app.js | 129 +++++++++++++++--- routes/web.php | 6 + 10 files changed, 351 insertions(+), 43 deletions(-) create mode 100644 app/Controllers/CustomerApiController.php create mode 100644 database/migrations/20260518_000001_add_api_match_field_to_customer_type.php diff --git a/app/Controllers/CustomerApiController.php b/app/Controllers/CustomerApiController.php new file mode 100644 index 0000000..bedfe4d --- /dev/null +++ b/app/Controllers/CustomerApiController.php @@ -0,0 +1,76 @@ +input('customer_type_id') ?? 0); + + $repo = new CustomerRepository(database()); + $rows = $customerTypeId > 0 + ? $repo->allByTypeWithType($customerTypeId) + : $repo->allWithType(); + + return $this->json(array_map([$this, 'formatCustomer'], $rows)); + } + + public function customer(string $id): Response + { + $repo = new CustomerRepository(database()); + $row = $repo->findWithType((int) $id); + + if ($row === null) { + return Response::json(['error' => 'Not found'], 404); + } + + return $this->json($this->formatCustomer($row)); + } + + public function customerTypes(): Response + { + $repo = new CustomerTypeRepository(database()); + $rows = $repo->allOrderedByName(); + + $data = array_map(static function (array $row): array { + $attributes = []; + if (!empty($row['attributes'])) { + $attributes = json_decode((string) $row['attributes'], true) ?? []; + } + + return [ + 'id' => (int) $row['id'], + 'name' => (string) $row['name'], + 'api_match_field' => (string) ($row['api_match_field'] ?? ''), + 'attributes' => $attributes, + ]; + }, $rows); + + return $this->json($data); + } + + private function formatCustomer(array $row): array + { + $attributeValues = []; + if (!empty($row['attribute_values'])) { + $attributeValues = json_decode((string) $row['attribute_values'], true) ?? []; + } + + return [ + 'id' => (int) $row['id'], + 'customer_type_id' => (int) $row['customer_type_id'], + 'customer_type_name' => (string) ($row['customer_type_name'] ?? ''), + 'attribute_values' => $attributeValues, + ]; + } +} diff --git a/app/Controllers/JobTypeController.php b/app/Controllers/JobTypeController.php index 9d27ce0..5c62f14 100644 --- a/app/Controllers/JobTypeController.php +++ b/app/Controllers/JobTypeController.php @@ -223,12 +223,13 @@ class JobTypeController extends Controller ->maxLength('name', $name, 255, 'Name must be 255 characters or fewer.') ->errors()); - $attributeAliases = (array) ($request->input('attribute_alias') ?? []); - $attributeApiUrls = (array) ($request->input('attribute_api_url') ?? []); - $attributeApiFormats = (array) ($request->input('attribute_api_format') ?? []); - $attributeApiReturnTypes = (array) ($request->input('attribute_api_return_type') ?? []); - $attributeApiMatchFields = (array) ($request->input('attribute_api_match_field') ?? []); - $attributeApiAutoFills = (array) ($request->input('attribute_api_auto_fill') ?? []); + $attributeAliases = (array) ($request->input('attribute_alias') ?? []); + $attributeApiUrls = (array) ($request->input('attribute_api_url') ?? []); + $attributeApiFormats = (array) ($request->input('attribute_api_format') ?? []); + $attributeApiReturnTypes = (array) ($request->input('attribute_api_return_type') ?? []); + $attributeApiMatchFields = (array) ($request->input('attribute_api_match_field') ?? []); + $attributeApiAutoFills = (array) ($request->input('attribute_api_auto_fill') ?? []); + $attributeCustomerTypeIds = (array) ($request->input('attribute_customer_type_id') ?? []); $attributes = []; @@ -237,11 +238,10 @@ class JobTypeController extends Controller $attrType = trim((string) ($attributeTypes[$i] ?? 'text')); if ($attrName === '') continue; - // 'customer' is a design-time placeholder that is expanded into real attribute rows - // by the Job Type editor JS before submit. If one slips through, drop it. + // 'customer' is a legacy design-time placeholder (now replaced by customer_lookup). Drop if it slips through. if ($attrType === 'customer') continue; - $validatedType = in_array($attrType, ['text', 'number', 'date', 'boolean', 'api_lookup'], true) ? $attrType : 'text'; + $validatedType = in_array($attrType, ['text', 'number', 'date', 'boolean', 'api_lookup', 'customer_lookup'], true) ? $attrType : 'text'; $attr = [ 'name' => $attrName, @@ -262,6 +262,11 @@ class JobTypeController extends Controller $attr['api_auto_fill'] = trim((string) ($attributeApiAutoFills[$i] ?? '')); } + if ($validatedType === 'customer_lookup') { + $attr['customer_type_id'] = (int) ($attributeCustomerTypeIds[$i] ?? 0); + $attr['api_match_field'] = trim((string) ($attributeApiMatchFields[$i] ?? '')); + } + $attributes[] = $attr; } @@ -289,14 +294,15 @@ class JobTypeController extends Controller return new CustomerTypeRepository(database()); } - /** @return list */ + /** @return list */ private function loadCustomerTypes(): array { return array_map(static function (array $t): array { return [ - 'id' => (int) $t['id'], - 'name' => (string) $t['name'], - 'attributes' => json_decode((string) ($t['attributes'] ?? '[]'), true) ?? [], + 'id' => (int) $t['id'], + 'name' => (string) $t['name'], + 'api_match_field' => (string) ($t['api_match_field'] ?? ''), + 'attributes' => json_decode((string) ($t['attributes'] ?? '[]'), true) ?? [], ]; }, $this->customerTypeRepo()->allOrderedByName()); } diff --git a/app/Repositories/CustomerRepository.php b/app/Repositories/CustomerRepository.php index a2d2309..bf263c4 100644 --- a/app/Repositories/CustomerRepository.php +++ b/app/Repositories/CustomerRepository.php @@ -48,20 +48,39 @@ class CustomerRepository extends Repository 'SELECT c.id, c.customer_type_id, c.attribute_values, c.created_at, c.updated_at, ct.name AS customer_type_name, - ct.attributes AS customer_type_attributes + ct.attributes AS customer_type_attributes, + ct.api_match_field FROM customer c INNER JOIN customer_type ct ON c.customer_type_id = ct.id ORDER BY c.id DESC' ); } + /** @return list> */ + public function allByTypeWithType(int $typeId): array + { + return $this->database->query( + 'SELECT c.id, c.customer_type_id, c.attribute_values, + c.created_at, c.updated_at, + ct.name AS customer_type_name, + ct.attributes AS customer_type_attributes, + ct.api_match_field + FROM customer c + INNER JOIN customer_type ct ON c.customer_type_id = ct.id + WHERE c.customer_type_id = :type_id + ORDER BY c.id DESC', + ['type_id' => $typeId] + ); + } + public function findWithType(int $id): ?array { return $this->database->first( 'SELECT c.id, c.customer_type_id, c.attribute_values, c.created_at, c.updated_at, ct.name AS customer_type_name, - ct.attributes AS customer_type_attributes + ct.attributes AS customer_type_attributes, + ct.api_match_field FROM customer c INNER JOIN customer_type ct ON c.customer_type_id = ct.id WHERE c.id = :id', diff --git a/app/Views/job-types/create.php b/app/Views/job-types/create.php index 844331e..6dcb6a9 100644 --- a/app/Views/job-types/create.php +++ b/app/Views/job-types/create.php @@ -74,7 +74,7 @@ window.__customerTypes = customerTypes, JSON_HEX_T - +
@@ -121,7 +121,9 @@ window.__customerTypes = customerTypes, JSON_HEX_T
-
+
+ +
@@ -128,7 +128,9 @@ window.__customerTypes = customerTypes, JSON_HEX_T
-
+
+ +