Merge branch 'test' into feauture-20261104

# Conflicts:
#	bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcProfitReportServiceImpl.java
This commit is contained in:
liuminghui
2026-11-05 16:38:53 +08:00
73 changed files with 5246 additions and 830 deletions
+66 -16
View File
@@ -102,31 +102,81 @@ export function getLocalRebillTask(taskId) {
})
}
export function cancelLocalRebill(taskId) {
return request({
url: '/price/billing/orderBill/rebill/task/' + taskId + '/cancel',
method: 'post'
})
}
/** One in-flight poll per taskId — avoids stacked loops that flood XHR and freeze the page. */
const localRebillPollControllers = new Map()
/**
* Poll local rebill task until terminal status.
* @param {string} taskId
* @param {(task: object) => void} [onProgress]
*/
export async function pollLocalRebillTask(taskId, onProgress) {
const id = String(taskId || '')
if (!id) throw new Error('missing local rebill taskId')
const prev = localRebillPollControllers.get(id)
if (prev) prev.aborted = true
const ctrl = { aborted: false }
localRebillPollControllers.set(id, ctrl)
const started = Date.now()
const maxMs = 30 * 60 * 1000
let lastSig = ''
try {
while (Date.now() - started < maxMs) {
if (ctrl.aborted) {
const err = new Error('local rebill poll superseded')
err.superseded = true
throw err
}
// Hidden tab: slow down; no need to hammer Redis while user is away.
const hidden = typeof document !== 'undefined' && document.visibilityState === 'hidden'
const res = await getLocalRebillTask(id)
const task = res.data || {}
const st = String(task.status || '').toUpperCase()
const sig = [st, task.percent, task.success, task.fail, task.total].join('|')
if (typeof onProgress === 'function' && sig !== lastSig) {
lastSig = sig
onProgress(task)
}
if (st === 'SUCCESS') return { task }
if (st === 'CANCELLED') return { task, cancelled: true }
if (st === 'FAIL') {
const err = new Error(task.error || task.message || 'local rebill failed')
err.task = task
throw err
}
// 1s visible / 2.5s hidden — dense 400ms polls were freezing the list page.
await new Promise(resolve => setTimeout(resolve, hidden ? 2500 : 1000))
}
throw new Error('local rebill timeout')
} finally {
if (localRebillPollControllers.get(id) === ctrl) {
localRebillPollControllers.delete(id)
}
}
}
/**
* Async local rebill; poll until done.
* @param {object} data LocalRebillRequest
* @param {(task: object) => void} [onProgress]
* @param {{ getTaskId?: (taskId: string) => void }} [options]
*/
export async function localRebillAsync(data, onProgress) {
export async function localRebillAsync(data, onProgress, options) {
const submitRes = await submitLocalRebill(data)
const d = submitRes && submitRes.data
const taskId = d && (d.taskId || d)
if (!taskId) throw new Error('missing local rebill taskId')
const started = Date.now()
const maxMs = 30 * 60 * 1000
while (Date.now() - started < maxMs) {
const res = await getLocalRebillTask(String(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 || 'local rebill failed')
err.task = task
throw err
}
await new Promise(resolve => setTimeout(resolve, 800))
}
throw new Error('local rebill timeout')
if (options && typeof options.getTaskId === 'function') options.getTaskId(String(taskId))
return pollLocalRebillTask(String(taskId), onProgress)
}
/** Receivable bill detail */
@@ -0,0 +1,484 @@
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
}
}
+43
View File
@@ -0,0 +1,43 @@
import { createPriceSetApi } from './createPriceSetApi'
export const exclusiveQuoteApi = createPriceSetApi('/price/exclusive')
export const {
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
} = exclusiveQuoteApi
+41 -438
View File
@@ -1,440 +1,43 @@
import request from '@/utils/request'
import { createPriceSetApi } from './createPriceSetApi'
export function listPriceSet(query) {
return request({
url: '/price/set/list',
method: 'get',
params: query
})
}
export const priceSetApi = createPriceSetApi('/price/set')
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',
timeout: 10 * 60 * 1000
})
}
/**
* 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
})
}
/** Update package name and note only. */
export function updatePriceSetBasic(data) {
return request({
url: '/price/set/basic',
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 }
})
}
/** All detail rows for a version (no pagination); one call instead of N byProduct. */
export function listSetDetailByVersion(psvId) {
return request({
url: '/price/set/detail/byVersion',
method: 'get',
params: { psvId }
})
}
/** 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 */
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 }>}
*/
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)
}
}
export 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: '/price/set/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 }.
*/
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()
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: '/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 batchAddSetCustomer(data) {
return request({
url: '/price/set/customer/batch',
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'
})
}
export const {
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
} = priceSetApi
+4
View File
@@ -19,6 +19,10 @@ export function listProfitCustomerOptions(params) {
})
}
export function listProfitAreaRules() {
return request({ url: '/report/profit/area-rules', method: 'get' })
}
export function listProfitSummary(params) {
return request({
url: '/report/profit/summary',