Files
sam-api/app/Services/BendingCodeService.php
김보곤 6af9162ce4 feat: [bending] 절곡품 품목 일괄 등록 + LOT 매핑 (222건 신규)
- BendingCodeService에 42(4200mm) 길이코드 복원
- 품목 일괄 등록 스크립트 추가 (scripts/register-bending-items.php)
- 신규 종류(RW/RF/SW/SF/TE/GH) + 신규 길이(06/17/20/45) 조합 등록
- bending_item_mappings 320건 매핑 동시 등록
2026-03-18 19:43:15 +09:00

205 lines
7.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services;
use App\Models\Orders\Order;
use App\Models\Production\BendingItemMapping;
class BendingCodeService extends Service
{
// =========================================================================
// 제품 코드 (7종)
// =========================================================================
public const PRODUCTS = [
['code' => 'R', 'name' => '가이드레일(벽면형)'],
['code' => 'S', 'name' => '가이드레일(측면형)'],
['code' => 'G', 'name' => '연기차단재'],
['code' => 'B', 'name' => '하단마감재(스크린)'],
['code' => 'T', 'name' => '하단마감재(철재)'],
['code' => 'L', 'name' => 'L-Bar'],
['code' => 'C', 'name' => '케이스'],
];
// =========================================================================
// 종류 코드 + 사용 가능 제품
// 경동기업 재공품 LOT 채번 규칙 기준 (2026-03 최신)
// =========================================================================
public const SPECS = [
['code' => 'M', 'name' => '본체', 'products' => ['R']],
['code' => 'M', 'name' => '본체디딤', 'products' => ['S']],
['code' => 'T', 'name' => '본체(철재)', 'products' => ['R', 'S']],
['code' => 'C', 'name' => 'C형', 'products' => ['R', 'S']],
['code' => 'D', 'name' => 'D형', 'products' => ['R', 'S']],
['code' => 'S', 'name' => 'SUS마감재', 'products' => ['R', 'B', 'T']],
['code' => 'S', 'name' => 'SUS마감재(3)', 'products' => ['S']],
['code' => 'U', 'name' => 'SUS마감재(3)', 'products' => ['S']],
['code' => 'W', 'name' => '본체(L120)', 'products' => ['R', 'S']],
['code' => 'F', 'name' => 'SUS마감재(L120)', 'products' => ['R', 'S']],
['code' => 'E', 'name' => 'EGI', 'products' => ['B', 'T']],
['code' => 'I', 'name' => '화이바원단(W50)', 'products' => ['G']],
['code' => 'H', 'name' => '화이바원단(W80)', 'products' => ['G']],
['code' => 'A', 'name' => '스크린용', 'products' => ['L']],
['code' => 'F', 'name' => '전면부', 'products' => ['C']],
['code' => 'P', 'name' => '점검구', 'products' => ['C']],
['code' => 'L', 'name' => '린텔부', 'products' => ['C']],
['code' => 'B', 'name' => '후면코너부', 'products' => ['C']],
];
// =========================================================================
// 모양&길이 코드
// 연기차단재(G)는 SMOKE_BARRIER, 그 외는 GENERAL 사용
// 신규 길이 발생 시 코드 추가 가능 (확장형)
// =========================================================================
public const LENGTHS_SMOKE_BARRIER = [
['code' => '53', 'name' => 'W50 × 3000'],
['code' => '54', 'name' => 'W50 × 4000'],
['code' => '83', 'name' => 'W80 × 3000'],
['code' => '84', 'name' => 'W80 × 4000'],
];
public const LENGTHS_GENERAL = [
['code' => '06', 'name' => '610'],
['code' => '12', 'name' => '1219'],
['code' => '17', 'name' => '1750'],
['code' => '20', 'name' => '2000'],
['code' => '24', 'name' => '2438'],
['code' => '30', 'name' => '3000'],
['code' => '35', 'name' => '3500'],
['code' => '40', 'name' => '4000'],
['code' => '41', 'name' => '4150'],
['code' => '42', 'name' => '4200'],
['code' => '43', 'name' => '4300'],
['code' => '45', 'name' => '4500'],
];
// =========================================================================
// 제품+종류 → 원자재(재질) 매핑
// =========================================================================
public const MATERIAL_MAP = [
// 연기차단재
'G:I' => '화이바원단',
'G:H' => '화이바원단',
// 하단마감재(스크린)
'B:S' => 'SUS 1.2T',
'B:E' => 'EGI 1.55T',
// 하단마감재(철재)
'T:S' => 'SUS 1.2T',
'T:E' => 'EGI 1.55T',
// L-Bar
'L:A' => 'EGI 1.55T',
// 가이드레일(벽면형)
'R:M' => 'EGI 1.55T',
'R:T' => 'EGI 1.55T',
'R:C' => 'EGI 1.55T',
'R:D' => 'EGI 1.55T',
'R:S' => 'SUS 1.2T',
'R:W' => 'EGI 1.55T',
'R:F' => 'SUS 1.2T',
// 가이드레일(측면형)
'S:M' => 'EGI 1.55T',
'S:T' => 'EGI 1.55T',
'S:C' => 'EGI 1.55T',
'S:D' => 'EGI 1.55T',
'S:S' => 'SUS 1.2T',
'S:U' => 'SUS 1.2T',
'S:W' => 'EGI 1.55T',
'S:F' => 'SUS 1.2T',
// 케이스
'C:F' => 'EGI 1.55T',
'C:P' => 'EGI 1.55T',
'C:L' => 'EGI 1.55T',
'C:B' => 'EGI 1.55T',
];
/**
* 코드맵 전체 반환 (프론트엔드 드롭다운 구성용)
*/
public function getCodeMap(): array
{
return [
'products' => self::PRODUCTS,
'specs' => self::SPECS,
'lengths' => [
'smoke_barrier' => self::LENGTHS_SMOKE_BARRIER,
'general' => self::LENGTHS_GENERAL,
],
'material_map' => self::MATERIAL_MAP,
];
}
/**
* 드롭다운 선택 조합 → items 테이블 품목 매핑 조회
*/
public function resolveItem(string $prodCode, string $specCode, string $lengthCode): ?array
{
$mapping = BendingItemMapping::where('tenant_id', $this->tenantId())
->where('prod_code', $prodCode)
->where('spec_code', $specCode)
->where('length_code', $lengthCode)
->where('is_active', true)
->with('item:id,code,name,specification,unit')
->first();
if (! $mapping || ! $mapping->item) {
return null;
}
return [
'item_id' => $mapping->item->id,
'item_code' => $mapping->item->code,
'item_name' => $mapping->item->name,
'specification' => $mapping->item->specification,
'unit' => $mapping->item->unit ?? 'EA',
];
}
/**
* LOT 번호 생성 (일련번호 suffix 포함)
*
* base: 'GI6317-53' → 결과: 'GI6317-53-001'
*/
public function generateLotNumber(string $lotBase): string
{
$tenantId = $this->tenantId();
// 같은 base로 시작하는 기존 LOT 수 조회
$count = Order::withoutGlobalScopes()
->where('tenant_id', $tenantId)
->where('order_type_code', Order::TYPE_STOCK)
->where('options->bending_lot->lot_number', 'LIKE', $lotBase.'-%')
->count();
$seq = str_pad($count + 1, 3, '0', STR_PAD_LEFT);
return "{$lotBase}-{$seq}";
}
/**
* 날짜 → 4자리 날짜코드
*
* 2026-03-17 → '6317'
* 2026-10-05 → '6A05'
*/
public static function generateDateCode(string $date): string
{
$dt = \Carbon\Carbon::parse($date);
$year = $dt->year % 10;
$month = $dt->month;
$day = $dt->day;
$monthCode = $month >= 10
? chr(55 + $month) // 10=A, 11=B, 12=C
: (string) $month;
return $year.$monthCode.str_pad($day, 2, '0', STR_PAD_LEFT);
}
/**
* 제품+종류 → 원자재(재질) 반환
*/
public static function getMaterial(string $prodCode, string $specCode): ?string
{
return self::MATERIAL_MAP["{$prodCode}:{$specCode}"] ?? null;
}
}