Refactor order management to remove marketplace fee handling. Update OrderRequest, Order model, and related services to utilize marketplace settings snapshot instead. Adjust frontend components and TypeScript types accordingly for consistency in order processing.
This commit is contained in:
parent
64088c82bb
commit
584387504c
@ -31,7 +31,6 @@ public function rules(): array
|
|||||||
'channel' => ['required', Rule::enum(OrderChannel::class)],
|
'channel' => ['required', Rule::enum(OrderChannel::class)],
|
||||||
'price_type' => ['required', Rule::enum(PriceType::class)],
|
'price_type' => ['required', Rule::enum(PriceType::class)],
|
||||||
'discount' => ['nullable', 'integer', 'min:0'],
|
'discount' => ['nullable', 'integer', 'min:0'],
|
||||||
'marketplace_fee' => ['nullable', 'integer', 'min:0'],
|
|
||||||
'notes' => ['nullable', 'string'],
|
'notes' => ['nullable', 'string'],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -58,7 +57,6 @@ public function attributes(): array
|
|||||||
'channel' => 'channel',
|
'channel' => 'channel',
|
||||||
'price_type' => 'tipe harga',
|
'price_type' => 'tipe harga',
|
||||||
'discount' => 'diskon',
|
'discount' => 'diskon',
|
||||||
'marketplace_fee' => 'biaya marketplace',
|
|
||||||
'notes' => 'keterangan',
|
'notes' => 'keterangan',
|
||||||
'items' => 'produk',
|
'items' => 'produk',
|
||||||
'items.*.product_variant_id' => 'varian produk',
|
'items.*.product_variant_id' => 'varian produk',
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
#[Appends([
|
#[Appends([
|
||||||
'subtotal_formatted',
|
'subtotal_formatted',
|
||||||
'discount_formatted',
|
'discount_formatted',
|
||||||
'marketplace_fee_formatted',
|
|
||||||
'total_amount_formatted',
|
'total_amount_formatted',
|
||||||
'created_at_formatted',
|
'created_at_formatted',
|
||||||
'channel_label',
|
'channel_label',
|
||||||
@ -40,7 +39,7 @@ protected function casts(): array
|
|||||||
'status' => OrderStatus::class,
|
'status' => OrderStatus::class,
|
||||||
'subtotal' => 'integer',
|
'subtotal' => 'integer',
|
||||||
'discount' => 'integer',
|
'discount' => 'integer',
|
||||||
'marketplace_fee' => 'integer',
|
'marketplace_settings_snapshot' => 'array',
|
||||||
'total_amount' => 'integer',
|
'total_amount' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -86,13 +85,6 @@ public function discountFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function marketplaceFeeFormatted(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: fn () => 'Rp '.number_format($this->marketplace_fee, 0, ',', '.'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function totalAmountFormatted(): Attribute
|
public function totalAmountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
use App\Models\ProductPrice;
|
use App\Models\ProductPrice;
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Services\System\Setting\MarketplaceService;
|
||||||
use App\Support\Media\MediaPresenter;
|
use App\Support\Media\MediaPresenter;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
@ -22,6 +23,10 @@
|
|||||||
|
|
||||||
class OrderService
|
class OrderService
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly MarketplaceService $marketplaceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||||
*/
|
*/
|
||||||
@ -283,18 +288,18 @@ public function create(array $validated, User $user): Order
|
|||||||
|
|
||||||
$subtotal = $draftItems->sum('subtotal');
|
$subtotal = $draftItems->sum('subtotal');
|
||||||
$discount = (int) ($validated['discount'] ?? 0);
|
$discount = (int) ($validated['discount'] ?? 0);
|
||||||
$marketplaceFee = (int) ($validated['marketplace_fee'] ?? 0);
|
$totalAmount = max($subtotal - $discount, 0);
|
||||||
$totalAmount = max($subtotal - $discount - $marketplaceFee, 0);
|
$channel = OrderChannel::from($validated['channel']);
|
||||||
|
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
'customer_id' => $validated['customer_id'] ?? null,
|
'customer_id' => $validated['customer_id'] ?? null,
|
||||||
'channel' => $validated['channel'],
|
'channel' => $channel,
|
||||||
'price_type' => $priceType,
|
'price_type' => $priceType,
|
||||||
'status' => OrderStatus::PENDING,
|
'status' => OrderStatus::PENDING,
|
||||||
'created_by_id' => $user->id,
|
'created_by_id' => $user->id,
|
||||||
'subtotal' => $subtotal,
|
'subtotal' => $subtotal,
|
||||||
'discount' => $discount,
|
'discount' => $discount,
|
||||||
'marketplace_fee' => $marketplaceFee,
|
'marketplace_settings_snapshot' => $this->marketplaceService->snapshotForChannel($channel),
|
||||||
'total_amount' => $totalAmount,
|
'total_amount' => $totalAmount,
|
||||||
'notes' => $validated['notes'] ?? null,
|
'notes' => $validated['notes'] ?? null,
|
||||||
]);
|
]);
|
||||||
@ -333,15 +338,15 @@ public function update(Order $order, array $validated): void
|
|||||||
$lineItems = $this->buildLineItems($validated['items'], $priceType);
|
$lineItems = $this->buildLineItems($validated['items'], $priceType);
|
||||||
$subtotal = array_sum(array_column($lineItems, 'subtotal'));
|
$subtotal = array_sum(array_column($lineItems, 'subtotal'));
|
||||||
$discount = (int) ($validated['discount'] ?? 0);
|
$discount = (int) ($validated['discount'] ?? 0);
|
||||||
$marketplaceFee = (int) ($validated['marketplace_fee'] ?? 0);
|
$totalAmount = max($subtotal - $discount, 0);
|
||||||
$totalAmount = max($subtotal - $discount - $marketplaceFee, 0);
|
$channel = OrderChannel::from($validated['channel']);
|
||||||
|
|
||||||
$order->customer_id = $validated['customer_id'] ?? null;
|
$order->customer_id = $validated['customer_id'] ?? null;
|
||||||
$order->channel = $validated['channel'];
|
$order->channel = $channel;
|
||||||
$order->price_type = $priceType;
|
$order->price_type = $priceType;
|
||||||
$order->subtotal = $subtotal;
|
$order->subtotal = $subtotal;
|
||||||
$order->discount = $discount;
|
$order->discount = $discount;
|
||||||
$order->marketplace_fee = $marketplaceFee;
|
$order->marketplace_settings_snapshot = $this->marketplaceService->snapshotForChannel($channel);
|
||||||
$order->total_amount = $totalAmount;
|
$order->total_amount = $totalAmount;
|
||||||
$order->notes = $validated['notes'] ?? null;
|
$order->notes = $validated['notes'] ?? null;
|
||||||
$order->save();
|
$order->save();
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Services\System\Setting;
|
namespace App\Services\System\Setting;
|
||||||
|
|
||||||
|
use App\Enums\OrderChannel;
|
||||||
use App\Settings\MarketplaceSettings;
|
use App\Settings\MarketplaceSettings;
|
||||||
|
|
||||||
class MarketplaceService
|
class MarketplaceService
|
||||||
@ -52,4 +53,35 @@ public function updateMarketplace(array $validated): void
|
|||||||
|
|
||||||
$settings->save();
|
$settings->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
public function snapshotForChannel(OrderChannel $channel): ?array
|
||||||
|
{
|
||||||
|
$settings = app(MarketplaceSettings::class);
|
||||||
|
|
||||||
|
return match ($channel) {
|
||||||
|
OrderChannel::SHOPEE => [
|
||||||
|
'platform' => 'shopee',
|
||||||
|
'commission_fee' => $settings->shopee_commission_fee,
|
||||||
|
'transaction_fee' => $settings->shopee_transaction_fee,
|
||||||
|
'service_fee' => $settings->shopee_service_fee,
|
||||||
|
'payment_fee' => $settings->shopee_payment_fee,
|
||||||
|
'affiliate_commission' => $settings->shopee_affiliate_commission,
|
||||||
|
'shipping_subsidy' => $settings->shopee_shipping_subsidy,
|
||||||
|
'voucher_fee' => $settings->shopee_voucher_fee,
|
||||||
|
],
|
||||||
|
OrderChannel::TIKTOK => [
|
||||||
|
'platform' => 'tiktok',
|
||||||
|
'admin_fee' => $settings->tiktok_shop_admin_fee,
|
||||||
|
'transaction_fee' => $settings->tiktok_shop_transaction_fee,
|
||||||
|
'payment_fee' => $settings->tiktok_shop_payment_fee,
|
||||||
|
'affiliate_commission' => $settings->tiktok_shop_affiliate_commission,
|
||||||
|
'shipping_subsidy' => $settings->tiktok_shop_shipping_subsidy,
|
||||||
|
'vat_rate' => $settings->tiktok_shop_vat_rate,
|
||||||
|
],
|
||||||
|
OrderChannel::STORE => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ public function up(): void
|
|||||||
$table->enum('status', array_column(OrderStatus::cases(), 'value'))->default(OrderStatus::PENDING->value);
|
$table->enum('status', array_column(OrderStatus::cases(), 'value'))->default(OrderStatus::PENDING->value);
|
||||||
$table->unsignedBigInteger('subtotal');
|
$table->unsignedBigInteger('subtotal');
|
||||||
$table->unsignedBigInteger('discount')->default(0);
|
$table->unsignedBigInteger('discount')->default(0);
|
||||||
|
$table->json('marketplace_settings_snapshot')->nullable();
|
||||||
$table->unsignedBigInteger('total_amount');
|
$table->unsignedBigInteger('total_amount');
|
||||||
$table->text('notes')->nullable();
|
$table->text('notes')->nullable();
|
||||||
|
|
||||||
|
|||||||
@ -113,10 +113,6 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
|||||||
}}</strong></span>
|
}}</strong></span>
|
||||||
<span>Diskon <strong class="text-foreground">{{ order.discount_formatted
|
<span>Diskon <strong class="text-foreground">{{ order.discount_formatted
|
||||||
}}</strong></span>
|
}}</strong></span>
|
||||||
<span v-if="order.channel !== 'store'">
|
|
||||||
Biaya MP <strong class="text-foreground">{{ order.marketplace_fee_formatted
|
|
||||||
}}</strong>
|
|
||||||
</span>
|
|
||||||
<span>Total <strong class="text-primary">{{ order.total_amount_formatted
|
<span>Total <strong class="text-primary">{{ order.total_amount_formatted
|
||||||
}}</strong></span>
|
}}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -49,7 +49,6 @@ const props = defineProps<{
|
|||||||
channel: string;
|
channel: string;
|
||||||
price_type: string;
|
price_type: string;
|
||||||
discount: string;
|
discount: string;
|
||||||
marketplace_fee: string;
|
|
||||||
notes: string;
|
notes: string;
|
||||||
items: OrderCartItem[];
|
items: OrderCartItem[];
|
||||||
};
|
};
|
||||||
@ -69,12 +68,10 @@ const form = useForm({
|
|||||||
channel: 'store',
|
channel: 'store',
|
||||||
price_type: 'ecer',
|
price_type: 'ecer',
|
||||||
discount: '',
|
discount: '',
|
||||||
marketplace_fee: '',
|
|
||||||
notes: '',
|
notes: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const isStoreChannel = computed(() => form.channel === 'store');
|
const isStoreChannel = computed(() => form.channel === 'store');
|
||||||
const isMarketplaceChannel = computed(() => form.channel === 'shopee' || form.channel === 'tiktok');
|
|
||||||
|
|
||||||
function populateForm() {
|
function populateForm() {
|
||||||
if (!props.initialData) {
|
if (!props.initialData) {
|
||||||
@ -85,7 +82,6 @@ function populateForm() {
|
|||||||
form.channel = props.initialData.channel;
|
form.channel = props.initialData.channel;
|
||||||
form.price_type = props.initialData.price_type;
|
form.price_type = props.initialData.price_type;
|
||||||
form.discount = props.initialData.discount;
|
form.discount = props.initialData.discount;
|
||||||
form.marketplace_fee = props.initialData.marketplace_fee;
|
|
||||||
form.notes = props.initialData.notes;
|
form.notes = props.initialData.notes;
|
||||||
cart.value = props.initialData.items.map((item) => ({ ...item }));
|
cart.value = props.initialData.items.map((item) => ({ ...item }));
|
||||||
}
|
}
|
||||||
@ -177,8 +173,7 @@ const subtotal = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const discountAmount = computed(() => Number(parseRupiah(form.discount)) || 0);
|
const discountAmount = computed(() => Number(parseRupiah(form.discount)) || 0);
|
||||||
const marketplaceFeeAmount = computed(() => Number(parseRupiah(form.marketplace_fee)) || 0);
|
const totalAmount = computed(() => Math.max(subtotal.value - discountAmount.value, 0));
|
||||||
const totalAmount = computed(() => Math.max(subtotal.value - discountAmount.value - marketplaceFeeAmount.value, 0));
|
|
||||||
|
|
||||||
function getVariantPrice(variant: ProductVariantItem): ProductPriceItem | undefined {
|
function getVariantPrice(variant: ProductVariantItem): ProductPriceItem | undefined {
|
||||||
return variant.prices.find((price) => price.type === form.price_type);
|
return variant.prices.find((price) => price.type === form.price_type);
|
||||||
@ -319,7 +314,6 @@ function buildFormData(): FormData {
|
|||||||
formData.append('channel', form.channel);
|
formData.append('channel', form.channel);
|
||||||
formData.append('price_type', form.price_type);
|
formData.append('price_type', form.price_type);
|
||||||
formData.append('discount', parseRupiah(form.discount));
|
formData.append('discount', parseRupiah(form.discount));
|
||||||
formData.append('marketplace_fee', parseRupiah(form.marketplace_fee));
|
|
||||||
formData.append('notes', form.notes);
|
formData.append('notes', form.notes);
|
||||||
|
|
||||||
if (props.method === 'put') {
|
if (props.method === 'put') {
|
||||||
@ -552,11 +546,6 @@ function submit() {
|
|||||||
<RupiahInput id="discount" v-model="form.discount" placeholder="0" />
|
<RupiahInput id="discount" v-model="form.discount" placeholder="0" />
|
||||||
<FieldError :errors="formErrors(form, 'discount')" />
|
<FieldError :errors="formErrors(form, 'discount')" />
|
||||||
</Field>
|
</Field>
|
||||||
<Field v-if="isMarketplaceChannel">
|
|
||||||
<FieldLabel for="marketplace_fee">Biaya Marketplace</FieldLabel>
|
|
||||||
<RupiahInput id="marketplace_fee" v-model="form.marketplace_fee" placeholder="0" />
|
|
||||||
<FieldError :errors="formErrors(form, 'marketplace_fee')" />
|
|
||||||
</Field>
|
|
||||||
<div class="flex justify-between text-base font-semibold">
|
<div class="flex justify-between text-base font-semibold">
|
||||||
<span>Total</span>
|
<span>Total</span>
|
||||||
<span class="text-primary">Rp {{ formatRupiah(totalAmount) }}</span>
|
<span class="text-primary">Rp {{ formatRupiah(totalAmount) }}</span>
|
||||||
|
|||||||
@ -40,7 +40,6 @@ export function encodeOrderReceipt(
|
|||||||
const createdBy = order.created_by?.profile?.full_name
|
const createdBy = order.created_by?.profile?.full_name
|
||||||
?? order.created_by?.username
|
?? order.created_by?.username
|
||||||
?? '-';
|
?? '-';
|
||||||
const isMarketplace = order.channel !== 'store';
|
|
||||||
|
|
||||||
encoder.initialize()
|
encoder.initialize()
|
||||||
.align('center')
|
.align('center')
|
||||||
@ -98,14 +97,9 @@ export function encodeOrderReceipt(
|
|||||||
const summaryRows: string[][] = [
|
const summaryRows: string[][] = [
|
||||||
['Subtotal', order.subtotal_formatted],
|
['Subtotal', order.subtotal_formatted],
|
||||||
['Diskon', order.discount_formatted],
|
['Diskon', order.discount_formatted],
|
||||||
|
['Total', order.total_amount_formatted],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isMarketplace) {
|
|
||||||
summaryRows.push(['Biaya MP', order.marketplace_fee_formatted]);
|
|
||||||
}
|
|
||||||
|
|
||||||
summaryRows.push(['Total', order.total_amount_formatted]);
|
|
||||||
|
|
||||||
encoder.table(
|
encoder.table(
|
||||||
[
|
[
|
||||||
{ width: labelColumnWidth, align: 'left' },
|
{ width: labelColumnWidth, align: 'left' },
|
||||||
|
|||||||
@ -20,7 +20,6 @@ const initialData = computed(() => ({
|
|||||||
channel: props.order.channel,
|
channel: props.order.channel,
|
||||||
price_type: props.order.price_type,
|
price_type: props.order.price_type,
|
||||||
discount: String(props.order.discount),
|
discount: String(props.order.discount),
|
||||||
marketplace_fee: String(props.order.marketplace_fee),
|
|
||||||
notes: props.order.notes ?? '',
|
notes: props.order.notes ?? '',
|
||||||
items: props.order.items.map((item) => ({
|
items: props.order.items.map((item) => ({
|
||||||
product_variant_id: item.product_variant_id,
|
product_variant_id: item.product_variant_id,
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export type OrderListItem = {
|
|||||||
available_actions: OrderStatusAction[];
|
available_actions: OrderStatusAction[];
|
||||||
subtotal_formatted: string;
|
subtotal_formatted: string;
|
||||||
discount_formatted: string;
|
discount_formatted: string;
|
||||||
marketplace_fee_formatted: string;
|
|
||||||
total_amount_formatted: string;
|
total_amount_formatted: string;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
created_at_formatted: string;
|
created_at_formatted: string;
|
||||||
@ -81,7 +80,6 @@ export type OrderEditItem = {
|
|||||||
price_type: string;
|
price_type: string;
|
||||||
status: string;
|
status: string;
|
||||||
discount: number;
|
discount: number;
|
||||||
marketplace_fee: number;
|
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
items: Array<{
|
items: Array<{
|
||||||
product_variant_id: number;
|
product_variant_id: number;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user