feat: split 'reject' price type into 'reject_capital' and 'reject_selling', update related components and database migration

This commit is contained in:
Yoga Pangestu 2026-08-16 14:27:19 +07:00
parent 0947cf3737
commit 0998c81803
15 changed files with 148 additions and 62 deletions

View File

@ -300,7 +300,7 @@ ## Enums
| `PayrollPeriodStatus` | open, closed | payroll_periods.status | | `PayrollPeriodStatus` | open, closed | payroll_periods.status |
| `PayrollStatus` | unpaid, paid, cancelled | payrolls.status | | `PayrollStatus` | unpaid, paid, cancelled | payrolls.status |
| `Permission` | — | Permission action names | | `Permission` | — | Permission action names |
| `PriceType` | retail, wholesale, capital | product_prices.type, orders.price_type | | `PriceType` | retail, wholesale, capital, reject_capital, reject_selling | product_prices.type, orders.price_type |
| `ProductStatus` | active, draft, inactive, pending, rejected | products.status | | `ProductStatus` | active, draft, inactive, pending, rejected | products.status |
| `ProductStockQuality` | good, reject | order_items.stock_quality, restocks.stock_type, stok_opname_items.stock_quality | | `ProductStockQuality` | good, reject | order_items.stock_quality, restocks.stock_type, stok_opname_items.stock_quality |
| `RawMaterialUnit` | kg, meter, yard | raw_materials.unit | | `RawMaterialUnit` | kg, meter, yard | raw_materials.unit |

View File

@ -108,7 +108,7 @@ public function fix(): array
private function resolvePriceType(string $orderPriceType, string $stockQuality): string private function resolvePriceType(string $orderPriceType, string $stockQuality): string
{ {
if ($stockQuality === ProductStockQuality::REJECT->value) { if ($stockQuality === ProductStockQuality::REJECT->value) {
return PriceType::REJECT->value; return PriceType::REJECT_SELLING->value;
} }
$map = [ $map = [

View File

@ -16,7 +16,8 @@ enum PriceType: string
case TIKTOK = 'tiktok'; case TIKTOK = 'tiktok';
case SHOPEE = 'shopee'; case SHOPEE = 'shopee';
case CAPITAL = 'capital'; case CAPITAL = 'capital';
case REJECT = 'reject'; case REJECT_CAPITAL = 'reject_capital';
case REJECT_SELLING = 'reject_selling';
public function label(): string public function label(): string
{ {
@ -29,7 +30,8 @@ public function label(): string
self::TIKTOK => 'TikTok', self::TIKTOK => 'TikTok',
self::SHOPEE => 'Shopee', self::SHOPEE => 'Shopee',
self::CAPITAL => 'Modal', self::CAPITAL => 'Modal',
self::REJECT => 'Reject', self::REJECT_CAPITAL => 'Reject Modal',
self::REJECT_SELLING => 'Reject Jual',
}; };
} }
} }

View File

