Files
Gnuboard7/modules/_bundled/sirsoft-ecommerce/database/seeders/Sample/ShippingPolicySeeder.php
T
HeuJung 55bc9399e6 fix(ecommerce): 배송비 구간 경계 포함 + 무게/부피 단위 환산
구간별 배송비의 종료값이 구간에 포함되지 않아 경계값이 무료배송으로
새고, 상품 옵션의 g/cm³ 저장값을 정책의 kg/L 로 환산하지 않아 금액이
1000배로 청구되던 문제를 수정한다.

- 구간 매칭을 종료값 오름차순 사다리로 교체 — 시작값은 표시 전용이
 되어 기존 저장 데이터의 두 형태 모두에서 정확히 계산된다
- 단위 환산을 배송비 계산 한 지점으로 모으고, 부피무게와 실무게를
 같은 kg 단위로 비교하도록 교정
- 계산이 조용히 0원이 되는 설정(구간 미등록·단위값/기준금액 누락·
 중간 구간 무제한·계산 API 폴백 0원)을 저장 시점에 차단하고,
 런타임 잔여 경로에는 경고 로그를 남긴다
- 연속성 규칙을 정책 타입별로 분리해 소수 구간(2.5kg) 저장을 허용
- 상품 옵션 무게/부피 응답 왕복, 주문 총 무게/부피 실값 기록
2026-08-12 23:43:08 +09:00

512 lines
22 KiB
PHP

