Merge branch 'develop' of http://114.203.209.83:3000/SamProject/sam-manage into develop
This commit is contained in:
@@ -12,4 +12,4 @@ ## 최근 커밋 이력 (참고용)
|
||||
|
||||
## 다음 단계 (필요 시)
|
||||
- API Explorer Phase 2-5 (API 실행, 즐겨찾기, 히스토리, UX 개선)
|
||||
- MNG 견적수식 관리 UI 개발 (`docs/plans/mng-quote-formula-development-plan.md`)
|
||||
- MNG 견적수식 관리 UI 개발 (`docs/dev/dev_plans/mng-quote-formula-development-plan.md`)
|
||||
|
||||
@@ -19,7 +19,7 @@ class PptxController extends Controller
|
||||
'/var/www/docs/rules' => ['label' => '정책/규칙', 'source' => 'docs'],
|
||||
'/var/www/docs/guides' => ['label' => '가이드', 'source' => 'docs'],
|
||||
'/var/www/docs/projects' => ['label' => '프로젝트', 'source' => 'docs'],
|
||||
'/var/www/docs/plans' => ['label' => '계획', 'source' => 'docs'],
|
||||
'/var/www/docs/dev_plans' => ['label' => '계획', 'source' => 'docs'],
|
||||
'/var/www/mng/docs/pptx-output' => ['label' => '산출물', 'source' => 'mng'],
|
||||
'/var/www/mng/docs' => ['label' => '교육/문서', 'source' => 'mng'],
|
||||
'/var/www/mng/public/docs' => ['label' => '공개 문서', 'source' => 'mng'],
|
||||
|
||||
@@ -271,6 +271,22 @@ public function show(int $id): View
|
||||
// 기본정보 bf_ 자동 backfill
|
||||
$this->resolveAndBackfillBasicFields($document);
|
||||
|
||||
// 절곡 작업일지용: bending_info 추출
|
||||
$bendingInfo = null;
|
||||
if ($workOrder) {
|
||||
$woOptions = json_decode($workOrder->options ?? '{}', true);
|
||||
$bendingInfo = $woOptions['bending_info'] ?? null;
|
||||
}
|
||||
|
||||
// 절곡 중간검사용: inspection_data 스냅샷 추출 (work_order_items.options.inspection_data)
|
||||
$inspectionData = null;
|
||||
foreach ($workOrderItems as $item) {
|
||||
if (! empty($item->options['inspection_data'])) {
|
||||
$inspectionData = $item->options['inspection_data'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return view('documents.show', [
|
||||
'document' => $document,
|
||||
'workOrderItems' => $workOrderItems,
|
||||
@@ -279,6 +295,8 @@ public function show(int $id): View
|
||||
'materialInputLots' => $materialInputLots,
|
||||
'itemLotMap' => $itemLotMap ?? collect(),
|
||||
'blockHtml' => $this->renderBlockHtml($document->template, $document, 'view'),
|
||||
'bendingInfo' => $bendingInfo,
|
||||
'inspectionData' => $inspectionData,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ class DocumentTemplateSection extends Model
|
||||
protected $fillable = [
|
||||
'template_id',
|
||||
'title',
|
||||
'description',
|
||||
'image_path',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
188
database/seeders/ProductInspectionRequestTemplateSeeder.php
Normal file
188
database/seeders/ProductInspectionRequestTemplateSeeder.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\DocumentTemplate;
|
||||
use App\Models\DocumentTemplateApprovalLine;
|
||||
use App\Models\DocumentTemplateBasicField;
|
||||
use App\Models\DocumentTemplateColumn;
|
||||
use App\Models\DocumentTemplateSection;
|
||||
use App\Models\DocumentTemplateSectionItem;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ProductInspectionRequestTemplateSeeder extends Seeder
|
||||
{
|
||||
private int $tenantId = 287;
|
||||
|
||||
private int $templateId = 66;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$def = $this->getTemplateDefinition();
|
||||
|
||||
$template = DocumentTemplate::updateOrCreate(
|
||||
['id' => $this->templateId, 'tenant_id' => $this->tenantId],
|
||||
[
|
||||
'name' => $def['name'],
|
||||
'category' => '품질/제품검사',
|
||||
'title' => $def['title'],
|
||||
'company_name' => '케이디산업',
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->cleanupRelations($template->id);
|
||||
|
||||
$this->createApprovalLines($template->id);
|
||||
$this->createBasicFields($template->id);
|
||||
|
||||
foreach ($def['sections'] as $i => $sectionDef) {
|
||||
$this->createSection($template->id, $sectionDef, $i + 1);
|
||||
}
|
||||
|
||||
$this->createColumns($template->id, $def['columns']);
|
||||
|
||||
$this->command->info("제품검사 요청서 (ID: {$template->id})");
|
||||
}
|
||||
|
||||
private function getTemplateDefinition(): array
|
||||
{
|
||||
return [
|
||||
'name' => '제품검사 요청서',
|
||||
'title' => '제 품 검 사 요 청 서',
|
||||
'sections' => [
|
||||
[
|
||||
'title' => '건축공사장 정보',
|
||||
'items' => [
|
||||
['item' => '현장명', 'measurement_type' => 'text_input'],
|
||||
['item' => '대지위치', 'measurement_type' => 'text_input'],
|
||||
['item' => '지번', 'measurement_type' => 'text_input'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => '자재유통업자 정보',
|
||||
'items' => [
|
||||
['item' => '회사명', 'measurement_type' => 'text_input'],
|
||||
['item' => '주소', 'measurement_type' => 'text_input'],
|
||||
['item' => '대표자', 'measurement_type' => 'text_input'],
|
||||
['item' => '전화번호', 'measurement_type' => 'text_input'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => '공사시공자 정보',
|
||||
'items' => [
|
||||
['item' => '회사명', 'measurement_type' => 'text_input'],
|
||||
['item' => '주소', 'measurement_type' => 'text_input'],
|
||||
['item' => '성명', 'measurement_type' => 'text_input'],
|
||||
['item' => '전화번호', 'measurement_type' => 'text_input'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => '공사감리자 정보',
|
||||
'items' => [
|
||||
['item' => '사무소명', 'measurement_type' => 'text_input'],
|
||||
['item' => '주소', 'measurement_type' => 'text_input'],
|
||||
['item' => '성명', 'measurement_type' => 'text_input'],
|
||||
['item' => '전화번호', 'measurement_type' => 'text_input'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => '검사대상 사전 고지 정보',
|
||||
'description' => '발주 사이즈와 시공 완료된 사이즈가 다를 시, 일정 범위를 벗어날 경우 인정마크를 부착할 수 없습니다. 제품검사를 위한 방문 전 미리 변경사항을 고지해주셔야 인정마크를 부착할 수 있습니다. (사전고지를 하지 않음으로 발생하는 문제의 귀책은 신청업체에 있습니다.)',
|
||||
'items' => [],
|
||||
],
|
||||
],
|
||||
// 사전 고지 정보 테이블 컬럼
|
||||
'columns' => [
|
||||
['label' => 'No.', 'column_type' => 'text', 'width' => '40px', 'sort_order' => 1],
|
||||
['label' => '층수', 'column_type' => 'text', 'width' => '60px', 'sort_order' => 2],
|
||||
['label' => '부호', 'column_type' => 'text', 'width' => '60px', 'sort_order' => 3],
|
||||
['label' => '발주 가로', 'column_type' => 'text', 'width' => '70px', 'sort_order' => 4, 'group_name' => '오픈사이즈(발주규격)'],
|
||||
['label' => '발주 세로', 'column_type' => 'text', 'width' => '70px', 'sort_order' => 5, 'group_name' => '오픈사이즈(발주규격)'],
|
||||
['label' => '시공 가로', 'column_type' => 'text', 'width' => '70px', 'sort_order' => 6, 'group_name' => '오픈사이즈(시공후규격)'],
|
||||
['label' => '시공 세로', 'column_type' => 'text', 'width' => '70px', 'sort_order' => 7, 'group_name' => '오픈사이즈(시공후규격)'],
|
||||
['label' => '변경사유', 'column_type' => 'text', 'width' => '1fr', 'sort_order' => 8],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function createApprovalLines(int $templateId): void
|
||||
{
|
||||
$lines = [
|
||||
['name' => '작성', 'dept' => '품질', 'role' => '담당자', 'sort_order' => 1],
|
||||
['name' => '승인', 'dept' => '경영', 'role' => '대표', 'sort_order' => 2],
|
||||
];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
DocumentTemplateApprovalLine::create(array_merge(
|
||||
['template_id' => $templateId],
|
||||
$line,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private function createBasicFields(int $templateId): void
|
||||
{
|
||||
$fields = [
|
||||
['label' => '수주처', 'field_key' => 'client', 'field_type' => 'text', 'sort_order' => 1],
|
||||
['label' => '업체명', 'field_key' => 'company_name', 'field_type' => 'text', 'sort_order' => 2],
|
||||
['label' => '담당자', 'field_key' => 'manager', 'field_type' => 'text', 'sort_order' => 3],
|
||||
['label' => '수주번호', 'field_key' => 'order_number', 'field_type' => 'text', 'sort_order' => 4],
|
||||
['label' => '담당자 연락처', 'field_key' => 'manager_contact', 'field_type' => 'text', 'sort_order' => 5],
|
||||
['label' => '현장명', 'field_key' => 'site_name', 'field_type' => 'text', 'sort_order' => 6],
|
||||
['label' => '납품일', 'field_key' => 'delivery_date', 'field_type' => 'date', 'sort_order' => 7],
|
||||
['label' => '현장 주소', 'field_key' => 'site_address', 'field_type' => 'text', 'sort_order' => 8],
|
||||
['label' => '총 개소', 'field_key' => 'total_locations', 'field_type' => 'number', 'sort_order' => 9],
|
||||
['label' => '접수일', 'field_key' => 'receipt_date', 'field_type' => 'date', 'sort_order' => 10],
|
||||
['label' => '검사방문요청일', 'field_key' => 'inspection_request_date', 'field_type' => 'date', 'sort_order' => 11],
|
||||
];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
DocumentTemplateBasicField::create(array_merge(
|
||||
['template_id' => $templateId],
|
||||
$field,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private function createSection(int $templateId, array $sectionDef, int $sortOrder): void
|
||||
{
|
||||
$section = DocumentTemplateSection::create([
|
||||
'template_id' => $templateId,
|
||||
'title' => $sectionDef['title'],
|
||||
'description' => $sectionDef['description'] ?? null,
|
||||
'sort_order' => $sortOrder,
|
||||
]);
|
||||
|
||||
foreach ($sectionDef['items'] as $i => $item) {
|
||||
DocumentTemplateSectionItem::create(array_merge(
|
||||
['section_id' => $section->id, 'sort_order' => $i + 1],
|
||||
$item,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private function createColumns(int $templateId, array $columns): void
|
||||
{
|
||||
foreach ($columns as $col) {
|
||||
DocumentTemplateColumn::create(array_merge(
|
||||
['template_id' => $templateId],
|
||||
$col,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupRelations(int $templateId): void
|
||||
{
|
||||
DocumentTemplateColumn::where('template_id', $templateId)->delete();
|
||||
|
||||
$sections = DocumentTemplateSection::where('template_id', $templateId)->get();
|
||||
foreach ($sections as $section) {
|
||||
DocumentTemplateSectionItem::where('section_id', $section->id)->delete();
|
||||
}
|
||||
DocumentTemplateSection::where('template_id', $templateId)->delete();
|
||||
|
||||
DocumentTemplateBasicField::where('template_id', $templateId)->delete();
|
||||
DocumentTemplateApprovalLine::where('template_id', $templateId)->delete();
|
||||
}
|
||||
}
|
||||
@@ -1940,7 +1940,7 @@ ## 11. 참고 자료
|
||||
|
||||
### 11.1 관련 문서
|
||||
|
||||
- `docs/data/견적/견적시스템_분석문서.md` - 견적 시스템 전체 분석
|
||||
- `docs/dev/data/견적/견적시스템_분석문서.md` - 견적 시스템 전체 분석
|
||||
- `design/src/components/FormulaManagement2.tsx` - 프론트엔드 수식 관리 참조
|
||||
- `SAM_QUICK_REFERENCE.md` - SAM 개발 규칙
|
||||
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
{{--
|
||||
절곡 중간검사 DATA 전용 렌더링
|
||||
React BendingInspectionContent.tsx와 동일한 구조
|
||||
|
||||
데이터 소스: work_order_items.options.inspection_data (스냅샷)
|
||||
- 제품 구조, 도면치수, 측정값이 모두 포함된 완전한 스냅샷
|
||||
- 정책 변경 시에도 저장 시점의 문서 그대로 유지
|
||||
|
||||
필요 변수:
|
||||
- $inspectionData: inspection_data 스냅샷 (array|null)
|
||||
- $docData: document_data collection (fallback용)
|
||||
- $document: Document model
|
||||
--}}
|
||||
|
||||
@php
|
||||
// inspection_data 스냅샷에서 제품 목록 로드
|
||||
$products = $inspectionData['products'] ?? [];
|
||||
$snapshotJudgment = $inspectionData['judgment'] ?? null;
|
||||
$inadequateContent = $inspectionData['inadequateContent'] ?? '';
|
||||
|
||||
// 스냅샷이 없으면 INITIAL_PRODUCTS 하드코딩 fallback (레거시 호환)
|
||||
if (empty($products)) {
|
||||
$products = [
|
||||
['id' => 'guide-rail-wall', 'category' => 'KWE01', 'productName' => '가이드레일', 'productType' => '벽면형',
|
||||
'lengthDesignValue' => '3000', 'widthDesignValue' => 'N/A', 'bendingStatus' => null, 'lengthMeasured' => '', 'widthMeasured' => '',
|
||||
'gapPoints' => [
|
||||
['point' => '①', 'designValue' => '30', 'measured' => ''],
|
||||
['point' => '②', 'designValue' => '80', 'measured' => ''],
|
||||
['point' => '③', 'designValue' => '45', 'measured' => ''],
|
||||
['point' => '④', 'designValue' => '40', 'measured' => ''],
|
||||
['point' => '⑤', 'designValue' => '34', 'measured' => ''],
|
||||
]],
|
||||
['id' => 'guide-rail-side', 'category' => 'KWE01', 'productName' => '가이드레일', 'productType' => '측면형',
|
||||
'lengthDesignValue' => '3000', 'widthDesignValue' => 'N/A', 'bendingStatus' => null, 'lengthMeasured' => '', 'widthMeasured' => '',
|
||||
'gapPoints' => [
|
||||
['point' => '①', 'designValue' => '28', 'measured' => ''],
|
||||
['point' => '②', 'designValue' => '75', 'measured' => ''],
|
||||
['point' => '③', 'designValue' => '42', 'measured' => ''],
|
||||
['point' => '④', 'designValue' => '38', 'measured' => ''],
|
||||
['point' => '⑤', 'designValue' => '32', 'measured' => ''],
|
||||
]],
|
||||
['id' => 'case', 'category' => 'KWE01', 'productName' => '케이스', 'productType' => '500X380',
|
||||
'lengthDesignValue' => '3000', 'widthDesignValue' => 'N/A', 'bendingStatus' => null, 'lengthMeasured' => '', 'widthMeasured' => '',
|
||||
'gapPoints' => [
|
||||
['point' => '①', 'designValue' => '380', 'measured' => ''],
|
||||
['point' => '②', 'designValue' => '50', 'measured' => ''],
|
||||
['point' => '③', 'designValue' => '240', 'measured' => ''],
|
||||
['point' => '④', 'designValue' => '50', 'measured' => ''],
|
||||
]],
|
||||
['id' => 'bottom-finish', 'category' => 'KWE01', 'productName' => '하단마감재', 'productType' => '60X40',
|
||||
'lengthDesignValue' => '3000', 'widthDesignValue' => 'N/A', 'bendingStatus' => null, 'lengthMeasured' => '', 'widthMeasured' => '',
|
||||
'gapPoints' => [
|
||||
['point' => '①', 'designValue' => '60', 'measured' => ''],
|
||||
['point' => '②', 'designValue' => '64', 'measured' => ''],
|
||||
]],
|
||||
['id' => 'bottom-l-bar', 'category' => 'KWE01', 'productName' => '하단L-BAR', 'productType' => '17X60',
|
||||
'lengthDesignValue' => '3000', 'widthDesignValue' => 'N/A', 'bendingStatus' => null, 'lengthMeasured' => '', 'widthMeasured' => '',
|
||||
'gapPoints' => [
|
||||
['point' => '①', 'designValue' => '17', 'measured' => ''],
|
||||
]],
|
||||
['id' => 'smoke-w50', 'category' => 'KWE01', 'productName' => '연기차단재', 'productType' => "W50\n가이드레일용",
|
||||
'lengthDesignValue' => '3000', 'widthDesignValue' => '', 'bendingStatus' => null, 'lengthMeasured' => '', 'widthMeasured' => '',
|
||||
'gapPoints' => [
|
||||
['point' => '①', 'designValue' => '50', 'measured' => ''],
|
||||
['point' => '②', 'designValue' => '12', 'measured' => ''],
|
||||
]],
|
||||
['id' => 'smoke-w80', 'category' => 'KWE01', 'productName' => '연기차단재', 'productType' => "W80\n케이스용",
|
||||
'lengthDesignValue' => '3000', 'widthDesignValue' => '', 'bendingStatus' => null, 'lengthMeasured' => '', 'widthMeasured' => '',
|
||||
'gapPoints' => [
|
||||
['point' => '①', 'designValue' => '80', 'measured' => ''],
|
||||
['point' => '②', 'designValue' => '12', 'measured' => ''],
|
||||
]],
|
||||
];
|
||||
}
|
||||
|
||||
// 제품 ID → 메타 정보 매핑 (스냅샷에 category/productName/productType가 없을 경우 보완)
|
||||
$productMeta = [
|
||||
'guide-rail-wall' => ['category' => 'KWE01', 'productName' => '가이드레일', 'productType' => '벽면형'],
|
||||
'guide_rail_wall' => ['category' => 'KWE01', 'productName' => '가이드레일', 'productType' => '벽면형'],
|
||||
'guide-rail-side' => ['category' => 'KWE01', 'productName' => '가이드레일', 'productType' => '측면형'],
|
||||
'guide_rail_side' => ['category' => 'KWE01', 'productName' => '가이드레일', 'productType' => '측면형'],
|
||||
'case' => ['category' => 'KWE01', 'productName' => '케이스', 'productType' => '500X380'],
|
||||
'bottom-finish' => ['category' => 'KWE01', 'productName' => '하단마감재', 'productType' => '60X40'],
|
||||
'bottom_finish' => ['category' => 'KWE01', 'productName' => '하단마감재', 'productType' => '60X40'],
|
||||
'bottom-l-bar' => ['category' => 'KWE01', 'productName' => '하단L-BAR', 'productType' => '17X60'],
|
||||
'bottom_l_bar' => ['category' => 'KWE01', 'productName' => '하단L-BAR', 'productType' => '17X60'],
|
||||
'smoke-w50' => ['category' => 'KWE01', 'productName' => '연기차단재', 'productType' => "W50\n가이드레일용"],
|
||||
'smoke_w50' => ['category' => 'KWE01', 'productName' => '연기차단재', 'productType' => "W50\n가이드레일용"],
|
||||
'smoke-w80' => ['category' => 'KWE01', 'productName' => '연기차단재', 'productType' => "W80\n케이스용"],
|
||||
'smoke_w80' => ['category' => 'KWE01', 'productName' => '연기차단재', 'productType' => "W80\n케이스용"],
|
||||
];
|
||||
|
||||
// 종합판정 결정
|
||||
$overallResult = $snapshotJudgment ?? ($docData->where('field_key', 'overall_result')->first()?->field_value ?? '');
|
||||
$isOverallPass = in_array(strtolower($overallResult), ['pass', 'ok', '적합', '합격']);
|
||||
@endphp
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">■ 중간검사 DATA</h2>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full border border-gray-300 text-sm">
|
||||
<thead class="bg-gray-50">
|
||||
{{-- 1단 헤더 --}}
|
||||
<tr>
|
||||
<th rowspan="2" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300 w-20">분류</th>
|
||||
<th rowspan="2" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300 w-24">제품명</th>
|
||||
<th rowspan="2" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300 w-24">타입</th>
|
||||
<th rowspan="2" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300 w-24">겉모양/<br>절곡상태</th>
|
||||
<th colspan="2" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300">길이</th>
|
||||
<th colspan="2" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300">너비</th>
|
||||
<th colspan="3" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300">간격</th>
|
||||
<th rowspan="2" class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gray-300 w-20">판정</th>
|
||||
</tr>
|
||||
{{-- 2단 헤더 (서브라벨) --}}
|
||||
<tr>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 border border-gray-300 bg-gray-50">도면치수</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 border border-gray-300 bg-gray-50">측정값</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 border border-gray-300 bg-gray-50">도면치수</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 border border-gray-300 bg-gray-50">측정값</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 border border-gray-300 bg-gray-50">포인트</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 border border-gray-300 bg-gray-50">도면치수</th>
|
||||
<th class="px-2 py-1 text-center text-xs font-medium text-gray-500 border border-gray-300 bg-gray-50">측정값</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($products as $pIdx => $product)
|
||||
@php
|
||||
$gapPoints = $product['gapPoints'] ?? [];
|
||||
$gapCount = count($gapPoints);
|
||||
if ($gapCount === 0) $gapCount = 1;
|
||||
|
||||
$productId = $product['id'] ?? '';
|
||||
$meta = $productMeta[$productId] ?? [];
|
||||
|
||||
$category = $product['category'] ?? ($meta['category'] ?? '');
|
||||
$productName = $product['productName'] ?? ($meta['productName'] ?? $productId);
|
||||
$productType = $product['productType'] ?? ($meta['productType'] ?? '');
|
||||
|
||||
// 스냅샷 필드 (React 저장 형식: lengthDesignValue, lengthMeasured 등)
|
||||
$lengthDesign = $product['lengthDesignValue'] ?? ($product['lengthDesign'] ?? '');
|
||||
$lengthMeasured = $product['lengthMeasured'] ?? '';
|
||||
$widthDesign = $product['widthDesignValue'] ?? ($product['widthDesign'] ?? '');
|
||||
$widthMeasured = $product['widthMeasured'] ?? '';
|
||||
|
||||
// 절곡상태
|
||||
$statusVal = $product['bendingStatus'] ?? null;
|
||||
|
||||
// 판정: 양호→적, 불량→부
|
||||
$judgmentVal = null;
|
||||
if ($statusVal === '양호' || strtolower($statusVal ?? '') === 'pass' || strtolower($statusVal ?? '') === 'ok') {
|
||||
$judgmentVal = '적';
|
||||
} elseif ($statusVal === '불량' || strtolower($statusVal ?? '') === 'fail' || strtolower($statusVal ?? '') === 'ng') {
|
||||
$judgmentVal = '부';
|
||||
}
|
||||
@endphp
|
||||
|
||||
@for($gIdx = 0; $gIdx < max($gapCount, 1); $gIdx++)
|
||||
<tr class="hover:bg-gray-50">
|
||||
@if($gIdx === 0)
|
||||
{{-- 분류 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-xs text-gray-700 align-middle">
|
||||
{{ $category }}
|
||||
</td>
|
||||
{{-- 제품명 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-xs font-medium text-gray-900 align-middle">
|
||||
{{ $productName }}
|
||||
</td>
|
||||
{{-- 타입 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-xs text-gray-700 align-middle whitespace-pre-line">
|
||||
{{ $productType }}
|
||||
</td>
|
||||
{{-- 겉모양/절곡상태 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-sm align-middle">
|
||||
@if($statusVal === '양호' || strtolower($statusVal ?? '') === 'pass' || strtolower($statusVal ?? '') === 'ok')
|
||||
<span class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-green-100 border border-green-300">
|
||||
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
</span>
|
||||
@elseif($statusVal === '불량' || strtolower($statusVal ?? '') === 'fail' || strtolower($statusVal ?? '') === 'ng')
|
||||
<span class="inline-flex items-center justify-center w-7 h-7 rounded-full bg-red-100 border border-red-300">
|
||||
<svg class="w-5 h-5 text-red-600" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-400">-</span>
|
||||
@endif
|
||||
</td>
|
||||
{{-- 길이 도면치수 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-sm text-gray-500 align-middle">
|
||||
{{ $lengthDesign ?: '-' }}
|
||||
</td>
|
||||
{{-- 길이 측정값 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-sm font-mono align-middle">
|
||||
{{ $lengthMeasured ?: '-' }}
|
||||
</td>
|
||||
{{-- 너비 도면치수 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-sm text-gray-500 align-middle">
|
||||
{{ $widthDesign ?: '-' }}
|
||||
</td>
|
||||
{{-- 너비 측정값 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-sm font-mono align-middle">
|
||||
{{ $widthMeasured ?: '-' }}
|
||||
</td>
|
||||
@endif
|
||||
|
||||
{{-- 간격 포인트 --}}
|
||||
@if(!empty($gapPoints))
|
||||
@php $gap = $gapPoints[$gIdx] ?? []; @endphp
|
||||
<td class="px-2 py-1.5 border border-gray-300 text-center text-xs text-gray-500">
|
||||
{{ $gap['point'] ?? '' }}
|
||||
</td>
|
||||
<td class="px-2 py-1.5 border border-gray-300 text-center text-sm text-gray-500">
|
||||
{{ $gap['designValue'] ?? '' }}
|
||||
</td>
|
||||
<td class="px-2 py-1.5 border border-gray-300 text-center text-sm font-mono">
|
||||
{{ ($gap['measured'] ?? '') ?: '-' }}
|
||||
</td>
|
||||
@else
|
||||
<td class="px-2 py-1.5 border border-gray-300 text-center text-gray-400">-</td>
|
||||
<td class="px-2 py-1.5 border border-gray-300 text-center text-gray-400">-</td>
|
||||
<td class="px-2 py-1.5 border border-gray-300 text-center text-gray-400">-</td>
|
||||
@endif
|
||||
|
||||
@if($gIdx === 0)
|
||||
{{-- 판정 --}}
|
||||
<td rowspan="{{ $gapCount }}" class="px-2 py-2 border border-gray-300 text-center text-sm align-middle">
|
||||
@if($judgmentVal === '적')
|
||||
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-bold bg-green-100 text-green-700 border border-green-300">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||
적
|
||||
</span>
|
||||
@elseif($judgmentVal === '부')
|
||||
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-md text-xs font-bold bg-red-100 text-red-700 border border-red-300">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
부
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-400">-</span>
|
||||
@endif
|
||||
</td>
|
||||
@endif
|
||||
</tr>
|
||||
@endfor
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- 종합판정 --}}
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
<span class="text-sm font-semibold text-gray-700">종합판정:</span>
|
||||
@if($overallResult)
|
||||
@if($isOverallPass)
|
||||
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-bold bg-green-100 text-green-700 border border-green-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
|
||||
적합
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-bold bg-red-100 text-red-700 border border-red-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
부적합
|
||||
</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-sm text-gray-400">미판정</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
671
resources/views/documents/partials/bending-worklog.blade.php
Normal file
671
resources/views/documents/partials/bending-worklog.blade.php
Normal file
@@ -0,0 +1,671 @@
|
||||
{{--
|
||||
절곡 작업일지 전용 렌더링
|
||||
React BendingWorkLogContent.tsx + bending/ sub-components와 동일 구조
|
||||
|
||||
필요 변수:
|
||||
- $document: Document model
|
||||
- $workOrder: work_orders row (stdClass)
|
||||
- $salesOrder: orders row (stdClass, nullable)
|
||||
- $workOrderItems: work_order_items collection
|
||||
- $bendingInfo: array (work_order.options.bending_info)
|
||||
- $itemLotMap: collection (work_order_item_id → lot_no)
|
||||
--}}
|
||||
|
||||
@php
|
||||
// ============================================================
|
||||
// 상수 (React utils.ts와 동일)
|
||||
// ============================================================
|
||||
$SUS_DENSITY = 7.93;
|
||||
$EGI_DENSITY = 7.85;
|
||||
$WALL_PART_WIDTH = 412;
|
||||
$SIDE_PART_WIDTH = 462;
|
||||
$WALL_BASE_HEIGHT = 80;
|
||||
$SIDE_BASE_HEIGHT = 130;
|
||||
$BASE_WIDTH = 135;
|
||||
$BOTTOM_BAR_WIDTH = 184;
|
||||
$EXTRA_FINISH_WIDTH = 238;
|
||||
$SMOKE_BARRIER_WIDTH = 26;
|
||||
$BOX_FINISH_MATERIAL = 'EGI 1.55T';
|
||||
$BOX_COVER_LENGTH = 1219;
|
||||
|
||||
$apiBaseUrl = rtrim(config('app.api_url', 'http://api.sam.kr'), '/');
|
||||
|
||||
// ============================================================
|
||||
// 유틸 함수 (React utils.ts 포팅)
|
||||
// ============================================================
|
||||
|
||||
$calcWeight = function(string $material, float $width, float $height) use ($SUS_DENSITY, $EGI_DENSITY): array {
|
||||
preg_match('/(\d+(\.\d+)?)/', $material, $m);
|
||||
$thickness = $m ? (float)$m[1] : 0;
|
||||
$isSUS = stripos($material, 'SUS') !== false;
|
||||
$density = $isSUS ? $SUS_DENSITY : $EGI_DENSITY;
|
||||
$volume_cm3 = ($thickness * $width * $height) / 1000;
|
||||
$weight_kg = ($volume_cm3 * $density) / 1000;
|
||||
return ['weight' => round($weight_kg, 2), 'type' => $isSUS ? 'SUS' : 'EGI'];
|
||||
};
|
||||
|
||||
$parseBaseDimension = function(?string $baseDimension, float $fallbackHeight) use ($BASE_WIDTH): array {
|
||||
if ($baseDimension) {
|
||||
$parts = array_map('intval', explode('*', $baseDimension));
|
||||
if (count($parts) === 2 && $parts[0] > 0 && $parts[1] > 0) {
|
||||
return ['width' => $parts[0], 'height' => $parts[1]];
|
||||
}
|
||||
}
|
||||
return ['width' => $BASE_WIDTH, 'height' => $fallbackHeight];
|
||||
};
|
||||
|
||||
// getMaterialMapping (React utils.ts 포팅)
|
||||
$getMaterialMapping = function(string $productCode, string $finishMaterial): array {
|
||||
if (in_array($productCode, ['KQTS01', 'KSS01', 'KSS02'])) {
|
||||
return [
|
||||
'guideRailFinish' => 'SUS 1.2T', 'bodyMaterial' => 'EGI 1.55T',
|
||||
'guideRailExtraFinish' => '', 'bottomBarFinish' => 'SUS 1.5T', 'bottomBarExtraFinish' => '없음',
|
||||
];
|
||||
}
|
||||
if ($productCode === 'KTE01') {
|
||||
$isSUS = $finishMaterial === 'SUS마감';
|
||||
return [
|
||||
'guideRailFinish' => 'EGI 1.55T', 'bodyMaterial' => 'EGI 1.55T',
|
||||
'guideRailExtraFinish' => $isSUS ? 'SUS 1.2T' : '',
|
||||
'bottomBarFinish' => 'EGI 1.55T', 'bottomBarExtraFinish' => $isSUS ? 'SUS 1.2T' : '없음',
|
||||
];
|
||||
}
|
||||
$isSUS = str_contains($finishMaterial ?? '', 'SUS');
|
||||
return [
|
||||
'guideRailFinish' => 'EGI 1.55T', 'bodyMaterial' => 'EGI 1.55T',
|
||||
'guideRailExtraFinish' => $isSUS ? 'SUS 1.2T' : '',
|
||||
'bottomBarFinish' => 'EGI 1.55T', 'bottomBarExtraFinish' => $isSUS ? 'SUS 1.2T' : '없음',
|
||||
];
|
||||
};
|
||||
|
||||
// 가이드레일 행 생성
|
||||
$buildGuideRailRows = function(array $lengthData, string $baseDimension, array $mapping, string $productCode, string $type) use ($calcWeight, $parseBaseDimension, $WALL_PART_WIDTH, $SIDE_PART_WIDTH, $WALL_BASE_HEIGHT, $SIDE_BASE_HEIGHT): array {
|
||||
$rows = [];
|
||||
$isWall = $type === 'wall';
|
||||
$partWidth = $isWall ? $WALL_PART_WIDTH : $SIDE_PART_WIDTH;
|
||||
$codePrefix = preg_replace('/\d+$/', '', $productCode) ?: 'KSS';
|
||||
$isSteel = $codePrefix === 'KTE';
|
||||
$isSUS = in_array($codePrefix, ['KSS', 'KQTS', 'KTE']);
|
||||
$finishPrefix = $isWall ? ($isSUS ? 'RS' : 'RE') : ($isSUS ? 'SS' : 'SE');
|
||||
$bodyPrefix = $isWall ? ($isSteel ? 'RT' : 'RM') : ($isSteel ? 'ST' : 'SM');
|
||||
|
||||
foreach ($lengthData as $ld) {
|
||||
if (($ld['quantity'] ?? 0) <= 0) continue;
|
||||
$len = $ld['length']; $qty = $ld['quantity'];
|
||||
|
||||
$fw = $calcWeight($mapping['guideRailFinish'], $partWidth, $len);
|
||||
$rows[] = ['partName' => '①②마감재', 'material' => $mapping['guideRailFinish'], 'length' => $len, 'quantity' => $qty, 'weight' => round($fw['weight'] * $qty, 2), 'lotPrefix' => $finishPrefix];
|
||||
|
||||
$bw = $calcWeight($mapping['bodyMaterial'], $partWidth, $len);
|
||||
$rows[] = ['partName' => '③본체', 'material' => $mapping['bodyMaterial'], 'length' => $len, 'quantity' => $qty, 'weight' => round($bw['weight'] * $qty, 2), 'lotPrefix' => $bodyPrefix];
|
||||
$rows[] = ['partName' => '④C형', 'material' => $mapping['bodyMaterial'], 'length' => $len, 'quantity' => $qty, 'weight' => round($bw['weight'] * $qty, 2), 'lotPrefix' => $isWall ? 'RC' : 'SC'];
|
||||
$rows[] = ['partName' => '⑤D형', 'material' => $mapping['bodyMaterial'], 'length' => $len, 'quantity' => $qty, 'weight' => round($bw['weight'] * $qty, 2), 'lotPrefix' => $isWall ? 'RD' : 'SD'];
|
||||
|
||||
if ($isWall && $mapping['guideRailExtraFinish']) {
|
||||
$ew = $calcWeight($mapping['guideRailExtraFinish'], $partWidth, $len);
|
||||
$rows[] = ['partName' => '⑥별도마감', 'material' => $mapping['guideRailExtraFinish'], 'length' => $len, 'quantity' => $qty, 'weight' => round($ew['weight'] * $qty, 2), 'lotPrefix' => 'YY'];
|
||||
}
|
||||
}
|
||||
|
||||
$totalQty = array_sum(array_column($lengthData, 'quantity'));
|
||||
if ($totalQty > 0) {
|
||||
$baseDim = $parseBaseDimension($baseDimension, $isWall ? $WALL_BASE_HEIGHT : $SIDE_BASE_HEIGHT);
|
||||
$baseW = $calcWeight('EGI 1.55T', $baseDim['width'], $baseDim['height']);
|
||||
$rows[] = ['partName' => '하부BASE', 'material' => 'EGI 1.55T', 'length' => 0, 'quantity' => $totalQty, 'weight' => round($baseW['weight'] * $totalQty, 2), 'lotPrefix' => 'XX', 'isBase' => true];
|
||||
}
|
||||
return $rows;
|
||||
};
|
||||
|
||||
// 하단마감재 행 생성
|
||||
$buildBottomBarRows = function(array $bottomBar, array $mapping, string $productCode) use ($calcWeight, $BOTTOM_BAR_WIDTH, $EXTRA_FINISH_WIDTH): array {
|
||||
$rows = [];
|
||||
$codePrefix = preg_replace('/\d+$/', '', $productCode) ?: 'KSS';
|
||||
$isSteel = $codePrefix === 'KTE';
|
||||
$lotPrefix = $isSteel ? 'TS' : (str_contains($mapping['bottomBarFinish'], 'SUS') ? 'BS' : 'BE');
|
||||
|
||||
foreach ([3000, 4000] as $len) {
|
||||
$qtyKey = "length{$len}Qty";
|
||||
$qty = $bottomBar[$qtyKey] ?? 0;
|
||||
if ($qty <= 0) continue;
|
||||
$w = $calcWeight($mapping['bottomBarFinish'], $BOTTOM_BAR_WIDTH, $len);
|
||||
$rows[] = ['partName' => '①하단마감재', 'material' => $mapping['bottomBarFinish'], 'length' => $len, 'quantity' => $qty, 'weight' => round($w['weight'] * $qty, 2), 'lotPrefix' => $lotPrefix];
|
||||
}
|
||||
|
||||
if ($mapping['bottomBarExtraFinish'] !== '없음' && $mapping['bottomBarExtraFinish']) {
|
||||
foreach ([3000, 4000] as $len) {
|
||||
$qtyKey = "length{$len}Qty";
|
||||
$qty = $bottomBar[$qtyKey] ?? 0;
|
||||
if ($qty <= 0) continue;
|
||||
$w = $calcWeight($mapping['bottomBarExtraFinish'], $EXTRA_FINISH_WIDTH, $len);
|
||||
$rows[] = ['partName' => '④별도마감재', 'material' => $mapping['bottomBarExtraFinish'], 'length' => $len, 'quantity' => $qty, 'weight' => round($w['weight'] * $qty, 2), 'lotPrefix' => 'YY'];
|
||||
}
|
||||
}
|
||||
return $rows;
|
||||
};
|
||||
|
||||
// 셔터박스 행 생성
|
||||
$buildShutterBoxRows = function(array $box) use ($calcWeight, $BOX_FINISH_MATERIAL, $BOX_COVER_LENGTH): array {
|
||||
$rows = [];
|
||||
$sizeParts = array_map('intval', explode('*', $box['size'] ?? '500*380'));
|
||||
$boxWidth = $sizeParts[0] ?: 500;
|
||||
$boxHeight = $sizeParts[1] ?: 380;
|
||||
$isStandard = $box['size'] === '500*380';
|
||||
$direction = $box['direction'] ?? '양면';
|
||||
|
||||
if ($isStandard) {
|
||||
$parts = [
|
||||
['name' => '①전면부', 'prefix' => 'CF', 'dim' => $boxHeight + 122],
|
||||
['name' => '②린텔부', 'prefix' => 'CL', 'dim' => $boxWidth - 330],
|
||||
['name' => '③⑤점검구', 'prefix' => 'CP', 'dim' => $boxWidth - 200],
|
||||
['name' => '④후면코너부', 'prefix' => 'CB', 'dim' => 170],
|
||||
];
|
||||
} elseif ($direction === '양면') {
|
||||
$parts = [
|
||||
['name' => '①전면부', 'prefix' => 'XX', 'dim' => $boxHeight + 122],
|
||||
['name' => '②린텔부', 'prefix' => 'CL', 'dim' => $boxWidth - 330],
|
||||
['name' => '③점검구', 'prefix' => 'XX', 'dim' => $boxWidth - 200],
|
||||
['name' => '④후면코너부', 'prefix' => 'CB', 'dim' => 170],
|
||||
['name' => '⑤점검구', 'prefix' => 'XX', 'dim' => $boxHeight - 100],
|
||||
];
|
||||
} elseif ($direction === '밑면') {
|
||||
$parts = [
|
||||
['name' => '①전면부', 'prefix' => 'XX', 'dim' => $boxHeight + 122],
|
||||
['name' => '②린텔부', 'prefix' => 'CL', 'dim' => $boxWidth - 330],
|
||||
['name' => '③점검구', 'prefix' => 'XX', 'dim' => $boxWidth - 200],
|
||||
['name' => '④후면부', 'prefix' => 'CB', 'dim' => $boxHeight + 85 * 2],
|
||||
];
|
||||
} elseif ($direction === '후면') {
|
||||
$parts = [
|
||||
['name' => '①전면부', 'prefix' => 'XX', 'dim' => $boxHeight + 122],
|
||||
['name' => '②린텔부', 'prefix' => 'CL', 'dim' => $boxWidth + 85 * 2],
|
||||
['name' => '③점검구', 'prefix' => 'XX', 'dim' => $boxHeight - 200],
|
||||
['name' => '④후면코너부', 'prefix' => 'CB', 'dim' => $boxHeight + 85 * 2],
|
||||
];
|
||||
} else {
|
||||
$parts = [];
|
||||
}
|
||||
|
||||
foreach ($parts as $p) {
|
||||
foreach ($box['lengthData'] ?? [] as $ld) {
|
||||
if (($ld['quantity'] ?? 0) <= 0) continue;
|
||||
$w = $calcWeight($BOX_FINISH_MATERIAL, $p['dim'], $ld['length']);
|
||||
$rows[] = ['partName' => $p['name'], 'material' => $BOX_FINISH_MATERIAL, 'dimension' => (string)$ld['length'], 'quantity' => $ld['quantity'], 'weight' => round($w['weight'] * $ld['quantity'], 2), 'lotPrefix' => $p['prefix']];
|
||||
}
|
||||
}
|
||||
|
||||
$coverQty = $box['coverQty'] ?? 0;
|
||||
if ($coverQty > 0) {
|
||||
$coverWidth = $boxWidth - 111;
|
||||
$w = $calcWeight($BOX_FINISH_MATERIAL, $coverWidth, $BOX_COVER_LENGTH);
|
||||
$coverName = $isStandard ? '⑤상부덮개' : ($direction === '양면' ? '⑥상부덮개' : '⑤상부덮개');
|
||||
$rows[] = ['partName' => $coverName, 'material' => $BOX_FINISH_MATERIAL, 'dimension' => "1219 * {$coverWidth}", 'quantity' => $coverQty, 'weight' => round($w['weight'] * $coverQty, 2), 'lotPrefix' => 'XX'];
|
||||
}
|
||||
|
||||
$finCoverQty = $box['finCoverQty'] ?? 0;
|
||||
if ($finCoverQty > 0) {
|
||||
$w = $calcWeight($BOX_FINISH_MATERIAL, $boxWidth, $boxHeight);
|
||||
$finName = $isStandard ? '⑥측면부(마구리)' : ($direction === '양면' ? '⑦측면부(마구리)' : '⑥측면부(마구리)');
|
||||
$rows[] = ['partName' => $finName, 'material' => $BOX_FINISH_MATERIAL, 'dimension' => ($boxWidth+5).' * '.($boxHeight+5), 'quantity' => $finCoverQty, 'weight' => round($w['weight'] * $finCoverQty, 2), 'lotPrefix' => 'XX'];
|
||||
}
|
||||
return $rows;
|
||||
};
|
||||
|
||||
// lengthToCode (React utils.ts getSLengthCode 포팅)
|
||||
$lengthToCode = function(int $length, string $category = ''): ?string {
|
||||
if ($category === '연기차단재50') return match($length) { 3000 => '53', 4000 => '54', default => null };
|
||||
if ($category === '연기차단재80') return match($length) { 3000 => '83', 4000 => '84', default => null };
|
||||
$map = [1219 => '12', 2438 => '24', 3000 => '30', 3500 => '35', 4000 => '40', 4150 => '41', 4200 => '42', 4300 => '43'];
|
||||
return $map[$length] ?? null;
|
||||
};
|
||||
|
||||
// 연기차단재 행 생성
|
||||
$buildSmokeBarrierRows = function(array $smokeBarrier) use ($calcWeight, $SMOKE_BARRIER_WIDTH, $lengthToCode): array {
|
||||
$rows = [];
|
||||
foreach ($smokeBarrier['w50'] ?? [] as $ld) {
|
||||
if (($ld['quantity'] ?? 0) <= 0) continue;
|
||||
$w = $calcWeight('EGI 0.8T', $SMOKE_BARRIER_WIDTH, $ld['length']);
|
||||
$code = $lengthToCode($ld['length'], '연기차단재50');
|
||||
$rows[] = ['partName' => '레일용 [W50]', 'material' => 'EGI 0.8T', 'length' => $ld['length'], 'quantity' => $ld['quantity'], 'weight' => round($w['weight'] * $ld['quantity'], 2), 'lotCode' => $code ? "GI-{$code}" : 'GI'];
|
||||
}
|
||||
$w80Qty = $smokeBarrier['w80Qty'] ?? 0;
|
||||
if ($w80Qty > 0) {
|
||||
$w = $calcWeight('EGI 0.8T', $SMOKE_BARRIER_WIDTH, 3000);
|
||||
$code = $lengthToCode(3000, '연기차단재80');
|
||||
$rows[] = ['partName' => '케이스용 [W80]', 'material' => 'EGI 0.8T', 'length' => 3000, 'quantity' => $w80Qty, 'weight' => round($w['weight'] * $w80Qty, 2), 'lotCode' => $code ? "GI-{$code}" : 'GI'];
|
||||
}
|
||||
return $rows;
|
||||
};
|
||||
|
||||
// 생산량 합계 계산
|
||||
$calculateProductionSummary = function(array $bi, array $mapping) use ($calcWeight, $parseBaseDimension, $buildShutterBoxRows, $buildSmokeBarrierRows, $WALL_PART_WIDTH, $SIDE_PART_WIDTH, $BOTTOM_BAR_WIDTH, $EXTRA_FINISH_WIDTH, $WALL_BASE_HEIGHT, $SIDE_BASE_HEIGHT): array {
|
||||
$susTotal = 0; $egiTotal = 0;
|
||||
$addWeight = function(string $mat, float $w, float $h, int $qty) use (&$susTotal, &$egiTotal, $calcWeight) {
|
||||
if ($qty <= 0) return;
|
||||
$r = $calcWeight($mat, $w, $h);
|
||||
$total = $r['weight'] * $qty;
|
||||
if ($r['type'] === 'SUS') $susTotal += $total; else $egiTotal += $total;
|
||||
};
|
||||
// 가이드레일 벽면형
|
||||
if (!empty($bi['guideRail']['wall'])) {
|
||||
foreach ($bi['guideRail']['wall']['lengthData'] ?? [] as $ld) {
|
||||
if (($ld['quantity'] ?? 0) <= 0) continue;
|
||||
$addWeight($mapping['guideRailFinish'], $WALL_PART_WIDTH, $ld['length'], $ld['quantity']);
|
||||
$addWeight($mapping['bodyMaterial'], $WALL_PART_WIDTH, $ld['length'], $ld['quantity'] * 3);
|
||||
if ($mapping['guideRailExtraFinish']) $addWeight($mapping['guideRailExtraFinish'], $WALL_PART_WIDTH, $ld['length'], $ld['quantity']);
|
||||
}
|
||||
$totalWallQty = array_sum(array_column($bi['guideRail']['wall']['lengthData'] ?? [], 'quantity'));
|
||||
$wallBase = $parseBaseDimension($bi['guideRail']['wall']['baseDimension'] ?? null, $WALL_BASE_HEIGHT);
|
||||
$addWeight('EGI 1.55T', $wallBase['width'], $wallBase['height'], $totalWallQty);
|
||||
}
|
||||
// 가이드레일 측면형
|
||||
if (!empty($bi['guideRail']['side'])) {
|
||||
foreach ($bi['guideRail']['side']['lengthData'] ?? [] as $ld) {
|
||||
if (($ld['quantity'] ?? 0) <= 0) continue;
|
||||
$addWeight($mapping['guideRailFinish'], $SIDE_PART_WIDTH, $ld['length'], $ld['quantity']);
|
||||
$addWeight($mapping['bodyMaterial'], $SIDE_PART_WIDTH, $ld['length'], $ld['quantity'] * 3);
|
||||
}
|
||||
$totalSideQty = array_sum(array_column($bi['guideRail']['side']['lengthData'] ?? [], 'quantity'));
|
||||
$sideBase = $parseBaseDimension($bi['guideRail']['side']['baseDimension'] ?? null, $SIDE_BASE_HEIGHT);
|
||||
$addWeight('EGI 1.55T', $sideBase['width'], $sideBase['height'], $totalSideQty);
|
||||
}
|
||||
// 하단마감재
|
||||
foreach ([3000, 4000] as $len) {
|
||||
$qty = $bi['bottomBar']["length{$len}Qty"] ?? 0;
|
||||
if ($qty > 0) {
|
||||
$addWeight($mapping['bottomBarFinish'], $BOTTOM_BAR_WIDTH, $len, $qty);
|
||||
if ($mapping['bottomBarExtraFinish'] !== '없음' && $mapping['bottomBarExtraFinish']) {
|
||||
$addWeight($mapping['bottomBarExtraFinish'], $EXTRA_FINISH_WIDTH, $len, $qty);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 셔터박스
|
||||
foreach ($bi['shutterBox'] ?? [] as $box) {
|
||||
foreach ($buildShutterBoxRows($box) as $row) {
|
||||
if ($row['weight'] > 0) $egiTotal += $row['weight'];
|
||||
}
|
||||
}
|
||||
// 연기차단재
|
||||
foreach ($buildSmokeBarrierRows($bi['smokeBarrier'] ?? []) as $row) {
|
||||
if ($row['weight'] > 0) $egiTotal += $row['weight'];
|
||||
}
|
||||
return ['susTotal' => round($susTotal, 2), 'egiTotal' => round($egiTotal, 2), 'grandTotal' => round($susTotal + $egiTotal, 2)];
|
||||
};
|
||||
|
||||
// 포맷 헬퍼
|
||||
$fmt = fn($v) => ($v !== null && $v > 0) ? number_format($v) : '-';
|
||||
$fmtWeight = fn($v) => $v > 0 ? number_format($v, 2) : '-';
|
||||
|
||||
// lookupLotNo (React utils.ts 포팅 - lotNoMap에서 LOT NO 조회)
|
||||
$lookupLotNo = function(array $lotNoMap, string $prefix, ?int $length = null) use ($lengthToCode): string {
|
||||
if (empty($lotNoMap)) return '-';
|
||||
// 1. 정확한 매칭 (prefix + lengthCode)
|
||||
if ($length) {
|
||||
$code = $lengthToCode($length);
|
||||
if ($code && isset($lotNoMap["BD-{$prefix}-{$code}"])) return $lotNoMap["BD-{$prefix}-{$code}"];
|
||||
}
|
||||
// 2. Fallback: prefix만으로 매칭
|
||||
$prefixKey = "BD-{$prefix}-";
|
||||
foreach ($lotNoMap as $k => $v) {
|
||||
if (str_starts_with($k, $prefixKey)) return $v;
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 데이터 준비
|
||||
// ============================================================
|
||||
$bi = $bendingInfo ?? [];
|
||||
$hasBendingData = !empty($bi['productCode']);
|
||||
$mapping = $hasBendingData ? $getMaterialMapping($bi['productCode'], $bi['finishMaterial'] ?? '') : null;
|
||||
$summary = ($hasBendingData && $mapping) ? $calculateProductionSummary($bi, $mapping) : ['susTotal' => 0, 'egiTotal' => 0, 'grandTotal' => 0];
|
||||
|
||||
// 날짜 포맷
|
||||
$today = now()->format('Y-m-d');
|
||||
$fullDate = now()->format('Y년 n월 j일');
|
||||
|
||||
// 기본 정보
|
||||
$documentNo = $workOrder->work_order_no ?? '-';
|
||||
$primaryAssignee = '-';
|
||||
// work_order_assignees에서 primary 찾기
|
||||
if ($workOrder) {
|
||||
$assignee = \Illuminate\Support\Facades\DB::table('work_order_assignees')
|
||||
->join('users', 'users.id', '=', 'work_order_assignees.user_id')
|
||||
->where('work_order_assignees.work_order_id', $workOrder->id)
|
||||
->where('work_order_assignees.is_primary', true)
|
||||
->select('users.name')
|
||||
->first();
|
||||
$primaryAssignee = $assignee->name ?? '-';
|
||||
}
|
||||
$dueDate = $salesOrder->delivery_date ?? $workOrder->scheduled_date ?? null;
|
||||
$formattedDueDate = $dueDate ? \Carbon\Carbon::parse($dueDate)->format('Y-m-d') : '-';
|
||||
|
||||
// 담당자(수주 작성자) 조회
|
||||
$salesWriterName = '-';
|
||||
if ($salesOrder && $salesOrder->writer_id) {
|
||||
$salesWriter = \Illuminate\Support\Facades\DB::table('users')->where('id', $salesOrder->writer_id)->first();
|
||||
$salesWriterName = $salesWriter->name ?? '-';
|
||||
}
|
||||
|
||||
// LOT NO (work_order에 lot_no 컬럼 없음 - work_order_no 활용)
|
||||
$lotNo = $workOrder->work_order_no ?? '-';
|
||||
|
||||
// lotNoMap 빌드: item.code (BD-{prefix}-{lengthCode}) → lot_no 매핑
|
||||
$lotNoMap = [];
|
||||
if ($workOrder) {
|
||||
$lotRows = \Illuminate\Support\Facades\DB::table('work_order_material_inputs as mi')
|
||||
->join('stock_lots as sl', 'sl.id', '=', 'mi.stock_lot_id')
|
||||
->join('items as i', 'i.id', '=', 'mi.item_id')
|
||||
->where('mi.work_order_id', $workOrder->id)
|
||||
->select('i.code', 'sl.lot_no')
|
||||
->distinct()
|
||||
->get();
|
||||
foreach ($lotRows as $lr) {
|
||||
if ($lr->code && !isset($lotNoMap[$lr->code])) {
|
||||
$lotNoMap[$lr->code] = $lr->lot_no;
|
||||
}
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
{{-- ===== 헤더 ===== --}}
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">작업일지 (절곡)</h1>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
문서번호: {{ $documentNo }} | 작성일자: {{ $fullDate }}
|
||||
</p>
|
||||
</div>
|
||||
{{-- 결재란 --}}
|
||||
<table class="border-collapse text-xs flex-shrink-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-gray-400 bg-gray-100 px-3 py-1 text-center">작성</th>
|
||||
<th class="border border-gray-400 bg-gray-100 px-3 py-1 text-center">검토</th>
|
||||
<th class="border border-gray-400 bg-gray-100 px-3 py-1 text-center">승인</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-400 px-3 py-3 text-center min-w-[60px]">{{ $primaryAssignee }}</td>
|
||||
<td class="border border-gray-400 px-3 py-3 text-center min-w-[60px]"></td>
|
||||
<td class="border border-gray-400 px-3 py-3 text-center min-w-[60px]"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ===== 신청업체 / 신청내용 ===== --}}
|
||||
<table class="w-full border-collapse text-xs mb-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border border-gray-400 bg-gray-100 px-3 py-2 text-center" colspan="2">신청업체</th>
|
||||
<th class="border border-gray-400 bg-gray-100 px-3 py-2 text-center" colspan="2">신청내용</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium w-24">수주일</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $salesOrder?->received_at ? \Carbon\Carbon::parse($salesOrder->received_at)->format('Y-m-d') : '-' }}</td>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium w-24">현장명</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $salesOrder->site_name ?? $workOrder->project_name ?? '-' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium">수주처</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $salesOrder->client_name ?? '-' }}</td>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium">작업일자</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $today }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium">담당자</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $salesWriterName }}</td>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium">제품 LOT NO</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $lotNo }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium">연락처</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $salesOrder->client_contact ?? '-' }}</td>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium">생산담당자</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $primaryAssignee }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium" colspan="2"></td>
|
||||
<td class="border border-gray-400 bg-gray-50 px-3 py-2 font-medium">출고예정일</td>
|
||||
<td class="border border-gray-400 px-3 py-2">{{ $formattedDueDate }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{-- ===== 제품 정보 ===== --}}
|
||||
<table class="w-full border-collapse text-xs mb-6">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-400 p-2">제품명</th>
|
||||
<th class="border border-gray-400 p-2">재질</th>
|
||||
<th class="border border-gray-400 p-2">마감</th>
|
||||
<th class="border border-gray-400 p-2">유형</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-400 p-2">{{ $hasBendingData ? $bi['productCode'] : '-' }}</td>
|
||||
<td class="border border-gray-400 p-2">{{ $mapping['bodyMaterial'] ?? '-' }}</td>
|
||||
<td class="border border-gray-400 p-2">{{ $hasBendingData ? $bi['finishMaterial'] : '-' }}</td>
|
||||
<td class="border border-gray-400 p-2">{{ $hasBendingData ? ($bi['common']['type'] ?? '-') : '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if($hasBendingData && $mapping)
|
||||
{{-- ===== 1. 가이드레일 ===== --}}
|
||||
@php
|
||||
$wallRows = !empty($bi['guideRail']['wall']) ? $buildGuideRailRows($bi['guideRail']['wall']['lengthData'] ?? [], $bi['guideRail']['wall']['baseDimension'] ?? '135*80', $mapping, $bi['productCode'], 'wall') : [];
|
||||
$sideRows = !empty($bi['guideRail']['side']) ? $buildGuideRailRows($bi['guideRail']['side']['lengthData'] ?? [], $bi['guideRail']['side']['baseDimension'] ?? '135*130', $mapping, $bi['productCode'], 'side') : [];
|
||||
@endphp
|
||||
@if(count($wallRows) > 0 || count($sideRows) > 0)
|
||||
<div class="mb-6">
|
||||
<div class="bg-gray-800 text-white text-xs font-bold px-3 py-1.5 mb-2">1. 가이드레일</div>
|
||||
|
||||
@foreach(['wall' => $wallRows, 'side' => $sideRows] as $grType => $grRows)
|
||||
@if(count($grRows) > 0)
|
||||
@php
|
||||
$grTitle = $grType === 'wall' ? '1.1 벽면형' : '1.2 측면형';
|
||||
$grData = $bi['guideRail'][$grType] ?? [];
|
||||
$baseSize = $grData['baseDimension'] ?? $grData['baseSize'] ?? '-';
|
||||
@endphp
|
||||
<div class="mb-4">
|
||||
<div class="text-xs font-bold mb-1">{{ $grTitle }}</div>
|
||||
<div class="flex-1">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-400 px-1 py-0.5">세부품명</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">재질</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">길이</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">수량</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">LOT NO</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">무게(kg)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($grRows as $row)
|
||||
<tr>
|
||||
<td class="border border-gray-400 px-1 py-0.5">{{ $row['partName'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $row['material'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ ($row['isBase'] ?? false) ? $baseSize : $fmt($row['length']) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmt($row['quantity']) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $lookupLotNo($lotNoMap, $row['lotPrefix'], $row['length'] ?? null) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmtWeight($row['weight']) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ===== 2. 하단마감재 ===== --}}
|
||||
@php
|
||||
$bottomRows = $buildBottomBarRows($bi['bottomBar'] ?? [], $mapping, $bi['productCode']);
|
||||
@endphp
|
||||
@if(count($bottomRows) > 0)
|
||||
<div class="mb-6">
|
||||
<div class="bg-gray-800 text-white text-xs font-bold px-3 py-1.5 mb-2">2. 하단마감재</div>
|
||||
<div class="flex-1">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-400 px-1 py-0.5">세부품명</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">재질</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">길이</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">수량</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">LOT NO</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">무게(kg)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($bottomRows as $row)
|
||||
<tr>
|
||||
<td class="border border-gray-400 px-1 py-0.5">{{ $row['partName'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $row['material'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmt($row['length']) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmt($row['quantity']) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $lookupLotNo($lotNoMap, $row['lotPrefix'], $row['length'] ?? null) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmtWeight($row['weight']) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ===== 3. 셔터박스 ===== --}}
|
||||
@if(!empty($bi['shutterBox']))
|
||||
<div class="mb-6">
|
||||
<div class="bg-gray-800 text-white text-xs font-bold px-3 py-1.5 mb-2">3. 셔터박스</div>
|
||||
@foreach($bi['shutterBox'] as $boxIdx => $box)
|
||||
@php $boxRows = $buildShutterBoxRows($box); @endphp
|
||||
@if(count($boxRows) > 0)
|
||||
<div class="mb-4">
|
||||
<div class="text-xs font-bold mb-1">3.{{ $boxIdx + 1 }} 셔터박스 [{{ $box['size'] ?? '-' }}] {{ $box['direction'] ?? '' }}</div>
|
||||
<div class="flex-1">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-400 px-1 py-0.5">구성요소</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">재질</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">길이/치수</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">수량</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">LOT NO</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">무게(kg)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($boxRows as $row)
|
||||
@php
|
||||
// 셔터박스 LOT: dimension에서 숫자 추출하여 길이코드로 변환
|
||||
$boxDimNum = is_numeric($row['dimension']) ? (int)$row['dimension'] : null;
|
||||
$boxLot = $boxDimNum ? $lookupLotNo($lotNoMap, $row['lotPrefix'], $boxDimNum) : $lookupLotNo($lotNoMap, $row['lotPrefix']);
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="border border-gray-400 px-1 py-0.5">{{ $row['partName'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $row['material'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $row['dimension'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmt($row['quantity']) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $boxLot }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmtWeight($row['weight']) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ===== 4. 연기차단재 ===== --}}
|
||||
@php
|
||||
$smokeRows = $buildSmokeBarrierRows($bi['smokeBarrier'] ?? []);
|
||||
@endphp
|
||||
@if(count($smokeRows) > 0)
|
||||
<div class="mb-6">
|
||||
<div class="bg-gray-800 text-white text-xs font-bold px-3 py-1.5 mb-2">4. 연기차단재</div>
|
||||
<div class="flex-1">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-400 px-1 py-0.5">파트</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">재질</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">길이</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">수량</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">LOT NO</th>
|
||||
<th class="border border-gray-400 px-1 py-0.5">무게(kg)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($smokeRows as $row)
|
||||
@php
|
||||
// 연기차단재 LOT: lotCode (GI-53, GI-83 등) 사용
|
||||
$smokeLotCode = $row['lotCode'] ?? 'GI';
|
||||
$smokeLot = isset($lotNoMap["BD-{$smokeLotCode}"]) ? $lotNoMap["BD-{$smokeLotCode}"] : $lookupLotNo($lotNoMap, explode('-', $smokeLotCode)[0], $row['length']);
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="border border-gray-400 px-1 py-0.5">{{ $row['partName'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $row['material'] }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmt($row['length']) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmt($row['quantity']) }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $smokeLot }}</td>
|
||||
<td class="border border-gray-400 px-1 py-0.5 text-center">{{ $fmtWeight($row['weight']) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="text-center text-gray-400 text-sm py-8 border border-gray-300 mb-6">
|
||||
절곡 데이터가 없습니다. (bending_info 미등록)
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ===== 생산량 합계 ===== --}}
|
||||
<div class="mb-6">
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-400 px-3 py-2">생산량 합계 [kg]</th>
|
||||
<th class="border border-gray-400 px-3 py-2">SUS</th>
|
||||
<th class="border border-gray-400 px-3 py-2">EGI</th>
|
||||
<th class="border border-gray-400 px-3 py-2">합계</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-400 px-3 py-2 text-center font-medium">무게</td>
|
||||
<td class="border border-gray-400 px-3 py-2 text-center">{{ $summary['susTotal'] > 0 ? number_format($summary['susTotal'], 2) . ' kg' : '-' }}</td>
|
||||
<td class="border border-gray-400 px-3 py-2 text-center">{{ $summary['egiTotal'] > 0 ? number_format($summary['egiTotal'], 2) . ' kg' : '-' }}</td>
|
||||
<td class="border border-gray-400 px-3 py-2 text-center font-bold">{{ $summary['grandTotal'] > 0 ? number_format($summary['grandTotal'], 2) . ' kg' : '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{-- ===== 비고 ===== --}}
|
||||
<table class="w-full border-collapse text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-40 align-top">비고</td>
|
||||
<td class="border border-gray-400 px-3 py-3 min-h-[60px]">
|
||||
{{ $workOrder->memo ?? '' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -25,6 +25,13 @@ class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg transiti
|
||||
{{-- 성적서 본문 --}}
|
||||
<div class="bg-white border border-gray-300 p-8 print:p-4 print:border-0" id="printArea">
|
||||
|
||||
{{-- HTML 스냅샷 우선 출력 (React에서 저장한 rendered_html) --}}
|
||||
@if($document->rendered_html)
|
||||
<div class="document-snapshot-container">
|
||||
{!! $document->rendered_html !!}
|
||||
</div>
|
||||
@else
|
||||
{{-- 레거시: 템플릿 기반 동적 렌더링 --}}
|
||||
@php
|
||||
$template = $document->template;
|
||||
$hasComplexCol = $template->columns->contains(fn($c) => $c->column_type === 'complex' && $c->sub_labels);
|
||||
@@ -364,6 +371,7 @@ class="doc-th"
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
@endif {{-- 스냅샷 vs 레거시 분기 끝 --}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@section('title', '문서 상세')
|
||||
|
||||
@section('content')
|
||||
@php $docData = $document->data ?? collect(); @endphp
|
||||
@php $docData = $document->getRelation('data') ?? $document->data()->get(); @endphp
|
||||
<!-- 헤더 -->
|
||||
<div class="flex flex-col lg:flex-row lg:justify-between lg:items-center gap-4 mb-6">
|
||||
<div>
|
||||
@@ -108,16 +108,24 @@ class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg transiti
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{{-- HTML 스냅샷 우선 출력 (React에서 저장한 rendered_html) --}}
|
||||
@if($document->rendered_html)
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<div class="document-snapshot-container">
|
||||
{!! $document->rendered_html !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 블록 빌더 서식: 블록 렌더러로 조회 --}}
|
||||
@if($document->template?->isBlockBuilder() && !empty($document->template->schema))
|
||||
@elseif($document->template?->isBlockBuilder() && !empty($document->template->schema))
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ $document->template->title ?? $document->template->name }}</h2>
|
||||
<div>{!! $blockHtml !!}</div>
|
||||
</div>
|
||||
@else
|
||||
|
||||
{{-- 기본 필드 데이터 (레거시 서식) --}}
|
||||
@if($document->template?->basicFields && $document->template->basicFields->count() > 0)
|
||||
{{-- 기본 필드 데이터 (절곡 작업일지는 전용 partial에서 렌더링하므로 스킵) --}}
|
||||
@if($document->template?->basicFields && $document->template->basicFields->count() > 0 && !(str_contains($document->template->name ?? '', '절곡') && str_contains($document->template->name ?? '', '작업일지')))
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ $document->template->title ?? '문서 정보' }}</h2>
|
||||
|
||||
@@ -141,8 +149,11 @@ class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg transiti
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- 작업일지 전용: 작업내역 + 자재LOT + 통계 (섹션 없는 work_order 연결 문서) --}}
|
||||
@if($document->linkable_type === 'work_order' && $workOrderItems->isNotEmpty() && (!$document->template?->sections || $document->template->sections->count() === 0))
|
||||
{{-- 절곡 작업일지 전용 렌더링 (React BendingWorkLogContent와 동일 구조) --}}
|
||||
@if($document->linkable_type === 'work_order' && $document->template && str_contains($document->template->name ?? '', '절곡') && str_contains($document->template->name ?? '', '작업일지'))
|
||||
@include('documents.partials.bending-worklog')
|
||||
@elseif($document->linkable_type === 'work_order' && $workOrderItems->isNotEmpty() && (!$document->template?->sections || $document->template->sections->count() === 0))
|
||||
{{-- 일반 작업일지: 작업내역 + 자재LOT + 통계 (섹션 없는 work_order 연결 문서) --}}
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">작업 내역</h2>
|
||||
|
||||
@@ -403,6 +414,29 @@ class="px-2 py-2 text-center text-xs font-medium text-gray-600 border border-gra
|
||||
@endif
|
||||
@endif
|
||||
|
||||
{{-- 절곡 중간검사 DATA 전용 렌더링 (React BendingInspectionContent와 동일 구조) --}}
|
||||
@if($document->template && str_contains($document->template->name ?? '', '절곡') && str_contains($document->template->name ?? '', '검사'))
|
||||
@php
|
||||
$docData = $document->getRelation('data') ?? $document->data()->get();
|
||||
@endphp
|
||||
{{-- 검사기준서 섹션 (이미지만 렌더링) --}}
|
||||
@foreach(($document->template->sections ?? collect()) as $section)
|
||||
@if($section->title !== '중간검사 DATA' && $section->image_path)
|
||||
<div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">{{ $section->title }}</h2>
|
||||
@php
|
||||
$sectionImgUrl = preg_match('/^\d+\//', $section->image_path)
|
||||
? rtrim(config('app.api_url', 'http://api.sam.kr'), '/') . '/storage/tenants/' . $section->image_path
|
||||
: asset('storage/' . $section->image_path);
|
||||
@endphp
|
||||
<img src="{{ $sectionImgUrl }}" alt="{{ $section->title }}" class="max-w-full h-auto mb-4 rounded border">
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
{{-- 중간검사 DATA 테이블 (rowSpan + 간격 포인트) --}}
|
||||
@include('documents.partials.bending-inspection-data', ['inspectionData' => $inspectionData ?? null])
|
||||
@else
|
||||
|
||||
{{-- 섹션 데이터 (테이블) --}}
|
||||
@if($document->template?->sections && $document->template->sections->count() > 0)
|
||||
@foreach($document->template->sections as $section)
|
||||
@@ -776,7 +810,9 @@ class="px-2 py-2 text-center text-xs font-medium text-gray-600 uppercase border
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@endif {{-- end: 블록 빌더 vs 레거시 분기 --}}
|
||||
@endif {{-- end: 스냅샷 vs 블록빌더 vs 레거시 분기 --}}
|
||||
|
||||
@endif {{-- end: 절곡 검사 vs 일반 섹션 분기 --}}
|
||||
|
||||
{{-- 첨부파일 --}}
|
||||
@if($document->attachments && $document->attachments->count() > 0)
|
||||
|
||||
0
storage/fonts/Pretendard-Regular.ttf
Normal file → Executable file
0
storage/fonts/Pretendard-Regular.ttf
Normal file → Executable file
0
storage/fonts/tcpdf/helvetica.php
Normal file → Executable file
0
storage/fonts/tcpdf/helvetica.php
Normal file → Executable file
0
storage/fonts/tcpdf/pretendard.ctg.z
Normal file → Executable file
0
storage/fonts/tcpdf/pretendard.ctg.z
Normal file → Executable file
0
storage/fonts/tcpdf/pretendard.php
Normal file → Executable file
0
storage/fonts/tcpdf/pretendard.php
Normal file → Executable file
0
storage/fonts/tcpdf/pretendard.z
Normal file → Executable file
0
storage/fonts/tcpdf/pretendard.z
Normal file → Executable file
Reference in New Issue
Block a user