Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions tests/unit/handlers/work.orders.handlers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,31 @@ test('handlers: createWorkOrder Type 2 (move) relocates the part on its own rack
body: { type: 2, deviceType: 'psu', deviceModel: 'P', deviceIdentifier: 'SN-1', info: { location: 'site.warehouse' } }
})
const partPush = pushed.find(p => p.action === 'updateThing')
const regPush = pushed.find(p => p.action === 'registerThing')
t.is(partPush.params[0].rackId, 'psu-rack-1', 'relocation targets the part rack')
t.is(partPush.params[0].info.location, 'site.warehouse', 'part moved to the destination')
t.ok(partPush.params[0].info.workOrderId, 'relocation carries a workOrderId (part-move gate)')
t.is(partPush.params[0].info.workOrderId, regPush.params[0].id, 'relocation references the created WO id')
})

test('handlers: createWorkOrder Type 2 (move) surfaces a failed relocation push instead of swallowing it', async (t) => {
const ctx = createMockCtxWithOrks([{ rpcPublicKey: 'k' }], async (_k, method, params) => {
if (method === 'pushAction') {
if (params.action === 'updateThing') return { id: null, errors: ['ERR_ORK_ACTION_CALLS_EMPTY'] }
return { id: 'a', errors: [] }
}
if (method === 'listThings') return [{ id: 'part-1', type: 'inventory-miner_part-psu', rack: 'psu-rack-1', info: { location: 'site.lab' } }]
return null
})
ctx.authLib = mockAuthLib
ctx._workOrderRackId = RACK
await t.exception(
() => handlers.createWorkOrder(ctx, {
...userMeta(),
body: { type: 2, deviceType: 'psu', deviceModel: 'P', deviceIdentifier: 'SN-1', info: { location: 'site.warehouse' } }
}),
/ERR_PART_MOVE_PUSH_FAILED/
)
})

test('handlers: createWorkOrdersBatch Type 2 (move) relocates every part', async (t) => {
Expand All @@ -109,8 +132,10 @@ test('handlers: createWorkOrdersBatch Type 2 (move) relocates every part', async
}
})
const partPushes = pushed.filter(p => p.action === 'updateThing')
const regPush = pushed.find(p => p.action === 'registerThing')
t.is(partPushes.length, 2, 'one relocation per device')
t.is(partPushes[0].params[0].info.location, 'site.miner-room')
t.ok(partPushes.every(p => p.params[0].info.workOrderId === regPush.params[0].id), 'every relocation references the created WO id')
})

test('handlers: createWorkOrder merges info.notes, info.remarks, info.site, info.location into thing info', async (t) => {
Expand Down
19 changes: 14 additions & 5 deletions workers/lib/server/handlers/work.orders.handlers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const { randomUUID } = require('crypto')
const { parseJsonQueryParam, flattenRpcResults, escapeRegex, listThingsWithCount } = require('../../utils')
const {
WORK_ORDER_THING_TYPE,
Expand All @@ -9,7 +10,7 @@ const {
SPARE_PART_INITIAL_LOCATION
} = require('../../constants')
const { renderWorkOrderCsv, renderRmaCsv } = require('../lib/work.order.export')
const { submitWorkOrderAction, getWorkOrderRackId } = require('../lib/work.orders')
const { submitWorkOrderAction, getWorkOrderRackId, assertActionApplied } = require('../lib/work.orders')

async function _resolvePartByIdentifier (ctx, identifier) {
const results = await ctx.dataProxy.requestData('listThings', {
Expand All @@ -35,6 +36,7 @@ async function createWorkOrder (ctx, req) {
}

const voter = req._info.user.metadata.email
const woId = randomUUID()
const { info: extraInfo, ...body } = req.body
const info = { ...body, ...extraInfo, createdBy: voter, createdAt: Date.now() }

Expand Down Expand Up @@ -85,10 +87,14 @@ async function createWorkOrder (ctx, req) {
user: voter
}]
// Move WOs auto-close, so the relocation has to happen here or it never will.
if (info.location != null) await submitWorkOrderAction(ctx, req, 'updateThing', { id: part.id, info: { location: info.location } }, part.rack)
// The part rack rejects a location change that omits workOrderId (ERR_PART_MOVE_REQUIRES_WO).
if (info.location != null) {
const partResults = await submitWorkOrderAction(ctx, req, 'updateThing', { id: part.id, info: { location: info.location, workOrderId: woId } }, part.rack)
assertActionApplied(partResults, 'ERR_PART_MOVE_PUSH_FAILED')
}
}

return submitWorkOrderAction(ctx, req, 'registerThing', { info })
return submitWorkOrderAction(ctx, req, 'registerThing', { id: woId, info })
}

function _buildPartsMove (type, part, device, info, voter, ts) {
Expand Down Expand Up @@ -126,6 +132,7 @@ async function createWorkOrdersBatch (ctx, req) {
}

const voter = req._info.user.metadata.email
const woId = randomUUID()
const ts = Date.now()
const [summary] = devices

Expand Down Expand Up @@ -153,13 +160,15 @@ async function createWorkOrdersBatch (ctx, req) {
const move = _buildPartsMove(type, part, device, info, voter, ts)
if (move) partsMoves.push(move)
// Move WOs auto-close, so relocate each part here or it never happens.
// The part rack rejects a location change that omits workOrderId (ERR_PART_MOVE_REQUIRES_WO).
if (type === WORK_ORDER_TYPES.MOVE && info.location != null) {
await submitWorkOrderAction(ctx, req, 'updateThing', { id: part.id, info: { location: info.location } }, part.rack)
const partResults = await submitWorkOrderAction(ctx, req, 'updateThing', { id: part.id, info: { location: info.location, workOrderId: woId } }, part.rack)
assertActionApplied(partResults, 'ERR_PART_MOVE_PUSH_FAILED')
}
}
info.partsMoves = partsMoves

return submitWorkOrderAction(ctx, req, 'registerThing', { info })
return submitWorkOrderAction(ctx, req, 'registerThing', { id: woId, info })
}

async function updateWorkOrder (ctx, req) {
Expand Down
11 changes: 10 additions & 1 deletion workers/lib/server/lib/work.orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,13 @@ async function submitWorkOrderAction (ctx, req, action, paramObj, rackId) {
})
}

module.exports = { getWorkOrderRackId, submitWorkOrderAction }
function assertActionApplied (results, errCode) {
const errors = (results || []).flatMap(r => r?.errors || [])
if (errors.length) {
const err = new Error(`${errCode}:${errors.join(',')}`)
err.statusCode = 502
throw err
}
}

module.exports = { getWorkOrderRackId, submitWorkOrderAction, assertActionApplied }
Loading