diff --git a/app/Models/Order.php b/app/Models/Order.php
index fc1907b..4be6598 100644
--- a/app/Models/Order.php
+++ b/app/Models/Order.php
@@ -20,7 +20,7 @@
use Spatie\Activitylog\Traits\LogsActivity;
#[Guarded(['id'])]
-#[Appends(['cogs_formatted', 'subtotal_formatted', 'discount_formatted', 'payment_formatted', 'total_formatted'])]
+#[Appends(['cogs_formatted', 'subtotal_formatted', 'discount_formatted', 'payment_formatted', 'total_formatted', 'created_at_formatted'])]
#[ObservedBy([OrderObserver::class])]
class Order extends Model
{
@@ -43,35 +43,42 @@ protected function casts(): array
protected function cogsFormatted(): Attribute
{
return Attribute::make(
- get: fn () => 'Rp '.number_format($this->cogs, 0, ',', '.'),
+ get: fn() => 'Rp ' . number_format($this->cogs, 0, ',', '.'),
);
}
protected function subtotalFormatted(): Attribute
{
return Attribute::make(
- get: fn () => 'Rp '.number_format($this->subtotal, 0, ',', '.'),
+ get: fn() => 'Rp ' . number_format($this->subtotal, 0, ',', '.'),
);
}
protected function discountFormatted(): Attribute
{
return Attribute::make(
- get: fn () => 'Rp '.number_format($this->discount, 0, ',', '.'),
+ get: fn() => 'Rp ' . number_format($this->discount, 0, ',', '.'),
);
}
protected function paymentFormatted(): Attribute
{
return Attribute::make(
- get: fn () => 'Rp '.number_format($this->payment, 0, ',', '.'),
+ get: fn() => 'Rp ' . number_format($this->payment, 0, ',', '.'),
);
}
protected function totalFormatted(): Attribute
{
return Attribute::make(
- get: fn () => 'Rp '.number_format($this->total, 0, ',', '.'),
+ get: fn() => 'Rp ' . number_format($this->total, 0, ',', '.'),
+ );
+ }
+
+ protected function createdAtFormatted(): Attribute
+ {
+ return Attribute::make(
+ get: fn() => $this->created_at->translatedFormat('d F Y H:i'),
);
}
diff --git a/app/Observers/OrderObserver.php b/app/Observers/OrderObserver.php
index 5c9d7ec..ea3a1d4 100644
--- a/app/Observers/OrderObserver.php
+++ b/app/Observers/OrderObserver.php
@@ -27,6 +27,14 @@ public function creating(Order $order): void
$order->total = $order->subtotal - $order->discount;
}
+ /**
+ * Handle the Order "updating" event.
+ */
+ public function updating(Order $order): void
+ {
+ $order->total = $order->subtotal - $order->discount;
+ }
+
/**
* Handle the Order "deleting" event.
*/
diff --git a/resources/js/pages/admin/manage/order/partials/columns.tsx b/resources/js/pages/admin/manage/order/partials/columns.tsx
index ad1d5b0..46aef4f 100644
--- a/resources/js/pages/admin/manage/order/partials/columns.tsx
+++ b/resources/js/pages/admin/manage/order/partials/columns.tsx
@@ -9,10 +9,9 @@ import {
import { usePermission } from '@/hooks/use-permission';
import * as orderRoutes from '@/routes/order';
import type { Order } from '@/types/order';
+import { ORDER_CHANNEL, ORDER_STATUS, PAYMENT_METHOD } 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';
interface ColumnProps {
@@ -43,11 +42,7 @@ export const getColumns = ({
{row.original.order_number}
- {format(
- new Date(row.original.created_at),
- 'dd MMM yyyy HH:mm',
- { locale: id },
- )}
+ {row.original.created_at_formatted}
),
@@ -65,12 +60,25 @@ export const getColumns = ({
variant="outline"
className="h-4 w-fit px-1 py-0 text-[10px]"
>
- {row.original.order_channel}
+ {ORDER_CHANNEL[row.original.order_channel] ||
+ row.original.order_channel}
),
meta: { title: 'Pelanggan' },
},
+ {
+ accessorKey: 'cogs',
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => (
+
+ {row.original.cogs_formatted}
+
+ ),
+ meta: { title: 'Modal' },
+ },
{
accessorKey: 'items',
header: ({ column }) => (
@@ -114,33 +122,55 @@ export const getColumns = ({
header: ({ column }) => (
),
- cell: ({ row }) => (
-
-
- {row.original.subtotal_formatted}
-
-
- - {row.original.discount_formatted}
-
-
- {row.original.total_formatted}
-
-
- {row.original.order_status}
-
-
- ),
+ cell: ({ row }) => {
+ const order = row.original;
+
+ return (
+
+
+
+ Subtotal:
+
+
+ {order.subtotal_formatted}
+
+
+
+
+ Potongan:
+
+
+ - {order.discount_formatted}
+
+
+
+
+ Total Tagihan:
+
+
+ {order.total_formatted}
+
+
+
+
+ Dibayar ({PAYMENT_METHOD[order.payment_method]}):
+
+
+ {order.payment_formatted}
+
+
+
+ {ORDER_STATUS[order.order_status]?.label ??
+ order.order_status}
+
+
+ );
+ },
meta: { title: 'Total' },
},
{
diff --git a/resources/js/types/order.ts b/resources/js/types/order.ts
index b7787f8..64bb16b 100644
--- a/resources/js/types/order.ts
+++ b/resources/js/types/order.ts
@@ -1,6 +1,32 @@
import type { Product } from "./product";
import type { User } from "./auth";
+export const ORDER_STATUS = {
+ pending: { label: 'Menunggu', color: 'secondary' },
+ processing: { label: 'Proses', color: 'outline' },
+ shipped: { label: 'Dikirim', color: 'outline' },
+ delivered: { label: 'Selesai', color: 'default' },
+ cancelled: { label: 'Gagal', color: 'destructive' },
+} as const;
+
+export const PAYMENT_METHOD = {
+ cash: 'Tunai',
+ transfer: 'Transfer',
+ e_wallet: 'E-Wallet',
+ qris: 'QRIS',
+} as const;
+
+export const ORDER_CHANNEL = {
+ tiktok: 'Tiktok',
+ shopee: 'Shopee',
+ tokopedia: 'Tokopedia',
+ lazada: 'Lazada',
+ facebook: 'Facebook',
+ website: 'Website',
+ store: 'Toko',
+ other: 'Lainnya',
+} as const;
+
export interface Order {
id: number;
order_number: string;
@@ -15,13 +41,14 @@ export interface Order {
payment_formatted: string;
total: number;
total_formatted: string;
- payment_method: 'cash' | 'transfer' | 'e_wallet' | 'qris';
- order_status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
- order_channel: 'tiktok' | 'shopee' | 'tokopedia' | 'lazada' | 'facebook' | 'website' | 'store' | 'other';
+ payment_method: keyof typeof PAYMENT_METHOD;
+ order_status: keyof typeof ORDER_STATUS;
+ order_channel: keyof typeof ORDER_CHANNEL;
items?: OrderItem[];
user?: User;
created_at: string;
updated_at: string;
+ created_at_formatted: string;
}
export interface OrderItem {
diff --git a/tests/Feature/Admin/Manage/OrderTest.php b/tests/Feature/Admin/Manage/OrderTest.php
index 8a43cc3..3c0c1e6 100644
--- a/tests/Feature/Admin/Manage/OrderTest.php
+++ b/tests/Feature/Admin/Manage/OrderTest.php
@@ -56,6 +56,9 @@
->component('admin/manage/order/index')
->has('orders', fn ($page) => $page
->has('0.user')
+ ->has('0.cogs_formatted')
+ ->has('0.payment_formatted')
+ ->has('0.created_at_formatted')
->etc()
)
);
@@ -109,9 +112,8 @@
it('can store a new order', function () {
$product = Product::factory()->create(['stock' => 10]);
- // Add a price for PURCHASE to avoid errors in COGS calculation if applicable
- $product->prices()->create([
- 'price_type' => PriceType::PURCHASE->value,
+ // Update existing PURCHASE price from factory to ensure COGS calculation is predictable
+ $product->prices()->where('price_type', PriceType::PURCHASE->value)->update([
'price' => 8000,
]);
@@ -140,7 +142,14 @@
assertDatabaseHas('orders', [
'customer_name' => 'John Doe',
+ 'subtotal' => 20000,
+ 'discount' => 2000,
'total' => 18000,
+ 'payment' => 18000,
+ 'payment_method' => PaymentMethod::CASH->value,
+ 'order_status' => OrderStatus::PENDING->value,
+ 'order_channel' => OrderChannel::STORE->value,
+ 'cogs' => 16000, // 8000 * 2
]);
// Check stock decrement
@@ -215,6 +224,9 @@
assertDatabaseHas('orders', [
'id' => $order->id,
'customer_name' => 'Updated Customer',
+ 'order_status' => OrderStatus::PROCESSING->value,
+ 'payment' => 15000,
+ 'total' => 15000,
]);
// Check stock: product1 should be restored (10), product2 should be decremented (7)