From 2f6cdbb3cdf72b4a04fdc95dfd80dce08a27917e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=AB=E5=9C=B0=E5=83=A7?= <357099073@qq.com> Date: Sun, 13 Sep 2026 14:09:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=9E=E6=AC=BE=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/components.d.ts | 1 + backend/src/api/crmPayback.js | 19 + .../crm/contract/components/ProductList.vue | 48 +- .../apps/crm/contract/components/create.vue | 7 + .../contract/components/detail_payback.vue | 211 +++--- .../apps/crm/contract/components/utils.js | 3 + .../apps/crm/payback/components/detail.vue | 349 +++++++--- backend/src/views/apps/crm/payback/index.vue | 608 ++++-------------- go/controllers/backend_crm_contract.go | 18 +- go/controllers/backend_crm_payback.go | 188 +++++- go/models/init.go | 33 + go/models/tenant_crm_payback_record.go | 34 + go/routers/backend/backend.go | 4 + 13 files changed, 855 insertions(+), 668 deletions(-) create mode 100644 go/models/tenant_crm_payback_record.go diff --git a/backend/components.d.ts b/backend/components.d.ts index 2f3d0d6..db5e9e8 100644 --- a/backend/components.d.ts +++ b/backend/components.d.ts @@ -15,6 +15,7 @@ declare module 'vue' { CommonHeader: typeof import('./src/components/CommonHeader.vue')['default'] ElAlert: typeof import('element-plus/es')['ElAlert'] ElAside: typeof import('element-plus/es')['ElAside'] + ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete'] ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElBacktop: typeof import('element-plus/es')['ElBacktop'] ElBadge: typeof import('element-plus/es')['ElBadge'] diff --git a/backend/src/api/crmPayback.js b/backend/src/api/crmPayback.js index 272497f..140d31f 100644 --- a/backend/src/api/crmPayback.js +++ b/backend/src/api/crmPayback.js @@ -47,3 +47,22 @@ export function getPaybackProgress(params) { export function registerPaybackReceive(id, data) { return request({ url: `/backend/crm/payback/${id}/receive`, method: "post", data }); } + +/* ============================ 回款记录(回款进度流水) ============================ + * 以「回款计划」为先导条件:每次「新建回款」追加一条记录,构成回款进度列表。 + * ============================================================================== */ + +/** 回款记录列表:按合同 / 计划查询一笔笔实际回款(含计划总额与已回款汇总) */ +export function getPaybackRecordList(params) { + return request({ url: "/backend/crm/payback/record/list", method: "get", params }); +} + +/** 新建回款:追加一条回款记录 */ +export function createPaybackRecord(data) { + return request({ url: "/backend/crm/payback/record", method: "post", data }); +} + +/** 删除回款记录 */ +export function deletePaybackRecord(id) { + return request({ url: `/backend/crm/payback/record/${id}`, method: "delete" }); +} diff --git a/backend/src/views/apps/crm/contract/components/ProductList.vue b/backend/src/views/apps/crm/contract/components/ProductList.vue index 3883933..7b46fc9 100644 --- a/backend/src/views/apps/crm/contract/components/ProductList.vue +++ b/backend/src/views/apps/crm/contract/components/ProductList.vue @@ -41,29 +41,25 @@ maxlength="80" /> - + { if (v) onProductFocus(row) }" - @change="(val) => onProductPick(row, val)" + @select="(item) => onProductPick(row, item.product_name)" + @blur="() => onProductNameCommit(row)" > - - {{ p.product_name }} - {{ p.spec || "—" }} · ¥{{ p.price }} - - + + {{ item.product_name }} + {{ item.spec || "—" }} · ¥{{ item.price }} + + @@ -128,6 +124,11 @@ + + + 税率(%) + + !kw || String(p.product_name || "").toLowerCase().includes(kw) + ); + // 附加 value 字段:兼容 autocomplete 默认以 value 作为选中值 + cb(list.slice(0, 50).map((p) => ({ ...p, value: p.product_name }))); +} + +/** 名称失焦:命中产品库则带出规格/单位/单价/成本;未命中则标记为新增,保存时由后端自动建档 */ +function onProductNameCommit(row) { + onProductPick(row, String(row.name || "").trim()); +} + /** 按分类加载产品选项(本地 filterable 即可做到可写文字 + 模糊搜索,allow-create 支持自定义新增) */ async function ensureOptions(row) { const cat = row.category; diff --git a/backend/src/views/apps/crm/contract/components/create.vue b/backend/src/views/apps/crm/contract/components/create.vue index 78d949d..af80a39 100644 --- a/backend/src/views/apps/crm/contract/components/create.vue +++ b/backend/src/views/apps/crm/contract/components/create.vue @@ -611,6 +611,13 @@ const saveContract = async ({ finish = false }) => { ElMessage.warning("请先补全合同信息必填项"); return false; } + // 未命名的产品行无法计价、也不会写入产品管理,完成保存前必须补全 + const unnamed = products.value.filter((r) => !String(r?.name || "").trim()).length; + if (unnamed) { + activeStep.value = 1; + ElMessage.warning(`产品清单有 ${unnamed} 行未填写产品名称,请补全后再保存`); + return false; + } } saving.value = true; diff --git a/backend/src/views/apps/crm/contract/components/detail_payback.vue b/backend/src/views/apps/crm/contract/components/detail_payback.vue index e85c1fa..8325dd2 100644 --- a/backend/src/views/apps/crm/contract/components/detail_payback.vue +++ b/backend/src/views/apps/crm/contract/components/detail_payback.vue @@ -49,7 +49,7 @@ - + 回款进度 @@ -57,49 +57,29 @@ 已回款:¥{{ formatMoney(progressSummary.total_received) }} 待回款:¥{{ formatMoney((progressSummary.total_planned || 0) - (progressSummary.total_received || 0)) }} - + - - 新建回款计划 + + 新建回款 - - - {{ paybackCycleText(row.plan_type) }} - - - {{ row.name || `第${row.seq}期` }} - - - {{ row.percent ? `${row.percent}%` : "-" }} - - - {{ formatMoney(row.amount) }} - - - {{ row.plan_date || "-" }} - - - {{ formatMoney(row.received_amount) }} - + - {{ (row.received_date && row.received_date !== "") ? row.received_date : "-" }} + {{ row.received_date || "-" }} - + + {{ planText(planOf(row.payback_id)) }} + + - - {{ overdueText(paybackItemOverdue(row).days) }} - - - + ¥{{ formatMoney(row.amount) }} - - - {{ paybackItemStatusText(row.status) }} - + + {{ paybackMethodText(row.pay_method) }} @@ -107,16 +87,16 @@ - - - {{ row.owner_user_name || "-" }} + + {{ row.remark || "-" }} - + - 回款登记 + 删除 - + @@ -131,21 +111,26 @@ - - + + - - {{ receiveForm.name || `第${receiveForm.seq}期` }} + + + + - - ¥{{ formatMoney(receiveForm.amount) }} - - + @@ -158,6 +143,11 @@ style="width: 100%" /> + + + + + + + + 取消 @@ -188,21 +181,23 @@ diff --git a/backend/src/views/apps/crm/contract/components/utils.js b/backend/src/views/apps/crm/contract/components/utils.js index 509bfd4..3fee762 100644 --- a/backend/src/views/apps/crm/contract/components/utils.js +++ b/backend/src/views/apps/crm/contract/components/utils.js @@ -6,6 +6,9 @@ * - 软件部分 = Σ 产品类别为「软件(许可)」的小计 * - 其他部分 = Σ 服务 / 开发 / 其他类小计 * - 合同总金额 = 硬件 + 软件 + 其他 + * + * 计税口径:清单中填写的单价即为「含税价」,小计 = 数量 × 单价,不再叠加税率; + * 税率字段仅作开票记录,不参与任何金额计算(前后端保持一致)。 * - 产品总成本 = Σ (数量 × 成本单价),软件开发行按其「开发总成本」计入 * - 合同总利润 = 合同总金额 - 产品总成本 * diff --git a/backend/src/views/apps/crm/payback/components/detail.vue b/backend/src/views/apps/crm/payback/components/detail.vue index 61e2ad2..09dfe56 100644 --- a/backend/src/views/apps/crm/payback/components/detail.vue +++ b/backend/src/views/apps/crm/payback/components/detail.vue @@ -35,10 +35,6 @@ ¥{{ formatMoney(detail.received_amount) }} - - {{ detail.receipt_name || "查看回执" }} - - - {{ detail.remind_days ? `${detail.remind_days} 天` : "-" }} @@ -46,74 +42,127 @@ {{ detail.remark || "-" }} + 回款明细 - - 共 {{ items.length }} 期,已完成 {{ doneCount }} 期 - + + + 共 {{ records.length }} 笔,累计 ¥{{ formatMoney(receivedSum) }} + + 新建回款记录 + - - - - - {{ paybackItemStatusText(row.status) }} - - - - - - {{ row.plan_date || "-" }} - - - - - - ¥{{ formatMoney(row.received_amount) }} - / - ¥{{ formatMoney(row.amount) }} - - - - - - + + {{ row.received_date || "-" }} - + - - {{ overdueText(paybackItemOverdue(row).days) }} - + ¥{{ formatMoney(row.amount) }} + + + + {{ paybackMethodText(row.pay_method) }} + + + + + 查看回执 + - - + + {{ row.remark || "-" }} + + + + 删除 + + + + + + + + + + + + + + + + + + + + + + + + 上传流水回执 + + + + + {{ recordForm.receipt_name || "流水回执" }} + + 移除 + + + + + + + + + 取消 + 保存 + +