Files
Gnuboard7/modules/_bundled/sirsoft-ecommerce/database/seeders/Sample/CartSeeder.php
T
2026-07-01 10:30:32 +09:00

319 lines
11 KiB
PHP

<?php
namespace Modules\Sirsoft\Ecommerce\Database\Seeders\Sample;
use App\Models\User;
use App\Traits\HasSeederCounts;
use Illuminate\Database\Seeder;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Modules\Sirsoft\Ecommerce\Models\Cart;
use Modules\Sirsoft\Ecommerce\Models\Product;
use Modules\Sirsoft\Ecommerce\Models\ProductAdditionalOptionValue;
use Modules\Sirsoft\Ecommerce\Models\ProductOption;
/**
* 장바구니 더미 데이터 시더
*/
class CartSeeder extends Seeder
{
use HasSeederCounts;
/**
* 회원 장바구니를 생성할 기본 사용자 수
*/
private const USER_COUNT = 10;
/**
* 사용자당 장바구니 아이템 최소 수
*/
private const MIN_ITEMS_PER_USER = 20;
/**
* 사용자당 장바구니 아이템 최대 수
*/
private const MAX_ITEMS_PER_USER = 30;
/**
* 비회원 장바구니 수
*/
private const GUEST_CART_COUNT = 5;
/**
* 시더 실행
*/
public function run(): void
{
$this->command->info('장바구니 더미 데이터 생성을 시작합니다.');
$this->deleteExistingCarts();
$this->createCarts();
$count = Cart::count();
$this->command->info("장바구니 더미 데이터 {$count}건이 성공적으로 생성되었습니다.");
}
/**
* 기존 장바구니 삭제
*/
private function deleteExistingCarts(): void
{
$deletedCount = Cart::count();
if ($deletedCount > 0) {
Cart::query()->delete();
$this->command->warn("기존 장바구니 데이터 {$deletedCount}건을 삭제했습니다.");
}
}
/**
* 배송비 부여 상품 최소 비중 (60%)
*/
private const MIN_SHIPPING_PRODUCT_RATIO = 0.6;
/**
* 장바구니 생성
*/
private function createCarts(): void
{
// 상품 옵션 목록 가져오기
$productOptions = ProductOption::with('product')
->whereHas('product', function ($query) {
$query->where('sales_status', 'on_sale')
->where('display_status', 'visible');
})
->where('is_active', true)
->where('stock_quantity', '>', 0)
->get();
if ($productOptions->isEmpty()) {
$this->command->error('판매 가능한 상품 옵션이 없습니다. ProductSeeder를 먼저 실행해주세요.');
return;
}
// 배송정책 유무에 따라 분류
$optionsWithShipping = $productOptions->filter(
fn ($option) => $option->product->shipping_policy_id !== null
);
$optionsWithoutShipping = $productOptions->filter(
fn ($option) => $option->product->shipping_policy_id === null
);
$this->command->line(" - 판매 가능한 상품 옵션 {$productOptions->count()}개를 찾았습니다.");
$this->command->line(" - 배송비 부여: {$optionsWithShipping->count()}개");
$this->command->line(" - 배송비 없음: {$optionsWithoutShipping->count()}개");
if ($optionsWithShipping->isEmpty()) {
$this->command->warn('배송정책이 있는 상품이 없습니다. 모든 상품으로 장바구니를 생성합니다.');
$optionsWithShipping = $productOptions;
$optionsWithoutShipping = collect();
}
// 회원 장바구니 생성
$userCount = $this->getSeederCount('cart_users', self::USER_COUNT);
$this->createMemberCarts($optionsWithShipping, $optionsWithoutShipping, $userCount);
// 비회원 장바구니 생성
$guestCount = $this->getSeederCount('cart_guests', self::GUEST_CART_COUNT);
$this->createGuestCarts($optionsWithShipping, $optionsWithoutShipping, $guestCount);
}
/**
* 회원 장바구니 생성
*
* @param Collection $optionsWithShipping 배송정책이 있는 상품 옵션
* @param Collection $optionsWithoutShipping 배송정책이 없는 상품 옵션
*/
private function createMemberCarts($optionsWithShipping, $optionsWithoutShipping, int $userCount): void
{
// 사용자 목록 가져오기 (없으면 생성)
$users = User::take($userCount)->get();
if ($users->isEmpty()) {
$this->command->line(" - 사용자가 없어 {$userCount}명 생성합니다.");
$users = User::factory()->count($userCount)->create();
}
$this->command->line(' - 회원 장바구니 생성 중...');
$totalItems = 0;
foreach ($users as $user) {
$itemCount = rand(self::MIN_ITEMS_PER_USER, self::MAX_ITEMS_PER_USER);
// 배송비 부여 상품 60% 이상 비중으로 선택
$selectedOptions = $this->selectOptionsWithShippingRatio(
$optionsWithShipping,
$optionsWithoutShipping,
$itemCount
);
// 회원용 cart_key 생성
$cartKey = 'ck_'.Str::random(32);
foreach ($selectedOptions as $option) {
$this->createCartItem($option, $user->id, $cartKey);
$totalItems++;
}
}
$this->command->line(" - 회원 장바구니 {$totalItems}건 생성 완료");
}
/**
* 비회원 장바구니 생성
*
* @param Collection $optionsWithShipping 배송정책이 있는 상품 옵션
* @param Collection $optionsWithoutShipping 배송정책이 없는 상품 옵션
*/
private function createGuestCarts($optionsWithShipping, $optionsWithoutShipping, int $guestCount): void
{
$this->command->line(' - 비회원 장바구니 생성 중...');
$totalItems = 0;
for ($i = 0; $i < $guestCount; $i++) {
// 비회원용 cart_key 생성
$cartKey = 'ck_'.Str::random(32);
$itemCount = rand(self::MIN_ITEMS_PER_USER, self::MAX_ITEMS_PER_USER);
// 배송비 부여 상품 60% 이상 비중으로 선택
$selectedOptions = $this->selectOptionsWithShippingRatio(
$optionsWithShipping,
$optionsWithoutShipping,
$itemCount
);
foreach ($selectedOptions as $option) {
$this->createCartItem($option, null, $cartKey);
$totalItems++;
}
}
$this->command->line(" - 비회원 장바구니 {$totalItems}건 생성 완료");
}
/**
* 배송비 부여 상품 비중을 60% 이상으로 유지하며 옵션 선택
*
* @param Collection $optionsWithShipping 배송정책이 있는 상품 옵션
* @param Collection $optionsWithoutShipping 배송정책이 없는 상품 옵션
* @param int $totalCount 선택할 총 개수
* @return Collection 선택된 상품 옵션 컬렉션
*/
private function selectOptionsWithShippingRatio($optionsWithShipping, $optionsWithoutShipping, int $totalCount)
{
// 배송비 부여 상품 최소 개수 (60% 이상)
$minShippingCount = (int) ceil($totalCount * self::MIN_SHIPPING_PRODUCT_RATIO);
// 실제 선택 가능한 배송비 부여 상품 수
$availableShippingCount = $optionsWithShipping->count();
$shippingCount = min($minShippingCount, $availableShippingCount);
// 나머지는 배송비 없는 상품으로 채움
$remainingCount = $totalCount - $shippingCount;
$availableWithoutShippingCount = $optionsWithoutShipping->count();
$withoutShippingCount = min($remainingCount, $availableWithoutShippingCount);
// 배송비 없는 상품이 부족한 경우 배송비 부여 상품으로 추가
if ($withoutShippingCount < $remainingCount) {
$additionalShipping = $remainingCount - $withoutShippingCount;
$shippingCount = min($shippingCount + $additionalShipping, $availableShippingCount);
}
// 랜덤 선택
$selectedWithShipping = $optionsWithShipping->random(min($shippingCount, $availableShippingCount));
$selectedWithoutShipping = $optionsWithoutShipping->isNotEmpty() && $withoutShippingCount > 0
? $optionsWithoutShipping->random(min($withoutShippingCount, $availableWithoutShippingCount))
: collect();
// Collection이 아닌 단일 모델이 반환될 수 있음
if (! ($selectedWithShipping instanceof Collection)) {
$selectedWithShipping = collect([$selectedWithShipping]);
}
if (! ($selectedWithoutShipping instanceof Collection)) {
$selectedWithoutShipping = collect([$selectedWithoutShipping]);
}
// 합치고 섞기
return $selectedWithShipping->merge($selectedWithoutShipping)->shuffle();
}
/**
* 장바구니 아이템 생성
*
* @param ProductOption $option 상품 옵션
* @param int|null $userId 사용자 ID (회원인 경우)
* @param string|null $cartKey 장바구니 키 (비회원인 경우)
*/
private function createCartItem(ProductOption $option, ?int $userId, ?string $cartKey): void
{
// 재고 수량 내에서 랜덤 수량 설정
$maxQuantity = min($option->stock_quantity, 10);
$quantity = rand(1, max(1, $maxQuantity));
Cart::create([
'cart_key' => $cartKey,
'user_id' => $userId,
'product_id' => $option->product_id,
'product_option_id' => $option->id,
'quantity' => $quantity,
'additional_option_selections' => $this->buildAdditionalOptionSelections($option->product),
]);
}
/**
* 장바구니 추가옵션 선택을 생성합니다.
*
* 상품에 활성 추가옵션이 있으면 AdditionalOptionSelectionService 가 검증하는 형식
* ([{additional_option_id, value_id, custom_text?}]) 으로 그룹당 1개를 선택합니다.
* 필수 그룹은 항상, 비필수 그룹은 50% 확률로 선택합니다.
*
* @param Product|null $product 상품 모델
* @return array<int, array>|null 추가옵션 선택 배열 (없으면 null)
*/
private function buildAdditionalOptionSelections($product): ?array
{
if ($product === null) {
return null;
}
$product->loadMissing('additionalOptions.activeValues');
if ($product->additionalOptions->isEmpty()) {
return null;
}
$selections = [];
foreach ($product->additionalOptions as $group) {
$values = $group->activeValues;
if ($values->isEmpty()) {
continue;
}
// 필수 그룹은 항상, 비필수 그룹은 50% 확률로 선택
if (! $group->is_required && rand(1, 100) > 50) {
continue;
}
/** @var ProductAdditionalOptionValue $value */
$value = $values->random();
$row = [
'additional_option_id' => (int) $group->id,
'value_id' => (int) $value->id,
];
// 직접입력 선택지면 custom_text 더미 포함
if ($value->allow_custom_text) {
$row['custom_text'] = '장바구니 직접 입력 문구';
}
$selections[] = $row;
}
return empty($selections) ? null : $selections;
}
}