float|null, 'price_history_id' => int|null, 'client_group_id' => int|null, 'warning' => string|null] */ public function getItemPrice(string $itemType, int $itemId, ?int $clientId = null, ?string $date = null): array { $date = $date ?? Carbon::today()->format('Y-m-d'); $clientGroupId = null; // 1. 고객의 그룹 ID 확인 if ($clientId) { $client = Client::where('tenant_id', $this->tenantId()) ->where('id', $clientId) ->first(); if ($client) { $clientGroupId = $client->client_group_id; } } // 2. 가격 조회 (우선순위대로) $priceHistory = null; // 1순위: 고객 그룹별 매출단가 if ($clientGroupId) { $priceHistory = $this->findPrice($itemType, $itemId, $clientGroupId, $date); } // 2순위: 기본 매출단가 (client_group_id = NULL) if (! $priceHistory) { $priceHistory = $this->findPrice($itemType, $itemId, null, $date); } // 3순위: NULL (경고) if (! $priceHistory) { return [ 'price' => null, 'price_history_id' => null, 'client_group_id' => null, 'warning' => __('error.price_not_found', [ 'item_type' => $itemType, 'item_id' => $itemId, 'date' => $date, ]), ]; } return [ 'price' => (float) $priceHistory->price, 'price_history_id' => $priceHistory->id, 'client_group_id' => $priceHistory->client_group_id, 'warning' => null, ]; } /** * 가격 이력에서 유효한 가격 조회 */ private function findPrice(string $itemType, int $itemId, ?int $clientGroupId, string $date): ?PriceHistory { return PriceHistory::where('tenant_id', $this->tenantId()) ->forItem($itemType, $itemId) ->forClientGroup($clientGroupId) ->salePrice() ->validAt($date) ->orderBy('started_at', 'desc') ->first(); } /** * 여러 항목의 단가를 일괄 조회 * * @param array $items [['item_type' => 'PRODUCT', 'item_id' => 1], ...] * @return array ['prices' => [...], 'warnings' => [...]] */ public function getBulkItemPrices(array $items, ?int $clientId = null, ?string $date = null): array { $prices = []; $warnings = []; foreach ($items as $item) { $result = $this->getItemPrice( $item['item_type'], $item['item_id'], $clientId, $date ); $prices[] = array_merge($item, [ 'price' => $result['price'], 'price_history_id' => $result['price_history_id'], 'client_group_id' => $result['client_group_id'], ]); if ($result['warning']) { $warnings[] = $result['warning']; } } return [ 'prices' => $prices, 'warnings' => $warnings, ]; } /** * 가격 등록/수정 */ public function upsertPrice(array $data): PriceHistory { $data['tenant_id'] = $this->tenantId(); $data['created_by'] = $this->apiUserId(); $data['updated_by'] = $this->apiUserId(); // 중복 확인: 동일 조건(item, client_group, date 범위)의 가격이 이미 있는지 $existing = PriceHistory::where('tenant_id', $data['tenant_id']) ->where('item_type_code', $data['item_type_code']) ->where('item_id', $data['item_id']) ->where('price_type_code', $data['price_type_code']) ->where('client_group_id', $data['client_group_id'] ?? null) ->where('started_at', $data['started_at']) ->first(); if ($existing) { $existing->update($data); return $existing->fresh(); } return PriceHistory::create($data); } /** * 가격 이력 조회 (페이지네이션) * * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator */ public function listPrices(array $filters = [], int $perPage = 15) { $query = PriceHistory::where('tenant_id', $this->tenantId()); if (isset($filters['item_type_code'])) { $query->where('item_type_code', $filters['item_type_code']); } if (isset($filters['item_id'])) { $query->where('item_id', $filters['item_id']); } if (isset($filters['price_type_code'])) { $query->where('price_type_code', $filters['price_type_code']); } if (isset($filters['client_group_id'])) { $query->where('client_group_id', $filters['client_group_id']); } if (isset($filters['date'])) { $query->validAt($filters['date']); } return $query->orderBy('started_at', 'desc') ->orderBy('created_at', 'desc') ->paginate($perPage); } /** * 가격 삭제 (Soft Delete) */ public function deletePrice(int $id): bool { $price = PriceHistory::where('tenant_id', $this->tenantId()) ->findOrFail($id); $price->deleted_by = $this->apiUserId(); $price->save(); return $price->delete(); } }