Update stock status calculations and add restock/overstock qty fields and calculations

This commit is contained in:
2025-01-15 13:45:55 -05:00
parent 12532d4f6f
commit 6b9fdcb162
7 changed files with 211 additions and 145 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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();

View File

@@ -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;
}