import request from '@/utils/request' /** Build price-set style API against a controller base path (e.g. /price/set, /price/exclusive). */ export function createPriceSetApi(basePath) { const base = String(basePath || '/price/set').replace(/\/$/, '') function listPriceSet(query) { return request({ url: base + '/list', method: 'get', params: query }) } function getPriceSet(priceSetId) { return request({ url: base + '/' + priceSetId, method: 'get' }) } function savePriceSet(data) { return request({ url: base + '', method: 'post', data: data }) } function getPriceSetSaveTask(taskId) { return request({ url: base + '/save/task/' + taskId, method: 'get', timeout: 10 * 60 * 1000 }) } /** * Submit save async and poll Redis progress until SUCCESS/FAIL. * @returns {Promise<{ priceSetId: number|string, task: object }>} */ async function savePriceSetAsync(data, onProgress) { const submitRes = await savePriceSet(data) const taskId = pickSaveTaskId(submitRes) if (!taskId) { throw new Error('missing save taskId') } const started = Date.now() const maxMs = 30 * 60 * 1000 while (Date.now() - started < maxMs) { const res = await getPriceSetSaveTask(taskId) const task = res.data || {} if (typeof onProgress === 'function') { onProgress(task) } if (task.status === 'SUCCESS') { return { priceSetId: task.priceSetId, task } } if (task.status === 'FAIL') { const err = new Error(task.error || task.message || 'save failed') err.task = task throw err } await new Promise(resolve => setTimeout(resolve, 400)) } throw new Error('save timeout') } /** Accept data.taskId / data string / legacy msg UUID (Bms String overload). */ function pickSaveTaskId(res) { if (!res) return null const d = res.data if (d && typeof d === 'object' && d.taskId) return String(d.taskId) if (typeof d === 'string' && d.trim()) return d.trim() if (typeof res.msg === 'string') { const m = res.msg.trim() if (/^[a-f0-9]{32}$/i.test(m)) return m if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(m)) { return m.replace(/-/g, '') } } return null } function updatePriceSet(data) { return request({ url: base + '', method: 'put', data: data }) } /** Update package name and note only. */ function updatePriceSetBasic(data) { return request({ url: base + '/basic', method: 'put', data: data }) } function delPriceSet(priceSetIds) { return request({ url: base + '/' + priceSetIds, method: 'delete' }) } function submitPriceSet(priceSetId) { return request({ url: base + '/submit/' + priceSetId, method: 'put' }) } function approvePriceSet(priceSetId) { return request({ url: base + '/approve/' + priceSetId, method: 'put' }) } function rejectPriceSet(priceSetId) { return request({ url: base + '/reject/' + priceSetId, method: 'put' }) } function activatePriceSetVersion(psvId, data) { return request({ url: base + '/version/activate/' + psvId, method: 'put', data: data || {} }) } function copyPriceSetVersion(psvId) { return request({ url: base + '/version/copy/' + psvId, method: 'post' }) } /** Create empty draft version under package */ function createPriceSetVersion(priceSetId) { return request({ url: base + '/version/create/' + priceSetId, method: 'post' }) } /** Import excel attachments for a price-set version (via prc_price_set_product.psv_id). */ function listPriceSetVersionExcelFiles(psvId) { return request({ url: base + '/version/' + psvId + '/excelFiles', method: 'get' }) } function deletePriceSetVersion(psvId) { return request({ url: base + '/version/' + psvId, method: 'delete' }) } function disablePriceSet(priceSetId) { return request({ url: base + '/disable/' + priceSetId, method: 'put' }) } function enablePriceSet(priceSetId) { return request({ url: base + '/enable/' + priceSetId, method: 'put' }) } function copyPriceSet(priceSetId) { return request({ url: base + '/copy/' + priceSetId, method: 'post' }) } function validatePriceSet(data) { return request({ url: base + '/validate', method: 'post', data: data }) } function listSetDetail(query) { return request({ url: base + '/detail/list', method: 'get', params: query }) } /** All detail rows for one product under a version (no pagination). */ function listSetDetailByProduct(psvId, productCode) { return request({ url: base + '/detail/byProduct', method: 'get', params: { psvId, productCode } }) } /** All detail rows for a version (no pagination); one call instead of N byProduct. */ function listSetDetailByVersion(psvId) { return request({ url: base + '/detail/byVersion', method: 'get', params: { psvId } }) } /** 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 */ function listSetDetailOptions(psvId) { return request({ url: base + '/detail/options', method: 'get', params: { psvId } }) } function addSetDetail(data) { return request({ url: base + '/detail', method: 'post', data: data }) } /** * Async import quote Excel into FIXED package detail prices; poll until done. * @param {File|Blob} file * @param {{ priceSetId: string|number, psvId?: string|number, updateOnly?: boolean, replaceByProductCountry?: boolean, countryCode?: string }} query * @param {(task: object) => void} [onProgress] * @returns {Promise<{ task: object }>} */ function appendUploadFiles(formData, file) { const list = Array.isArray(file) ? file : [file] for (const item of list) { const raw = item && (item.raw || item) if (raw) formData.append('files', raw) } } async function importSetDetailPricesAsync(file, query, onProgress) { const formData = new FormData() appendUploadFiles(formData, file) const params = new URLSearchParams() if (query && query.priceSetId != null) params.set('priceSetId', query.priceSetId) if (query && query.psvId != null && query.psvId !== '') params.set('psvId', query.psvId) if (query && query.updateOnly) params.set('updateOnly', 'true') // Package Excel build-price only (product+country replace scope) if (query && query.replaceByProductCountry) params.set('replaceByProductCountry', 'true') if (query && query.countryCode) params.set('countryCode', query.countryCode) const submitRes = await request({ url: base + '/detail/importPrices?' + params.toString(), method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false } }) const taskId = pickSaveTaskId(submitRes) if (!taskId) { throw new Error('missing import taskId') } return pollPriceSetSaveTask(taskId, onProgress) } /** * Preview pull product library (no DB write). Returns { products, details, message }. */ function previewPullFromLibrary() { return request({ url: base + '/detail/previewPullFromLibrary', method: 'post' }) } /** * Preview Excel import (no DB write). * @param {File|Blob} file * @param {{ updateOnly?: boolean, replaceByProductCountry?: boolean, countryCode?: string, details?: Array }} [opts] */ function previewImportDetailPrices(file, opts = {}) { const formData = new FormData() appendUploadFiles(formData, file) if (opts.updateOnly) formData.append('updateOnly', 'true') if (opts.replaceByProductCountry) formData.append('replaceByProductCountry', 'true') if (opts.countryCode) formData.append('countryCode', opts.countryCode) // updateOnly / Excel build-price merge need current draft details if ((opts.updateOnly || opts.replaceByProductCountry || opts.countryCode) && Array.isArray(opts.details)) { formData.append('detailsJson', JSON.stringify(opts.details)) } return request({ url: base + '/detail/previewImportPrices', method: 'post', data: formData, // FormData must not go through repeat-submit JSON.stringify (collapses to "{}") headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false }, timeout: 10 * 60 * 1000 }) } /** * Async pull product library into package version details; poll until done. * @param {{ priceSetId: string|number, psvId?: string|number, force?: boolean }} query * @param {(task: object) => void} [onProgress] */ async function pullSetDetailsFromLibraryAsync(query, onProgress) { const params = new URLSearchParams() if (query && query.priceSetId != null) params.set('priceSetId', query.priceSetId) if (query && query.psvId != null && query.psvId !== '') params.set('psvId', query.psvId) params.set('force', query && query.force ? 'true' : 'false') const submitRes = await request({ url: base + '/detail/pullFromLibrary?' + params.toString(), method: 'post' }) const taskId = pickSaveTaskId(submitRes) if (!taskId) { throw new Error('missing pull taskId') } return pollPriceSetSaveTask(taskId, onProgress) } async function pollPriceSetSaveTask(taskId, onProgress) { const started = Date.now() const maxMs = 30 * 60 * 1000 while (Date.now() - started < maxMs) { const res = await getPriceSetSaveTask(taskId) const task = res.data || {} if (typeof onProgress === 'function') { onProgress(task) } if (task.status === 'SUCCESS') { return { task } } if (task.status === 'FAIL') { const err = new Error(task.error || task.message || 'task failed') err.task = task throw err } await new Promise(resolve => setTimeout(resolve, 400)) } throw new Error('task timeout') } function updateSetDetail(data) { return request({ url: base + '/detail', method: 'put', data: data }) } /** * Submit async batch write of detail surcharge snapshots and poll until done. * @returns {Promise<{ task: object }>} */ async function updateSetDetailSurchargeAsync(details, onProgress) { const submitRes = await request({ url: base + '/detail/surcharge/async', method: 'post', data: details || [] }) const taskId = pickSaveTaskId(submitRes) if (!taskId) { throw new Error('missing surcharge taskId') } const started = Date.now() const maxMs = 30 * 60 * 1000 while (Date.now() - started < maxMs) { const res = await getPriceSetSaveTask(taskId) const task = res.data || {} if (typeof onProgress === 'function') { onProgress(task) } if (task.status === 'SUCCESS') { return { task } } if (task.status === 'FAIL') { const err = new Error(task.error || task.message || 'surcharge save failed') err.task = task throw err } await new Promise(resolve => setTimeout(resolve, 400)) } throw new Error('surcharge save timeout') } function delSetDetail(psdIds) { return request({ url: base + '/detail/' + psdIds, method: 'delete' }) } function listSetCustomer(query) { return request({ url: base + '/customer/list', method: 'get', params: query }) } function addSetCustomer(data) { return request({ url: base + '/customer', method: 'post', data: data }) } function batchAddSetCustomer(data) { return request({ url: base + '/customer/batch', method: 'post', data: data }) } function updateSetCustomer(data) { return request({ url: base + '/customer', method: 'put', data: data }) } function delSetCustomer(ids) { return request({ url: base + '/customer/' + ids, method: 'delete' }) } return { listPriceSet, getPriceSet, savePriceSet, getPriceSetSaveTask, savePriceSetAsync, updatePriceSet, updatePriceSetBasic, delPriceSet, submitPriceSet, approvePriceSet, rejectPriceSet, activatePriceSetVersion, copyPriceSetVersion, createPriceSetVersion, listPriceSetVersionExcelFiles, deletePriceSetVersion, disablePriceSet, enablePriceSet, copyPriceSet, validatePriceSet, listSetDetail, listSetDetailByProduct, listSetDetailByVersion, listSetDetailOptions, addSetDetail, importSetDetailPricesAsync, previewPullFromLibrary, previewImportDetailPrices, pullSetDetailsFromLibraryAsync, updateSetDetail, updateSetDetailSurchargeAsync, delSetDetail, listSetCustomer, addSetCustomer, batchAddSetCustomer, updateSetCustomer, delSetCustomer } }