update UI batch
This commit is contained in:
parent
8b4f817d49
commit
def35fceff
BIN
products.db
BIN
products.db
Binary file not shown.
BIN
products_old.db
BIN
products_old.db
Binary file not shown.
1349
public/index.html
1349
public/index.html
File diff suppressed because it is too large
Load Diff
203
server.js
203
server.js
|
|
@ -37,7 +37,9 @@ db.serialize(() => {
|
|||
db.run(`CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
batch_id INTEGER NOT NULL,
|
||||
brand TEXT,
|
||||
mpn TEXT NOT NULL,
|
||||
mpn_custom TEXT,
|
||||
sn TEXT NOT NULL,
|
||||
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (batch_id) REFERENCES batches(id) ON DELETE CASCADE
|
||||
|
|
@ -47,7 +49,9 @@ db.serialize(() => {
|
|||
db.run(`CREATE TABLE IF NOT EXISTS items_mix (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
batch_id INTEGER NOT NULL,
|
||||
brand TEXT,
|
||||
mpn TEXT NOT NULL,
|
||||
mpn_custom TEXT,
|
||||
sn TEXT NOT NULL,
|
||||
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (batch_id) REFERENCES batches(id) ON DELETE CASCADE
|
||||
|
|
@ -58,12 +62,38 @@ db.serialize(() => {
|
|||
db.run(`CREATE INDEX IF NOT EXISTS idx_items_mix_batch_id ON items_mix(batch_id)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_items_mpn ON items(mpn)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_items_sn ON items(sn)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_items_brand ON items(brand)`);
|
||||
db.run(`CREATE INDEX IF NOT EXISTS idx_items_mix_brand ON items_mix(brand)`);
|
||||
});
|
||||
|
||||
// ==================== BATCH API ROUTES ====================
|
||||
|
||||
function runAsync(db, sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve(this); // this.lastID, this.changes
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function prepareRunAsync(stmt, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
stmt.run(params, function (err) {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function finalizeAsync(stmt) {
|
||||
return new Promise((resolve, reject) => {
|
||||
stmt.finalize((err) => (err ? reject(err) : resolve()));
|
||||
});
|
||||
}
|
||||
|
||||
// Save batch with items and items_mix
|
||||
app.post('/api/batch/save', (req, res) => {
|
||||
app.post('/api/batch/save', async (req, res) => {
|
||||
const { batch_name, items, items_mix } = req.body;
|
||||
|
||||
if (!batch_name) {
|
||||
|
|
@ -74,70 +104,108 @@ app.post('/api/batch/save', (req, res) => {
|
|||
return res.status(400).json({ error: 'items array is required and must not be empty' });
|
||||
}
|
||||
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION');
|
||||
|
||||
// Insert batch
|
||||
db.run('INSERT INTO batches (batch_name) VALUES (?)', [batch_name], function(err) {
|
||||
if (err) {
|
||||
db.run('ROLLBACK');
|
||||
return res.status(500).json({ error: 'Batch name already exists or database error: ' + err.message });
|
||||
}
|
||||
|
||||
const batchId = this.lastID;
|
||||
let insertedItems = 0;
|
||||
let insertedMixItems = 0;
|
||||
let errors = [];
|
||||
|
||||
// Insert items
|
||||
const itemStmt = db.prepare('INSERT INTO items (batch_id, mpn, sn) VALUES (?, ?, ?)');
|
||||
items.forEach((item, index) => {
|
||||
if (!item.mpn || !item.sn) {
|
||||
errors.push(`Item at index ${index} is missing mpn or sn`);
|
||||
return;
|
||||
}
|
||||
itemStmt.run(batchId, item.mpn, item.sn, (err) => {
|
||||
if (err) errors.push(`Error inserting item at index ${index}: ${err.message}`);
|
||||
else insertedItems++;
|
||||
});
|
||||
});
|
||||
itemStmt.finalize();
|
||||
try {
|
||||
// BEGIN
|
||||
await runAsync(db, 'BEGIN TRANSACTION');
|
||||
|
||||
// Insert items_mix if provided
|
||||
// Insert batch
|
||||
const batchResult = await runAsync(
|
||||
db,
|
||||
'INSERT INTO batches (batch_name) VALUES (?)',
|
||||
[batch_name]
|
||||
);
|
||||
|
||||
const batchId = batchResult.lastID;
|
||||
|
||||
// ===== Insert items =====
|
||||
const itemStmt = db.prepare(
|
||||
'INSERT INTO items (batch_id, brand, mpn, mpn_custom, sn) VALUES (?, ?, ?, ?, ?)'
|
||||
);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
|
||||
if (!item.mpn || !item.sn) {
|
||||
errors.push(`Item at index ${i} is missing mpn or sn`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await prepareRunAsync(itemStmt, [
|
||||
batchId,
|
||||
item.brand || null,
|
||||
item.mpn,
|
||||
item.mpn_custom || null,
|
||||
item.sn
|
||||
]);
|
||||
insertedItems++;
|
||||
} catch (err) {
|
||||
errors.push(`Error inserting item at index ${i}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
await finalizeAsync(itemStmt);
|
||||
|
||||
// ===== Insert items_mix (optional) =====
|
||||
if (Array.isArray(items_mix) && items_mix.length > 0) {
|
||||
const mixStmt = db.prepare('INSERT INTO items_mix (batch_id, mpn, sn) VALUES (?, ?, ?)');
|
||||
items_mix.forEach((item, index) => {
|
||||
const mixStmt = db.prepare(
|
||||
'INSERT INTO items_mix (batch_id, brand, mpn, mpn_custom, sn) VALUES (?, ?, ?, ?, ?)'
|
||||
);
|
||||
|
||||
for (let i = 0; i < items_mix.length; i++) {
|
||||
const item = items_mix[i];
|
||||
|
||||
if (!item.mpn || !item.sn) {
|
||||
errors.push(`Mixed item at index ${index} is missing mpn or sn`);
|
||||
return;
|
||||
}
|
||||
mixStmt.run(batchId, item.mpn, item.sn, (err) => {
|
||||
if (err) errors.push(`Error inserting mixed item at index ${index}: ${err.message}`);
|
||||
else insertedMixItems++;
|
||||
});
|
||||
});
|
||||
mixStmt.finalize();
|
||||
errors.push(`Mixed item at index ${i} is missing mpn or sn`);
|
||||
continue;
|
||||
}
|
||||
|
||||
db.run('COMMIT', (err) => {
|
||||
if (err) {
|
||||
db.run('ROLLBACK');
|
||||
return res.status(500).json({ error: 'Transaction failed: ' + err.message });
|
||||
try {
|
||||
await prepareRunAsync(mixStmt, [
|
||||
batchId,
|
||||
item.brand || null,
|
||||
item.mpn,
|
||||
item.mpn_custom || null,
|
||||
item.sn
|
||||
]);
|
||||
insertedMixItems++;
|
||||
} catch (err) {
|
||||
errors.push(`Error inserting mixed item at index ${i}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
await finalizeAsync(mixStmt);
|
||||
}
|
||||
|
||||
// COMMIT
|
||||
await runAsync(db, 'COMMIT');
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
batch_id: batchId,
|
||||
batch_name,
|
||||
inserted_items: insertedItems,
|
||||
inserted_mix_items: insertedMixItems,
|
||||
errors: errors.length > 0 ? errors : undefined
|
||||
});
|
||||
});
|
||||
errors: errors.length ? errors : undefined
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
// ROLLBACK nếu có lỗi nghiêm trọng
|
||||
try {
|
||||
await runAsync(db, 'ROLLBACK');
|
||||
} catch (_) { }
|
||||
|
||||
return res.status(500).json({
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Get all batches with their items and items_mix
|
||||
app.get('/api/batch/get-all', (req, res) => {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
|
|
@ -157,12 +225,28 @@ app.get('/api/batch/get-all', (req, res) => {
|
|||
let params = [];
|
||||
|
||||
if (search) {
|
||||
const searchCondition = ` WHERE batch_name LIKE ? OR id LIKE ?`;
|
||||
const searchCondition = `
|
||||
WHERE
|
||||
batch_name LIKE ?
|
||||
OR id LIKE ?
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM items
|
||||
WHERE items.batch_id = batches.id
|
||||
AND sn LIKE ?
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM items_mix
|
||||
WHERE items_mix.batch_id = batches.id
|
||||
AND sn LIKE ?
|
||||
)
|
||||
`;
|
||||
|
||||
query += searchCondition;
|
||||
countQuery += searchCondition;
|
||||
|
||||
const searchParam = `%${search}%`;
|
||||
params = [searchParam, searchParam];
|
||||
}
|
||||
params = [searchParam, searchParam, searchParam, searchParam];
|
||||
}
|
||||
|
||||
query += ` ORDER BY ${column} ${order} LIMIT ? OFFSET ?`;
|
||||
params.push(limit, offset);
|
||||
|
|
@ -193,14 +277,14 @@ app.get('/api/batch/get-all', (req, res) => {
|
|||
|
||||
batches.forEach(batch => {
|
||||
// Get items
|
||||
db.all('SELECT mpn, sn, createdAt FROM items WHERE batch_id = ?', [batch.id], (err, items) => {
|
||||
db.all('SELECT brand, mpn, mpn_custom, sn, createdAt FROM items WHERE batch_id = ?', [batch.id], (err, items) => {
|
||||
if (err) {
|
||||
console.error('Error fetching items:', err);
|
||||
items = [];
|
||||
}
|
||||
|
||||
// Get items_mix
|
||||
db.all('SELECT mpn, sn, createdAt FROM items_mix WHERE batch_id = ?', [batch.id], (err, items_mix) => {
|
||||
db.all('SELECT brand, mpn, mpn_custom, sn, createdAt FROM items_mix WHERE batch_id = ?', [batch.id], (err, items_mix) => {
|
||||
if (err) {
|
||||
console.error('Error fetching items_mix:', err);
|
||||
items_mix = [];
|
||||
|
|
@ -246,13 +330,13 @@ app.get('/api/batch/get/:id', (req, res) => {
|
|||
}
|
||||
|
||||
// Get items
|
||||
db.all('SELECT mpn, sn, createdAt FROM items WHERE batch_id = ?', [id], (err, items) => {
|
||||
db.all('SELECT brand, mpn, mpn_custom, sn, createdAt FROM items WHERE batch_id = ?', [id], (err, items) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
||||
// Get items_mix
|
||||
db.all('SELECT mpn, sn, createdAt FROM items_mix WHERE batch_id = ?', [id], (err, items_mix) => {
|
||||
db.all('SELECT brand, mpn, mpn_custom, sn, createdAt FROM items_mix WHERE batch_id = ?', [id], (err, items_mix) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
|
@ -273,7 +357,7 @@ app.get('/api/batch/get/:id', (req, res) => {
|
|||
app.delete('/api/batch/delete/:id', (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
db.run('DELETE FROM batches WHERE id = ?', id, function(err) {
|
||||
db.run('DELETE FROM batches WHERE id = ?', id, function (err) {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
|
@ -300,29 +384,36 @@ app.get('/api/items/search', (req, res) => {
|
|||
SELECT
|
||||
b.id as batch_id,
|
||||
b.batch_name,
|
||||
i.brand,
|
||||
i.mpn,
|
||||
i.mpn_custom,
|
||||
i.sn,
|
||||
i.createdAt,
|
||||
'items' as type
|
||||
FROM items i
|
||||
JOIN batches b ON i.batch_id = b.id
|
||||
WHERE i.mpn LIKE ? OR i.sn LIKE ?
|
||||
WHERE i.mpn LIKE ? OR i.sn LIKE ? OR i.brand LIKE ? OR i.mpn_custom LIKE ?
|
||||
UNION ALL
|
||||
SELECT
|
||||
b.id as batch_id,
|
||||
b.batch_name,
|
||||
im.brand,
|
||||
im.mpn,
|
||||
im.mpn_custom,
|
||||
im.sn,
|
||||
im.createdAt,
|
||||
'items_mix' as type
|
||||
FROM items_mix im
|
||||
JOIN batches b ON im.batch_id = b.id
|
||||
WHERE im.mpn LIKE ? OR im.sn LIKE ?
|
||||
WHERE im.mpn LIKE ? OR im.sn LIKE ? OR im.brand LIKE ? OR im.mpn_custom LIKE ?
|
||||
ORDER BY createdAt DESC
|
||||
LIMIT 100
|
||||
`;
|
||||
|
||||
db.all(query, [searchParam, searchParam, searchParam, searchParam], (err, results) => {
|
||||
db.all(query, [
|
||||
searchParam, searchParam, searchParam, searchParam,
|
||||
searchParam, searchParam, searchParam, searchParam
|
||||
], (err, results) => {
|
||||
if (err) {
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue