feat: implement comprehensive modules for daily reports, project management, material logistics, and approval workflows

This commit is contained in:
Ajjj
2026-08-26 15:48:41 +08:00
parent d4e2b468ff
commit d6efc46624
52 changed files with 5275 additions and 932 deletions

View File

@@ -7,6 +7,7 @@ use App\Models\User;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Modules\MasterData\Models\Material;
use Modules\MasterData\Models\MaterialGroup;
use Modules\MaterialLogistics\Models\ProjectInventory;
use Modules\ProjectManagement\Enums\DelayReason;
use Modules\ProjectManagement\Enums\ProjectStatus;
@@ -17,6 +18,7 @@ use Modules\ProjectManagement\Models\ProjectClassification;
use Modules\ProjectManagement\Models\ProjectMilestone;
use Modules\ProjectManagement\Models\Task;
use Modules\Labors\Models\Labor;
use Modules\Equipments\Models\Equipment;
use Modules\MasterData\Models\Team;
use Modules\MasterData\Models\EquipmentRate;
use Barryvdh\DomPDF\Facade\Pdf;
@@ -122,18 +124,19 @@ class ProjectController extends Controller
$validated = $request->validate([
'name' => 'required|string|max:255',
'client_name' => 'nullable|string|max:255',
'description' => 'nullable|string',
'location' => 'nullable|string|max:255',
'contract_value' => 'nullable|numeric|min:0',
'contract_duration' => 'nullable|integer|min:0',
'start_date' => 'nullable|date',
'target_end_date' => 'nullable|date|after_or_equal:start_date',
'client_name' => 'required|string|max:255',
'description' => 'required|string|min:3',
'location' => 'required|string|max:255',
'contract_value' => 'required|numeric|min:0.01',
'contract_duration' => 'required|integer|min:1',
'start_date' => 'required|date',
'target_end_date' => 'required|date|after_or_equal:start_date',
'project_type' => 'required|string|in:standard,special,extension',
'classifications' => 'nullable|array',
'classifications' => 'required|array|min:1',
'classifications.*' => 'string',
'parent_project_id' => 'nullable|string',
'is_unprofitable' => 'nullable|boolean',
'pm_id' => 'required',
]);
if (!empty($validated['parent_project_id'])) {
@@ -336,15 +339,15 @@ class ProjectController extends Controller
$validated = $request->validate([
'name' => 'required|string|max:255',
'client_name' => 'nullable|string|max:255',
'description' => 'nullable|string',
'location' => 'nullable|string|max:255',
'contract_value' => 'nullable|numeric|min:0',
'contract_duration' => 'nullable|integer|min:0',
'start_date' => 'nullable|date',
'target_end_date' => 'nullable|date|after_or_equal:start_date',
'client_name' => 'required|string|max:255',
'description' => 'required|string|min:3',
'location' => 'required|string|max:255',
'contract_value' => 'required|numeric|min:0.01',
'contract_duration' => 'required|integer|min:1',
'start_date' => 'required|date',
'target_end_date' => 'required|date|after_or_equal:start_date',
'project_type' => 'required|string|in:standard,special,extension',
'classifications' => 'nullable|array',
'classifications' => 'required|array|min:1',
'classifications.*' => 'string',
'parent_project_id' => 'nullable|string',
'is_unprofitable' => 'nullable|boolean',
@@ -483,38 +486,22 @@ class ProjectController extends Controller
->with(['employeeProfile', 'roles'])
->select('id', 'ulid', 'name', 'email', 'user_type', 'profile_picture')
->get();
$parentProjects = Project::active()->with('parentProject:id,ulid,name,code')->where('id', '!=', $project->id)->get(['id', 'ulid', 'name', 'code', 'parent_project_id', 'project_type']);
$materials = \Modules\MasterData\Models\Material::where('type', 'single')
->where('status', 'active')
->select('id', 'ulid', 'name', 'unit', 'unit_cost')
->get();
$materialGroups = \Modules\MasterData\Models\Material::with('components.component:id,ulid,name,sku,category,unit,unit_cost')
$parentProjects = Project::where('id', '!=', $project->id)->active()->with('parentProject:id,ulid,name,code')->get(['id', 'ulid', 'name', 'code', 'parent_project_id', 'project_type']);
$materials = Material::where('type', 'single')->where('status', 'active')->orderBy('name')->get();
$materialGroups = Material::with('components.component:id,ulid,name,sku,category,unit,unit_cost')
->whereIn('type', ['kit', 'assembly'])
->where('status', 'active')
->orderBy('name')
->get()
->map(function ($kit) {
return [
'id' => $kit->id,
'ulid' => $kit->ulid,
'name' => $kit->name,
'description' => $kit->description ?? '',
'status' => $kit->status,
'materials' => $kit->components->map(function ($comp) {
return array_merge($comp->component->toArray(), [
'pivot' => ['quantity' => $comp->quantity]
]);
})->toArray()
];
});
->get();
$labors = Labor::active()->with('skills')->orderBy('name')->get();
$equipments = Equipment::active()->orderBy('name')->get();
$teams = Team::with('users:id,ulid,name')->get();
$labors = \Modules\Labors\Models\Labor::with('skills')->active()->get();
$equipments = \Modules\Equipments\Models\Equipment::with('specifications')->active()->get();
$teams = Team::with('users:id,ulid,name,email')->active()->get();
$workflowService = app(\Modules\ProjectManagement\Services\ProjectWorkflowService::class);
$budget = $workflowService->getBudgetAnalysis($project);
$budget = [
'total_materials' => $project->materialsEstimates->sum(fn ($e) => $e->estimated_qty * $e->unit_cost),
'total_labor' => $project->tasks->flatMap->taskLabors->sum(fn ($tl) => $tl->estimated_hours * ($tl->labor->hourly_rate ?? 0)),
'total_equipment' => $project->tasks->flatMap->taskEquipments->sum(fn ($te) => $te->estimated_hours * ($te->equipment->hourly_rate ?? 0)),
];
return Inertia::render('ProjectManagement::Projects/Wizard', [
'project' => $project,
@@ -539,16 +526,16 @@ class ProjectController extends Controller
$validated = $request->validate([
'name' => 'required|string|max:255',
'client_name' => 'nullable|string|max:255',
'description' => 'nullable|string',
'pm_id' => 'nullable|string',
'location' => 'nullable|string|max:255',
'contract_value' => 'nullable|numeric|min:0',
'contract_duration' => 'nullable|integer|min:0',
'start_date' => 'nullable|date',
'target_end_date' => 'nullable|date|after_or_equal:start_date',
'client_name' => 'required|string|max:255',
'description' => 'required|string|min:3',
'pm_id' => 'required',
'location' => 'required|string|max:255',
'contract_value' => 'required|numeric|min:0.01',
'contract_duration' => 'required|integer|min:1',
'start_date' => 'required|date',
'target_end_date' => 'required|date|after_or_equal:start_date',
'project_type' => 'required|string|in:standard,special,extension',
'classifications' => 'nullable|array',
'classifications' => 'required|array|min:1',
'classifications.*' => 'string',
'parent_project_id' => 'nullable|string',
'is_unprofitable' => 'nullable|boolean',
@@ -602,11 +589,12 @@ class ProjectController extends Controller
'milestones.*.ulid' => 'nullable|string',
'milestones.*.name' => 'required|string|max:255',
'milestones.*.weight_percentage' => 'required|numeric|min:0|max:100',
'milestones.*.target_date' => 'required|date',
'tasks' => 'nullable|array',
'tasks.*.ulid' => 'nullable|string',
'tasks.*.name' => 'required|string|max:255',
'tasks.*.description' => 'nullable|string',
'tasks.*.milestone_ulid' => 'nullable|string',
'tasks.*.description' => 'required|string|min:3',
'tasks.*.milestone_ulid' => 'required|string',
'tasks.*.start_date' => 'nullable|date',
'tasks.*.end_date' => 'nullable|date|after_or_equal:tasks.*.start_date',
]);
@@ -624,16 +612,19 @@ class ProjectController extends Controller
if (!empty($m['ulid'])) {
$milestone = $project->milestones()->where('ulid', $m['ulid'])->first();
}
$plannedDate = !empty($m['target_date']) ? $m['target_date'] : ($m['planned_date'] ?? null);
if (!$milestone) {
$milestone = $project->milestones()->create([
'name' => $m['name'],
'weight_percentage' => $m['weight_percentage'],
'planned_date' => $plannedDate,
'sort_order' => $index,
]);
} else {
$milestone->update([
'name' => $m['name'],
'weight_percentage' => $m['weight_percentage'],
'planned_date' => $plannedDate,
'sort_order' => $index,
]);
}