import request from '@/utils/request' export function listPriceSet(query) { return request({ url: '/price/set/list', method: 'get', params: query }) } export function getPriceSet(priceSetId) { return request({ url: '/price/set/' + priceSetId, method: 'get' }) } export function savePriceSet(data) { return request({ url: '/price/set', method: 'post', data: data }) } export function getPriceSetSaveTask(taskId) { return request({ url: '/price/set/save/task/' + taskId, method: 'get' }) } /** * Submit save async and poll Redis progress until SUCCESS/FAIL. * @returns {Promise<{ priceSetId: number|string, task: object }>} */ export 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 } export function updatePriceSet(data) { return request({ url: '/price/set', method: 'put', data: data }) } export function delPriceSet(priceSetIds) { return request({ url: '/price/set/' + priceSetIds, method: 'delete' }) } export function submitPriceSet(priceSetId) { return request({ url: '/price/set/submit/' + priceSetId, method: 'put' }) } export function approvePriceSet(priceSetId) { return request({ url: '/price/set/approve/' + priceSetId, method: 'put' }) } export function rejectPriceSet(priceSetId) { return request({ url: '/price/set/reject/' + priceSetId, method: 'put' }) } export function activatePriceSetVersion(psvId, data) { return request({ url: '/price/set/version/activate/' + psvId, method: 'put', data: data || {} }) } export function copyPriceSetVersion(psvId) { return request({ url: '/price/set/version/copy/' + psvId, method: 'post' }) } /** Create empty draft version under package */ export function createPriceSetVersion(priceSetId) { return request({ url: '/price/set/version/create/' + priceSetId, method: 'post' }) } /** Import excel attachments for a price-set version (via prc_price_set_product.psv_id). */ export function listPriceSetVersionExcelFiles(psvId) { return request({ url: '/price/set/version/' + psvId + '/excelFiles', method: 'get' }) } export function deletePriceSetVersion(psvId) { return request({ url: '/price/set/version/' + psvId, method: 'delete' }) } export function disablePriceSet(priceSetId) { return request({ url: '/price/set/disable/' + priceSetId, method: 'put' }) } export function enablePriceSet(priceSetId) { return request({ url: '/price/set/enable/' + priceSetId, method: 'put' }) } export function copyPriceSet(priceSetId) { return request({ url: '/price/set/copy/' + priceSetId, method: 'post' }) } export function validatePriceSet(data) { return request({ url: '/price/set/validate', method: 'post', data: data }) } export function listSetDetail(query) { return request({ url: '/price/set/detail/list', method: 'get', params: query }) } /** All detail rows for one product under a version (no pagination). */ export function listSetDetailByProduct(psvId, productCode) { return request({ url: '/price/set/detail/byProduct', method: 'get', params: { psvId, productCode } }) } /** 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 */ export function listSetDetailOptions(psvId) { return request({ url: '/price/set/detail/options', method: 'get', params: { psvId } }) } export function addSetDetail(data) { return request({ url: '/price/set/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 }>} */ export async function importSetDetailPricesAsync(file, query, onProgress) { const formData = new FormData() formData.append('file', 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: '/price/set/detail/importPrices?' + params.toString(), method: 'post', data: formData, headers: { 'Content-Type': 'multipart/form-data' } }) 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 }. */ export function previewPullFromLibrary() { return request({ url: '/price/set/detail/previewPullFromLibrary', method: 'post' }) } /** * Preview Excel import (no DB write). * @param {File|Blob} file * @param {{ updateOnly?: boolean, replaceByProductCountry?: boolean, countryCode?: string, details?: Array }} [opts] */ export function previewImportDetailPrices(file, opts = {}) { const formData = new FormData() formData.append('file', 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: '/price/set/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] */ export 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: '/price/set/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') } export function updateSetDetail(data) { return request({ url: '/price/set/detail', method: 'put', data: data }) } /** * Submit async batch write of detail surcharge snapshots and poll until done. * @returns {Promise<{ task: object }>} */ export async function updateSetDetailSurchargeAsync(details, onProgress) { const submitRes = await request({ url: '/price/set/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') } export function delSetDetail(psdIds) { return request({ url: '/price/set/detail/' + psdIds, method: 'delete' }) } export function listSetCustomer(query) { return request({ url: '/price/set/customer/list', method: 'get', params: query }) } export function addSetCustomer(data) { return request({ url: '/price/set/customer', method: 'post', data: data }) } export function updateSetCustomer(data) { return request({ url: '/price/set/customer', method: 'put', data: data }) } export function delSetCustomer(ids) { return request({ url: '/price/set/customer/' + ids, method: 'delete' }) }