chore: update document approval workflow and bug fixes

This commit is contained in:
2026-05-25 13:05:20 +08:00
parent d573c02893
commit 39a8e1d4cd
910 changed files with 49994 additions and 1010 deletions

View File

@@ -0,0 +1,105 @@
<?php
namespace Modules\TimelineScheduling\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Modules\ProjectManagement\Enums\WeatherCondition;
use Modules\ProjectManagement\Models\Project;
use Modules\ProjectManagement\Models\ProjectMilestone;
class MilestoneController extends Controller
{
public function index(Request $request)
{
$projectUlid = $request->query('project');
$project = null;
if ($projectUlid) {
$project = Project::where('ulid', $projectUlid)->firstOrFail();
$project->load([
'tasks' => fn ($q) => $q->select('id', 'project_id')->with('delays'),
'milestones',
]);
}
return \Inertia\Inertia::render('TimelineScheduling::Timeline/Index', [
'project' => $project,
'projects' => Project::select('id', 'ulid', 'name')->get(),
'milestones' => $project ? $project->milestones->map(fn ($m) => array_merge($m->toArray(), [
'is_completed' => $m->is_completed,
'is_overdue' => $m->is_overdue,
'status' => $m->status,
'status_color' => $m->status_color,
'days_delayed' => $m->days_delayed,
])) : [],
'milestoneStats' => $project ? [
'total' => $project->milestones->count(),
'completed' => $project->milestones->where('actual_date', '!=', null)->count(),
'completion_percentage' => $project->milestone_completion,
'weather_delay_days' => $project->weather_delay_days,
] : null,
'weatherConditions' => collect(WeatherCondition::cases())->map(fn ($c) => [
'value' => $c->value,
'label' => $c->label(),
'icon' => $c->icon(),
]),
'allowedTransitions' => $project ? collect($project->status->allowedTransitions())->map(fn ($s) => [
'value' => $s->value,
'label' => $s->label(),
]) : [],
]);
}
public function store(Request $request)
{
$validated = $request->validate([
'project_id' => 'required|exists:projects,id',
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'planned_date' => 'nullable|date',
'weight_percentage' => 'required|numeric|min:0|max:100',
]);
$project = Project::findOrFail($validated['project_id']);
$validated['sort_order'] = $project->milestones()->count();
$validated['is_default'] = false;
ProjectMilestone::create($validated);
return back()->with('success', 'Milestone added.');
}
public function update(Request $request, ProjectMilestone $milestone)
{
$validated = $request->validate([
'name' => 'sometimes|required|string|max:255',
'description' => 'nullable|string',
'planned_date' => 'nullable|date',
'actual_date' => 'nullable|date',
'weight_percentage' => 'sometimes|numeric|min:0|max:100',
'weather_impacted' => 'sometimes|boolean',
'weather_condition' => 'nullable|string',
'weather_delay_days' => 'sometimes|integer|min:0',
'weather_notes' => 'nullable|string|max:2000',
]);
// Clear weather fields if not impacted
if (isset($validated['weather_impacted']) && !$validated['weather_impacted']) {
$validated['weather_condition'] = null;
$validated['weather_delay_days'] = 0;
$validated['weather_notes'] = null;
}
$milestone->update($validated);
return back()->with('success', 'Milestone updated.');
}
public function destroy(ProjectMilestone $milestone)
{
$milestone->delete();
return back()->with('success', 'Milestone removed.');
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace Modules\TimelineScheduling\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Modules\ProjectManagement\Models\Project;
use Modules\ProjectManagement\Models\Task;
use Modules\TimelineScheduling\Models\Milestone;
use Modules\TimelineScheduling\Models\TaskDependency;
use Modules\TimelineScheduling\Services\GanttDataTransformer;
class ScheduleController extends Controller
{
public function __construct(
private GanttDataTransformer $ganttTransformer,
) {}
// --- Gantt view for a project ---
public function gantt(Project $project)
{
$ganttData = $this->ganttTransformer->transform($project->id);
$milestones = Milestone::where('project_id', $project->id)->orderBy('target_date')->get();
$tasks = Task::where('project_id', $project->id)->select('id', 'ulid', 'name')->get();
return Inertia::render('TimelineScheduling::Schedule/Gantt', [
'project' => $project->only('id', 'ulid', 'name', 'code'),
'ganttData' => $ganttData,
'milestones' => $milestones,
'tasks' => $tasks,
]);
}
// --- API: Gantt JSON data ---
public function ganttData(Project $project)
{
return response()->json($this->ganttTransformer->transform($project->id));
}
// --- Update task schedule (drag-and-drop) ---
public function updateSchedule(Request $request, Project $project, Task $task)
{
$validated = $request->validate([
'start_date' => 'required|date',
'end_date' => 'required|date|after_or_equal:start_date',
]);
$task->update($validated);
return response()->json(['success' => true]);
}
// --- Dependencies ---
public function storeDependency(Request $request)
{
$validated = $request->validate([
'task_id' => 'required|string',
'predecessor_id' => 'required|string|different:task_id',
'dependency_type' => 'required|in:FS,FF,SS,SF',
'lag_days' => 'nullable|integer',
]);
$taskId = Task::resolveUlidToId($validated['task_id']);
$predecessorId = Task::resolveUlidToId($validated['predecessor_id']);
TaskDependency::updateOrCreate(
['task_id' => $taskId, 'predecessor_id' => $predecessorId],
['dependency_type' => $validated['dependency_type'], 'lag_days' => $validated['lag_days'] ?? 0],
);
return back()->with('success', 'Dependency added.');
}
public function destroyDependency(TaskDependency $dependency)
{
$dependency->delete();
return back()->with('success', 'Dependency removed.');
}
// --- Milestones ---
public function storeMilestone(Request $request)
{
$validated = $request->validate([
'project_id' => 'required|string',
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'target_date' => 'required|date',
'is_critical' => 'nullable|boolean',
]);
$validated['project_id'] = Project::resolveUlidToId($validated['project_id']);
Milestone::create($validated);
return back()->with('success', 'Milestone created.');
}
public function achieveMilestone(Milestone $milestone)
{
$milestone->update(['achieved_date' => now()->toDateString()]);
return back()->with('success', 'Milestone marked as achieved.');
}
public function destroyMilestone(Milestone $milestone)
{
$milestone->delete();
return back()->with('success', 'Milestone deleted.');
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Modules\TimelineScheduling\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class TimelineSchedulingController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
return view('timelinescheduling::index');
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('timelinescheduling::create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request) {}
/**
* Show the specified resource.
*/
public function show($id)
{
return view('timelinescheduling::show');
}
/**
* Show the form for editing the specified resource.
*/
public function edit($id)
{
return view('timelinescheduling::edit');
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, $id) {}
/**
* Remove the specified resource from storage.
*/
public function destroy($id) {}
}