Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

342 lines
11KB

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controllers;
  4. use App\Models\Customer;
  5. use App\Repositories\CustomerAuditRepository;
  6. use App\Repositories\CustomerRepository;
  7. use App\Repositories\CustomerTypeRepository;
  8. use App\ViewModels\CustomerViewModel;
  9. use Core\Controller;
  10. use Core\Request;
  11. use Core\Response;
  12. class CustomerController extends Controller
  13. {
  14. public function index(): Response
  15. {
  16. $request = Request::capture();
  17. $model = new CustomerViewModel();
  18. $model->saved = $request->input('saved') === '1';
  19. $model->deleted = $request->input('deleted') === '1';
  20. return $this->view('customers.index', [
  21. 'model' => $model,
  22. 'pageTitle' => $model->title,
  23. ]);
  24. }
  25. public function data(): Response
  26. {
  27. $rows = $this->repo()->allWithType();
  28. $data = array_map(static function (array $row): array {
  29. $attrValues = !empty($row['attribute_values'])
  30. ? (json_decode((string) $row['attribute_values'], true) ?? [])
  31. : [];
  32. $customerTypeAttributes = !empty($row['customer_type_attributes'])
  33. ? (json_decode((string) $row['customer_type_attributes'], true) ?? [])
  34. : [];
  35. $summary = implode(', ', array_map(
  36. static fn($k, $v) => "{$k}: {$v}",
  37. array_keys($attrValues),
  38. array_values($attrValues)
  39. ));
  40. return [
  41. 'id' => (int) $row['id'],
  42. 'customer_type_id' => (int) $row['customer_type_id'],
  43. 'customer_type_name' => (string) $row['customer_type_name'],
  44. 'customer_type_attributes' => $customerTypeAttributes,
  45. 'attribute_values' => $attrValues,
  46. 'attributes_summary' => $summary,
  47. 'created_at' => (string) $row['created_at'],
  48. ];
  49. }, $rows);
  50. return $this->json($data);
  51. }
  52. public function create(): Response
  53. {
  54. $model = new CustomerViewModel();
  55. $model->title = 'New Customer';
  56. $model->customerTypes = $this->loadCustomerTypes();
  57. return $this->view('customers.create', [
  58. 'model' => $model,
  59. 'pageTitle' => $model->title,
  60. ]);
  61. }
  62. public function store(): Response
  63. {
  64. $request = Request::capture();
  65. $model = new CustomerViewModel();
  66. $model->title = 'New Customer';
  67. $model->customerTypes = $this->loadCustomerTypes();
  68. [$form, $errors] = $this->validateForm($request, $model->customerTypes);
  69. if (!empty($errors)) {
  70. $model->form = $form;
  71. $model->errors = $errors;
  72. return $this->view('customers.create', [
  73. 'model' => $model,
  74. 'pageTitle' => $model->title,
  75. ]);
  76. }
  77. $customer = new Customer();
  78. $customer->customerTypeId = (int) $form['customer_type_id'];
  79. $customer->attributeValues = $form['attribute_values'];
  80. $this->repo()->create($customer);
  81. $inserted = $this->repo()->findLatestByType($customer->customerTypeId);
  82. if ($inserted !== null) {
  83. $this->auditRepo()->log((int) $inserted['id'], 'I', $this->toAuditFields($inserted), $this->currentUsername());
  84. }
  85. return $this->redirect('/customers?saved=1');
  86. }
  87. public function edit(string $id): Response
  88. {
  89. $row = $this->repo()->findWithType((int) $id);
  90. if ($row === null) {
  91. return $this->redirect('/customers');
  92. }
  93. $storedValues = !empty($row['attribute_values'])
  94. ? (json_decode((string) $row['attribute_values'], true) ?? [])
  95. : [];
  96. $model = new CustomerViewModel();
  97. $model->title = 'Edit Customer';
  98. $model->customer = $row;
  99. $model->saved = Request::capture()->input('saved') === '1';
  100. $model->customerTypes = $this->loadCustomerTypes();
  101. $model->form = [
  102. 'customer_type_id' => (int) $row['customer_type_id'],
  103. 'attribute_values' => $storedValues,
  104. ];
  105. return $this->view('customers.edit', [
  106. 'model' => $model,
  107. 'pageTitle' => $model->title,
  108. ]);
  109. }
  110. public function update(string $id): Response
  111. {
  112. $before = $this->repo()->findWithType((int) $id);
  113. if ($before === null) {
  114. return $this->redirect('/customers');
  115. }
  116. $request = Request::capture();
  117. $model = new CustomerViewModel();
  118. $model->title = 'Edit Customer';
  119. $model->customer = $before;
  120. $model->customerTypes = $this->loadCustomerTypes();
  121. [$form, $errors] = $this->validateForm($request, $model->customerTypes);
  122. if (!empty($errors)) {
  123. $model->form = $form;
  124. $model->errors = $errors;
  125. return $this->view('customers.edit', [
  126. 'model' => $model,
  127. 'pageTitle' => $model->title,
  128. ]);
  129. }
  130. $customer = new Customer();
  131. $customer->id = (int) $id;
  132. $customer->customerTypeId = (int) $form['customer_type_id'];
  133. $customer->attributeValues = $form['attribute_values'];
  134. $this->repo()->update($customer);
  135. $after = $this->repo()->findWithType((int) $id);
  136. $this->auditRepo()->log((int) $id, 'U', [
  137. 'before' => $this->toAuditFields($before),
  138. 'after' => $this->toAuditFields($after ?? []),
  139. ], $this->currentUsername());
  140. return $this->redirect('/customers/' . $id . '/edit?saved=1');
  141. }
  142. public function destroy(string $id): Response
  143. {
  144. $row = $this->repo()->findWithType((int) $id);
  145. if ($row !== null) {
  146. $this->repo()->delete((int) $id);
  147. $this->auditRepo()->log((int) $row['id'], 'D', $this->toAuditFields($row), $this->currentUsername());
  148. }
  149. return $this->redirect('/customers?deleted=1');
  150. }
  151. public function lookup(): Response
  152. {
  153. $request = Request::capture();
  154. $typeId = (int) $request->input('type_id', 0);
  155. $matchParam = trim((string) $request->input('match_field', ''));
  156. if ($typeId <= 0) {
  157. return $this->json(['fields' => [], 'records' => []]);
  158. }
  159. // match_field is an attribute NAME (not alias)
  160. $matchNames = $matchParam !== ''
  161. ? array_values(array_filter(array_map('trim', explode(';', $matchParam))))
  162. : [];
  163. $rows = $this->repo()->searchByType($typeId);
  164. if (empty($rows)) {
  165. return $this->json(['fields' => [], 'records' => []]);
  166. }
  167. $typeAttrs = [];
  168. if (!empty($rows[0]['type_attributes'])) {
  169. $typeAttrs = json_decode((string) $rows[0]['type_attributes'], true) ?? [];
  170. }
  171. // Collect all attribute names in order
  172. $attrNames = [];
  173. foreach ($typeAttrs as $attr) {
  174. $name = trim((string) ($attr['name'] ?? ''));
  175. if ($name !== '') {
  176. $attrNames[] = $name;
  177. }
  178. }
  179. if (empty($matchNames) && !empty($attrNames)) {
  180. $matchNames = [$attrNames[0]];
  181. }
  182. $records = [];
  183. foreach ($rows as $row) {
  184. $attrValues = !empty($row['attribute_values'])
  185. ? (json_decode((string) $row['attribute_values'], true) ?? [])
  186. : [];
  187. // _row keyed by attribute NAME — matches attribute_values storage format
  188. $rowByName = [];
  189. foreach ($attrNames as $name) {
  190. $rowByName[$name] = (string) ($attrValues[$name] ?? '');
  191. }
  192. $display = array_map(fn($n) => $rowByName[$n] ?? '', $matchNames);
  193. $primary = $display[0] ?? '';
  194. if ($primary === '') {
  195. continue;
  196. }
  197. $records[] = [
  198. '_primary' => $primary,
  199. '_display' => array_values($display),
  200. '_row' => $rowByName,
  201. ];
  202. }
  203. return $this->json(['fields' => $matchNames, 'records' => $records]);
  204. }
  205. // ── Helpers ───────────────────────────────────────────────────────────────
  206. private function loadCustomerTypes(): array
  207. {
  208. return array_map(static function (array $type): array {
  209. return [
  210. 'id' => (int) $type['id'],
  211. 'name' => (string) $type['name'],
  212. 'attributes' => json_decode((string) ($type['attributes'] ?? '[]'), true) ?? [],
  213. ];
  214. }, $this->ctRepo()->allOrderedByName());
  215. }
  216. private function attributesForType(int $typeId, array $types): array
  217. {
  218. foreach ($types as $type) {
  219. if ($type['id'] === $typeId) return $type['attributes'];
  220. }
  221. return [];
  222. }
  223. private function validateForm(Request $request, array $customerTypes): array
  224. {
  225. $customerTypeId = (int) $request->input('customer_type_id', 0);
  226. $submittedValues = (array) ($request->input('attribute_values') ?? []);
  227. $errors = [];
  228. if (!verify_csrf_token((string) $request->input('_token', ''))) {
  229. $errors['_token'][] = 'Your form session expired. Please refresh and try again.';
  230. }
  231. if ($customerTypeId === 0) {
  232. $errors['customer_type_id'][] = 'Please select a customer type.';
  233. }
  234. $typeAttributes = $this->attributesForType($customerTypeId, $customerTypes);
  235. $attributeValues = [];
  236. foreach ($typeAttributes as $attr) {
  237. $attributeValues[$attr['name']] = trim((string) ($submittedValues[$attr['name']] ?? ''));
  238. }
  239. return [
  240. ['customer_type_id' => $customerTypeId, 'attribute_values' => $attributeValues],
  241. $errors,
  242. ];
  243. }
  244. private function toAuditFields(array $row): array
  245. {
  246. $attrValues = [];
  247. if (!empty($row['attribute_values'])) {
  248. $raw = $row['attribute_values'];
  249. $attrValues = is_string($raw) ? (json_decode($raw, true) ?? []) : (array) $raw;
  250. }
  251. return [
  252. 'customer_type_id' => (int) ($row['customer_type_id'] ?? 0),
  253. 'customer_type_name' => (string) ($row['customer_type_name'] ?? ''),
  254. 'attribute_values' => $attrValues,
  255. 'created_at' => (string) ($row['created_at'] ?? ''),
  256. 'updated_at' => (string) ($row['updated_at'] ?? ''),
  257. ];
  258. }
  259. private function currentUsername(): string
  260. {
  261. return auth()->user()?->username ?? 'system';
  262. }
  263. private function repo(): CustomerRepository
  264. {
  265. return new CustomerRepository(database());
  266. }
  267. private function auditRepo(): CustomerAuditRepository
  268. {
  269. return new CustomerAuditRepository(database());
  270. }
  271. private function ctRepo(): CustomerTypeRepository
  272. {
  273. return new CustomerTypeRepository(database());
  274. }
  275. }

Powered by TurnKey Linux.