@ -30,7 +30,7 @@ public function rules(): array
return [ return [
'stock_type' => ['sometimes', 'required', Rule::in(ProductStockQuality::values())], 'stock_type' => ['sometimes', 'required', Rule::in(ProductStockQuality::values())],
'channel' => ['sometimes', 'required', Rule::in(OrderChannel::values())], 'channel' => ['sometimes', 'required', Rule::in(OrderChannel::values())],
'price_type' => ['sometimes', 'required', Rule::in(array_diff(PriceType::values(), [PriceType::CAPITAL->value]))], 'price_type' => ['sometimes', 'required', Rule::in(array_diff(PriceType::values(), [PriceType::CAPITAL->value, PriceType::REJECT_CAPITAL->value]))],
'payment_type' => ['sometimes', 'required', Rule::in(PaymentType::values())], 'payment_type' => ['sometimes', 'required', Rule::in(PaymentType::values())],
'customer_id' => ['nullable', 'integer', Rule::exists('customers', 'id')], 'customer_id' => ['nullable', 'integer', Rule::exists('customers', 'id')],
'marketing_id' => ['nullable', 'integer', Rule::exists('users', 'id')], 'marketing_id' => ['nullable', 'integer', Rule::exists('users', 'id')],

View File

@ -188,7 +188,7 @@ public function destroy(Restock $restock): bool
private function buildItemRows(array $items, string $stockType, $now, int &$total): array private function buildItemRows(array $items, string $stockType, $now, int &$total): array
{ {
$priceType = $stockType === ProductStockQuality::REJECT->value $priceType = $stockType === ProductStockQuality::REJECT->value
? PriceType::REJECT ? PriceType::REJECT_CAPITAL
: PriceType::CAPITAL; : PriceType::CAPITAL;
$variantIds = collect($items)->pluck('product_variant_id')->unique()->all(); $variantIds = collect($items)->pluck('product_variant_id')->unique()->all();

View File

@ -33,6 +33,7 @@ class TransactionService
PriceType::RETAIL->value => PriceType::RETAIL, PriceType::RETAIL->value => PriceType::RETAIL,
PriceType::TIKTOK->value => PriceType::TIKTOK, PriceType::TIKTOK->value => PriceType::TIKTOK,
PriceType::SHOPEE->value => PriceType::SHOPEE, PriceType::SHOPEE->value => PriceType::SHOPEE,
PriceType::REJECT_SELLING->value => PriceType::REJECT_SELLING,
]; ];
public function __construct( public function __construct(
@ -355,7 +356,7 @@ public function updateStatus(Order $order, string $status): Order
private function buildItemRows(array $items, string $stockType, string $priceType, $now, int &$subtotal, int &$totalCost): array private function buildItemRows(array $items, string $stockType, string $priceType, $now, int &$subtotal, int &$totalCost): array
{ {
$resolvedPriceType = $stockType === ProductStockQuality::REJECT->value $resolvedPriceType = $stockType === ProductStockQuality::REJECT->value
? PriceType::REJECT ? PriceType::REJECT_SELLING
: (self::SELLING_PRICE_MAP[$priceType] ?? PriceType::RETAIL); : (self::SELLING_PRICE_MAP[$priceType] ?? PriceType::RETAIL);
$variantIds = collect($items)->pluck('product_variant_id')->unique()->all(); $variantIds = collect($items)->pluck('product_variant_id')->unique()->all();
@ -375,9 +376,13 @@ private function buildItemRows(array $items, string $stockType, string $priceTyp
return [$variant->id => $price?->price ?? 0]; return [$variant->id => $price?->price ?? 0];
}); });
$capitalPrices = $variants->mapWithKeys(function (ProductVariant $variant) { $capitalPriceType = $stockType === ProductStockQuality::REJECT->value
? PriceType::REJECT_CAPITAL
: PriceType::CAPITAL;
$capitalPrices = $variants->mapWithKeys(function (ProductVariant $variant) use ($capitalPriceType) {
$price = $variant->productPrices $price = $variant->productPrices
->first(fn ($p) => $p->type === PriceType::CAPITAL); ->first(fn ($p) => $p->type === $capitalPriceType);
return [$variant->id => $price?->price ?? 0]; return [$variant->id => $price?->price ?? 0];
}); });

View File

@ -41,11 +41,11 @@ public function getForRestock(): array
if ($allVariantIds !== []) { if ($allVariantIds !== []) {
$mediaByVariant = Media::query() $mediaByVariant = Media::query()
->whereIn('mediable_id', $allVariantIds) ->whereIn('model_id', $allVariantIds)
->where('mediable_type', ProductVariant::class) ->where('model_type', ProductVariant::class)
->where('collection_name', 'images') ->where('collection_name', 'images')
->get() ->get()
->groupBy('mediable_id'); ->groupBy('model_id');
} else { } else {
$mediaByVariant = collect(); $mediaByVariant = collect();
} }
@ -62,7 +62,7 @@ public function getForRestock(): array
$variant->capital_price = $capitalPrice?->price ?? 0; $variant->capital_price = $capitalPrice?->price ?? 0;
$rejectPrice = $variant->productPrices $rejectPrice = $variant->productPrices
->first(fn ($price) => $price->type === PriceType::REJECT); ->first(fn ($price) => $price->type === PriceType::REJECT_CAPITAL);
$variant->reject_price = $rejectPrice?->price ?? 0; $variant->reject_price = $rejectPrice?->price ?? 0;
}); });
})->toArray(); })->toArray();
@ -84,11 +84,11 @@ public function getForTransaction(): array
if ($allVariantIds !== []) { if ($allVariantIds !== []) {
$mediaByVariant = Media::query() $mediaByVariant = Media::query()
->whereIn('mediable_id', $allVariantIds) ->whereIn('model_id', $allVariantIds)
->where('mediable_type', ProductVariant::class) ->where('model_type', ProductVariant::class)
->where('collection_name', 'images') ->where('collection_name', 'images')
->get() ->get()
->groupBy('mediable_id'); ->groupBy('model_id');
} else { } else {
$mediaByVariant = collect(); $mediaByVariant = collect();
} }

