Update stock status calculations and add restock/overstock qty fields and calculations
This commit is contained in:
@@ -26,62 +26,9 @@ import {
|
||||
useSortable,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { Product } from "@/types/products";
|
||||
|
||||
interface Product {
|
||||
product_id: number;
|
||||
title: string;
|
||||
SKU: string;
|
||||
stock_quantity: number;
|
||||
price: number;
|
||||
regular_price: number;
|
||||
cost_price: number;
|
||||
landing_cost_price: number | null;
|
||||
barcode: string;
|
||||
vendor: string;
|
||||
vendor_reference: string;
|
||||
brand: string;
|
||||
categories: string[];
|
||||
tags: string[];
|
||||
options: Record<string, any>;
|
||||
image: string | null;
|
||||
moq: number;
|
||||
uom: number;
|
||||
visible: boolean;
|
||||
managing_stock: boolean;
|
||||
replenishable: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
||||
// Metrics
|
||||
daily_sales_avg?: number;
|
||||
weekly_sales_avg?: number;
|
||||
monthly_sales_avg?: number;
|
||||
avg_quantity_per_order?: number;
|
||||
number_of_orders?: number;
|
||||
first_sale_date?: string;
|
||||
last_sale_date?: string;
|
||||
days_of_inventory?: number;
|
||||
weeks_of_inventory?: number;
|
||||
reorder_point?: number;
|
||||
safety_stock?: number;
|
||||
avg_margin_percent?: number;
|
||||
total_revenue?: number;
|
||||
inventory_value?: number;
|
||||
cost_of_goods_sold?: number;
|
||||
gross_profit?: number;
|
||||
gmroi?: number;
|
||||
avg_lead_time_days?: number;
|
||||
last_purchase_date?: string;
|
||||
last_received_date?: string;
|
||||
abc_class?: string;
|
||||
stock_status?: string;
|
||||
turnover_rate?: number;
|
||||
current_lead_time?: number;
|
||||
target_lead_time?: number;
|
||||
lead_time_status?: string;
|
||||
}
|
||||
|
||||
type ColumnKey = keyof Product | 'image';
|
||||
export type ColumnKey = keyof Product | 'image';
|
||||
|
||||
interface ColumnDef {
|
||||
key: ColumnKey;
|
||||
@@ -296,6 +243,12 @@ export function ProductTable({
|
||||
) : (
|
||||
<Badge variant="outline">Hidden</Badge>
|
||||
);
|
||||
case 'replenishable':
|
||||
return value ? (
|
||||
<Badge variant="secondary">Replenishable</Badge>
|
||||
) : (
|
||||
<Badge variant="outline">Non-Replenishable</Badge>
|
||||
);
|
||||
default:
|
||||
if (columnDef?.format && value !== undefined && value !== null) {
|
||||
// For numeric formats (those using toFixed), ensure the value is a number
|
||||
|
||||
@@ -23,14 +23,14 @@ export const PRODUCT_VIEWS: ProductView[] = [
|
||||
label: "Critical Stock",
|
||||
icon: AlertTriangle,
|
||||
iconClassName: "text-destructive",
|
||||
columns: ["image", "title", "SKU", "stock_quantity", "daily_sales_avg", "last_purchase_date", "lead_time_status"]
|
||||
columns: ["image", "title", "SKU", "stock_quantity", "daily_sales_avg", "reorder_qty", "replenishable", "last_purchase_date", "lead_time_status"]
|
||||
},
|
||||
{
|
||||
id: "Reorder",
|
||||
label: "Reorder Soon",
|
||||
icon: AlertCircle,
|
||||
iconClassName: "text-warning",
|
||||
columns: ["image", "title", "SKU", "stock_quantity", "daily_sales_avg", "last_purchase_date", "lead_time_status"]
|
||||
columns: ["image", "title", "SKU", "stock_quantity", "daily_sales_avg", "reorder_qty", "replenishable", "last_purchase_date", "lead_time_status"]
|
||||
},
|
||||
{
|
||||
id: "Healthy",
|
||||
@@ -44,7 +44,7 @@ export const PRODUCT_VIEWS: ProductView[] = [
|
||||
label: "Overstock",
|
||||
icon: PackageSearch,
|
||||
iconClassName: "text-muted-foreground",
|
||||
columns: ["image", "title", "stock_quantity", "daily_sales_avg", "last_sale_date", "abc_class"]
|
||||
columns: ["image", "title", "stock_quantity", "daily_sales_avg", "overstocked_amt", "replenishable", "last_sale_date", "abc_class"]
|
||||
},
|
||||
{
|
||||
id: "New",
|
||||
|
||||
@@ -6,6 +6,8 @@ import { ProductTableSkeleton } from '@/components/products/ProductTableSkeleton
|
||||
import { ProductDetail } from '@/components/products/ProductDetail';
|
||||
import { ProductViews } from '@/components/products/ProductViews';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Product } from '@/types/products';
|
||||
import type { ColumnKey } from '@/components/products/ProductTable';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
@@ -26,65 +28,9 @@ import {
|
||||
PaginationPrevious,
|
||||
} from "@/components/ui/pagination"
|
||||
|
||||
// Enhanced Product interface with all possible fields
|
||||
interface Product {
|
||||
// Basic product info
|
||||
product_id: number;
|
||||
title: string;
|
||||
SKU: string;
|
||||
stock_quantity: number;
|
||||
price: number;
|
||||
regular_price: number;
|
||||
cost_price: number;
|
||||
landing_cost_price: number | null;
|
||||
barcode: string;
|
||||
vendor: string;
|
||||
vendor_reference: string;
|
||||
brand: string;
|
||||
categories: string[];
|
||||
tags: string[];
|
||||
options: Record<string, any>;
|
||||
image: string | null;
|
||||
moq: number;
|
||||
uom: number;
|
||||
visible: boolean;
|
||||
managing_stock: boolean;
|
||||
replenishable: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
||||
// Metrics
|
||||
daily_sales_avg?: number;
|
||||
weekly_sales_avg?: number;
|
||||
monthly_sales_avg?: number;
|
||||
avg_quantity_per_order?: number;
|
||||
number_of_orders?: number;
|
||||
first_sale_date?: string;
|
||||
last_sale_date?: string;
|
||||
days_of_inventory?: number;
|
||||
weeks_of_inventory?: number;
|
||||
reorder_point?: number;
|
||||
safety_stock?: number;
|
||||
avg_margin_percent?: number;
|
||||
total_revenue?: number;
|
||||
inventory_value?: number;
|
||||
cost_of_goods_sold?: number;
|
||||
gross_profit?: number;
|
||||
gmroi?: number;
|
||||
avg_lead_time_days?: number;
|
||||
last_purchase_date?: string;
|
||||
last_received_date?: string;
|
||||
abc_class?: string;
|
||||
stock_status?: string;
|
||||
turnover_rate?: number;
|
||||
current_lead_time?: number;
|
||||
target_lead_time?: number;
|
||||
lead_time_status?: string;
|
||||
}
|
||||
|
||||
// Column definition type
|
||||
interface ColumnDef {
|
||||
key: keyof Product | 'image';
|
||||
key: ColumnKey;
|
||||
label: string;
|
||||
group: string;
|
||||
noLabel?: boolean;
|
||||
@@ -105,6 +51,10 @@ const AVAILABLE_COLUMNS: ColumnDef[] = [
|
||||
{ key: 'stock_status', label: 'Stock Status', group: 'Stock' },
|
||||
{ key: 'days_of_inventory', label: 'Days of Stock', group: 'Stock', format: (v) => v?.toFixed(1) ?? '-' },
|
||||
{ key: 'abc_class', label: 'ABC Class', group: 'Stock' },
|
||||
{ key: 'replenishable', label: 'Replenishable', group: 'Stock' },
|
||||
{ key: 'moq', label: 'MOQ', group: 'Stock', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'reorder_qty', label: 'Reorder Qty', group: 'Stock', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'overstocked_amt', label: 'Overstock Amt', group: 'Stock', format: (v) => v?.toString() ?? '-' },
|
||||
{ key: 'price', label: 'Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'regular_price', label: 'Default Price', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
{ key: 'cost_price', label: 'Cost', group: 'Pricing', format: (v) => v?.toFixed(2) ?? '-' },
|
||||
@@ -124,7 +74,7 @@ const AVAILABLE_COLUMNS: ColumnDef[] = [
|
||||
];
|
||||
|
||||
// Default visible columns
|
||||
const DEFAULT_VISIBLE_COLUMNS: (keyof Product | 'image')[] = [
|
||||
const DEFAULT_VISIBLE_COLUMNS: ColumnKey[] = [
|
||||
'image',
|
||||
'title',
|
||||
'SKU',
|
||||
@@ -132,6 +82,8 @@ const DEFAULT_VISIBLE_COLUMNS: (keyof Product | 'image')[] = [
|
||||
'vendor',
|
||||
'stock_quantity',
|
||||
'stock_status',
|
||||
'replenishable',
|
||||
'reorder_qty',
|
||||
'price',
|
||||
'regular_price',
|
||||
'daily_sales_avg',
|
||||
@@ -141,11 +93,11 @@ const DEFAULT_VISIBLE_COLUMNS: (keyof Product | 'image')[] = [
|
||||
|
||||
export function Products() {
|
||||
const [filters, setFilters] = useState<Record<string, string | number | boolean>>({});
|
||||
const [sortColumn, setSortColumn] = useState<keyof Product>('title');
|
||||
const [sortColumn, setSortColumn] = useState<ColumnKey>('title');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [visibleColumns, setVisibleColumns] = useState<Set<keyof Product | 'image'>>(new Set(DEFAULT_VISIBLE_COLUMNS));
|
||||
const [columnOrder, setColumnOrder] = useState<(keyof Product | 'image')[]>([
|
||||
const [visibleColumns, setVisibleColumns] = useState<Set<ColumnKey>>(new Set(DEFAULT_VISIBLE_COLUMNS));
|
||||
const [columnOrder, setColumnOrder] = useState<ColumnKey[]>([
|
||||
...DEFAULT_VISIBLE_COLUMNS,
|
||||
...AVAILABLE_COLUMNS.map(col => col.key).filter(key => !DEFAULT_VISIBLE_COLUMNS.includes(key))
|
||||
]);
|
||||
@@ -162,11 +114,6 @@ export function Products() {
|
||||
return acc;
|
||||
}, {} as Record<string, typeof AVAILABLE_COLUMNS>);
|
||||
|
||||
// Handle column reordering from drag and drop
|
||||
const handleColumnOrderChange = (newOrder: (keyof Product | 'image')[]) => {
|
||||
setColumnOrder(newOrder);
|
||||
};
|
||||
|
||||
// Function to fetch products data
|
||||
const fetchProducts = async () => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
@@ -32,11 +32,24 @@ export interface Product {
|
||||
first_sale_date?: string;
|
||||
last_sale_date?: string;
|
||||
last_purchase_date?: string;
|
||||
days_of_stock?: number;
|
||||
stock_status?: string;
|
||||
abc_class?: string;
|
||||
profit_margin?: number;
|
||||
days_of_inventory?: number;
|
||||
weeks_of_inventory?: number;
|
||||
reorder_point?: number;
|
||||
max_stock?: number;
|
||||
safety_stock?: number;
|
||||
avg_margin_percent?: number;
|
||||
total_revenue?: number;
|
||||
inventory_value?: number;
|
||||
cost_of_goods_sold?: number;
|
||||
gross_profit?: number;
|
||||
gmroi?: number;
|
||||
avg_lead_time_days?: number;
|
||||
last_received_date?: string;
|
||||
abc_class?: string;
|
||||
stock_status?: string;
|
||||
turnover_rate?: number;
|
||||
current_lead_time?: number;
|
||||
target_lead_time?: number;
|
||||
lead_time_status?: string;
|
||||
reorder_qty?: number;
|
||||
overstocked_amt?: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user