> */ public function rules(): array { $rules = [ 'name' => ['required'], 'email' => $this->emailRules(), ]; foreach (self::OPTIONAL_FIELDS as $field) { $rules[$field] = ['nullable']; } $rules['enable_portal'] = ['boolean']; $rules['currency_id'] = ['nullable']; foreach (['billing', 'shipping'] as $block) { foreach (self::ADDRESS_FIELDS as $field) { $rules[$block.'.'.$field] = ['nullable']; } } return $rules; } /** * The row to persist: the allow-listed columns of the validated payload, * with authorship and tenancy stamped on top of whatever was sent. * * @return array */ public function customerAttributes(): array { $attributes = Arr::only($this->validated(), self::PERSISTED_FIELDS); $attributes['creator_id'] = $this->user()->id; $attributes['company_id'] = $this->header('company'); return $attributes; } /** * The shipping block, ready for the addresses table. * * @return array|null */ public function shippingAddress(): ?array { return $this->addressBlock('shipping', Address::SHIPPING_TYPE); } /** * The billing block, ready for the addresses table. * * @return array|null */ public function billingAddress(): ?array { return $this->addressBlock('billing', Address::BILLING_TYPE); } /** * Custom-field values, or null when none came in. An empty array counts as * none, so the writer is never called for nothing. * * @return array|null */ public function customFields(): ?array { $values = $this->input('customFields'); if (! is_array($values) || $values === []) { return null; } return $values; } /** * Optional, but once given it has to parse — internationalised domains * included — and be unclaimed by another contact of the same company. * * On an update carrying an address the contact being edited is excused * from that check. The verb test is exact, so the same payload sent as * PATCH would collide with the contact's own row; kept as it stands. * * @return array */ private function emailRules(): array { $unclaimed = Rule::unique('customers')->where('company_id', $this->header('company')); if ($this->email != null && $this->isMethod('PUT')) { $unclaimed->ignore($this->route('customer')->id); } return [new IdnEmail, 'nullable', $unclaimed]; } /** * One address block tagged with its type, or null when the payload has no * such block — a block whose every field is null counts as no block. * * @return array|null */ private function addressBlock(string $key, string $type): ?array { $block = $this->input($key); if (! is_array($block) || Arr::where($block, fn ($value): bool => isset($value)) === []) { return null; } return array_merge($block, ['type' => $type]); } }