package->status === BidPackageStatus::Open, 403, 'Bid package is not open.'); abort_if($invitation->submission()->exists(), 422, 'You have already submitted a bid.'); $invitation->load('package.project:id,name,code', 'package.criteria'); return Inertia::render('BiddingManagement::Bids/Contractor/Submit', [ 'invitation' => $invitation, ]); } public function store(Request $request, BidInvitation $invitation) { abort_unless($invitation->package->status === BidPackageStatus::Open, 403, 'Bid package is not open.'); abort_if($invitation->submission()->exists(), 422, 'You have already submitted a bid.'); $validated = $request->validate([ 'submitted_price' => 'required|numeric|min:0', 'completion_days' => 'required|integer|min:1', 'validity_date' => 'nullable|date|after_or_equal:today', 'technical_notes' => 'nullable|string', 'documents' => 'nullable|array', 'documents.*' => 'file|max:20480|mimes:pdf,doc,docx,xls,xlsx,png,jpg,jpeg', ]); // Accept invitation if still invited if ($invitation->status === 'invited') { $invitation->accept(); } $documents = $validated['documents'] ?? []; unset($validated['documents']); $validated['bid_invitation_id'] = $invitation->id; $validated['submitted_at'] = now(); $submission = BidSubmission::create($validated); // Handle document uploads via DocumentManagement pattern foreach ($documents as $file) { $path = $file->store('bid-submissions', 'public'); $document = $submission->documents()->create([ 'title' => $file->getClientOriginalName(), 'category' => 'bid_document', 'documentable_type' => BidSubmission::class, 'documentable_id' => $submission->id, 'uploaded_by' => Auth::id(), 'current_file_path' => $path, 'current_file_name' => $file->getClientOriginalName(), 'mime_type' => $file->getMimeType(), 'file_size' => $file->getSize(), 'version_count' => 1, ]); } BidSubmitted::dispatch($submission); return redirect() ->route('bids.my') ->with('success', 'Your bid has been submitted successfully.'); } public function show(BidSubmission $submission) { $submission->load([ 'invitation.contractor:id,ulid,company_name,contact_person,email,specialization', 'invitation.package:id,ulid,title,evaluation_mode', 'invitation.package.criteria', 'scores.criteria', 'scores.evaluator:id,name', 'documents', ]); return Inertia::render('BiddingManagement::Bids/Submission/Show', [ 'submission' => $submission, ]); } public function shortlist(BidSubmission $submission) { try { $submission->transitionTo(BidSubmissionStatus::Shortlisted); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return back()->with('success', 'Submission shortlisted.'); } public function reject(BidSubmission $submission) { try { $submission->transitionTo(BidSubmissionStatus::Rejected); } catch (\InvalidArgumentException $e) { return back()->with('error', $e->getMessage()); } return back()->with('success', 'Submission rejected.'); } public function myBids() { $user = Auth::user(); $contractor = Contractor::where('id', $user->contractor_id ?? null)->first(); abort_unless($contractor, 403, 'No contractor profile found for your account.'); $invitations = $contractor->bidInvitations() ->with([ 'package:id,ulid,title,submission_deadline,status,evaluation_mode', 'package.project:id,name,code', 'submission:id,ulid,bid_invitation_id,submitted_price,completion_days,status,submitted_at', ]) ->latest() ->get(); return Inertia::render('BiddingManagement::Bids/Contractor/MyBids', [ 'invitations' => $invitations, 'contractor' => $contractor, ]); } }