订单财务状态,订单异议汇总完善

This commit is contained in:
朱昊天
2026-05-26 12:04:16 +08:00
parent 4408e80e1c
commit a44497f92c
23 changed files with 1543 additions and 86 deletions

View File

@@ -24,6 +24,7 @@ import com.gear.oa.domain.vo.GearReturnExchangeVo;
import com.gear.oa.domain.bo.GearReturnExchangeBo;
import com.gear.oa.service.IGearReturnExchangeService;
import com.gear.common.core.page.TableDataInfo;
import java.util.Map;
/**
* 退换货管理
@@ -47,6 +48,14 @@ public class GearReturnExchangeController extends BaseController {
return iGearReturnExchangeService.queryPageList(bo, pageQuery);
}
/**
* 汇总(总数/总金额/按状态/按类型)
*/
@GetMapping("/summary")
public R<Map<String, Object>> summary(GearReturnExchangeBo bo) {
return R.ok(iGearReturnExchangeService.summary(bo));
}
/**
* 导出退换货管理列表
*/

View File

@@ -46,6 +46,16 @@ public class GearJournal extends BaseEntity {
* 对方户名
*/
private String counterpart;
/**
* 客户ID用于按客户过滤收款记录
*/
@TableField("customer_id")
private Long customerId;
/**
* 销售员ID用于按销售员过滤收款记录
*/
@TableField("salesman_id")
private Long salesmanId;
/**
* 收入金额
*/

View File

@@ -36,6 +36,8 @@ public class GearStockIoOrder extends BaseEntity {
private String responsibleName;
private Date ioTime;
private Date planArrivalTime;
private Date actualArrivalTime;

View File

@@ -52,6 +52,16 @@ public class GearJournalBo extends BaseEntity {
*/
private String counterpart;
/**
* 客户ID用于按客户过滤收款记录
*/
private Long customerId;
/**
* 销售员ID用于按销售员过滤收款记录
*/
private Long salesmanId;
/**
* 收入金额
*/
@@ -72,5 +82,15 @@ public class GearJournalBo extends BaseEntity {
*/
private String remark;
/**
* 时间范围筛选(按 journalDate
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
}

View File

@@ -66,5 +66,16 @@ public class GearReturnExchangeBo extends BaseEntity {
//订单id
private Long orderId;
//时间范围筛选(按创建时间)
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/**
* 汇总维度status,type逗号分隔为空则不返回分组汇总
*/
private String summaryDims;
}

View File

@@ -35,6 +35,8 @@ public class GearStockIoOrderBo extends BaseEntity {
private String responsibleName;
private Date ioTime;
private Date planArrivalTime;
private Date actualArrivalTime;

View File

@@ -32,8 +32,12 @@ public class GearStockIoOrderWithDetailBo {
private String responsibleName;
private Date ioTime;
private Date planArrivalTime;
private Date actualArrivalTime;
private Date planFinishTime;
private Long warehouseId;
@@ -48,4 +52,3 @@ public class GearStockIoOrderWithDetailBo {
private List<GearStockIoOrderDetailBo> details;
}

View File

@@ -58,6 +58,16 @@ public class GearJournalVo {
@ExcelProperty(value = "对方户名")
private String counterpart;
/**
* 客户ID用于按客户过滤收款记录
*/
private Long customerId;
/**
* 销售员ID用于按销售员过滤收款记录
*/
private Long salesmanId;
/**
* 收入金额
*/

View File

@@ -36,6 +36,9 @@ public class GearStockIoOrderVo extends BaseEntity {
private String responsibleName;
@ExcelProperty(value = "出入库时间")
private Date ioTime;
private Date planArrivalTime;
private Date actualArrivalTime;

View File

@@ -7,6 +7,9 @@ import com.gear.oa.domain.vo.GearReturnExchangeVo;
import com.gear.common.core.mapper.BaseMapperPlus;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 退换货管理Mapper接口
*
@@ -16,4 +19,10 @@ import org.apache.ibatis.annotations.Param;
public interface GearReturnExchangeMapper extends BaseMapperPlus<GearReturnExchangeMapper, GearReturnExchange, GearReturnExchangeVo> {
Page<GearReturnExchangeVo> selectVoPagePlus(Page<Object> build,@Param("ew") QueryWrapper<GearReturnExchange> lqw);
Map<String, Object> selectSummary(@Param("ew") QueryWrapper<GearReturnExchange> lqw);
List<Map<String, Object>> selectStatusStats(@Param("ew") QueryWrapper<GearReturnExchange> lqw);
List<Map<String, Object>> selectTypeStats(@Param("ew") QueryWrapper<GearReturnExchange> lqw);
}

View File

@@ -8,6 +8,7 @@ import com.gear.common.core.domain.PageQuery;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 退换货管理Service接口
@@ -46,4 +47,9 @@ public interface IGearReturnExchangeService {
* 校验并批量删除退换货管理信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 汇总(总数/总金额/按状态/按类型)
*/
Map<String, Object> summary(GearReturnExchangeBo bo);
}

View File

@@ -67,9 +67,12 @@ public class GearJournalServiceImpl implements IGearJournalService {
lqw.eq(StringUtils.isNotBlank(bo.getSummary()), GearJournal::getSummary, bo.getSummary());
lqw.eq(StringUtils.isNotBlank(bo.getTransType()), GearJournal::getTransType, bo.getTransType());
lqw.eq(StringUtils.isNotBlank(bo.getCounterpart()), GearJournal::getCounterpart, bo.getCounterpart());
lqw.eq(bo.getCustomerId() != null, GearJournal::getCustomerId, bo.getCustomerId());
lqw.eq(bo.getSalesmanId() != null, GearJournal::getSalesmanId, bo.getSalesmanId());
lqw.eq(bo.getIncomeAmount() != null, GearJournal::getIncomeAmount, bo.getIncomeAmount());
lqw.eq(bo.getExpenseAmount() != null, GearJournal::getExpenseAmount, bo.getExpenseAmount());
lqw.eq(bo.getBalanceAmount() != null, GearJournal::getBalanceAmount, bo.getBalanceAmount());
lqw.between(bo.getStartTime() != null && bo.getEndTime() != null, GearJournal::getJournalDate, bo.getStartTime(), bo.getEndTime());
//根据这个创建时间进行一个倒叙
lqw.orderByDesc(GearJournal::getCreateTime);
return lqw;

View File

@@ -198,6 +198,13 @@ public class GearReceivableServiceImpl implements IGearReceivableService {
journal.setSummary("客户付款");
journal.setTransType("收入");
journal.setCounterpart(customerMapper.selectById(receivable.getCustomerId()).getName());
journal.setCustomerId(receivable.getCustomerId());
if (receivable.getOrderId() != null) {
GearOrder order = orderMapper.selectById(receivable.getOrderId());
if (order != null) {
journal.setSalesmanId(order.getSalesmanId());
}
}
journal.setIncomeAmount(changePaidAmount);
journal.setExpenseAmount(BigDecimal.ZERO);
gearJournalService.computeBalance(journal);

View File

@@ -75,6 +75,10 @@ public class GearReturnExchangeServiceImpl implements IGearReturnExchangeService
queryWrapper.eq(StringUtils.isNotBlank(bo.getReason()), "gre.reason", bo.getReason());
queryWrapper.eq(StringUtils.isNotBlank(bo.getStatus()), "gre.status", bo.getStatus());
queryWrapper.eq(bo.getAmount() != null, "gre.amount", bo.getAmount());
queryWrapper.and(bo.getSalesmanId() != null,
w -> w.eq("o.salesman_id", bo.getSalesmanId()).or().eq("gre.salesman_id", bo.getSalesmanId()));
queryWrapper.between(bo.getStartTime() != null && bo.getEndTime() != null,
"gre.create_time", bo.getStartTime(), bo.getEndTime());
//逻辑删除
queryWrapper.eq("gre.del_flag", 0);
@@ -101,9 +105,30 @@ public class GearReturnExchangeServiceImpl implements IGearReturnExchangeService
lqw.eq(StringUtils.isNotBlank(bo.getReason()), GearReturnExchange::getReason, bo.getReason());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), GearReturnExchange::getStatus, bo.getStatus());
lqw.eq(bo.getAmount() != null, GearReturnExchange::getAmount, bo.getAmount());
lqw.eq(bo.getSalesmanId() != null, GearReturnExchange::getSalesmanId, bo.getSalesmanId());
lqw.between(bo.getStartTime() != null && bo.getEndTime() != null, GearReturnExchange::getCreateTime, bo.getStartTime(), bo.getEndTime());
return lqw;
}
@Override
public Map<String, Object> summary(GearReturnExchangeBo bo) {
QueryWrapper<GearReturnExchange> lqw = buildQueryWrapperPlus(bo);
Map<String, Object> summary = baseMapper.selectSummary(lqw);
String dims = bo.getSummaryDims() == null ? "" : bo.getSummaryDims().trim();
boolean needStatus = dims.contains("status");
boolean needType = dims.contains("type");
List<Map<String, Object>> statusStats = needStatus ? baseMapper.selectStatusStats(lqw) : java.util.Collections.emptyList();
List<Map<String, Object>> typeStats = needType ? baseMapper.selectTypeStats(lqw) : java.util.Collections.emptyList();
if (summary == null) {
summary = new java.util.HashMap<>();
summary.put("totalCount", 0);
summary.put("totalAmount", java.math.BigDecimal.ZERO);
}
summary.put("statusStats", statusStats);
summary.put("typeStats", typeStats);
return summary;
}
/**
* 新增退换货管理
*/

View File

@@ -143,6 +143,9 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService {
}
GearStockIoOrder order = BeanUtil.toBean(bo, GearStockIoOrder.class);
Date ioTime = bo.getIoTime() != null ? bo.getIoTime() : new Date();
ensureIoTimeNotAfterToday(ioTime);
order.setIoTime(ioTime);
if (StringUtils.isBlank(order.getOrderCode())) {
order.setOrderCode("SIOO_" + IdUtil.getSnowflakeNextIdStr());
}
@@ -197,6 +200,9 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService {
throw new ServiceException("单据明细不能为空");
}
if (bo.getIoTime() != null) {
ensureIoTimeNotAfterToday(bo.getIoTime());
}
GearStockIoOrder update = BeanUtil.toBean(bo, GearStockIoOrder.class);
update.setStatus(null);
update.setExecFlag(null);
@@ -268,6 +274,22 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService {
}
}
private void ensureIoTimeNotAfterToday(Date ioTime) {
if (ioTime == null) {
return;
}
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 999);
Date endOfToday = cal.getTime();
if (ioTime.after(endOfToday)) {
throw new ServiceException("出入库时间不能晚于当天");
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
@@ -326,7 +348,7 @@ public class GearStockIoOrderServiceImpl implements IGearStockIoOrderService {
update.setConfirmInFlag("1");
update.setConfirmInBy(LoginHelper.getNickName());
update.setConfirmInTime(new Date());
update.setActualArrivalTime(new Date());
update.setActualArrivalTime(order.getActualArrivalTime() != null ? order.getActualArrivalTime() : new Date());
update.setExecFlag("1");
baseMapper.updateById(update);
}

View File

@@ -57,6 +57,40 @@
${ew.customSqlSegment}
</select>
<select id="selectSummary" resultType="java.util.Map">
SELECT
COUNT(1) AS totalCount,
IFNULL(SUM(IFNULL(gre.amount, 0)), 0) AS totalAmount
FROM gear_return_exchange gre
LEFT JOIN gear_order_detail god ON gre.order_detail_id = god.detail_id
LEFT JOIN gear_order o ON god.order_id = o.order_id AND o.del_flag = 0
${ew.customSqlSegment}
</select>
<select id="selectStatusStats" resultType="java.util.Map">
SELECT
gre.status AS status,
COUNT(1) AS cnt,
IFNULL(SUM(IFNULL(gre.amount, 0)), 0) AS amount
FROM gear_return_exchange gre
LEFT JOIN gear_order_detail god ON gre.order_detail_id = god.detail_id
LEFT JOIN gear_order o ON god.order_id = o.order_id AND o.del_flag = 0
${ew.customSqlSegment}
GROUP BY gre.status
</select>
<select id="selectTypeStats" resultType="java.util.Map">
SELECT
gre.type AS type,
COUNT(1) AS cnt,
IFNULL(SUM(IFNULL(gre.amount, 0)), 0) AS amount
FROM gear_return_exchange gre
LEFT JOIN gear_order_detail god ON gre.order_detail_id = god.detail_id
LEFT JOIN gear_order o ON god.order_id = o.order_id AND o.del_flag = 0
${ew.customSqlSegment}
GROUP BY gre.type
</select>
</mapper>

View File

@@ -42,3 +42,12 @@ export function delReturnExchange(returnExchangeId) {
method: 'delete'
})
}
// 订单异议汇总
export function getReturnExchangeSummary(query) {
return request({
url: '/oa/returnExchange/summary',
method: 'get',
params: query
})
}

View File

@@ -70,6 +70,32 @@ export const constantRoutes = [
}
]
},
{
path: '/finance',
component: Layout,
hidden: true,
children: [
{
path: 'summary',
component: () => import('@/views/finance/summary/index.vue'),
name: 'FinanceSummary',
meta: { title: '财务汇总', icon: 'money', noCache: true }
}
]
},
{
path: '/oms',
component: Layout,
hidden: true,
children: [
{
path: 'returnExchangeSummary',
component: () => import('@/views/oms/returnExchange/summary/index.vue'),
name: 'ReturnExchangeSummary',
meta: { title: '订单异议汇总', icon: 'list', noCache: true }
}
]
},
{ path: '/user', component: Layout, hidden: true, redirect: 'noredirect', children: [ { path: 'profile/:activeTab?', component: () => import('@/views/system/user/profile/index'), name: 'Profile', meta: { title: '个人中心', icon: 'user' } } ] }, { path: '/mat/product', component: Layout, hidden: true, children: [ { path: 'detail/:id(\\d+)', component: () => import('@/views/mat/product/detail'), name: 'ProductDetail', meta: { title: '产品详情', activeMenu: '/mat/product' } } ] }
]

View File

@@ -0,0 +1,611 @@
<template>
<div class="finance-summary-page">
<el-card shadow="never">
<div class="toolbar">
<div class="title">财务汇总</div>
<div class="actions">
<el-button plain icon="Refresh" :loading="loading" @click="loadAll">刷新</el-button>
<el-select v-model="exportGroupBy" clearable placeholder="导出分组" size="small" style="width: 160px; margin-left: 8px;">
<el-option v-for="it in exportGroupOptions" :key="it.value" :label="it.label" :value="it.value" />
</el-select>
<el-button plain icon="Download" :loading="loading" @click="exportData" style="margin-left: 8px;">导出</el-button>
</div>
</div>
<el-form :model="filterForm" ref="filterFormRef" size="small" :inline="true" label-width="80px" class="filter-form">
<el-form-item label="时间" prop="timeRange">
<el-date-picker
v-model="filterForm.timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<template v-if="activeTab === 'receivable'">
<el-form-item label="客户" prop="receivableCustomerId">
<CustomerSelect :value="filterForm.receivableCustomerId" @input="(v) => (filterForm.receivableCustomerId = v)" />
</el-form-item>
<el-form-item label="状态" prop="receivableStatus">
<el-input v-model="filterForm.receivableStatus" placeholder="请输入状态" clearable style="width: 160px" />
</el-form-item>
</template>
<template v-else-if="activeTab === 'payable'">
<el-form-item label="供应商" prop="payableSupplierId">
<VendorSelect :value="filterForm.payableSupplierId" @input="(v) => (filterForm.payableSupplierId = v)" />
</el-form-item>
<el-form-item label="状态" prop="payableStatus">
<el-input v-model="filterForm.payableStatus" placeholder="请输入状态" clearable style="width: 160px" />
</el-form-item>
</template>
<template v-else-if="activeTab === 'journal'">
<el-form-item label="类型" prop="journalTransType">
<el-select v-model="filterForm.journalTransType" placeholder="请选择" clearable style="width: 140px">
<el-option label="收入" value="收入" />
<el-option label="支出" value="支出" />
</el-select>
</el-form-item>
<el-form-item label="对方户名" prop="journalCounterpart">
<el-input v-model="filterForm.journalCounterpart" placeholder="请输入对方户名" clearable style="width: 200px" />
</el-form-item>
</template>
<el-form-item>
<el-button type="primary" icon="Search" :loading="loading" @click="applyFilters">搜索</el-button>
<el-button icon="Refresh" :disabled="loading" @click="resetFilters">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="12" class="cards" v-loading="loading">
<el-col :span="8">
<div class="card">
<div class="card__label">应收总金额</div>
<div class="card__value">{{ formatMoney(receivableSummary.total) }}</div>
<div class="card__sub">已收 {{ formatMoney(receivableSummary.paid) }} · 未收 {{ formatMoney(receivableSummary.unpaid) }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="card">
<div class="card__label">应付总金额</div>
<div class="card__value">{{ formatMoney(payableSummary.total) }}</div>
<div class="card__sub">已付 {{ formatMoney(payableSummary.paid) }} · 未付 {{ formatMoney(payableSummary.unpaid) }}</div>
</div>
</el-col>
<el-col :span="8">
<div class="card">
<div class="card__label">资金日记账</div>
<div class="card__value">{{ formatMoney(journalSummary.balance) }}</div>
<div class="card__sub">收入 {{ formatMoney(journalSummary.income) }} · 支出 {{ formatMoney(journalSummary.expense) }}</div>
</div>
</el-col>
</el-row>
<el-tabs v-model="activeTab" type="border-card" class="mt-12">
<el-tab-pane label="应收款" name="receivable">
<el-table :data="receivableList" border stripe size="small" v-loading="loading" :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="客户" prop="customerName" min-width="180" show-overflow-tooltip />
<el-table-column label="订单ID" prop="orderId" width="160" />
<el-table-column label="到期日" prop="dueDate" width="180" />
<el-table-column label="应收" prop="amount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.amount) }}</template>
</el-table-column>
<el-table-column label="已收" prop="paidAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.paidAmount) }}</template>
</el-table-column>
<el-table-column label="未收" prop="balanceAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.balanceAmount) }}</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="120" />
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<el-tab-pane label="应付款" name="payable">
<el-table :data="payableList" border stripe size="small" v-loading="loading" :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="供应商" prop="supplierName" min-width="180" show-overflow-tooltip />
<el-table-column label="采购单" prop="detailCode" width="160" show-overflow-tooltip />
<el-table-column label="到期日" prop="dueDate" width="180" />
<el-table-column label="应付" prop="amount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.amount) }}</template>
</el-table-column>
<el-table-column label="已付" prop="paidAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.paidAmount) }}</template>
</el-table-column>
<el-table-column label="未付" prop="balanceAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.balanceAmount) }}</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="120" />
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
</el-table>
</el-tab-pane>
<el-tab-pane label="资金日记账" name="journal">
<el-table :data="journalList" border stripe size="small" v-loading="loading" :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="时间" prop="journalDate" width="180" />
<el-table-column label="摘要" prop="summary" min-width="160" show-overflow-tooltip />
<el-table-column label="类型" prop="transType" width="100" />
<el-table-column label="对方户名" prop="counterpart" min-width="160" show-overflow-tooltip />
<el-table-column label="收入" prop="incomeAmount" width="120" align="right">
<template #default="scope">{{ formatMoney(scope.row.incomeAmount) }}</template>
</el-table-column>
<el-table-column label="支出" prop="expenseAmount" width="120" align="right">
<template #default="scope">{{ formatMoney(scope.row.expenseAmount) }}</template>
</el-table-column>
<el-table-column label="余额" prop="balanceAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.balanceAmount) }}</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
</el-table>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script>
import { listReceivable } from "@/api/finance/receivable";
import { listPayable } from "@/api/finance/payable";
import { listJournal } from "@/api/finance/journal";
import CustomerSelect from "@/components/CustomerSelect";
import VendorSelect from "@/components/VendorSelect";
import * as XLSX from "xlsx";
export default {
name: "FinanceSummary",
components: { CustomerSelect, VendorSelect },
data() {
return {
loading: false,
activeTab: "receivable",
filterForm: {
timeRange: [],
receivableCustomerId: undefined,
receivableStatus: undefined,
payableSupplierId: undefined,
payableStatus: undefined,
journalTransType: undefined,
journalCounterpart: undefined
},
exportGroupBy: "",
receivableList: [],
payableList: [],
journalList: [],
receivableSummary: { total: 0, paid: 0, unpaid: 0 },
payableSummary: { total: 0, paid: 0, unpaid: 0 },
journalSummary: { income: 0, expense: 0, balance: 0 }
};
},
computed: {
exportGroupOptions() {
if (this.activeTab === "receivable") {
return [
{ label: "按客户", value: "customerName" },
{ label: "按状态", value: "status" },
{ label: "按订单", value: "orderId" },
{ label: "按到期日(天)", value: "dueDateDay" },
{ label: "按到期日(月)", value: "dueDateMonth" }
];
}
if (this.activeTab === "payable") {
return [
{ label: "按供应商", value: "supplierName" },
{ label: "按状态", value: "status" },
{ label: "按采购单", value: "detailCode" },
{ label: "按到期日(天)", value: "dueDateDay" },
{ label: "按到期日(月)", value: "dueDateMonth" }
];
}
return [
{ label: "按类型", value: "transType" },
{ label: "按对方户名", value: "counterpart" },
{ label: "按客户", value: "customerKey" },
{ label: "按销售员", value: "salesmanId" },
{ label: "按日期(天)", value: "journalDateDay" },
{ label: "按日期(月)", value: "journalDateMonth" }
];
}
},
created() {
this.loadAll();
},
methods: {
formatMoney(v) {
const n = Number(v);
return Number.isFinite(n) ? n.toFixed(2) : "0.00";
},
applyFilters() {
this.loadAll();
},
resetFilters() {
this.filterForm.timeRange = [];
this.filterForm.receivableCustomerId = undefined;
this.filterForm.receivableStatus = undefined;
this.filterForm.payableSupplierId = undefined;
this.filterForm.payableStatus = undefined;
this.filterForm.journalTransType = undefined;
this.filterForm.journalCounterpart = undefined;
this.exportGroupBy = "";
this.loadAll();
},
exportData() {
if (!this.exportGroupBy) {
this.exportCurrent();
} else {
this.exportGrouped();
}
},
exportCurrent() {
if (this.activeTab === "receivable") {
this.exportReceivable();
} else if (this.activeTab === "payable") {
this.exportPayable();
} else {
this.exportJournal();
}
},
buildExportParams() {
const range = this.filterForm.timeRange || [];
const startTime = range.length === 2 ? range[0] : undefined;
const endTime = range.length === 2 ? range[1] : undefined;
const receivableParams = {
startTime,
endTime,
customerId: this.filterForm.receivableCustomerId,
status: this.filterForm.receivableStatus
};
const payableParams = {
startTime,
endTime,
supplierId: this.filterForm.payableSupplierId,
status: this.filterForm.payableStatus
};
const journalParams = {
startTime,
endTime,
transType: this.filterForm.journalTransType,
counterpart: this.filterForm.journalCounterpart
};
return { receivableParams, payableParams, journalParams };
},
exportReceivable() {
const { receivableParams } = this.buildExportParams();
this.download("oa/receivable/export", receivableParams, `receivable_${new Date().getTime()}.xlsx`);
},
exportPayable() {
const { payableParams } = this.buildExportParams();
this.download("oa/payable/export", payableParams, `payable_${new Date().getTime()}.xlsx`);
},
exportJournal() {
const { journalParams } = this.buildExportParams();
this.download("oa/journal/export", journalParams, `journal_${new Date().getTime()}.xlsx`);
},
exportGrouped() {
if (this.activeTab === "receivable") {
this.exportReceivableGrouped();
} else if (this.activeTab === "payable") {
this.exportPayableGrouped();
} else {
this.exportJournalGrouped();
}
},
exportReceivableGrouped() {
const groupBy = String(this.exportGroupBy || "");
const { receivableParams } = this.buildExportParams();
this.loading = true;
listReceivable({ ...receivableParams, pageNum: 1, pageSize: 99999 })
.then((res) => {
const list = (res && res.rows) ? res.rows : [];
const allRows = list.map((r) => ({
客户: r && r.customerName != null ? r.customerName : "",
订单ID: r && r.orderId != null ? r.orderId : "",
到期日: r && r.dueDate != null ? r.dueDate : "",
应收: r && r.amount != null ? r.amount : 0,
已收: r && r.paidAmount != null ? r.paidAmount : 0,
未收: r && r.balanceAmount != null ? r.balanceAmount : "",
状态: r && r.status != null ? r.status : "",
备注: r && r.remark != null ? r.remark : ""
}));
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(allRows), "全部");
const groups = this.groupList(list, groupBy);
const used = new Set(["全部"]);
Object.keys(groups).forEach((k) => {
const rows = groups[k].map((r) => ({
客户: r && r.customerName != null ? r.customerName : "",
订单ID: r && r.orderId != null ? r.orderId : "",
到期日: r && r.dueDate != null ? r.dueDate : "",
应收: r && r.amount != null ? r.amount : 0,
已收: r && r.paidAmount != null ? r.paidAmount : 0,
未收: r && r.balanceAmount != null ? r.balanceAmount : "",
状态: r && r.status != null ? r.status : "",
备注: r && r.remark != null ? r.remark : ""
}));
const sheetName = this.uniqueSheetName(k, used);
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(rows), sheetName);
});
XLSX.writeFile(wb, `receivable_groupBy_${groupBy}_${new Date().getTime()}.xlsx`);
})
.finally(() => {
this.loading = false;
});
},
exportPayableGrouped() {
const groupBy = String(this.exportGroupBy || "");
const { payableParams } = this.buildExportParams();
this.loading = true;
listPayable({ ...payableParams, pageNum: 1, pageSize: 99999 })
.then((res) => {
const list = (res && res.rows) ? res.rows : [];
const allRows = list.map((r) => ({
供应商: r && r.supplierName != null ? r.supplierName : "",
采购单: r && r.detailCode != null ? r.detailCode : "",
到期日: r && r.dueDate != null ? r.dueDate : "",
应付: r && r.amount != null ? r.amount : 0,
已付: r && r.paidAmount != null ? r.paidAmount : 0,
未付: r && r.balanceAmount != null ? r.balanceAmount : "",
状态: r && r.status != null ? r.status : "",
备注: r && r.remark != null ? r.remark : ""
}));
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(allRows), "全部");
const groups = this.groupList(list, groupBy);
const used = new Set(["全部"]);
Object.keys(groups).forEach((k) => {
const rows = groups[k].map((r) => ({
供应商: r && r.supplierName != null ? r.supplierName : "",
采购单: r && r.detailCode != null ? r.detailCode : "",
到期日: r && r.dueDate != null ? r.dueDate : "",
应付: r && r.amount != null ? r.amount : 0,
已付: r && r.paidAmount != null ? r.paidAmount : 0,
未付: r && r.balanceAmount != null ? r.balanceAmount : "",
状态: r && r.status != null ? r.status : "",
备注: r && r.remark != null ? r.remark : ""
}));
const sheetName = this.uniqueSheetName(k, used);
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(rows), sheetName);
});
XLSX.writeFile(wb, `payable_groupBy_${groupBy}_${new Date().getTime()}.xlsx`);
})
.finally(() => {
this.loading = false;
});
},
exportJournalGrouped() {
const groupBy = String(this.exportGroupBy || "");
const { journalParams } = this.buildExportParams();
this.loading = true;
listJournal({ ...journalParams, pageNum: 1, pageSize: 99999 })
.then((res) => {
const list = (res && res.rows) ? res.rows : [];
const allRows = list.map((r) => ({
时间: r && r.journalDate != null ? r.journalDate : "",
摘要: r && r.summary != null ? r.summary : "",
类型: r && r.transType != null ? r.transType : "",
对方户名: r && r.counterpart != null ? r.counterpart : "",
收入: r && r.incomeAmount != null ? r.incomeAmount : 0,
支出: r && r.expenseAmount != null ? r.expenseAmount : 0,
余额: r && r.balanceAmount != null ? r.balanceAmount : "",
备注: r && r.remark != null ? r.remark : ""
}));
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(allRows), "全部");
const groups = this.groupList(list, groupBy);
const used = new Set(["全部"]);
Object.keys(groups).forEach((k) => {
const rows = groups[k].map((r) => ({
时间: r && r.journalDate != null ? r.journalDate : "",
摘要: r && r.summary != null ? r.summary : "",
类型: r && r.transType != null ? r.transType : "",
对方户名: r && r.counterpart != null ? r.counterpart : "",
收入: r && r.incomeAmount != null ? r.incomeAmount : 0,
支出: r && r.expenseAmount != null ? r.expenseAmount : 0,
余额: r && r.balanceAmount != null ? r.balanceAmount : "",
备注: r && r.remark != null ? r.remark : ""
}));
const sheetName = this.uniqueSheetName(k, used);
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(rows), sheetName);
});
XLSX.writeFile(wb, `journal_groupBy_${groupBy}_${new Date().getTime()}.xlsx`);
})
.finally(() => {
this.loading = false;
});
},
groupList(list, groupBy) {
const groups = {};
(list || []).forEach((r) => {
const key = this.groupKey(r, groupBy);
if (!groups[key]) groups[key] = [];
groups[key].push(r);
});
return groups;
},
groupKey(row, groupBy) {
const v = (k) => (row && row[k] != null ? row[k] : "");
const toText = (val) => {
if (val == null) return "";
if (val instanceof Date) {
const yyyy = val.getFullYear();
const mm = String(val.getMonth() + 1).padStart(2, "0");
const dd = String(val.getDate()).padStart(2, "0");
return `${yyyy}-${mm}-${dd}`;
}
return String(val);
};
const toDay = (val) => {
const s = toText(val);
return s.length >= 10 ? s.slice(0, 10) : s;
};
const toMonth = (val) => {
const s = toText(val);
return s.length >= 7 ? s.slice(0, 7) : s;
};
let key = "";
if (groupBy === "dueDateDay") key = toDay(v("dueDate"));
else if (groupBy === "dueDateMonth") key = toMonth(v("dueDate"));
else if (groupBy === "journalDateDay") key = toDay(v("journalDate"));
else if (groupBy === "journalDateMonth") key = toMonth(v("journalDate"));
else if (groupBy === "customerKey") key = (v("customerId") != null && String(v("customerId")) !== "" ? String(v("customerId")) : toText(v("counterpart")));
else key = toText(v(groupBy));
key = key != null ? String(key).trim() : "";
return key ? key : "未填写";
},
sanitizeSheetName(name) {
const n = String(name || "").replace(/[\[\]\*\/\\\?\:]/g, "_").replace(/\s+/g, " ").trim();
const cut = n.length > 31 ? n.slice(0, 31) : n;
return cut || "未命名";
},
uniqueSheetName(base, used) {
const set = used || new Set();
let name = this.sanitizeSheetName(base);
if (!set.has(name)) {
set.add(name);
return name;
}
for (let i = 2; i < 999; i++) {
const suffix = `_${i}`;
const max = 31 - suffix.length;
const next = this.sanitizeSheetName(name.slice(0, Math.max(1, max)) + suffix);
if (!set.has(next)) {
set.add(next);
return next;
}
}
const fallback = this.sanitizeSheetName(`${name}_${Date.now()}`);
set.add(fallback);
return fallback;
},
loadAll() {
this.loading = true;
const range = this.filterForm.timeRange || [];
const startTime = range.length === 2 ? range[0] : undefined;
const endTime = range.length === 2 ? range[1] : undefined;
const receivableParams = {
pageNum: 1,
pageSize: 9999,
startTime,
endTime,
customerId: this.filterForm.receivableCustomerId,
status: this.filterForm.receivableStatus
};
const payableParams = {
pageNum: 1,
pageSize: 9999,
startTime,
endTime,
supplierId: this.filterForm.payableSupplierId,
status: this.filterForm.payableStatus
};
const journalParams = {
pageNum: 1,
pageSize: 9999,
startTime,
endTime,
transType: this.filterForm.journalTransType,
counterpart: this.filterForm.journalCounterpart
};
Promise.all([
listReceivable(receivableParams),
listPayable(payableParams),
listJournal(journalParams)
])
.then(([r1, r2, r3]) => {
const receivables = (r1 && r1.rows) ? r1.rows : [];
const payables = (r2 && r2.rows) ? r2.rows : [];
const journals = (r3 && r3.rows) ? r3.rows : [];
this.receivableList = receivables;
this.payableList = payables;
this.journalList = journals;
const recTotal = receivables.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
const recPaid = receivables.reduce((sum, r) => sum + (Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0), 0);
const recUnpaid = receivables.reduce((sum, r) => {
const bal = r && r.balanceAmount != null ? Number(r.balanceAmount) : NaN;
if (Number.isFinite(bal)) return sum + bal;
const a = Number(r && r.amount != null ? r.amount : 0) || 0;
const p = Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0;
return sum + Math.max(0, a - p);
}, 0);
this.receivableSummary = { total: recTotal, paid: recPaid, unpaid: recUnpaid };
const payTotal = payables.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
const payPaid = payables.reduce((sum, r) => sum + (Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0), 0);
const payUnpaid = payables.reduce((sum, r) => {
const bal = r && r.balanceAmount != null ? Number(r.balanceAmount) : NaN;
if (Number.isFinite(bal)) return sum + bal;
const a = Number(r && r.amount != null ? r.amount : 0) || 0;
const p = Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0;
return sum + Math.max(0, a - p);
}, 0);
this.payableSummary = { total: payTotal, paid: payPaid, unpaid: payUnpaid };
const income = journals.reduce((sum, r) => sum + (Number(r && r.incomeAmount != null ? r.incomeAmount : 0) || 0), 0);
const expense = journals.reduce((sum, r) => sum + (Number(r && r.expenseAmount != null ? r.expenseAmount : 0) || 0), 0);
const balance = journals.length ? (Number(journals[0] && journals[0].balanceAmount != null ? journals[0].balanceAmount : 0) || 0) : 0;
this.journalSummary = { income, expense, balance };
})
.finally(() => {
this.loading = false;
});
}
}
};
</script>
<style scoped>
.finance-summary-page .toolbar {
display: flex;
align-items: center;
justify-content: space-between;
}
.filter-form {
margin-top: 12px;
}
.finance-summary-page .title {
font-size: 18px;
font-weight: 700;
}
.finance-summary-page .cards {
margin-top: 12px;
}
.finance-summary-page .card {
border: 1px solid #ebeef5;
border-radius: 10px;
padding: 14px 16px;
background: #fff;
}
.finance-summary-page .card__label {
color: #909399;
font-size: 12px;
}
.finance-summary-page .card__value {
margin-top: 6px;
font-size: 22px;
font-weight: 700;
color: #303133;
}
.finance-summary-page .card__sub {
margin-top: 6px;
font-size: 12px;
color: #606266;
}
.mt-12 {
margin-top: 12px;
}
</style>

