feat: [문서스냅샷] Lazy Snapshot API - snapshot 엔드포인트 + resolve에 snapshot_document_id 추가

- PATCH /documents/{id}/snapshot: canEdit 체크 없이 rendered_html만 업데이트
- DocumentService::patchSnapshot() 메서드 추가
- WorkOrderService::resolveInspectionDocument()에 snapshot_document_id 반환 (상태 무관, rendered_html NULL인 문서)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 20:59:17 +09:00
parent 5ebf940873
commit c5d5b5d076
4 changed files with 54 additions and 0 deletions

View File

@@ -74,6 +74,22 @@ public function destroy(int $id): JsonResponse
}, __('message.deleted'));
}
/**
* rendered_html 스냅샷 저장 (Lazy Snapshot)
* PATCH /v1/documents/{id}/snapshot
*/
public function patchSnapshot(int $id, UpdateRequest $request): JsonResponse
{
return ApiResponse::handle(function () use ($id, $request) {
$renderedHtml = $request->validated()['rendered_html'] ?? null;
if (! $renderedHtml) {
throw new \Symfony\Component\HttpKernel\Exception\BadRequestHttpException('rendered_html is required');
}
return $this->service->patchSnapshot($id, $renderedHtml);
}, __('message.updated'));
}
// =========================================================================
// FQC 일괄생성 (제품검사)
// =========================================================================

View File

@@ -718,6 +718,28 @@ public function fqcStatus(int $orderId, int $templateId): array
];
}
// =========================================================================
// Snapshot (Lazy Snapshot)
// =========================================================================
/**
* rendered_html만 업데이트 (상태 무관, canEdit 체크 없음)
* Lazy Snapshot: 조회 시 rendered_html이 없으면 프론트에서 캡처 후 저장
*/
public function patchSnapshot(int $id, string $renderedHtml): Document
{
$tenantId = $this->tenantId();
$document = Document::query()
->where('tenant_id', $tenantId)
->findOrFail($id);
$document->rendered_html = $renderedHtml;
$document->save();
return $document;
}
// =========================================================================
// Resolve/Upsert (React 연동용)
// =========================================================================

View File

@@ -2431,11 +2431,26 @@ public function resolveInspectionDocument(int $workOrderId, array $params = []):
->latest()
->first();
// Lazy Snapshot 대상: rendered_html이 없는 문서 (상태 무관)
$snapshotDocumentId = null;
$snapshotCandidate = Document::query()
->where('tenant_id', $tenantId)
->where('template_id', $templateId)
->where('linkable_type', 'work_order')
->where('linkable_id', $workOrderId)
->whereNull('rendered_html')
->latest()
->value('id');
if ($snapshotCandidate) {
$snapshotDocumentId = $snapshotCandidate;
}
return [
'work_order_id' => $workOrderId,
'template_id' => $templateId,
'template' => $formattedTemplate,
'existing_document' => $existingDocument,
'snapshot_document_id' => $snapshotDocumentId,
'work_order_info' => $this->buildWorkOrderInfo($workOrder),
];
}

View File

@@ -33,6 +33,7 @@
Route::get('/{id}', [DocumentController::class, 'show'])->whereNumber('id')->name('v1.documents.show');
Route::post('/', [DocumentController::class, 'store'])->name('v1.documents.store');
Route::patch('/{id}', [DocumentController::class, 'update'])->whereNumber('id')->name('v1.documents.update');
Route::patch('/{id}/snapshot', [DocumentController::class, 'patchSnapshot'])->whereNumber('id')->name('v1.documents.snapshot');
Route::delete('/{id}', [DocumentController::class, 'destroy'])->whereNumber('id')->name('v1.documents.destroy');
// 결재 워크플로우