feat: add nego_price handling to order management, update validation, formatting, and frontend components
This commit is contained in:
parent
8146da86c6
commit
607bae444e
@ -39,6 +39,7 @@ public function rules(): array
|
||||
'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'],
|
||||
'discount' => ['nullable', 'integer', 'min:0'],
|
||||
'nego_price' => ['nullable', 'integer', 'min:0'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'status' => ['nullable', Rule::enum(OrderStatus::class)],
|
||||
];
|
||||
@ -72,6 +73,7 @@ public function attributes(): array
|
||||
'tiktok_order_id' => 'ID pesanan TikTok Shop',
|
||||
'shopee_order_id' => 'ID pesanan Shopee',
|
||||
'discount' => 'diskon',
|
||||
'nego_price' => 'harga nego',
|
||||
'notes' => 'keterangan',
|
||||
'status' => 'status pesanan',
|
||||
'items' => 'produk',
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
#[Appends([
|
||||
'subtotal_formatted',
|
||||
'discount_formatted',
|
||||
'nego_price_formatted',
|
||||
'total_amount_formatted',
|
||||
'created_at_formatted',
|
||||
'channel_label',
|
||||
@ -45,6 +46,7 @@ protected function casts(): array
|
||||
'status' => OrderStatus::class,
|
||||
'subtotal' => 'integer',
|
||||
'discount' => 'integer',
|
||||
'nego_price' => 'integer',
|
||||
'marketplace_settings_snapshot' => 'array',
|
||||
'total_amount' => 'integer',
|
||||
];
|
||||
@ -96,6 +98,13 @@ public function discountFormatted(): Attribute
|
||||
);
|
||||
}
|
||||
|
||||
public function negoPriceFormatted(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->nego_price !== null ? 'Rp '.number_format($this->nego_price, 0, ',', '.') : null,
|
||||
);
|
||||
}
|
||||
|
||||
public function totalAmountFormatted(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@ -437,7 +437,8 @@ public function create(array $validated, User $user): Order
|
||||
|
||||
$subtotal = $draftItems->sum('subtotal');
|
||||
$discount = (int) ($validated['discount'] ?? 0);
|
||||
$totalAmount = max($subtotal - $discount, 0);
|
||||
$negoPrice = isset($validated['nego_price']) && $validated['nego_price'] !== '' ? (int) $validated['nego_price'] : null;
|
||||
$totalAmount = $negoPrice !== null ? max($negoPrice, 0) : max($subtotal - $discount, 0);
|
||||
$channel = OrderChannel::from($validated['channel']);
|
||||
|
||||
$status = isset($validated['status'])
|
||||
@ -457,6 +458,7 @@ public function create(array $validated, User $user): Order
|
||||
'created_by_id' => $user->id,
|
||||
'subtotal' => $subtotal,
|
||||
'discount' => $discount,
|
||||
'nego_price' => $negoPrice,
|
||||
'marketplace_settings_snapshot' => $this->marketplaceService->buildOrderSnapshot(
|
||||
$channel,
|
||||
$totalAmount,
|
||||
@ -549,7 +551,8 @@ public function update(Order $order, array $validated): void
|
||||
$lineItems = $this->buildLineItems($validated['items'], $priceType);
|
||||
$subtotal = array_sum(array_column($lineItems, 'subtotal'));
|
||||
$discount = (int) ($validated['discount'] ?? 0);
|
||||
$totalAmount = max($subtotal - $discount, 0);
|
||||
$negoPrice = isset($validated['nego_price']) && $validated['nego_price'] !== '' ? (int) $validated['nego_price'] : null;
|
||||
$totalAmount = $negoPrice !== null ? max($negoPrice, 0) : max($subtotal - $discount, 0);
|
||||
$channel = OrderChannel::from($validated['channel']);
|
||||
|
||||
$order->customer_id = $validated['customer_id'] ?? null;
|
||||
@ -562,6 +565,7 @@ public function update(Order $order, array $validated): void
|
||||
$order->shopee_order_id = $validated['shopee_order_id'] ?? null;
|
||||
$order->subtotal = $subtotal;
|
||||
$order->discount = $discount;
|
||||
$order->nego_price = $negoPrice;
|
||||
$order->marketplace_settings_snapshot = $this->marketplaceService->buildOrderSnapshot(
|
||||
$channel,
|
||||
$totalAmount,
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('orders', function (Blueprint $table): void {
|
||||
$table->unsignedBigInteger('nego_price')->nullable()->after('discount');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('orders', function (Blueprint $table): void {
|
||||
$table->dropColumn('nego_price');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -97,9 +97,14 @@ export function encodeOrderReceipt(
|
||||
const summaryRows: string[][] = [
|
||||
['Subtotal', order.subtotal_formatted],
|
||||
['Diskon', order.discount_formatted],
|
||||
['Total', order.total_amount_formatted],
|
||||
];
|
||||
|
||||
if (order.nego_price_formatted) {
|
||||
summaryRows.push(['Harga Nego', order.nego_price_formatted]);
|
||||
}
|
||||
|
||||
summaryRows.push(['Total', order.total_amount_formatted]);
|
||||
|
||||
encoder.table(
|
||||
[
|
||||
{ width: labelColumnWidth, align: 'left' },
|
||||
|
||||
@ -36,6 +36,7 @@ const initialData = computed(() => ({
|
||||
tiktok_order_id: props.order.tiktok_order_id ?? '',
|
||||
shopee_order_id: props.order.shopee_order_id ?? '',
|
||||
discount: String(props.order.discount),
|
||||
nego_price: props.order.nego_price != null ? String(props.order.nego_price) : '',
|
||||
notes: props.order.notes ?? '',
|
||||
status: props.order.status,
|
||||
items: props.order.items.map((item) => ({
|
||||
|
||||
@ -142,6 +142,10 @@ const summaryRows = computed(() => {
|
||||
rows.push({ label: 'Diskon', value: `-${props.order.discount_formatted}` });
|
||||
}
|
||||
|
||||
if (props.order.nego_price != null && props.order.nego_price > 0) {
|
||||
rows.push({ label: 'Harga Nego', value: props.order.nego_price_formatted });
|
||||
}
|
||||
|
||||
return rows;
|
||||
});
|
||||
|
||||
|
||||
@ -75,6 +75,7 @@ const props = defineProps<{
|
||||
tiktok_order_id: string;
|
||||
shopee_order_id: string;
|
||||
discount: string;
|
||||
nego_price: string;
|
||||
notes: string;
|
||||
status: string;
|
||||
items: OrderCartItem[];
|
||||
@ -131,6 +132,7 @@ const form = useForm({
|
||||
tiktok_order_id: '',
|
||||
shopee_order_id: '',
|
||||
discount: '',
|
||||
nego_price: '',
|
||||
notes: '',
|
||||
status: OrderStatus.PENDING,
|
||||
});
|
||||
@ -152,6 +154,7 @@ function populateForm() {
|
||||
form.tiktok_order_id = props.initialData.tiktok_order_id;
|
||||
form.shopee_order_id = props.initialData.shopee_order_id;
|
||||
form.discount = props.initialData.discount;
|
||||
form.nego_price = props.initialData.nego_price;
|
||||
form.notes = props.initialData.notes;
|
||||
form.status = props.initialData.status || OrderStatus.PENDING;
|
||||
cart.value = props.initialData.items.map((item) => ({
|
||||
@ -260,7 +263,13 @@ const subtotal = computed(() =>
|
||||
);
|
||||
|
||||
const discountAmount = computed(() => Number(parseRupiah(form.discount)) || 0);
|
||||
const totalAmount = computed(() => Math.max(subtotal.value - discountAmount.value, 0));
|
||||
const negoPriceAmount = computed(() => Number(parseRupiah(form.nego_price)) || 0);
|
||||
const totalAmount = computed(() => {
|
||||
if (negoPriceAmount.value > 0) {
|
||||
return negoPriceAmount.value;
|
||||
}
|
||||
return Math.max(subtotal.value - discountAmount.value, 0);
|
||||
});
|
||||
|
||||
function getVariantPrice(variant: ProductVariantItem): ProductPriceItem | undefined {
|
||||
return variant.prices.find((price) => price.type === form.price_type);
|
||||
@ -475,6 +484,7 @@ function buildFormData(): FormData {
|
||||
}
|
||||
|
||||
formData.append('discount', parseRupiah(form.discount));
|
||||
formData.append('nego_price', parseRupiah(form.nego_price));
|
||||
formData.append('notes', form.notes);
|
||||
formData.append('status', form.status);
|
||||
|
||||
@ -841,6 +851,12 @@ function submit() {
|
||||
<RupiahInput id="discount" v-model="form.discount" placeholder="0" />
|
||||
<FieldError :errors="formErrors(form, 'discount')" />
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel for="nego_price">Harga Nego</FieldLabel>
|
||||
<RupiahInput id="nego_price" v-model="form.nego_price" placeholder="Kosongkan jika tidak ada nego" />
|
||||
<p class="text-xs text-muted-foreground">Jika diisi, harga nego menjadi total akhir.</p>
|
||||
<FieldError :errors="formErrors(form, 'nego_price')" />
|
||||
</Field>
|
||||
<div class="flex justify-between text-base font-semibold">
|
||||
<span>Total</span>
|
||||
<span class="text-primary">Rp {{ formatRupiah(totalAmount) }}</span>
|
||||
|
||||
@ -126,6 +126,8 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
||||
}}</strong></span>
|
||||
<span>Diskon <strong class="text-primary">{{ order.discount_formatted
|
||||
}}</strong></span>
|
||||
<span v-if="order.nego_price_formatted">Harga Nego <strong class="text-primary">{{ order.nego_price_formatted
|
||||
}}</strong></span>
|
||||
<span>Total <strong class="text-primary">{{ order.total_amount_formatted
|
||||
}}</strong></span>
|
||||
<span v-if="order.marketplace_settings_snapshot?.total_fee_amount" class="text-destructive">
|
||||
|
||||
@ -52,6 +52,7 @@ export type OrderListItem = {
|
||||
shopee_order_id: string | null;
|
||||
subtotal_formatted: string;
|
||||
discount_formatted: string;
|
||||
nego_price_formatted: string | null;
|
||||
total_amount: number;
|
||||
total_amount_formatted: string;
|
||||
notes: string | null;
|
||||
@ -118,6 +119,7 @@ export type OrderEditItem = {
|
||||
tiktok_order_id: string | null;
|
||||
shopee_order_id: string | null;
|
||||
discount: number;
|
||||
nego_price: number | null;
|
||||
notes: string | null;
|
||||
items: Array<{
|
||||
product_variant_id: number;
|
||||
@ -152,6 +154,8 @@ export type OrderDetail = {
|
||||
subtotal_formatted: string;
|
||||
discount: number;
|
||||
discount_formatted: string;
|
||||
nego_price: number | null;
|
||||
nego_price_formatted: string | null;
|
||||
total_amount: number;
|
||||
total_amount_formatted: string;
|
||||
notes: string | null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user