Additional Changes
Some checks failed
Tests / PHP 8.3 (push) Has been cancelled
Tests / PHP 8.4 (push) Has been cancelled
Tests / PHP 8.5 (push) Has been cancelled

This commit is contained in:
2026-06-02 22:04:03 +08:00
parent e6a9a80c32
commit 64d4b331a8
151 changed files with 15070 additions and 945 deletions

View File

@@ -13,26 +13,61 @@ use Modules\MaterialLogistics\Models\MaterialRequisition;
use Modules\MasterData\Models\MaterialGroup;
use Modules\MaterialLogistics\Services\DocumentNumberService;
use Modules\ProjectManagement\Models\Project;
use Modules\ProjectManagement\Services\ProjectWorkflowService;
class MaterialRequisitionController extends Controller
{
public function index(Request $request)
{
$requisitions = MaterialRequisition::with([
$query = MaterialRequisition::with([
'requester:id,ulid,name',
'approver:id,ulid,name',
'items:id,material_requisition_id,quantity,unit_cost' // Need items for frontend line total
])
->latest()
->paginate(20);
'project:id,ulid,name,code',
'items:id,material_requisition_id,quantity,unit_cost'
]);
if ($request->filled('project_ulid')) {
$project = Project::where('ulid', $request->project_ulid)->first();
if ($project) {
$query->where('project_id', $project->id);
}
}
$requisitions = $query->latest()->paginate(20);
return Inertia::render('MaterialLogistics::Requisitions/Index', [
'requisitions' => $requisitions,
'projects' => Project::active()->where('current_wizard_step', '>=', 8)->select('id', 'ulid', 'name', 'code', 'client_name', 'location', 'start_date', 'target_end_date', 'status', 'contract_value')->get(),
]);
}
public function create()
public function create(Request $request)
{
$selectedProject = null;
$prefilledItems = [];
if ($request->filled('project_ulid')) {
$selectedProject = Project::where('ulid', $request->project_ulid)
->with('materialsEstimates.material:id,ulid,name,unit,unit_cost')
->firstOrFail();
$workflowService = new ProjectWorkflowService();
if (!$workflowService->canCreateRequisition($selectedProject)) {
return redirect()->back()->with('error', 'Cannot create material requisition: Project is not in scheduled/procuring state or task resources are missing.');
}
// Auto-populate from project materials estimation
foreach ($selectedProject->materialsEstimates as $estimate) {
$prefilledItems[] = [
'material_ulid' => $estimate->material->ulid,
'material_name' => $estimate->material->name,
'unit' => $estimate->material->unit,
'quantity' => (float) $estimate->estimated_qty,
'unit_cost' => (float) $estimate->unit_cost,
];
}
}
$materials = Material::select('id', 'ulid', 'name', 'unit', 'unit_cost', 'sku', 'category', 'status')
->where('status', 'active')
->orderBy('category')
@@ -62,12 +97,16 @@ class MaterialRequisitionController extends Controller
return Inertia::render('MaterialLogistics::Requisitions/Form', [
'materials' => $materials,
'materialGroups' => $materialGroups,
'projects' => Project::active()->where('current_wizard_step', '>=', 8)->select('id', 'ulid', 'name', 'code', 'client_name', 'location', 'start_date', 'target_end_date', 'status', 'contract_value')->get(),
'selectedProject' => $selectedProject,
'prefilledItems' => $prefilledItems,
]);
}
public function store(Request $request)
{
$validated = $request->validate([
'project_ulid' => 'required|string|exists:projects,ulid',
'notes' => 'nullable|string|max:1000',
'items' => 'required|array|min:1',
'items.*.material_ulid' => 'required|string',
@@ -75,10 +114,30 @@ class MaterialRequisitionController extends Controller
'items.*.unit_cost' => 'required|numeric|min:0',
]);
$project = Project::where('ulid', $validated['project_ulid'])->firstOrFail();
if ($project->current_wizard_step < 8) {
return redirect()->back()->with('error', 'Cannot create material requisition: Project estimation is not approved.');
}
$workflowService = new ProjectWorkflowService();
if (!$workflowService->canCreateRequisition($project)) {
return redirect()->back()->with('error', 'Cannot create material requisition: Project is not in scheduled/procuring state.');
}
// Validate that all items are in the project's materials estimates
$estimatedMaterialIds = $project->materialsEstimates()->pluck('material_id')->toArray();
foreach ($validated['items'] as $item) {
$material = Material::where('ulid', $item['material_ulid'])->firstOrFail();
if (!in_array($material->id, $estimatedMaterialIds)) {
return redirect()->back()->with('error', "Cannot create material requisition: Material '{$material->name}' is not in the project's approved estimation.");
}
}
$docService = new DocumentNumberService();
$mr = DB::transaction(function () use ($validated, $docService) {
$mr = DB::transaction(function () use ($validated, $project, $docService) {
$mr = MaterialRequisition::create([
'project_id' => $project->id,
'document_number' => $docService->nextMrNumber(),
'status' => 'draft',
'requested_by' => auth()->id(),
@@ -106,7 +165,7 @@ class MaterialRequisitionController extends Controller
return redirect()->back()->with('error', 'Only draft requisitions can be edited.');
}
$requisition->load('items.material:id,ulid,name,unit,unit_cost');
$requisition->load('items.material:id,ulid,name,unit,unit_cost', 'project:id,ulid,name,code');
$materials = Material::select('id', 'ulid', 'name', 'sku', 'category', 'unit', 'unit_cost', 'status')
->where('status', 'active')
@@ -138,6 +197,7 @@ class MaterialRequisitionController extends Controller
'requisition' => $requisition,
'materials' => $materials,
'materialGroups' => $materialGroups,
'projects' => Project::active()->where('current_wizard_step', '>=', 8)->select('id', 'ulid', 'name', 'code', 'client_name', 'location', 'start_date', 'target_end_date', 'status', 'contract_value')->get(),
]);
}
@@ -148,6 +208,7 @@ class MaterialRequisitionController extends Controller
}
$validated = $request->validate([
'project_ulid' => 'required|string|exists:projects,ulid',
'notes' => 'nullable|string|max:1000',
'items' => 'required|array|min:1',
'items.*.material_ulid' => 'required|string',
@@ -155,8 +216,28 @@ class MaterialRequisitionController extends Controller
'items.*.unit_cost' => 'required|numeric|min:0',
]);
DB::transaction(function () use ($validated, $requisition) {
$project = Project::where('ulid', $validated['project_ulid'])->firstOrFail();
if ($project->current_wizard_step < 8) {
return redirect()->back()->with('error', 'Cannot update material requisition: Project estimation is not approved.');
}
$workflowService = new ProjectWorkflowService();
if (!$workflowService->canCreateRequisition($project)) {
return redirect()->back()->with('error', 'Cannot update material requisition: Project is not in scheduled/procuring state.');
}
// Validate that all items are in the project's materials estimates
$estimatedMaterialIds = $project->materialsEstimates()->pluck('material_id')->toArray();
foreach ($validated['items'] as $item) {
$material = Material::where('ulid', $item['material_ulid'])->firstOrFail();
if (!in_array($material->id, $estimatedMaterialIds)) {
return redirect()->back()->with('error', "Cannot update material requisition: Material '{$material->name}' is not in the project's approved estimation.");
}
}
DB::transaction(function () use ($validated, $project, $requisition) {
$requisition->update([
'project_id' => $project->id,
'notes' => $validated['notes'] ?? null,
]);