delete and undo

This commit is contained in:
2026-10-10 10:43:24 +05:30
parent 575b00172d
commit ee71d4c24e
25 changed files with 3604 additions and 3469 deletions
+68
View File
@@ -339,6 +339,74 @@ async function run(port) {
});
assert(moved.status === 200 && moved.json.data.currentDealerId === dealerB.json.data.id, 'transfer failed');
const dealerUndo = await request(port, 'POST', '/api/stocks/CH-RED/stage/undo', {
token: dealerToken,
});
assert(dealerUndo.status === 403, 'dealer should not undo a stage');
const undonePayment = await request(port, 'POST', '/api/stocks/CH-RED/stage/undo', {
token: repToken,
});
assert(
undonePayment.status === 200 &&
undonePayment.json.data.currentStage === 'documentsProcessed' &&
undonePayment.json.data.paymentType == null &&
undonePayment.json.data.leaseProviderType == null &&
undonePayment.json.data.financeCompanyName == null &&
!undonePayment.json.data.stageLogDates.paymentApproved,
`undo payment stage failed ${JSON.stringify(undonePayment.json)}`
);
const undoneDocs = await request(port, 'POST', '/api/stocks/CH-RED/stage/undo', {
token: repToken,
});
assert(
undoneDocs.status === 200 && undoneDocs.json.data.currentStage === 'orderedByCustomer',
'undo documents stage failed'
);
const undoneOrder = await request(port, 'POST', '/api/stocks/CH-RED/stage/undo', {
token: repToken,
});
assert(
undoneOrder.status === 200 && undoneOrder.json.data.currentStage === 'received',
'undo ordered stage failed'
);
const undoReceived = await request(port, 'POST', '/api/stocks/CH-RED/stage/undo', {
token: repToken,
});
assert(undoReceived.status === 400, 'received stage should not be undoable');
const undonePlate = await request(port, 'POST', '/api/stocks/CH-GREEN/stage/undo', {
token: adminToken,
});
assert(
undonePlate.status === 200 &&
undonePlate.json.data.currentStage === 'rmvRegistration' &&
undonePlate.json.data.plateNumber == null &&
!undonePlate.json.data.stageLogDates.plateReceived,
`undo plate stage failed ${JSON.stringify(undonePlate.json)}`
);
await addStock('CH-OUT', 'EN-OUT', 1, dealerC.json.data.id);
const repUndoOutside = await request(port, 'POST', '/api/stocks/CH-OUT/stage/undo', {
token: repToken,
});
assert(repUndoOutside.status === 403, 'sales rep should not undo stock outside assignment');
const repDeleteOutside = await request(port, 'DELETE', '/api/stocks/CH-OUT', { token: repToken });
assert(repDeleteOutside.status === 403, 'sales rep should not delete stock outside assignment');
const dealerDelete = await request(port, 'DELETE', '/api/stocks/CH-YELLOW', { token: dealerToken });
assert(dealerDelete.status === 403, 'dealer should not delete a bike');
const deleted = await request(port, 'DELETE', '/api/stocks/CH-YELLOW', { token: repToken });
assert(deleted.status === 200, `stock delete failed ${deleted.status} ${JSON.stringify(deleted.json)}`);
const stockGone = await prisma.stock.findUnique({ where: { chassisNumber: 'CH-YELLOW' } });
const transferGone = await prisma.stockTransfer.findFirst({ where: { stockId: moved.json.data.id } });
assert(!stockGone && !transferGone, 'deleted bike and its transfers should be gone');
const missingStock = await request(port, 'DELETE', '/api/stocks/CH-YELLOW', { token: adminToken });
assert(missingStock.status === 404, 'deleting a missing bike should be 404');
const missingUndo = await request(port, 'POST', '/api/stocks/NO-SUCH/stage/undo', {
token: adminToken,
});
assert(missingUndo.status === 404, 'undo of a missing bike should be 404');
const eventBad = await request(port, 'POST', '/api/events', {
token: dealerToken,
body: {
+67
View File
@@ -166,6 +166,73 @@ router.patch(
})
);
router.post(
'/:chassisNumber/stage/undo',
requireRole('salesRep', 'superAdmin'),
asyncHandler(async (req, res) => {
const chassisNumber = req.params.chassisNumber;
const stock = await prisma.stock.findUnique({
where: { chassisNumber },
include: stockInclude,
});
if (!stock) throw httpError(404, 'Stock not found');
if (!canAccessDealer(req.user, stock.currentDealerId)) {
throw httpError(403, 'Forbidden');
}
const logs = [...stock.stageLogs].sort((a, b) => {
const diff = new Date(a.loggedAt).getTime() - new Date(b.loggedAt).getTime();
if (diff !== 0) return diff;
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
});
const latest = logs[logs.length - 1];
if (!latest || logs.length < 2 || stock.currentStage === 'received' || latest.stage === 'received') {
throw httpError(400, 'Nothing to undo');
}
if (latest.stage !== stock.currentStage) {
throw httpError(400, 'Stage log does not match the current stage');
}
const previous = logs[logs.length - 2];
const data = { currentStage: previous.stage };
if (latest.stage === 'paymentApproved') {
data.paymentType = null;
data.leaseProviderType = null;
data.financeCompanyName = null;
}
if (latest.stage === 'plateReceived') {
data.plateNumber = null;
}
const updated = await prisma.$transaction(async (tx) => {
await tx.stageLog.delete({ where: { id: latest.id } });
return tx.stock.update({
where: { id: stock.id },
data,
include: stockInclude,
});
});
return res.json({ success: true, data: serializeStock(updated) });
})
);
router.delete(
'/:chassisNumber',
requireRole('salesRep', 'superAdmin'),
asyncHandler(async (req, res) => {
const chassisNumber = req.params.chassisNumber;
const stock = await prisma.stock.findUnique({ where: { chassisNumber } });
if (!stock) throw httpError(404, 'Stock not found');
if (!canAccessDealer(req.user, stock.currentDealerId)) {
throw httpError(403, 'Forbidden');
}
await prisma.stock.delete({ where: { id: stock.id } });
return res.json({ success: true });
})
);
router.post(
'/:chassisNumber/transfer',
requireRole('salesRep', 'superAdmin'),