Files
MyPOS/server/controller/salesController.js
T
2025-11-13 19:09:00 +08:00

281 lines
8.8 KiB
JavaScript

// server/controllers/salesController.js
import supabase from "../config/supabaseClient.js";
// server/controllers/salesController.js
export const getSales = async (req, res) => {
try {
const { page = 1, limit = 10 } = req.query;
const pageNum = parseInt(page);
const limitNum = parseInt(limit);
const offset = (pageNum - 1) * limitNum;
// Get total count
const { count, error: countError } = await supabase
.from('sales')
.select('*', { count: 'exact', head: true });
if (countError) throw countError;
// Get paginated sales
const { data: sales, error } = await supabase
.from('sales')
.select(`
*,
users(full_name),
sale_items(
*,
products(name, price)
)
`)
.order('sale_date', { ascending: false })
.range(offset, offset + limitNum - 1);
if (error) throw error;
res.json({
sales,
pagination: {
currentPage: pageNum,
totalPages: Math.ceil(count / limitNum),
totalSales: count,
hasNext: pageNum < Math.ceil(count / limitNum),
hasPrev: pageNum > 1
}
});
} catch (error) {
console.error('Error fetching sales:', error);
res.status(500).json({ error: 'Failed to fetch sales' });
}
};
export const createSale = async (req, res) => {
const {
user_id,
items,
payment_type,
amount_tendered,
discount_type,
discount_amount,
customer_name
} = req.body;
try {
// Calculate total amount from items
const subtotal = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
// Calculate discount
const discount = discount_amount || 0;
// Calculate tax (no tax if discount is applied)
const taxable_amount = subtotal - discount;
const tax = (discount > 0) ? 0 : taxable_amount * 0.12;
const total_amount = subtotal - discount + tax;
// For GCash and Card, amount_tendered should equal total_amount (no change)
let change_due = 0;
let final_amount_tendered = amount_tendered;
if (payment_type === 'cash') {
change_due = amount_tendered - total_amount;
if (change_due < 0) {
return res.status(400).json({ error: 'Insufficient payment amount' });
}
} else {
// For GCash and Card, payment should be exact
final_amount_tendered = total_amount;
change_due = 0;
}
// Create sale record
const { data: sale, error: saleError } = await supabase
.from('sales')
.insert([
{
user_id,
total_amount,
payment_type,
amount_tendered: final_amount_tendered,
change_due,
discount_type,
discount_amount: discount,
tax_amount: tax,
customer_name: customer_name || null, // Add customer name
status: 'completed'
}
])
.select()
.single();
if (saleError) throw saleError;
// Insert sale items
const saleItems = items.map(item => ({
sale_id: sale.sale_id,
product_id: item.product_id,
quantity: item.quantity,
price: item.price
}));
const { error: itemsError } = await supabase
.from('sale_items')
.insert(saleItems);
if (itemsError) throw itemsError;
// Update inventory and stock together
for (const item of items) {
// Fetch current stock
const { data: product, error: fetchError } = await supabase
.from('products')
.select('stock')
.eq('product_id', item.product_id)
.single();
if (fetchError) throw fetchError;
if (product.stock < item.quantity) {
return res.status(400).json({ error: `Insufficient stock for product ${item.product_id}` });
}
// Update stock
const { error: updateError } = await supabase
.from('products')
.update({ stock: product.stock - item.quantity })
.eq('product_id', item.product_id);
if (updateError) throw updateError;
// Insert inventory log
const { error: inventoryError } = await supabase
.from('inventory_logs')
.insert([
{
product_id: item.product_id,
user_id: user_id,
action: 'sale',
quantity: -item.quantity,
remarks: `Sale #${sale.sale_id}`
}
]);
if (inventoryError) throw inventoryError;
}
res.status(201).json(sale);
} catch (error) {
console.error('Error creating sale:', error);
res.status(500).json({ error: 'Failed to create sale' });
}
};
export const voidSale = async (req, res) => {
const { sale_id } = req.params;
try {
// Get sale details with items
const { data: sale, error: saleError } = await supabase
.from('sales')
.select(`
*,
sale_items(
*,
products(name)
)
`)
.eq('sale_id', sale_id)
.single();
if (saleError) throw saleError;
// Check if sale can be voided
if (sale.status !== 'completed') {
return res.status(400).json({ error: 'Only completed sales can be voided' });
}
// Start transaction - update sale status and restore inventory
const { error: updateError } = await supabase
.from('sales')
.update({ status: 'voided' })
.eq('sale_id', sale_id);
if (updateError) throw updateError;
// Restore inventory for each item
for (const item of sale.sale_items) {
const { error: inventoryError } = await supabase
.from('inventory_logs')
.insert([
{
product_id: item.product_id,
user_id: req.user?.user_id, // from auth middleware
action: 'adjust',
quantity: item.quantity,
remarks: `Voided sale #${sale_id} - ${item.products.name}`
}
]);
if (inventoryError) throw inventoryError;
}
res.json({ message: 'Sale voided successfully', sale_id });
} catch (error) {
console.error('Error voiding sale:', error);
res.status(500).json({ error: 'Failed to void sale' });
}
};
export const refundSale = async (req, res) => {
const { sale_id } = req.params;
try {
// Get sale details with items
const { data: sale, error: saleError } = await supabase
.from('sales')
.select(`
*,
sale_items(
*,
products(name)
)
`)
.eq('sale_id', sale_id)
.single();
if (saleError) throw saleError;
// Check if sale can be refunded
if (sale.status !== 'completed') {
return res.status(400).json({ error: 'Only completed sales can be refunded' });
}
// Update sale status
const { error: updateError } = await supabase
.from('sales')
.update({ status: 'refunded' })
.eq('sale_id', sale_id);
if (updateError) throw updateError;
// Restore inventory for each item
for (const item of sale.sale_items) {
const { error: inventoryError } = await supabase
.from('inventory_logs')
.insert([
{
product_id: item.product_id,
user_id: req.user?.user_id, // from auth middleware
action: 'adjust',
quantity: item.quantity,
remarks: `Refunded sale #${sale_id} - ${item.products.name}`
}
]);
if (inventoryError) throw inventoryError;
}
res.json({ message: 'Sale refunded successfully', sale_id });
} catch (error) {
console.error('Error refunding sale:', error);
res.status(500).json({ error: 'Failed to refund sale' });
}
};