应收账单调整优化

This commit is contained in:
caojia
2026-09-16 16:27:29 +08:00
parent d660bfc69f
commit b1ceae4776
10 changed files with 198 additions and 14 deletions
@@ -22,6 +22,7 @@ import com.ruoyi.ar.domain.ArBillLog;
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
import com.ruoyi.ar.domain.vo.ArBillGenerateQuery;
import com.ruoyi.ar.domain.vo.ArBillGenerateRequest;
import com.ruoyi.ar.domain.vo.ArBillQuery;
import com.ruoyi.ar.service.IArBillService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
@@ -43,7 +44,7 @@ public class ArBillController extends BaseController
@PreAuthorize("@ss.hasPermi('ar:bill:list')")
@GetMapping("/list")
public TableDataInfo list(ArBill query)
public TableDataInfo list(ArBillQuery query)
{
startPage();
List<ArBill> list = billService.selectArBillList(query);
@@ -12,9 +12,9 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.ar.domain.ArBalanceWriteoff;
import com.ruoyi.ar.domain.ArBill;
import com.ruoyi.ar.service.IArBalanceWriteoffService;
import com.ruoyi.ar.service.IArBillService;
import com.ruoyi.ar.domain.vo.ArBillQuery;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
@@ -93,7 +93,7 @@ public class CustomerBillController extends BaseController
*/
@PreAuthorize("@ss.hasPermi('customer:bill:list')")
@GetMapping("/list")
public TableDataInfo billList(ArBill query)
public TableDataInfo billList(ArBillQuery query)
{
startPage();
return getDataTable(billService.selectArBillList(query));
@@ -4,6 +4,7 @@ import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -21,10 +22,13 @@ public class ArBill extends BaseEntity
private Long customerId;
private String customerCode;
private String customerName;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date billPeriodStart;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date billPeriodEnd;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date billDate;
private String currency;
@@ -0,0 +1,23 @@
package com.ruoyi.ar.domain.vo;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import com.ruoyi.ar.domain.ArBill;
import lombok.Data;
import lombok.EqualsAndHashCode;
/** 应收账单列表查询参数。 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ArBillQuery extends ArBill
{
private static final long serialVersionUID = 1L;
/** 账单日期查询起始日。 */
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date billDateStart;
/** 账单日期查询结束日。 */
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date billDateEnd;
}
@@ -5,10 +5,11 @@ import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.ar.domain.ArBill;
import com.ruoyi.ar.domain.vo.ArBillQuery;
public interface ArBillMapper
{
List<ArBill> selectArBillList(ArBill query);
List<ArBill> selectArBillList(ArBillQuery query);
ArBill selectArBillById(Long arBillId);
@@ -8,11 +8,12 @@ import com.ruoyi.ar.domain.ArBillLog;
import com.ruoyi.ar.domain.vo.ArBillGenerateRequest;
import com.ruoyi.ar.domain.vo.ArBillGenerateQuery;
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
import com.ruoyi.ar.domain.vo.ArBillQuery;
import com.ruoyi.price.domain.PrcOrderBill;
public interface IArBillService
{
List<ArBill> selectArBillList(ArBill query);
List<ArBill> selectArBillList(ArBillQuery query);
ArBill selectArBillById(Long arBillId);
@@ -50,6 +50,7 @@ import com.ruoyi.ar.service.IArBillService;
import com.ruoyi.ar.domain.vo.ArBillActionRequest;
import com.ruoyi.ar.domain.vo.ArBillGenerateQuery;
import com.ruoyi.ar.domain.vo.ArBillGenerateRequest;
import com.ruoyi.ar.domain.vo.ArBillQuery;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
@@ -83,7 +84,7 @@ public class ArBillServiceImpl implements IArBillService
@Autowired private ICustomerMoneyService customerMoneyService;
@Override
public List<ArBill> selectArBillList(ArBill query)
public List<ArBill> selectArBillList(ArBillQuery query)
{
List<ArBill> bills = billMapper.selectArBillList(query);
for (ArBill bill : bills)
@@ -41,7 +41,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from ar_bill
</sql>
<select id="selectArBillList" parameterType="ArBill" resultMap="ArBillResult">
<select id="selectArBillList" parameterType="com.ruoyi.ar.domain.vo.ArBillQuery" resultMap="ArBillResult">
<include refid="selectArBillColumns"/>
<where>
<if test="billNo != null and billNo != ''">and bill_no like concat('%', #{billNo}, '%')</if>
@@ -51,8 +51,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="customerId != null">and customer_id = #{customerId}</if>
<if test="status != null and status != ''">and status = #{status}</if>
<if test="currency != null and currency != ''">and currency = #{currency}</if>
<if test="billPeriodStart != null">and bill_period_end &gt;= #{billPeriodStart}</if>
<if test="billPeriodEnd != null">and bill_period_start &lt;= #{billPeriodEnd}</if>
<if test="billDateStart != null">and bill_date &gt;= #{billDateStart}</if>
<if test="billDateEnd != null">and bill_date &lt;= #{billDateEnd}</if>
<if test="billPeriodEnd != null">and bill_period_end = #{billPeriodEnd}</if>
</where>
order by create_time desc, ar_bill_id desc
</select>
@@ -0,0 +1,95 @@
-- 应收账单来源ID字段命名迁移
--
-- 将历史字段名迁移为与当前来源表一致的名称:
-- ar_bill_detail.prc_bill_id -> prc_order_bill_id
-- ar_bill_detail_fee.prc_fee_id -> prc_income_bill_fee_id
-- 脚本可重复执行,已完成迁移的字段和索引会自动跳过。
SET NAMES utf8mb4;
SET @db_name := DATABASE();
-- 1. 应收账单订单明细:来源计费订单ID。
SET @has_old_detail_column := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail'
AND COLUMN_NAME = 'prc_bill_id'
);
SET @has_new_detail_column := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail'
AND COLUMN_NAME = 'prc_order_bill_id'
);
SET @sql := IF(@has_old_detail_column > 0 AND @has_new_detail_column = 0,
'ALTER TABLE ar_bill_detail CHANGE COLUMN prc_bill_id prc_order_bill_id VARCHAR(36) NOT NULL COMMENT ''来源计费订单ID,对应 prc_order_bill.bill_id''',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 2. 应收账单费用明细:来源收入费用ID。
SET @has_old_fee_column := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail_fee'
AND COLUMN_NAME = 'prc_fee_id'
);
SET @has_new_fee_column := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail_fee'
AND COLUMN_NAME = 'prc_income_bill_fee_id'
);
SET @sql := IF(@has_old_fee_column > 0 AND @has_new_fee_column = 0,
'ALTER TABLE ar_bill_detail_fee CHANGE COLUMN prc_fee_id prc_income_bill_fee_id VARCHAR(36) NOT NULL COMMENT ''来源收入费用ID,对应 prc_income_bill_fee.fee_id''',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 3. 费用明细索引同步改名,避免数据库中继续保留旧字段命名。
SET @has_old_fee_unique := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail_fee'
AND INDEX_NAME = 'uk_ar_detail_prc_fee'
);
SET @has_new_fee_unique := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail_fee'
AND INDEX_NAME = 'uk_ar_detail_prc_income_fee'
);
SET @sql := IF(@has_old_fee_unique > 0 AND @has_new_fee_unique = 0,
'ALTER TABLE ar_bill_detail_fee RENAME INDEX uk_ar_detail_prc_fee TO uk_ar_detail_prc_income_fee',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @has_old_fee_index := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail_fee'
AND INDEX_NAME = 'idx_ar_detail_prc_fee'
);
SET @has_new_fee_index := (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = @db_name
AND TABLE_NAME = 'ar_bill_detail_fee'
AND INDEX_NAME = 'idx_ar_detail_prc_income_fee'
);
SET @sql := IF(@has_old_fee_index > 0 AND @has_new_fee_index = 0,
'ALTER TABLE ar_bill_detail_fee RENAME INDEX idx_ar_detail_prc_fee TO idx_ar_detail_prc_income_fee',
'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
+62 -5
View File
@@ -1,9 +1,12 @@
<template>
<div class="app-container ar-bill-page">
<basic-table ref="tableRef" :columns="columns" :search-columns="searchColumns" :query="queryList" row-key="arBillId" :selection="false">
<basic-table ref="tableRef" :columns="columns" :search-columns="searchColumns" :search-label-width="82" :query="queryList" row-key="arBillId" :selection="false">
<template #toolbar>
<el-col :span="1.5"><el-button type="primary" plain icon="Plus" v-hasPermi="['ar:bill:generate']" @click="generateOpen = true">生成账单</el-button></el-col>
</template>
<template #search-customerId="{ model }">
<customer-select v-model="model.customerId" placeholder="请选择客户" :page-size="1000" />
</template>
<template #col-status="{ row }"><el-tag :type="statusType(row.status)">{{ statusText(row.status) }}</el-tag></template>
<template #col-totalAmount="{ row }"><span class="amount">{{ money(row.totalAmount) }}</span></template>
<template #col-writtenOffAmount="{ row }"><span class="written">{{ money(row.writtenOffAmount) }}</span></template>
@@ -36,18 +39,18 @@ const detail = ref(null)
const generateOpen = ref(false)
const statusOptions = AR_BILL_STATUS_OPTIONS
const columns = [
{ label: '账单号', prop: 'billNo', minWidth: 180, showOverflowTooltip: true }, { label: '账单名称', prop: 'billName', minWidth: 180, showOverflowTooltip: true }, { label: '客户', prop: 'customerName', minWidth: 130, showOverflowTooltip: true },
{ label: '账单号', prop: 'billNo', minWidth: 180, showOverflowTooltip: true }, { label: '账单名称', prop: 'billName', minWidth: 180, showOverflowTooltip: true }, { label: '客户名称', prop: 'customerName', minWidth: 130, showOverflowTooltip: true }, { label: '客户编码', prop: 'customerCode', minWidth: 120, showOverflowTooltip: true },
{ label: '账单截止日', prop: 'billPeriodEnd', minWidth: 120, formatter: row => `截至 ${date(row.billPeriodEnd)}` }, { label: '订单数', prop: 'orderCount', width: 80 },
{ label: '应收总额', prop: 'totalAmount', width: 120, align: 'right', slot: 'totalAmount' }, { label: '已核销', prop: 'writtenOffAmount', width: 110, align: 'right', slot: 'writtenOffAmount' },
{ label: '未核销', prop: 'unwrittenAmount', width: 110, align: 'right', slot: 'unwrittenAmount' }, { label: '状态', prop: 'status', width: 110, slot: 'status' },
{ label: '账单日期', prop: 'billDate', width: 120, formatter: row => date(row.billDate) }, { label: '操作', prop: 'action', width: 260, fixed: 'right', slot: 'action', showInToolbar: false }
]
const searchColumns = [
{ label: '账单号', prop: 'billNo', component: 'input', placeholder: '账单号', width: '180px' }, { label: '账单名称', prop: 'billName', component: 'input', placeholder: '账单名称', width: '180px' }, { label: '客户编码', prop: 'customerCode', component: 'input', placeholder: '客户编码', width: '140px' },
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '全部状态', width: '150px' }, { label: '账期', prop: 'periodRange', component: 'daterange', width: '300px' }
{ label: '账单号', prop: 'billNo', component: 'input', placeholder: '账单号', width: '180px' }, { label: '账单名称', prop: 'billName', component: 'input', placeholder: '账单名称', width: '180px' }, { label: '客户名称', prop: 'customerId', component: 'input', placeholder: '请选择客户', width: '220px' }, { label: '客户编码', prop: 'customerCode', component: 'input', placeholder: '客户编码', width: '140px' },
{ label: '状态', prop: 'status', component: 'select', options: statusOptions, placeholder: '全部状态', width: '150px' }, { label: '账单日期', prop: 'billDateRange', component: 'daterange', width: '300px' }, { label: '账单截止日', prop: 'billPeriodEnd', component: 'date', width: '160px' }
]
function queryList(params) { const query = { ...params }; if (query.periodRange?.length === 2) { query.billPeriodStart = query.periodRange[0]; query.billPeriodEnd = query.periodRange[1] }; delete query.periodRange; return listArBill(query) }
function queryList(params) { const query = { ...params }; if (query.billDateRange?.length === 2) { query.billDateStart = query.billDateRange[0]; query.billDateEnd = query.billDateRange[1] }; delete query.billDateRange; return listArBill(query) }
function handleGenerateSuccess(bill) { tableRef.value.refresh(); viewBill(bill) }
function viewBill(row) { if (!row?.arBillId) return; detailOpen.value = true; detailLoading.value = true; detail.value = null; getArBill(row.arBillId).then(res => { detail.value = res.data || null }).finally(() => { detailLoading.value = false }) }
function confirmBill(row) { proxy.$modal.confirm(`确认账单「${row.billNo}」?确认后将进入正式账单状态,请确认订单及费用金额无误后继续。`).then(() => confirmArBill(row.arBillId)).then(() => { proxy.$modal.msgSuccess('账单已确认'); tableRef.value.refresh(); viewBill(row) }).catch(() => {}) }
@@ -61,7 +64,61 @@ function statusType(value) { return AR_BILL_STATUS_TYPES[value] || 'info' }
</script>
<style scoped>
.ar-bill-page :deep(.basic-table > .basic-form) {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14px 20px;
align-items: center;
padding: 16px 18px 4px;
margin-bottom: 18px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 8px;
background: var(--el-bg-color);
}
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item) {
min-width: 0;
margin-right: 0;
margin-bottom: 0;
}
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:not(:last-child) .el-form-item__content) {
min-width: 0;
}
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:not(:last-child) .el-form-item__content > *) {
width: 100% !important;
}
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:last-child) {
grid-column: 1 / -1;
justify-content: flex-start;
}
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:last-child .el-form-item__content) {
justify-content: flex-start;
}
.ar-bill-page :deep(.basic-table > .basic-form > .el-form-item:last-child .el-button) {
min-width: 88px;
margin-left: 0;
margin-right: 12px;
}
.ar-bill-page .amount { color: #f56c6c; font-weight: 600; }
.ar-bill-page .written { color: #67c23a; }
.ar-bill-page .unwritten { color: #e6a23c; }
@media (max-width: 1200px) {
.ar-bill-page :deep(.basic-table > .basic-form) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.ar-bill-page :deep(.basic-table > .basic-form) {
grid-template-columns: minmax(0, 1fr);
padding: 14px 12px 4px;
}
}
</style>