feat: add tiktok and shopee order IDs to transaction model and UI components

This commit is contained in:
Yoga Pangestu 2026-08-07 09:04:16 +07:00
parent 74c374e126
commit 9e60a1cf39
4 changed files with 60 additions and 53 deletions

View File

@ -41,7 +41,7 @@ public function __construct(
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
{ {
$paginator = Order::query() $paginator = Order::query()
->select(['id', 'created_by_id', 'customer_id', 'marketing_id', 'order_number', 'channel', 'price_type', 'status', 'payment_type', 'subtotal', 'discount', 'nego_price', 'total_amount', 'cogs', 'notes', 'created_at']) ->select(['id', 'created_by_id', 'customer_id', 'marketing_id', 'order_number', 'channel', 'price_type', 'status', 'payment_type', 'tiktok_order_id', 'shopee_order_id', 'subtotal', 'discount', 'nego_price', 'total_amount', 'cogs', 'notes', 'created_at'])
->with([ ->with([
'createdBy:id', 'createdBy:id',
'createdBy.userProfile:id,user_id,full_name', 'createdBy.userProfile:id,user_id,full_name',
@ -53,18 +53,18 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
'orderItems.productVariant.product:id,name', 'orderItems.productVariant.product:id,name',
]) ])
->when($search, function ($q) use ($search) { ->when($search, function ($q) use ($search) {
$q->whereHas('orderItems.productVariant.product', fn ($sq) => $sq->where('name', 'like', "%{$search}%")) $q->whereHas('orderItems.productVariant.product', fn($sq) => $sq->where('name', 'like', "%{$search}%"))
->orWhere('order_number', 'like', "%{$search}%") ->orWhere('order_number', 'like', "%{$search}%")
->orWhere('notes', 'like', "%{$search}%"); ->orWhere('notes', 'like', "%{$search}%");
}) })
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status)) ->when($filters['status'] ?? null, fn($q, $status) => $q->where('status', $status))
->when($filters['channel'] ?? null, fn ($q, $channel) => $q->where('channel', $channel)) ->when($filters['channel'] ?? null, fn($q, $channel) => $q->where('channel', $channel))
->when($filters['payment_type'] ?? null, fn ($q, $paymentType) => $q->where('payment_type', $paymentType)) ->when($filters['payment_type'] ?? null, fn($q, $paymentType) => $q->where('payment_type', $paymentType))
->when($filters['customer_id'] ?? null, fn ($q, $customerId) => $q->where('customer_id', $customerId)) ->when($filters['customer_id'] ?? null, fn($q, $customerId) => $q->where('customer_id', $customerId))
->when($filters['marketing_id'] ?? null, fn ($q, $marketingId) => $q->where('marketing_id', $marketingId)) ->when($filters['marketing_id'] ?? null, fn($q, $marketingId) => $q->where('marketing_id', $marketingId))
->when($filters['created_by_id'] ?? null, fn ($q, $createdById) => $q->where('created_by_id', $createdById)) ->when($filters['created_by_id'] ?? null, fn($q, $createdById) => $q->where('created_by_id', $createdById))
->when($filters['date_from'] ?? null, fn ($q, $dateFrom) => $q->whereDate('created_at', '>=', $dateFrom)) ->when($filters['date_from'] ?? null, fn($q, $dateFrom) => $q->whereDate('created_at', '>=', $dateFrom))
->when($filters['date_to'] ?? null, fn ($q, $dateTo) => $q->whereDate('created_at', '<=', $dateTo)) ->when($filters['date_to'] ?? null, fn($q, $dateTo) => $q->whereDate('created_at', '<=', $dateTo))
->orderBy($sort, $direction) ->orderBy($sort, $direction)
->paginate($perPage); ->paginate($perPage);
@ -99,14 +99,14 @@ public function getSummary(array $filters = []): array
->selectRaw('COALESCE(SUM(subtotal) - SUM(COALESCE(nego_price, subtotal)), 0) as total_discount') ->selectRaw('COALESCE(SUM(subtotal) - SUM(COALESCE(nego_price, subtotal)), 0) as total_discount')
->selectRaw('COALESCE(SUM(total_amount), 0) as total_amount') ->selectRaw('COALESCE(SUM(total_amount), 0) as total_amount')
->selectRaw('COALESCE(SUM(cogs), 0) as total_cogs') ->selectRaw('COALESCE(SUM(cogs), 0) as total_cogs')
->when($filters['status'] ?? null, fn ($q, $status) => $q->where('status', $status)) ->when($filters['status'] ?? null, fn($q, $status) => $q->where('status', $status))
->when($filters['channel'] ?? null, fn ($q, $channel) => $q->where('channel', $channel)) ->when($filters['channel'] ?? null, fn($q, $channel) => $q->where('channel', $channel))
->when($filters['payment_type'] ?? null, fn ($q, $paymentType) => $q->where('payment_type', $paymentType)) ->when($filters['payment_type'] ?? null, fn($q, $paymentType) => $q->where('payment_type', $paymentType))
->when($filters['customer_id'] ?? null, fn ($q, $customerId) => $q->where('customer_id', $customerId)) ->when($filters['customer_id'] ?? null, fn($q, $customerId) => $q->where('customer_id', $customerId))
->when($filters['marketing_id'] ?? null, fn ($q, $marketingId) => $q->where('marketing_id', $marketingId)) ->when($filters['marketing_id'] ?? null, fn($q, $marketingId) => $q->where('marketing_id', $marketingId))
->when($filters['created_by_id'] ?? null, fn ($q, $createdById) => $q->where('created_by_id', $createdById)) ->when($filters['created_by_id'] ?? null, fn($q, $createdById) => $q->where('created_by_id', $createdById))
->when($filters['date_from'] ?? null, fn ($q, $dateFrom) => $q->whereDate('created_at', '>=', $dateFrom)) ->when($filters['date_from'] ?? null, fn($q, $dateFrom) => $q->whereDate('created_at', '>=', $dateFrom))
->when($filters['date_to'] ?? null, fn ($q, $dateTo) => $q->whereDate('created_at', '<=', $dateTo)) ->when($filters['date_to'] ?? null, fn($q, $dateTo) => $q->whereDate('created_at', '<=', $dateTo))
->first(); ->first();
return [ return [
@ -134,9 +134,9 @@ public function getFilterOptions(): array
->with('userProfile:id,user_id,full_name') ->with('userProfile:id,user_id,full_name')
->orderBy('id') ->orderBy('id')
->get() ->get()
->filter(fn (User $user) => $user->userProfile?->full_name) ->filter(fn(User $user) => $user->userProfile?->full_name)
->values() ->values()
->map(fn (User $user) => [ ->map(fn(User $user) => [
'id' => $user->id, 'id' => $user->id,
'name' => $user->userProfile->full_name, 'name' => $user->userProfile->full_name,
]), ]),
@ -162,7 +162,7 @@ public function getForCreate(): array
? $this->s3Service->getTemporaryUrl($media->file_name) ? $this->s3Service->getTemporaryUrl($media->file_name)
: null; : null;
$prices = $variant->productPrices->mapWithKeys(fn ($p) => [$p->type->value => $p->price]); $prices = $variant->productPrices->mapWithKeys(fn($p) => [$p->type->value => $p->price]);
$variant->prices = $prices; $variant->prices = $prices;
}); });
}), }),
@ -176,11 +176,11 @@ public function getForCreate(): array
->with('userProfile:id,user_id,full_name') ->with('userProfile:id,user_id,full_name')
->orderBy('id') ->orderBy('id')
->get() ->get()
->filter(fn (User $user) => $user->userProfile?->full_name) ->filter(fn(User $user) => $user->userProfile?->full_name)
->values(), ->values(),
'channelOptions' => OrderChannel::toSelect(), 'channelOptions' => OrderChannel::toSelect(),
'paymentTypeOptions' => PaymentType::toSelect(), 'paymentTypeOptions' => PaymentType::toSelect(),
'priceTypeOptions' => PriceType::toSelect()->filter(fn ($p) => $p['value'] !== PriceType::CAPITAL->value)->values(), 'priceTypeOptions' => PriceType::toSelect()->filter(fn($p) => $p['value'] !== PriceType::CAPITAL->value)->values(),
]; ];
} }
@ -210,7 +210,7 @@ public function getForEdit(Order $order): array
'photo_url' => $media 'photo_url' => $media
? $this->s3Service->getTemporaryUrl($media->file_name) ? $this->s3Service->getTemporaryUrl($media->file_name)
: null, : null,
'items' => $order->orderItems->map(fn (OrderItem $item) => [ 'items' => $order->orderItems->map(fn(OrderItem $item) => [
'id' => $item->id, 'id' => $item->id,
'product_variant_id' => $item->product_variant_id, 'product_variant_id' => $item->product_variant_id,
'quantity' => $item->quantity, 'quantity' => $item->quantity,
@ -270,7 +270,7 @@ public function create(array $data): Order
NotificationService::notify( NotificationService::notify(
roles: ['Owner', 'Developer', 'Admin Toko'], roles: ['Owner', 'Developer', 'Admin Toko'],
title: 'Transaksi Baru', title: 'Transaksi Baru',
body: 'Transaksi '.$order->order_number.' sebesar Rp '.number_format($totalAmount, 0, ',', '.').' berhasil dicatat oleh '.auth()->user()->full_name.'.', body: 'Transaksi ' . $order->order_number . ' sebesar Rp ' . number_format($totalAmount, 0, ',', '.') . ' berhasil dicatat oleh ' . auth()->user()->full_name . '.',
url: route('admin.manage.transactions.index'), url: route('admin.manage.transactions.index'),
); );
@ -379,14 +379,14 @@ private function buildItemRows(array $items, string $stockType, string $priceTyp
$prices = $variants->mapWithKeys(function (ProductVariant $variant) use ($resolvedPriceType) { $prices = $variants->mapWithKeys(function (ProductVariant $variant) use ($resolvedPriceType) {
$price = $variant->productPrices $price = $variant->productPrices
->first(fn ($p) => $p->type === $resolvedPriceType); ->first(fn($p) => $p->type === $resolvedPriceType);
return [$variant->id => $price?->price ?? 0]; return [$variant->id => $price?->price ?? 0];
}); });
$capitalPrices = $variants->mapWithKeys(function (ProductVariant $variant) { $capitalPrices = $variants->mapWithKeys(function (ProductVariant $variant) {
$price = $variant->productPrices $price = $variant->productPrices
->first(fn ($p) => $p->type === PriceType::CAPITAL); ->first(fn($p) => $p->type === PriceType::CAPITAL);
return [$variant->id => $price?->price ?? 0]; return [$variant->id => $price?->price ?? 0];
}); });
@ -429,6 +429,6 @@ private function generateOrderNumber(): string
$sequence = 1; $sequence = 1;
} }
return $prefix.$date.str_pad($sequence, 4, '0', STR_PAD_LEFT); return $prefix . $date . str_pad($sequence, 4, '0', STR_PAD_LEFT);
} }
} }

