chore: update document approval workflow and bug fixes
This commit is contained in:
0
Modules/MasterData/app/Http/Controllers/.gitkeep
Normal file
0
Modules/MasterData/app/Http/Controllers/.gitkeep
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\MasterData\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MasterDataController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('masterdata::index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('masterdata::create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request) {}
|
||||
|
||||
/**
|
||||
* Show the specified resource.
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
return view('masterdata::show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
return view('masterdata::edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, $id) {}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy($id) {}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\MasterData\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Modules\MasterData\Models\Material;
|
||||
use Modules\MasterData\Models\MaterialGroup;
|
||||
|
||||
class MaterialGroupController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = MaterialGroup::withCount('materials');
|
||||
|
||||
if ($search = $request->search) {
|
||||
$query->where('name', 'like', "%{$search}%");
|
||||
}
|
||||
|
||||
if ($status = $request->status) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
$groups = $query->latest()->paginate(15)->withQueryString();
|
||||
|
||||
$allMaterials = Material::where('status', 'active')
|
||||
->select('id', 'ulid', 'name', 'sku', 'unit', 'unit_cost', 'category')
|
||||
->get();
|
||||
|
||||
return Inertia::render('MaterialLogistics::Groups/Index', [
|
||||
'groups' => $groups,
|
||||
'allMaterials' => $allMaterials,
|
||||
'filters' => $request->only(['search', 'status']),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(MaterialGroup $materialGroup)
|
||||
{
|
||||
$materialGroup->load('materials:id,ulid,name,sku,unit,unit_cost,category');
|
||||
|
||||
return response()->json($materialGroup);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$group = MaterialGroup::create($validated);
|
||||
|
||||
if ($request->has('material_ids') && is_array($request->material_ids)) {
|
||||
$materialIds = Material::whereIn('ulid', $request->material_ids)->pluck('id');
|
||||
$group->materials()->attach($materialIds);
|
||||
}
|
||||
|
||||
return back()->with('success', "Material group \"{$group->name}\" created.");
|
||||
}
|
||||
|
||||
public function update(Request $request, MaterialGroup $materialGroup)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'status' => 'nullable|in:active,archived',
|
||||
]);
|
||||
|
||||
$materialGroup->update($validated);
|
||||
|
||||
return back()->with('success', 'Group updated.');
|
||||
}
|
||||
|
||||
public function destroy(MaterialGroup $materialGroup)
|
||||
{
|
||||
if ($materialGroup->projects()->exists()) {
|
||||
return back()->with('error', 'Cannot delete a group that is assigned to projects. Remove it from all projects first.');
|
||||
}
|
||||
|
||||
$name = $materialGroup->name;
|
||||
$materialGroup->delete();
|
||||
|
||||
return back()->with('success', "Group \"{$name}\" deleted.");
|
||||
}
|
||||
|
||||
public function addMaterial(Request $request, MaterialGroup $materialGroup)
|
||||
{
|
||||
$request->validate([
|
||||
'material_id' => 'required|string',
|
||||
]);
|
||||
|
||||
$material = Material::where('ulid', $request->material_id)->firstOrFail();
|
||||
|
||||
if ($materialGroup->materials()->where('material_id', $material->id)->exists()) {
|
||||
return back()->with('error', 'Material is already in this group.');
|
||||
}
|
||||
|
||||
$materialGroup->materials()->attach($material->id);
|
||||
|
||||
return back()->with('success', "{$material->name} added to group.");
|
||||
}
|
||||
|
||||
public function removeMaterial(MaterialGroup $materialGroup, Material $material)
|
||||
{
|
||||
$materialGroup->materials()->detach($material->id);
|
||||
|
||||
return back()->with('success', "{$material->name} removed from group.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\MasterData\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Modules\MasterData\Exports\BoqTemplateExport;
|
||||
use Modules\MasterData\Exports\MaterialTemplateExport;
|
||||
use Modules\MasterData\Imports\BoqImport;
|
||||
use Modules\MasterData\Imports\MaterialImport;
|
||||
use Modules\MasterData\Models\Material;
|
||||
use Modules\ProjectManagement\Models\Project;
|
||||
use Modules\ProjectManagement\Models\Task;
|
||||
|
||||
class MaterialImportController extends Controller
|
||||
{
|
||||
public function template()
|
||||
{
|
||||
return Excel::download(new MaterialTemplateExport(), 'material_import_template.xlsx');
|
||||
}
|
||||
|
||||
public function import(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:xlsx,csv,xls|max:10240',
|
||||
]);
|
||||
|
||||
$import = new MaterialImport();
|
||||
|
||||
try {
|
||||
Excel::import($import, $request->file('file'));
|
||||
} catch (\Exception $e) {
|
||||
return back()->with('error', 'Import failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$failures = $import->failures();
|
||||
$failureCount = $failures->count();
|
||||
|
||||
$message = "{$import->getImportedCount()} materials imported.";
|
||||
|
||||
if ($import->getSkippedCount() > 0) {
|
||||
$message .= " {$import->getSkippedCount()} skipped (duplicate SKU).";
|
||||
}
|
||||
|
||||
if (count($import->getGroupsCreated()) > 0) {
|
||||
$message .= " Groups: " . implode(', ', $import->getGroupsCreated()) . ".";
|
||||
}
|
||||
|
||||
if ($failureCount > 0) {
|
||||
$message .= " {$failureCount} rows had validation errors.";
|
||||
}
|
||||
|
||||
return back()->with('success', $message);
|
||||
}
|
||||
|
||||
// --- BOQ Import ---
|
||||
|
||||
public function boqTemplate()
|
||||
{
|
||||
return Excel::download(new BoqTemplateExport(), 'boq_template.xlsx');
|
||||
}
|
||||
|
||||
public function boqPreview(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:xlsx,csv,xls|max:10240',
|
||||
]);
|
||||
|
||||
$import = new BoqImport();
|
||||
|
||||
try {
|
||||
Excel::import($import, $request->file('file'));
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to parse file: ' . $e->getMessage(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
if (!empty($import->getErrors())) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => implode('; ', $import->getErrors()),
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'header' => $import->getHeader(),
|
||||
'items' => $import->getItems(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function boqConfirm(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'project_ulid' => 'required|string',
|
||||
'task_ulid' => 'required|string',
|
||||
'header' => 'required|array',
|
||||
'header.project_name' => 'nullable|string',
|
||||
'header.owner_name' => 'nullable|string',
|
||||
'header.location' => 'nullable|string',
|
||||
'header.date' => 'nullable|string',
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.material' => 'required|string|max:255',
|
||||
'items.*.unit' => 'required|string|max:20',
|
||||
'items.*.approved_qty' => 'nullable|numeric|min:0',
|
||||
'items.*.request_qty' => 'nullable|numeric|min:0',
|
||||
'items.*.work_area' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$project = Project::where('ulid', $validated['project_ulid'])->firstOrFail();
|
||||
$task = Task::where('ulid', $validated['task_ulid'])
|
||||
->where('project_id', $project->id)
|
||||
->firstOrFail();
|
||||
|
||||
$created = 0;
|
||||
|
||||
DB::transaction(function () use ($validated, $project, $task, &$created) {
|
||||
// Update project fields from header
|
||||
$updates = [];
|
||||
if (!empty($validated['header']['location'])) {
|
||||
$updates['location'] = $validated['header']['location'];
|
||||
}
|
||||
if (!empty($validated['header']['date'])) {
|
||||
try {
|
||||
$updates['start_date'] = Carbon::parse($validated['header']['date'])->format('Y-m-d');
|
||||
} catch (\Exception) {
|
||||
// Skip invalid date
|
||||
}
|
||||
}
|
||||
if (!empty($updates)) {
|
||||
$project->update($updates);
|
||||
}
|
||||
|
||||
// Create materials and assign to task
|
||||
foreach ($validated['items'] as $item) {
|
||||
$material = Material::create([
|
||||
'name' => $item['material'],
|
||||
'unit' => $item['unit'],
|
||||
'unit_cost' => 0,
|
||||
'category' => null,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$task->taskMaterials()->create([
|
||||
'material_id' => $material->id,
|
||||
'planned_qty' => $item['request_qty'] ?? 0,
|
||||
'actual_qty' => $item['approved_qty'] ?? 0,
|
||||
'unit_cost' => 0,
|
||||
'notes' => $item['work_area'] ?? null,
|
||||
]);
|
||||
|
||||
$created++;
|
||||
}
|
||||
|
||||
// Recalculate project capitalization
|
||||
$project->load('tasks.taskMaterials');
|
||||
$project->recalculateCapitalization();
|
||||
});
|
||||
|
||||
return back()->with('success', "{$created} materials imported and assigned to task \"{$task->name}\".");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user