View File

@@ -194,7 +194,16 @@
<div class="orders-section">
<div class="finance-title-row">
<div class="finance-title">财务状态</div>
<el-button size="small" plain icon="Refresh" :loading="financeLoading" @click="loadFinance">刷新</el-button>
<div>
<el-button size="small" plain type="primary" icon="Plus" :disabled="!selectedCustomerId" @click="openReceiveAdd">新增收款</el-button>
<el-button size="small" plain icon="Refresh" :loading="financeLoading" @click="loadFinance" style="margin-left: 8px;">刷新</el-button>
<el-select v-model="financeExportGroupBy" clearable placeholder="导出分组" size="small" style="width: 140px; margin-left: 8px;">
<el-option label="按日期(天)" value="journalDateDay" />
<el-option label="按日期(月)" value="journalDateMonth" />
<el-option label="按摘要" value="summary" />
</el-select>
<el-button size="small" plain icon="Download" :disabled="!financeList.length" @click="exportFinance" style="margin-left: 8px;">导出</el-button>
</div>
</div>
<div class="finance-strip" v-loading="financeLoading">
@@ -214,20 +223,40 @@
<div class="finance-subtitle">收款明细</div>
<el-table v-loading="financeLoading" :data="financeList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
<el-table-column label="到期日" prop="dueDate" width="180" />
<el-table-column label="应收" prop="amount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.amount) }}</template>
<el-table-column label="收款时间" prop="journalDate" width="180" />
<el-table-column label="已收金额" prop="incomeAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.incomeAmount) }}</template>
</el-table-column>
<el-table-column label="已收" prop="paidAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.paidAmount) }}</template>
</el-table-column>
<el-table-column label="未收" prop="balanceAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.balanceAmount) }}</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="120" />
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
<el-table-column label="摘要" prop="summary" width="160" show-overflow-tooltip />
<el-table-column label="备注" prop="remark" min-width="220" show-overflow-tooltip />
</el-table>
<el-dialog title="新增收款" v-model="financeReceiveAddOpen" width="520px" append-to-body>
<el-form :model="financeReceiveAddForm" label-width="80px">
<el-form-item label="客户">
<el-input :model-value="selectedCustomer && selectedCustomer.name ? selectedCustomer.name : '-'" disabled />
</el-form-item>
<el-form-item label="收款时间">
<el-date-picker
v-model="financeReceiveAddForm.journalDate"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择收款时间"
style="width: 100%;"
/>
</el-form-item>
<el-form-item label="收款金额">
<el-input-number v-model="financeReceiveAddForm.incomeAmount" :controls="false" :min="0" :precision="2" style="width: 100%;" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="financeReceiveAddForm.remark" type="textarea" :rows="3" placeholder="备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="financeReceiveAddOpen = false">取消</el-button>
<el-button type="primary" :loading="financeReceiveAddLoading" @click="submitReceiveAdd">确定</el-button>
</template>
</el-dialog>
</div>
</div>
@@ -285,7 +314,9 @@ import { listOrder } from "@/api/oms/order";
import { listShippingOrder } from "@/api/oms/shippingOrder";
import { listOrderDetail } from "@/api/oms/orderDetail";
import { listReceivable } from "@/api/finance/receivable";
import request from "@/utils/request";
import ReturnExchange from "@/views/oms/order/panels/return.vue";
import * as XLSX from "xlsx";
export default {
name: "Customer",
@@ -341,12 +372,19 @@ export default {
// 财务状态(按客户维度汇总应收/已收/未收,并展示收款明细)
financeLoading: false,
financeList: [],
financeReceiveAddOpen: false,
financeReceiveAddLoading: false,
financeReceiveAddForm: {
journalDate: "",
incomeAmount: 0,
remark: ""
},
financeExportGroupBy: "",
financeSummary: {
receivableAmount: 0,
receivedAmount: 0,
unreceivedAmount: 0
},
financeOrderCodeMap: {},
// 订单异议按客户维度展示订单列表点“查看”进入订单异议管理ReturnExchange
disputeLoading: false,
disputeOrderList: [],
@@ -392,7 +430,10 @@ export default {
this.shippingList = [];
this.financeList = [];
this.financeSummary = { receivableAmount: 0, receivedAmount: 0, unreceivedAmount: 0 };
this.financeOrderCodeMap = {};
this.financeReceiveAddOpen = false;
this.financeReceiveAddLoading = false;
this.financeReceiveAddForm = { journalDate: "", incomeAmount: 0, remark: "" };
this.financeExportGroupBy = "";
this.disputeOrderList = [];
this.disputeOpen = false;
this.disputeOrderId = null;
@@ -501,42 +542,90 @@ export default {
return Number.isFinite(n) ? n.toFixed(2) : "0.00";
},
openReceiveAdd() {
if (!this.selectedCustomerId) return;
this.financeReceiveAddForm = {
journalDate: this.parseNow(),
incomeAmount: 0,
remark: ""
};
this.financeReceiveAddOpen = true;
},
parseNow() {
const d = new Date();
if (typeof this.parseTime === "function") {
return this.parseTime(d, "{y}-{m}-{d} {h}:{i}:{s}");
}
return "";
},
submitReceiveAdd() {
if (!this.selectedCustomerId) return;
const amount = Number(this.financeReceiveAddForm && this.financeReceiveAddForm.incomeAmount != null ? this.financeReceiveAddForm.incomeAmount : 0);
if (!Number.isFinite(amount) || amount <= 0) {
this.$modal.msgError("请输入收款金额");
return;
}
this.financeReceiveAddLoading = true;
const payload = {
journalDate: this.financeReceiveAddForm.journalDate || undefined,
summary: "客户收款",
transType: "收入",
customerId: this.selectedCustomerId,
counterpart: (this.selectedCustomer && this.selectedCustomer.name) ? String(this.selectedCustomer.name) : "",
incomeAmount: amount,
expenseAmount: 0,
remark: this.financeReceiveAddForm.remark != null ? String(this.financeReceiveAddForm.remark) : ""
};
request({
url: "/oa/journal",
method: "post",
data: payload
}).then(() => {
this.$modal.msgSuccess("已新增收款");
this.financeReceiveAddOpen = false;
this.loadFinance();
}).finally(() => {
this.financeReceiveAddLoading = false;
});
},
loadFinance() {
if (!this.selectedCustomerId) return;
this.financeLoading = true;
const customerName = (this.selectedCustomer && this.selectedCustomer.name) ? String(this.selectedCustomer.name) : "";
Promise.all([
listReceivable({ customerId: this.selectedCustomerId, pageNum: 1, pageSize: 9999 }),
listOrder({ customerId: this.selectedCustomerId, pageNum: 1, pageSize: 9999 })
request({
url: "/oa/journal/list",
method: "get",
params: { customerId: this.selectedCustomerId, transType: "收入", pageNum: 1, pageSize: 9999 }
}),
customerName
? request({
url: "/oa/journal/list",
method: "get",
params: { counterpart: customerName, transType: "收入", pageNum: 1, pageSize: 9999 }
})
: Promise.resolve({ rows: [] })
])
.then(([recRes, orderRes]) => {
.then(([recRes, j1, j2]) => {
const receivables = (recRes && recRes.rows) ? recRes.rows : [];
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
const receivableAmount = receivables.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
const codeMap = {};
orders.forEach(o => {
if (!o || o.orderId == null) return;
codeMap[o.orderId] = o.orderCode || String(o.orderId);
const jRows1 = (j1 && j1.rows) ? j1.rows : [];
const jRows2 = (j2 && j2.rows) ? j2.rows : [];
const journalMap = new Map();
[...jRows1, ...jRows2].forEach(r => {
if (!r || r.journalId == null) return;
journalMap.set(r.journalId, r);
});
this.financeOrderCodeMap = codeMap;
const journals = Array.from(journalMap.values());
this.financeList = journals;
const rows = receivables.map(r => {
const orderId = r && r.orderId != null ? r.orderId : null;
return {
...r,
orderCode: orderId != null ? (codeMap[orderId] || String(orderId)) : "-"
};
});
this.financeList = rows;
const receivableAmount = rows.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
const receivedAmount = rows.reduce((sum, r) => sum + (Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0), 0);
const unreceivedAmount = rows.reduce((sum, r) => {
const bal = r && r.balanceAmount != null ? Number(r.balanceAmount) : NaN;
if (Number.isFinite(bal)) return sum + bal;
const a = Number(r && r.amount != null ? r.amount : 0) || 0;
const p = Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0;
return sum + Math.max(0, a - p);
}, 0);
const receivedAmount = journals.reduce((sum, r) => sum + (Number(r && r.incomeAmount != null ? r.incomeAmount : 0) || 0), 0);
const unreceivedAmount = Math.max(0, receivableAmount - receivedAmount);
this.financeSummary = { receivableAmount, receivedAmount, unreceivedAmount };
})
@@ -545,6 +634,70 @@ export default {
});
},
exportFinance() {
const list = this.financeList || [];
const groupBy = this.financeExportGroupBy ? String(this.financeExportGroupBy) : "";
const rowMap = (r) => ({
收款时间: r && r.journalDate != null ? r.journalDate : "",
已收金额: r && r.incomeAmount != null ? r.incomeAmount : 0,
摘要: r && r.summary != null ? r.summary : "",
备注: r && r.remark != null ? r.remark : ""
});
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(list.map(rowMap)), "全部");
if (groupBy) {
const groups = new Map();
list.forEach((r) => {
const key = this.financeExportKey(r, groupBy);
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(r);
});
const used = new Set(["全部"]);
Array.from(groups.entries()).forEach(([k, items]) => {
const sheetName = this.financeUniqueSheetName(k, used);
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(items.map(rowMap)), sheetName);
});
}
const cid = this.selectedCustomerId != null ? String(this.selectedCustomerId) : "customer";
XLSX.writeFile(wb, `customer_finance_${cid}_${new Date().getTime()}.xlsx`);
},
financeExportKey(r, groupBy) {
const raw = r && r[groupBy] != null ? r[groupBy] : "";
const s = raw == null ? "" : String(raw);
const toDay = (val) => (val && String(val).length >= 10 ? String(val).slice(0, 10) : String(val || ""));
const toMonth = (val) => (val && String(val).length >= 7 ? String(val).slice(0, 7) : String(val || ""));
let key = "";
if (groupBy === "journalDateDay") key = toDay(r && r.journalDate);
else if (groupBy === "journalDateMonth") key = toMonth(r && r.journalDate);
else key = s.trim();
return key ? key : "未填写";
},
financeSanitizeSheetName(name) {
const n = String(name || "").replace(/[\[\]\*\/\\\?\:]/g, "_").replace(/\s+/g, " ").trim();
const cut = n.length > 31 ? n.slice(0, 31) : n;
return cut || "未命名";
},
financeUniqueSheetName(base, used) {
const set = used || new Set();
let name = this.financeSanitizeSheetName(base);
if (!set.has(name)) {
set.add(name);
return name;
}
for (let i = 2; i < 999; i++) {
const suffix = `_${i}`;
const max = 31 - suffix.length;
const next = this.financeSanitizeSheetName(name.slice(0, Math.max(1, max)) + suffix);
if (!set.has(next)) {
set.add(next);
return next;
}
}
const fallback = this.financeSanitizeSheetName(`${name}_${Date.now()}`);
set.add(fallback);
return fallback;
},
loadDisputeOrders() {
if (!this.selectedCustomerId) return;
this.disputeLoading = true;

View File

@@ -0,0 +1,321 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="客户" prop="customerId">
<CustomerSelect v-model="queryParams.customerId" />
</el-form-item>
<el-form-item label="销售员" prop="salesmanId">
<el-select v-model="queryParams.salesmanId" placeholder="请选择销售员" clearable style="width: 160px">
<el-option v-for="it in salesmanOptions" :key="it.salesmanId" :label="it.name" :value="it.salesmanId" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-input v-model="queryParams.status" placeholder="请输入状态" clearable style="width: 160px" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-input v-model="queryParams.type" placeholder="请输入类型" clearable style="width: 160px" />
</el-form-item>
<el-form-item label="原因" prop="reason">
<el-input v-model="queryParams.reason" placeholder="请输入原因" clearable style="width: 160px" />
</el-form-item>
<el-form-item label="汇总维度" prop="summaryDims">
<el-select v-model="summaryDims" multiple collapse-tags collapse-tags-tooltip placeholder="请选择" style="width: 200px">
<el-option label="按状态" value="status" />
<el-option label="按类型" value="type" />
</el-select>
</el-form-item>
<el-form-item label="时间" prop="timeRange">
<el-date-picker
v-model="timeRange"
type="datetimerange"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 360px"
/>
</el-form-item>
<el-form-item>
<el-button size="small" type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button size="small" icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-select v-model="exportGroupBy" clearable placeholder="导出分组" size="small" style="width: 140px">
<el-option label="按状态" value="status" />
<el-option label="按类型" value="type" />
<el-option label="按客户" value="customerName" />
<el-option label="按销售员" value="salesmanName" />
</el-select>
</el-col>
<el-col :span="1.5">
<el-button size="small" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="handleQuery" />
</el-row>
<el-row :gutter="12" style="margin-bottom: 12px;">
<el-col :span="6">
<el-card shadow="never">
<div>异议总数</div>
<div style="font-size: 20px; font-weight: 600;">{{ summary.totalCount }}</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card shadow="never">
<div>涉及金额合计</div>
<div style="font-size: 20px; font-weight: 600;">{{ summary.totalAmount }}</div>
</el-card>
</el-col>
</el-row>
<el-row v-if="showStatusSummary || showTypeSummary" :gutter="12" style="margin-bottom: 12px;">
<el-col :span="12">
<el-card v-if="showStatusSummary" shadow="never">
<template #header>按状态汇总</template>
<el-table :data="summary.statusStats" border style="width: 100%">
<el-table-column label="状态" prop="status" min-width="120" />
<el-table-column label="数量" prop="cnt" width="120" />
<el-table-column label="金额" prop="amount" min-width="140" />
</el-table>
</el-card>
</el-col>
<el-col :span="12">
<el-card v-if="showTypeSummary" shadow="never">
<template #header>按类型汇总</template>
<el-table :data="summary.typeStats" border style="width: 100%">
<el-table-column label="类型" prop="type" min-width="120" />
<el-table-column label="数量" prop="cnt" width="120" />
<el-table-column label="金额" prop="amount" min-width="140" />
</el-table>
</el-card>
</el-col>
</el-row>
<el-card shadow="never">
<template #header>明细</template>
<el-table v-loading="loading" :data="rows" border>
<el-table-column label="创建时间" prop="createTime" min-width="170" />
<el-table-column label="订单ID" prop="orderId" width="100" />
<el-table-column label="订单明细ID" prop="orderDetailId" width="110" />
<el-table-column label="产品" prop="productName" min-width="160" />
<el-table-column label="客户" prop="customerName" min-width="140" />
<el-table-column label="销售员" prop="salesmanName" min-width="120" />
<el-table-column label="类型" prop="type" width="120" />
<el-table-column label="原因" prop="reason" min-width="160" />
<el-table-column label="状态" prop="status" width="120" />
<el-table-column label="涉及金额" prop="amount" width="120" />
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="fetchDetail"
/>
</el-card>
</div>
</template>
<script setup name="ReturnExchangeSummary">
import { computed, getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue'
import CustomerSelect from '@/components/CustomerSelect/index.vue'
import { listSalesman } from '@/api/oms/salesman'
import { getReturnExchangeSummary, listReturnExchange } from '@/api/oa/returnExchange'
import * as XLSX from 'xlsx'
const { proxy } = getCurrentInstance()
const showSearch = ref(true)
const loading = ref(false)
const total = ref(0)
const rows = ref([])
const salesmanOptions = ref([])
const timeRange = ref([])
const summaryDims = ref([])
const exportGroupBy = ref('')
const summary = reactive({
totalCount: 0,
totalAmount: 0,
statusStats: [],
typeStats: []
})
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
customerId: undefined,
salesmanId: undefined,
status: undefined,
type: undefined,
reason: undefined,
startTime: undefined,
endTime: undefined
}
})
const { queryParams } = toRefs(data)
const showStatusSummary = computed(() => (summaryDims.value || []).includes('status'))
const showTypeSummary = computed(() => (summaryDims.value || []).includes('type'))
function syncTimeRange() {
if (!timeRange.value || timeRange.value.length !== 2) {
queryParams.value.startTime = undefined
queryParams.value.endTime = undefined
return
}
queryParams.value.startTime = timeRange.value[0]
queryParams.value.endTime = timeRange.value[1]
}
function fetchSummary() {
syncTimeRange()
const params = Object.assign({}, queryParams.value, { summaryDims: (summaryDims.value || []).join(',') })
getReturnExchangeSummary(params).then((res) => {
const d = (res && res.data) || {}
summary.totalCount = d.totalCount || 0
summary.totalAmount = d.totalAmount || 0
summary.statusStats = d.statusStats || []
summary.typeStats = d.typeStats || []
})
}
function fetchDetail() {
loading.value = true
syncTimeRange()
listReturnExchange(queryParams.value)
.then((res) => {
rows.value = res.rows || []
total.value = res.total || 0
})
.finally(() => {
loading.value = false
})
}
function handleQuery() {
queryParams.value.pageNum = 1
fetchSummary()
fetchDetail()
}
function handleExport() {
syncTimeRange()
const groupBy = exportGroupBy.value ? String(exportGroupBy.value) : ''
if (!groupBy) {
proxy.download(
'oa/returnExchange/export',
{
...queryParams.value
},
`returnExchange_${new Date().getTime()}.xlsx`
)
return
}
const params = { ...queryParams.value, pageNum: 1, pageSize: 99999 }
loading.value = true
listReturnExchange(params)
.then((res) => {
const list = (res && res.rows) ? res.rows : []
const baseRows = list.map((r) => ({
创建时间: r && r.createTime != null ? r.createTime : '',
订单ID: r && r.orderId != null ? r.orderId : '',
订单明细ID: r && r.orderDetailId != null ? r.orderDetailId : '',
产品: r && r.productName != null ? r.productName : '',
客户: r && r.customerName != null ? r.customerName : '',
销售员: r && r.salesmanName != null ? r.salesmanName : '',
类型: r && r.type != null ? r.type : '',
原因: r && r.reason != null ? r.reason : '',
状态: r && r.status != null ? r.status : '',
涉及金额: r && r.amount != null ? r.amount : 0
}))
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(baseRows), '全部')
const groups = new Map()
list.forEach((r) => {
const raw = r && r[groupBy] != null ? String(r[groupBy]) : ''
const key = raw && raw.trim() ? raw.trim() : '未填写'
if (!groups.has(key)) groups.set(key, [])
groups.get(key).push(r)
})
const sheetNameSet = new Set(['全部'])
const sanitizeSheetName = (name) => {
const n = String(name || '')
.replace(/[\[\]\*\/\\\?\:]/g, '_')
.replace(/\s+/g, ' ')
.trim()
const cut = n.length > 31 ? n.slice(0, 31) : n
return cut || '未命名'
}
const uniqueSheetName = (base) => {
let name = sanitizeSheetName(base)
if (!sheetNameSet.has(name)) {
sheetNameSet.add(name)
return name
}
for (let i = 2; i < 999; i++) {
const suffix = `_${i}`
const max = 31 - suffix.length
const next = sanitizeSheetName(name.slice(0, Math.max(1, max)) + suffix)
if (!sheetNameSet.has(next)) {
sheetNameSet.add(next)
return next
}
}
return sanitizeSheetName(`${base}_${Date.now()}`)
}
Array.from(groups.entries()).forEach(([k, items]) => {
const dataRows = items.map((r) => ({
创建时间: r && r.createTime != null ? r.createTime : '',
订单ID: r && r.orderId != null ? r.orderId : '',
订单明细ID: r && r.orderDetailId != null ? r.orderDetailId : '',
产品: r && r.productName != null ? r.productName : '',
客户: r && r.customerName != null ? r.customerName : '',
销售员: r && r.salesmanName != null ? r.salesmanName : '',
类型: r && r.type != null ? r.type : '',
原因: r && r.reason != null ? r.reason : '',
状态: r && r.status != null ? r.status : '',
涉及金额: r && r.amount != null ? r.amount : 0
}))
const sheetName = uniqueSheetName(k)
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(dataRows), sheetName)
})
const file = `returnExchange_groupBy_${groupBy}_${new Date().getTime()}.xlsx`
XLSX.writeFile(wb, file)
})
.finally(() => {
loading.value = false
})
}
function resetQuery() {
proxy.resetForm('queryRef')
timeRange.value = []
summaryDims.value = []
exportGroupBy.value = ''
handleQuery()
}
function loadSalesmanOptions() {
listSalesman({ pageNum: 1, pageSize: 9999 }).then((res) => {
salesmanOptions.value = res.rows || []
})
}
onMounted(() => {
loadSalesmanOptions()
handleQuery()
})
</script>

View File

@@ -200,7 +200,16 @@
<div class="orders-section">
<div class="finance-title-row">
<div class="finance-title">财务状态</div>
<el-button size="small" plain icon="Refresh" :loading="financeLoading" @click="loadFinance">刷新</el-button>
<div>
<el-button size="small" plain type="primary" icon="Plus" :disabled="!selectedSalesmanId" @click="openReceiveAdd">新增收款</el-button>
<el-button size="small" plain icon="Refresh" :loading="financeLoading" @click="loadFinance" style="margin-left: 8px;">刷新</el-button>
<el-select v-model="financeExportGroupBy" clearable placeholder="导出分组" size="small" style="width: 140px; margin-left: 8px;">
<el-option label="按日期(天)" value="journalDateDay" />
<el-option label="按日期(月)" value="journalDateMonth" />
<el-option label="按摘要" value="summary" />
</el-select>
<el-button size="small" plain icon="Download" :disabled="!financeList.length" @click="exportFinance" style="margin-left: 8px;">导出</el-button>
</div>
</div>
<div class="finance-strip" v-loading="financeLoading">
@@ -220,21 +229,40 @@
<div class="finance-subtitle">收款明细</div>
<el-table v-loading="financeLoading" :data="financeList" border stripe :header-cell-style="{ background: '#f5f7fa' }">
<el-table-column label="订单编号" prop="orderCode" min-width="160" />
<el-table-column label="客户" prop="customerName" min-width="180" show-overflow-tooltip />
<el-table-column label="到期日" prop="dueDate" width="180" />
<el-table-column label="应收" prop="amount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.amount) }}</template>
<el-table-column label="收款时间" prop="journalDate" width="180" />
<el-table-column label="已收金额" prop="incomeAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.incomeAmount) }}</template>
</el-table-column>
<el-table-column label="已收" prop="paidAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.paidAmount) }}</template>
</el-table-column>
<el-table-column label="未收" prop="balanceAmount" width="140" align="right">
<template #default="scope">{{ formatMoney(scope.row.balanceAmount) }}</template>
</el-table-column>
<el-table-column label="状态" prop="status" width="120" />
<el-table-column label="备注" prop="remark" min-width="180" show-overflow-tooltip />
<el-table-column label="摘要" prop="summary" width="160" show-overflow-tooltip />
<el-table-column label="备注" prop="remark" min-width="220" show-overflow-tooltip />
</el-table>
<el-dialog title="新增收款" v-model="financeReceiveAddOpen" width="520px" append-to-body>
<el-form :model="financeReceiveAddForm" label-width="80px">
<el-form-item label="销售员">
<el-input :model-value="selectedSalesmanName || '-'" disabled />
</el-form-item>
<el-form-item label="收款时间">
<el-date-picker
v-model="financeReceiveAddForm.journalDate"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择收款时间"
style="width: 100%;"
/>
</el-form-item>
<el-form-item label="收款金额">
<el-input-number v-model="financeReceiveAddForm.incomeAmount" :controls="false" :min="0" :precision="2" style="width: 100%;" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="financeReceiveAddForm.remark" type="textarea" :rows="3" placeholder="备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="financeReceiveAddOpen = false">取消</el-button>
<el-button type="primary" :loading="financeReceiveAddLoading" @click="submitReceiveAdd">确定</el-button>
</template>
</el-dialog>
</div>
</div>
@@ -378,7 +406,9 @@ import { listOrder } from "@/api/oms/order";
import { listShippingOrder } from "@/api/oms/shippingOrder";
import { listOrderDetail } from "@/api/oms/orderDetail";
import { listReceivable } from "@/api/finance/receivable";
import request from "@/utils/request";
import ReturnExchange from "@/views/oms/order/panels/return.vue";
import * as XLSX from "xlsx";
export default {
name: "Salesman",
@@ -430,12 +460,19 @@ export default {
financeLoading: false,
financeList: [],
financeReceiveAddOpen: false,
financeReceiveAddLoading: false,
financeReceiveAddForm: {
journalDate: "",
incomeAmount: 0,
remark: ""
},
financeExportGroupBy: "",
financeSummary: {
receivableAmount: 0,
receivedAmount: 0,
unreceivedAmount: 0
},
financeOrderCodeMap: {},
disputeLoading: false,
disputeOrderList: [],
@@ -702,42 +739,75 @@ export default {
return Number.isFinite(n) ? n.toFixed(2) : "0.00";
},
openReceiveAdd() {
if (!this.selectedSalesmanId) return;
this.financeReceiveAddForm = {
journalDate: this.parseNow(),
incomeAmount: 0,
remark: ""
};
this.financeReceiveAddOpen = true;
},
parseNow() {
const d = new Date();
if (typeof this.parseTime === "function") {
return this.parseTime(d, "{y}-{m}-{d} {h}:{i}:{s}");
}
return "";
},
submitReceiveAdd() {
if (!this.selectedSalesmanId) return;
const amount = Number(this.financeReceiveAddForm && this.financeReceiveAddForm.incomeAmount != null ? this.financeReceiveAddForm.incomeAmount : 0);
if (!Number.isFinite(amount) || amount <= 0) {
this.$modal.msgError("请输入收款金额");
return;
}
this.financeReceiveAddLoading = true;
const payload = {
journalDate: this.financeReceiveAddForm.journalDate || undefined,
summary: "客户收款",
transType: "收入",
salesmanId: this.selectedSalesmanId,
counterpart: this.selectedSalesmanName || "",
incomeAmount: amount,
expenseAmount: 0,
remark: this.financeReceiveAddForm.remark != null ? String(this.financeReceiveAddForm.remark) : ""
};
request({
url: "/oa/journal",
method: "post",
data: payload
}).then(() => {
this.$modal.msgSuccess("已新增收款");
this.financeReceiveAddOpen = false;
this.loadFinance();
}).finally(() => {
this.financeReceiveAddLoading = false;
});
},
loadFinance() {
if (!this.selectedSalesmanId) return;
this.financeLoading = true;
Promise.all([
listReceivable({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 }),
listOrder({ salesmanId: this.selectedSalesmanId, pageNum: 1, pageSize: 9999 })
request({
url: "/oa/journal/list",
method: "get",
params: { salesmanId: this.selectedSalesmanId, transType: "收入", pageNum: 1, pageSize: 9999 }
})
])
.then(([recRes, orderRes]) => {
.then(([recRes, journalRes]) => {
const receivables = (recRes && recRes.rows) ? recRes.rows : [];
const orders = (orderRes && orderRes.rows) ? orderRes.rows : [];
const receivableAmount = receivables.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
const codeMap = {};
orders.forEach(o => {
if (!o || o.orderId == null) return;
codeMap[o.orderId] = o.orderCode || String(o.orderId);
});
this.financeOrderCodeMap = codeMap;
const journals = (journalRes && journalRes.rows) ? journalRes.rows : [];
this.financeList = journals;
const rows = receivables.map(r => {
const orderId = r && r.orderId != null ? r.orderId : null;
return {
...r,
orderCode: orderId != null ? (codeMap[orderId] || String(orderId)) : "-"
};
});
this.financeList = rows;
const receivableAmount = rows.reduce((sum, r) => sum + (Number(r && r.amount != null ? r.amount : 0) || 0), 0);
const receivedAmount = rows.reduce((sum, r) => sum + (Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0), 0);
const unreceivedAmount = rows.reduce((sum, r) => {
const bal = r && r.balanceAmount != null ? Number(r.balanceAmount) : NaN;
if (Number.isFinite(bal)) return sum + bal;
const a = Number(r && r.amount != null ? r.amount : 0) || 0;
const p = Number(r && r.paidAmount != null ? r.paidAmount : 0) || 0;
return sum + Math.max(0, a - p);
}, 0);
const receivedAmount = journals.reduce((sum, r) => sum + (Number(r && r.incomeAmount != null ? r.incomeAmount : 0) || 0), 0);
const unreceivedAmount = Math.max(0, receivableAmount - receivedAmount);
this.financeSummary = { receivableAmount, receivedAmount, unreceivedAmount };
})
@@ -746,6 +816,70 @@ export default {
});
},
exportFinance() {
const list = this.financeList || [];
const groupBy = this.financeExportGroupBy ? String(this.financeExportGroupBy) : "";
const rowMap = (r) => ({
收款时间: r && r.journalDate != null ? r.journalDate : "",
已收金额: r && r.incomeAmount != null ? r.incomeAmount : 0,
摘要: r && r.summary != null ? r.summary : "",
备注: r && r.remark != null ? r.remark : ""
});
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(list.map(rowMap)), "全部");
if (groupBy) {
const groups = new Map();
list.forEach((r) => {
const key = this.financeExportKey(r, groupBy);
if (!groups.has(key)) groups.set(key, []);
groups.get(key).push(r);
});
const used = new Set(["全部"]);
Array.from(groups.entries()).forEach(([k, items]) => {
const sheetName = this.financeUniqueSheetName(k, used);
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(items.map(rowMap)), sheetName);
});
}
const sid = this.selectedSalesmanId != null ? String(this.selectedSalesmanId) : "salesman";
XLSX.writeFile(wb, `salesman_finance_${sid}_${new Date().getTime()}.xlsx`);
},
financeExportKey(r, groupBy) {
const raw = r && r[groupBy] != null ? r[groupBy] : "";
const s = raw == null ? "" : String(raw);
const toDay = (val) => (val && String(val).length >= 10 ? String(val).slice(0, 10) : String(val || ""));
const toMonth = (val) => (val && String(val).length >= 7 ? String(val).slice(0, 7) : String(val || ""));
let key = "";
if (groupBy === "journalDateDay") key = toDay(r && r.journalDate);
else if (groupBy === "journalDateMonth") key = toMonth(r && r.journalDate);
else key = s.trim();
return key ? key : "未填写";
},
financeSanitizeSheetName(name) {
const n = String(name || "").replace(/[\[\]\*\/\\\?\:]/g, "_").replace(/\s+/g, " ").trim();
const cut = n.length > 31 ? n.slice(0, 31) : n;
return cut || "未命名";
},
financeUniqueSheetName(base, used) {
const set = used || new Set();
let name = this.financeSanitizeSheetName(base);
if (!set.has(name)) {
set.add(name);
return name;
}
for (let i = 2; i < 999; i++) {
const suffix = `_${i}`;
const max = 31 - suffix.length;
const next = this.financeSanitizeSheetName(name.slice(0, Math.max(1, max)) + suffix);
if (!set.has(next)) {
set.add(next);
return next;
}
}
const fallback = this.financeSanitizeSheetName(`${name}_${Date.now()}`);
set.add(fallback);
return fallback;
},
loadPlanShipping() {
if (!this.selectedSalesmanId) return;
this.planDetailLoading = true;

View File

@@ -59,6 +59,7 @@
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="单据ID" align="center" prop="orderId" v-if="false" />
<el-table-column label="单据编号" align="center" prop="orderCode" min-width="160" />
<el-table-column label="出入库时间" align="center" prop="ioTime" min-width="160" />
<el-table-column label="创建时间" align="center" prop="createTime" min-width="160" />
<el-table-column label="类型" align="center" prop="ioType" width="90">
<template #default="scope">
@@ -125,6 +126,18 @@
<el-input v-model="editForm.responsibleName" placeholder="请输入责任人" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="出入库时间" prop="ioTime">
<el-date-picker
v-model="editForm.ioTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
:disabled-date="disabledAfterToday"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="主仓库" prop="warehouseId">
<WarehouseSelect v-model="editForm.warehouseId" placeholder="请选择仓库" />
@@ -145,6 +158,11 @@
<el-date-picker v-model="editForm.planArrivalTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="实际到货" prop="actualArrivalTime">
<el-date-picker v-model="editForm.actualArrivalTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="预计完成" prop="planFinishTime">
<el-date-picker v-model="editForm.planFinishTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width: 100%" />
@@ -257,6 +275,7 @@
<el-descriptions :title="'单号:' + (detailData.order.orderCode || '-')" :column="2" border>
<el-descriptions-item label="类型">{{ ioTypeLabel(detailData.order.ioType) }}</el-descriptions-item>
<el-descriptions-item label="业务类型">{{ detailData.order.bizType }}</el-descriptions-item>
<el-descriptions-item label="出入库时间">{{ detailData.order.ioTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ detailData.order.status === '1' ? '已完成' : '进行中' }}</el-descriptions-item>
<el-descriptions-item label="已执行">{{ detailData.order.execFlag === '1' ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="责任人">{{ detailData.order.responsibleName }}</el-descriptions-item>
@@ -454,7 +473,8 @@ export default {
detailSelection: [],
editRules: {
ioType: [{ required: true, message: '出入库类型不能为空', trigger: 'change' }],
bizType: [{ required: true, message: '业务类型不能为空', trigger: 'blur' }]
bizType: [{ required: true, message: '业务类型不能为空', trigger: 'blur' }],
ioTime: [{ required: true, message: '出入库时间不能为空', trigger: 'change' }]
},
detailOpen: false,
detailData: null,
@@ -575,7 +595,9 @@ export default {
sourceOrderId: undefined,
responsibleId: undefined,
responsibleName: undefined,
ioTime: this.formatDateTime(new Date()),
planArrivalTime: undefined,
actualArrivalTime: undefined,
planFinishTime: undefined,
warehouseId: undefined,
fromWarehouseId: undefined,
@@ -675,6 +697,11 @@ export default {
const ss = pad(d.getSeconds())
return `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`
},
disabledAfterToday(date) {
const end = new Date()
end.setHours(23, 59, 59, 999)
return date.getTime() > end.getTime()
},
onFlowMaterialChange(material) {
this.flowItemName = material && material.materialName ? material.materialName : ''
if (this.flowForm.itemId) {