feat: introduce PaymentType enum and integrate payment type selection in order management, including validation and UI updates
This commit is contained in:
parent
91f7858cfe
commit
191743632b
25
app/Enums/PaymentType.php
Normal file
25
app/Enums/PaymentType.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\ProvidesEnumOptions;
|
||||
|
||||
enum PaymentType: string
|
||||
{
|
||||
use ProvidesEnumOptions;
|
||||
|
||||
case CASH = 'cash';
|
||||
case TRANSFER = 'transfer';
|
||||
case QRIS = 'qris';
|
||||
case MARKETPLACE = 'marketplace';
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::CASH => 'Tunai',
|
||||
self::TRANSFER => 'Transfer',
|
||||
self::QRIS => 'Qris',
|
||||
self::MARKETPLACE => 'Marketplace',
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Http\Controllers\Concerns\FlashesEntityMessage;
|
||||
use App\Http\Controllers\Concerns\ParsesDataTableQuery;
|
||||
use App\Http\Controllers\Controller;
|
||||
@ -45,6 +46,7 @@ public function create(Request $request): Response
|
||||
'catalog' => $this->orderService->catalogItems(user: $user),
|
||||
'channels' => OrderChannel::selectOptions(),
|
||||
'storePriceTypes' => $this->orderService->storePriceTypeOptions(),
|
||||
'paymentTypes' => PaymentType::selectOptions(),
|
||||
'draftItems' => $this->orderService->draftItemsForUser($user),
|
||||
]);
|
||||
}
|
||||
@ -73,6 +75,7 @@ public function edit(Order $order): Response|RedirectResponse
|
||||
'catalog' => $this->orderService->catalogItems($order),
|
||||
'channels' => OrderChannel::selectOptions(),
|
||||
'storePriceTypes' => $this->orderService->storePriceTypeOptions(),
|
||||
'paymentTypes' => PaymentType::selectOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Requests\Admin\Manage;
|
||||
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Enums\Permission;
|
||||
use App\Enums\PriceType;
|
||||
use App\Models\Order;
|
||||
@ -31,6 +32,7 @@ public function rules(): array
|
||||
'marketing_id' => ['nullable', 'integer', Rule::exists('users', 'id')->whereNull('deleted_at')],
|
||||
'channel' => ['required', Rule::enum(OrderChannel::class)],
|
||||
'price_type' => ['required', Rule::enum(PriceType::class)],
|
||||
'payment_type' => ['required', Rule::enum(PaymentType::class)],
|
||||
'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'],
|
||||
@ -60,6 +62,7 @@ public function attributes(): array
|
||||
'marketing_id' => 'marketing',
|
||||
'channel' => 'channel',
|
||||
'price_type' => 'tipe harga',
|
||||
'payment_type' => 'tipe pembayaran',
|
||||
'tiktok_order_id' => 'ID pesanan TikTok Shop',
|
||||
'shopee_order_id' => 'ID pesanan Shopee',
|
||||
'discount' => 'diskon',
|
||||
|
||||
@ -135,6 +135,7 @@ public static function labelForReferenceType(?string $referenceType): string
|
||||
Expense::class => 'Pengeluaran',
|
||||
EmployeeAdvance::class => 'Kasbon Pegawai',
|
||||
Payroll::class => 'Gaji Pegawai',
|
||||
Order::class => 'Pesanan',
|
||||
default => class_basename($referenceType),
|
||||
};
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Enums\PriceType;
|
||||
use App\Models\Concerns\InteractsWithActivityLog;
|
||||
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||
@ -22,6 +23,7 @@
|
||||
'total_amount_formatted',
|
||||
'created_at_formatted',
|
||||
'channel_label',
|
||||
'payment_type_label',
|
||||
'price_type_label',
|
||||
'status_label',
|
||||
])]
|
||||
@ -36,6 +38,7 @@ protected function casts(): array
|
||||
return [
|
||||
'channel' => OrderChannel::class,
|
||||
'price_type' => PriceType::class,
|
||||
'payment_type' => PaymentType::class,
|
||||
'status' => OrderStatus::class,
|
||||
'subtotal' => 'integer',
|
||||
'discount' => 'integer',
|
||||
@ -97,6 +100,13 @@ public function totalAmountFormatted(): Attribute
|
||||
);
|
||||
}
|
||||
|
||||
public function paymentTypeLabel(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->payment_type->label(),
|
||||
);
|
||||
}
|
||||
|
||||
public function priceTypeLabel(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Enums\PriceType;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Order;
|
||||
@ -12,6 +13,7 @@
|
||||
use App\Models\ProductPrice;
|
||||
use App\Models\ProductVariant;
|
||||
use App\Models\User;
|
||||
use App\Services\Finance\CashService;
|
||||
use App\Services\System\PushNotificationService;
|
||||
use App\Services\System\Setting\MarketplaceService;
|
||||
use App\Support\Media\MediaPresenter;
|
||||
@ -27,6 +29,7 @@ class OrderService
|
||||
public function __construct(
|
||||
private readonly MarketplaceService $marketplaceService,
|
||||
private readonly PushNotificationService $pushNotificationService,
|
||||
private readonly CashService $cashService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -325,6 +328,7 @@ public function create(array $validated, User $user): Order
|
||||
'marketing_id' => $validated['marketing_id'] ?? null,
|
||||
'channel' => $channel,
|
||||
'price_type' => $priceType,
|
||||
'payment_type' => PaymentType::from($validated['payment_type']),
|
||||
'status' => OrderStatus::PENDING,
|
||||
'tiktok_order_id' => $validated['tiktok_order_id'] ?? null,
|
||||
'shopee_order_id' => $validated['shopee_order_id'] ?? null,
|
||||
@ -340,6 +344,18 @@ public function create(array $validated, User $user): Order
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
]);
|
||||
|
||||
if ($order->payment_type === PaymentType::CASH) {
|
||||
$cashTransaction = $this->cashService->recordIncoming(
|
||||
$order,
|
||||
$totalAmount,
|
||||
"Pembayaran pesanan {$order->order_number}",
|
||||
$user,
|
||||
);
|
||||
|
||||
$order->cash_transaction_id = $cashTransaction->id;
|
||||
$order->save();
|
||||
}
|
||||
|
||||
foreach ($draftItems as $item) {
|
||||
$variant = ProductVariant::query()->with('product')->lockForUpdate()->find($item->product_variant_id);
|
||||
if ($variant === null) {
|
||||
@ -453,6 +469,10 @@ public function delete(Order $order): void
|
||||
}
|
||||
}
|
||||
|
||||
if ($order->cashTransaction) {
|
||||
$this->cashService->deleteReferencedTransaction($order->cashTransaction);
|
||||
}
|
||||
|
||||
$order->items()->delete();
|
||||
$order->delete();
|
||||
});
|
||||
@ -480,6 +500,11 @@ public function transitionStatus(Order $order, OrderStatus $status): void
|
||||
foreach ($order->items as $item) {
|
||||
$this->incrementStock($item);
|
||||
}
|
||||
|
||||
if ($order->cashTransaction) {
|
||||
$this->cashService->deleteReferencedTransaction($order->cashTransaction);
|
||||
$order->cash_transaction_id = null;
|
||||
}
|
||||
}
|
||||
|
||||
$order->status = $status;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\OrderStatus;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Enums\PriceType;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@ -21,6 +22,7 @@ public function up(): void
|
||||
$table->enum('channel', array_column(OrderChannel::cases(), 'value'));
|
||||
$table->enum('price_type', array_column(PriceType::cases(), 'value'));
|
||||
$table->enum('status', array_column(OrderStatus::cases(), 'value'))->default(OrderStatus::PENDING->value);
|
||||
$table->enum('payment_type', array_column(PaymentType::cases(), 'value'))->default(PaymentType::CASH->value);
|
||||
$table->string('tiktok_order_id', 100)->nullable();
|
||||
$table->string('shopee_order_id', 100)->nullable();
|
||||
$table->unsignedBigInteger('subtotal');
|
||||
|
||||
@ -114,9 +114,11 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||
<span>Tipe Harga <strong class="text-foreground">{{ order.price_type_label
|
||||
}}</strong></span>
|
||||
}}</strong></span>
|
||||
<span>Pembayaran <strong class="text-foreground">{{ order.payment_type_label
|
||||
}}</strong></span>
|
||||
<span>Subtotal <strong class="text-foreground">{{ order.subtotal_formatted
|
||||
}}</strong></span>
|
||||
}}</strong></span>
|
||||
<span>Diskon <strong class="text-foreground">{{ order.discount_formatted
|
||||
}}</strong></span>
|
||||
<span>Total <strong class="text-primary">{{ order.total_amount_formatted
|
||||
|
||||
@ -48,11 +48,13 @@ const props = defineProps<{
|
||||
catalog: OrderCatalogItem[];
|
||||
channels: EnumOption[];
|
||||
storePriceTypes: EnumOption[];
|
||||
paymentTypes: EnumOption[];
|
||||
initialData?: {
|
||||
customer_id: string;
|
||||
marketing_id: string;
|
||||
channel: string;
|
||||
price_type: string;
|
||||
payment_type: string;
|
||||
tiktok_order_id: string;
|
||||
shopee_order_id: string;
|
||||
discount: string;
|
||||
@ -86,6 +88,7 @@ const form = useForm({
|
||||
marketing_id: defaultMarketingId.value || 'none',
|
||||
channel: 'store',
|
||||
price_type: 'ecer',
|
||||
payment_type: 'cash',
|
||||
tiktok_order_id: '',
|
||||
shopee_order_id: '',
|
||||
discount: '',
|
||||
@ -103,6 +106,7 @@ function populateForm() {
|
||||
form.marketing_id = props.initialData.marketing_id || 'none';
|
||||
form.channel = props.initialData.channel;
|
||||
form.price_type = props.initialData.price_type;
|
||||
form.payment_type = props.initialData.payment_type;
|
||||
form.tiktok_order_id = props.initialData.tiktok_order_id;
|
||||
form.shopee_order_id = props.initialData.shopee_order_id;
|
||||
form.discount = props.initialData.discount;
|
||||
@ -354,6 +358,7 @@ function buildFormData(): FormData {
|
||||
|
||||
formData.append('channel', form.channel);
|
||||
formData.append('price_type', form.price_type);
|
||||
formData.append('payment_type', form.payment_type);
|
||||
|
||||
if (form.channel === 'tiktok' && form.tiktok_order_id) {
|
||||
formData.append('tiktok_order_id', form.tiktok_order_id);
|
||||
@ -539,6 +544,24 @@ function submit() {
|
||||
<FieldError :errors="formErrors(form, 'shopee_order_id')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<FieldLabel for="payment_type" required>Tipe Pembayaran</FieldLabel>
|
||||
<Select v-model="form.payment_type">
|
||||
<SelectTrigger id="payment_type" class="w-full">
|
||||
<SelectValue placeholder="Pilih tipe pembayaran" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="pt in paymentTypes" :key="pt.value"
|
||||
:value="pt.value">
|
||||
{{ pt.label }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FieldError :errors="formErrors(form, 'payment_type')" />
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<FieldLabel for="customer">Pelanggan</FieldLabel>
|
||||
<Select v-model="form.customer_id">
|
||||
|
||||
@ -12,6 +12,7 @@ defineProps<{
|
||||
catalog: OrderCatalogItem[];
|
||||
channels: EnumOption[];
|
||||
storePriceTypes: EnumOption[];
|
||||
paymentTypes: EnumOption[];
|
||||
draftItems: OrderCartItem[];
|
||||
}>();
|
||||
</script>
|
||||
@ -41,6 +42,7 @@ defineProps<{
|
||||
:catalog="catalog"
|
||||
:channels="channels"
|
||||
:store-price-types="storePriceTypes"
|
||||
:payment-types="paymentTypes"
|
||||
:draft-items="draftItems"
|
||||
submit-url="/admin/manage/orders"
|
||||
method="post"
|
||||
|
||||
@ -14,6 +14,7 @@ const props = defineProps<{
|
||||
catalog: OrderCatalogItem[];
|
||||
channels: EnumOption[];
|
||||
storePriceTypes: EnumOption[];
|
||||
paymentTypes: EnumOption[];
|
||||
}>();
|
||||
|
||||
const initialData = computed(() => ({
|
||||
@ -21,6 +22,7 @@ const initialData = computed(() => ({
|
||||
marketing_id: props.order.marketing_id ? String(props.order.marketing_id) : '',
|
||||
channel: props.order.channel,
|
||||
price_type: props.order.price_type,
|
||||
payment_type: props.order.payment_type,
|
||||
tiktok_order_id: props.order.tiktok_order_id ?? '',
|
||||
shopee_order_id: props.order.shopee_order_id ?? '',
|
||||
discount: String(props.order.discount),
|
||||
@ -64,6 +66,7 @@ const initialData = computed(() => ({
|
||||
:catalog="catalog"
|
||||
:channels="channels"
|
||||
:store-price-types="storePriceTypes"
|
||||
:payment-types="paymentTypes"
|
||||
:initial-data="initialData"
|
||||
:submit-url="`/admin/manage/orders/${order.id}`"
|
||||
method="put"
|
||||
|
||||
@ -41,6 +41,8 @@ export type OrderListItem = {
|
||||
channel_label: string;
|
||||
price_type: string;
|
||||
price_type_label: string;
|
||||
payment_type: string;
|
||||
payment_type_label: string;
|
||||
status: string;
|
||||
status_label: string;
|
||||
is_editable: boolean;
|
||||
@ -81,6 +83,7 @@ export type OrderEditItem = {
|
||||
marketing_id: number | null;
|
||||
channel: string;
|
||||
price_type: string;
|
||||
payment_type: string;
|
||||
status: string;
|
||||
tiktok_order_id: string | null;
|
||||
shopee_order_id: string | null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user