StudentContractVariationConsistency.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <?php
  2. namespace App\Rules;
  3. use App\Models\ClassPackageUnit;
  4. use App\Models\ClassPackageUnitVariation;
  5. use Closure;
  6. // confere se os valores e parcelas do contrato sao compativeis com a
  7. // proposta (variation) escolhida do pacote da unidade.
  8. // usada no studentcontractrequest e no booted do studentcontract, para que
  9. // nenhum fluxo grave um contrato incongruente com a proposta.
  10. class StudentContractVariationConsistency
  11. {
  12. // vinculo entre os campos do contrato e as colunas da proposta
  13. private const ITEMS = [
  14. [
  15. 'value' => 'tax_register',
  16. 'installments' => 'installments',
  17. 'proposal_value' => 'register_value',
  18. 'proposal_min' => 'register_installments_min',
  19. 'proposal_max' => 'register_installments_max',
  20. ],
  21. [
  22. 'value' => 'package_value',
  23. 'installments' => 'package_installments',
  24. 'proposal_value' => 'package_value',
  25. 'proposal_min' => 'package_installments_min',
  26. 'proposal_max' => 'package_installments_max',
  27. ],
  28. [
  29. 'value' => 'material_value',
  30. 'installments' => 'material_installments',
  31. 'proposal_value' => 'material_value',
  32. 'proposal_min' => 'material_installments_min',
  33. 'proposal_max' => 'material_installments_max',
  34. ],
  35. ];
  36. public function __construct(
  37. private readonly ?Closure $packageUnitResolver = null,
  38. ) {}
  39. // retorna [campo => [mensagens]]; vazio quando esta tudo consistente.
  40. public function errors(array $data): array
  41. {
  42. if (empty($data['class_package_unit_id'])) {
  43. return [];
  44. }
  45. $packageUnit = $this->packageUnitResolver
  46. ? ($this->packageUnitResolver)((int) $data['class_package_unit_id'])
  47. : ClassPackageUnit::with('variations')->find($data['class_package_unit_id']);
  48. if (! $packageUnit) {
  49. return [];
  50. }
  51. // proposta fixa selecionada: valores e parcelas vem dela
  52. if (! empty($data['class_package_unit_variation_id'])) {
  53. $proposal = $packageUnit->variations->firstWhere('id', (int) $data['class_package_unit_variation_id']);
  54. if (! $proposal) {
  55. return ['class_package_unit_variation_id' => [__('validation.student_contract_variation_unavailable')]];
  56. }
  57. return $this->fixedProposalErrors($data, $proposal);
  58. }
  59. // sem proposta o contrato e custom: limites entre irrecusavel e pavao
  60. return $this->customProposalErrors(
  61. $data,
  62. $packageUnit->variations->firstWhere('type', ClassPackageUnitVariation::TYPE_PAVAO),
  63. $packageUnit->variations->firstWhere('type', ClassPackageUnitVariation::TYPE_IRRECUSAVEL),
  64. );
  65. }
  66. //
  67. private function fixedProposalErrors(array $data, ClassPackageUnitVariation $proposal): array
  68. {
  69. $errors = [];
  70. // qualquer valor diferente da proposta caracteriza custom
  71. foreach (self::ITEMS as $item) {
  72. if (round((float) ($data[$item['value']] ?? 0), 2) !== round((float) $proposal->{$item['proposal_value']}, 2)) {
  73. $errors[$item['value']][] = __('validation.student_contract_variation_value_mismatch');
  74. }
  75. $this->addInstallmentsError($errors, $item, $data, $proposal->{$item['proposal_min']}, $proposal->{$item['proposal_max']});
  76. }
  77. return $errors;
  78. }
  79. private function customProposalErrors(
  80. array $data,
  81. ?ClassPackageUnitVariation $pavao,
  82. ?ClassPackageUnitVariation $irrecusavel,
  83. ): array {
  84. // sem as duas propostas configuradas nao existem limites para conferir
  85. if (! $pavao || ! $irrecusavel) {
  86. return [];
  87. }
  88. $errors = [];
  89. // o montante final fica entre o total da irrecusavel e o total da pavao
  90. $total = round(
  91. (float) ($data['tax_register'] ?? 0)
  92. + (float) ($data['package_value'] ?? 0)
  93. + (float) ($data['material_value'] ?? 0),
  94. 2,
  95. );
  96. $min = $irrecusavel->totalValue();
  97. $max = $pavao->totalValue();
  98. if ($total < $min || $total > $max) {
  99. $errors['package_value'][] = __('validation.student_contract_custom_total_range', [
  100. 'min' => number_format($min, 2, ',', '.'),
  101. 'max' => number_format($max, 2, ',', '.'),
  102. ]);
  103. }
  104. // as parcelas ficam entre o minimo da irrecusavel e o maximo da pavao
  105. foreach (self::ITEMS as $item) {
  106. $this->addInstallmentsError($errors, $item, $data, $irrecusavel->{$item['proposal_min']}, $pavao->{$item['proposal_max']});
  107. }
  108. return $errors;
  109. }
  110. //
  111. private function addInstallmentsError(array &$errors, array $item, array $data, int $min, int $max): void
  112. {
  113. $installments = $data[$item['installments']] ?? null;
  114. if ($installments === null) {
  115. return;
  116. }
  117. if ((int) $installments < $min || (int) $installments > $max) {
  118. $errors[$item['installments']][] = __('validation.student_contract_installments_range', [
  119. 'min' => $min,
  120. 'max' => $max,
  121. ]);
  122. }
  123. }
  124. }