@@ -49,7 +49,7 @@
< / template >
< / el-table >
<!-- === === === === === === === === === = 回款进度 === === === === === === === === === = -- >
<!-- === === === === === === === === === = 回款进度 ( 回款记录流水 ) === === === === === === === === === = -- >
< div class = "section-head progress-head" >
< span class = "section-title" > 回款进度 < / span >
< div class = "progress-summary" >
@@ -57,49 +57,29 @@
< span > 已回款 : < b class = "success" > ¥ { { formatMoney ( progressSummary . total _received ) } } < / b > < / span >
< span > 待回款 : < b class = "warning" > ¥ { { formatMoney ( ( progressSummary . total _planned || 0 ) - ( progressSummary . total _received || 0 ) ) } } < / b > < / span >
< / div >
< el-tooltip :disabled = "!paidOff" content = "回款已全部完成(进度 100%),不可再 新建回款计划" placement = "top" >
< el-tooltip : disabled = "list.length > 0" content = "回款进度以回款计划为先导条件,请先 新建回款计划" placement = "top" >
< span class = "btn-wrap" >
< el-button type = "primary" size = "small" :icon = "Plus" :disabled = "paidOff " @click ="openCreat e" >
新建回款计划
< el-button type = "primary" size = "small" :icon = "Plus" :disabled = "!list.length " @click ="openReceiv e" >
新建回款
< / el -button >
< / span >
< / el-tooltip >
< / div >
< el-table v-loading = "progressLoading" :data="progressList " border size="small" row-key="rowKey " >
< el -table -column label = "回款周期" width = "90" align = "center" >
< template # default = "{ row }" > { { paybackCycleText ( row . plan _type ) } } < / template >
< / el-table-column >
< el-table-column label = "明细" min -width = " 140 " show -overflow -tooltip >
< template # default = "{ row }" > { { row . name || ` 第 ${ row . seq } 期 ` } } < / template >
< / el-table-column >
< el-table-column label = "进度百分比" width = "100" align = "center" >
< template # default = "{ row }" > { { row . percent ? ` ${ row . percent } % ` : "-" } } < / template >
< / el-table-column >
< el-table-column label = "计划金额" width = "120" align = "right" >
< template # default = "{ row }" > { { formatMoney ( row . amount ) } } < / template >
< / el-table-column >
< el-table-column label = "计划日期" width = "110" align = "center" >
< template # default = "{ row }" > { { row . plan _date || "-" } } < / template >
< / el-table-column >
< el-table-column label = "已回款" width = "120" align = "right" >
< template # default = "{ row }" > { { formatMoney ( row . received _amount ) } } < / template >
< / el-table-column >
< el-table v-loading = "progressLoading" :data="records " border size="small" row-key="id " >
< el -table -column label = "回款日期" width = "110" align = "center" >
< template # default = "{ row }" > { { ( row . received _date && row . received _date !== "<nil>" ) ? row . received _date : "-" } } < / template >
< template # default = "{ row }" > { { row . received _date || "-" } } < / template >
< / el-table-column >
< el-table-column label = "逾期" width = "100" align = "center" >
< el-table-column label = "对应回款计划" min -width = " 180 " show -overflow -tooltip >
< template # default = "{ row }" > { { planText ( planOf ( row . payback _id ) ) } } < / template >
< / el-table-column >
< el-table-column label = "本次回款金额" width = "130" align = "right" >
< template # default = "{ row }" >
< el-tag v-if = "paybackItemOverdue(row).overdue" type="danger" size="small" effect="dark" >
{{ overdueText ( paybackItemOverdue ( row ) .days ) }}
< / el -tag >
< span v-else > - < / span >
< span class = "amount" > ¥ { { formatMoney ( row . amount ) } } < / span >
< / template >
< / el-table-column >
< el-table-column label = "状态 " width = "100" align = "center" >
< template # default = "{ row }" >
< el-tag :type = "paybackItemStatusTag(row.status)" size = "small" > { { paybackItemStatusText ( row . status ) } } < / el-tag >
< / template >
< el-table-column label = "回款方式 " width = "100" align = "center" >
< template # default = "{ row }" > { { paybackMethodText ( row . pay _method ) } } < / template >
< / el-table-column >
< el-table-column label = "流水回执" width = "110" align = "center" >
< template # default = "{ row }" >
@@ -107,16 +87,16 @@
< span v-else > - < / span >
< / template >
< / el -table -column >
< el-table-column label = "负责人" width = "90" >
< template # default = "{ row }" > { { row . owner _user _name || "-" } } < / template >
< el-table-column label = "备注" min -width = " 120 " show -overflow -tooltip >
< template # default = "{ row }" > { { row . remark || "-" } } < / template >
< / el-table-column >
< el-table-column label = "操作" width = "10 0" align = "center" fixed = "right" >
< el-table-column label = "操作" width = "8 0" align = "center" fixed = "right" >
< template # default = "{ row }" >
< el-button link type = "primary " size = "small" @click ="openReceive (row)" > 回款登记 < / el -button >
< el-button link type = "danger " size = "small" @click ="handleDeleteRecord (row)" > 删除 < / el -button >
< / template >
< / el-table-column >
< template # empty >
< el-empty description = "该合同 暂无回款进度,先新建回款计划 " :image-size = "60" / >
< el-empty description = "暂无回款记录,点击右上角「新建回款」登记一笔实收 " :image-size = "60" / >
< / template >
< / el-table >
@@ -131,21 +111,26 @@
<!-- 回款计划详情 -- >
< PaybackDetail v -model :visible = "detailVisible" :payback = "currentRow" @edit ="handleEdit" / >
<!-- 回款登记 -- >
< el-dialog v-model = "receiveVisible" title="回款登记 " width="44 0px" >
<!-- 新建回款 ( 回款进度登记 : 以回款计划为先导 ) -- >
< el-dialog v-model = "receiveVisible" title="新建 回款" width="46 0px" >
< el -form :model = "receiveForm" label -width = " 90px " >
< el-form-item label = "明细" >
< span > { { receiveForm . name || ` 第 ${ receiveForm . seq } 期 ` } } < / span >
< el-form-item label = "回款计划" required >
< el-select
v-model = "receiveForm.payback_id"
placeholder = "请选择回款计划"
style = "width: 100%"
@change ="onPlanChange"
>
< el-option v-for = "p in list" :key="p.id" :label="planText(p)" :value="p.id" / >
< / el-select >
< / el-form-item >
< el-form-item label = "计划金额" >
< span class = "amount" > ¥ { { formatMoney ( receiveForm . amount ) } } < / span >
< / el-form-item >
< el-form-item label = "实收金额" >
< el-form-item label = "本次回款" required >
< el-input-number
v-model = "receiveForm.received_ amount"
v-model = "receiveForm.amount"
:min = "0"
:precision = "2"
:controls = "false"
placeholder = "0.00"
style = "width: 100%"
/ >
< / el-form-item >
@@ -158,6 +143,11 @@
style = "width: 100%"
/ >
< / el-form-item >
< el-form-item label = "回款方式" >
< el-select v-model = "receiveForm.pay_method" style="width: 100%" >
< el -option v-for = "m in PAYBACK_METHOD_OPTIONS" :key="m.value" :label="m.label" :value="m.value" / >
< / el-select >
< / el-form-item >
< el-form-item label = "流水回执" >
< div class = "receipt-block" >
< el-upload
@@ -177,6 +167,9 @@
< / div >
< / div >
< / el-form-item >
< el-form-item label = "备注" >
< el-input v-model = "receiveForm.remark" type="textarea" :rows="2" maxlength="200" placeholder="选填" / >
< / el-form-item >
< / el-form >
< template # footer >
< el-button @click ="receiveVisible = false" > 取消 < / el -button >
@@ -188,21 +181,23 @@
< script setup >
import { computed , ref , reactive } from "vue" ;
import { ElMessage } from "element-plus" ;
import { ElMessage , ElMessageBox } from "element-plus" ;
import { Plus , Upload , Document , Delete } from "@element-plus/icons-vue" ;
import { getPaybackList , getPaybackProgress , registerPaybackReceive } from "@/api/crmPayback" ;
import {
getPaybackList ,
getPaybackRecordList ,
createPaybackRecord ,
deletePaybackRecord ,
} from "@/api/crmPayback" ;
import { uploadFile } from "@/api/file" ;
import PaybackCreate from "../../payback/components/create.vue" ;
import PaybackDetail from "../../payback/components/detail.vue" ;
import {
PAYBACK _METHOD _OPTIONS ,
paybackCycleText ,
paybackMethodText ,
paybackStatusText ,
paybackStatusTag ,
paybackItemStatusText ,
paybackItemStatusTag ,
paybackItemOverdue ,
overdueText ,
formatMoney ,
} from "../../dict" ;
@@ -220,11 +215,32 @@ const detailVisible = ref(false);
const currentRow = ref ( null ) ;
const editRow = ref ( null ) ;
/** 回款进度(摊平的分期明细 ) */
/** 回款进度:一笔笔实际回款记录(回款流水 ) */
const progressLoading = ref ( false ) ;
const progressList = ref ( [ ] ) ;
const records = ref ( [ ] ) ;
const progressSummary = ref ( { total _planned : 0 , total _received : 0 } ) ;
/** 今日日期(新建回款默认回款日期) */
function todayStr ( ) {
const d = new Date ( ) ;
const m = String ( d . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) ;
const day = String ( d . getDate ( ) ) . padStart ( 2 , "0" ) ;
return ` ${ d . getFullYear ( ) } - ${ m } - ${ day } ` ;
}
/** 按计划 id 取回款计划对象(回款记录上只存 payback_id) */
function planOf ( paybackId ) {
return list . value . find ( ( i ) => Number ( i . id ) === Number ( paybackId ) ) || null ;
}
/** 回款计划展示文案:周期 + 计划金额 + 已回款 */
function planText ( p ) {
if ( ! p ) return "-" ;
return ` ${ paybackCycleText ( p . plan _type ) } |计划 ¥ ${ formatMoney ( p . total _amount ) } |已回 ¥ ${ formatMoney (
p . received _amount
) } ` ;
}
/** 回款计划是否已全部收齐(总进度达到 100%),收齐后禁止再新建回款计划 */
const paidOff = computed ( ( ) => {
const planned = Number ( progressSummary . value . total _planned ) || 0 ;
@@ -271,7 +287,7 @@ async function reload() {
const contractId = props . contract ? . id ;
if ( ! contractId ) {
list . value = [ ] ;
progressList . value = [ ] ;
records . value = [ ] ;
progressSummary . value = { total _planned : 0 , total _received : 0 } ;
emit ( "count" , 0 ) ;
return ;
@@ -286,23 +302,23 @@ async function reload() {
loading . value = false ;
emit ( "count" , list . value . length ) ;
}
await fetchProgress ( ) ;
await fetchRecords ( ) ;
}
/** 拉取本合同回款进度 */
async function fetchProgress ( ) {
/** 拉取本合同回款记录(回款进度流水,一笔一条) */
async function fetchRecords ( ) {
const contractId = props . contract ? . id ;
if ( ! contractId ) return ;
progressLoading . value = true ;
try {
const res = await getPaybackProgress ( { contract _id : contractId } ) ;
progressList . value = ( res ? . data ? . list || [ ] ) . map ( ( r ) => ( { ... r , rowKey : ` ${ r . payback _id } _ ${ r . seq } ` } ) ) ;
const res = await getPaybackRecordList ( { contract _id : contractId } ) ;
records . value = res ? . data ? . list || [ ] ;
progressSummary . value = {
total _planned : res ? . data ? . total _planned || 0 ,
total _received : res ? . data ? . total _received || 0 ,
} ;
} catch ( e ) {
progressList . value = [ ] ;
records . value = [ ] ;
progressSummary . value = { total _planned : 0 , total _received : 0 } ;
ElMessage . error ( e . message || "查询回款进度失败" ) ;
} finally {
@@ -310,34 +326,44 @@ async function fetchProgress() {
}
}
/* ---------------------------- 回款 登记 ---------------------------- */
/* ---------------------------- 新建回款(回款进度 登记) ---------------------------- */
const receiveVisible = ref ( false ) ;
const receiveSaving = ref ( false ) ;
const receiveForm = reactive ( {
payback _id : null ,
seq : 0 ,
name : "" ,
amount : 0 ,
received _amount : 0 ,
received _date : "" ,
pay _method : 1 ,
receipt _url : "" ,
receipt _name : "" ,
remark : "" ,
} ) ;
function openReceive ( row ) {
/** 新建回款:以回款计划为先导条件(无计划时不允许登记) */
function openReceive ( ) {
if ( ! list . value . length ) {
ElMessage . warning ( "请先新建回款计划,回款进度以计划为先导条件" ) ;
return ;
}
const first = list . value [ 0 ] ;
Object . assign ( receiveForm , {
payback _id : row . payback _id ,
seq : row . seq ,
name : row . name ,
amount : row . amount ,
received _amount : Number ( row . received _amount ) || 0 ,
received _date : row . received _date && row . received _date !== "<nil>" ? row . received _date : "" ,
receipt _url : row . receipt _url || "" ,
receipt _name : row . receipt _name || "" ,
payback _id : first . id ,
amount : 0 ,
received _date : todayStr ( ) ,
pay _method : first . pay _method || 1 ,
receipt _url : "" ,
receipt _name : "" ,
remark : "" ,
} ) ;
receiveVisible . value = true ;
}
/** 切换回款计划:同步该计划的回款方式作为默认值 */
function onPlanChange ( pid ) {
const p = planOf ( pid ) ;
if ( p ) receiveForm . pay _method = p . pay _method || 1 ;
}
const uploading = ref ( false ) ;
async function handleReceiptUpload ( options ) {
const file = options . file ;
@@ -363,18 +389,28 @@ function clearReceipt() {
}
async function submitReceive ( ) {
if ( ! receiveForm . payback _id ) {
ElMessage . warning ( "请选择回款计划" ) ;
return ;
}
if ( ! ( Number ( receiveForm . amount ) > 0 ) ) {
ElMessage . warning ( "请填写本次回款金额" ) ;
return ;
}
receiveSaving . value = true ;
try {
await registerPaybackReceive ( receiveForm . payback _id , {
seq : receiveForm . seq ,
received _amount : Number ( receiveForm . received _amount ) || 0 ,
await createPaybackRecord ( {
payback _id : receiveForm . payback _id ,
amount : Number ( receiveForm . amount ) || 0 ,
received _date : receiveForm . received _date || "" ,
pay _method : receiveForm . pay _method ,
receipt _url : receiveForm . receipt _url ,
receipt _name : receiveForm . receipt _name ,
remark : receiveForm . remark ,
} ) ;
ElMessage . success ( "回款登记成功" ) ;
receiveVisible . value = false ;
// 回款计划(含各期已回款金额、总进度) 与回款进度明细 一并刷新,无需重开抽屉
// 计划的已回款金额 与回款进度流水 一并刷新,无需重开抽屉
await reload ( ) ;
emit ( "saved" ) ;
} catch ( e ) {
@@ -384,6 +420,23 @@ async function submitReceive() {
}
}
/** 删除一条回款记录(会同步回写计划的已回款金额) */
async function handleDeleteRecord ( row ) {
try {
await ElMessageBox . confirm (
` 确定删除 ${ row . received _date || "该" } 的这笔回款(¥ ${ formatMoney ( row . amount ) } )吗? ` ,
"删除确认" ,
{ type : "warning" }
) ;
await deletePaybackRecord ( row . id ) ;
ElMessage . success ( "删除成功" ) ;
await reload ( ) ;
emit ( "saved" ) ;
} catch ( e ) {
if ( e !== "cancel" ) ElMessage . error ( e . message || "删除失败" ) ;
}
}
defineExpose ( { reload } ) ;
< / script >