✨ 支持按单号批量排除账单订单
This commit is contained in:
@@ -134,6 +134,23 @@ public class ArBillController extends BaseController {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 查询需要从全选结果中排除的订单;空条件不执行查询,避免返回全部订单。 */
|
||||
@PreAuthorize("@ss.hasPermi('ar:bill:generate')")
|
||||
@PostMapping("/generate/exclusion-preview")
|
||||
public AjaxResult previewExcluded(@RequestBody ArBillGenerateQuery query) {
|
||||
if (query == null || StringUtils.isEmpty(StringUtils.trim(query.getExcludeOrderOrTrackingNo()))) {
|
||||
throw new ServiceException("请输入订单号、运单号或转单号后再查询");
|
||||
}
|
||||
PageHelper.startPage(query.getPageNum() == null ? 1 : query.getPageNum(), 20).setReasonable(true);
|
||||
List<PrcOrderBill> list = billService.previewExcludedBill(query);
|
||||
TableDataInfo pageData = getDataTable(list);
|
||||
AjaxResult result = success();
|
||||
result.put("rows", pageData.getRows());
|
||||
result.put("total", pageData.getTotal());
|
||||
result.put("totalAmount", billService.sumPreviewBill(query));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建账单头和异步生成任务,接口不等待全部订单明细处理完成。
|
||||
*/
|
||||
|
||||
@@ -32,6 +32,8 @@ public class ArBillGenerateRequest
|
||||
private String selectionMode = ArBillSelectionMode.EXPLICIT.getCode();
|
||||
/** 全选模式中用户明确排除的来源订单 ID。 */
|
||||
private List<String> excludedSourceBillIds;
|
||||
/** 全选模式下按订单号、运单号或转单号排除;后台生成任务按批次应用该条件。 */
|
||||
private String excludedOrderOrTrackingNo;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@JsonDeserialize(using = StrictDateDeserializer.class)
|
||||
private Date billDate;
|
||||
|
||||
@@ -8,7 +8,7 @@ public enum ArBillSelectionMode
|
||||
{
|
||||
/** 手动选择当前预览结果中的订单。 */
|
||||
EXPLICIT("EXPLICIT", "手动选择"),
|
||||
/** 选择全部查询结果,仅排除用户明确勾选的订单。 */
|
||||
/** 选择全部查询结果,再按用户提供的排除条件移除匹配订单。 */
|
||||
ALL_MATCHED("ALL_MATCHED", "全选查询结果");
|
||||
|
||||
private final String code;
|
||||
|
||||
@@ -37,6 +37,8 @@ public interface IArBillService
|
||||
|
||||
List<PrcOrderBill> previewBill(ArBillGenerateQuery query);
|
||||
|
||||
List<PrcOrderBill> previewExcludedBill(ArBillGenerateQuery query);
|
||||
|
||||
BigDecimal sumPreviewBill(ArBillGenerateQuery query);
|
||||
|
||||
ArBillGenerateTask generateBill(ArBillGenerateRequest request, String operator, String requestId);
|
||||
|
||||
@@ -299,6 +299,15 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 查询待排除的订单;必须带至少一个精确单号,避免空条件返回整个账单范围。 */
|
||||
@Override
|
||||
public List<PrcOrderBill> previewExcludedBill(ArBillGenerateQuery query) {
|
||||
if (query == null || normalizeOrderIdentifiers(query.getExcludeOrderOrTrackingNo()).isEmpty()) {
|
||||
throw new ServiceException("请输入订单号、运单号或转单号后再查询");
|
||||
}
|
||||
return previewBill(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总当前生成条件下的订单应收金额,用于弹窗顶部合计展示。
|
||||
*/
|
||||
@@ -358,8 +367,18 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
throw new ServiceException("全选模式最多排除 " + EXCLUDED_SOURCE_ID_LIMIT
|
||||
+ " 条订单,请缩小查询条件后再生成");
|
||||
}
|
||||
List<String> excludedOrderIdentifiers = allMatched
|
||||
? normalizeOrderIdentifiers(request.getExcludedOrderOrTrackingNo()) : new ArrayList<>();
|
||||
if (!allMatched) {
|
||||
request.setExcludedOrderOrTrackingNo(null);
|
||||
}
|
||||
int orderIdentifierCount = normalizeOrderIdentifiers(request.getOrderOrTrackingNo()).size()
|
||||
+ excludedOrderIdentifiers.size();
|
||||
if (orderIdentifierCount > ORDER_IDENTIFIER_LIMIT) {
|
||||
throw new ServiceException("筛选单号和排除单号合计最多输入 " + ORDER_IDENTIFIER_LIMIT + " 个");
|
||||
}
|
||||
validateOrderIdentifierParameterCount(
|
||||
normalizeOrderIdentifiers(request.getOrderOrTrackingNo()).size(),
|
||||
orderIdentifierCount,
|
||||
allMatched ? excludedSourceBillIds.size() : sourceBillIds.size());
|
||||
request.setSelectionMode(selectionMode);
|
||||
|
||||
@@ -535,6 +554,8 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
ArBillGenerateRequest request = JSON.parseObject(task.getRequestPayload(), ArBillGenerateRequest.class);
|
||||
List<String> sourceBillIds = normalizeSourceBillIds(request.getSourceBillIds());
|
||||
List<String> excludedSourceBillIds = normalizeSourceBillIds(request.getExcludedSourceBillIds());
|
||||
List<String> excludedOrderIdentifiers = ArBillSelectionMode.ALL_MATCHED.getCode().equals(task.getSelectionMode())
|
||||
? normalizeOrderIdentifiers(request.getExcludedOrderOrTrackingNo()) : new ArrayList<>();
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
transactionTemplate.setTimeout(300);
|
||||
|
||||
@@ -544,8 +565,8 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
request.getCustomerId(), request.getCustomerCode(),
|
||||
normalizeOrderIdentifiers(request.getOrderOrTrackingNo()),
|
||||
request.getBillPeriodEnd(), request.getCurrency(), eligibleOrderBillStatuses(),
|
||||
ArIncomeBillFeeStatus.UNBILLED.getCode(),
|
||||
task.getSnapshotCreateTime(), null, excludedSourceBillIds);
|
||||
ArIncomeBillFeeStatus.UNBILLED.getCode(), task.getSnapshotCreateTime(), null,
|
||||
excludedSourceBillIds, excludedOrderIdentifiers);
|
||||
if (totalCount <= 0) {
|
||||
throw new ServiceException("当前全选范围内没有可生成的订单");
|
||||
}
|
||||
@@ -572,7 +593,7 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
}
|
||||
ArBillGenerateTask currentTask = task;
|
||||
GenerateBatchResult result = transactionTemplate.execute(status -> processGenerateBatch(
|
||||
currentTask, request, sourceBillIds, excludedSourceBillIds));
|
||||
currentTask, request, sourceBillIds, excludedSourceBillIds, excludedOrderIdentifiers));
|
||||
if (result == null || result.finished) {
|
||||
transactionTemplate.execute(status -> {
|
||||
completeGenerateTask(taskId);
|
||||
@@ -659,7 +680,8 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
* 每批最多 GENERATE_PAGE_SIZE 条,成功后原子更新任务进度。
|
||||
*/
|
||||
private GenerateBatchResult processGenerateBatch(ArBillGenerateTask task, ArBillGenerateRequest request,
|
||||
List<String> sourceBillIds, List<String> excludedSourceBillIds) {
|
||||
List<String> sourceBillIds, List<String> excludedSourceBillIds,
|
||||
List<String> excludedOrderIdentifiers) {
|
||||
ArBillSelectionMode selectionMode = ArBillSelectionMode.of(task.getSelectionMode());
|
||||
if (selectionMode == null) {
|
||||
throw new ServiceException("账单生成任务的订单选择模式无效");
|
||||
@@ -673,6 +695,7 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
eligibleOrderBillStatuses(), ArIncomeBillFeeStatus.UNBILLED.getCode(),
|
||||
task.getSnapshotCreateTime(), task.getLastSourceBillId(),
|
||||
explicitIds, selectionMode == ArBillSelectionMode.ALL_MATCHED ? excludedSourceBillIds : null,
|
||||
selectionMode == ArBillSelectionMode.ALL_MATCHED ? excludedOrderIdentifiers : null,
|
||||
GENERATE_PAGE_SIZE);
|
||||
if (sources == null || sources.isEmpty()) {
|
||||
return new GenerateBatchResult(true);
|
||||
|
||||
@@ -68,14 +68,16 @@ public interface PrcOrderBillMapper
|
||||
@Param("arBillStatuses") List<String> arBillStatuses, @Param("feeArBillStatus") String feeArBillStatus,
|
||||
@Param("snapshotCreateTime") Date snapshotCreateTime,
|
||||
@Param("lastSourceBillId") String lastSourceBillId, @Param("billIds") List<String> billIds,
|
||||
@Param("excludedBillIds") List<String> excludedBillIds, @Param("limit") int limit);
|
||||
@Param("excludedBillIds") List<String> excludedBillIds,
|
||||
@Param("excludedOrderIdentifiers") List<String> excludedOrderIdentifiers, @Param("limit") int limit);
|
||||
|
||||
int countPrcOrderBillByCustomerPeriod(@Param("customerId") Long customerId,
|
||||
@Param("customerCode") String customerCode, @Param("orderIdentifiers") List<String> orderIdentifiers,
|
||||
@Param("cutoffDate") Date cutoffDate, @Param("currency") String currency,
|
||||
@Param("arBillStatuses") List<String> arBillStatuses, @Param("feeArBillStatus") String feeArBillStatus,
|
||||
@Param("snapshotCreateTime") Date snapshotCreateTime,
|
||||
@Param("billIds") List<String> billIds, @Param("excludedBillIds") List<String> excludedBillIds);
|
||||
@Param("billIds") List<String> billIds, @Param("excludedBillIds") List<String> excludedBillIds,
|
||||
@Param("excludedOrderIdentifiers") List<String> excludedOrderIdentifiers);
|
||||
|
||||
/** 按生成账单时用户勾选的订单读取,并再次校验账单筛选条件。 */
|
||||
public List<PrcOrderBill> selectPrcOrderBillByCustomerCutoffAndIds(@Param("customerId") Long customerId,
|
||||
|
||||
@@ -693,6 +693,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
#{excludedBillId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="excludedOrderIdentifiers != null and excludedOrderIdentifiers.size() > 0">
|
||||
and not (
|
||||
coalesce(p.order_no, '') in
|
||||
<foreach collection="excludedOrderIdentifiers" item="excludedIdentifier" open="(" separator="," close=")">
|
||||
#{excludedIdentifier}
|
||||
</foreach>
|
||||
or coalesce(p.tracking_no, '') in
|
||||
<foreach collection="excludedOrderIdentifiers" item="excludedIdentifier" open="(" separator="," close=")">
|
||||
#{excludedIdentifier}
|
||||
</foreach>
|
||||
or coalesce(p.transfer_no, '') in
|
||||
<foreach collection="excludedOrderIdentifiers" item="excludedIdentifier" open="(" separator="," close=")">
|
||||
#{excludedIdentifier}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<!-- 异步生成使用 bill_id 游标批量读取,避免深分页和一次加载全量订单。 -->
|
||||
|
||||
@@ -50,6 +50,15 @@ export function previewArBill(query) {
|
||||
})
|
||||
}
|
||||
|
||||
export function previewExcludedArBill(query) {
|
||||
return request({
|
||||
url: '/ar/bill/generate/exclusion-preview',
|
||||
method: 'post',
|
||||
data: query,
|
||||
timeout: 60000
|
||||
})
|
||||
}
|
||||
|
||||
export function confirmArBill(arBillId, data = {}) {
|
||||
return request({ url: '/ar/bill/' + arBillId + '/confirm', method: 'put', data })
|
||||
}
|
||||
|
||||
@@ -51,17 +51,16 @@
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-checkbox>
|
||||
</el-tooltip>
|
||||
<el-badge v-if="allSelected" :value="excludedRows.length" :hidden="!excludedRows.length" class="exclude-order-badge">
|
||||
<el-badge v-if="allSelected" :value="confirmedExcludeCount" :hidden="!confirmedExcludeKeyword" class="exclude-order-badge">
|
||||
<el-tooltip content="选择排除订单" placement="top">
|
||||
<el-button circle text :icon="Search" aria-label="选择排除订单" @click="openExcludeDialog" />
|
||||
</el-tooltip>
|
||||
</el-badge>
|
||||
</div>
|
||||
<div v-if="allSelected && excludedRows.length" class="excluded-order-preview">
|
||||
<span class="excluded-order-title">排除订单:</span>
|
||||
<el-tag v-for="row in excludedRows" :key="row.billId" closable size="small" @close="removeExcludedRow(row.billId)">
|
||||
{{ row.orderNo || row.trackingNo || row.billId }}
|
||||
</el-tag>
|
||||
<div v-if="allSelected && confirmedExcludeKeyword" class="excluded-order-preview">
|
||||
<span>已排除 {{ confirmedExcludeCount }} 条订单,应收 {{ money(confirmedExcludeAmount) }}(按单号条件)</span>
|
||||
<el-button link type="primary" @click="openExcludeDialog">修改</el-button>
|
||||
<el-button link type="danger" @click="clearConfirmedExclusions">清除</el-button>
|
||||
</div>
|
||||
<el-table ref="previewTableRef" v-loading="previewLoading" :data="previewRows" row-key="billId" border stripe max-height="400" empty-text="暂无符合条件的计费订单" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" fixed="left" :selectable="isMainRowSelectable" />
|
||||
@@ -96,13 +95,13 @@
|
||||
<el-dialog v-model="excludeDialogOpen" title="选择排除订单" width="1100px" append-to-body destroy-on-close @close="handleExcludeDialogClose">
|
||||
<div class="exclude-toolbar">
|
||||
<div class="order-identifier-input exclude-search-input">
|
||||
<el-input v-model="excludeKeyword" type="textarea" :rows="4" resize="vertical" clearable placeholder="请输入订单号、运单号或转单号" />
|
||||
<el-input v-model="excludeKeyword" type="textarea" :rows="4" resize="vertical" clearable placeholder="请输入订单号、运单号或转单号" @input="invalidateExcludeSearch" />
|
||||
<el-button v-if="excludeKeyword" class="order-identifier-clear" link type="primary" @click="clearExcludeIdentifiers">清空</el-button>
|
||||
</div>
|
||||
<el-button type="primary" :loading="excludeLoading" @click="queryExcludeRows">查询</el-button>
|
||||
<el-button type="primary" :loading="excludeLoading" :disabled="!excludeKeyword.trim()" @click="queryExcludeRows">查询</el-button>
|
||||
</div>
|
||||
<el-table ref="excludeTableRef" v-loading="excludeLoading" :data="excludeRowsPage" row-key="billId" border stripe max-height="420" empty-text="暂无符合条件的订单" @selection-change="handleExcludeSelectionChange">
|
||||
<el-table-column type="selection" width="50" fixed="left" />
|
||||
<div v-if="!excludeSearchSubmitted && !excludeLoading" class="exclude-empty-hint">输入单号并点击查询后显示匹配订单</div>
|
||||
<el-table v-if="excludeSearchSubmitted || excludeLoading" v-loading="excludeLoading" :data="excludeRowsPage" row-key="billId" border stripe max-height="420" empty-text="没有匹配的订单">
|
||||
<el-table-column prop="orderNo" label="订单号" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="trackingNo" label="运单号" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="transferNo" label="转单号" min-width="180" show-overflow-tooltip />
|
||||
@@ -112,7 +111,7 @@
|
||||
<el-table-column prop="currency" label="币种" width="80" />
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="excludeTotal > 0"
|
||||
v-if="excludeSearchSubmitted && excludeTotal > 0"
|
||||
class="exclude-pagination"
|
||||
:current-page="excludePageNum"
|
||||
:page-size="excludePageSize"
|
||||
@@ -121,9 +120,9 @@
|
||||
@current-change="handleExcludePageChange"
|
||||
/>
|
||||
<template #footer>
|
||||
<span class="generate-footer-summary">已选择 {{ excludeDraftRows.length }} 条排除订单</span>
|
||||
<el-button @click="cancelExcludeSelection">取消</el-button>
|
||||
<el-button type="primary" @click="confirmExcludeSelection">确定</el-button>
|
||||
<span class="generate-footer-summary">{{ excludeSearchSubmitted ? `命中 ${excludeTotal} 条 · 应收合计 ${money(excludeTotalAmount)},确认后全部排除` : '查询后将把所有命中订单从本次账单中排除' }}</span>
|
||||
<el-button @click="excludeDialogOpen = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!excludeSearchSubmitted || excludeLoading" @click="confirmExcludeSelection">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -143,7 +142,7 @@
|
||||
</template>
|
||||
|
||||
<script setup name="GenerateBillDialog">
|
||||
import { getDefaultArBankAccount, previewArBill, generateArBill } from '@/api/ar/bill'
|
||||
import { getDefaultArBankAccount, previewArBill, previewExcludedArBill, generateArBill } from '@/api/ar/bill'
|
||||
import { Refresh, Search, Select } from '@element-plus/icons-vue'
|
||||
import { AR_BILL_SELECTION_MODE } from '@/constants/ar/enums'
|
||||
|
||||
@@ -175,21 +174,21 @@ const selectedRows = ref([])
|
||||
const selectedRowMap = new Map()
|
||||
// 选择模式只保存编码,提交时由后端枚举再次校验。
|
||||
const selectionMode = ref(AR_BILL_SELECTION_MODE.EXPLICIT)
|
||||
const excludedRows = ref([])
|
||||
const excludedRowMap = new Map()
|
||||
const excludeDialogOpen = ref(false)
|
||||
const excludeLoading = ref(false)
|
||||
const excludeKeyword = ref('')
|
||||
const excludeSearchKeyword = ref('')
|
||||
const excludeSearchSubmitted = ref(false)
|
||||
const excludeRowsPage = ref([])
|
||||
const excludeTotal = ref(0)
|
||||
const excludeTotalAmount = ref(0)
|
||||
const excludePageNum = ref(1)
|
||||
// 全选后的排除弹窗也按 5 条分页,查询条件固定继承主弹窗条件。
|
||||
const excludePageSize = 5
|
||||
const excludeTableRef = ref()
|
||||
const excludeDraftRows = ref([])
|
||||
let excludeDraftMap = new Map()
|
||||
let excludeDialogConfirmed = false
|
||||
let syncingExcludeSelection = false
|
||||
// 排除弹窗每页固定 20 条;输入改变后必须重新查询才能确认。
|
||||
const excludePageSize = 20
|
||||
const confirmedExcludeKeyword = ref('')
|
||||
const confirmedExcludeCount = ref(0)
|
||||
const confirmedExcludeAmount = ref(0)
|
||||
let excludeRequestSeq = 0
|
||||
const previewQuery = ref(null)
|
||||
let syncingPageSelection = false
|
||||
const bankAccountLoading = ref(false)
|
||||
@@ -206,12 +205,11 @@ const generateRules = {
|
||||
}
|
||||
const billNameRules = { billName: [{ required: true, message: '请输入账单名称', trigger: 'blur' }] }
|
||||
const selectedAmount = computed(() => selectedRows.value.reduce((sum, row) => sum + (Number(row.incomeTotal) || 0), 0))
|
||||
const excludedAmount = computed(() => excludedRows.value.reduce((sum, row) => sum + (Number(row.incomeTotal) || 0), 0))
|
||||
const allSelected = computed(() => selectionMode.value === AR_BILL_SELECTION_MODE.ALL_MATCHED)
|
||||
const displaySelectedCount = computed(() => selectionMode.value === AR_BILL_SELECTION_MODE.ALL_MATCHED
|
||||
? Math.max(previewTotal.value - excludedRows.value.length, 0) : selectedRows.value.length)
|
||||
? Math.max(previewTotal.value - confirmedExcludeCount.value, 0) : selectedRows.value.length)
|
||||
const displaySelectedAmount = computed(() => selectionMode.value === AR_BILL_SELECTION_MODE.ALL_MATCHED
|
||||
? Math.max(Number(previewTotalAmount.value || 0) - excludedAmount.value, 0) : selectedAmount.value)
|
||||
? Math.max(Number(previewTotalAmount.value || 0) - confirmedExcludeAmount.value, 0) : selectedAmount.value)
|
||||
|
||||
watch(() => props.modelValue, visible => {
|
||||
if (visible) {
|
||||
@@ -249,6 +247,7 @@ function clearOrderIdentifiers() {
|
||||
// 只清空排除输入内容,不自动刷新排除结果;用户点击“查询”后再按新条件筛选。
|
||||
function clearExcludeIdentifiers() {
|
||||
excludeKeyword.value = ''
|
||||
invalidateExcludeSearch()
|
||||
}
|
||||
|
||||
function loadDefaultBankAccount() {
|
||||
@@ -270,10 +269,8 @@ function resetPreview() {
|
||||
selectedRows.value = []
|
||||
selectedRowMap.clear()
|
||||
selectionMode.value = AR_BILL_SELECTION_MODE.EXPLICIT
|
||||
excludedRows.value = []
|
||||
excludedRowMap.clear()
|
||||
excludeDraftRows.value = []
|
||||
excludeDraftMap = new Map()
|
||||
clearConfirmedExclusionState()
|
||||
invalidateExcludeSearch()
|
||||
excludeDialogOpen.value = false
|
||||
previewQuery.value = null
|
||||
previewSearched.value = false
|
||||
@@ -292,14 +289,16 @@ function buildGenerateQuery() {
|
||||
}
|
||||
}
|
||||
|
||||
// 任务请求携带完整筛选快照;全选模式只提交排除 ID,后台按条件游标扫描其余订单。
|
||||
// 任务请求携带完整筛选快照;全选模式将排除单号条件交给后台按批次过滤。
|
||||
function buildGenerateRequest(requestId) {
|
||||
return {
|
||||
...(previewQuery.value || buildGenerateQuery()),
|
||||
billName: billNameForm.billName.trim(),
|
||||
selectionMode: selectionMode.value,
|
||||
sourceBillIds: selectionMode.value === AR_BILL_SELECTION_MODE.ALL_MATCHED ? [] : selectedRows.value.map(row => row.billId),
|
||||
excludedSourceBillIds: selectionMode.value === AR_BILL_SELECTION_MODE.ALL_MATCHED ? Array.from(excludedRowMap.keys()) : [],
|
||||
excludedSourceBillIds: [],
|
||||
excludedOrderOrTrackingNo: selectionMode.value === AR_BILL_SELECTION_MODE.ALL_MATCHED
|
||||
? confirmedExcludeKeyword.value || undefined : undefined,
|
||||
requestId
|
||||
}
|
||||
}
|
||||
@@ -359,8 +358,7 @@ function handleSelectAllChange(checked) {
|
||||
return
|
||||
}
|
||||
selectionMode.value = AR_BILL_SELECTION_MODE.ALL_MATCHED
|
||||
excludedRows.value = []
|
||||
excludedRowMap.clear()
|
||||
clearConfirmedExclusionState()
|
||||
selectedRowMap.clear()
|
||||
selectedRows.value = []
|
||||
if (!generateLoading.value) generateRequestId.value = ''
|
||||
@@ -374,8 +372,7 @@ function handleManualModeChange(checked) {
|
||||
// 从全选切回手动选择时清除排除集合,恢复主表跨页勾选流程。
|
||||
function switchToManualMode() {
|
||||
selectionMode.value = AR_BILL_SELECTION_MODE.EXPLICIT
|
||||
excludedRows.value = []
|
||||
excludedRowMap.clear()
|
||||
clearConfirmedExclusionState()
|
||||
selectedRowMap.clear()
|
||||
selectedRows.value = []
|
||||
if (!generateLoading.value) generateRequestId.value = ''
|
||||
@@ -386,52 +383,64 @@ function isMainRowSelectable() {
|
||||
return selectionMode.value !== AR_BILL_SELECTION_MODE.ALL_MATCHED
|
||||
}
|
||||
|
||||
function syncExcludedRows() {
|
||||
excludedRows.value = Array.from(excludedRowMap.values())
|
||||
}
|
||||
|
||||
function syncExcludeDraftRows() {
|
||||
excludeDraftRows.value = Array.from(excludeDraftMap.values())
|
||||
}
|
||||
|
||||
// 排除弹窗继承客户、币种、截止日和主关键字,只额外允许订单号/运单号 OR 查询。
|
||||
// 打开时恢复已确认输入,但不自动请求;用户点击查询后才显示匹配订单。
|
||||
function openExcludeDialog() {
|
||||
if (!allSelected.value) return
|
||||
excludeDialogConfirmed = false
|
||||
excludeDraftMap = new Map(excludedRowMap)
|
||||
syncExcludeDraftRows()
|
||||
excludeKeyword.value = ''
|
||||
excludeKeyword.value = confirmedExcludeKeyword.value
|
||||
excludeSearchKeyword.value = ''
|
||||
excludeSearchSubmitted.value = false
|
||||
excludePageNum.value = 1
|
||||
excludeRowsPage.value = []
|
||||
excludeTotal.value = 0
|
||||
excludeTotalAmount.value = 0
|
||||
excludeLoading.value = false
|
||||
excludeRequestSeq += 1
|
||||
excludeDialogOpen.value = true
|
||||
fetchExcludeRows()
|
||||
}
|
||||
|
||||
function buildExcludeQuery() {
|
||||
return {
|
||||
...(previewQuery.value || {}),
|
||||
excludeOrderOrTrackingNo: excludeKeyword.value?.trim() || undefined,
|
||||
excludeOrderOrTrackingNo: excludeSearchKeyword.value,
|
||||
pageNum: excludePageNum.value,
|
||||
pageSize: excludePageSize
|
||||
}
|
||||
}
|
||||
|
||||
function queryExcludeRows() {
|
||||
const keyword = excludeKeyword.value?.trim()
|
||||
if (!keyword) {
|
||||
proxy.$modal.msgWarning('请输入订单号、运单号或转单号后再查询')
|
||||
invalidateExcludeSearch()
|
||||
return
|
||||
}
|
||||
excludeSearchKeyword.value = keyword
|
||||
excludePageNum.value = 1
|
||||
fetchExcludeRows()
|
||||
}
|
||||
|
||||
function invalidateExcludeSearch() {
|
||||
excludeRequestSeq += 1
|
||||
excludeSearchSubmitted.value = false
|
||||
excludeRowsPage.value = []
|
||||
excludeTotal.value = 0
|
||||
excludeTotalAmount.value = 0
|
||||
excludePageNum.value = 1
|
||||
excludeLoading.value = false
|
||||
}
|
||||
|
||||
function fetchExcludeRows() {
|
||||
if (!previewQuery.value) return
|
||||
if (!previewQuery.value || !excludeSearchKeyword.value) return
|
||||
const requestSeq = ++excludeRequestSeq
|
||||
excludeLoading.value = true
|
||||
previewArBill(buildExcludeQuery()).then(res => {
|
||||
syncingExcludeSelection = true
|
||||
previewExcludedArBill(buildExcludeQuery()).then(res => {
|
||||
if (requestSeq !== excludeRequestSeq) return
|
||||
excludeRowsPage.value = res.rows || []
|
||||
excludeTotal.value = Number(res.total) || 0
|
||||
nextTick(syncExcludePageSelection)
|
||||
excludeTotalAmount.value = Number(res.totalAmount) || 0
|
||||
excludeSearchSubmitted.value = true
|
||||
}).finally(() => {
|
||||
excludeLoading.value = false
|
||||
if (requestSeq === excludeRequestSeq) excludeLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -440,56 +449,28 @@ function handleExcludePageChange(page) {
|
||||
fetchExcludeRows()
|
||||
}
|
||||
|
||||
// 排除弹窗使用独立 Map 保存跨页选择,关闭或翻页不会丢失已勾选订单。
|
||||
function handleExcludeSelectionChange(rows) {
|
||||
if (syncingExcludeSelection) return
|
||||
const currentPageIds = new Set(excludeRowsPage.value.map(row => row.billId))
|
||||
currentPageIds.forEach(billId => excludeDraftMap.delete(billId))
|
||||
rows.forEach(row => excludeDraftMap.set(row.billId, row))
|
||||
syncExcludeDraftRows()
|
||||
}
|
||||
|
||||
function syncExcludePageSelection() {
|
||||
if (!excludeTableRef.value) {
|
||||
syncingExcludeSelection = false
|
||||
return
|
||||
}
|
||||
syncingExcludeSelection = true
|
||||
excludeTableRef.value.clearSelection()
|
||||
excludeRowsPage.value.forEach(row => {
|
||||
if (excludeDraftMap.has(row.billId)) excludeTableRef.value.toggleRowSelection(row, true)
|
||||
})
|
||||
syncingExcludeSelection = false
|
||||
}
|
||||
|
||||
function confirmExcludeSelection() {
|
||||
excludedRowMap.clear()
|
||||
excludeDraftMap.forEach((row, billId) => excludedRowMap.set(billId, row))
|
||||
syncExcludedRows()
|
||||
if (!excludeSearchSubmitted.value || excludeLoading.value) return
|
||||
confirmedExcludeKeyword.value = excludeSearchKeyword.value
|
||||
confirmedExcludeCount.value = excludeTotal.value
|
||||
confirmedExcludeAmount.value = excludeTotalAmount.value
|
||||
if (!generateLoading.value) generateRequestId.value = ''
|
||||
excludeDialogConfirmed = true
|
||||
excludeDialogOpen.value = false
|
||||
}
|
||||
|
||||
function removeExcludedRow(billId) {
|
||||
excludedRowMap.delete(billId)
|
||||
excludeDraftMap.delete(billId)
|
||||
syncExcludeDraftRows()
|
||||
syncExcludedRows()
|
||||
if (!generateLoading.value) generateRequestId.value = ''
|
||||
function clearConfirmedExclusionState() {
|
||||
confirmedExcludeKeyword.value = ''
|
||||
confirmedExcludeCount.value = 0
|
||||
confirmedExcludeAmount.value = 0
|
||||
}
|
||||
|
||||
function cancelExcludeSelection() {
|
||||
excludeDialogConfirmed = false
|
||||
excludeDialogOpen.value = false
|
||||
function clearConfirmedExclusions() {
|
||||
clearConfirmedExclusionState()
|
||||
if (!generateLoading.value) generateRequestId.value = ''
|
||||
}
|
||||
|
||||
function handleExcludeDialogClose() {
|
||||
if (!excludeDialogConfirmed) {
|
||||
excludeDraftMap = new Map(excludedRowMap)
|
||||
syncExcludeDraftRows()
|
||||
}
|
||||
excludeDialogConfirmed = false
|
||||
invalidateExcludeSearch()
|
||||
}
|
||||
|
||||
// 翻页后把 Map 中的选择同步回 Element Plus 当前页复选框。
|
||||
@@ -561,8 +542,8 @@ function createRequestId() { return globalThis.crypto?.randomUUID?.() || `ar-${D
|
||||
.preview-selection-toolbar :deep(.el-checkbox) { margin-right: 6px; }
|
||||
.preview-selection-toolbar :deep(.el-checkbox__label) { display: inline-flex; align-items: center; padding-left: 4px; font-size: 16px; }
|
||||
.exclude-order-badge { margin-left: 4px; }
|
||||
.excluded-order-preview { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; max-height: 64px; margin: 0 0 8px; padding: 4px 8px; overflow-y: auto; border-radius: 4px; background: var(--el-fill-color-light); }
|
||||
.excluded-order-title { color: var(--el-text-color-secondary); font-size: 13px; }
|
||||
.excluded-order-preview { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; min-height: 34px; margin: 0 0 8px; padding: 4px 8px; border-radius: 4px; background: var(--el-fill-color-light); color: var(--el-text-color-secondary); font-size: 13px; }
|
||||
.exclude-empty-hint { display: flex; align-items: center; justify-content: center; min-height: 180px; margin-top: 12px; border: 1px solid var(--el-border-color-lighter); color: var(--el-text-color-secondary); }
|
||||
.exclude-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
|
||||
.exclude-search-input { width: 540px; }
|
||||
.exclude-pagination { justify-content: flex-end; margin: 12px 0 4px; }
|
||||
|
||||
Reference in New Issue
Block a user