修改更新欠费状态功能
This commit is contained in:
+53
@@ -116,6 +116,59 @@ public class BillingController extends BaseController
|
||||
return success(prcOrderBillService.getBill(billId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch update order credit arrears status (ARREARS -> NORMAL) with available-credit check.
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:billing:arrearsUpdate')")
|
||||
@Log(title = "Billing credit arrears", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/bill/creditArrears")
|
||||
public AjaxResult updateCreditArrears(@RequestBody java.util.List<String> billIds)
|
||||
{
|
||||
if (billIds == null || billIds.isEmpty())
|
||||
{
|
||||
return error("请勾选要更新的订单");
|
||||
}
|
||||
int success = 0;
|
||||
java.util.List<String> fails = new java.util.ArrayList<String>();
|
||||
for (String billId : billIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
prcOrderBillService.updateCreditArrearsStatus(billId);
|
||||
success++;
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
String msg = e.getMessage();
|
||||
if (msg == null || msg.isEmpty())
|
||||
{
|
||||
msg = e.getClass().getSimpleName();
|
||||
}
|
||||
fails.add((billId == null ? "" : billId) + ": " + msg);
|
||||
}
|
||||
}
|
||||
if (fails.isEmpty())
|
||||
{
|
||||
return success("成功更新 " + success + " 条");
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("成功 ").append(success).append(" 条,失败 ").append(fails.size()).append(" 条");
|
||||
int limit = Math.min(fails.size(), 5);
|
||||
for (int i = 0; i < limit; i++)
|
||||
{
|
||||
sb.append(";").append(fails.get(i));
|
||||
}
|
||||
if (fails.size() > limit)
|
||||
{
|
||||
sb.append("…");
|
||||
}
|
||||
if (success > 0)
|
||||
{
|
||||
return success(sb.toString());
|
||||
}
|
||||
return error(sb.toString());
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:billing:feeAdd')")
|
||||
@Log(title = "Billing income fee", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/bill/fee")
|
||||
|
||||
@@ -50,5 +50,13 @@ public interface IPrcOrderBillService
|
||||
*/
|
||||
void clearCreditArrearsByAmount(String customerCode, BigDecimal amount);
|
||||
|
||||
/**
|
||||
* 计费列表手动更新欠费状态:仅 ARREARS 可更为 NORMAL。
|
||||
* 校验逻辑与计费同步额度一致(客户正常 + 有足够可用额度),不通过则拒绝。
|
||||
*
|
||||
* @param billId 订单计费ID
|
||||
* @return 更新后的计费结果
|
||||
*/
|
||||
TmsPriceQuoteResult updateCreditArrearsStatus(String billId);
|
||||
}
|
||||
|
||||
|
||||
+71
@@ -22,6 +22,7 @@ import com.ruoyi.ar.enums.ArIncomeBillFeeStatus;
|
||||
import com.ruoyi.ar.enums.ArOrderBillStatus;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.MessageUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.sign.Md5Utils;
|
||||
import com.ruoyi.price.domain.PrcIncomeBill;
|
||||
@@ -530,6 +531,76 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计费列表手动更新欠费状态:校验可用额度(与计费同步一致)后将 ARREARS 更为 NORMAL。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult updateCreditArrearsStatus(String billId)
|
||||
{
|
||||
if (StringUtils.isEmpty(billId))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.billIdRequired"));
|
||||
}
|
||||
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(billId.trim());
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.notFound"));
|
||||
}
|
||||
if (!BillCreditArrearsStatus.ARREARS.getCode().equals(bill.getCreditArrearsStatus()))
|
||||
{
|
||||
throw new ServiceException("仅欠费订单可更新为正常");
|
||||
}
|
||||
String customerActive = normalizeCustomerActiveStatus(bill.getCustomerActiveStatus());
|
||||
if (!BillCustomerActiveStatus.ACTIVE.getCode().equals(customerActive))
|
||||
{
|
||||
throw new ServiceException("客户状态非正常,不能更新欠费状态");
|
||||
}
|
||||
// 与计费同步一致:已欠费或可用额度不足则拒绝(本单额度已占用,按可用额度>=0 判定)
|
||||
if (isCreditInsufficient(bill.getCustomerId(), bill.getCustomerCode(), BigDecimal.ZERO))
|
||||
{
|
||||
throw new ServiceException("可用额度不足,不能更新为正常");
|
||||
}
|
||||
String operator = SecurityUtils.getUsername();
|
||||
if (StringUtils.isEmpty(operator))
|
||||
{
|
||||
operator = "system";
|
||||
}
|
||||
String normal = BillCreditArrearsStatus.NORMAL.getCode();
|
||||
orderBillMapper.updateCreditArrearsStatus(bill.getBillId(), normal, operator);
|
||||
bill.setCreditArrearsStatus(normal);
|
||||
saveManualArrearsUpdateEsLog(bill, true, null);
|
||||
return toResult(bill, incomeBillFeeMapper.selectByBillId(bill.getBillId()),
|
||||
payBillFeeMapper.selectByBillId(bill.getBillId()));
|
||||
}
|
||||
|
||||
private void saveManualArrearsUpdateEsLog(PrcOrderBill bill, boolean success, String errorMessage)
|
||||
{
|
||||
if (billSearchStore == null || bill == null || StringUtils.isEmpty(bill.getOrderNo()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
JSONObject req = new JSONObject();
|
||||
req.put("action", "MANUAL_CLEAR_ARREARS");
|
||||
req.put("scope", "ORDER");
|
||||
req.put("orderNo", bill.getOrderNo());
|
||||
req.put("billId", bill.getBillId());
|
||||
req.put("customerCode", bill.getCustomerCode());
|
||||
req.put("incomeTotal", bill.getIncomeTotal());
|
||||
req.put("fromStatus", BillCreditArrearsStatus.ARREARS.getCode());
|
||||
req.put("toStatus", BillCreditArrearsStatus.NORMAL.getCode());
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("success", Boolean.valueOf(success));
|
||||
if (StringUtils.isNotEmpty(errorMessage))
|
||||
{
|
||||
resp.put("msg", errorMessage);
|
||||
}
|
||||
String status = success ? EsBizLogSupport.STATUS_SUCCESS : EsBizLogSupport.STATUS_FAIL;
|
||||
String err = success ? null : EsBizLogSupport.cut(errorMessage);
|
||||
billSearchStore.save(EsLogType.ARREARS, bill.getOrderNo(), req.toJSONString(), resp.toJSONString(), status, err);
|
||||
}
|
||||
|
||||
private TmsOrderBillMatchType resolveTmsMatchType(String identifier, PrcOrderBill bill)
|
||||
{
|
||||
if (identifier.equalsIgnoreCase(StringUtils.trim(bill.getOrderNo())))
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Billing list: update credit arrears status permission (parent = price:billing:bill)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
SET @parent_id := (SELECT menu_id FROM sys_menu WHERE perms = 'price:billing:bill' AND menu_type = 'C' LIMIT 1);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT N'更新欠费状态', @parent_id, 13, '', NULL, 1, 0, 'F', '0', '0', 'price:billing:arrearsUpdate', '#', 'admin', sysdate(), N'计费列表手动更新订单额度欠费状态(需校验可用额度)'
|
||||
FROM DUAL
|
||||
WHERE @parent_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:billing:arrearsUpdate');
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, menu_id FROM sys_menu WHERE perms = 'price:billing:arrearsUpdate'
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = sys_menu.menu_id);
|
||||
@@ -104,3 +104,12 @@ export function deleteIncomeBillFee(feeId) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/** 计费列表:批量更新订单额度欠费状态(校验可用额度) */
|
||||
export function updateCreditArrears(billIds) {
|
||||
return request({
|
||||
url: '/price/billing/bill/creditArrears',
|
||||
method: 'put',
|
||||
data: billIds
|
||||
})
|
||||
}
|
||||
|
||||
@@ -159,7 +159,9 @@ const props = defineProps({
|
||||
// schema 项配置
|
||||
item: { type: Object, required: true },
|
||||
// 字典映射表(来自父级 BasicForm)
|
||||
dictMap: { type: Object, default: () => ({}) }
|
||||
dictMap: { type: Object, default: () => ({}) },
|
||||
// 拉伸模式(固定每行 N 项的栅格布局):忽略 item.width,控件填满单元格
|
||||
stretch: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const comp = computed(() => props.item.component || 'input')
|
||||
@@ -167,8 +169,11 @@ const placeholder = computed(() => props.item.placeholder)
|
||||
const valueKey = computed(() => props.item.valueKey || 'value')
|
||||
const labelKey = computed(() => props.item.labelKey || 'label')
|
||||
|
||||
// 控件宽度样式
|
||||
const controlStyle = computed(() => (props.item.width ? { width: props.item.width } : null))
|
||||
// 控件宽度样式(stretch 模式下填满单元格)
|
||||
const controlStyle = computed(() => {
|
||||
if (props.stretch) return { width: '100%' }
|
||||
return props.item.width ? { width: props.item.width } : null
|
||||
})
|
||||
|
||||
// 选项:静态 options 优先,其次字典
|
||||
const options = computed(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:label-position="labelPosition"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:class="['basic-form', { 'basic-form--grid': !inline }]"
|
||||
:class="['basic-form', { 'basic-form--grid': !inline, 'basic-form--perrow': inline && perRow > 0 }]"
|
||||
>
|
||||
<!-- 行内布局:直接平铺 -->
|
||||
<template v-if="inline">
|
||||
@@ -17,10 +17,11 @@
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
:label-width="item.labelWidth"
|
||||
:style="perRow > 0 && item.colSpan > 1 ? { gridColumn: 'span ' + item.colSpan } : undefined"
|
||||
v-bind="item.formItemProps"
|
||||
>
|
||||
<slot :name="`form-${item.prop}`" :model="modelValue" :item="item">
|
||||
<form-control :model="modelValue" :item="item" :dict-map="dictMap" />
|
||||
<form-control :model="modelValue" :item="item" :dict-map="dictMap" :stretch="perRow > 0" />
|
||||
</slot>
|
||||
</el-form-item>
|
||||
</template>
|
||||
@@ -81,6 +82,7 @@
|
||||
* valueKey: 'value', // 选项值字段(默认 value)
|
||||
* labelKey: 'label', // 选项标签字段(默认 label)
|
||||
* span: 12, // 栅格占位(grid 布局,默认 24)
|
||||
* colSpan: 2, // 占据的列数(行内 perRow 栅格模式,默认 1)
|
||||
* width: '200px', // 控件宽度
|
||||
* show: true, // 是否显示
|
||||
* disabled: false,
|
||||
@@ -117,6 +119,11 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 行内布局下固定每行项数(>0 时启用栅格模式,控件拉伸填满单元格;0 表示自动流式换行)
|
||||
perRow: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 标签宽度
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
@@ -239,6 +246,26 @@ defineExpose({
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
/* 行内固定每行 N 项:CSS 栅格,多余项自动换行 */
|
||||
.basic-form--perrow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(v-bind(perRow), minmax(0, 1fr));
|
||||
column-gap: 16px;
|
||||
row-gap: 2px;
|
||||
}
|
||||
.basic-form--perrow :deep(.el-form-item) {
|
||||
display: flex;
|
||||
margin-right: 0;
|
||||
}
|
||||
.basic-form--perrow :deep(.el-form-item__content) {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
/* 操作区居中时独占整行 */
|
||||
.basic-form--perrow .basic-form-action--center {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 操作区居中:占满整行、内容水平居中(搜索按钮换行后居中显示的场景) */
|
||||
.basic-form .basic-form-action--center {
|
||||
width: 100%;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
:rules="searchRules"
|
||||
:label-width="searchLabelWidth"
|
||||
:action-align="searchActionAlign"
|
||||
:per-row="searchPerRow"
|
||||
:size="size"
|
||||
inline
|
||||
v-show="showSearch"
|
||||
@@ -184,6 +185,11 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 搜索区固定每行项数(>0 时启用栅格布局,多余项自动换行;0 为流式换行)
|
||||
searchPerRow: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 列表加载函数:(params) => Promise<{rows, total}> | {rows, total}
|
||||
query: {
|
||||
type: Function,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:search-columns="searchColumns"
|
||||
:query="queryList"
|
||||
search-action-align="center"
|
||||
:search-per-row="4"
|
||||
row-key="billId"
|
||||
>
|
||||
<template #search-orderNos="{ model }">
|
||||
@@ -66,6 +67,18 @@
|
||||
<template #col-payTotal="{ row }">
|
||||
<span class="payable">{{ formatMoney(row.payTotal) }}</span>
|
||||
</template>
|
||||
<template #toolbar="{ selection, ids }">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Refresh"
|
||||
:disabled="!selection.length"
|
||||
v-hasPermi="['price:billing:arrearsUpdate']"
|
||||
@click="handleUpdateArrears(ids, selection)"
|
||||
>更新欠费状态</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
<template #col-action="{ row }">
|
||||
<el-button link type="primary" icon="View" @click="handleView(row)">查看</el-button>
|
||||
</template>
|
||||
@@ -76,7 +89,8 @@
|
||||
</template>
|
||||
|
||||
<script setup name="BillingBill">
|
||||
import { listBill, getBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions } from '@/api/price/billing'
|
||||
import { listBill, getBill, listOrderNoOptions, listTrackingNoOptions, listTransferNoOptions, updateCreditArrears } from '@/api/price/billing'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useProducts } from '@/components/ProductSelect/useProducts'
|
||||
import BillDetail from './components/BillDetail.vue'
|
||||
|
||||
@@ -132,18 +146,53 @@ const columns = [
|
||||
{ label: '操作', prop: 'action', align: 'center', width: 90, fixed: 'right', slot: 'action', showInToolbar: false }
|
||||
]
|
||||
|
||||
const arrearsStatusOptions = [
|
||||
{ label: '正常', value: 'NORMAL' },
|
||||
{ label: '欠费', value: 'ARREARS' }
|
||||
]
|
||||
|
||||
const searchColumns = [
|
||||
{ label: '订单号', prop: 'orderNos', placeholder: '订单号' },
|
||||
{ label: '运单号', prop: 'trackingNos', placeholder: '运单号' },
|
||||
{ label: '转单号', prop: 'transferNos', placeholder: '转单号' },
|
||||
{ label: '客户', prop: 'customerCodes', placeholder: '客户代码' },
|
||||
{ label: '产品代码', prop: 'productCodes', placeholder: '产品代码' }
|
||||
{ label: '产品代码', prop: 'productCodes', placeholder: '产品代码' },
|
||||
{
|
||||
label: '额度欠费',
|
||||
prop: 'creditArrearsStatus',
|
||||
component: 'select',
|
||||
options: arrearsStatusOptions,
|
||||
placeholder: '请选择',
|
||||
props: { clearable: true }
|
||||
}
|
||||
]
|
||||
|
||||
function queryList(params) {
|
||||
return listBill(params)
|
||||
}
|
||||
|
||||
function handleUpdateArrears(ids, selection) {
|
||||
const billIds = (ids && ids.length)
|
||||
? ids
|
||||
: (selection || []).map(row => row.billId).filter(Boolean)
|
||||
if (!billIds.length) {
|
||||
ElMessage.warning('请勾选要更新的订单')
|
||||
return
|
||||
}
|
||||
const arrearsCount = (selection || []).filter(row => row.creditArrearsStatus === 'ARREARS').length
|
||||
ElMessageBox.confirm(
|
||||
'确认将勾选的 ' + billIds.length + ' 条订单更新欠费状态为正常?将逐单校验可用额度,校验不通过的不会更新' +
|
||||
(arrearsCount < billIds.length ? '(当前勾选中欠费 ' + arrearsCount + ' 条)' : '') + '。',
|
||||
'更新欠费状态',
|
||||
{ type: 'warning' }
|
||||
).then(() => {
|
||||
return updateCreditArrears(billIds)
|
||||
}).then(res => {
|
||||
ElMessage.success(res.msg || '更新完成')
|
||||
tableRef.value?.refresh?.()
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function handleView(row) {
|
||||
title.value = '计费详情'
|
||||
open.value = true
|
||||
@@ -217,17 +266,10 @@ function creditArrearsTagType(status) {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* 搜索栏铺满整行:搜索项 flex 等分拉伸,控件占满所在项 */
|
||||
.billing-bill :deep(.basic-form.el-form--inline) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.billing-bill :deep(.basic-form.el-form--inline > .el-form-item:not(.basic-form-action--center)) {
|
||||
flex: 1 1 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.billing-bill :deep(.basic-form.el-form--inline .el-form-item .el-select),
|
||||
.billing-bill :deep(.basic-form.el-form--inline .el-form-item .el-input) {
|
||||
/* 搜索栏走 BasicTable search-per-row=4 的栅格布局(固定 4 项一行,多余换行),
|
||||
这里只补齐插槽渲染的控件宽度,不再覆盖 BasicForm 的布局 */
|
||||
.billing-bill :deep(.basic-form .el-form-item .el-select),
|
||||
.billing-bill :deep(.basic-form .el-form-item .el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user