573 lines
19 KiB
JavaScript
573 lines
19 KiB
JavaScript
// @ts-check
|
|
|
|
import { getOrderById } from '../../../repositories/order-repo.js'
|
|
import { updateTask } from '../../../repositories/task-repo.js'
|
|
import { createTaskEvent } from '../../../repositories/task-event-repo.js'
|
|
import {
|
|
buyCloudtentaclesSku,
|
|
getCloudtentaclesAsset,
|
|
listCloudtentaclesSku,
|
|
useCloudtentaclesSku,
|
|
} from '../../platforms/cloudtentacles/catalog-service.js'
|
|
import { getCloudtentaclesKnapsack } from '../../platforms/cloudtentacles/knapsack-service.js'
|
|
import {
|
|
backCloudtentaclesVirtualNumber,
|
|
getCloudtentaclesBindInfo,
|
|
} from '../../platforms/cloudtentacles/virtual-number-service.js'
|
|
import { consumeKuaishouEticket } from '../../platforms/kuaishou-eticket/consume-service.js'
|
|
import {
|
|
getKuaishouEticketSourceConfig,
|
|
resolveKuaishouEticketShopConfig,
|
|
} from '../../platforms/kuaishou-eticket/source-config-service.js'
|
|
import { createHttpError } from '../../../utils/http.js'
|
|
import { nowIso } from '../../../utils/time.js'
|
|
import {
|
|
createAdminViewerContext,
|
|
parseTaskContext,
|
|
} from '../admin-read-shared-helpers.js'
|
|
import {
|
|
getRequiredTask,
|
|
mapTaskActionPayload,
|
|
} from '../admin-task-read-helpers.js'
|
|
import {
|
|
ensureTaskClaimLink,
|
|
getTaskClaimExpiresAt,
|
|
maskCode,
|
|
maskPhone,
|
|
} from './shared.js'
|
|
import {
|
|
isKuaishouCloudTask,
|
|
normalizeKuaishouCloudFlow,
|
|
normalizeKuaishouCloudRoleInfo,
|
|
prepareKuaishouCloudBindResourceWithFallback,
|
|
resolveKuaishouCloudBindUrlExpiresAt,
|
|
resolveKuaishouCloudBindingResources,
|
|
resolveKuaishouCloudVnKeyCandidates,
|
|
resolvePersistedCloudtentaclesContext,
|
|
} from './kuaishou-cloud-helpers.js'
|
|
|
|
/** @typedef {import('../../../types/admin-read-inputs.js').AdminEntityIdInput} AdminEntityIdInput */
|
|
/** @typedef {import('../../../types/admin-read-inputs.js').AdminViewerSessionInput} AdminViewerSessionInput */
|
|
/** @typedef {import('../../../types/admin-write-inputs.js').AdminTaskKuaishouCloudDispatchInput} AdminTaskKuaishouCloudDispatchInput */
|
|
/** @typedef {import('../../../types/admin-write-models.js').AdminTaskActionResponse} AdminTaskActionResponse */
|
|
|
|
/** @returns {Promise<AdminTaskActionResponse>} */
|
|
/** @param {AdminEntityIdInput} taskId */
|
|
/** @param {AdminViewerSessionInput | null} [session] */
|
|
export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session = null) {
|
|
const task = await getRequiredTask(taskId)
|
|
const now = nowIso()
|
|
const viewerContext = createAdminViewerContext(session)
|
|
|
|
if (!viewerContext.canManageTaskLifecycle) {
|
|
throw createHttpError('当前账号没有权限准备绑定资源', {
|
|
statusCode: 403,
|
|
errorCode: 'admin_task_prepare_kuaishou_cloud_forbidden',
|
|
})
|
|
}
|
|
|
|
if (!isKuaishouCloudTask(task)) {
|
|
throw createHttpError('当前任务不是快手 cloud 履约任务', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_not_kuaishou_cloud',
|
|
})
|
|
}
|
|
|
|
const taskContext = parseTaskContext(task)
|
|
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
|
const cloudContext = resolvePersistedCloudtentaclesContext()
|
|
const [knapsack, skuList] = await Promise.all([
|
|
getCloudtentaclesKnapsack(cloudContext),
|
|
listCloudtentaclesSku(cloudContext),
|
|
])
|
|
const resolvedBinding = resolveKuaishouCloudBindingResources(flow, {
|
|
skuItems: Array.isArray(skuList.items) ? skuList.items : [],
|
|
knapsackItems: Array.isArray(knapsack.items) ? knapsack.items : [],
|
|
})
|
|
const vnKeyCandidates = resolveKuaishouCloudVnKeyCandidates({
|
|
flow,
|
|
binding: resolvedBinding,
|
|
})
|
|
|
|
if (!resolvedBinding.skuId || vnKeyCandidates.length === 0) {
|
|
throw createHttpError('当前任务缺少可用 cloud 资源,且无法根据商品名自动解析 SKU / VN Key', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_kuaishou_cloud_missing_binding_config',
|
|
})
|
|
}
|
|
|
|
const flowWithResolvedBinding = {
|
|
...flow,
|
|
binding: {
|
|
...flow.binding,
|
|
skuId: resolvedBinding.skuId,
|
|
skuName: resolvedBinding.skuName,
|
|
vnKey: resolvedBinding.vnKey,
|
|
},
|
|
}
|
|
const knapsackItem = resolvedBinding.knapsackItem
|
|
let usedKnapsack = Number(knapsackItem?.count || 0) > 0
|
|
let purchaseTriggered = false
|
|
let assetBefore = 0
|
|
let assetAfter = 0
|
|
const claimLinkState = await ensureTaskClaimLink(task)
|
|
|
|
if (!usedKnapsack) {
|
|
if (!flowWithResolvedBinding.purchase.autoBuyEnabled) {
|
|
throw createHttpError('背包中没有现成库存,且当前配置未开启自动购买', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_kuaishou_cloud_auto_buy_disabled',
|
|
})
|
|
}
|
|
|
|
const asset = await getCloudtentaclesAsset(cloudContext)
|
|
const targetSku = resolvedBinding.skuItem
|
|
|
|
if (!targetSku) {
|
|
throw createHttpError(`cloudtentacles 未找到 SKU ${flowWithResolvedBinding.binding.skuId}`, {
|
|
statusCode: 404,
|
|
errorCode: 'admin_task_kuaishou_cloud_sku_not_found',
|
|
})
|
|
}
|
|
|
|
assetBefore = Number(asset.asset || 0) || 0
|
|
const targetPrice = Number(targetSku.price || 0) || 0
|
|
const requiredAsset = targetPrice + flowWithResolvedBinding.purchase.minAssetReserve
|
|
if (assetBefore < requiredAsset) {
|
|
throw createHttpError(`余额不足,当前 ${assetBefore},至少需要 ${requiredAsset}`, {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_kuaishou_cloud_asset_not_enough',
|
|
})
|
|
}
|
|
|
|
await buyCloudtentaclesSku({
|
|
...cloudContext,
|
|
id: flowWithResolvedBinding.binding.skuId,
|
|
count: 1,
|
|
})
|
|
purchaseTriggered = true
|
|
|
|
const assetResult = await getCloudtentaclesAsset(cloudContext)
|
|
assetAfter = Number(assetResult.asset || 0) || 0
|
|
}
|
|
|
|
const preparedBinding = await prepareKuaishouCloudBindResourceWithFallback({
|
|
cloudContext,
|
|
vnKeyCandidates,
|
|
})
|
|
const vnId = preparedBinding.vnId
|
|
const vnPhone = preparedBinding.vnPhone
|
|
|
|
const nextContext = {
|
|
...taskContext,
|
|
kuaishouCloudFulfillment: {
|
|
...flowWithResolvedBinding,
|
|
binding: {
|
|
...flowWithResolvedBinding.binding,
|
|
vnKey: preparedBinding.vnKey,
|
|
prepareStatus: 'ready',
|
|
vnId,
|
|
vnPhone,
|
|
bindUrl: preparedBinding.bindUrl,
|
|
bindPreparedAt: now,
|
|
bindExpiresAt: resolveKuaishouCloudBindUrlExpiresAt(now),
|
|
bindProbeAt: null,
|
|
bindProbeStatus: 'pending',
|
|
bindProbeMessage: '',
|
|
roleName: '',
|
|
roleId: '',
|
|
},
|
|
role: {
|
|
status: 'pending',
|
|
name: '',
|
|
rid: '',
|
|
refreshedAt: null,
|
|
errorMessage: '',
|
|
rawInfo: null,
|
|
},
|
|
purchase: {
|
|
...flowWithResolvedBinding.purchase,
|
|
usedKnapsack,
|
|
purchaseTriggered,
|
|
assetBefore,
|
|
assetAfter,
|
|
purchaseAt: purchaseTriggered ? now : flowWithResolvedBinding.purchase.purchaseAt,
|
|
},
|
|
},
|
|
}
|
|
|
|
const updatedTask = await updateTask(task.id, {
|
|
task_status: 'waiting_binding',
|
|
inventory_status: 'not_required',
|
|
user_action_status: 'pending',
|
|
claim_token: claimLinkState.token || task.claim_token || '',
|
|
claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task),
|
|
last_error: '',
|
|
context_json: JSON.stringify(nextContext),
|
|
updated_at: now,
|
|
})
|
|
|
|
await createTaskEvent(task.id, 'kuaishou_cloud_binding_prepared', {
|
|
skuId: flowWithResolvedBinding.binding.skuId,
|
|
skuName: flowWithResolvedBinding.binding.skuName,
|
|
vnKey: preparedBinding.vnKey,
|
|
vnId,
|
|
vnPhoneMasked: maskPhone(vnPhone),
|
|
bindUrl: preparedBinding.bindUrl,
|
|
purchaseTriggered,
|
|
usedKnapsack,
|
|
resolvedByName: resolvedBinding.resolvedByName,
|
|
}, now)
|
|
|
|
return {
|
|
task: mapTaskActionPayload(updatedTask),
|
|
claimUrl: claimLinkState.claimUrl,
|
|
token: claimLinkState.token,
|
|
}
|
|
}
|
|
|
|
/** @returns {Promise<AdminTaskActionResponse>} */
|
|
/** @param {AdminEntityIdInput} taskId */
|
|
/** @param {AdminTaskKuaishouCloudDispatchInput} [payload] */
|
|
/** @param {AdminViewerSessionInput | null} [session] */
|
|
export async function dispatchAdminTaskKuaishouCloudFulfillment(taskId, payload = {}, session = null) {
|
|
const task = await getRequiredTask(taskId)
|
|
const now = nowIso()
|
|
const viewerContext = createAdminViewerContext(session)
|
|
|
|
if (!viewerContext.canManageTaskLifecycle) {
|
|
throw createHttpError('当前账号没有权限执行发货', {
|
|
statusCode: 403,
|
|
errorCode: 'admin_task_dispatch_kuaishou_cloud_forbidden',
|
|
})
|
|
}
|
|
|
|
if (!isKuaishouCloudTask(task)) {
|
|
throw createHttpError('当前任务不是快手 cloud 履约任务', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_not_kuaishou_cloud',
|
|
})
|
|
}
|
|
|
|
const taskContext = parseTaskContext(task)
|
|
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
|
const cloudContext = resolvePersistedCloudtentaclesContext()
|
|
|
|
if (!flow.binding.skuId || !flow.binding.vnId || !flow.binding.vnPhone) {
|
|
throw createHttpError('当前任务还没有准备好绑定资源,请先准备绑定资源', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_kuaishou_cloud_not_prepared',
|
|
})
|
|
}
|
|
|
|
const ticketCode = String(payload.ticketCode || '').trim()
|
|
const persistedTicketCode = String(flow.ticket.code || '').trim()
|
|
|
|
if (!persistedTicketCode && !ticketCode) {
|
|
throw createHttpError('客户还没有在领取页提交核销码,暂时不能直接发货', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_kuaishou_cloud_missing_ticket_code',
|
|
})
|
|
}
|
|
|
|
const dispatchResult = await useCloudtentaclesSku({
|
|
...cloudContext,
|
|
id: flow.binding.skuId,
|
|
virtualNumberId: flow.binding.vnId,
|
|
phone: flow.binding.vnPhone,
|
|
})
|
|
|
|
const nextContext = {
|
|
...taskContext,
|
|
kuaishouCloudFulfillment: {
|
|
...flow,
|
|
ticket: {
|
|
...flow.ticket,
|
|
code: ticketCode || persistedTicketCode,
|
|
capturedAt: ticketCode ? now : flow.ticket.capturedAt,
|
|
capturedBy: ticketCode && session
|
|
? {
|
|
userId: Number(session.userId || 0) || 0,
|
|
username: String(session.username || '').trim(),
|
|
role: String(session.role || '').trim(),
|
|
}
|
|
: flow.ticket.capturedBy,
|
|
},
|
|
dispatch: {
|
|
...flow.dispatch,
|
|
status: 'success',
|
|
dispatchAt: now,
|
|
dispatchBy: session
|
|
? {
|
|
userId: Number(session.userId || 0) || 0,
|
|
username: String(session.username || '').trim(),
|
|
role: String(session.role || '').trim(),
|
|
}
|
|
: null,
|
|
sendType: Number(dispatchResult.sendType || 0) || 0,
|
|
note: String(dispatchResult.note || dispatchResult.responseMessage || '').trim(),
|
|
},
|
|
},
|
|
}
|
|
|
|
const updatedTask = await updateTask(task.id, {
|
|
task_status: 'dispatched_pending_return',
|
|
delivery_status: 'delivered',
|
|
result_code: 'kuaishou_cloud_dispatched',
|
|
result_message: String(dispatchResult.responseMessage || dispatchResult.note || 'cloudtentacles 发货成功').trim(),
|
|
last_error: '',
|
|
context_json: JSON.stringify(nextContext),
|
|
updated_at: now,
|
|
})
|
|
|
|
await createTaskEvent(task.id, 'kuaishou_cloud_dispatched', {
|
|
ticketCodeMasked: maskCode(ticketCode || persistedTicketCode),
|
|
skuId: flow.binding.skuId,
|
|
vnId: flow.binding.vnId,
|
|
vnPhoneMasked: maskPhone(flow.binding.vnPhone),
|
|
sendType: dispatchResult.sendType,
|
|
note: dispatchResult.note,
|
|
}, now)
|
|
|
|
return {
|
|
task: mapTaskActionPayload(updatedTask),
|
|
}
|
|
}
|
|
|
|
/** @returns {Promise<AdminTaskActionResponse>} */
|
|
/** @param {AdminEntityIdInput} taskId */
|
|
/** @param {AdminViewerSessionInput | null} [session] */
|
|
export async function refreshAdminTaskKuaishouCloudRoleInfo(taskId, session = null) {
|
|
const task = await getRequiredTask(taskId)
|
|
const now = nowIso()
|
|
const viewerContext = createAdminViewerContext(session)
|
|
|
|
if (!viewerContext.canOperateAssistedTask) {
|
|
throw createHttpError('当前账号没有权限刷新角色信息', {
|
|
statusCode: 403,
|
|
errorCode: 'admin_task_refresh_kuaishou_cloud_role_forbidden',
|
|
})
|
|
}
|
|
|
|
if (!isKuaishouCloudTask(task)) {
|
|
throw createHttpError('当前任务不是快手 cloud 履约任务', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_not_kuaishou_cloud',
|
|
})
|
|
}
|
|
|
|
const taskContext = parseTaskContext(task)
|
|
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
|
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
|
throw createHttpError('当前任务还没有可查询的绑定角色信息,请先准备绑定资源', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_kuaishou_cloud_missing_bind_info_context',
|
|
})
|
|
}
|
|
|
|
const cloudContext = resolvePersistedCloudtentaclesContext()
|
|
const bindInfoResult = await getCloudtentaclesBindInfo({
|
|
...cloudContext,
|
|
key: flow.binding.vnKey,
|
|
id: flow.binding.vnId,
|
|
})
|
|
|
|
const bindInfo = normalizeKuaishouCloudRoleInfo(bindInfoResult.bindInfo)
|
|
const nextContext = {
|
|
...taskContext,
|
|
kuaishouCloudFulfillment: {
|
|
...flow,
|
|
binding: {
|
|
...flow.binding,
|
|
roleName: bindInfo.name,
|
|
roleId: bindInfo.rid,
|
|
},
|
|
role: {
|
|
status: bindInfo.name || bindInfo.rid ? 'ready' : 'pending',
|
|
name: bindInfo.name,
|
|
rid: bindInfo.rid,
|
|
refreshedAt: now,
|
|
errorMessage: bindInfo.name || bindInfo.rid ? '' : '当前还没有查询到角色信息,请让客户完成绑定后再刷新',
|
|
rawInfo: bindInfo.rawInfo,
|
|
},
|
|
},
|
|
}
|
|
|
|
const updatedTask = await updateTask(task.id, {
|
|
role_id: bindInfo.rid || '',
|
|
role_name: bindInfo.name || '',
|
|
context_json: JSON.stringify(nextContext),
|
|
updated_at: now,
|
|
})
|
|
|
|
await createTaskEvent(task.id, 'kuaishou_cloud_role_info_refreshed', {
|
|
roleName: bindInfo.name,
|
|
roleId: bindInfo.rid,
|
|
vnId: flow.binding.vnId,
|
|
refreshedBy: session
|
|
? {
|
|
userId: Number(session.userId || 0) || 0,
|
|
username: String(session.username || '').trim(),
|
|
role: String(session.role || '').trim(),
|
|
}
|
|
: null,
|
|
}, now)
|
|
|
|
return {
|
|
task: mapTaskActionPayload(updatedTask),
|
|
}
|
|
}
|
|
|
|
/** @returns {Promise<AdminTaskActionResponse>} */
|
|
/** @param {AdminEntityIdInput} taskId */
|
|
/** @param {AdminViewerSessionInput | null} [session] */
|
|
export async function returnNumberAdminTaskKuaishouCloudFulfillment(taskId, session = null) {
|
|
const task = await getRequiredTask(taskId)
|
|
const now = nowIso()
|
|
const viewerContext = createAdminViewerContext(session)
|
|
|
|
if (!viewerContext.canManageTaskLifecycle) {
|
|
throw createHttpError('当前账号没有权限退还号码', {
|
|
statusCode: 403,
|
|
errorCode: 'admin_task_return_kuaishou_cloud_forbidden',
|
|
})
|
|
}
|
|
|
|
if (!isKuaishouCloudTask(task)) {
|
|
throw createHttpError('当前任务不是快手 cloud 履约任务', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_not_kuaishou_cloud',
|
|
})
|
|
}
|
|
|
|
const taskContext = parseTaskContext(task)
|
|
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
|
|
const cloudContext = resolvePersistedCloudtentaclesContext()
|
|
|
|
if (!flow.binding.vnId || !flow.binding.vnKey) {
|
|
throw createHttpError('当前任务缺少可退还的虚拟号信息', {
|
|
statusCode: 409,
|
|
errorCode: 'admin_task_kuaishou_cloud_missing_return_context',
|
|
})
|
|
}
|
|
|
|
await backCloudtentaclesVirtualNumber({
|
|
...cloudContext,
|
|
key: flow.binding.vnKey,
|
|
id: flow.binding.vnId,
|
|
})
|
|
|
|
const order = await getOrderById(task.order_id)
|
|
const ticketCode = String(flow.ticket.code || '').trim()
|
|
const shopId = String(order?.shop_id || '').trim()
|
|
const eticketSource = getKuaishouEticketSourceConfig()
|
|
const shopConfig = resolveKuaishouEticketShopConfig({
|
|
shopId,
|
|
shopName: String(order?.shop_name || '').trim(),
|
|
})
|
|
let consumeStatus = 'pending'
|
|
let consumeErrorMessage = ''
|
|
let consumedAt = null
|
|
let nextTaskStatus = 'completed'
|
|
let nextResultCode = 'kuaishou_cloud_completed'
|
|
let nextResultMessage = 'cloudtentacles 发货、退号并完成快手核销'
|
|
|
|
if (!order) {
|
|
consumeStatus = 'failed'
|
|
consumeErrorMessage = '任务关联订单不存在,无法执行快手核销'
|
|
} else if (!ticketCode) {
|
|
consumeStatus = 'failed'
|
|
consumeErrorMessage = '客户未提交有效核销码,无法执行快手核销'
|
|
} else if (!shopConfig || shopConfig.enabled === false || !String(shopConfig.cookie || '').trim()) {
|
|
consumeStatus = 'failed'
|
|
consumeErrorMessage = '订单对应快手小店缺少可用 Cookie,无法执行快手核销'
|
|
} else {
|
|
try {
|
|
const consumeResult = await consumeKuaishouEticket({
|
|
baseUrl: eticketSource.baseUrl,
|
|
cookie: shopConfig.cookie,
|
|
eTicketId: ticketCode,
|
|
oid: String(flow.ticket.oid || '').trim(),
|
|
formToken: String(flow.ticket.formToken || '').trim(),
|
|
})
|
|
|
|
if (consumeResult.consumed) {
|
|
consumeStatus = 'success'
|
|
consumedAt = now
|
|
} else {
|
|
consumeStatus = 'failed'
|
|
consumeErrorMessage = String(consumeResult.errorMessage || '快手核销失败').trim()
|
|
}
|
|
} catch (error) {
|
|
consumeStatus = 'failed'
|
|
consumeErrorMessage = error instanceof Error ? error.message : '快手核销失败'
|
|
}
|
|
}
|
|
|
|
if (consumeStatus !== 'success') {
|
|
nextTaskStatus = 'manual_review'
|
|
nextResultCode = 'kuaishou_cloud_consume_failed'
|
|
nextResultMessage = consumeErrorMessage || '号码已退还,但快手核销未完成,请人工处理'
|
|
}
|
|
|
|
const nextContext = {
|
|
...taskContext,
|
|
kuaishouCloudFulfillment: {
|
|
...flow,
|
|
returnNumber: {
|
|
...flow.returnNumber,
|
|
status: 'success',
|
|
returnedAt: now,
|
|
returnedBy: session
|
|
? {
|
|
userId: Number(session.userId || 0) || 0,
|
|
username: String(session.username || '').trim(),
|
|
role: String(session.role || '').trim(),
|
|
}
|
|
: null,
|
|
},
|
|
consume: {
|
|
...flow.consume,
|
|
status: consumeStatus,
|
|
shopId: shopId || flow.consume.shopId,
|
|
shopName: String(flow.consume.shopName || order?.shop_name || '').trim(),
|
|
autoConsumeEnabled: flow.consume.autoConsumeEnabled === true,
|
|
consumedAt,
|
|
errorMessage: consumeErrorMessage,
|
|
},
|
|
},
|
|
}
|
|
|
|
const updatedTask = await updateTask(task.id, {
|
|
task_status: nextTaskStatus,
|
|
delivery_status: 'delivered',
|
|
result_code: nextResultCode,
|
|
result_message: nextResultMessage,
|
|
redeemed_at: consumeStatus === 'success' ? now : task.redeemed_at,
|
|
last_error: consumeErrorMessage,
|
|
context_json: JSON.stringify(nextContext),
|
|
updated_at: now,
|
|
})
|
|
|
|
await createTaskEvent(task.id, 'kuaishou_cloud_number_returned', {
|
|
vnId: flow.binding.vnId,
|
|
vnPhoneMasked: maskPhone(flow.binding.vnPhone),
|
|
}, now)
|
|
|
|
await createTaskEvent(
|
|
task.id,
|
|
consumeStatus === 'success' ? 'kuaishou_cloud_consumed' : 'kuaishou_cloud_consume_failed',
|
|
{
|
|
ticketCodeMasked: maskCode(ticketCode),
|
|
shopId,
|
|
consumeStatus,
|
|
errorMessage: consumeErrorMessage,
|
|
},
|
|
now,
|
|
)
|
|
|
|
return {
|
|
task: mapTaskActionPayload(updatedTask),
|
|
}
|
|
}
|