Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

304 řádky
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. $encodedValues = json_encode($form['attribute_values'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
  78. $duplicate = $this->repo()->findDuplicate((int) $form['customer_type_id'], $encodedValues);
  79. if ($duplicate !== null) {
  80. $model->form = $form;
  81. $model->errors['_duplicate'] = [
  82. '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>.',
  83. ];
  84. return $this->view('customers.create', [
  85. 'model' => $model,
  86. 'pageTitle' => $model->title,
  87. ]);
  88. }
  89. $customer = new Customer();
  90. $customer->customerTypeId = (int) $form['customer_type_id'];
  91. $customer->attributeValues = $form['attribute_values'];
  92. $this->repo()->create($customer);
  93. $inserted = $this->repo()->findLatestByType($customer->customerTypeId);
  94. if ($inserted !== null) {
  95. $this->auditRepo()->log((int) $inserted['id'], 'I', $this->toAuditFields($inserted), $this->currentUsername());
  96. }
  97. return $this->redirect('/customers?saved=1');
  98. }
  99. public function edit(string $id): Response
  100. {
  101. $row = $this->repo()->findWithType((int) $id);
  102. if ($row === null) {
  103. return $this->redirect('/customers');
  104. }
  105. $storedValues = !empty($row['attribute_values'])
  106. ? (json_decode((string) $row['attribute_values'], true) ?? [])
  107. : [];
  108. $model = new CustomerViewModel();
  109. $model->title = 'Edit Customer';
  110. $model->customer = $row;
  111. $model->saved = Request::capture()->input('saved') === '1';
  112. $model->customerTypes = $this->loadCustomerTypes();
  113. $model->form = [
  114. 'customer_type_id' => (int) $row['customer_type_id'],
  115. 'attribute_values' => $storedValues,
  116. ];
  117. return $this->view('customers.edit', [
  118. 'model' => $model,
  119. 'pageTitle' => $model->title,
  120. ]);
  121. }
  122. public function update(string $id): Response
  123. {
  124. $before = $this->repo()->findWithType((int) $id);
  125. if ($before === null) {
  126. return $this->redirect('/customers');
  127. }
  128. $request = Request::capture();
  129. $model = new CustomerViewModel();
  130. $model->title = 'Edit Customer';
  131. $model->customer = $before;
  132. $model->customerTypes = $this->loadCustomerTypes();
  133. [$form, $errors] = $this->validateForm($request, $model->customerTypes);
  134. if (!empty($errors)) {
  135. $model->form = $form;
  136. $model->errors = $errors;
  137. return $this->view('customers.edit', [
  138. 'model' => $model,
  139. 'pageTitle' => $model->title,
  140. ]);
  141. }
  142. $encodedValues = json_encode($form['attribute_values'], JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
  143. $duplicate = $this->repo()->findDuplicate((int) $form['customer_type_id'], $encodedValues, (int) $id);
  144. if ($duplicate !== null) {
  145. $model->form = $form;
  146. $model->errors['_duplicate'] = [
  147. '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>.',
  148. ];
  149. return $this->view('customers.edit', [
  150. 'model' => $model,
  151. 'pageTitle' => $model->title,
  152. ]);
  153. }
  154. $customer = new Customer();
  155. $customer->id = (int) $id;
  156. $customer->customerTypeId = (int) $form['customer_type_id'];
  157. $customer->attributeValues = $form['attribute_values'];
  158. $this->repo()->update($customer);
  159. $after = $this->repo()->findWithType((int) $id);
  160. $this->auditRepo()->log((int) $id, 'U', [
  161. 'before' => $this->toAuditFields($before),
  162. 'after' => $this->toAuditFields($after ?? []),
  163. ], $this->currentUsername());
  164. return $this->redirect('/customers/' . $id . '/edit?saved=1');
  165. }
  166. public function destroy(string $id): Response
  167. {
  168. $row = $this->repo()->findWithType((int) $id);
  169. if ($row !== null) {
  170. $this->repo()->delete((int) $id);
  171. $this->auditRepo()->log((int) $row['id'], 'D', $this->toAuditFields($row), $this->currentUsername());
  172. }
  173. return $this->redirect('/customers?deleted=1');
  174. }
  175. // ── Helpers ───────────────────────────────────────────────────────────────
  176. private function loadCustomerTypes(): array
  177. {
  178. return array_map(static function (array $type): array {
  179. return [
  180. 'id' => (int) $type['id'],
  181. 'name' => (string) $type['name'],
  182. 'attributes' => json_decode((string) ($type['attributes'] ?? '[]'), true) ?? [],
  183. ];
  184. }, $this->ctRepo()->allOrderedByName());
  185. }
  186. private function attributesForType(int $typeId, array $types): array
  187. {
  188. foreach ($types as $type) {
  189. if ($type['id'] === $typeId) return $type['attributes'];
  190. }
  191. return [];
  192. }
  193. private function validateForm(Request $request, array $customerTypes): array
  194. {
  195. $customerTypeId = (int) $request->input('customer_type_id', 0);
  196. $submittedValues = (array) ($request->input('attribute_values') ?? []);
  197. $errors = [];
  198. if (!verify_csrf_token((string) $request->input('_token', ''))) {
  199. $errors['_token'][] = 'Your form session expired. Please refresh and try again.';
  200. }
  201. if ($customerTypeId === 0) {
  202. $errors['customer_type_id'][] = 'Please select a customer type.';
  203. }
  204. $typeAttributes = $this->attributesForType($customerTypeId, $customerTypes);
  205. $attributeValues = [];
  206. foreach ($typeAttributes as $attr) {
  207. $attributeValues[$attr['name']] = trim((string) ($submittedValues[$attr['name']] ?? ''));
  208. }
  209. return [
  210. ['customer_type_id' => $customerTypeId, 'attribute_values' => $attributeValues],
  211. $errors,
  212. ];
  213. }
  214. private function toAuditFields(array $row): array
  215. {
  216. $attrValues = [];
  217. if (!empty($row['attribute_values'])) {
  218. $raw = $row['attribute_values'];
  219. $attrValues = is_string($raw) ? (json_decode($raw, true) ?? []) : (array) $raw;
  220. }
  221. return [
  222. 'customer_type_id' => (int) ($row['customer_type_id'] ?? 0),
  223. 'customer_type_name' => (string) ($row['customer_type_name'] ?? ''),
  224. 'attribute_values' => $attrValues,
  225. 'created_at' => (string) ($row['created_at'] ?? ''),
  226. 'updated_at' => (string) ($row['updated_at'] ?? ''),
  227. ];
  228. }
  229. private function currentUsername(): string
  230. {
  231. return auth()->user()?->username ?? 'system';
  232. }
  233. private function repo(): CustomerRepository
  234. {
  235. return new CustomerRepository(database());
  236. }
  237. private function auditRepo(): CustomerAuditRepository
  238. {
  239. return new CustomerAuditRepository(database());
  240. }
  241. private function ctRepo(): CustomerTypeRepository
  242. {
  243. return new CustomerTypeRepository(database());
  244. }
  245. }

Powered by TurnKey Linux.