diff --git a/app/Enums/PaymentType.php b/app/Enums/PaymentType.php new file mode 100644 index 0000000..3d29314 --- /dev/null +++ b/app/Enums/PaymentType.php @@ -0,0 +1,25 @@ + 'Tunai', + self::TRANSFER => 'Transfer', + self::QRIS => 'Qris', + self::MARKETPLACE => 'Marketplace', + }; + } +} diff --git a/app/Http/Controllers/Admin/Manage/OrderController.php b/app/Http/Controllers/Admin/Manage/OrderController.php index 469b341..429feaf 100644 --- a/app/Http/Controllers/Admin/Manage/OrderController.php +++ b/app/Http/Controllers/Admin/Manage/OrderController.php @@ -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(), ]); } diff --git a/app/Http/Requests/Admin/Manage/OrderRequest.php b/app/Http/Requests/Admin/Manage/OrderRequest.php index ef733f4..6c6eb24 100644 --- a/app/Http/Requests/Admin/Manage/OrderRequest.php +++ b/app/Http/Requests/Admin/Manage/OrderRequest.php @@ -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', diff --git a/app/Models/CashTransaction.php b/app/Models/CashTransaction.php index b9487e6..c4af611 100644 --- a/app/Models/CashTransaction.php +++ b/app/Models/CashTransaction.php @@ -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), }; } diff --git a/app/Models/Order.php b/app/Models/Order.php index a9593b3..f904c72 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -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( diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php index 478a37e..8590efd 100644 --- a/app/Services/Manage/OrderService.php +++ b/app/Services/Manage/OrderService.php @@ -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; diff --git a/database/migrations/2026_06_12_100001_create_orders_table.php b/database/migrations/2026_06_12_100001_create_orders_table.php index 4490f75..f9975a5 100644 --- a/database/migrations/2026_06_12_100001_create_orders_table.php +++ b/database/migrations/2026_06_12_100001_create_orders_table.php @@ -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'); diff --git a/resources/js/components/admin/manage/orders/OrderGroupedTable.vue b/resources/js/components/admin/manage/orders/OrderGroupedTable.vue index ea11cf8..0b7d395 100644 --- a/resources/js/components/admin/manage/orders/OrderGroupedTable.vue +++ b/resources/js/components/admin/manage/orders/OrderGroupedTable.vue @@ -114,9 +114,11 @@ function statusVariant(status: string): 'default' | 'secondary' | 'destructive'
Tipe Harga {{ order.price_type_label - }} + }} + Pembayaran {{ order.payment_type_label + }} Subtotal {{ order.subtotal_formatted - }} + }} Diskon {{ order.discount_formatted }} Total {{ order.total_amount_formatted diff --git a/resources/js/components/admin/manage/orders/OrderPosForm.vue b/resources/js/components/admin/manage/orders/OrderPosForm.vue index 6bce9b7..e477cad 100644 --- a/resources/js/components/admin/manage/orders/OrderPosForm.vue +++ b/resources/js/components/admin/manage/orders/OrderPosForm.vue @@ -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() { + + Tipe Pembayaran + + + + Pelanggan