订单财务状态,订单异议汇总完善
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出退换货管理列表
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
* 收入金额
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,8 @@ public class GearStockIoOrder extends BaseEntity {
|
||||
|
||||
private String responsibleName;
|
||||
|
||||
private Date ioTime;
|
||||
|
||||
private Date planArrivalTime;
|
||||
|
||||
private Date actualArrivalTime;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public class GearStockIoOrderBo extends BaseEntity {
|
||||
|
||||
private String responsibleName;
|
||||
|
||||
private Date ioTime;
|
||||
|
||||
private Date planArrivalTime;
|
||||
|
||||
private Date actualArrivalTime;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,16 @@ public class GearJournalVo {
|
||||
@ExcelProperty(value = "对方户名")
|
||||
private String counterpart;
|
||||
|
||||
/**
|
||||
* 客户ID(用于按客户过滤收款记录)
|
||||
*/
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 销售员ID(用于按销售员过滤收款记录)
|
||||
*/
|
||||
private Long salesmanId;
|
||||
|
||||
/**
|
||||
* 收入金额
|
||||
*/
|
||||
|
||||
@@ -36,6 +36,9 @@ public class GearStockIoOrderVo extends BaseEntity {
|
||||
|
||||
private String responsibleName;
|
||||
|
||||
@ExcelProperty(value = "出入库时间")
|
||||
private Date ioTime;
|
||||
|
||||
private Date planArrivalTime;
|
||||
|
||||
private Date actualArrivalTime;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增退换货管理
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -42,3 +42,12 @@ export function delReturnExchange(returnExchangeId) {
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 订单异议汇总
|
||||
export function getReturnExchangeSummary(query) {
|
||||
return request({
|
||||
url: '/oa/returnExchange/summary',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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' } } ] }
|
||||
]
|
||||
|
||||
|
||||
611
gear-ui3/src/views/finance/summary/index.vue
Normal file
611
gear-ui3/src/views/finance/summary/index.vue
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
321
gear-ui3/src/views/oms/returnExchange/summary/index.vue
Normal file
321
gear-ui3/src/views/oms/returnExchange/summary/index.vue
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user