Merge branch 'develop' of http://114.203.209.83:3000/SamProject/sam-api into develop
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 논리적 데이터베이스 관계 문서
|
||||
|
||||
> **자동 생성**: 2026-02-07 09:56:46
|
||||
> **자동 생성**: 2026-02-07 01:10:55
|
||||
> **소스**: Eloquent 모델 관계 분석
|
||||
|
||||
## 📊 모델별 관계 현황
|
||||
@@ -499,6 +499,8 @@ ### orders
|
||||
- **item()**: belongsTo → `items`
|
||||
- **sale()**: belongsTo → `sales`
|
||||
- **items()**: hasMany → `order_items`
|
||||
- **nodes()**: hasMany → `order_nodes`
|
||||
- **rootNodes()**: hasMany → `order_nodes`
|
||||
- **histories()**: hasMany → `order_histories`
|
||||
- **versions()**: hasMany → `order_versions`
|
||||
- **workOrders()**: hasMany → `work_orders`
|
||||
@@ -514,6 +516,7 @@ ### order_items
|
||||
**모델**: `App\Models\Orders\OrderItem`
|
||||
|
||||
- **order()**: belongsTo → `orders`
|
||||
- **node()**: belongsTo → `order_nodes`
|
||||
- **item()**: belongsTo → `items`
|
||||
- **quote()**: belongsTo → `quotes`
|
||||
- **quoteItem()**: belongsTo → `quote_items`
|
||||
@@ -524,6 +527,14 @@ ### order_item_components
|
||||
|
||||
- **orderItem()**: belongsTo → `order_items`
|
||||
|
||||
### order_nodes
|
||||
**모델**: `App\Models\Orders\OrderNode`
|
||||
|
||||
- **parent()**: belongsTo → `order_nodes`
|
||||
- **order()**: belongsTo → `orders`
|
||||
- **children()**: hasMany → `order_nodes`
|
||||
- **items()**: hasMany → `order_items`
|
||||
|
||||
### order_versions
|
||||
**모델**: `App\Models\Orders\OrderVersion`
|
||||
|
||||
@@ -597,6 +608,7 @@ ### work_orders
|
||||
- **primaryAssignee()**: hasMany → `work_order_assignees`
|
||||
- **items()**: hasMany → `work_order_items`
|
||||
- **issues()**: hasMany → `work_order_issues`
|
||||
- **stepProgress()**: hasMany → `work_order_step_progress`
|
||||
- **shipments()**: hasMany → `shipments`
|
||||
- **bendingDetail()**: hasOne → `work_order_bending_details`
|
||||
|
||||
@@ -624,6 +636,14 @@ ### work_order_items
|
||||
- **workOrder()**: belongsTo → `work_orders`
|
||||
- **item()**: belongsTo → `items`
|
||||
|
||||
### work_order_step_progress
|
||||
**모델**: `App\Models\Production\WorkOrderStepProgress`
|
||||
|
||||
- **workOrder()**: belongsTo → `work_orders`
|
||||
- **processStep()**: belongsTo → `process_steps`
|
||||
- **workOrderItem()**: belongsTo → `work_order_items`
|
||||
- **completedByUser()**: belongsTo → `users`
|
||||
|
||||
### work_results
|
||||
**모델**: `App\Models\Production\WorkResult`
|
||||
|
||||
|
||||
@@ -254,6 +254,7 @@ public function update(int $id, array $data)
|
||||
public function destroy(int $id)
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
$order = Order::where('tenant_id', $tenantId)->find($id);
|
||||
if (! $order) {
|
||||
@@ -263,16 +264,49 @@ public function destroy(int $id)
|
||||
// 진행 중이거나 완료된 수주는 삭제 불가
|
||||
if (in_array($order->status_code, [
|
||||
Order::STATUS_IN_PROGRESS,
|
||||
Order::STATUS_IN_PRODUCTION,
|
||||
Order::STATUS_PRODUCED,
|
||||
Order::STATUS_SHIPPING,
|
||||
Order::STATUS_SHIPPED,
|
||||
Order::STATUS_COMPLETED,
|
||||
])) {
|
||||
throw new BadRequestHttpException(__('error.order.cannot_delete_in_progress'));
|
||||
}
|
||||
|
||||
$order->deleted_by = $this->apiUserId();
|
||||
$order->save();
|
||||
$order->delete();
|
||||
// 작업지시가 존재하면 삭제 불가
|
||||
if ($order->workOrders()->exists()) {
|
||||
throw new BadRequestHttpException(__('error.order.cannot_delete_has_work_orders'));
|
||||
}
|
||||
|
||||
return 'success';
|
||||
// 출하 정보가 존재하면 삭제 불가
|
||||
if ($order->shipments()->exists()) {
|
||||
throw new BadRequestHttpException(__('error.order.cannot_delete_has_shipments'));
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($order, $userId) {
|
||||
// 1. order_item_components soft delete
|
||||
foreach ($order->items as $item) {
|
||||
$item->components()->update(['deleted_by' => $userId]);
|
||||
$item->components()->delete();
|
||||
}
|
||||
|
||||
// 2. order_items soft delete
|
||||
$order->items()->update(['deleted_by' => $userId]);
|
||||
$order->items()->delete();
|
||||
|
||||
// 3. order_nodes soft delete
|
||||
$order->nodes()->update(['deleted_by' => $userId]);
|
||||
$order->nodes()->delete();
|
||||
|
||||
// 4. order 마스터 soft delete
|
||||
$order->deleted_by = $userId;
|
||||
$order->save();
|
||||
$order->delete();
|
||||
|
||||
// order_histories, order_versions는 감사 기록이므로 보존
|
||||
|
||||
return 'success';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1092,7 +1092,7 @@ public function updateItemStatus(int $workOrderId, int $itemId, string $status)
|
||||
* 작업지시에 필요한 자재 목록 조회 (BOM 기반 + 로트별 재고)
|
||||
*
|
||||
* 작업지시 품목의 BOM 자재별로 StockLot(입고 로트)를 FIFO 순서로 반환합니다.
|
||||
* 로트번호는 입고관리(Receiving)에서 생성된 실제 로트번호입니다.
|
||||
* 동일 자재가 여러 작업지시 품목에 걸쳐 있으면 필요수량을 합산하고 로트는 중복 없이 반환합니다.
|
||||
*
|
||||
* @param int $workOrderId 작업지시 ID
|
||||
* @return array 자재 목록 (로트 단위)
|
||||
@@ -1109,8 +1109,8 @@ public function getMaterials(int $workOrderId): array
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
$materials = [];
|
||||
$rank = 1;
|
||||
// Phase 1: 작업지시 품목들에서 유니크 자재 목록 수집 (item_id 기준 합산)
|
||||
$uniqueMaterials = [];
|
||||
|
||||
foreach ($workOrder->items as $woItem) {
|
||||
$materialItems = [];
|
||||
@@ -1140,7 +1140,6 @@ public function getMaterials(int $workOrderId): array
|
||||
'item' => $childItem,
|
||||
'bom_qty' => $bomQty,
|
||||
'required_qty' => $bomQty * ($woItem->quantity ?? 1),
|
||||
'work_order_item_id' => $woItem->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1152,73 +1151,83 @@ public function getMaterials(int $workOrderId): array
|
||||
'item' => $woItem->item,
|
||||
'bom_qty' => 1,
|
||||
'required_qty' => $woItem->quantity ?? 1,
|
||||
'work_order_item_id' => $woItem->id,
|
||||
];
|
||||
}
|
||||
|
||||
// 각 자재별로 StockLot(입고 로트) 조회
|
||||
// 유니크 자재 수집 (같은 item_id면 required_qty 합산)
|
||||
foreach ($materialItems as $matInfo) {
|
||||
$materialItem = $matInfo['item'];
|
||||
|
||||
// Stock 조회
|
||||
$stock = \App\Models\Tenants\Stock::where('tenant_id', $tenantId)
|
||||
->where('item_id', $materialItem->id)
|
||||
->first();
|
||||
|
||||
if ($stock) {
|
||||
// 가용 로트를 FIFO 순서로 조회
|
||||
$lots = \App\Models\Tenants\StockLot::where('tenant_id', $tenantId)
|
||||
->where('stock_id', $stock->id)
|
||||
->where('status', 'available')
|
||||
->where('available_qty', '>', 0)
|
||||
->orderBy('fifo_order', 'asc')
|
||||
->get();
|
||||
|
||||
foreach ($lots as $lot) {
|
||||
$materials[] = [
|
||||
'stock_lot_id' => $lot->id,
|
||||
'item_id' => $materialItem->id,
|
||||
'work_order_item_id' => $matInfo['work_order_item_id'],
|
||||
'lot_no' => $lot->lot_no,
|
||||
'material_code' => $materialItem->code,
|
||||
'material_name' => $materialItem->name,
|
||||
'specification' => $materialItem->specification,
|
||||
'unit' => $lot->unit ?? $materialItem->unit ?? 'EA',
|
||||
'bom_qty' => $matInfo['bom_qty'],
|
||||
'required_qty' => $matInfo['required_qty'],
|
||||
'lot_qty' => (float) $lot->qty,
|
||||
'lot_available_qty' => (float) $lot->available_qty,
|
||||
'lot_reserved_qty' => (float) $lot->reserved_qty,
|
||||
'receipt_date' => $lot->receipt_date,
|
||||
'supplier' => $lot->supplier,
|
||||
'fifo_rank' => $rank++,
|
||||
];
|
||||
}
|
||||
$itemId = $matInfo['item']->id;
|
||||
if (isset($uniqueMaterials[$itemId])) {
|
||||
$uniqueMaterials[$itemId]['required_qty'] += $matInfo['required_qty'];
|
||||
} else {
|
||||
$uniqueMaterials[$itemId] = $matInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 가용 로트가 없는 경우 자재 정보만 반환 (재고 없음 표시)
|
||||
$hasLots = collect($materials)->where('item_id', $materialItem->id)->isNotEmpty();
|
||||
if (! $hasLots) {
|
||||
// Phase 2: 유니크 자재별로 StockLot 조회
|
||||
$materials = [];
|
||||
$rank = 1;
|
||||
|
||||
foreach ($uniqueMaterials as $matInfo) {
|
||||
$materialItem = $matInfo['item'];
|
||||
|
||||
$stock = \App\Models\Tenants\Stock::where('tenant_id', $tenantId)
|
||||
->where('item_id', $materialItem->id)
|
||||
->first();
|
||||
|
||||
$lotsFound = false;
|
||||
|
||||
if ($stock) {
|
||||
$lots = \App\Models\Tenants\StockLot::where('tenant_id', $tenantId)
|
||||
->where('stock_id', $stock->id)
|
||||
->where('status', 'available')
|
||||
->where('available_qty', '>', 0)
|
||||
->orderBy('fifo_order', 'asc')
|
||||
->get();
|
||||
|
||||
foreach ($lots as $lot) {
|
||||
$lotsFound = true;
|
||||
$materials[] = [
|
||||
'stock_lot_id' => null,
|
||||
'stock_lot_id' => $lot->id,
|
||||
'item_id' => $materialItem->id,
|
||||
'work_order_item_id' => $matInfo['work_order_item_id'],
|
||||
'lot_no' => null,
|
||||
'lot_no' => $lot->lot_no,
|
||||
'material_code' => $materialItem->code,
|
||||
'material_name' => $materialItem->name,
|
||||
'specification' => $materialItem->specification,
|
||||
'unit' => $materialItem->unit ?? 'EA',
|
||||
'unit' => $lot->unit ?? $materialItem->unit ?? 'EA',
|
||||
'bom_qty' => $matInfo['bom_qty'],
|
||||
'required_qty' => $matInfo['required_qty'],
|
||||
'lot_qty' => 0,
|
||||
'lot_available_qty' => 0,
|
||||
'lot_reserved_qty' => 0,
|
||||
'receipt_date' => null,
|
||||
'supplier' => null,
|
||||
'lot_qty' => (float) $lot->qty,
|
||||
'lot_available_qty' => (float) $lot->available_qty,
|
||||
'lot_reserved_qty' => (float) $lot->reserved_qty,
|
||||
'receipt_date' => $lot->receipt_date,
|
||||
'supplier' => $lot->supplier,
|
||||
'fifo_rank' => $rank++,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 가용 로트가 없는 경우 자재 정보만 반환 (재고 없음 표시)
|
||||
if (! $lotsFound) {
|
||||
$materials[] = [
|
||||
'stock_lot_id' => null,
|
||||
'item_id' => $materialItem->id,
|
||||
'lot_no' => null,
|
||||
'material_code' => $materialItem->code,
|
||||
'material_name' => $materialItem->name,
|
||||
'specification' => $materialItem->specification,
|
||||
'unit' => $materialItem->unit ?? 'EA',
|
||||
'bom_qty' => $matInfo['bom_qty'],
|
||||
'required_qty' => $matInfo['required_qty'],
|
||||
'lot_qty' => 0,
|
||||
'lot_available_qty' => 0,
|
||||
'lot_reserved_qty' => 0,
|
||||
'receipt_date' => null,
|
||||
'supplier' => null,
|
||||
'fifo_rank' => $rank++,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $materials;
|
||||
|
||||
234
app/Swagger/v1/TriggerAuditLogApi.php
Normal file
234
app/Swagger/v1/TriggerAuditLogApi.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
|
||||
namespace App\Swagger\v1;
|
||||
|
||||
/**
|
||||
* @OA\Tag(
|
||||
* name="Trigger Audit",
|
||||
* description="DB 트리거 기반 데이터 변경 추적 로그"
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="TriggerAuditLog",
|
||||
* type="object",
|
||||
*
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="table_name", type="string", example="products"),
|
||||
* @OA\Property(property="row_id", type="string", example="42"),
|
||||
* @OA\Property(property="dml_type", type="string", enum={"INSERT","UPDATE","DELETE"}),
|
||||
* @OA\Property(property="old_values", type="object", nullable=true),
|
||||
* @OA\Property(property="new_values", type="object", nullable=true),
|
||||
* @OA\Property(property="changed_columns", type="array", nullable=true, @OA\Items(type="string")),
|
||||
* @OA\Property(property="tenant_id", type="integer", nullable=true),
|
||||
* @OA\Property(property="actor_id", type="integer", nullable=true),
|
||||
* @OA\Property(property="session_info", type="object", nullable=true,
|
||||
* @OA\Property(property="ip", type="string"),
|
||||
* @OA\Property(property="ua", type="string"),
|
||||
* @OA\Property(property="route", type="string")
|
||||
* ),
|
||||
* @OA\Property(property="db_user", type="string", nullable=true, example="samuser@%"),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time")
|
||||
* )
|
||||
*/
|
||||
class TriggerAuditLogApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/trigger-audit-logs",
|
||||
* tags={"Trigger Audit"},
|
||||
* summary="감사 로그 목록 조회",
|
||||
* description="DB 트리거 기반 변경 로그를 페이지네이션으로 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="page", in="query", @OA\Schema(type="integer", minimum=1)),
|
||||
* @OA\Parameter(name="size", in="query", @OA\Schema(type="integer", minimum=1, maximum=200)),
|
||||
* @OA\Parameter(name="table_name", in="query", description="테이블명 필터", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="row_id", in="query", description="레코드 PK 필터", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="dml_type", in="query", description="DML 유형 필터", @OA\Schema(type="string", enum={"INSERT","UPDATE","DELETE"})),
|
||||
* @OA\Parameter(name="tenant_id", in="query", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="actor_id", in="query", @OA\Schema(type="integer")),
|
||||
* @OA\Parameter(name="db_user", in="query", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="from", in="query", description="시작일", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="to", in="query", description="종료일", @OA\Schema(type="string", format="date")),
|
||||
* @OA\Parameter(name="sort", in="query", @OA\Schema(type="string", enum={"created_at","id"})),
|
||||
* @OA\Parameter(name="order", in="query", @OA\Schema(type="string", enum={"asc","desc"})),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="목록 조회 성공",
|
||||
*
|
||||
* @OA\JsonContent(type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="current_page", type="integer"),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/TriggerAuditLog")),
|
||||
* @OA\Property(property="last_page", type="integer"),
|
||||
* @OA\Property(property="per_page", type="integer"),
|
||||
* @OA\Property(property="total", type="integer")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/trigger-audit-logs/stats",
|
||||
* tags={"Trigger Audit"},
|
||||
* summary="감사 로그 통계",
|
||||
* description="전체/오늘/DML별 건수, 상위 테이블, 저장소 크기 통계를 반환합니다.",
|
||||
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="tenant_id", in="query", @OA\Schema(type="integer")),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="통계 조회 성공",
|
||||
*
|
||||
* @OA\JsonContent(type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="total", type="integer"),
|
||||
* @OA\Property(property="today", type="integer"),
|
||||
* @OA\Property(property="by_dml_type", type="object",
|
||||
* @OA\Property(property="INSERT", type="integer"),
|
||||
* @OA\Property(property="UPDATE", type="integer"),
|
||||
* @OA\Property(property="DELETE", type="integer")
|
||||
* ),
|
||||
* @OA\Property(property="top_tables", type="object"),
|
||||
* @OA\Property(property="storage_mb", type="number")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function stats() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/trigger-audit-logs/{id}",
|
||||
* tags={"Trigger Audit"},
|
||||
* summary="감사 로그 상세 조회",
|
||||
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="상세 조회 성공",
|
||||
*
|
||||
* @OA\JsonContent(type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", ref="#/components/schemas/TriggerAuditLog")
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=404, description="Not Found")
|
||||
* )
|
||||
*/
|
||||
public function show() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/trigger-audit-logs/{tableName}/{rowId}/history",
|
||||
* tags={"Trigger Audit"},
|
||||
* summary="레코드 변경 이력 조회",
|
||||
* description="특정 테이블의 특정 레코드에 대한 전체 변경 이력을 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="tableName", in="path", required=true, description="테이블명", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(name="rowId", in="path", required=true, description="레코드 PK", @OA\Schema(type="string")),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="이력 조회 성공",
|
||||
*
|
||||
* @OA\JsonContent(type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/TriggerAuditLog"))
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function recordHistory() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/trigger-audit-logs/{id}/rollback-preview",
|
||||
* tags={"Trigger Audit"},
|
||||
* summary="롤백 SQL 미리보기",
|
||||
* description="해당 변경을 되돌리기 위한 SQL문을 미리 확인합니다.",
|
||||
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="롤백 SQL 반환",
|
||||
*
|
||||
* @OA\JsonContent(type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="audit_id", type="integer"),
|
||||
* @OA\Property(property="rollback_sql", type="string")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=404, description="Not Found")
|
||||
* )
|
||||
*/
|
||||
public function rollbackPreview() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/trigger-audit-logs/{id}/rollback",
|
||||
* tags={"Trigger Audit"},
|
||||
* summary="롤백 실행",
|
||||
* description="해당 변경을 실제로 되돌립니다. confirm=true 필수.",
|
||||
* security={{"ApiKeyAuth": {}, "BearerAuth": {}}},
|
||||
*
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
*
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
*
|
||||
* @OA\JsonContent(
|
||||
* required={"confirm"},
|
||||
*
|
||||
* @OA\Property(property="confirm", type="boolean", example=true, description="롤백 확인 (true 필수)")
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="롤백 성공",
|
||||
*
|
||||
* @OA\JsonContent(type="object",
|
||||
*
|
||||
* @OA\Property(property="success", type="boolean"),
|
||||
* @OA\Property(property="message", type="string"),
|
||||
* @OA\Property(property="data", type="object",
|
||||
* @OA\Property(property="rolled_back", type="boolean"),
|
||||
* @OA\Property(property="sql_executed", type="string")
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
*
|
||||
* @OA\Response(response=404, description="Not Found"),
|
||||
* @OA\Response(response=422, description="Validation Error")
|
||||
* )
|
||||
*/
|
||||
public function rollbackExecute() {}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
'failed_jobs',
|
||||
'migrations',
|
||||
'password_reset_tokens',
|
||||
'api_request_logs',
|
||||
];
|
||||
|
||||
/** 변경 추적 제외 컬럼 */
|
||||
@@ -56,6 +57,7 @@ public function up(): void
|
||||
|
||||
if (in_array($tableName, $this->excludeTables, true)) {
|
||||
$skipped++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -106,12 +108,12 @@ private function createTriggersForTable(string $dbName, string $tableName): void
|
||||
$pk = $pkRow->COLUMN_NAME;
|
||||
|
||||
// 컬럼 목록 (제외 컬럼 필터링)
|
||||
$columns = DB::select("
|
||||
$columns = DB::select('
|
||||
SELECT COLUMN_NAME
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
||||
ORDER BY ORDINAL_POSITION
|
||||
", [$dbName, $tableName]);
|
||||
', [$dbName, $tableName]);
|
||||
|
||||
$cols = [];
|
||||
$hasTenantId = false;
|
||||
@@ -144,8 +146,8 @@ private function createTriggersForTable(string $dbName, string $tableName): void
|
||||
$cols
|
||||
));
|
||||
|
||||
$tenantNew = $hasTenantId ? "NEW.`tenant_id`" : 'NULL';
|
||||
$tenantOld = $hasTenantId ? "OLD.`tenant_id`" : 'NULL';
|
||||
$tenantNew = $hasTenantId ? 'NEW.`tenant_id`' : 'NULL';
|
||||
$tenantOld = $hasTenantId ? 'OLD.`tenant_id`' : 'NULL';
|
||||
|
||||
// 기존 트리거 삭제
|
||||
DB::unprepared("DROP TRIGGER IF EXISTS `trg_{$tableName}_ai`");
|
||||
|
||||
@@ -400,6 +400,8 @@
|
||||
'order' => [
|
||||
'cannot_update_completed' => '완료 또는 취소된 수주는 수정할 수 없습니다.',
|
||||
'cannot_delete_in_progress' => '진행 중이거나 완료된 수주는 삭제할 수 없습니다.',
|
||||
'cannot_delete_has_work_orders' => '작업지시가 존재하는 수주는 삭제할 수 없습니다. 작업지시를 먼저 삭제해주세요.',
|
||||
'cannot_delete_has_shipments' => '출하 정보가 존재하는 수주는 삭제할 수 없습니다. 출하를 먼저 삭제해주세요.',
|
||||
'invalid_status_transition' => '유효하지 않은 상태 전환입니다.',
|
||||
'already_created_from_quote' => '이미 해당 견적에서 수주가 생성되었습니다.',
|
||||
'must_be_confirmed_for_production' => '확정 상태의 수주만 생산지시를 생성할 수 있습니다.',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user