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(); } }