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