View File

@ -0,0 +1,62 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
// 1. product_prices: rename 'reject' → 'reject_selling', then add 'reject_capital' rows
DB::table('product_prices')
->where('type', 'reject')
->update(['type' => 'reject_selling']);
// Insert reject_capital rows (price = 0) for each variant that now has reject_selling
$rejectSellingPrices = DB::table('product_prices')
->where('type', 'reject_selling')
->get();
foreach ($rejectSellingPrices as $row) {
DB::table('product_prices')->insert([
'variant_id' => $row->variant_id,
'type' => 'reject_capital',
'price' => 0,
'created_at' => $row->created_at,
'updated_at' => $row->updated_at,
]);
}
// 2. orders: rename 'reject' → 'reject_selling'
DB::table('orders')
->where('price_type', 'reject')
->update(['price_type' => 'reject_selling']);
// 3. Modify enum columns to reflect new values (MySQL only)
if (DB::getDriverName() === 'mysql') {
DB::statement("ALTER TABLE product_prices MODIFY COLUMN type ENUM('distributor','agent','sub_agent','wholesale','retail','tiktok','shopee','capital','reject_capital','reject_selling') NOT NULL");
DB::statement("ALTER TABLE orders MODIFY COLUMN price_type ENUM('distributor','agent','sub_agent','wholesale','retail','tiktok','shopee','capital','reject_capital','reject_selling') NOT NULL");
}
}
public function down(): void
{
// Reverse: rename back and clean up
DB::table('product_prices')
->where('type', 'reject_capital')
->delete();
DB::table('product_prices')
->where('type', 'reject_selling')
->update(['type' => 'reject']);
DB::table('orders')
->where('price_type', 'reject_selling')
->update(['price_type' => 'reject']);
if (DB::getDriverName() === 'mysql') {
DB::statement("ALTER TABLE product_prices MODIFY COLUMN type ENUM('distributor','agent','sub_agent','wholesale','retail','tiktok','shopee','capital','reject') NOT NULL");
DB::statement("ALTER TABLE orders MODIFY COLUMN price_type ENUM('distributor','agent','sub_agent','wholesale','retail','tiktok','shopee','capital','reject') NOT NULL");
}
}
};

View File

