'tax_register', 'installments' => 'installments', 'proposal_value' => 'register_value', 'proposal_min' => 'register_installments_min', 'proposal_max' => 'register_installments_max', ], [ 'value' => 'package_value', 'installments' => 'package_installments', 'proposal_value' => 'package_value', 'proposal_min' => 'package_installments_min', 'proposal_max' => 'package_installments_max', ], [ 'value' => 'material_value', 'installments' => 'material_installments', 'proposal_value' => 'material_value', 'proposal_min' => 'material_installments_min', 'proposal_max' => 'material_installments_max', ], ]; public function __construct( private readonly ?Closure $packageUnitResolver = null, ) {} // retorna [campo => [mensagens]]; vazio quando esta tudo consistente. public function errors(array $data): array { if (empty($data['class_package_unit_id'])) { return []; } $packageUnit = $this->packageUnitResolver ? ($this->packageUnitResolver)((int) $data['class_package_unit_id']) : ClassPackageUnit::with('variations')->find($data['class_package_unit_id']); if (! $packageUnit) { return []; } // proposta fixa selecionada: valores e parcelas vem dela if (! empty($data['class_package_unit_variation_id'])) { $proposal = $packageUnit->variations->firstWhere('id', (int) $data['class_package_unit_variation_id']); if (! $proposal) { return ['class_package_unit_variation_id' => [__('validation.student_contract_variation_unavailable')]]; } return $this->fixedProposalErrors($data, $proposal); } // sem proposta o contrato e custom: limites entre irrecusavel e pavao return $this->customProposalErrors( $data, $packageUnit->variations->firstWhere('type', ClassPackageUnitVariation::TYPE_PAVAO), $packageUnit->variations->firstWhere('type', ClassPackageUnitVariation::TYPE_IRRECUSAVEL), ); } // private function fixedProposalErrors(array $data, ClassPackageUnitVariation $proposal): array { $errors = []; // qualquer valor diferente da proposta caracteriza custom foreach (self::ITEMS as $item) { if (round((float) ($data[$item['value']] ?? 0), 2) !== round((float) $proposal->{$item['proposal_value']}, 2)) { $errors[$item['value']][] = __('validation.student_contract_variation_value_mismatch'); } $this->addInstallmentsError($errors, $item, $data, $proposal->{$item['proposal_min']}, $proposal->{$item['proposal_max']}); } return $errors; } private function customProposalErrors( array $data, ?ClassPackageUnitVariation $pavao, ?ClassPackageUnitVariation $irrecusavel, ): array { // sem as duas propostas configuradas nao existem limites para conferir if (! $pavao || ! $irrecusavel) { return []; } $errors = []; // o montante final fica entre o total da irrecusavel e o total da pavao $total = round( (float) ($data['tax_register'] ?? 0) + (float) ($data['package_value'] ?? 0) + (float) ($data['material_value'] ?? 0), 2, ); $min = $irrecusavel->totalValue(); $max = $pavao->totalValue(); if ($total < $min || $total > $max) { $errors['package_value'][] = __('validation.student_contract_custom_total_range', [ 'min' => number_format($min, 2, ',', '.'), 'max' => number_format($max, 2, ',', '.'), ]); } // as parcelas ficam entre o minimo da irrecusavel e o maximo da pavao foreach (self::ITEMS as $item) { $this->addInstallmentsError($errors, $item, $data, $irrecusavel->{$item['proposal_min']}, $pavao->{$item['proposal_max']}); } return $errors; } // private function addInstallmentsError(array &$errors, array $item, array $data, int $min, int $max): void { $installments = $data[$item['installments']] ?? null; if ($installments === null) { return; } if ((int) $installments < $min || (int) $installments > $max) { $errors[$item['installments']][] = __('validation.student_contract_installments_range', [ 'min' => $min, 'max' => $max, ]); } } }