feat(work-order): 품목 상태 변경 및 작업지시 상태 자동 연동
- WorkOrderItem 모델에 status 컬럼 및 상수 추가 (waiting/in_progress/completed)
- 품목 상태 변경 API 엔드포인트 추가 (PATCH /work-orders/{id}/items/{itemId}/status)
- syncWorkOrderStatusFromItems() 메서드로 품목→작업지시 상태 자동 동기화
- 품목 중 하나라도 in_progress → 작업지시 in_progress
- 모든 품목 completed → 작업지시 completed
- 모든 품목 waiting → 작업지시 waiting
- 감사 로그: item_status_changed, status_synced_from_items 액션 추가
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -127,4 +127,14 @@ public function resolveIssue(int $workOrderId, int $issueId)
|
|||||||
return $this->service->resolveIssue($workOrderId, $issueId);
|
return $this->service->resolveIssue($workOrderId, $issueId);
|
||||||
}, __('message.work_order.issue_resolved'));
|
}, __('message.work_order.issue_resolved'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 품목 상태 변경
|
||||||
|
*/
|
||||||
|
public function updateItemStatus(Request $request, int $workOrderId, int $itemId)
|
||||||
|
{
|
||||||
|
return ApiResponse::handle(function () use ($request, $workOrderId, $itemId) {
|
||||||
|
return $this->service->updateItemStatus($workOrderId, $itemId, $request->input('status'));
|
||||||
|
}, __('message.work_order.item_status_updated'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,20 @@ class WorkOrderItem extends Model
|
|||||||
'quantity',
|
'quantity',
|
||||||
'unit',
|
'unit',
|
||||||
'sort_order',
|
'sort_order',
|
||||||
|
'status',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 품목 상태 상수
|
||||||
|
*/
|
||||||
|
public const STATUS_WAITING = 'waiting';
|
||||||
|
public const STATUS_IN_PROGRESS = 'in_progress';
|
||||||
|
public const STATUS_COMPLETED = 'completed';
|
||||||
|
|
||||||
|
public const STATUSES = [
|
||||||
|
self::STATUS_WAITING,
|
||||||
|
self::STATUS_IN_PROGRESS,
|
||||||
|
self::STATUS_COMPLETED,
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
use App\Models\Production\WorkOrder;
|
use App\Models\Production\WorkOrder;
|
||||||
use App\Models\Production\WorkOrderAssignee;
|
use App\Models\Production\WorkOrderAssignee;
|
||||||
use App\Models\Production\WorkOrderBendingDetail;
|
use App\Models\Production\WorkOrderBendingDetail;
|
||||||
|
use App\Models\Production\WorkOrderItem;
|
||||||
use App\Services\Audit\AuditLogger;
|
use App\Services\Audit\AuditLogger;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||||
@@ -29,7 +30,7 @@ public function index(array $params)
|
|||||||
$size = (int) ($params['size'] ?? 20);
|
$size = (int) ($params['size'] ?? 20);
|
||||||
$q = trim((string) ($params['q'] ?? ''));
|
$q = trim((string) ($params['q'] ?? ''));
|
||||||
$status = $params['status'] ?? null;
|
$status = $params['status'] ?? null;
|
||||||
$processType = $params['process_type'] ?? null;
|
$processId = $params['process_id'] ?? null;
|
||||||
$assigneeId = $params['assignee_id'] ?? null;
|
$assigneeId = $params['assignee_id'] ?? null;
|
||||||
$teamId = $params['team_id'] ?? null;
|
$teamId = $params['team_id'] ?? null;
|
||||||
$scheduledFrom = $params['scheduled_from'] ?? null;
|
$scheduledFrom = $params['scheduled_from'] ?? null;
|
||||||
@@ -37,7 +38,7 @@ public function index(array $params)
|
|||||||
|
|
||||||
$query = WorkOrder::query()
|
$query = WorkOrder::query()
|
||||||
->where('tenant_id', $tenantId)
|
->where('tenant_id', $tenantId)
|
||||||
->with(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'salesOrder:id,order_no']);
|
->with(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'salesOrder:id,order_no', 'process:id,process_name,process_code']);
|
||||||
|
|
||||||
// 검색어
|
// 검색어
|
||||||
if ($q !== '') {
|
if ($q !== '') {
|
||||||
@@ -52,9 +53,9 @@ public function index(array $params)
|
|||||||
$query->where('status', $status);
|
$query->where('status', $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 공정유형 필터
|
// 공정 필터 (process_id)
|
||||||
if ($processType !== null) {
|
if ($processId !== null) {
|
||||||
$query->where('process_type', $processType);
|
$query->where('process_id', $processId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 담당자 필터
|
// 담당자 필터
|
||||||
@@ -116,7 +117,9 @@ public function show(int $id)
|
|||||||
'assignee:id,name',
|
'assignee:id,name',
|
||||||
'assignees.user:id,name',
|
'assignees.user:id,name',
|
||||||
'team:id,name',
|
'team:id,name',
|
||||||
'salesOrder:id,order_no,project_name',
|
'salesOrder:id,order_no,site_name',
|
||||||
|
'salesOrder.client:id,name',
|
||||||
|
'process:id,process_name,process_code,work_steps',
|
||||||
'items',
|
'items',
|
||||||
'bendingDetail',
|
'bendingDetail',
|
||||||
'issues' => fn ($q) => $q->orderByDesc('created_at'),
|
'issues' => fn ($q) => $q->orderByDesc('created_at'),
|
||||||
@@ -156,6 +159,9 @@ public function store(array $data)
|
|||||||
|
|
||||||
$workOrder = WorkOrder::create($data);
|
$workOrder = WorkOrder::create($data);
|
||||||
|
|
||||||
|
// process 관계 로드 (isBending 체크용)
|
||||||
|
$workOrder->load('process:id,process_name,process_code');
|
||||||
|
|
||||||
// 품목 저장
|
// 품목 저장
|
||||||
foreach ($items as $index => $item) {
|
foreach ($items as $index => $item) {
|
||||||
$item['tenant_id'] = $tenantId;
|
$item['tenant_id'] = $tenantId;
|
||||||
@@ -164,7 +170,7 @@ public function store(array $data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 벤딩 상세 저장 (벤딩 공정인 경우)
|
// 벤딩 상세 저장 (벤딩 공정인 경우)
|
||||||
if ($data['process_type'] === WorkOrder::PROCESS_BENDING && $bendingDetail) {
|
if ($workOrder->isBending() && $bendingDetail) {
|
||||||
$bendingDetail['tenant_id'] = $tenantId;
|
$bendingDetail['tenant_id'] = $tenantId;
|
||||||
$workOrder->bendingDetail()->create($bendingDetail);
|
$workOrder->bendingDetail()->create($bendingDetail);
|
||||||
}
|
}
|
||||||
@@ -179,7 +185,7 @@ public function store(array $data)
|
|||||||
$workOrder->toArray()
|
$workOrder->toArray()
|
||||||
);
|
);
|
||||||
|
|
||||||
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'items', 'bendingDetail']);
|
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'process:id,process_name,process_code', 'items', 'bendingDetail']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +197,9 @@ public function update(int $id, array $data)
|
|||||||
$tenantId = $this->tenantId();
|
$tenantId = $this->tenantId();
|
||||||
$userId = $this->apiUserId();
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
$workOrder = WorkOrder::where('tenant_id', $tenantId)->find($id);
|
$workOrder = WorkOrder::where('tenant_id', $tenantId)
|
||||||
|
->with('process:id,process_name,process_code')
|
||||||
|
->find($id);
|
||||||
if (! $workOrder) {
|
if (! $workOrder) {
|
||||||
throw new NotFoundHttpException(__('error.not_found'));
|
throw new NotFoundHttpException(__('error.not_found'));
|
||||||
}
|
}
|
||||||
@@ -259,8 +267,8 @@ public function update(int $id, array $data)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 벤딩 상세 업데이트
|
// 벤딩 상세 업데이트 (벤딩 공정인 경우에만)
|
||||||
if ($bendingDetail !== null && $workOrder->process_type === WorkOrder::PROCESS_BENDING) {
|
if ($bendingDetail !== null && $workOrder->isBending()) {
|
||||||
$bendingDetail['tenant_id'] = $workOrder->tenant_id;
|
$bendingDetail['tenant_id'] = $workOrder->tenant_id;
|
||||||
$workOrder->bendingDetail()->updateOrCreate(
|
$workOrder->bendingDetail()->updateOrCreate(
|
||||||
['work_order_id' => $workOrder->id],
|
['work_order_id' => $workOrder->id],
|
||||||
@@ -278,7 +286,7 @@ public function update(int $id, array $data)
|
|||||||
$workOrder->fresh()->toArray()
|
$workOrder->fresh()->toArray()
|
||||||
);
|
);
|
||||||
|
|
||||||
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'items', 'bendingDetail']);
|
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'process:id,process_name,process_code', 'items', 'bendingDetail']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +386,7 @@ public function updateStatus(int $id, string $status)
|
|||||||
['status' => $status]
|
['status' => $status]
|
||||||
);
|
);
|
||||||
|
|
||||||
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name']);
|
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'process:id,process_name,process_code']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -454,7 +462,7 @@ public function assign(int $id, array $data)
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name']);
|
return $workOrder->load(['assignee:id,name', 'assignees.user:id,name', 'team:id,name', 'process:id,process_name,process_code']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,12 +473,14 @@ public function toggleBendingField(int $id, string $field)
|
|||||||
{
|
{
|
||||||
$tenantId = $this->tenantId();
|
$tenantId = $this->tenantId();
|
||||||
|
|
||||||
$workOrder = WorkOrder::where('tenant_id', $tenantId)->find($id);
|
$workOrder = WorkOrder::where('tenant_id', $tenantId)
|
||||||
|
->with('process:id,process_name,process_code')
|
||||||
|
->find($id);
|
||||||
if (! $workOrder) {
|
if (! $workOrder) {
|
||||||
throw new NotFoundHttpException(__('error.not_found'));
|
throw new NotFoundHttpException(__('error.not_found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($workOrder->process_type !== WorkOrder::PROCESS_BENDING) {
|
if (! $workOrder->isBending()) {
|
||||||
throw new BadRequestHttpException(__('error.work_order.not_bending_process'));
|
throw new BadRequestHttpException(__('error.work_order.not_bending_process'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,4 +598,131 @@ private function generateWorkOrderNo(int $tenantId): string
|
|||||||
|
|
||||||
return sprintf('%s%s%04d', $prefix, $date, $seq);
|
return sprintf('%s%s%04d', $prefix, $date, $seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 품목 상태 변경
|
||||||
|
*/
|
||||||
|
public function updateItemStatus(int $workOrderId, int $itemId, string $status)
|
||||||
|
{
|
||||||
|
$tenantId = $this->tenantId();
|
||||||
|
$userId = $this->apiUserId();
|
||||||
|
|
||||||
|
$workOrder = WorkOrder::where('tenant_id', $tenantId)->find($workOrderId);
|
||||||
|
if (! $workOrder) {
|
||||||
|
throw new NotFoundHttpException(__('error.not_found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = $workOrder->items()->find($itemId);
|
||||||
|
if (! $item) {
|
||||||
|
throw new NotFoundHttpException(__('error.not_found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상태 유효성 검증
|
||||||
|
if (! in_array($status, WorkOrderItem::STATUSES)) {
|
||||||
|
throw new BadRequestHttpException(__('error.invalid_status'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$beforeStatus = $item->status;
|
||||||
|
$item->status = $status;
|
||||||
|
$item->save();
|
||||||
|
|
||||||
|
// 감사 로그
|
||||||
|
$this->auditLogger->log(
|
||||||
|
$tenantId,
|
||||||
|
self::AUDIT_TARGET,
|
||||||
|
$workOrderId,
|
||||||
|
'item_status_changed',
|
||||||
|
['item_id' => $itemId, 'status' => $beforeStatus],
|
||||||
|
['item_id' => $itemId, 'status' => $status]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 작업지시 상태 자동 연동
|
||||||
|
$workOrderStatusChanged = $this->syncWorkOrderStatusFromItems($workOrder);
|
||||||
|
|
||||||
|
// 품목과 함께 작업지시 상태도 반환
|
||||||
|
return [
|
||||||
|
'item' => $item,
|
||||||
|
'work_order_status' => $workOrder->fresh()->status,
|
||||||
|
'work_order_status_changed' => $workOrderStatusChanged,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 품목 상태 기반으로 작업지시 상태 자동 동기화
|
||||||
|
*
|
||||||
|
* 규칙:
|
||||||
|
* - 품목 중 하나라도 in_progress → 작업지시 in_progress
|
||||||
|
* - 모든 품목이 completed → 작업지시 completed
|
||||||
|
* - 모든 품목이 waiting → 작업지시 waiting (단, waiting 이상인 경우만)
|
||||||
|
*
|
||||||
|
* @return bool 상태 변경 여부
|
||||||
|
*/
|
||||||
|
private function syncWorkOrderStatusFromItems(WorkOrder $workOrder): bool
|
||||||
|
{
|
||||||
|
// 품목이 없으면 동기화하지 않음
|
||||||
|
$items = $workOrder->items()->get();
|
||||||
|
if ($items->isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 작업 가능 상태가 아니면 동기화하지 않음 (unassigned, pending은 제외)
|
||||||
|
$syncableStatuses = [
|
||||||
|
WorkOrder::STATUS_WAITING,
|
||||||
|
WorkOrder::STATUS_IN_PROGRESS,
|
||||||
|
WorkOrder::STATUS_COMPLETED,
|
||||||
|
];
|
||||||
|
if (! in_array($workOrder->status, $syncableStatuses)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 품목 상태 집계
|
||||||
|
$statusCounts = $items->groupBy('status')->map->count();
|
||||||
|
$totalItems = $items->count();
|
||||||
|
|
||||||
|
$waitingCount = $statusCounts->get(WorkOrderItem::STATUS_WAITING, 0);
|
||||||
|
$inProgressCount = $statusCounts->get(WorkOrderItem::STATUS_IN_PROGRESS, 0);
|
||||||
|
$completedCount = $statusCounts->get(WorkOrderItem::STATUS_COMPLETED, 0);
|
||||||
|
|
||||||
|
// 새 상태 결정
|
||||||
|
$newStatus = null;
|
||||||
|
if ($inProgressCount > 0) {
|
||||||
|
// 하나라도 진행중이면 작업지시도 진행중
|
||||||
|
$newStatus = WorkOrder::STATUS_IN_PROGRESS;
|
||||||
|
} elseif ($completedCount === $totalItems) {
|
||||||
|
// 모두 완료면 작업지시도 완료
|
||||||
|
$newStatus = WorkOrder::STATUS_COMPLETED;
|
||||||
|
} elseif ($waitingCount === $totalItems) {
|
||||||
|
// 모두 대기면 작업지시도 대기
|
||||||
|
$newStatus = WorkOrder::STATUS_WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상태가 변경되어야 하고, 현재와 다른 경우에만 업데이트
|
||||||
|
if ($newStatus && $newStatus !== $workOrder->status) {
|
||||||
|
$oldStatus = $workOrder->status;
|
||||||
|
$workOrder->status = $newStatus;
|
||||||
|
|
||||||
|
// 상태에 따른 타임스탬프 업데이트
|
||||||
|
if ($newStatus === WorkOrder::STATUS_IN_PROGRESS && ! $workOrder->started_at) {
|
||||||
|
$workOrder->started_at = now();
|
||||||
|
} elseif ($newStatus === WorkOrder::STATUS_COMPLETED) {
|
||||||
|
$workOrder->completed_at = now();
|
||||||
|
}
|
||||||
|
|
||||||
|
$workOrder->save();
|
||||||
|
|
||||||
|
// 상태 변경 감사 로그
|
||||||
|
$this->auditLogger->log(
|
||||||
|
$workOrder->tenant_id,
|
||||||
|
self::AUDIT_TARGET,
|
||||||
|
$workOrder->id,
|
||||||
|
'status_synced_from_items',
|
||||||
|
['status' => $oldStatus],
|
||||||
|
['status' => $newStatus]
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('work_order_items', function (Blueprint $table) {
|
||||||
|
$table->string('status', 20)->default('waiting')->after('sort_order')
|
||||||
|
->comment('품목 상태: waiting=대기, in_progress=작업중, completed=완료');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('work_order_items', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('status');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
// use App\Http\Controllers\Api\V1\MaterialController; // REMOVED: materials 테이블 삭제됨
|
// use App\Http\Controllers\Api\V1\MaterialController; // REMOVED: materials 테이블 삭제됨
|
||||||
use App\Http\Controllers\Api\V1\ItemsController;
|
use App\Http\Controllers\Api\V1\ItemsController;
|
||||||
use App\Http\Controllers\Api\V1\ItemsFileController;
|
use App\Http\Controllers\Api\V1\ItemsFileController;
|
||||||
|
use App\Http\Controllers\Api\V1\LaborController;
|
||||||
use App\Http\Controllers\Api\V1\LeaveController;
|
use App\Http\Controllers\Api\V1\LeaveController;
|
||||||
use App\Http\Controllers\Api\V1\LeavePolicyController;
|
use App\Http\Controllers\Api\V1\LeavePolicyController;
|
||||||
use App\Http\Controllers\Api\V1\LoanController;
|
use App\Http\Controllers\Api\V1\LoanController;
|
||||||
@@ -69,10 +70,9 @@
|
|||||||
use App\Http\Controllers\Api\V1\PayrollController;
|
use App\Http\Controllers\Api\V1\PayrollController;
|
||||||
use App\Http\Controllers\Api\V1\PermissionController;
|
use App\Http\Controllers\Api\V1\PermissionController;
|
||||||
use App\Http\Controllers\Api\V1\PlanController;
|
use App\Http\Controllers\Api\V1\PlanController;
|
||||||
use App\Http\Controllers\Api\V1\PopupController;
|
|
||||||
// use App\Http\Controllers\Api\V1\ProductBomItemController; // REMOVED: products 테이블 삭제됨
|
// use App\Http\Controllers\Api\V1\ProductBomItemController; // REMOVED: products 테이블 삭제됨
|
||||||
// use App\Http\Controllers\Api\V1\ProductController; // REMOVED: products 테이블 삭제됨
|
// use App\Http\Controllers\Api\V1\ProductController; // REMOVED: products 테이블 삭제됨
|
||||||
use App\Http\Controllers\Api\V1\LaborController;
|
use App\Http\Controllers\Api\V1\PopupController;
|
||||||
use App\Http\Controllers\Api\V1\PositionController;
|
use App\Http\Controllers\Api\V1\PositionController;
|
||||||
use App\Http\Controllers\Api\V1\PostController;
|
use App\Http\Controllers\Api\V1\PostController;
|
||||||
use App\Http\Controllers\Api\V1\PricingController;
|
use App\Http\Controllers\Api\V1\PricingController;
|
||||||
@@ -89,6 +89,7 @@
|
|||||||
use App\Http\Controllers\Api\V1\SalaryController;
|
use App\Http\Controllers\Api\V1\SalaryController;
|
||||||
use App\Http\Controllers\Api\V1\SaleController;
|
use App\Http\Controllers\Api\V1\SaleController;
|
||||||
use App\Http\Controllers\Api\V1\ShipmentController;
|
use App\Http\Controllers\Api\V1\ShipmentController;
|
||||||
|
use App\Http\Controllers\Api\V1\SiteBriefingController;
|
||||||
use App\Http\Controllers\Api\V1\SiteController;
|
use App\Http\Controllers\Api\V1\SiteController;
|
||||||
use App\Http\Controllers\Api\V1\StockController;
|
use App\Http\Controllers\Api\V1\StockController;
|
||||||
use App\Http\Controllers\Api\V1\SubscriptionController;
|
use App\Http\Controllers\Api\V1\SubscriptionController;
|
||||||
@@ -425,6 +426,17 @@
|
|||||||
Route::delete('/{id}', [SiteController::class, 'destroy'])->whereNumber('id')->name('v1.sites.destroy');
|
Route::delete('/{id}', [SiteController::class, 'destroy'])->whereNumber('id')->name('v1.sites.destroy');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Site Briefing API (현장설명회 관리)
|
||||||
|
Route::prefix('site-briefings')->group(function () {
|
||||||
|
Route::get('', [SiteBriefingController::class, 'index'])->name('v1.site-briefings.index');
|
||||||
|
Route::post('', [SiteBriefingController::class, 'store'])->name('v1.site-briefings.store');
|
||||||
|
Route::get('/stats', [SiteBriefingController::class, 'stats'])->name('v1.site-briefings.stats');
|
||||||
|
Route::delete('/bulk', [SiteBriefingController::class, 'bulkDestroy'])->name('v1.site-briefings.bulk-destroy');
|
||||||
|
Route::get('/{id}', [SiteBriefingController::class, 'show'])->whereNumber('id')->name('v1.site-briefings.show');
|
||||||
|
Route::put('/{id}', [SiteBriefingController::class, 'update'])->whereNumber('id')->name('v1.site-briefings.update');
|
||||||
|
Route::delete('/{id}', [SiteBriefingController::class, 'destroy'])->whereNumber('id')->name('v1.site-briefings.destroy');
|
||||||
|
});
|
||||||
|
|
||||||
// Construction API (시공관리)
|
// Construction API (시공관리)
|
||||||
Route::prefix('construction')->group(function () {
|
Route::prefix('construction')->group(function () {
|
||||||
// Contract API (계약관리)
|
// Contract API (계약관리)
|
||||||
@@ -1105,6 +1117,7 @@
|
|||||||
// 견적 관리 API
|
// 견적 관리 API
|
||||||
Route::prefix('estimates')->group(function () {
|
Route::prefix('estimates')->group(function () {
|
||||||
Route::get('/', [EstimateController::class, 'index'])->name('v1.estimates.index'); // 견적 목록
|
Route::get('/', [EstimateController::class, 'index'])->name('v1.estimates.index'); // 견적 목록
|
||||||
|
Route::get('/stats', [EstimateController::class, 'stats'])->name('v1.estimates.stats'); // 견적 통계
|
||||||
Route::post('/', [EstimateController::class, 'store'])->name('v1.estimates.store'); // 견적 생성
|
Route::post('/', [EstimateController::class, 'store'])->name('v1.estimates.store'); // 견적 생성
|
||||||
Route::get('/{id}', [EstimateController::class, 'show'])->name('v1.estimates.show'); // 견적 상세
|
Route::get('/{id}', [EstimateController::class, 'show'])->name('v1.estimates.show'); // 견적 상세
|
||||||
Route::put('/{id}', [EstimateController::class, 'update'])->name('v1.estimates.update'); // 견적 수정
|
Route::put('/{id}', [EstimateController::class, 'update'])->name('v1.estimates.update'); // 견적 수정
|
||||||
@@ -1170,6 +1183,9 @@
|
|||||||
// 이슈 관리
|
// 이슈 관리
|
||||||
Route::post('/{id}/issues', [WorkOrderController::class, 'addIssue'])->whereNumber('id')->name('v1.work-orders.issues.store'); // 이슈 등록
|
Route::post('/{id}/issues', [WorkOrderController::class, 'addIssue'])->whereNumber('id')->name('v1.work-orders.issues.store'); // 이슈 등록
|
||||||
Route::patch('/{id}/issues/{issueId}/resolve', [WorkOrderController::class, 'resolveIssue'])->whereNumber('id')->name('v1.work-orders.issues.resolve'); // 이슈 해결
|
Route::patch('/{id}/issues/{issueId}/resolve', [WorkOrderController::class, 'resolveIssue'])->whereNumber('id')->name('v1.work-orders.issues.resolve'); // 이슈 해결
|
||||||
|
|
||||||
|
// 품목 상태 변경
|
||||||
|
Route::patch('/{id}/items/{itemId}/status', [WorkOrderController::class, 'updateItemStatus'])->whereNumber('id')->whereNumber('itemId')->name('v1.work-orders.items.status');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 작업실적 관리 API (Production)
|
// 작업실적 관리 API (Production)
|
||||||
|
|||||||
Reference in New Issue
Block a user