cangku/backend/src/api/planningSelectionFissionExternal.js

204 lines
6.7 KiB
JavaScript

const express = require('express');
const {
claimSelectionFissionDeliveryTask,
getSelectionFissionDeliveryPackage,
listSelectionFissionDeliveryPlans,
listSelectionFissionUserShops,
receiveSelectionFissionAutoListingFailure,
receiveSelectionFissionAutoListingResult,
syncSelectionFissionUserShops,
} = require('../services/planningSelectionFissionService');
const {
requireExternalAutomationApiKey: requireApiKey,
requireDesktopPageAccess,
} = require('../services/externalAutomationAuth');
const router = express.Router();
const requirePlanningAccess = requireDesktopPageAccess('planning_upload');
function normalizeBoolean(value, fallback = false) {
if (value === true || value === false) return value;
const text = String(value ?? '').trim().toLowerCase();
if (['1', 'true', 'yes', 'on'].includes(text)) return true;
if (['0', 'false', 'no', 'off'].includes(text)) return false;
return fallback;
}
function getAuthenticatedDesktopUserName(req) {
if (req.externalApiKey?.authType !== 'jwt') return '';
return String(req.user?.name || req.user?.username || '').trim();
}
function buildInput(req) {
const authenticatedUserName = getAuthenticatedDesktopUserName(req);
return {
...req.query,
schemeCode: req.params.schemeCode || req.query.schemeCode || req.query.scheme_code,
schemeId: req.params.schemeCode || req.query.schemeId || req.query.scheme_id,
dingtalkUserName: authenticatedUserName ||
req.query.dingtalkUserName
|| req.query.dingtalk_user_name
|| req.query.dingtalkUsername
|| req.query.userName
|| req.query.user_name,
includeIncomplete: normalizeBoolean(req.query.includeIncomplete || req.query.include_incomplete, false),
};
}
function buildListingResultInput(req) {
const body = req.body || {};
const query = req.query || {};
const input = {
...query,
...body,
claimToken: req.get('x-task-claim-token')
|| req.get('x-claim-token')
|| body.claimToken
|| body.claim_token
|| body.taskClaimToken
|| body.task_claim_token
|| body.task?.claimToken
|| body.task?.claim_token
|| body.claim?.claimToken
|| body.claim?.claim_token
|| query.claimToken
|| query.claim_token
|| query.taskClaimToken
|| query.task_claim_token,
};
const authenticatedUserName = getAuthenticatedDesktopUserName(req);
return authenticatedUserName
? { ...input, dingtalkUserName: authenticatedUserName }
: input;
}
function buildRequestInput(req) {
const input = {
...(req.query || {}),
...(req.body || {}),
};
const authenticatedUserName = getAuthenticatedDesktopUserName(req);
return authenticatedUserName
? { ...input, dingtalkUserName: authenticatedUserName }
: input;
}
function buildRequestContext(req) {
return {
apiKeyId: req.externalApiKey?.id,
apiKeyName: req.externalApiKey?.name,
remoteIp: req.externalApiKey?.remoteIp,
};
}
function handleError(res, error, fallbackMessage) {
const statusCode = error.statusCode || error.status || 500;
if (statusCode >= 500) {
console.error('[SelectionFissionExternalAPI] 请求失败:', error);
}
return res.status(statusCode).json({
success: false,
error: error.message || fallbackMessage,
data: error.data || undefined,
});
}
router.get('/plans', requireApiKey, requirePlanningAccess, async (req, res) => {
try {
const data = await listSelectionFissionDeliveryPlans(buildInput(req));
res.setHeader('Cache-Control', 'no-store');
return res.json({ success: true, data });
} catch (error) {
return handleError(res, error, '查询可交付选品方案失败');
}
});
router.get('/plans/:schemeCode', requireApiKey, requirePlanningAccess, async (req, res) => {
try {
const data = await getSelectionFissionDeliveryPackage(buildInput(req));
res.setHeader('Cache-Control', 'no-store');
return res.json({ success: true, data });
} catch (error) {
return handleError(res, error, '查询选品方案交付包失败');
}
});
router.get('/shops', requireApiKey, requirePlanningAccess, async (req, res) => {
try {
const input = buildRequestInput(req);
const dingtalkUserName = input.dingtalkUserName
|| input.dingtalk_user_name
|| input.dingtalkUsername
|| input.userName
|| input.user_name;
const data = await listSelectionFissionUserShops(dingtalkUserName, {
includeInactive: normalizeBoolean(input.includeInactive || input.include_inactive, false),
});
res.setHeader('Cache-Control', 'no-store');
return res.json({ success: true, data });
} catch (error) {
return handleError(res, error, '查询用户上架店铺失败');
}
});
router.post('/shops/sync', requireApiKey, requirePlanningAccess, async (req, res) => {
try {
const data = await syncSelectionFissionUserShops(
buildRequestInput(req),
buildRequestContext(req)
);
res.setHeader('Cache-Control', 'no-store');
return res.json({ success: true, data });
} catch (error) {
return handleError(res, error, '同步用户上架店铺失败');
}
});
router.post('/tasks/claim', requireApiKey, requirePlanningAccess, async (req, res) => {
try {
const data = await claimSelectionFissionDeliveryTask(
buildRequestInput(req),
buildRequestContext(req)
);
res.setHeader('Cache-Control', 'no-store');
return res.json({ success: true, data });
} catch (error) {
return handleError(res, error, '领取自动上架任务失败');
}
});
async function receiveListingResult(req, res) {
try {
const data = await receiveSelectionFissionAutoListingResult(
req.params.taskId,
buildListingResultInput(req),
buildRequestContext(req)
);
res.setHeader('Cache-Control', 'no-store');
return res.json({ success: true, data });
} catch (error) {
return handleError(res, error, '回传自动上架商品ID失败');
}
}
async function receiveListingFailure(req, res) {
try {
const data = await receiveSelectionFissionAutoListingFailure(
req.params.taskId,
buildListingResultInput(req),
buildRequestContext(req)
);
res.setHeader('Cache-Control', 'no-store');
return res.json({ success: true, data });
} catch (error) {
return handleError(res, error, '回传自动上架失败结果失败');
}
}
router.post('/tasks/:taskId/listing-result', requireApiKey, requirePlanningAccess, receiveListingResult);
router.post('/plans/:taskId/listing-result', requireApiKey, requirePlanningAccess, receiveListingResult);
router.post('/tasks/:taskId/listing-failure', requireApiKey, requirePlanningAccess, receiveListingFailure);
router.post('/plans/:taskId/listing-failure', requireApiKey, requirePlanningAccess, receiveListingFailure);
module.exports = router;