From cf0d60f74bfa4bb6fc98c89296134b43ab93175f Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 29 Apr 2026 20:23:29 +0700 Subject: [PATCH] feat: rename invoice_number to order_number and refactor order management components and logic --- .../Admin/Manage/OrderController.php | 9 +- .../Admin/Manage/Order/OrderRequest.php | 1 - app/Models/Order.php | 4 +- database/factories/OrderFactory.php | 7 +- .../2026_04_21_193515_create_orders_table.php | 2 +- .../js/pages/admin/manage/order/create.tsx | 1 - .../js/pages/admin/manage/order/edit.tsx | 1 - .../manage/order/hooks/use-order-print.ts | 10 +- .../js/pages/admin/manage/order/index.tsx | 29 +++-- .../admin/manage/order/partials/columns.tsx | 113 ++++++++++++------ resources/js/types/order.ts | 2 +- 11 files changed, 121 insertions(+), 58 deletions(-) diff --git a/app/Http/Controllers/Admin/Manage/OrderController.php b/app/Http/Controllers/Admin/Manage/OrderController.php index d1d3148..8b2c260 100644 --- a/app/Http/Controllers/Admin/Manage/OrderController.php +++ b/app/Http/Controllers/Admin/Manage/OrderController.php @@ -57,9 +57,16 @@ public function store(OrderRequest $request): RedirectResponse return $purchasePrice * $item['qty']; }); + $lastOrder = Order::whereDate('created_at', now()->format('Y-m-d')) + ->lockForUpdate() + ->count(); + + $sequence = str_pad($lastOrder + 1, 3, '0', STR_PAD_LEFT); + $orderNumber = 'ORD-'.now()->format('Ymd').'-'.$sequence; + $order = Order::create([ 'user_id' => auth()->id(), - 'invoice_number' => $validated['invoice_number'] ?? 'INV-'.now()->format('YmdHis').'-'.strtoupper(fake()->bothify('??##')), + 'order_number' => $orderNumber, 'customer_name' => $validated['customer_name'], 'cogs' => $cogs, 'discount' => $validated['discount'], diff --git a/app/Http/Requests/Admin/Manage/Order/OrderRequest.php b/app/Http/Requests/Admin/Manage/Order/OrderRequest.php index 42f36d8..f299aab 100644 --- a/app/Http/Requests/Admin/Manage/Order/OrderRequest.php +++ b/app/Http/Requests/Admin/Manage/Order/OrderRequest.php @@ -28,7 +28,6 @@ public function authorize(): bool public function rules(): array { return [ - 'invoice_number' => ['nullable', 'string', 'max:30'], 'customer_name' => ['required', 'string', 'max:100'], 'discount' => ['required', 'integer', 'min:0'], 'payment' => ['required', 'integer', 'min:0'], diff --git a/app/Models/Order.php b/app/Models/Order.php index 1f49747..21a2617 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -77,7 +77,7 @@ public function items(): HasMany public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() - ->logOnly(['invoice_number', 'customer_name', 'cogs', 'discount', 'payment', 'total', 'payment_method', 'order_status', 'order_channel']) + ->logOnly(['order_number', 'customer_name', 'cogs', 'discount', 'payment', 'total', 'payment_method', 'order_status', 'order_channel']) ->logOnlyDirty() ->useLogName('Pesanan'); } @@ -93,7 +93,7 @@ public function tapActivity(Activity $activity, string $eventName) if (isset($activity->properties['attributes'])) { $attributeMap = [ - 'invoice_number' => 'Nomor Invoice', + 'order_number' => 'Nomor Order', 'customer_name' => 'Nama Pelanggan', 'cogs' => 'Modal', 'discount' => 'Diskon', diff --git a/database/factories/OrderFactory.php b/database/factories/OrderFactory.php index 201e680..271fb4b 100644 --- a/database/factories/OrderFactory.php +++ b/database/factories/OrderFactory.php @@ -24,10 +24,15 @@ class OrderFactory extends Factory public function definition(): array { $date = $this->faker->dateTimeBetween('-1 year', 'now'); + $formattedDate = $date->format('Y-m-d'); + + $countToday = Order::whereDate('created_at', $formattedDate)->count(); + + $sequence = str_pad($countToday + 1, 3, '0', STR_PAD_LEFT); return [ 'user_id' => User::inRandomOrder()->first()?->id ?? User::factory(), - 'invoice_number' => 'INV-'.$date->format('YmdHis').'-'.strtoupper($this->faker->bothify('??##')), + 'order_number' => 'ORD-'.$date->format('YmdHi').'-'.$sequence, 'customer_name' => $this->faker->name(), 'cogs' => 0, 'discount' => 0, diff --git a/database/migrations/2026_04_21_193515_create_orders_table.php b/database/migrations/2026_04_21_193515_create_orders_table.php index d7704ab..723c002 100644 --- a/database/migrations/2026_04_21_193515_create_orders_table.php +++ b/database/migrations/2026_04_21_193515_create_orders_table.php @@ -17,7 +17,7 @@ public function up(): void Schema::create('orders', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); - $table->string('invoice_number', 30); + $table->string('order_number', 30); $table->string('customer_name', 100); $table->unsignedInteger('cogs'); $table->unsignedInteger('discount'); diff --git a/resources/js/pages/admin/manage/order/create.tsx b/resources/js/pages/admin/manage/order/create.tsx index ab2009e..db9f3d9 100644 --- a/resources/js/pages/admin/manage/order/create.tsx +++ b/resources/js/pages/admin/manage/order/create.tsx @@ -63,7 +63,6 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha }, [products]); const { data, setData, post, processing, errors, transform } = useForm({ - invoice_number: '', customer_name: '', discount: 0, payment: 0, diff --git a/resources/js/pages/admin/manage/order/edit.tsx b/resources/js/pages/admin/manage/order/edit.tsx index 2220891..9362b3b 100644 --- a/resources/js/pages/admin/manage/order/edit.tsx +++ b/resources/js/pages/admin/manage/order/edit.tsx @@ -59,7 +59,6 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels, }, [products]); const { data, setData, patch, processing, errors } = useForm({ - invoice_number: order.invoice_number, customer_name: order.customer_name, discount: order.discount, payment: order.payment, diff --git a/resources/js/pages/admin/manage/order/hooks/use-order-print.ts b/resources/js/pages/admin/manage/order/hooks/use-order-print.ts index 3399463..c73b92b 100644 --- a/resources/js/pages/admin/manage/order/hooks/use-order-print.ts +++ b/resources/js/pages/admin/manage/order/hooks/use-order-print.ts @@ -1,11 +1,11 @@ -import { format } from 'date-fns'; -import { id } from 'date-fns/locale'; -import { useCallback } from 'react'; -import { toast } from 'sonner'; import { usePrinter } from '@/hooks/use-printer'; import { EscPosEncoder } from '@/lib/esc-pos-encoder'; import { formatCurrency } from '@/lib/formatters'; import type { Order } from '@/types/order'; +import { format } from 'date-fns'; +import { id } from 'date-fns/locale'; +import { useCallback } from 'react'; +import { toast } from 'sonner'; export function useOrderPrint() { const { @@ -40,7 +40,7 @@ export function useOrderPrint() { .bold(false) .line(line) .align(0) - .line(`No. Invoice : ${order.invoice_number}`) + .line(`No. Order : ${order.order_number}`) .line(`Tanggal : ${format(new Date(order.created_at), 'dd MMMM yyyy HH:mm', { locale: id })}`) .line(`Pelanggan : ${order.customer_name}`) .line(line); diff --git a/resources/js/pages/admin/manage/order/index.tsx b/resources/js/pages/admin/manage/order/index.tsx index 48bd485..02cb665 100644 --- a/resources/js/pages/admin/manage/order/index.tsx +++ b/resources/js/pages/admin/manage/order/index.tsx @@ -1,17 +1,17 @@ -import { Head, Link } from '@inertiajs/react'; -import { Plus, Trash2 } from 'lucide-react'; import { DataTable } from '@/components/data-table'; import { DeleteConfirmation } from '@/components/modal/delete-confirmation'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; +import { Head, Link } from '@inertiajs/react'; +import { Plus, Trash2 } from 'lucide-react'; +import { usePermission } from '@/hooks/use-permission'; import * as orderRoutes from '@/routes/order'; import type { Order } from '@/types/order'; import { useOrderIndex } from './hooks/use-order-index'; import { useOrderPrint } from './hooks/use-order-print'; import { getColumns } from './partials/columns'; import { PrinterSettings } from './partials/printer-settings'; -import { usePermission } from '@/hooks/use-permission'; export default function OrderIndex({ orders }: { orders: Order[] }) { const { hasPermission } = usePermission(); @@ -38,11 +38,14 @@ export default function OrderIndex({ orders }: { orders: Order[] }) { connectUsb, disconnect, setPaperSize, - handlePrint + handlePrint, } = useOrderPrint(); const columns = getColumns({ onDelete, onPrint: handlePrint }).filter( - (col) => col.id !== 'actions' || hasPermission('Edit:Order') || hasPermission('Delete:Order') + (col) => + col.id !== 'actions' || + hasPermission('Edit:Order') || + hasPermission('Delete:Order'), ); return ( @@ -51,7 +54,9 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
-

Pesanan

+

+ Pesanan +

- + - Tindakan ini tidak dapat dibatalkan. Pesanan {orderToDelete?.invoice_number} akan dihapus secara permanen dan stok akan dikembalikan. + Tindakan ini tidak dapat dibatalkan. Pesanan{' '} + {orderToDelete?.order_number} akan + dihapus secara permanen dan stok akan dikembalikan. } onDelete={confirmDelete} @@ -122,7 +129,9 @@ export default function OrderIndex({ orders }: { orders: Order[] }) { title={`Hapus ${rowsToDelete.length} pesanan?`} description={ <> - Tindakan ini tidak dapat dibatalkan. {rowsToDelete.length} item yang terpilih akan dihapus secara permanen. + Tindakan ini tidak dapat dibatalkan.{' '} + {rowsToDelete.length} item yang + terpilih akan dihapus secara permanen. } onDelete={confirmBulkDelete} @@ -135,6 +144,6 @@ OrderIndex.layout = { breadcrumbs: [ { title: 'Kelola', - } + }, ], }; diff --git a/resources/js/pages/admin/manage/order/partials/columns.tsx b/resources/js/pages/admin/manage/order/partials/columns.tsx index 40efdd2..330b9a6 100644 --- a/resources/js/pages/admin/manage/order/partials/columns.tsx +++ b/resources/js/pages/admin/manage/order/partials/columns.tsx @@ -1,54 +1,68 @@ +import { DataTableColumnHeader } from '@/components/data-table-column-header'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip'; import { usePermission } from '@/hooks/use-permission'; +import * as orderRoutes from '@/routes/order'; +import type { Order } from '@/types/order'; import { Link } from '@inertiajs/react'; import type { ColumnDef } from '@tanstack/react-table'; import { format } from 'date-fns'; import { id } from 'date-fns/locale'; import { Pencil, Printer, Trash2 } from 'lucide-react'; -import { DataTableColumnHeader } from '@/components/data-table-column-header'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; -import * as orderRoutes from '@/routes/order'; -import type { Order } from '@/types/order'; interface ColumnProps { onDelete: (order: Order) => void; onPrint: (order: Order) => void; } -export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef[] => [ +export const getColumns = ({ + onDelete, + onPrint, +}: ColumnProps): ColumnDef[] => [ { - accessorKey: "invoice_number", + accessorKey: 'order_number', header: ({ column }) => ( - + ), cell: ({ row }) => (
- {row.original.invoice_number} + {row.original.order_number} - {format(new Date(row.original.created_at), 'dd MMM yyyy HH:mm', { locale: id })} + {format( + new Date(row.original.created_at), + 'dd MMM yyyy HH:mm', + { locale: id }, + )}
), - meta: { title: "No. Invoice" }, + meta: { title: 'No. Order' }, }, { - accessorKey: "customer_name", + accessorKey: 'customer_name', header: ({ column }) => ( ), cell: ({ row }) => (
{row.original.customer_name} - + {row.original.order_channel}
), - meta: { title: "Pelanggan" }, + meta: { title: 'Pelanggan' }, }, { - accessorKey: "items", + accessorKey: 'items', header: ({ column }) => ( ), @@ -56,27 +70,37 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef const items = row.original.items; if (!items || !Array.isArray(items) || items.length === 0) { - return -; + return ( + + - + + ); } return (
{items.slice(0, 2).map((item, i) => (
- {item.product?.name}:{' '} - {item.qty} x {item.price_formatted} + + {item.product?.name}: + {' '} + + {item.qty} x {item.price_formatted} +
))} {items.length > 2 && ( - +{items.length - 2} item lainnya... + + +{items.length - 2} item lainnya... + )}
); }, - meta: { title: "Item" }, + meta: { title: 'Item' }, }, { - accessorKey: "total", + accessorKey: 'total', header: ({ column }) => ( ), @@ -85,20 +109,27 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef {row.original.total_formatted} - + {row.original.order_status}
), - meta: { title: "Total" }, + meta: { title: 'Total' }, }, { - id: "actions", - header: "Aksi", + id: 'actions', + header: 'Aksi', cell: ({ row }) => { const order = row.original; const { hasPermission } = usePermission(); @@ -107,7 +138,12 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef
- @@ -119,7 +155,11 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef - @@ -132,7 +172,12 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef {hasPermission('Delete:Order') && ( - @@ -144,6 +189,6 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef
); }, - meta: { title: "Aksi" }, + meta: { title: 'Aksi' }, }, ]; diff --git a/resources/js/types/order.ts b/resources/js/types/order.ts index 9f8e5bd..579df8d 100644 --- a/resources/js/types/order.ts +++ b/resources/js/types/order.ts @@ -2,7 +2,7 @@ import type { Product } from "./product"; export interface Order { id: number; - invoice_number: string; + order_number: string; customer_name: string; cogs: number; discount: number;