diff --git a/app/Domains/Taxation/Http/Requests/TaxTypeRequest.php b/app/Domains/Taxation/Http/Requests/TaxTypeRequest.php new file mode 100644 index 00000000..79982d3d --- /dev/null +++ b/app/Domains/Taxation/Http/Requests/TaxTypeRequest.php @@ -0,0 +1,166 @@ + + */ + public function rules(): array + { + $name = Rule::unique('tax_types') + ->where('type', TaxType::TYPE_GENERAL) + ->where('company_id', $this->header('company')); + + // Quirk kept as is: only PUT excuses the edited row from the name + // check. A PATCH would collide with its own stored name. + if ($this->isMethod('PUT')) { + $name->ignore($this->route('tax_type')->id); + } + + return [ + 'name' => [ + 'required', + $name, + ], + 'calculation_type' => [ + 'required', + Rule::in(['percentage', 'fixed']), + ], + 'percent' => [ + 'nullable', + 'numeric', + ], + 'fixed_amount' => [ + 'nullable', + 'numeric', + ], + 'description' => [ + 'nullable', + ], + 'compound_tax' => [ + 'sometimes', + 'boolean', + ], + 'collective_tax' => [ + 'nullable', + ], + 'transaction_type' => [ + 'sometimes', + Rule::in([ + TaxType::TRANSACTION_TYPE_SALES, + TaxType::TRANSACTION_TYPE_PURCHASES, + ]), + ], + ]; + } + + /** + * Compounding is reserved for percentage taxes on the sales side. + * + * The check is skipped while any other rule has already failed, so a + * request with, say, a bad calculation type reports that alone. + */ + public function withValidator(Validator $validator): void + { + $validator->after(function (Validator $validator): void { + if ($validator->errors()->isNotEmpty() || ! $this->compoundFlag()) { + return; + } + + $allowed = $this->settledValue('calculation_type', 'percentage') === 'percentage' + && $this->settledValue('transaction_type', TaxType::TRANSACTION_TYPE_SALES) + === TaxType::TRANSACTION_TYPE_SALES; + + if (! $allowed) { + $validator->errors()->add( + 'compound_tax', + 'Compound tax is only available for percentage sales taxes.' + ); + } + }); + } + + /** + * Attributes to write, with the gaps a legacy client leaves filled in. + */ + public function getTaxTypePayload() + { + $payload = $this->validated(); + + if (! array_key_exists('transaction_type', $payload)) { + $payload['transaction_type'] = $this->isEdit() + ? $this->storedValue('transaction_type') + : TaxType::TRANSACTION_TYPE_SALES; + } + + // An edit that omits the flag keeps whatever the row already holds; + // a create that omits it is plainly not compound. + if (! $this->isEdit() && ! array_key_exists('compound_tax', $payload)) { + $payload['compound_tax'] = false; + } + + $payload['company_id'] = $this->header('company'); + $payload['type'] = TaxType::TYPE_GENERAL; + + return $payload; + } + + /** + * Whether the row will carry the compound flag once written. + */ + private function compoundFlag(): bool + { + if ($this->has('compound_tax')) { + return $this->boolean('compound_tax'); + } + + return $this->isEdit() ? $this->storedValue('compound_tax') : false; + } + + /** + * The value a field will hold after the write: what was sent, else the + * stored value on an edit, else the create-time default. + */ + private function settledValue(string $field, string $default): ?string + { + if ($this->has($field)) { + return $this->input($field); + } + + return $this->isEdit() ? $this->storedValue($field) : $default; + } + + private function storedValue(string $field) + { + return $this->route('tax_type')->{$field}; + } + + private function isEdit(): bool + { + return $this->isMethod('PUT') || $this->isMethod('PATCH'); + } +}