fix : 모델, BOM 구성 수정
- 설계용 모델, BOM 기능 추가
This commit is contained in:
63
app/Http/Controllers/Api/V1/Design/BomTemplateController.php
Normal file
63
app/Http/Controllers/Api/V1/Design/BomTemplateController.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Design\BomTemplateService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BomTemplateController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected BomTemplateService $service
|
||||
) {}
|
||||
|
||||
public function listByVersion(int $versionId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($versionId) {
|
||||
// 내부에서 tenant 스코프 처리
|
||||
return $this->service->paginate($versionId, page: 1, size: PHP_INT_MAX)->items();
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function upsertTemplate(Request $request, int $versionId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $versionId) {
|
||||
$payload = $request->validate([
|
||||
'name' => 'nullable|string|max:100',
|
||||
'is_primary' => 'boolean',
|
||||
'notes' => 'nullable|string',
|
||||
]);
|
||||
return $this->service->upsertTemplate(
|
||||
modelVersionId: $versionId,
|
||||
name: $payload['name'] ?? 'Main',
|
||||
isPrimary: (bool)($payload['is_primary'] ?? true),
|
||||
notes: $payload['notes'] ?? null
|
||||
);
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
public function show(int $templateId)
|
||||
{
|
||||
return ApiResponse::handle(fn() => $this->service->show($templateId, true), __('message.fetched'));
|
||||
}
|
||||
|
||||
public function replaceItems(Request $request, int $templateId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $templateId) {
|
||||
$payload = $request->validate([
|
||||
'items' => 'required|array|min:1',
|
||||
'items.*.ref_type' => 'required|string|in:MATERIAL,PRODUCT',
|
||||
'items.*.ref_id' => 'required|integer|min:1',
|
||||
'items.*.qty' => 'required|numeric|gt:0',
|
||||
'items.*.waste_rate' => 'nullable|numeric|min:0',
|
||||
'items.*.uom_id' => 'nullable|integer|min:1',
|
||||
'items.*.notes' => 'nullable|string|max:255',
|
||||
'items.*.sort_order' => 'nullable|integer',
|
||||
]);
|
||||
$this->service->replaceItems($templateId, $payload['items']);
|
||||
return null;
|
||||
}, __('message.bom.bulk_upsert'));
|
||||
}
|
||||
}
|
||||
68
app/Http/Controllers/Api/V1/Design/DesignModelController.php
Normal file
68
app/Http/Controllers/Api/V1/Design/DesignModelController.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Design\ModelService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DesignModelController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected ModelService $service
|
||||
) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$q = $request->query('q', '');
|
||||
$page = (int) $request->query('page', 1);
|
||||
$size = (int) $request->query('size', 20);
|
||||
return $this->service->list($q, $page, $size);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$payload = $request->validate([
|
||||
'code' => 'required|string|max:100',
|
||||
'name' => 'required|string|max:200',
|
||||
'category_id' => 'nullable|integer',
|
||||
'lifecycle' => 'nullable|string|max:30',
|
||||
'description' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
return $this->service->create($payload);
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(fn() => $this->service->find($id), __('message.fetched'));
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
$payload = $request->validate([
|
||||
'code' => 'sometimes|string|max:100',
|
||||
'name' => 'sometimes|string|max:200',
|
||||
'category_id' => 'nullable|integer',
|
||||
'lifecycle' => 'nullable|string|max:30',
|
||||
'description' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
]);
|
||||
return $this->service->update($id, $payload);
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$this->service->delete($id);
|
||||
return null;
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Design\ModelVersionService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ModelVersionController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected ModelVersionService $service
|
||||
) {}
|
||||
|
||||
public function index(int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(fn() => $this->service->listByModel($modelId), __('message.fetched'));
|
||||
}
|
||||
|
||||
public function createDraft(Request $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
$payload = $request->validate([
|
||||
'version_no' => 'nullable|integer|min:1',
|
||||
'notes' => 'nullable|string',
|
||||
'effective_from' => 'nullable|date',
|
||||
'effective_to' => 'nullable|date|after:effective_from',
|
||||
]);
|
||||
return $this->service->createDraft($modelId, $payload);
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
public function release(int $versionId)
|
||||
{
|
||||
return ApiResponse::handle(fn() => $this->service->release($versionId), __('message.updated'));
|
||||
}
|
||||
}
|
||||
29
app/Models/Design/BomTemplate.php
Normal file
29
app/Models/Design/BomTemplate.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Design;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class BomTemplate extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'bom_templates';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id','model_version_id','name','is_primary','notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_primary' => 'boolean',
|
||||
];
|
||||
|
||||
public function modelVersion() {
|
||||
return $this->belongsTo(ModelVersion::class, 'model_version_id');
|
||||
}
|
||||
|
||||
public function items() {
|
||||
return $this->hasMany(BomTemplateItem::class, 'bom_template_id');
|
||||
}
|
||||
}
|
||||
23
app/Models/Design/BomTemplateItem.php
Normal file
23
app/Models/Design/BomTemplateItem.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Design;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BomTemplateItem extends Model
|
||||
{
|
||||
protected $table = 'bom_template_items';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id','bom_template_id','ref_type','ref_id','qty','waste_rate','uom_id','notes','sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'qty' => 'decimal:6',
|
||||
'waste_rate' => 'decimal:6',
|
||||
];
|
||||
|
||||
public function template() {
|
||||
return $this->belongsTo(BomTemplate::class, 'bom_template_id');
|
||||
}
|
||||
}
|
||||
26
app/Models/Design/DesignModel.php
Normal file
26
app/Models/Design/DesignModel.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Design;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class DesignModel extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'models';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id','code','name','category_id','lifecycle','description','is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
// 관계: 모델은 여러 버전을 가짐
|
||||
public function versions() {
|
||||
return $this->hasMany(ModelVersion::class, 'model_id');
|
||||
}
|
||||
}
|
||||
35
app/Models/Design/ModelVersion.php
Normal file
35
app/Models/Design/ModelVersion.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Design;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class ModelVersion extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'model_versions';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id','model_id','version_no','status','effective_from','effective_to','notes','is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'effective_from' => 'datetime',
|
||||
'effective_to' => 'datetime',
|
||||
];
|
||||
|
||||
public function model() {
|
||||
return $this->belongsTo(DesignModel::class, 'model_id');
|
||||
}
|
||||
|
||||
public function bomTemplates() {
|
||||
return $this->hasMany(BomTemplate::class, 'model_version_id');
|
||||
}
|
||||
|
||||
public function scopeReleased($q) {
|
||||
return $q->where('status', 'RELEASED');
|
||||
}
|
||||
}
|
||||
221
app/Services/Design/BomTemplateService.php
Normal file
221
app/Services/Design/BomTemplateService.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Design;
|
||||
|
||||
use App\Models\Design\BomTemplate;
|
||||
use App\Models\Design\BomTemplateItem;
|
||||
use App\Models\Design\ModelVersion;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class BomTemplateService extends Service
|
||||
{
|
||||
/** 페이징 목록(옵션: 모델버전 필터) */
|
||||
public function paginate(?int $modelVersionId, int $page = 1, int $size = 20): LengthAwarePaginator
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$q = BomTemplate::query()->where('tenant_id', $tenantId);
|
||||
|
||||
if ($modelVersionId) {
|
||||
$q->where('model_version_id', $modelVersionId);
|
||||
}
|
||||
|
||||
return $q->orderByDesc('is_primary')
|
||||
->orderBy('name')
|
||||
->paginate($size, ['*'], 'page', $page);
|
||||
}
|
||||
|
||||
/** 템플릿 upsert(name 기준) */
|
||||
public function upsertTemplate(int $modelVersionId, string $name = 'Main', bool $isPrimary = true, ?string $notes = null): BomTemplate
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$mv = ModelVersion::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($modelVersionId);
|
||||
|
||||
if (!$mv) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($tenantId, $mv, $name, $isPrimary, $notes) {
|
||||
$tpl = BomTemplate::query()
|
||||
->where('model_version_id', $mv->id)
|
||||
->where('name', $name)
|
||||
->first();
|
||||
|
||||
if (!$tpl) {
|
||||
$tpl = BomTemplate::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_version_id' => $mv->id,
|
||||
'name' => $name,
|
||||
'is_primary' => $isPrimary,
|
||||
'notes' => $notes,
|
||||
]);
|
||||
} else {
|
||||
$tpl->fill(['is_primary' => $isPrimary, 'notes' => $notes])->save();
|
||||
}
|
||||
|
||||
if ($isPrimary) {
|
||||
BomTemplate::query()
|
||||
->where('model_version_id', $mv->id)
|
||||
->where('id', '<>', $tpl->id)
|
||||
->update(['is_primary' => false]);
|
||||
}
|
||||
|
||||
return $tpl;
|
||||
});
|
||||
}
|
||||
|
||||
/** 템플릿 메타 수정 */
|
||||
public function updateTemplate(int $templateId, array $data): BomTemplate
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$tpl = BomTemplate::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($templateId);
|
||||
|
||||
if (!$tpl) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
$name = $data['name'] ?? $tpl->name;
|
||||
if ($name !== $tpl->name) {
|
||||
$dup = BomTemplate::query()
|
||||
->where('model_version_id', $tpl->model_version_id)
|
||||
->where('name', $name)
|
||||
->where('id', '<>', $tpl->id)
|
||||
->exists();
|
||||
if ($dup) {
|
||||
throw ValidationException::withMessages(['name' => __('error.duplicate')]);
|
||||
}
|
||||
}
|
||||
|
||||
$tpl->fill($data)->save();
|
||||
|
||||
if (array_key_exists('is_primary', $data) && $data['is_primary']) {
|
||||
// 다른 템플릿 대표 해제
|
||||
BomTemplate::query()
|
||||
->where('model_version_id', $tpl->model_version_id)
|
||||
->where('id', '<>', $tpl->id)
|
||||
->update(['is_primary' => false]);
|
||||
}
|
||||
|
||||
return $tpl;
|
||||
}
|
||||
|
||||
/** 템플릿 삭제(soft) */
|
||||
public function deleteTemplate(int $templateId): void
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$tpl = BomTemplate::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($templateId);
|
||||
|
||||
if (!$tpl) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
$tpl->delete();
|
||||
}
|
||||
|
||||
/** 템플릿 상세 (옵션: 항목 포함) */
|
||||
public function show(int $templateId, bool $withItems = false): BomTemplate
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$q = BomTemplate::query()->where('tenant_id', $tenantId);
|
||||
if ($withItems) {
|
||||
$q->with(['items' => fn($w) => $w->orderBy('sort_order')]);
|
||||
}
|
||||
|
||||
$tpl = $q->find($templateId);
|
||||
if (!$tpl) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
return $tpl;
|
||||
}
|
||||
|
||||
/** 항목 일괄 치환 */
|
||||
public function replaceItems(int $templateId, array $items): void
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$tpl = BomTemplate::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($templateId);
|
||||
|
||||
if (!$tpl) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
// 1차 검증
|
||||
foreach ($items as $i => $row) {
|
||||
$refType = strtoupper((string) Arr::get($row, 'ref_type'));
|
||||
$qty = (float) Arr::get($row, 'qty', 0);
|
||||
if (!in_array($refType, ['MATERIAL','PRODUCT'], true)) {
|
||||
throw ValidationException::withMessages(["items.$i.ref_type" => __('error.validation_failed')]);
|
||||
}
|
||||
if ($qty <= 0) {
|
||||
throw ValidationException::withMessages(["items.$i.qty" => __('error.validation_failed')]);
|
||||
}
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($tenantId, $tpl, $items) {
|
||||
BomTemplateItem::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('bom_template_id', $tpl->id)
|
||||
->delete();
|
||||
|
||||
$now = now();
|
||||
$payloads = [];
|
||||
foreach ($items as $row) {
|
||||
$payloads[] = [
|
||||
'tenant_id' => $tenantId,
|
||||
'bom_template_id' => $tpl->id,
|
||||
'ref_type' => strtoupper($row['ref_type']),
|
||||
'ref_id' => (int) $row['ref_id'],
|
||||
'qty' => (string) ($row['qty'] ?? 1),
|
||||
'waste_rate' => (string) ($row['waste_rate'] ?? 0),
|
||||
'uom_id' => $row['uom_id'] ?? null,
|
||||
'notes' => $row['notes'] ?? null,
|
||||
'sort_order' => (int) ($row['sort_order'] ?? 0),
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($payloads)) {
|
||||
BomTemplateItem::insert($payloads);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 항목 조회 */
|
||||
public function listItems(int $templateId)
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$tpl = BomTemplate::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($templateId);
|
||||
|
||||
if (!$tpl) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
return BomTemplateItem::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('bom_template_id', $tpl->id)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
112
app/Services/Design/ModelService.php
Normal file
112
app/Services/Design/ModelService.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Design;
|
||||
|
||||
use App\Models\Design\DesignModel;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ModelService extends Service
|
||||
{
|
||||
/** 목록 */
|
||||
public function list(string $q = '', int $page = 1, int $size = 20): LengthAwarePaginator
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$query = DesignModel::query()->where('tenant_id', $tenantId);
|
||||
|
||||
if ($q !== '') {
|
||||
$query->where(function ($w) use ($q) {
|
||||
$w->where('code', 'like', "%{$q}%")
|
||||
->orWhere('name', 'like', "%{$q}%")
|
||||
->orWhere('description', 'like', "%{$q}%");
|
||||
});
|
||||
}
|
||||
|
||||
return $query->orderByDesc('id')->paginate($size, ['*'], 'page', $page);
|
||||
}
|
||||
|
||||
/** 생성 */
|
||||
public function create(array $data): DesignModel
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$exists = DesignModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('code', $data['code'])
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
throw ValidationException::withMessages(['code' => __('error.duplicate')]);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($tenantId, $data) {
|
||||
$payload = array_merge($data, ['tenant_id' => $tenantId]);
|
||||
return DesignModel::create($payload);
|
||||
});
|
||||
}
|
||||
|
||||
/** 단건 */
|
||||
public function find(int $id): DesignModel
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
$model = DesignModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->with(['versions' => fn($q) => $q->orderBy('version_no')])
|
||||
->find($id);
|
||||
|
||||
if (!$model) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/** 수정 */
|
||||
public function update(int $id, array $data): DesignModel
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$model = DesignModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($id);
|
||||
|
||||
if (!$model) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
if (isset($data['code']) && $data['code'] !== $model->code) {
|
||||
$dup = DesignModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('code', $data['code'])
|
||||
->exists();
|
||||
if ($dup) {
|
||||
throw ValidationException::withMessages(['code' => __('error.duplicate')]);
|
||||
}
|
||||
}
|
||||
|
||||
$model->fill($data);
|
||||
$model->save();
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
/** 삭제(soft) */
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$model = DesignModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($id);
|
||||
|
||||
if (!$model) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
$model->delete();
|
||||
}
|
||||
}
|
||||
102
app/Services/Design/ModelVersionService.php
Normal file
102
app/Services/Design/ModelVersionService.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Design;
|
||||
|
||||
use App\Models\Design\DesignModel;
|
||||
use App\Models\Design\ModelVersion;
|
||||
use App\Services\Service;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class ModelVersionService extends Service
|
||||
{
|
||||
/** 특정 모델의 버전 목록 */
|
||||
public function listByModel(int $modelId)
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$model = DesignModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($modelId);
|
||||
|
||||
if (!$model) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
return ModelVersion::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('model_id', $modelId)
|
||||
->orderBy('version_no')
|
||||
->get();
|
||||
}
|
||||
|
||||
/** DRAFT 생성 (version_no 자동/수동) */
|
||||
public function createDraft(int $modelId, array $extra = []): ModelVersion
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$model = DesignModel::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($modelId);
|
||||
|
||||
if (!$model) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($tenantId, $model, $extra) {
|
||||
$versionNo = $extra['version_no'] ?? null;
|
||||
|
||||
if ($versionNo === null) {
|
||||
$max = ModelVersion::query()
|
||||
->where('model_id', $model->id)
|
||||
->max('version_no');
|
||||
$versionNo = (int)($max ?? 0) + 1;
|
||||
} else {
|
||||
$exists = ModelVersion::query()
|
||||
->where('model_id', $model->id)
|
||||
->where('version_no', $versionNo)
|
||||
->exists();
|
||||
if ($exists) {
|
||||
throw ValidationException::withMessages(['version_no' => __('error.duplicate')]);
|
||||
}
|
||||
}
|
||||
|
||||
return ModelVersion::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'model_id' => $model->id,
|
||||
'version_no' => $versionNo,
|
||||
'status' => 'DRAFT',
|
||||
'is_active' => true,
|
||||
'notes' => $extra['notes'] ?? null,
|
||||
'effective_from' => $extra['effective_from'] ?? null,
|
||||
'effective_to' => $extra['effective_to'] ?? null,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/** RELEASED 전환 */
|
||||
public function release(int $versionId): ModelVersion
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$mv = ModelVersion::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->find($versionId);
|
||||
|
||||
if (!$mv) {
|
||||
throw new NotFoundHttpException(__('error.not_found'));
|
||||
}
|
||||
|
||||
if ($mv->status === 'RELEASED') {
|
||||
return $mv; // 멱등
|
||||
}
|
||||
|
||||
// TODO: 대표 템플릿 존재 등 사전 검증 훅 가능
|
||||
$mv->status = 'RELEASED';
|
||||
$mv->effective_from = $mv->effective_from ?? now();
|
||||
$mv->save();
|
||||
|
||||
return $mv;
|
||||
}
|
||||
}
|
||||
397
app/Swagger/v1/DesignModelApi.php
Normal file
397
app/Swagger/v1/DesignModelApi.php
Normal file
@@ -0,0 +1,397 @@
|
||||
<?php
|
||||
|
||||
namespace App\Swagger\v1;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Model", description="모델(설계 상위) 관리")
|
||||
* @OA\Tag(name="ModelVersion", description="모델 버전(DRAFT/RELEASED) 관리")
|
||||
* @OA\Tag(name="BomTemplate", description="모델버전 기준 BOM 템플릿 관리")
|
||||
*/
|
||||
|
||||
/* =========================
|
||||
* 공통 스키마 (설계 영역)
|
||||
* ========================= */
|
||||
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="DesignModel",
|
||||
* type="object",
|
||||
* required={"id","tenant_id","code","name"},
|
||||
* @OA\Property(property="id", type="integer", example=101),
|
||||
* @OA\Property(property="tenant_id", type="integer", example=1),
|
||||
* @OA\Property(property="code", type="string", example="KSS01"),
|
||||
* @OA\Property(property="name", type="string", example="KSS 표준 모델"),
|
||||
* @OA\Property(property="category_id", type="integer", nullable=true, example=12),
|
||||
* @OA\Property(property="lifecycle", type="string", nullable=true, example="ACTIVE"),
|
||||
* @OA\Property(property="description", type="string", nullable=true, example="롤러 구조 표준 설계"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time", example="2025-09-05 10:11:12"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time", example="2025-09-05 10:11:12"),
|
||||
* @OA\Property(property="deleted_at", type="string", format="date-time", nullable=true, example=null)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="DesignModelPagination",
|
||||
* type="object",
|
||||
* description="라라벨 LengthAwarePaginator",
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/DesignModel")),
|
||||
* @OA\Property(property="first_page_url", type="string", example="/api/v1/design/models?page=1"),
|
||||
* @OA\Property(property="from", type="integer", example=1),
|
||||
* @OA\Property(property="last_page", type="integer", example=5),
|
||||
* @OA\Property(property="last_page_url", type="string", example="/api/v1/design/models?page=5"),
|
||||
* @OA\Property(property="links", type="array", @OA\Items(
|
||||
* type="object",
|
||||
* @OA\Property(property="url", type="string", nullable=true, example=null),
|
||||
* @OA\Property(property="label", type="string", example="« Previous"),
|
||||
* @OA\Property(property="active", type="boolean", example=false)
|
||||
* )),
|
||||
* @OA\Property(property="next_page_url", type="string", nullable=true, example="/api/v1/design/models?page=2"),
|
||||
* @OA\Property(property="path", type="string", example="/api/v1/design/models"),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="prev_page_url", type="string", nullable=true, example=null),
|
||||
* @OA\Property(property="to", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=92)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="ModelVersion",
|
||||
* type="object",
|
||||
* required={"id","tenant_id","model_id","version_no","status"},
|
||||
* @OA\Property(property="id", type="integer", example=201),
|
||||
* @OA\Property(property="tenant_id", type="integer", example=1),
|
||||
* @OA\Property(property="model_id", type="integer", example=101),
|
||||
* @OA\Property(property="version_no", type="integer", example=1),
|
||||
* @OA\Property(property="status", type="string", example="RELEASED"),
|
||||
* @OA\Property(property="effective_from", type="string", format="date-time", nullable=true, example="2025-09-05 11:00:00"),
|
||||
* @OA\Property(property="effective_to", type="string", format="date-time", nullable=true, example=null),
|
||||
* @OA\Property(property="notes", type="string", nullable=true, example="초도 릴리즈"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="deleted_at", type="string", format="date-time", nullable=true, example=null)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BomTemplate",
|
||||
* type="object",
|
||||
* required={"id","tenant_id","model_version_id","name"},
|
||||
* @OA\Property(property="id", type="integer", example=301),
|
||||
* @OA\Property(property="tenant_id", type="integer", example=1),
|
||||
* @OA\Property(property="model_version_id", type="integer", example=201),
|
||||
* @OA\Property(property="name", type="string", example="Main"),
|
||||
* @OA\Property(property="is_primary", type="boolean", example=true),
|
||||
* @OA\Property(property="notes", type="string", nullable=true, example="표준 템플릿"),
|
||||
* @OA\Property(property="items_count", type="integer", example=3, description="withCount(items) 사용 시"),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="deleted_at", type="string", format="date-time", nullable=true, example=null)
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BomTemplateItem",
|
||||
* type="object",
|
||||
* required={"id","tenant_id","bom_template_id","ref_type","ref_id","qty"},
|
||||
* @OA\Property(property="id", type="integer", example=401),
|
||||
* @OA\Property(property="tenant_id", type="integer", example=1),
|
||||
* @OA\Property(property="bom_template_id", type="integer", example=301),
|
||||
* @OA\Property(property="ref_type", type="string", example="MATERIAL", enum={"MATERIAL","PRODUCT"}),
|
||||
* @OA\Property(property="ref_id", type="integer", example=10101),
|
||||
* @OA\Property(property="qty", type="number", format="double", example=2.000000),
|
||||
* @OA\Property(property="waste_rate", type="number", format="double", example=0.000000),
|
||||
* @OA\Property(property="uom_id", type="integer", nullable=true, example=null),
|
||||
* @OA\Property(property="notes", type="string", nullable=true, example="프레임용"),
|
||||
* @OA\Property(property="sort_order", type="integer", example=10),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time")
|
||||
* )
|
||||
*
|
||||
* @OA\Schema(
|
||||
* schema="BomTemplateItemReplaceRequest",
|
||||
* type="object",
|
||||
* required={"items"},
|
||||
* @OA\Property(
|
||||
* property="items",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* required={"ref_type","ref_id","qty"},
|
||||
* @OA\Property(property="ref_type", type="string", enum={"MATERIAL","PRODUCT"}, example="MATERIAL"),
|
||||
* @OA\Property(property="ref_id", type="integer", example=101),
|
||||
* @OA\Property(property="qty", type="number", format="double", example=2),
|
||||
* @OA\Property(property="waste_rate", type="number", format="double", nullable=true, example=0),
|
||||
* @OA\Property(property="uom_id", type="integer", nullable=true, example=null),
|
||||
* @OA\Property(property="notes", type="string", nullable=true, example="프레임용"),
|
||||
* @OA\Property(property="sort_order", type="integer", example=10)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
|
||||
/* ================
|
||||
* 모델 엔드포인트
|
||||
* ================ */
|
||||
class DesignModelApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/design/models",
|
||||
* tags={"Model"},
|
||||
* summary="모델 목록",
|
||||
* description="모델(설계 상위) 목록을 페이징으로 조회합니다.",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="q", in="query", description="검색어(code/name/description like)", @OA\Schema(type="string")),
|
||||
* @OA\Parameter(ref="#/components/parameters/Page"),
|
||||
* @OA\Parameter(ref="#/components/parameters/Size"),
|
||||
* @OA\Response(
|
||||
* response=200, description="조회 성공",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/DesignModelPagination"))
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=500, description="서버 에러", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function index() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/design/models",
|
||||
* tags={"Model"},
|
||||
* summary="모델 생성",
|
||||
* description="모델(설계 상위)을 생성합니다.",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"code","name"},
|
||||
* @OA\Property(property="code", type="string", maxLength=100, example="KSS01"),
|
||||
* @OA\Property(property="name", type="string", maxLength=200, example="KSS 표준 모델"),
|
||||
* @OA\Property(property="category_id", type="integer", nullable=true, example=12),
|
||||
* @OA\Property(property="lifecycle", type="string", nullable=true, example="ACTIVE"),
|
||||
* @OA\Property(property="description", type="string", nullable=true, example="롤러 구조 표준 설계"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true)
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=200, description="등록 성공", @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/DesignModel"))
|
||||
* })),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function store() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/design/models/{id}",
|
||||
* tags={"Model"},
|
||||
* summary="모델 상세",
|
||||
* description="단일 모델을 조회합니다. (versions 포함 가능)",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="조회 성공", @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/DesignModel"))
|
||||
* })),
|
||||
* @OA\Response(response=404, description="없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function show() {}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/v1/design/models/{id}",
|
||||
* tags={"Model"},
|
||||
* summary="모델 수정",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="code", type="string", maxLength=100, example="KSS01"),
|
||||
* @OA\Property(property="name", type="string", maxLength=200, example="KSS 표준 모델(개정)"),
|
||||
* @OA\Property(property="category_id", type="integer", nullable=true, example=12),
|
||||
* @OA\Property(property="lifecycle", type="string", nullable=true, example="ACTIVE"),
|
||||
* @OA\Property(property="description", type="string", nullable=true, example="개정 메모"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true)
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=200, description="수정 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")) ,
|
||||
* @OA\Response(response=404, description="없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function update() {}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/design/models/{id}",
|
||||
* tags={"Model"},
|
||||
* summary="모델 삭제(soft)",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="id", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="삭제 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
|
||||
* @OA\Response(response=404, description="없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function destroy() {}
|
||||
}
|
||||
|
||||
/* ==================
|
||||
* 모델버전 엔드포인트
|
||||
* ================== */
|
||||
class ModelVersionApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/design/models/{modelId}/versions",
|
||||
* tags={"ModelVersion"},
|
||||
* summary="모델의 버전 목록",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="modelId", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="조회 성공",
|
||||
* @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/ModelVersion")))
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=404, description="없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function listVersions() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/design/models/{modelId}/versions",
|
||||
* tags={"ModelVersion"},
|
||||
* summary="버전 DRAFT 생성",
|
||||
* description="version_no 미지정 시 자동 증가",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="modelId", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(
|
||||
* required=false,
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="version_no", type="integer", nullable=true, example=1),
|
||||
* @OA\Property(property="notes", type="string", nullable=true, example="초안 메모"),
|
||||
* @OA\Property(property="effective_from", type="string", format="date-time", nullable=true),
|
||||
* @OA\Property(property="effective_to", type="string", format="date-time", nullable=true)
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=200, description="생성 성공",
|
||||
* @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/ModelVersion"))
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function createDraft() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/design/versions/{versionId}/release",
|
||||
* tags={"ModelVersion"},
|
||||
* summary="버전 RELEASED 전환",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="versionId", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="전환 성공",
|
||||
* @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/ModelVersion"))
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=409, description="상태 충돌", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")),
|
||||
* @OA\Response(response=404, description="없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function release() {}
|
||||
}
|
||||
|
||||
/* ===================
|
||||
* BOM 템플릿 엔드포인트
|
||||
* =================== */
|
||||
class BomTemplateApi
|
||||
{
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/design/versions/{versionId}/bom-templates",
|
||||
* tags={"BomTemplate"},
|
||||
* summary="모델버전의 BOM 템플릿 목록",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="versionId", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="조회 성공",
|
||||
* @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", type="array", @OA\Items(ref="#/components/schemas/BomTemplate")))
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=404, description="없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function listByVersion() {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/v1/design/versions/{versionId}/bom-templates",
|
||||
* tags={"BomTemplate"},
|
||||
* summary="BOM 템플릿 upsert (name 기준)",
|
||||
* description="is_primary=true 지정 시 동일 모델버전의 기존 대표 템플릿은 자동 해제",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="versionId", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(
|
||||
* required=false,
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="name", type="string", maxLength=100, example="Main"),
|
||||
* @OA\Property(property="is_primary", type="boolean", example=true),
|
||||
* @OA\Property(property="notes", type="string", nullable=true, example="표준 템플릿")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(response=200, description="저장 성공",
|
||||
* @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/BomTemplate"))
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function upsertTemplate() {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/design/bom-templates/{templateId}",
|
||||
* tags={"BomTemplate"},
|
||||
* summary="BOM 템플릿 상세 (항목 포함)",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="templateId", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\Response(response=200, description="조회 성공",
|
||||
* @OA\JsonContent(allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(@OA\Property(property="data", ref="#/components/schemas/BomTemplate"))
|
||||
* })
|
||||
* ),
|
||||
* @OA\Response(response=404, description="없음", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function show() {}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/v1/design/bom-templates/{templateId}/items",
|
||||
* tags={"BomTemplate"},
|
||||
* summary="BOM 항목 일괄 치환",
|
||||
* description="기존 항목을 모두 삭제 후 본문 items로 재삽입",
|
||||
* security={{"ApiKeyAuth": {}},{"BearerAuth": {}}},
|
||||
* @OA\Parameter(name="templateId", in="path", required=true, @OA\Schema(type="integer")),
|
||||
* @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/BomTemplateItemReplaceRequest")),
|
||||
* @OA\Response(response=200, description="저장 성공", @OA\JsonContent(ref="#/components/schemas/ApiResponse")),
|
||||
* @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse"))
|
||||
* )
|
||||
*/
|
||||
public function replaceItems() {}
|
||||
}
|
||||
Reference in New Issue
Block a user