393 lines
11 KiB
TypeScript
393 lines
11 KiB
TypeScript
import { getDb } from "@/lib/db";
|
|
import type {
|
|
AccountBalanceRow,
|
|
AccountRow,
|
|
ContactRow,
|
|
DashboardStats,
|
|
InvoiceRow,
|
|
JournalEntryRow,
|
|
KitRow,
|
|
LowStockRow,
|
|
OrderItemOption,
|
|
PartRow,
|
|
PurchaseOrderListRow,
|
|
PurchaseOrderLineDetailRow,
|
|
SalesOrderListRow,
|
|
SalesOrderLineDetailRow,
|
|
VendorBillRow
|
|
} from "@/lib/types";
|
|
|
|
function db() {
|
|
return getDb();
|
|
}
|
|
|
|
export function getDashboardStats(): DashboardStats {
|
|
const row = db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
(SELECT COUNT(*) FROM parts WHERE kind = 'part') AS total_parts,
|
|
(SELECT COUNT(*) FROM parts WHERE kind = 'assembly') AS total_assemblies,
|
|
(SELECT COUNT(*) FROM customers) AS active_customers,
|
|
(SELECT COUNT(*) FROM vendors) AS active_vendors,
|
|
(SELECT COUNT(*) FROM sales_orders WHERE status IN ('draft', 'open', 'partial')) AS open_sales_orders,
|
|
(SELECT COUNT(*) FROM purchase_orders WHERE status IN ('draft', 'ordered', 'partial')) AS open_purchase_orders,
|
|
(SELECT COUNT(*) FROM customer_invoices WHERE status IN ('open', 'partial')) AS open_invoices,
|
|
(SELECT COUNT(*) FROM vendor_bills WHERE status IN ('open', 'partial')) AS open_vendor_bills,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM parts p
|
|
LEFT JOIN inventory_balances ib ON ib.part_id = p.id
|
|
WHERE ib.quantity_on_hand <= p.reorder_point
|
|
) AS low_stock_count,
|
|
(
|
|
SELECT COALESCE(SUM(ib.quantity_on_hand * p.unit_cost), 0)
|
|
FROM parts p
|
|
LEFT JOIN inventory_balances ib ON ib.part_id = p.id
|
|
) AS inventory_value,
|
|
(
|
|
SELECT COALESCE(SUM(total_amount - paid_amount), 0)
|
|
FROM customer_invoices
|
|
WHERE status IN ('open', 'partial')
|
|
) AS accounts_receivable,
|
|
(
|
|
SELECT COALESCE(SUM(total_amount - paid_amount), 0)
|
|
FROM vendor_bills
|
|
WHERE status IN ('open', 'partial')
|
|
) AS accounts_payable
|
|
`
|
|
)
|
|
.get() as Record<string, number>;
|
|
|
|
return {
|
|
totalParts: row.total_parts ?? 0,
|
|
totalAssemblies: row.total_assemblies ?? 0,
|
|
activeCustomers: row.active_customers ?? 0,
|
|
activeVendors: row.active_vendors ?? 0,
|
|
openSalesOrders: row.open_sales_orders ?? 0,
|
|
openPurchaseOrders: row.open_purchase_orders ?? 0,
|
|
openInvoices: row.open_invoices ?? 0,
|
|
openVendorBills: row.open_vendor_bills ?? 0,
|
|
lowStockCount: row.low_stock_count ?? 0,
|
|
inventoryValue: row.inventory_value ?? 0,
|
|
accountsReceivable: row.accounts_receivable ?? 0,
|
|
accountsPayable: row.accounts_payable ?? 0
|
|
};
|
|
}
|
|
|
|
export function getParts(): PartRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
p.id,
|
|
p.sku,
|
|
p.name,
|
|
p.kind,
|
|
p.unit_cost AS unitCost,
|
|
p.sale_price AS salePrice,
|
|
p.reorder_point AS reorderPoint,
|
|
p.unit_of_measure AS unitOfMeasure,
|
|
COALESCE(ib.quantity_on_hand, 0) AS quantityOnHand
|
|
FROM parts p
|
|
LEFT JOIN inventory_balances ib ON ib.part_id = p.id
|
|
ORDER BY p.kind DESC, p.sku ASC
|
|
`
|
|
)
|
|
.all() as PartRow[];
|
|
}
|
|
|
|
export function getOrderItemOptions(): OrderItemOption[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
p.id,
|
|
p.sku,
|
|
p.name,
|
|
p.kind,
|
|
COALESCE(ib.quantity_on_hand, 0) AS quantityOnHand,
|
|
p.sale_price AS salePrice,
|
|
p.unit_cost AS unitCost,
|
|
p.unit_of_measure AS unitOfMeasure
|
|
FROM parts p
|
|
LEFT JOIN inventory_balances ib ON ib.part_id = p.id
|
|
WHERE p.is_active = 1
|
|
ORDER BY p.kind DESC, p.sku ASC
|
|
`
|
|
)
|
|
.all() as OrderItemOption[];
|
|
}
|
|
|
|
export function getAssembliesWithComponents(): KitRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
a.sku AS assemblySku,
|
|
a.name AS assemblyName,
|
|
c.sku AS componentSku,
|
|
c.name AS componentName,
|
|
kc.quantity
|
|
FROM kit_components kc
|
|
INNER JOIN parts a ON a.id = kc.assembly_part_id
|
|
INNER JOIN parts c ON c.id = kc.component_part_id
|
|
ORDER BY a.sku, c.sku
|
|
`
|
|
)
|
|
.all() as KitRow[];
|
|
}
|
|
|
|
export function getCustomers(): ContactRow[] {
|
|
return db().prepare(`SELECT id, code, name, email, phone FROM customers ORDER BY code`).all() as ContactRow[];
|
|
}
|
|
|
|
export function getVendors(): ContactRow[] {
|
|
return db().prepare(`SELECT id, code, name, email, phone FROM vendors ORDER BY code`).all() as ContactRow[];
|
|
}
|
|
|
|
export function getSalesOrders(): SalesOrderListRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
so.id,
|
|
so.order_number AS orderNumber,
|
|
c.name AS customerName,
|
|
so.status,
|
|
so.created_at AS createdAt,
|
|
COALESCE(SUM(sol.quantity * sol.unit_price), 0) AS totalAmount,
|
|
COALESCE(SUM(sol.quantity), 0) AS orderedQuantity,
|
|
COALESCE(SUM(sol.shipped_quantity), 0) AS fulfilledQuantity
|
|
FROM sales_orders so
|
|
INNER JOIN customers c ON c.id = so.customer_id
|
|
LEFT JOIN sales_order_lines sol ON sol.sales_order_id = so.id
|
|
GROUP BY so.id, so.order_number, c.name, so.status, so.created_at
|
|
ORDER BY so.created_at DESC
|
|
`
|
|
)
|
|
.all() as SalesOrderListRow[];
|
|
}
|
|
|
|
export function getPurchaseOrders(): PurchaseOrderListRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
po.id,
|
|
po.order_number AS orderNumber,
|
|
v.name AS vendorName,
|
|
po.status,
|
|
po.created_at AS createdAt,
|
|
COALESCE(SUM(pol.quantity * pol.unit_cost), 0) AS totalAmount,
|
|
COALESCE(SUM(pol.quantity), 0) AS orderedQuantity,
|
|
COALESCE(SUM(pol.received_quantity), 0) AS fulfilledQuantity
|
|
FROM purchase_orders po
|
|
INNER JOIN vendors v ON v.id = po.vendor_id
|
|
LEFT JOIN purchase_order_lines pol ON pol.purchase_order_id = po.id
|
|
GROUP BY po.id, po.order_number, v.name, po.status, po.created_at
|
|
ORDER BY po.created_at DESC
|
|
`
|
|
)
|
|
.all() as PurchaseOrderListRow[];
|
|
}
|
|
|
|
export function getSalesOrderLineDetails(): SalesOrderLineDetailRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
sol.id AS lineId,
|
|
sol.sales_order_id AS salesOrderId,
|
|
sol.part_id AS partId,
|
|
p.sku,
|
|
p.name AS partName,
|
|
sol.quantity,
|
|
sol.shipped_quantity AS fulfilledQuantity,
|
|
sol.quantity - sol.shipped_quantity AS remainingQuantity,
|
|
sol.unit_price AS unitPrice,
|
|
COALESCE(ib.quantity_on_hand, 0) AS quantityOnHand,
|
|
p.unit_of_measure AS unitOfMeasure
|
|
FROM sales_order_lines sol
|
|
INNER JOIN parts p ON p.id = sol.part_id
|
|
LEFT JOIN inventory_balances ib ON ib.part_id = p.id
|
|
ORDER BY sol.sales_order_id DESC, sol.id ASC
|
|
`
|
|
)
|
|
.all() as SalesOrderLineDetailRow[];
|
|
}
|
|
|
|
export function getPurchaseOrderLineDetails(): PurchaseOrderLineDetailRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
pol.id AS lineId,
|
|
pol.purchase_order_id AS purchaseOrderId,
|
|
pol.part_id AS partId,
|
|
p.sku,
|
|
p.name AS partName,
|
|
pol.quantity,
|
|
pol.received_quantity AS fulfilledQuantity,
|
|
pol.quantity - pol.received_quantity AS remainingQuantity,
|
|
pol.unit_cost AS unitCost,
|
|
p.unit_of_measure AS unitOfMeasure
|
|
FROM purchase_order_lines pol
|
|
INNER JOIN parts p ON p.id = pol.part_id
|
|
ORDER BY pol.purchase_order_id DESC, pol.id ASC
|
|
`
|
|
)
|
|
.all() as PurchaseOrderLineDetailRow[];
|
|
}
|
|
|
|
export function getJournalEntries(): JournalEntryRow[] {
|
|
const entries = db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
id,
|
|
entry_type AS entryType,
|
|
reference_type AS referenceType,
|
|
reference_id AS referenceId,
|
|
description,
|
|
created_at AS createdAt
|
|
FROM journal_entries
|
|
ORDER BY created_at DESC, id DESC
|
|
`
|
|
)
|
|
.all() as Array<Omit<JournalEntryRow, "lines">>;
|
|
|
|
const lineStatement = db().prepare(
|
|
`
|
|
SELECT
|
|
account_code AS accountCode,
|
|
account_name AS accountName,
|
|
debit,
|
|
credit
|
|
FROM journal_lines
|
|
WHERE journal_entry_id = ?
|
|
ORDER BY id
|
|
`
|
|
);
|
|
|
|
return entries.map((entry) => ({
|
|
...entry,
|
|
lines: lineStatement.all(entry.id) as JournalEntryRow["lines"]
|
|
}));
|
|
}
|
|
|
|
export function getInvoices(): InvoiceRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
ci.id,
|
|
ci.invoice_number AS invoiceNumber,
|
|
ci.sales_order_id AS salesOrderId,
|
|
c.name AS customerName,
|
|
ci.status,
|
|
ci.invoice_date AS invoiceDate,
|
|
ci.due_date AS dueDate,
|
|
ci.total_amount AS totalAmount,
|
|
ci.paid_amount AS paidAmount,
|
|
ci.total_amount - ci.paid_amount AS balanceDue
|
|
FROM customer_invoices ci
|
|
INNER JOIN customers c ON c.id = ci.customer_id
|
|
ORDER BY ci.invoice_date DESC, ci.id DESC
|
|
`
|
|
)
|
|
.all() as InvoiceRow[];
|
|
}
|
|
|
|
export function getVendorBills(): VendorBillRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
vb.id,
|
|
vb.bill_number AS billNumber,
|
|
vb.purchase_order_id AS purchaseOrderId,
|
|
v.name AS vendorName,
|
|
vb.status,
|
|
vb.bill_date AS billDate,
|
|
vb.due_date AS dueDate,
|
|
vb.total_amount AS totalAmount,
|
|
vb.paid_amount AS paidAmount,
|
|
vb.total_amount - vb.paid_amount AS balanceDue
|
|
FROM vendor_bills vb
|
|
INNER JOIN vendors v ON v.id = vb.vendor_id
|
|
ORDER BY vb.bill_date DESC, vb.id DESC
|
|
`
|
|
)
|
|
.all() as VendorBillRow[];
|
|
}
|
|
|
|
export function getAccounts(): AccountRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
code,
|
|
name,
|
|
category,
|
|
is_system AS isSystem
|
|
FROM accounts
|
|
ORDER BY code
|
|
`
|
|
)
|
|
.all() as AccountRow[];
|
|
}
|
|
|
|
export function getAccountBalances(): AccountBalanceRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
a.code,
|
|
a.name,
|
|
a.category,
|
|
COALESCE(SUM(jl.debit), 0) AS debitTotal,
|
|
COALESCE(SUM(jl.credit), 0) AS creditTotal,
|
|
CASE
|
|
WHEN a.category IN ('asset', 'expense') THEN COALESCE(SUM(jl.debit), 0) - COALESCE(SUM(jl.credit), 0)
|
|
ELSE COALESCE(SUM(jl.credit), 0) - COALESCE(SUM(jl.debit), 0)
|
|
END AS balance
|
|
FROM accounts a
|
|
LEFT JOIN journal_lines jl ON jl.account_code = a.code
|
|
GROUP BY a.code, a.name, a.category
|
|
ORDER BY a.code
|
|
`
|
|
)
|
|
.all() as AccountBalanceRow[];
|
|
}
|
|
|
|
export function getLowStockParts(): LowStockRow[] {
|
|
return db()
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
p.id,
|
|
p.sku,
|
|
p.name,
|
|
p.unit_of_measure AS unitOfMeasure,
|
|
COALESCE(ib.quantity_on_hand, 0) AS quantityOnHand,
|
|
p.reorder_point AS reorderPoint,
|
|
MAX(p.reorder_point - COALESCE(ib.quantity_on_hand, 0), 0) AS suggestedReorderQuantity,
|
|
(
|
|
SELECT v.name
|
|
FROM purchase_order_lines pol
|
|
INNER JOIN purchase_orders po ON po.id = pol.purchase_order_id
|
|
INNER JOIN vendors v ON v.id = po.vendor_id
|
|
WHERE pol.part_id = p.id
|
|
ORDER BY po.created_at DESC
|
|
LIMIT 1
|
|
) AS preferredVendorName
|
|
FROM parts p
|
|
LEFT JOIN inventory_balances ib ON ib.part_id = p.id
|
|
WHERE p.kind = 'part' AND COALESCE(ib.quantity_on_hand, 0) <= p.reorder_point
|
|
ORDER BY suggestedReorderQuantity DESC, p.sku ASC
|
|
`
|
|
)
|
|
.all() as LowStockRow[];
|
|
}
|