View File

@ -40,6 +40,8 @@ export type Transaction = {
payment_type_label: string; payment_type_label: string;
channel_label: string; channel_label: string;
price_type_label: string; price_type_label: string;
tiktok_order_id: string | null;
shopee_order_id: string | null;
profit: number; profit: number;
cogs: number; cogs: number;
subtotal: number; subtotal: number;

View File

@ -16,6 +16,7 @@ import {
ComboboxItem, ComboboxItem,
ComboboxList, ComboboxList,
} from '@/components/ui/combobox'; } from '@/components/ui/combobox';
import { FieldDescription } from '@/components/ui/field';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { import {
@ -38,7 +39,6 @@ import { formatNumber } from '@/lib/format';
import { getTemporaryUrl } from '@/lib/upload'; import { getTemporaryUrl } from '@/lib/upload';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import { index as transactionIndex, update } from '@/routes/admin/manage/transactions'; import { index as transactionIndex, update } from '@/routes/admin/manage/transactions';
import { FieldDescription } from '@base-ui/react';
import { Form, Head, Link } from '@inertiajs/react'; import { Form, Head, Link } from '@inertiajs/react';
import { ArrowLeft, Minus, Plus, ShoppingCart, Trash2 } from 'lucide-react'; import { ArrowLeft, Minus, Plus, ShoppingCart, Trash2 } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

View File

@ -51,7 +51,7 @@ export function TransactionCardRow({
); );
const statusBadgeClass = const statusBadgeClass =
STATUS_BADGE_CLASSES[transaction.status] ?? STATUS_BADGE_CLASSES.pending; STATUS_BADGE_CLASSES[transaction.status] ?? STATUS_BADGE_CLASSES.pending;
console.log('transaction', transaction);
return ( return (
<Card className="overflow-hidden"> <Card className="overflow-hidden">
<CardContent className="p-0"> <CardContent className="p-0">
@ -84,6 +84,11 @@ export function TransactionCardRow({
<Badge variant="outline"> <Badge variant="outline">
{transaction.payment_type_label} {transaction.payment_type_label}
</Badge> </Badge>
{transaction.tiktok_order_id || transaction.shopee_order_id ? (
<Badge variant="outline">
{transaction.tiktok_order_id || transaction.shopee_order_id}
</Badge>
) : null}
</div> </div>
<div className="mt-1 text-xs text-muted-foreground"> <div className="mt-1 text-xs text-muted-foreground">
@ -188,35 +193,35 @@ export function TransactionCardRow({
actions={[ actions={[
...(transaction.status === 'pending' && can('orders.update') ...(transaction.status === 'pending' && can('orders.update')
? [ ? [
{ {
label: 'Kirim', label: 'Kirim',
icon: <Send className="h-4 w-4" />, icon: <Send className="h-4 w-4" />,
onClick: () => onUpdateStatus(transaction, 'processing'), onClick: () => onUpdateStatus(transaction, 'processing'),
}, },
] ]
: []), : []),
...((transaction.status === 'pending' || transaction.status === 'processing') && can('orders.update') ...((transaction.status === 'pending' || transaction.status === 'processing') && can('orders.update')
? [ ? [
{ {
label: 'Selesai', label: 'Selesai',
icon: <CheckCircle className="h-4 w-4" />, icon: <CheckCircle className="h-4 w-4" />,
onClick: () => onUpdateStatus(transaction, 'completed'), onClick: () => onUpdateStatus(transaction, 'completed'),
}, },
{ {
label: 'Dibatalkan', label: 'Dibatalkan',
icon: <XCircle className="h-4 w-4 text-destructive" />, icon: <XCircle className="h-4 w-4 text-destructive" />,
onClick: () => onUpdateStatus(transaction, 'cancelled'), onClick: () => onUpdateStatus(transaction, 'cancelled'),
}, },
] ]
: []), : []),
...(transaction.status === 'completed' && can('orders.update') ...(transaction.status === 'completed' && can('orders.update')
? [ ? [
{ {
label: 'Refund', label: 'Refund',
icon: <XCircle className="h-4 w-4 text-destructive" />, icon: <XCircle className="h-4 w-4 text-destructive" />,
onClick: () => onUpdateStatus(transaction, 'refunded'), onClick: () => onUpdateStatus(transaction, 'refunded'),
}, },
] ]
: []), : []),
{ {
label: 'Edit', label: 'Edit',