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;