@ -10,21 +10,21 @@ export function NotificationPermissionPrompt() {
usePushNotification(); usePushNotification();
useEffect(() => { useEffect(() => {
if (!isSupported || hasRequested.current) { if (!isSupported || hasRequested.current || !vapidPublicKey) {
return;
}
if (Notification.permission !== 'default') {
return; return;
} }
hasRequested.current = true; hasRequested.current = true;
void requestPermission().then(async (result) => { if (Notification.permission === 'default') {
if (result === 'granted' && vapidPublicKey) { void requestPermission().then(async (result) => {
await subscribe(vapidPublicKey); if (result === 'granted') {
} await subscribe(vapidPublicKey);
}); }
});
} else if (Notification.permission === 'granted') {
void subscribe(vapidPublicKey);
}
}, [isSupported, requestPermission, subscribe, vapidPublicKey]); }, [isSupported, requestPermission, subscribe, vapidPublicKey]);
return null; return null;

View File

@ -68,7 +68,7 @@ type Props = {
priceTypeOptions: TransactionCreateData['priceTypeOptions']; priceTypeOptions: TransactionCreateData['priceTypeOptions'];
}; };
const SELLING_PRICE_TYPES = ['distributor', 'agent', 'sub_agent', 'wholesale', 'retail', 'tiktok', 'shopee']; const SELLING_PRICE_TYPES = ['distributor', 'agent', 'sub_agent', 'wholesale', 'retail', 'tiktok', 'shopee', 'reject_selling'];
export default function TransactionCreate({ export default function TransactionCreate({
products, products,
@ -181,8 +181,8 @@ export default function TransactionCreate({
useEffect(() => { useEffect(() => {
if (stockType === 'reject') { if (stockType === 'reject') {
setPriceType('reject'); setPriceType('reject_selling');
} else if (priceType === 'reject') { } else if (priceType === 'reject_selling') {
setPriceType('retail'); setPriceType('retail');
} }
}, [stockType]); }, [stockType]);
@ -191,7 +191,7 @@ export default function TransactionCreate({
const availablePriceTypes = useMemo(() => { const availablePriceTypes = useMemo(() => {
if (stockType === 'reject') { if (stockType === 'reject') {
return priceTypeOptions.filter((o) => o.value === 'reject'); return priceTypeOptions.filter((o) => o.value === 'reject_selling');
} }
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value)); return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
@ -206,7 +206,7 @@ export default function TransactionCreate({
} }
if (stockType === 'reject') { if (stockType === 'reject') {
return variant.prices?.reject ?? 0; return variant.prices?.reject_selling ?? 0;
} }
return variant.prices?.[priceType] ?? 0; return variant.prices?.[priceType] ?? 0;
@ -284,7 +284,7 @@ export default function TransactionCreate({
return { return {
stock_type: stockType, stock_type: stockType,
channel, channel,
price_type: stockType === 'reject' ? 'reject' : priceType, price_type: stockType === 'reject' ? 'reject_selling' : priceType,
payment_type: paymentType, payment_type: paymentType,
customer_id: customerId, customer_id: customerId,
marketing_id: marketingId, marketing_id: marketingId,
@ -443,7 +443,7 @@ export default function TransactionCreate({
) : ( ) : (
formatCurrency( formatCurrency(
stockType === 'reject' stockType === 'reject'
? (variant.prices?.reject ?? 0) ? (variant.prices?.reject_selling ?? 0)
: (variant.prices?.[priceType] ?? 0), : (variant.prices?.[priceType] ?? 0),
) )
)} )}

View File

@ -67,7 +67,7 @@ type Props = {
priceTypeOptions: TransactionCreateData['priceTypeOptions']; priceTypeOptions: TransactionCreateData['priceTypeOptions'];
}; };
const SELLING_PRICE_TYPES = ['distributor', 'agent', 'sub_agent', 'wholesale', 'retail', 'tiktok', 'shopee']; const SELLING_PRICE_TYPES = ['distributor', 'agent', 'sub_agent', 'wholesale', 'retail', 'tiktok', 'shopee', 'reject_selling'];
export default function TransactionEdit({ export default function TransactionEdit({
transaction, transaction,
@ -134,8 +134,8 @@ export default function TransactionEdit({
useEffect(() => { useEffect(() => {
if (stockType === 'reject') { if (stockType === 'reject') {
setPriceType('reject'); setPriceType('reject_selling');
} else if (priceType === 'reject') { } else if (priceType === 'reject_selling') {
setPriceType('retail'); setPriceType('retail');
} }
}, [stockType]); }, [stockType]);
@ -162,7 +162,7 @@ export default function TransactionEdit({
const availablePriceTypes = useMemo(() => { const availablePriceTypes = useMemo(() => {
if (stockType === 'reject') { if (stockType === 'reject') {
return priceTypeOptions.filter((o) => o.value === 'reject'); return priceTypeOptions.filter((o) => o.value === 'reject_selling');
} }
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value)); return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
@ -177,7 +177,7 @@ export default function TransactionEdit({
} }
if (stockType === 'reject') { if (stockType === 'reject') {
return variant.prices?.reject ?? 0; return variant.prices?.reject_selling ?? 0;
} }
return variant.prices?.[priceType] ?? 0; return variant.prices?.[priceType] ?? 0;
@ -255,7 +255,7 @@ export default function TransactionEdit({
return { return {
stock_type: stockType, stock_type: stockType,
channel, channel,
price_type: stockType === 'reject' ? 'reject' : priceType, price_type: stockType === 'reject' ? 'reject_selling' : priceType,
payment_type: paymentType, payment_type: paymentType,
customer_id: customerId, customer_id: customerId,
marketing_id: marketingId, marketing_id: marketingId,
@ -423,7 +423,7 @@ export default function TransactionEdit({
) : ( ) : (
formatCurrency( formatCurrency(
stockType === 'reject' stockType === 'reject'
? (variant.prices?.reject ?? 0) ? (variant.prices?.reject_selling ?? 0)
: (variant.prices?.[priceType] ?? 0), : (variant.prices?.[priceType] ?? 0),
) )
)} )}

View File

@ -43,7 +43,8 @@ const PRICE_TYPES = [
{ key: 'tiktok', label: 'TikTok' }, { key: 'tiktok', label: 'TikTok' },
{ key: 'shopee', label: 'Shopee' }, { key: 'shopee', label: 'Shopee' },
{ key: 'capital', label: 'Modal' }, { key: 'capital', label: 'Modal' },
{ key: 'reject', label: 'Reject' }, { key: 'reject_capital', label: 'Reject Modal' },
{ key: 'reject_selling', label: 'Reject Jual' },
]; ];
function createEmptyPrices(): Array<{ type: string; price: number }> { function createEmptyPrices(): Array<{ type: string; price: number }> {

View File

@ -61,7 +61,8 @@ const PRICE_TYPES = [
{ key: 'tiktok', label: 'TikTok' }, { key: 'tiktok', label: 'TikTok' },
{ key: 'shopee', label: 'Shopee' }, { key: 'shopee', label: 'Shopee' },
{ key: 'capital', label: 'Modal' }, { key: 'capital', label: 'Modal' },
{ key: 'reject', label: 'Reject' }, { key: 'reject_capital', label: 'Reject Modal' },
{ key: 'reject_selling', label: 'Reject Jual' },
]; ];
function createEmptyPrices(): Array<{ type: string; price: number }> { function createEmptyPrices(): Array<{ type: string; price: number }> {
@ -104,6 +105,13 @@ export default function ProductEdit({ product, categories }: Props) {
product.description ?? '', product.description ?? '',
); );
function normalizePrices(serverPrices: Array<{ type: string; price: number }>): Array<{ type: string; price: number }> {
return PRICE_TYPES.map((pt) => {
const existing = serverPrices.find((p) => p.type === pt.key);
return { type: pt.key, price: existing?.price ?? 0 };
});
}
const serverVariants: VariantState[] = product.product_variants.map( const serverVariants: VariantState[] = product.product_variants.map(
(v) => ({ (v) => ({
id: v.id, id: v.id,
@ -116,7 +124,7 @@ export default function ProductEdit({ product, categories }: Props) {
url: v.photo_urls[i] ?? null, url: v.photo_urls[i] ?? null,
})), })),
uploading: false, uploading: false,
prices: v.prices.length > 0 ? v.prices : createEmptyPrices(), prices: normalizePrices(v.prices),
}), }),
); );

View File

@ -35,7 +35,8 @@ const PRICE_TYPES = [
{ key: 'tiktok', label: 'TikTok' }, { key: 'tiktok', label: 'TikTok' },
{ key: 'shopee', label: 'Shopee' }, { key: 'shopee', label: 'Shopee' },
{ key: 'capital', label: 'Modal' }, { key: 'capital', label: 'Modal' },
{ key: 'reject', label: 'Reject' }, { key: 'reject_capital', label: 'Reject Modal' },
{ key: 'reject_selling', label: 'Reject Jual' },
]; ];
export default function ProductVariantEdit({ variant }: Props) { export default function ProductVariantEdit({ variant }: Props) {
@ -53,14 +54,15 @@ export default function ProductVariantEdit({ variant }: Props) {
const [prices, setPrices] = useState< const [prices, setPrices] = useState<
Array<{ type: string; price: number }> Array<{ type: string; price: number }>
>( >(
variant.prices.length > 0 PRICE_TYPES.map((pt) => {
? variant.prices const existing = variant.prices.find((p) => p.type === pt.key);
: PRICE_TYPES.map((pt) => ({ type: pt.key, price: 0 })), return { type: pt.key, price: existing?.price ?? 0 };
}),
); );
function updatePrice(priceIndex: number, value: number) { function updatePrice(priceType: string, value: number) {
setPrices((prev) => setPrices((prev) =>
prev.map((p, i) => (i === priceIndex ? { ...p, price: value } : p)), prev.map((p) => (p.type === priceType ? { ...p, price: value } : p)),
); );
} }
@ -200,7 +202,9 @@ export default function ProductVariantEdit({ variant }: Props) {
<CardContent> <CardContent>
<div className="grid grid-cols-2 gap-4 md:grid-cols-3"> <div className="grid grid-cols-2 gap-4 md:grid-cols-3">
{PRICE_TYPES.map( {PRICE_TYPES.map(
(priceType, priceIndex) => ( (priceType) => {
const priceEntry = prices.find((p) => p.type === priceType.key);
return (
<div <div
key={priceType.key} key={priceType.key}
className="grid gap-2" className="grid gap-2"
@ -214,15 +218,13 @@ export default function ProductVariantEdit({ variant }: Props) {
</Label> </Label>
<RupiahInput <RupiahInput
value={ value={
prices[ priceEntry?.price ?? 0
priceIndex
]?.price ?? 0
} }
onValueChange={( onValueChange={(
val, val,
) => ) =>
updatePrice( updatePrice(
priceIndex, priceType.key,
val, val,
) )
} }
@ -230,12 +232,13 @@ export default function ProductVariantEdit({ variant }: Props) {
<InputError <InputError
message={ message={
errors[ errors[
`prices.${priceIndex}.price` `prices.${priceType.key}.price`
] ]
} }
/> />
</div> </div>
), );
},
)} )}
</div> </div>
</CardContent> </CardContent>

View File

@ -45,7 +45,8 @@ function makeValidProductPayload(array $overrides = []): array
['type' => 'tiktok', 'price' => 16000], ['type' => 'tiktok', 'price' => 16000],
['type' => 'shopee', 'price' => 16500], ['type' => 'shopee', 'price' => 16500],
['type' => 'capital', 'price' => 8000], ['type' => 'capital', 'price' => 8000],
['type' => 'reject', 'price' => 5000], ['type' => 'reject_capital', 'price' => 5000],
['type' => 'reject_selling', 'price' => 6000],
], ],
], ],
], ],
@ -71,7 +72,8 @@ function makeValidSharedPricePayload(array $overrides = []): array
['type' => 'tiktok', 'price' => 16000], ['type' => 'tiktok', 'price' => 16000],
['type' => 'shopee', 'price' => 16500], ['type' => 'shopee', 'price' => 16500],
['type' => 'capital', 'price' => 8000], ['type' => 'capital', 'price' => 8000],
['type' => 'reject', 'price' => 5000], ['type' => 'reject_capital', 'price' => 5000],
['type' => 'reject_selling', 'price' => 6000],
], ],
'variants' => [ 'variants' => [
[ [
@ -97,7 +99,8 @@ function allPriceTypes(): array
['type' => 'tiktok', 'price' => 16000], ['type' => 'tiktok', 'price' => 16000],
['type' => 'shopee', 'price' => 16500], ['type' => 'shopee', 'price' => 16500],
['type' => 'capital', 'price' => 8000], ['type' => 'capital', 'price' => 8000],
['type' => 'reject', 'price' => 5000], ['type' => 'reject_capital', 'price' => 5000],
['type' => 'reject_selling', 'price' => 6000],
]; ];
} }
@ -1946,7 +1949,8 @@ function allPriceTypes(): array
['type' => 'tiktok', 'price' => 16000], ['type' => 'tiktok', 'price' => 16000],
['type' => 'shopee', 'price' => 16500], ['type' => 'shopee', 'price' => 16500],
['type' => 'capital', 'price' => 8000], ['type' => 'capital', 'price' => 8000],
['type' => 'reject', 'price' => 5000], ['type' => 'reject_capital', 'price' => 5000],
['type' => 'reject_selling', 'price' => 6000],
]; ];
$response = $this->post(route('admin.master.products.store'), makeValidProductPayload([ $response = $this->post(route('admin.master.products.store'), makeValidProductPayload([
@ -1965,9 +1969,10 @@ function allPriceTypes(): array
$variant = ProductVariant::where('name', 'Semua Harga')->first(); $variant = ProductVariant::where('name', 'Semua Harga')->first();
$dbPrices = ProductPrice::where('variant_id', $variant->id)->get(); $dbPrices = ProductPrice::where('variant_id', $variant->id)->get();
expect($dbPrices)->toHaveCount(9); expect($dbPrices)->toHaveCount(10);
expect($dbPrices->firstWhere('type', 'distributor')->price)->toBe(10000); expect($dbPrices->firstWhere('type', 'distributor')->price)->toBe(10000);
expect($dbPrices->firstWhere('type', 'reject')->price)->toBe(5000); expect($dbPrices->firstWhere('type', 'reject_capital')->price)->toBe(5000);
expect($dbPrices->firstWhere('type', 'reject_selling')->price)->toBe(6000);
}); });
test('shared price applies to all variants', function () { test('shared price applies to all variants', function () {