commit9
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-30 20:02:30 +02:00
parent 6d14b4e6f0
commit c6373365bf
225 changed files with 15365 additions and 285 deletions
+12 -2
View File
@@ -8,8 +8,10 @@ use App\Models\PurchaseAward;
use App\Models\Quotation;
use App\Models\Requisition;
use App\Models\Supplier;
use App\Services\ComparisonPolicy;
use App\Services\ConflictGuard;
use App\Services\ProposalComparison;
use App\Services\QuotationPricing;
use App\Services\RequisitionWorkflow;
use Brick\Math\BigDecimal;
use Brick\Math\RoundingMode;
@@ -49,6 +51,8 @@ class QuotationController extends Controller
'canCreate' => ! $closed && $request->user()->hasFunctions([PurchasingRole::Procurement]) && $requisition->status === 'approved' && ! $guard->participationBlocked($request->user(), $requisition),
'canEvaluate' => ! $closed && $request->user()->hasFunctions([PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]) && $request->user()->id !== $requisition->requester_id && $requisition->status === 'approved' && ! $guard->participationBlocked($request->user(), $requisition),
'ready' => $result['ready'],
'topIds' => $result['top_ids'],
'comparisonPolicy' => $result['policy'],
]);
}
@@ -61,6 +65,8 @@ class QuotationController extends Controller
'payment_terms' => 'required|string|min:3|max:500', 'no_conflict' => 'accepted',
'items' => 'required|array|min:1|max:100', 'items.*.requisition_item_id' => 'required|integer|distinct',
'items.*.unit_price' => ['required', 'numeric', 'decimal:0,2', 'min:0.01', 'max:1000000000'],
'items.*.discount_type' => 'sometimes|required|in:none,percent,amount',
'items.*.discount_value' => 'required_with:items.*.discount_type|numeric|decimal:0,2|min:0|max:1000000000',
]);
DB::transaction(function () use ($request, $requisition, $data, $workflow) {
$guard = app(ConflictGuard::class);
@@ -84,6 +90,7 @@ class QuotationController extends Controller
throw ValidationException::withMessages(['items' => 'A proposta deve conter todas as linhas da requisição.']);
}
$items = [];
$terms = [];
$subtotal = 0;
$tax = 0;
foreach ($data['items'] as $input) {
@@ -91,7 +98,9 @@ class QuotationController extends Controller
if (! $line) {
throw ValidationException::withMessages(['items' => 'Artigo não pertence a esta requisição.']);
}
$price = BigDecimal::of($input['unit_price'])->multipliedBy(100)->toScale(0)->toInt();
$pricing = app(QuotationPricing::class)->unit((string) $input['unit_price'], $input['discount_type'] ?? 'none', (string) ($input['discount_value'] ?? '0'));
$terms[] = ['requisition_item_id' => $line->id, ...$pricing];
$price = $pricing['net_unit_minor'];
$amount = BigDecimal::of($line->quantity)->multipliedBy($price)->toScale(0, RoundingMode::HalfUp)->toInt();
$vat = BigDecimal::of($amount)->multipliedBy($line->tax_rate)->dividedBy(100, 0, RoundingMode::HalfUp)->toInt();
$subtotal += $amount;
@@ -108,7 +117,8 @@ class QuotationController extends Controller
'tenant_id' => $request->user()->tenant_id, 'requisition_id' => $requisition->id, 'supplier_id' => $supplier->id, 'created_by' => $request->user()->id,
'reference' => $data['reference'], 'delivery_days' => $data['delivery_days'], 'valid_until' => $data['valid_until'], 'payment_terms' => $data['payment_terms'],
'subtotal_minor' => $subtotal, 'tax_minor' => $tax, 'total_minor' => $subtotal + $tax, 'currency' => 'MZN',
'weights' => ['price' => 40, 'quality' => 20, 'delivery' => 15, 'history' => 15, 'payment' => 10],
'weights' => app(ComparisonPolicy::class)->forRequest($requisition)['weights'],
'commercial_terms' => array_filter($terms, fn ($term) => $term['discount_type'] !== 'none') === [] ? null : ['version' => 1, 'discount_basis' => 'unit_before_vat', 'rounding' => 'net_unit_half_up_cent', 'lines' => $terms],
]);
$quote->items()->createMany($items);
$workflow->audit($request->user(), 'quotation.created', $requisition, null, [...$quote->load('items')->toArray(), 'no_conflict' => true]);