feat: add shipping cost field to order management, including validation, formatting, and UI updates
This commit is contained in:
parent
8c2be8945d
commit
fe7a3e4813
@ -37,6 +37,7 @@ public function rules(): array
|
|||||||
'tiktok_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::TIKTOK->value), 'string', 'max:100'],
|
'tiktok_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::TIKTOK->value), 'string', 'max:100'],
|
||||||
'shopee_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::SHOPEE->value), 'string', 'max:100'],
|
'shopee_order_id' => [Rule::requiredIf(fn () => $this->channel === OrderChannel::SHOPEE->value), 'string', 'max:100'],
|
||||||
'discount' => ['nullable', 'integer', 'min:0'],
|
'discount' => ['nullable', 'integer', 'min:0'],
|
||||||
|
'shipping_cost' => ['nullable', 'integer', 'min:0'],
|
||||||
'notes' => ['nullable', 'string'],
|
'notes' => ['nullable', 'string'],
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -68,6 +69,7 @@ public function attributes(): array
|
|||||||
'tiktok_order_id' => 'ID pesanan TikTok Shop',
|
'tiktok_order_id' => 'ID pesanan TikTok Shop',
|
||||||
'shopee_order_id' => 'ID pesanan Shopee',
|
'shopee_order_id' => 'ID pesanan Shopee',
|
||||||
'discount' => 'diskon',
|
'discount' => 'diskon',
|
||||||
|
'shipping_cost' => 'ongkos kirim',
|
||||||
'notes' => 'keterangan',
|
'notes' => 'keterangan',
|
||||||
'items' => 'produk',
|
'items' => 'produk',
|
||||||
'items.*.product_variant_id' => 'varian produk',
|
'items.*.product_variant_id' => 'varian produk',
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#[Appends([
|
#[Appends([
|
||||||
'subtotal_formatted',
|
'subtotal_formatted',
|
||||||
'discount_formatted',
|
'discount_formatted',
|
||||||
|
'shipping_cost_formatted',
|
||||||
'total_amount_formatted',
|
'total_amount_formatted',
|
||||||
'created_at_formatted',
|
'created_at_formatted',
|
||||||
'channel_label',
|
'channel_label',
|
||||||
@ -43,6 +44,7 @@ protected function casts(): array
|
|||||||
'status' => OrderStatus::class,
|
'status' => OrderStatus::class,
|
||||||
'subtotal' => 'integer',
|
'subtotal' => 'integer',
|
||||||
'discount' => 'integer',
|
'discount' => 'integer',
|
||||||
|
'shipping_cost' => 'integer',
|
||||||
'marketplace_settings_snapshot' => 'array',
|
'marketplace_settings_snapshot' => 'array',
|
||||||
'total_amount' => 'integer',
|
'total_amount' => 'integer',
|
||||||
];
|
];
|
||||||
@ -94,6 +96,13 @@ public function discountFormatted(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function shippingCostFormatted(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => 'Rp '.number_format($this->shipping_cost, 0, ',', '.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function totalAmountFormatted(): Attribute
|
public function totalAmountFormatted(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
|||||||
@ -320,7 +320,8 @@ 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);
|
||||||
$totalAmount = max($subtotal - $discount, 0);
|
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
|
||||||
|
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
|
||||||
$channel = OrderChannel::from($validated['channel']);
|
$channel = OrderChannel::from($validated['channel']);
|
||||||
|
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
@ -336,6 +337,7 @@ public function create(array $validated, User $user): Order
|
|||||||
'created_by_id' => $user->id,
|
'created_by_id' => $user->id,
|
||||||
'subtotal' => $subtotal,
|
'subtotal' => $subtotal,
|
||||||
'discount' => $discount,
|
'discount' => $discount,
|
||||||
|
'shipping_cost' => $shippingCost,
|
||||||
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
|
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
|
||||||
$channel,
|
$channel,
|
||||||
$totalAmount,
|
$totalAmount,
|
||||||
@ -412,7 +414,8 @@ 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);
|
||||||
$totalAmount = max($subtotal - $discount, 0);
|
$shippingCost = (int) ($validated['shipping_cost'] ?? 0);
|
||||||
|
$totalAmount = max($subtotal - $discount + $shippingCost, 0);
|
||||||
$channel = OrderChannel::from($validated['channel']);
|
$channel = OrderChannel::from($validated['channel']);
|
||||||
|
|
||||||
$order->customer_id = $validated['customer_id'] ?? null;
|
$order->customer_id = $validated['customer_id'] ?? null;
|
||||||
@ -425,6 +428,7 @@ public function update(Order $order, array $validated): void
|
|||||||
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
|
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
|
||||||
$order->subtotal = $subtotal;
|
$order->subtotal = $subtotal;
|
||||||
$order->discount = $discount;
|
$order->discount = $discount;
|
||||||
|
$order->shipping_cost = $shippingCost;
|
||||||
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
|
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
|
||||||
$channel,
|
$channel,
|
||||||
$totalAmount,
|
$totalAmount,
|
||||||
|
|||||||
@ -28,6 +28,7 @@ public function up(): void
|
|||||||
$table->string('shopee_order_id', 100)->nullable();
|
$table->string('shopee_order_id', 100)->nullable();
|
||||||
$table->unsignedBigInteger('subtotal');
|
$table->unsignedBigInteger('subtotal');
|
||||||
$table->unsignedBigInteger('discount')->default(0);
|
$table->unsignedBigInteger('discount')->default(0);
|
||||||
|
$table->unsignedBigInteger('shipping_cost')->default(0);
|
||||||
$table->json('marketplace_settings_snapshot')->nullable();
|
$table->json('marketplace_settings_snapshot')->nullable();
|
||||||
$table->unsignedBigInteger('total_amount');
|
$table->unsignedBigInteger('total_amount');
|
||||||
$table->text('notes')->nullable();
|
$table->text('notes')->nullable();
|
||||||
|
|||||||
@ -124,6 +124,8 @@ 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.shipping_cost > 0">Ongkir <strong class="text-foreground">{{
|
||||||
|
order.shipping_cost_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>
|
||||||
|
|||||||
@ -63,6 +63,7 @@ const props = defineProps<{
|
|||||||
tiktok_order_id: string;
|
tiktok_order_id: string;
|
||||||
shopee_order_id: string;
|
shopee_order_id: string;
|
||||||
discount: string;
|
discount: string;
|
||||||
|
shipping_cost: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
items: OrderCartItem[];
|
items: OrderCartItem[];
|
||||||
};
|
};
|
||||||
@ -112,6 +113,7 @@ const form = useForm({
|
|||||||
tiktok_order_id: '',
|
tiktok_order_id: '',
|
||||||
shopee_order_id: '',
|
shopee_order_id: '',
|
||||||
discount: '',
|
discount: '',
|
||||||
|
shipping_cost: '',
|
||||||
notes: '',
|
notes: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -132,6 +134,7 @@ function populateForm() {
|
|||||||
form.tiktok_order_id = props.initialData.tiktok_order_id;
|
form.tiktok_order_id = props.initialData.tiktok_order_id;
|
||||||
form.shopee_order_id = props.initialData.shopee_order_id;
|
form.shopee_order_id = props.initialData.shopee_order_id;
|
||||||
form.discount = props.initialData.discount;
|
form.discount = props.initialData.discount;
|
||||||
|
form.shipping_cost = String(props.initialData.shipping_cost);
|
||||||
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 }));
|
||||||
}
|
}
|
||||||
@ -228,7 +231,8 @@ const subtotal = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const discountAmount = computed(() => Number(parseRupiah(form.discount)) || 0);
|
const discountAmount = computed(() => Number(parseRupiah(form.discount)) || 0);
|
||||||
const totalAmount = computed(() => Math.max(subtotal.value - discountAmount.value, 0));
|
const shippingAmount = computed(() => Number(parseRupiah(form.shipping_cost)) || 0);
|
||||||
|
const totalAmount = computed(() => Math.max(subtotal.value - discountAmount.value + shippingAmount.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);
|
||||||
@ -401,6 +405,7 @@ function buildFormData(): FormData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formData.append('discount', parseRupiah(form.discount));
|
formData.append('discount', parseRupiah(form.discount));
|
||||||
|
formData.append('shipping_cost', parseRupiah(form.shipping_cost));
|
||||||
formData.append('notes', form.notes);
|
formData.append('notes', form.notes);
|
||||||
|
|
||||||
if (isCreateMode.value && printAfterSave.value) {
|
if (isCreateMode.value && printAfterSave.value) {
|
||||||
@ -732,6 +737,11 @@ 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="isStoreChannel">
|
||||||
|
<FieldLabel for="shipping_cost">Ongkir</FieldLabel>
|
||||||
|
<RupiahInput id="shipping_cost" v-model="form.shipping_cost" placeholder="0" />
|
||||||
|
<FieldError :errors="formErrors(form, 'shipping_cost')" />
|
||||||
|
</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>
|
||||||
|
|||||||
@ -97,9 +97,14 @@ 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 (order.shipping_cost > 0) {
|
||||||
|
summaryRows.push(['Ongkir', order.shipping_cost_formatted]);
|
||||||
|
}
|
||||||
|
|
||||||
|
summaryRows.push(['Total', order.total_amount_formatted]);
|
||||||
|
|
||||||
encoder.table(
|
encoder.table(
|
||||||
[
|
[
|
||||||
{ width: labelColumnWidth, align: 'left' },
|
{ width: labelColumnWidth, align: 'left' },
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const initialData = computed(() => ({
|
|||||||
tiktok_order_id: props.order.tiktok_order_id ?? '',
|
tiktok_order_id: props.order.tiktok_order_id ?? '',
|
||||||
shopee_order_id: props.order.shopee_order_id ?? '',
|
shopee_order_id: props.order.shopee_order_id ?? '',
|
||||||
discount: String(props.order.discount),
|
discount: String(props.order.discount),
|
||||||
|
shipping_cost: String(props.order.shipping_cost),
|
||||||
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,
|
||||||
|
|||||||
@ -52,6 +52,8 @@ export type OrderListItem = {
|
|||||||
shopee_order_id: string | null;
|
shopee_order_id: string | null;
|
||||||
subtotal_formatted: string;
|
subtotal_formatted: string;
|
||||||
discount_formatted: string;
|
discount_formatted: string;
|
||||||
|
shipping_cost: number;
|
||||||
|
shipping_cost_formatted: string;
|
||||||
total_amount_formatted: string;
|
total_amount_formatted: string;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
created_at_formatted: string;
|
created_at_formatted: string;
|
||||||
@ -90,6 +92,7 @@ export type OrderEditItem = {
|
|||||||
tiktok_order_id: string | null;
|
tiktok_order_id: string | null;
|
||||||
shopee_order_id: string | null;
|
shopee_order_id: string | null;
|
||||||
discount: number;
|
discount: number;
|
||||||
|
shipping_cost: 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