<?php
namespace Modules\Sirsoft\Ecommerce\Database\Seeders\Sample;
use Illuminate\Database\Seeder;
use Modules\Sirsoft\Ecommerce\Models\ShippingPolicy;
use Modules\Sirsoft\Ecommerce\Models\ShippingPolicyCountrySetting;
use Modules\Sirsoft\Ecommerce\Services\CurrencyConversionService;
/**
* 배송정책 시더
*/
class ShippingPolicySeeder extends Seeder
{
/**
* 배송정책 시더를 실행합니다.
*/
public function run(): void
{
$this->command->info('배송정책 더미 데이터 생성을 시작합니다.');
$this->deleteExistingPolicies();
$policies = $this->getPoliciesData();
foreach ($policies as $policyData) {
$countrySettings = $policyData['country_settings'];
unset($policyData['country_settings']);
$policy = ShippingPolicy::create($policyData);
foreach ($countrySettings as $cs) {
// 통화 라벨을 설정의 기본 통화로 맞춤 (KRW 하드코딩 제거 — base 추종, mc 정합)
$cs['currency_code'] = $this->defaultCurrency();
$policy->countrySettings()->create($cs);
}
$this->command->line(" - 배송정책 생성: {$policyData['name']['ko']} ({$policyData['name']['en']})");
}
$count = ShippingPolicy::count();
$this->command->info("배송정책 더미 데이터 {$count}건이 성공적으로 생성되었습니다.");
}
/**
* 설정의 기본 통화 코드 캐시
*/
private ?string $defaultCurrencyCode = null;
/**
* 설정의 기본 통화 코드를 반환합니다 (KRW 하드코딩 제거 — base 추종).
*
* @return string 기본 통화 코드
*/
private function defaultCurrency(): string
{
if ($this->defaultCurrencyCode === null) {
$this->defaultCurrencyCode = app(CurrencyConversionService::class)
->getDefaultCurrency();
}
return $this->defaultCurrencyCode;
}
/**
* 기존 배송정책 데이터를 삭제합니다.
*/
private function deleteExistingPolicies(): void
{
$deletedCount = ShippingPolicy::count();
if ($deletedCount > 0) {
// 국가별 설정 먼저 명시적 삭제 (DB CASCADE에 의존하지 않음)
ShippingPolicyCountrySetting::query()->delete();
ShippingPolicy::query()->delete();
$this->command->warn("기존 배송정책 {$deletedCount}건을 삭제했습니다.");
}
}
/**
* 배송정책 더미 데이터를 반환합니다.
*
* @return array 배송정책 데이터 배열
*/
private function getPoliciesData(): array
{
return [
// 1. 국내 무료배송 (기본 배송정책)
[
'name' => ['ko' => '국내 무료배송', 'en' => 'Domestic Free Shipping'],
'is_active' => true,
'is_default' => true,
'sort_order' => 1,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'free',
'base_fee' => 0,
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 2. 국내 택배 (고정 배송비 + 도서산간)
[
'name' => ['ko' => '국내 택배 (고정)', 'en' => 'Domestic Parcel (Fixed)'],
'is_active' => true,
'sort_order' => 2,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'fixed',
'base_fee' => 3000,
'extra_fee_enabled' => true,
'extra_fee_settings' => [
['zipcode' => '63000-63644', 'fee' => 3000, 'region' => '제주도'],
['zipcode' => '52570-52571', 'fee' => 5000, 'region' => '경남 사천 섬지역'],
],
'is_active' => true,
],
],
],
// 3. 조건부 무료배송 (5만원 이상 + 도서산간)
[
'name' => ['ko' => '조건부 무료배송 (5만원 이상)', 'en' => 'Conditional Free (Over 50,000)'],
'is_active' => true,
'sort_order' => 3,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'conditional_free',
'base_fee' => 2500,
'free_threshold' => 50000,
'extra_fee_enabled' => true,
'extra_fee_settings' => [
['zipcode' => '63000-63644', 'fee' => 3000, 'region' => '제주도'],
],
'is_active' => true,
],
],
],
// 4. 금액별 구간 배송비 (5구간)
[
'name' => ['ko' => '금액별 구간 배송비', 'en' => 'Amount-based Range Shipping'],
'is_active' => true,
'sort_order' => 4,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'range_amount',
'base_fee' => 0,
'ranges' => [
'type' => 'amount',
// 연속형(금액) 구간: 다음 시작값 = 직전 종료값, 종료값은 포함
'tiers' => [
['min' => 0, 'max' => 10000, 'fee' => 5000],
['min' => 10000, 'max' => 30000, 'fee' => 3000],
['min' => 30000, 'max' => 50000, 'fee' => 2000],
['min' => 50000, 'max' => 100000, 'fee' => 1000],
['min' => 100000, 'max' => null, 'fee' => 0],
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 5. 수량별 구간 배송비 (2구간)
[
'name' => ['ko' => '수량별 구간 배송비', 'en' => 'Quantity-based Range Shipping'],
'is_active' => true,
'sort_order' => 5,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'range_quantity',
'base_fee' => 0,
'ranges' => [
'type' => 'quantity',
// 이산형(수량) 구간: 다음 시작값 = 직전 종료값 + 1, 종료값은 포함
'tiers' => [
['min' => 0, 'max' => 5, 'fee' => 3000],
['min' => 6, 'max' => null, 'fee' => 5000],
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 6. 무게별 구간 배송비 (4구간)
[
'name' => ['ko' => '무게별 구간 배송비', 'en' => 'Weight-based Range Shipping'],
'is_active' => true,
'sort_order' => 6,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'range_weight',
'base_fee' => 0,
'ranges' => [
'type' => 'weight',
// 무게 구간은 kg 단위 (상품 옵션의 g 값은 배송비 계산 시점에 환산된다)
'tiers' => [
['min' => 0, 'max' => 2, 'fee' => 3000],
['min' => 2, 'max' => 5, 'fee' => 4000],
['min' => 5, 'max' => 10, 'fee' => 6000],
['min' => 10, 'max' => null, 'fee' => 8000],
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 7. 부피별 구간 배송비 (3구간)
[
'name' => ['ko' => '부피별 구간 배송비', 'en' => 'Volume-based Range Shipping'],
'is_active' => true,
'sort_order' => 7,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'direct',
'currency_code' => 'KRW',
'charge_policy' => 'range_volume',
'base_fee' => 0,
'ranges' => [
'type' => 'volume',
// 부피 구간은 L 단위 (상품 옵션의 cm³ 값은 배송비 계산 시점에 환산된다)
'tiers' => [
['min' => 0, 'max' => 50, 'fee' => 5000],
['min' => 50, 'max' => 100, 'fee' => 10000],
['min' => 100, 'max' => null, 'fee' => 20000],
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 8. 부피무게 구간 배송비 (4구간)
[
'name' => ['ko' => '부피무게 구간 배송비', 'en' => 'Volume-Weight Range Shipping'],
'is_active' => true,
'sort_order' => 8,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'range_volume_weight',
'base_fee' => 0,
'ranges' => [
'type' => 'volume_weight',
// 부피무게 구간은 kg 단위 (실무게 kg 과 부피무게 kg 중 큰 값으로 매칭)
'tiers' => [
['min' => 0, 'max' => 5, 'fee' => 3500],
['min' => 5, 'max' => 10, 'fee' => 5000],
['min' => 10, 'max' => 20, 'fee' => 8000],
['min' => 20, 'max' => null, 'fee' => 12000],
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 9. 해외배송 (API 연동 - 국가별 독립)
[
'name' => ['ko' => '해외배송 (DHL)', 'en' => 'International Shipping (DHL)'],
'is_active' => true,
'sort_order' => 9,
'country_settings' => [
[
'country_code' => 'US',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'api',
// 외부 API 장애 시 폴백 배송비 (0 이면 장애가 곧 무음 무료배송이 된다)
'base_fee' => 30000,
'api_endpoint' => 'https://api.example.com/shipping/calculate',
'api_request_fields' => ['group_total', 'total_quantity', 'country_code'],
'api_response_fee_field' => 'shipping_fee',
// 계산 API 연동 상세 설정 (MP12 — A13) — api_config JSON
'api_config' => [
'http_method' => 'POST',
'auth_type' => 'bearer',
'auth_token' => 'sample-demo-token',
'auth_header_name' => 'Authorization',
'response_type' => 'json',
'response_path' => 'data.shipping_fee',
'field_map' => [
'order_amount' => 'orderAmount',
'weight' => 'totalWeight',
'zipcode' => 'postalCode',
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
[
'country_code' => 'CN',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'api',
// 외부 API 장애 시 폴백 배송비 (0 이면 장애가 곧 무음 무료배송이 된다)
'base_fee' => 30000,
'api_endpoint' => 'https://api.example.com/shipping/calculate',
'api_request_fields' => ['group_total', 'total_quantity', 'country_code'],
'api_response_fee_field' => 'shipping_fee',
// 계산 API 연동 상세 설정 (MP12 — A13) — api_config JSON
'api_config' => [
'http_method' => 'POST',
'auth_type' => 'bearer',
'auth_token' => 'sample-demo-token',
'auth_header_name' => 'Authorization',
'response_type' => 'json',
'response_path' => 'data.shipping_fee',
'field_map' => [
'order_amount' => 'orderAmount',
'weight' => 'totalWeight',
'zipcode' => 'postalCode',
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
[
'country_code' => 'JP',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'api',
// 외부 API 장애 시 폴백 배송비 (0 이면 장애가 곧 무음 무료배송이 된다)
'base_fee' => 30000,
'api_endpoint' => 'https://api.example.com/shipping/calculate',
'api_request_fields' => ['group_total', 'total_quantity', 'country_code'],
'api_response_fee_field' => 'shipping_fee',
// 계산 API 연동 상세 설정 (MP12 — A13) — api_config JSON
'api_config' => [
'http_method' => 'POST',
'auth_type' => 'bearer',
'auth_token' => 'sample-demo-token',
'auth_header_name' => 'Authorization',
'response_type' => 'json',
'response_path' => 'data.shipping_fee',
'field_map' => [
'order_amount' => 'orderAmount',
'weight' => 'totalWeight',
'zipcode' => 'postalCode',
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 10. 퀵서비스 (비활성)
[
'name' => ['ko' => '퀵서비스', 'en' => 'Quick Service'],
'is_active' => false,
'sort_order' => 10,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'quick',
'currency_code' => 'KRW',
'charge_policy' => 'fixed',
'base_fee' => 5000,
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 11. 수량당 배송비
[
'name' => ['ko' => '수량당 배송비 (3개당)', 'en' => 'Per Quantity Shipping (per 3)'],
'is_active' => true,
'sort_order' => 11,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'per_quantity',
'base_fee' => 3000,
'ranges' => ['type' => 'per_quantity', 'unit_value' => 3],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 12. 무게당 배송비
[
'name' => ['ko' => '무게당 배송비 (1kg당)', 'en' => 'Per Weight Shipping (per 1kg)'],
'is_active' => true,
'sort_order' => 12,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'per_weight',
'base_fee' => 1000,
'ranges' => ['type' => 'per_weight', 'unit_value' => 1],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 13. 부피당 배송비
[
'name' => ['ko' => '부피당 배송비 (10L당)', 'en' => 'Per Volume Shipping (per 10L)'],
'is_active' => true,
'sort_order' => 13,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'direct',
'currency_code' => 'KRW',
'charge_policy' => 'per_volume',
'base_fee' => 2000,
'ranges' => ['type' => 'per_volume', 'unit_value' => 10],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 14. 국내외 복합 배송 (KR + US, 부피무게당)
[
'name' => ['ko' => '국내외 복합 배송 (부피무게당)', 'en' => 'Domestic & Intl (Per Volume Weight)'],
'is_active' => true,
'sort_order' => 14,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'per_volume_weight',
'base_fee' => 3000,
'ranges' => ['type' => 'per_volume_weight', 'unit_value' => 5],
'extra_fee_enabled' => false,
'is_active' => true,
],
[
'country_code' => 'US',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'range_weight',
'base_fee' => 0,
'ranges' => [
'type' => 'weight',
// 배송비는 기본 통화(KRW) 기준 금액이다 — 국가만 US 일 뿐 통화가 바뀌지 않는다
'tiers' => [
['min' => 0, 'max' => 2, 'fee' => 25000],
['min' => 2, 'max' => 5, 'fee' => 40000],
['min' => 5, 'max' => null, 'fee' => 60000],
],
],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
// 15. 금액당 배송비
[
'name' => ['ko' => '금액당 배송비 (1만원당)', 'en' => 'Per Amount Shipping (per 10,000)'],
'is_active' => true,
'sort_order' => 15,
'country_settings' => [
[
'country_code' => 'KR',
'shipping_method' => 'parcel',
'currency_code' => 'KRW',
'charge_policy' => 'per_amount',
'base_fee' => 500,
'ranges' => ['type' => 'per_amount', 'unit_value' => 10000],
'extra_fee_enabled' => false,
'is_active' => true,
],
],
],
];
}
}