delete and undo
This commit is contained in:
@@ -339,6 +339,74 @@ async function run(port) {
|
|||||||
});
|
});
|
||||||
assert(moved.status === 200 && moved.json.data.currentDealerId === dealerB.json.data.id, 'transfer failed');
|
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', {
|
const eventBad = await request(port, 'POST', '/api/events', {
|
||||||
token: dealerToken,
|
token: dealerToken,
|
||||||
body: {
|
body: {
|
||||||
|
|||||||
@@ -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(
|
router.post(
|
||||||
'/:chassisNumber/transfer',
|
'/:chassisNumber/transfer',
|
||||||
requireRole('salesRep', 'superAdmin'),
|
requireRole('salesRep', 'superAdmin'),
|
||||||
|
|||||||
Reference in New Issue
Block a user