Allow copy down on empty cells, tweaks to dates on dashboards, new route for warehouse locations

This commit is contained in:
2026-08-04 09:41:29 -04:00
parent 2a8d9c473b
commit 718eb3f54c
14 changed files with 428 additions and 95 deletions
+35
View File
@@ -1439,6 +1439,41 @@ router.get('/search-products', async (req, res) => {
}
});
// Current warehouse locations for a batch of pids — a single-table read kept
// deliberately tiny. Added for the email app's printable count sheet: shelf
// locations move after putaway, so the sheet pulls them LIVE at print time
// instead of trusting its receipt mirror's snapshot. GET = authenticated-only,
// same as the other read endpoints here.
router.get('/product-locations', async (req, res) => {
const pids = String(req.query.pids || '')
.split(',')
.map(Number)
.filter((n) => Number.isInteger(n) && n > 0);
if (pids.length === 0) {
return res.status(400).json({ error: 'pids (comma-separated positive integers) is required' });
}
if (pids.length > 1000) {
return res.status(400).json({ error: 'Too many pids (max 1000)' });
}
try {
const { connection } = await getDbConnection();
const [rows] = await connection.query(
`SELECT pid, location FROM products WHERE pid IN (${pids.map((p) => connection.escape(p)).join(',')})`
);
// Empty-string locations (unshelved products) are omitted rather than sent
// as '' so callers can treat presence as "has a shelf location".
const locations = {};
for (const row of rows) {
if (row.location) locations[row.pid] = row.location;
}
res.json({ locations });
} catch (error) {
console.error('Error fetching product locations:', error);
res.status(500).json({ error: 'Failed to fetch product locations' });
}
});
// Shared SELECT for product queries (matches search-products fields)
const PRODUCT_SELECT = `
SELECT