迭代 ONE-OS 多原型:提车应收款收款/合同页增强,租赁明细标注同步,导航注册表更新;新增 Git 协作技能并忽略 .local 临时目录。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* docx-preview 高保真 Word 渲染(预览区)
|
||||
* 使用动态 import,避免阻塞原型入口模块加载。
|
||||
*/
|
||||
import { renderAsync } from 'docx-preview';
|
||||
|
||||
var DEFAULT_RENDER_OPTIONS = {
|
||||
className: 'ct-docx-preview',
|
||||
@@ -18,6 +18,11 @@ var DEFAULT_RENDER_OPTIONS = {
|
||||
export async function renderDocxPreview(container, arrayBuffer, options) {
|
||||
if (!container || !arrayBuffer) return;
|
||||
container.innerHTML = '';
|
||||
var mod = await import('docx-preview');
|
||||
var renderAsync = mod.renderAsync;
|
||||
if (typeof renderAsync !== 'function') {
|
||||
throw new Error('docx-preview 模块未正确加载');
|
||||
}
|
||||
await renderAsync(
|
||||
arrayBuffer,
|
||||
container,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Word (.doc / .docx) → HTML 导入
|
||||
*/
|
||||
import mammoth from 'mammoth';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
var MAMMOTH_STYLE_MAP = [
|
||||
"p[style-name='Title'] => h1.ct-word-doc-title:fresh",
|
||||
@@ -94,7 +95,6 @@ export function extractInlineStylesFromImportedHtml(html) {
|
||||
|
||||
export async function extractDocxThemeStyles(arrayBuffer) {
|
||||
try {
|
||||
var JSZip = (await import('jszip')).default;
|
||||
var zip = await JSZip.loadAsync(arrayBuffer);
|
||||
var stylesFile = zip.file('word/styles.xml');
|
||||
if (!stylesFile) return '';
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
| 预览地址 | `/prototypes/lease-business-detail` |
|
||||
| 目标用户 | 业务二部业管 / 运营人员 |
|
||||
| 核心任务 | 按 Excel 样表维护租赁车辆收入明细、查询汇总、批量导入导出 |
|
||||
| 页面结构 | 筛选区 + 7 项 KPI + 工具栏 + 47 列宽表(表尾 23 项汇总)+ 分页 |
|
||||
| 页面结构 | 筛选区 + 7 项 KPI + 工具栏 + 48 列宽表(表尾 23 项汇总)+ 分页 |
|
||||
| 设计基底 | `vm-page` + `ldb-page` + `lc-page`(见 `ledger-shared/DESIGN.md`) |
|
||||
|
||||
### 与「租赁业务台账」的分工
|
||||
|
||||
| 模块 | 定位 |
|
||||
|---|---|
|
||||
| **租赁业务明细**(本模块) | 对齐业管 Excel 运营台账宽表;手工列导入维护;6 个公式列自动计算;**不**做收款关联与账单状态 |
|
||||
| **租赁业务明细**(本模块) | 对齐业管 Excel 运营台账宽表;手工列导入维护;6 个公式列自动计算;收款状态按实收/应收展示 |
|
||||
| **租赁业务台账** | 账单维度维护、关联收款记录、收款状态(未收/部分/已收)、归档与角色数据范围 |
|
||||
|
||||
下游 **业务部台账 / 业务汇总分析** 中「租赁业绩」数据来源为本模块明细(见 `oneos-web-data-analysis/05-业务部台账`)。
|
||||
@@ -28,15 +28,14 @@
|
||||
### 不做范围(当前原型)
|
||||
|
||||
- 「租赁业务盈亏月度汇总」独立 Tab(Legacy 数据分析页有,本原型待规划)
|
||||
- 行内编辑、删除、服务端持久化
|
||||
- 服务端持久化、员工/主管数据权限隔离(主管预览:`?role=supervisor`)
|
||||
- 关联收款记录自动回写实收
|
||||
- 员工/主管数据权限隔离
|
||||
|
||||
---
|
||||
|
||||
## 2. 筛选区
|
||||
|
||||
默认展示 4 项(一行),「更多筛选」展开车牌多选。
|
||||
默认展示 5 项(一行),「更多筛选」展开车牌多选。
|
||||
|
||||
| 筛选项 | 说明 |
|
||||
|---|---|
|
||||
@@ -44,6 +43,7 @@
|
||||
| 业务部门 | 精确匹配 |
|
||||
| 业务员 | 精确匹配 |
|
||||
| 客户名称 | 模糊包含 |
|
||||
| 状态 | 已结清 / 部分收款 / 未收款(按实收与应收合计计算) |
|
||||
| 车牌号码 | 多选精确匹配(更多筛选) |
|
||||
|
||||
查询按钮触发筛选;KPI 与表尾汇总基于**全量筛选结果**(非当前页)。
|
||||
@@ -70,28 +70,36 @@
|
||||
|
||||
| 按钮 | 行为 |
|
||||
|---|---|
|
||||
| 批量导入 | 下载模板(仅手工录入列)→ 上传 Excel → 公式列自动计算 |
|
||||
| 批量导出 | 导出当前筛选结果(完整 47 列,含公式列结果) |
|
||||
| 批量导入 | 下载模板(36 列手工录入)→ 上传 Excel → 公式列自动计算 |
|
||||
| 批量导出 | 导出当前筛选结果(完整 48 列,含公式列结果) |
|
||||
|
||||
导入必填:**年份、月份、车牌号码**。
|
||||
### 行内操作
|
||||
|
||||
编辑、删除、变更日志(操作列);已锁定记录仅主管可改(`?role=supervisor`)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 明细列表(47 列)
|
||||
## 5. 明细列表(48 列)
|
||||
|
||||
表头顺序严格对齐 Excel「2.租赁车辆收入明细表」:
|
||||
|
||||
年份、月份、业务部门、业务员、付款日期、车牌号码、车型、客户名称、增值服务、享免政策、提车日期、合同生效日期、合同到期日期、起始日期、退车日期、平均天数、押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、开票日期、实际付款日期、付款方式、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏、GPS里程、其他减免金额明细内容、资产归属、签约公司、备注。
|
||||
表头顺序对齐 Excel「2.租赁车辆收入明细表」,并在月份列左侧增加「状态」列。
|
||||
|
||||
### 交互
|
||||
|
||||
- 横向滚动;左冻结:**年份 + 车牌号码**
|
||||
- 横向滚动;左冻结:**状态 + 月份 + 车牌号码**
|
||||
- 仅**日期类列**表头支持正序/倒序排序
|
||||
- 公式列(`calculated`)浅灰底色,表示系统计算、导入模板不含
|
||||
- 表尾**汇总行**:23 个金额列 SUBTOTAL,首列显示「汇总」
|
||||
- 公式列(`calculated`)浅灰底色,导入模板不含
|
||||
- 表尾**汇总行**:23 个金额列 SUBTOTAL,月份列显示「汇总」
|
||||
- 未收 > 0 单元格标红;盈亏正负着色
|
||||
- 底部分页:10 / 20 / 50 / 100
|
||||
|
||||
### 收款状态(状态列)
|
||||
|
||||
| 状态 | 判定规则 |
|
||||
|---|---|
|
||||
| 未收款 | 实收金额 = 0 或未录入 |
|
||||
| 部分收款 | 实收金额 > 0 且 < 应收合计 |
|
||||
| 已结清 | 实收金额 ≥ 应收合计 |
|
||||
|
||||
### 表尾汇总(23 项)
|
||||
|
||||
押金、合同标的租金、应收合计、应收租金、月度收入、月度租金、维保包干收入、保险上浮费、运维费(收入)、其他收入、里程减免金额、其他减免金额、实收金额、未收、车辆标准成本、车辆实际成本、保险费、氢费、运维费(成本)、居间费、其他、总成本、盈亏。
|
||||
@@ -104,21 +112,15 @@
|
||||
|
||||
| 字段 | 计算方式 |
|
||||
|---|---|
|
||||
| **应收合计** | 应收租金 + 维保包干收入 + 保险上浮费 + 运维费(收入) + 其他收入 + 里程减免 + 其他减免 |
|
||||
| **月度收入** | 月度租金 + 同上调整项 |
|
||||
| **应收合计** | 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免 + 其他减免 |
|
||||
| **月度收入** | 月度租金 + 维保包干收入 + 保险上浮费 + 运维费(收入)+ 其他收入 + 里程减免 + 其他减免 |
|
||||
| **未收** | 应收合计 − 实收金额 |
|
||||
| **车辆实际成本** | 车辆标准成本 × 平均天数 |
|
||||
| **总成本** | 车辆实际成本 + 保险费 + 氢费 + 运维费(成本) + 居间费 + 其他 |
|
||||
| **总成本** | 车辆实际成本 + 保险费 + 氢费 + 运维费(成本)+ 居间费 + 其他 |
|
||||
| **盈亏** | 月度收入 − 总成本 |
|
||||
|
||||
减免金额在 Excel 中以负数录入;导入时填负数即可。
|
||||
|
||||
### 导入模板
|
||||
|
||||
- **含**:41 列手工录入字段(不含上述 6 列)
|
||||
- **不含**:应收合计、月度收入、未收、车辆实际成本、总成本、盈亏
|
||||
- 导入后调用 `applyDetailCalculations` 统一重算
|
||||
|
||||
---
|
||||
|
||||
## 7. 样例数据
|
||||
@@ -137,21 +139,22 @@
|
||||
| 标注源 | `annotation-source.json` |
|
||||
| 列定义 | `components/tableColumns.ts` |
|
||||
| 筛选 / KPI 汇总 | `utils/detail.ts` |
|
||||
| 收款状态 | `utils/payment-status.ts` |
|
||||
| 公式计算 | `utils/calc-detail.ts` |
|
||||
| 导入导出 | `utils/batch-io.ts` |
|
||||
| 变更日志 | `utils/change-log.ts` |
|
||||
| 类型 | `types.ts` |
|
||||
| 组件 | `components/FilterPanel.tsx`、`DetailKpiRow.tsx`、`DetailTable.tsx`、`DetailImportModal.tsx` |
|
||||
| 组件 | `FilterPanel.tsx`、`DetailKpiRow.tsx`、`DetailTable.tsx`、`DetailImportModal.tsx`、`DetailEditModal.tsx`、`DetailChangeLogModal.tsx` |
|
||||
|
||||
---
|
||||
|
||||
## 9. 验收重点
|
||||
|
||||
1. 表头 47 列顺序与 Excel 样表一致;双「运维费」列位置正确
|
||||
1. 筛选区含「状态」;状态列展示未收款 / 部分收款 / 已结清
|
||||
2. 筛选后 KPI、表尾汇总与明细行合计一致
|
||||
3. 下载导入模板无 6 个公式列;导入后公式列自动计算且与 Excel 同口径
|
||||
4. 导出为完整 47 列
|
||||
5. 横向滚动与左冻结(年份、车牌)正常
|
||||
6. 未收 > 0 标红;盈亏正负着色
|
||||
3. 左冻结:状态 + 月份 + 车牌号码
|
||||
4. 氢费非零可点击明细;编辑/删除/变更日志可用
|
||||
5. 未收 > 0 标红;盈亏正负着色
|
||||
|
||||
---
|
||||
|
||||
@@ -162,4 +165,3 @@
|
||||
- P1:盈亏月度汇总 Tab
|
||||
- P1:与租赁业务台账 / 收款模块集成口径
|
||||
- P2:导入重复键(年+月+车牌)处理策略
|
||||
- P2:导入行级校验与实收 ≤ 应收校验
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 将 PRD、markdownMap 与标注节点说明同步到 annotation-source.json 的 directory.nodes
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const root = join(__dirname, '..');
|
||||
const jsonPath = join(root, 'annotation-source.json');
|
||||
const prdPath = join(root, '.spec/requirements-prd.md');
|
||||
|
||||
const doc = JSON.parse(readFileSync(jsonPath, 'utf8'));
|
||||
const prd = readFileSync(prdPath, 'utf8');
|
||||
const mm = doc.markdownMap;
|
||||
|
||||
doc.markdownMap['lbd-doc-overview'] = `## 1. 模块定位
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| 模块名称 | 台账数据 — 租赁业务明细 |
|
||||
| 目标用户 | 业务二部业管 / 运营人员 |
|
||||
| 核心任务 | 按 Excel 样表维护租赁车辆收入明细、查询汇总、批量导入导出 |
|
||||
| 页面结构 | 筛选区 + 7 项 KPI + 工具栏 + 48 列宽表(表尾 23 项汇总)+ 分页 |
|
||||
| 设计基底 | vm-page + ldb-page + lc-page |
|
||||
|
||||
对齐 Excel《2026年业务二部运营台账总表》「2.租赁车辆收入明细表」。
|
||||
|
||||
---`;
|
||||
|
||||
function markdownDoc(id, title) {
|
||||
return {
|
||||
type: 'markdown',
|
||||
id,
|
||||
title,
|
||||
markdown: mm[id.replace('lbd-doc-', '')] ?? mm[id] ?? doc.data.nodes.find((n) => n.id === id.replace('lbd-doc-', ''))?.annotationText ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
/** 目录 markdown 节点:优先 markdownMap 中带 lbd-doc- 前缀的键,否则用标注节点 id */
|
||||
function nodeDoc(nodeId, title, docId = `lbd-doc-${nodeId}`) {
|
||||
const markdown = mm[nodeId] ?? mm[docId] ?? '';
|
||||
return { type: 'markdown', id: docId, title, markdown };
|
||||
}
|
||||
|
||||
doc.directory.nodes = [
|
||||
{
|
||||
type: 'folder',
|
||||
id: 'lbd-doc-root',
|
||||
title: '租赁业务明细说明',
|
||||
defaultExpanded: true,
|
||||
children: [
|
||||
{
|
||||
type: 'markdown',
|
||||
id: 'lbd-doc-overview',
|
||||
title: '模块总览',
|
||||
markdown: doc.markdownMap['lbd-doc-overview'],
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
id: 'lbd-link-self',
|
||||
title: '租赁业务明细原型',
|
||||
href: '/prototypes/lease-business-detail',
|
||||
target: 'self',
|
||||
},
|
||||
{
|
||||
type: 'markdown',
|
||||
id: 'lbd-doc-prd',
|
||||
title: 'PRD 全文',
|
||||
markdown: prd,
|
||||
markdownPath: 'src/prototypes/lease-business-detail/.spec/requirements-prd.md',
|
||||
},
|
||||
{
|
||||
type: 'folder',
|
||||
id: 'lbd-doc-page-annotations',
|
||||
title: '页面标注',
|
||||
defaultExpanded: true,
|
||||
children: [
|
||||
nodeDoc('lbd-filter', '筛选条件'),
|
||||
nodeDoc('lbd-kpi', 'KPI 汇总'),
|
||||
nodeDoc('lbd-toolbar', '批量导入导出'),
|
||||
nodeDoc('lbd-table', '明细列表'),
|
||||
nodeDoc('lbd-changelog-modal', '变更日志'),
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'folder',
|
||||
id: 'lbd-doc-field-annotations',
|
||||
title: '字段标注',
|
||||
defaultExpanded: false,
|
||||
children: [
|
||||
nodeDoc('lbd-col-status', '状态'),
|
||||
nodeDoc('lbd-col-receivable-total', '应收合计'),
|
||||
nodeDoc('lbd-col-outstanding', '未收'),
|
||||
nodeDoc('lbd-col-vehicle-actual-cost', '车辆实际成本'),
|
||||
nodeDoc('lbd-col-total-cost', '总成本'),
|
||||
nodeDoc('lbd-col-profit-loss', '盈亏'),
|
||||
nodeDoc('lbd-col-hydrogen-fee', '氢费'),
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'folder',
|
||||
id: 'lbd-doc-specs',
|
||||
title: '规格说明',
|
||||
defaultExpanded: false,
|
||||
children: [
|
||||
{
|
||||
type: 'markdown',
|
||||
id: 'lbd-doc-formulas',
|
||||
title: '公式列',
|
||||
markdown: mm['lbd-doc-formulas'],
|
||||
},
|
||||
{
|
||||
type: 'markdown',
|
||||
id: 'lbd-doc-boundary',
|
||||
title: '模块边界',
|
||||
markdown: mm['lbd-doc-boundary'],
|
||||
},
|
||||
{
|
||||
type: 'markdown',
|
||||
id: 'lbd-doc-source',
|
||||
title: '源码入口',
|
||||
markdown: `## 8. 源码入口
|
||||
|
||||
| 类型 | 路径 |
|
||||
|---|---|
|
||||
| 主入口 | \`index.tsx\` |
|
||||
| 标注源 | \`annotation-source.json\` |
|
||||
| 列定义 | \`components/tableColumns.ts\` |
|
||||
| 筛选 / KPI 汇总 | \`utils/detail.ts\` |
|
||||
| 收款状态 | \`utils/payment-status.ts\` |
|
||||
| 公式计算 | \`utils/calc-detail.ts\` |
|
||||
| 导入导出 | \`utils/batch-io.ts\` |
|
||||
| 变更日志 | \`utils/change-log.ts\` |
|
||||
| 类型 | \`types.ts\` |
|
||||
| 组件 | \`FilterPanel.tsx\`、\`DetailKpiRow.tsx\`、\`DetailTable.tsx\`、\`DetailImportModal.tsx\`、\`DetailEditModal.tsx\`、\`DetailChangeLogModal.tsx\` |
|
||||
|
||||
---`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
doc.data.updatedAt = Date.now();
|
||||
writeFileSync(jsonPath, `${JSON.stringify(doc, null, 2)}\n`, 'utf8');
|
||||
console.log('Synced directory.nodes with PRD and', doc.data.nodes.length, 'annotation nodes');
|
||||
@@ -23,27 +23,45 @@ import {
|
||||
|
||||
const CURRENT_PROTOTYPE_ID = 'oneos-prototype-nav';
|
||||
const PUBLISHED_CLOUD_HOSTS = new Set(['prototype.lnoneos.com']);
|
||||
/** 导航页左侧 DIRECTORY 仅展示这些一级分区(小羚羚由 xll-nav-menu.json 单独注入) */
|
||||
const NAV_SIDEBAR_SECTION_TITLES = new Set(['OneOS']);
|
||||
|
||||
/** 与 axhub.config.json cloudPublishing.s3.pathAliases 保持一致 */
|
||||
const CLOUD_PUBLISH_PATH_ALIASES: Record<string, string> = {
|
||||
'oneos-prototype-nav': 'oneos-prototype-nav',
|
||||
};
|
||||
|
||||
function cloudPrototypeSegment(prototypeId: string): string {
|
||||
return CLOUD_PUBLISH_PATH_ALIASES[prototypeId] || `prototypes/${prototypeId}`;
|
||||
}
|
||||
|
||||
/** 对象存储静态发布站点(无 Make 本地 dev server 路由) */
|
||||
export function isPublishedCloudHost(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
const { hostname, pathname } = window.location;
|
||||
if (PUBLISHED_CLOUD_HOSTS.has(hostname)) return true;
|
||||
// export-html 发布页:/{prototype}/index.html
|
||||
return /\/index\.html$/u.test(pathname) && !pathname.startsWith('/prototypes/');
|
||||
// export-html 发布页:/{prototype}/index.html 或 /prototypes/{prototype}/index.html
|
||||
return /\/index\.html$/u.test(pathname);
|
||||
}
|
||||
|
||||
export function resolvePrototypeHref(prototypeId: string): string {
|
||||
if (typeof window === 'undefined') return `/prototypes/${prototypeId}`;
|
||||
if (isPublishedCloudHost()) {
|
||||
return `${window.location.origin}/prototypes/${prototypeId}/index.html`;
|
||||
return `${window.location.origin}/${cloudPrototypeSegment(prototypeId)}/index.html`;
|
||||
}
|
||||
return `/prototypes/${prototypeId}`;
|
||||
}
|
||||
|
||||
function normalizeAbsoluteHref(href: string): string {
|
||||
const matches = href.match(/https?:\/\/[^\s"']+/gu);
|
||||
if (matches && matches.length > 1) return matches[0];
|
||||
return href;
|
||||
}
|
||||
|
||||
/** 卡片/弹窗打开时按当前环境解析链接,避免静态包内 href 写死 */
|
||||
export function linkHref(link: NavLinkItem): string {
|
||||
if (link.external || /^https?:\/\//u.test(link.href)) return link.href;
|
||||
if (link.external) return normalizeAbsoluteHref(link.href);
|
||||
if (/^https?:\/\//u.test(link.href)) return normalizeAbsoluteHref(link.href);
|
||||
if (link.href.includes('#page=')) return link.href;
|
||||
return resolvePrototypeHref(link.prototypeId);
|
||||
}
|
||||
@@ -242,6 +260,7 @@ function transformSection(node: SidebarNode): NavSection | null {
|
||||
export function loadNavSections(): NavSection[] {
|
||||
const prototypes = (navMenu as { prototypes?: SidebarNode[] }).prototypes || [];
|
||||
const oneosSections = prototypes
|
||||
.filter((node) => node.kind === 'folder' && NAV_SIDEBAR_SECTION_TITLES.has(String(node.title || '')))
|
||||
.map((node) => transformSection(node))
|
||||
.filter((section): section is NavSection => Boolean(section));
|
||||
const xllSection = loadXllNavSection();
|
||||
|
||||
@@ -1,83 +1,5 @@
|
||||
{
|
||||
"prototypes": [
|
||||
{
|
||||
"id": "item-prototypes-task-work-order",
|
||||
"kind": "item",
|
||||
"title": "task work order",
|
||||
"itemKey": "prototypes/task-work-order"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-lease-business-line-overview",
|
||||
"kind": "item",
|
||||
"title": "lease business line overview",
|
||||
"itemKey": "prototypes/lease-business-line-overview"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-business",
|
||||
"kind": "item",
|
||||
"title": "oneos web business",
|
||||
"itemKey": "prototypes/oneos-web-business"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-contract-template",
|
||||
"kind": "item",
|
||||
"title": "oneos web contract template",
|
||||
"itemKey": "prototypes/oneos-web-contract-template"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-data-analysis",
|
||||
"kind": "item",
|
||||
"title": "oneos web data analysis",
|
||||
"itemKey": "prototypes/oneos-web-data-analysis"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-finance",
|
||||
"kind": "item",
|
||||
"title": "oneos web finance",
|
||||
"itemKey": "prototypes/oneos-web-finance"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-h2-station",
|
||||
"kind": "item",
|
||||
"title": "oneos web h2 station",
|
||||
"itemKey": "prototypes/oneos-web-h2-station"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-help-center",
|
||||
"kind": "item",
|
||||
"title": "oneos web help center",
|
||||
"itemKey": "prototypes/oneos-web-help-center"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-lease-contract",
|
||||
"kind": "item",
|
||||
"title": "oneos web lease contract",
|
||||
"itemKey": "prototypes/oneos-web-lease-contract"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-ledger-data",
|
||||
"kind": "item",
|
||||
"title": "oneos web ledger data",
|
||||
"itemKey": "prototypes/oneos-web-ledger-data"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-ops",
|
||||
"kind": "item",
|
||||
"title": "oneos web ops",
|
||||
"itemKey": "prototypes/oneos-web-ops"
|
||||
},
|
||||
{
|
||||
"id": "item-prototypes-oneos-web-procurement",
|
||||
"kind": "item",
|
||||
"title": "oneos web procurement",
|
||||
"itemKey": "prototypes/oneos-web-procurement"
|
||||
},
|
||||
{
|
||||
"id": "item:prototypes:oneos-prototype-nav",
|
||||
"kind": "item",
|
||||
"title": "原型导航",
|
||||
"itemKey": "prototypes/oneos-prototype-nav"
|
||||
},
|
||||
{
|
||||
"id": "folder-1782874576229-r8atbu",
|
||||
"kind": "folder",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -1,21 +1,19 @@
|
||||
import PickupReceivableViewPage from '../oneos-web-finance/pages/05-提车应收款-查看.jsx';
|
||||
import PickupReceivableInvoicePage from '../oneos-web-finance/pages/06-提车应收款-开票信息.jsx';
|
||||
import PickupReceivableAuditPage from '../oneos-web-finance/pages/07-提车应收款-审核.jsx';
|
||||
import PickupReceivableCollectPage from '../oneos-web-finance/pages/08-提车应收款-提车收款单.jsx';
|
||||
import PickupCollectEditPage from '../oneos-web-finance/pages/04-提车收款单-编辑.jsx';
|
||||
import VehiclePickupReceivableList from './VehiclePickupReceivableList.jsx';
|
||||
import VehiclePickupReceivableCollectPage from './VehiclePickupReceivableCollectPage.jsx';
|
||||
import VehiclePickupReceivableContractView from './VehiclePickupReceivableContractView.jsx';
|
||||
import {
|
||||
toCollectPageVehicles,
|
||||
appendPickupCollectChild,
|
||||
buildPickupSubmitCcMessage,
|
||||
applyPickupDateToVehicles,
|
||||
buildBatchPickupScheduleSummary,
|
||||
describePickupRule,
|
||||
buildCollectHandleContext,
|
||||
buildContractViewContext,
|
||||
canShowHandle,
|
||||
} from './pickup-receivable-bridge.js';
|
||||
|
||||
var DEFAULT_PICKUP_DATE = '2026-03-12';
|
||||
|
||||
/** @typedef {'list' | 'collect' | 'view' | 'edit' | 'invoice' | 'audit'} PickupView */
|
||||
/** @typedef {'list' | 'collect' | 'contractView' | 'view' | 'edit' | 'invoice' | 'audit'} PickupView */
|
||||
|
||||
const Component = function (props) {
|
||||
var useState = React.useState;
|
||||
@@ -26,7 +24,11 @@ const Component = function (props) {
|
||||
|
||||
useEffect(function () {
|
||||
if (props && props.onPageViewChange) {
|
||||
var pageId = view === 'collect' ? 'collect' : 'list';
|
||||
var pageId = view === 'collect'
|
||||
? 'collect'
|
||||
: view === 'contractView'
|
||||
? 'contractView'
|
||||
: 'list';
|
||||
props.onPageViewChange(pageId);
|
||||
}
|
||||
}, [view, props]);
|
||||
@@ -39,6 +41,14 @@ const Component = function (props) {
|
||||
var handleContext = handleContextState[0];
|
||||
var setHandleContext = handleContextState[1];
|
||||
|
||||
var viewContextState = useState(null);
|
||||
var viewContext = viewContextState[0];
|
||||
var setViewContext = viewContextState[1];
|
||||
|
||||
var contractViewRecordState = useState(null);
|
||||
var contractViewRecord = contractViewRecordState[0];
|
||||
var setContractViewRecord = contractViewRecordState[1];
|
||||
|
||||
useEffect(function () {
|
||||
window.__receivableBack = function () {
|
||||
setView('list');
|
||||
@@ -65,11 +75,6 @@ const Component = function (props) {
|
||||
if (window.__receivableBack) window.__receivableBack();
|
||||
};
|
||||
window.__pickupBuildSubmitCcMessage = buildPickupSubmitCcMessage;
|
||||
window.__pickupCalc = {
|
||||
applyPickupDateToVehicles: applyPickupDateToVehicles,
|
||||
buildBatchPickupScheduleSummary: buildBatchPickupScheduleSummary,
|
||||
describePickupRule: describePickupRule,
|
||||
};
|
||||
window.__pickupHandleSubmit = function (payload) {
|
||||
var ctx = window.__pickupHandleContext;
|
||||
if (ctx && ctx.contractCode) {
|
||||
@@ -85,9 +90,9 @@ const Component = function (props) {
|
||||
delete window.__receivableToAudit;
|
||||
delete window.__receivableSubmit;
|
||||
delete window.__pickupHandleContext;
|
||||
delete window.__pickupViewContext;
|
||||
delete window.__pickupHandleSubmit;
|
||||
delete window.__pickupBuildSubmitCcMessage;
|
||||
delete window.__pickupCalc;
|
||||
};
|
||||
}, [setView, bumpListRefresh]);
|
||||
|
||||
@@ -96,11 +101,26 @@ const Component = function (props) {
|
||||
if (view === 'collect' && handleContext) {
|
||||
window.__pickupHandleContext = handleContext;
|
||||
}
|
||||
if (view === 'contractView' && viewContext) {
|
||||
window.__pickupViewContext = viewContext;
|
||||
}
|
||||
},
|
||||
[view, handleContext],
|
||||
[view, handleContext, viewContext],
|
||||
);
|
||||
|
||||
if (view === 'collect') return React.createElement(PickupReceivableCollectPage);
|
||||
if (view === 'collect') return React.createElement(VehiclePickupReceivableCollectPage);
|
||||
if (view === 'contractView') {
|
||||
return React.createElement(VehiclePickupReceivableContractView, {
|
||||
canHandle: contractViewRecord ? canShowHandle(contractViewRecord) : false,
|
||||
onGoHandle: function () {
|
||||
if (!contractViewRecord) return;
|
||||
var ctx = buildCollectHandleContext(contractViewRecord);
|
||||
setHandleContext(ctx);
|
||||
window.__pickupHandleContext = ctx;
|
||||
setView('collect');
|
||||
},
|
||||
});
|
||||
}
|
||||
if (view === 'view') return React.createElement(PickupReceivableViewPage);
|
||||
if (view === 'edit') return React.createElement(PickupCollectEditPage);
|
||||
if (view === 'invoice') return React.createElement(PickupReceivableInvoicePage);
|
||||
@@ -109,28 +129,22 @@ const Component = function (props) {
|
||||
return React.createElement(VehiclePickupReceivableList, {
|
||||
refreshToken: listRefreshToken,
|
||||
onGoHandle: function (record) {
|
||||
var months = record.paymentCycleMonths || 1;
|
||||
var pickupDate = record.lastPickupDate || DEFAULT_PICKUP_DATE;
|
||||
var ctx = {
|
||||
contractCode: record.contractCode,
|
||||
contractType: record.contractType,
|
||||
projectName: record.projectName,
|
||||
customerName: record.customerName,
|
||||
paymentMethod: record.paymentMethod || '预付',
|
||||
paymentCycle: months + '个月',
|
||||
paymentCycleMonths: months,
|
||||
contractStart: record.contractEffectiveDate,
|
||||
contractEnd: record.contractEndDate || '',
|
||||
businessDept: record.businessDept,
|
||||
businessPerson: record.businessPerson,
|
||||
pickupDate: pickupDate,
|
||||
vehicles: toCollectPageVehicles(record, pickupDate),
|
||||
};
|
||||
var ctx = buildCollectHandleContext(record);
|
||||
setHandleContext(ctx);
|
||||
window.__pickupHandleContext = ctx;
|
||||
setView('collect');
|
||||
},
|
||||
onGoView: function () {
|
||||
onGoContractView: function (record) {
|
||||
setContractViewRecord(record);
|
||||
var ctx = buildContractViewContext(record);
|
||||
setViewContext(ctx);
|
||||
window.__pickupViewContext = ctx;
|
||||
setView('contractView');
|
||||
},
|
||||
onGoView: function (record, parent) {
|
||||
if (parent) {
|
||||
window.__pickupViewContext = { mode: 'batch', batch: record, parent: parent };
|
||||
}
|
||||
setView('view');
|
||||
},
|
||||
onGoEdit: function () {
|
||||
|
||||
@@ -0,0 +1,814 @@
|
||||
import {
|
||||
fmtMoneyYuan,
|
||||
} from './data/pickup-receivable-list-data.js';
|
||||
import {
|
||||
buildBatchPickupScheduleSummary,
|
||||
describePickupRule,
|
||||
buildCollectLinePreview,
|
||||
buildCollectSubmitPayload,
|
||||
buildCollectOrderTableRows,
|
||||
resolveCollectPlateLabel,
|
||||
PLATE_ACTUAL_DELIVERY_LABEL,
|
||||
} from './pickup-receivable-bridge.js';
|
||||
|
||||
function displayText(value) {
|
||||
var text = value == null ? '' : String(value).trim();
|
||||
return text || '—';
|
||||
}
|
||||
|
||||
function initLineQuantities(lines) {
|
||||
var map = {};
|
||||
(lines || []).forEach(function (line) {
|
||||
map[line.lineKey] = 0;
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
function formatBrandModelLabel(brand, model) {
|
||||
return [brand, model]
|
||||
.filter(function (part) {
|
||||
return part && String(part).trim() && part !== '-';
|
||||
})
|
||||
.join(' · ') || '—';
|
||||
}
|
||||
|
||||
function DeliveryFieldIconLocation() {
|
||||
return React.createElement(
|
||||
'svg',
|
||||
{
|
||||
className: 'vpr-collect-delivery-field__icon',
|
||||
width: 16,
|
||||
height: 16,
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
'aria-hidden': true,
|
||||
},
|
||||
React.createElement('path', { d: 'M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z' }),
|
||||
React.createElement('circle', { cx: '12', cy: '10', r: '3' }),
|
||||
);
|
||||
}
|
||||
|
||||
function DeliveryFieldIconCalendar() {
|
||||
return React.createElement(
|
||||
'svg',
|
||||
{
|
||||
className: 'vpr-collect-delivery-field__icon',
|
||||
width: 16,
|
||||
height: 16,
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
'aria-hidden': true,
|
||||
},
|
||||
React.createElement('rect', { x: '3', y: '4', width: '18', height: '18', rx: '2' }),
|
||||
React.createElement('line', { x1: '16', y1: '2', x2: '16', y2: '6' }),
|
||||
React.createElement('line', { x1: '8', y1: '2', x2: '8', y2: '6' }),
|
||||
React.createElement('line', { x1: '3', y1: '10', x2: '21', y2: '10' }),
|
||||
);
|
||||
}
|
||||
|
||||
const Component = function () {
|
||||
var useState = React.useState;
|
||||
var useMemo = React.useMemo;
|
||||
var useEffect = React.useEffect;
|
||||
|
||||
var antd = window.antd;
|
||||
var Card = antd.Card;
|
||||
var Button = antd.Button;
|
||||
var Input = antd.Input;
|
||||
var InputNumber = antd.InputNumber;
|
||||
var DatePicker = antd.DatePicker;
|
||||
var Modal = antd.Modal;
|
||||
var message = antd.message;
|
||||
var dayjs = window.dayjs;
|
||||
|
||||
var handleCtx = typeof window !== 'undefined' ? window.__pickupHandleContext : null;
|
||||
var project = handleCtx || {};
|
||||
var orderLines = project.leaseOrderLines || [];
|
||||
var paymentCycleMonths = project.paymentCycleMonths || 1;
|
||||
|
||||
var deliveryLocationState = useState(project.defaultDeliveryLocation || '');
|
||||
var deliveryLocation = deliveryLocationState[0];
|
||||
var setDeliveryLocation = deliveryLocationState[1];
|
||||
|
||||
var deliveryDateState = useState(project.defaultPickupDate || '');
|
||||
var deliveryDate = deliveryDateState[0];
|
||||
var setDeliveryDate = deliveryDateState[1];
|
||||
|
||||
var lineQuantitiesState = useState(function () {
|
||||
return initLineQuantities(orderLines);
|
||||
});
|
||||
var lineQuantities = lineQuantitiesState[0];
|
||||
var setLineQuantities = lineQuantitiesState[1];
|
||||
|
||||
var dirtyState = useState(false);
|
||||
var dirty = dirtyState[0];
|
||||
var setDirty = dirtyState[1];
|
||||
|
||||
var submitAttemptState = useState(false);
|
||||
var submitAttempted = submitAttemptState[0];
|
||||
var setSubmitAttempted = submitAttemptState[1];
|
||||
|
||||
var submitConfirmState = useState(false);
|
||||
var submitConfirmVisible = submitConfirmState[0];
|
||||
var setSubmitConfirmVisible = submitConfirmState[1];
|
||||
|
||||
useEffect(function () {
|
||||
setLineQuantities(initLineQuantities(orderLines));
|
||||
}, [project.contractCode]);
|
||||
|
||||
var selectedCount = useMemo(function () {
|
||||
return (orderLines || []).reduce(function (sum, line) {
|
||||
var qty = Math.min(
|
||||
line.availableCount,
|
||||
Math.max(0, parseInt(lineQuantities[line.lineKey], 10) || 0),
|
||||
);
|
||||
return sum + qty;
|
||||
}, 0);
|
||||
}, [orderLines, lineQuantities]);
|
||||
|
||||
var calcRows = useMemo(function () {
|
||||
var rows = [];
|
||||
(orderLines || []).forEach(function (line) {
|
||||
var qty = Math.min(
|
||||
line.availableCount,
|
||||
Math.max(0, parseInt(lineQuantities[line.lineKey], 10) || 0),
|
||||
);
|
||||
for (var i = 0; i < qty; i += 1) {
|
||||
rows.push({
|
||||
selected: true,
|
||||
receivableCompleted: false,
|
||||
monthRent: line.monthRent,
|
||||
serviceFeeTotal: line.serviceFeeTotal,
|
||||
deposit: line.deposit,
|
||||
receivableDeposit: line.deposit,
|
||||
receivableService: line.serviceFeeTotal,
|
||||
});
|
||||
}
|
||||
});
|
||||
return rows;
|
||||
}, [orderLines, lineQuantities]);
|
||||
|
||||
var batchSummary = useMemo(function () {
|
||||
if (!deliveryDate || !calcRows.length) return null;
|
||||
return buildBatchPickupScheduleSummary(calcRows, deliveryDate, paymentCycleMonths);
|
||||
}, [calcRows, deliveryDate, paymentCycleMonths]);
|
||||
|
||||
var amountBreakdown = useMemo(function () {
|
||||
if (!batchSummary) {
|
||||
return { rent: 0, deposit: 0, service: 0, total: 0 };
|
||||
}
|
||||
return {
|
||||
rent: (batchSummary.beforePickup.rent || 0) + (batchSummary.month25.rent || 0),
|
||||
deposit: batchSummary.beforePickup.deposit || 0,
|
||||
service: (batchSummary.beforePickup.service || 0) + (batchSummary.month25.service || 0),
|
||||
total: batchSummary.totalReceivable || 0,
|
||||
};
|
||||
}, [batchSummary]);
|
||||
|
||||
var orderTableRows = useMemo(function () {
|
||||
return buildCollectOrderTableRows(orderLines);
|
||||
}, [orderLines]);
|
||||
|
||||
var ruleText = useMemo(function () {
|
||||
return describePickupRule(batchSummary && batchSummary.rule);
|
||||
}, [batchSummary]);
|
||||
|
||||
var formReady = Boolean(deliveryDate && String(deliveryLocation || '').trim() && selectedCount > 0);
|
||||
|
||||
var submitHint = useMemo(function () {
|
||||
if (!deliveryDate) return '请先设置车辆交付时间';
|
||||
if (!String(deliveryLocation || '').trim()) return '请先填写车辆交付地点';
|
||||
if (!selectedCount) return '请至少选择 1 辆本次交车数量';
|
||||
return '';
|
||||
}, [deliveryDate, deliveryLocation, selectedCount]);
|
||||
|
||||
function markDirty() {
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
if (dirty) {
|
||||
Modal.confirm({
|
||||
title: '确认离开',
|
||||
content: '当前办理信息尚未提交,确定返回列表吗?',
|
||||
okText: '返回列表',
|
||||
cancelText: '继续编辑',
|
||||
onOk: function () {
|
||||
if (window.__receivableBack) window.__receivableBack();
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (window.__receivableBack) window.__receivableBack();
|
||||
}
|
||||
|
||||
function fillLineQuantity(lineKey, quantity) {
|
||||
setLineQuantities(function (prev) {
|
||||
var next = Object.assign({}, prev);
|
||||
next[lineKey] = quantity;
|
||||
return next;
|
||||
});
|
||||
markDirty();
|
||||
}
|
||||
|
||||
function fillAllPending() {
|
||||
setLineQuantities(function () {
|
||||
var next = {};
|
||||
(orderLines || []).forEach(function (line) {
|
||||
next[line.lineKey] = line.availableCount;
|
||||
});
|
||||
return next;
|
||||
});
|
||||
markDirty();
|
||||
}
|
||||
|
||||
function clearAllQuantities() {
|
||||
setLineQuantities(initLineQuantities(orderLines));
|
||||
markDirty();
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
setSubmitAttempted(true);
|
||||
if (!deliveryDate) {
|
||||
message.warning('请设置车辆交付时间');
|
||||
return;
|
||||
}
|
||||
if (!String(deliveryLocation || '').trim()) {
|
||||
message.warning('请填写车辆交付(交还)地点');
|
||||
return;
|
||||
}
|
||||
if (!selectedCount) {
|
||||
message.warning('请至少选择 1 辆本次交车数量');
|
||||
return;
|
||||
}
|
||||
setSubmitConfirmVisible(true);
|
||||
}
|
||||
|
||||
function confirmSubmit() {
|
||||
var payload = buildCollectSubmitPayload(project, {
|
||||
deliveryDate: deliveryDate,
|
||||
deliveryLocation: deliveryLocation,
|
||||
lineQuantities: lineQuantities,
|
||||
});
|
||||
if (window.__pickupHandleSubmit) window.__pickupHandleSubmit(payload);
|
||||
if (window.__pickupBuildSubmitCcMessage) {
|
||||
message.success(window.__pickupBuildSubmitCcMessage());
|
||||
} else {
|
||||
message.success('已提交办理并生成提车应收款单据');
|
||||
}
|
||||
setSubmitConfirmVisible(false);
|
||||
setDirty(false);
|
||||
if (window.__receivableBack) window.__receivableBack();
|
||||
}
|
||||
|
||||
function renderContractStat(label, value, options) {
|
||||
var opts = options || {};
|
||||
var valueClass = 'vm-detail-stat-value';
|
||||
if (opts.mono) valueClass += ' mono tabular-nums';
|
||||
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-stat', key: label },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-stat-main' },
|
||||
React.createElement('span', { className: 'vm-detail-stat-label' }, label),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: valueClass, title: displayText(value) },
|
||||
displayText(value),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function renderCollectPlateCell(vehicle) {
|
||||
var label = resolveCollectPlateLabel(vehicle);
|
||||
var isPending = label === PLATE_ACTUAL_DELIVERY_LABEL;
|
||||
return React.createElement(
|
||||
'span',
|
||||
{
|
||||
className: 'vpr-pickup-subtable__plate-no tabular-nums'
|
||||
+ (isPending ? ' vpr-pickup-subtable__plate-no--pending' : ''),
|
||||
title: label,
|
||||
},
|
||||
label,
|
||||
);
|
||||
}
|
||||
|
||||
function renderCollectModelGroupCell(line) {
|
||||
var label = formatBrandModelLabel(line.brand, line.model);
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-td--model', title: label },
|
||||
label,
|
||||
);
|
||||
}
|
||||
|
||||
function renderKpiStat(label, value, highlight) {
|
||||
return React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: 'vm-detail-stat vpr-collect-kpi-stat'
|
||||
+ (highlight ? ' vpr-collect-kpi-stat--active' : ''),
|
||||
key: label,
|
||||
},
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-stat-main' },
|
||||
React.createElement('span', { className: 'vm-detail-stat-label' }, label),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-stat-value vpr-collect-kpi-stat__value tabular-nums' },
|
||||
value,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
var businessMeta = [project.businessDept, project.businessPerson]
|
||||
.filter(function (part) {
|
||||
return part && String(part).trim();
|
||||
})
|
||||
.join(' · ');
|
||||
|
||||
var contractStats = [
|
||||
renderContractStat('签约公司', project.signingCompany),
|
||||
renderContractStat('付款方式', project.paymentMethodLabel || project.paymentMethod),
|
||||
renderContractStat('付款周期', project.paymentCycleLabel || project.paymentCycle),
|
||||
renderContractStat('氢费支付方式', project.hydrogenPaymentMethodLabel || project.hydrogenPaymentMethod),
|
||||
];
|
||||
if (project.hydrogenPrepayAmount != null) {
|
||||
contractStats.push(renderContractStat('预付款金额', fmtMoneyYuan(project.hydrogenPrepayAmount), { mono: true }));
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-page lc-page vpr-page vpr-collect-page' },
|
||||
React.createElement(
|
||||
'header',
|
||||
{
|
||||
className: 'vm-detail-topbar vpr-collect-topbar',
|
||||
'data-annotation-id': 'vpr-collect-topbar',
|
||||
},
|
||||
React.createElement(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
className: 'vm-btn vm-btn-back',
|
||||
onClick: handleBack,
|
||||
},
|
||||
'返回列表',
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'main',
|
||||
{ className: 'vpr-collect-main' },
|
||||
React.createElement(
|
||||
'section',
|
||||
{
|
||||
className: 'vm-detail-card vpr-collect-contract-card',
|
||||
'data-annotation-id': 'vpr-collect-project',
|
||||
},
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-hero vpr-collect-contract-hero' },
|
||||
React.createElement(
|
||||
'div',
|
||||
null,
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-plate', title: displayText(project.customerName) },
|
||||
displayText(project.customerName),
|
||||
),
|
||||
React.createElement(
|
||||
'p',
|
||||
{ className: 'vm-detail-meta mono tabular-nums' },
|
||||
displayText(project.contractCode),
|
||||
),
|
||||
React.createElement(
|
||||
'p',
|
||||
{ className: 'vm-detail-meta', title: displayText(project.projectName) },
|
||||
displayText(project.projectName),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-aside' },
|
||||
React.createElement(
|
||||
'p',
|
||||
{ className: 'vpr-collect-contract-hero__aside-main', title: displayText(project.signingCompany) },
|
||||
displayText(project.signingCompany),
|
||||
),
|
||||
businessMeta
|
||||
? React.createElement('p', { className: 'vm-detail-meta' }, businessMeta)
|
||||
: null,
|
||||
React.createElement(
|
||||
'p',
|
||||
{ className: 'vm-detail-meta' },
|
||||
displayText(project.contractType || '正式合同'),
|
||||
),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-detail-stats vpr-collect-contract-stats' },
|
||||
contractStats,
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: 'vm-detail-stats vpr-collect-summary-stats',
|
||||
'data-annotation-id': 'vpr-collect-kpi',
|
||||
},
|
||||
renderKpiStat('已选车辆数', selectedCount + ' 辆', selectedCount > 0),
|
||||
renderKpiStat('应收总额', fmtMoneyYuan(amountBreakdown.total), amountBreakdown.total > 0),
|
||||
renderKpiStat('租金总额', fmtMoneyYuan(amountBreakdown.rent), amountBreakdown.rent > 0),
|
||||
renderKpiStat('保证金总额', fmtMoneyYuan(amountBreakdown.deposit), amountBreakdown.deposit > 0),
|
||||
renderKpiStat('服务项总额', fmtMoneyYuan(amountBreakdown.service), amountBreakdown.service > 0),
|
||||
),
|
||||
React.createElement(
|
||||
Card,
|
||||
{
|
||||
className: 'vpr-collect-card vpr-collect-card--order',
|
||||
title: '提车应收款',
|
||||
'data-annotation-id': 'vpr-collect-form',
|
||||
extra: orderLines.length
|
||||
? React.createElement(
|
||||
'span',
|
||||
{ className: 'vpr-collect-card__extra tabular-nums' },
|
||||
orderLines.length,
|
||||
' 个待办型号',
|
||||
)
|
||||
: null,
|
||||
},
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-order-section' },
|
||||
React.createElement(
|
||||
'section',
|
||||
{ className: 'vpr-collect-delivery-panel', 'aria-label': '交付信息' },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-delivery-panel__head' },
|
||||
React.createElement('h3', { className: 'vpr-collect-delivery-panel__title' }, '交付信息'),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'vpr-collect-delivery-panel__hint' },
|
||||
'根据交付时间,自动计算提车应收款金额',
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-delivery-panel__grid' },
|
||||
React.createElement(
|
||||
'label',
|
||||
{
|
||||
className:
|
||||
'vpr-collect-delivery-field'
|
||||
+ (submitAttempted && !String(deliveryLocation || '').trim() ? ' vpr-collect-delivery-field--invalid' : ''),
|
||||
},
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'vpr-collect-delivery-field__label' },
|
||||
React.createElement(DeliveryFieldIconLocation),
|
||||
'车辆交付(交还)地点',
|
||||
React.createElement('span', { className: 'vpr-collect-form-item__required', 'aria-hidden': true }, ' *'),
|
||||
),
|
||||
React.createElement(Input, {
|
||||
value: deliveryLocation,
|
||||
placeholder: '请输入本次交车地点',
|
||||
className: 'vpr-collect-delivery-field__control',
|
||||
status: submitAttempted && !String(deliveryLocation || '').trim() ? 'error' : undefined,
|
||||
onChange: function (e) {
|
||||
setDeliveryLocation(e.target.value);
|
||||
markDirty();
|
||||
},
|
||||
}),
|
||||
submitAttempted && !String(deliveryLocation || '').trim()
|
||||
? React.createElement('span', { className: 'vpr-collect-form-item__error', role: 'alert' }, '请填写交付地点')
|
||||
: null,
|
||||
),
|
||||
React.createElement(
|
||||
'label',
|
||||
{
|
||||
className:
|
||||
'vpr-collect-delivery-field'
|
||||
+ (submitAttempted && !deliveryDate ? ' vpr-collect-delivery-field--invalid' : ''),
|
||||
},
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'vpr-collect-delivery-field__label' },
|
||||
React.createElement(DeliveryFieldIconCalendar),
|
||||
'车辆交付时间',
|
||||
React.createElement('span', { className: 'vpr-collect-form-item__required', 'aria-hidden': true }, ' *'),
|
||||
),
|
||||
React.createElement(DatePicker, {
|
||||
className: 'vpr-collect-delivery-field__control vpr-collect-date-picker',
|
||||
style: { width: '100%' },
|
||||
placeholder: '请选择交付日期',
|
||||
status: submitAttempted && !deliveryDate ? 'error' : undefined,
|
||||
value: deliveryDate && dayjs ? dayjs(deliveryDate) : null,
|
||||
onChange: function (_date, dateString) {
|
||||
setDeliveryDate(dateString || '');
|
||||
markDirty();
|
||||
},
|
||||
}),
|
||||
submitAttempted && !deliveryDate
|
||||
? React.createElement('span', { className: 'vpr-collect-form-item__error', role: 'alert' }, '请选择交付时间')
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-page-callout vpr-page-callout--rule vpr-collect-order-callout', role: 'note' },
|
||||
ruleText,
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-table-toolbar' },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-table-toolbar__left' },
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'vpr-collect-table-toolbar__badge tabular-nums' },
|
||||
'待办 ',
|
||||
orderLines.length,
|
||||
' 行',
|
||||
),
|
||||
React.createElement('span', { className: 'vpr-collect-table-toolbar__hint' }, '填写本次交车数后自动计算应收'),
|
||||
),
|
||||
orderLines.length
|
||||
? React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-table-toolbar__actions' },
|
||||
React.createElement(Button, { size: 'small', type: 'primary', ghost: true, onClick: fillAllPending }, '填入全部待办'),
|
||||
React.createElement(Button, { size: 'small', onClick: clearAllQuantities }, '清空数量'),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-table-wrap', 'data-annotation-id': 'vpr-collect-table' },
|
||||
orderLines.length
|
||||
? React.createElement(
|
||||
'table',
|
||||
{ className: 'vpr-collect-table' },
|
||||
React.createElement(
|
||||
'thead',
|
||||
null,
|
||||
React.createElement(
|
||||
'tr',
|
||||
null,
|
||||
React.createElement('th', null, '品牌型号'),
|
||||
React.createElement('th', { className: 'vpr-collect-th--plate' }, '车牌号'),
|
||||
React.createElement('th', { className: 'vpr-collect-th--num' }, '未交车辆数'),
|
||||
React.createElement('th', { className: 'vpr-collect-th--num' }, '本次交车数'),
|
||||
React.createElement('th', { className: 'vpr-collect-th--num' }, '租金总额'),
|
||||
React.createElement('th', { className: 'vpr-collect-th--num' }, '保证金总额'),
|
||||
React.createElement('th', { className: 'vpr-collect-th--num' }, '服务项总额'),
|
||||
React.createElement('th', { className: 'vpr-collect-th--num' }, '应收总额'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'tbody',
|
||||
null,
|
||||
orderTableRows.map(function (row) {
|
||||
var line = row.line;
|
||||
var preview = buildCollectLinePreview(
|
||||
line,
|
||||
lineQuantities[line.lineKey],
|
||||
deliveryDate,
|
||||
paymentCycleMonths,
|
||||
);
|
||||
var rowClass = preview.quantity ? 'vpr-collect-row--selected' : 'vpr-collect-row--idle';
|
||||
var cells = [];
|
||||
|
||||
if (row.isFirstInModelGroup) {
|
||||
cells.push(
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'model',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-collect-td--model-cell',
|
||||
},
|
||||
renderCollectModelGroupCell(line),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
cells.push(
|
||||
React.createElement(
|
||||
'td',
|
||||
{ key: 'plateNo', className: 'vpr-pickup-subtable__plate-cell' },
|
||||
renderCollectPlateCell(row.vehicle),
|
||||
),
|
||||
);
|
||||
|
||||
if (row.isFirstInModelGroup) {
|
||||
cells.push(
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'pendingCount',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-collect-td--num vpr-collect-td--action tabular-nums',
|
||||
title: '点击填入该行全部未交车辆',
|
||||
onClick: function () {
|
||||
fillLineQuantity(line.lineKey, line.availableCount);
|
||||
},
|
||||
},
|
||||
line.availableCount,
|
||||
),
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'pickupQty',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-collect-td--num',
|
||||
},
|
||||
React.createElement(InputNumber, {
|
||||
min: 0,
|
||||
max: line.availableCount,
|
||||
value: lineQuantities[line.lineKey] || 0,
|
||||
className: 'vpr-collect-qty-input',
|
||||
onChange: function (value) {
|
||||
setLineQuantities(function (prev) {
|
||||
var next = Object.assign({}, prev);
|
||||
next[line.lineKey] = value == null ? 0 : value;
|
||||
return next;
|
||||
});
|
||||
markDirty();
|
||||
},
|
||||
}),
|
||||
),
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'rentTotal',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-collect-td--num tabular-nums',
|
||||
},
|
||||
fmtMoneyYuan(preview.rentTotal),
|
||||
),
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'depositTotal',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-collect-td--num tabular-nums',
|
||||
},
|
||||
fmtMoneyYuan(preview.depositTotal),
|
||||
),
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'serviceTotal',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-collect-td--num tabular-nums',
|
||||
},
|
||||
fmtMoneyYuan(preview.serviceTotal),
|
||||
),
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'lineTotal',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-collect-td--num tabular-nums',
|
||||
},
|
||||
fmtMoneyYuan(preview.lineTotal),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
'tr',
|
||||
{ key: row.rowKey, className: rowClass },
|
||||
cells,
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
: React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-table-toolbar vpr-collect-table-toolbar--warn vpr-page-empty' },
|
||||
React.createElement('p', { className: 'vpr-page-empty__title' }, '暂无可办理的租赁订单明细'),
|
||||
React.createElement('p', { className: 'vpr-page-empty__desc' }, '本合同车辆可能已全部办理,请返回列表确认。'),
|
||||
),
|
||||
),
|
||||
batchSummary && batchSummary.rule === 'on-or-before-15' && batchSummary.month25.amount > 0
|
||||
? React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-batch-panel' },
|
||||
React.createElement('div', { className: 'vpr-collect-batch-panel__title' }, '当月 25 日应付(下周期租金及服务费)'),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-batch-panel__amount tabular-nums' },
|
||||
fmtMoneyYuan(batchSummary.month25.amount),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'footer',
|
||||
{ className: 'vpr-collect-footer', 'data-annotation-id': 'vpr-collect-footer' },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-footer__left' },
|
||||
dirty
|
||||
? React.createElement('span', { className: 'vpr-collect-draft-badge' }, '未提交')
|
||||
: null,
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-footer__summary' + (formReady ? '' : ' vpr-collect-footer__summary--warn') },
|
||||
formReady
|
||||
? React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
'已选 ',
|
||||
React.createElement('strong', { className: 'tabular-nums' }, String(selectedCount)),
|
||||
' 辆 · 应收合计 ',
|
||||
React.createElement(
|
||||
'strong',
|
||||
{ className: 'tabular-nums' },
|
||||
fmtMoneyYuan(amountBreakdown.total),
|
||||
),
|
||||
)
|
||||
: React.createElement('span', null, submitHint || '请完善交付信息与交车数量'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-footer__actions' },
|
||||
React.createElement(Button, { onClick: handleBack }, '取消'),
|
||||
React.createElement(
|
||||
Button,
|
||||
{
|
||||
type: 'primary',
|
||||
className: 'vm-btn-primary',
|
||||
disabled: !formReady,
|
||||
onClick: handleSubmit,
|
||||
title: formReady ? '提交并生成提车应收款' : submitHint,
|
||||
},
|
||||
'提交生成',
|
||||
),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
Modal,
|
||||
{
|
||||
title: '确认提交办理',
|
||||
open: submitConfirmVisible,
|
||||
okText: '确认提交',
|
||||
cancelText: '再检查一下',
|
||||
onOk: confirmSubmit,
|
||||
onCancel: function () {
|
||||
setSubmitConfirmVisible(false);
|
||||
},
|
||||
},
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-submit-confirm' },
|
||||
React.createElement(
|
||||
'p',
|
||||
null,
|
||||
'将为 ',
|
||||
React.createElement('strong', null, String(selectedCount)),
|
||||
' 辆车生成本次提车应收款。',
|
||||
),
|
||||
React.createElement(
|
||||
'ul',
|
||||
{ className: 'vpr-submit-confirm__list' },
|
||||
React.createElement('li', null, '交付时间:', React.createElement('strong', null, deliveryDate || '—')),
|
||||
React.createElement('li', null, '交付地点:', React.createElement('strong', null, deliveryLocation || '—')),
|
||||
React.createElement(
|
||||
'li',
|
||||
null,
|
||||
'应收合计:',
|
||||
React.createElement(
|
||||
'strong',
|
||||
{ className: 'tabular-nums' },
|
||||
fmtMoneyYuan(amountBreakdown.total),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
@@ -0,0 +1,342 @@
|
||||
import { StatusTag } from '../vehicle-management/components/StatusTag';
|
||||
import {
|
||||
fmtMoneyYuan,
|
||||
normalizeAuditStatus,
|
||||
auditStatusTone,
|
||||
} from './data/pickup-receivable-list-data.js';
|
||||
|
||||
function displayText(value) {
|
||||
var text = value == null ? '' : String(value).trim();
|
||||
return text || '—';
|
||||
}
|
||||
|
||||
function renderInfoField(label, value) {
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-field', key: label },
|
||||
React.createElement('div', { className: 'vpr-collect-field__label' }, label),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-field__value', title: displayText(value) },
|
||||
displayText(value),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const Component = function (props) {
|
||||
var useMemo = React.useMemo;
|
||||
var antd = window.antd;
|
||||
var Button = antd.Button;
|
||||
var Card = antd.Card;
|
||||
|
||||
var ctx = typeof window !== 'undefined' ? window.__pickupViewContext : null;
|
||||
var project = ctx || {};
|
||||
var publishedRecords = project.publishedRecords || [];
|
||||
var pendingVehicles = project.pendingVehicles || [];
|
||||
var onGoHandle = props && props.onGoHandle;
|
||||
var canHandle = props && props.canHandle;
|
||||
|
||||
var summary = useMemo(function () {
|
||||
var receivableTotal = 0;
|
||||
var receivedTotal = 0;
|
||||
publishedRecords.forEach(function (row) {
|
||||
receivableTotal += parseFloat(row.receivableTotal) || 0;
|
||||
receivedTotal += parseFloat(row.receivedTotal) || 0;
|
||||
});
|
||||
return {
|
||||
publishedCount: publishedRecords.length,
|
||||
pendingCount: pendingVehicles.length,
|
||||
receivableTotal: receivableTotal,
|
||||
receivedTotal: receivedTotal,
|
||||
};
|
||||
}, [publishedRecords, pendingVehicles]);
|
||||
|
||||
var showHandle = canHandle && pendingVehicles.length > 0 && onGoHandle;
|
||||
|
||||
function handleBack() {
|
||||
if (window.__receivableBack) window.__receivableBack();
|
||||
}
|
||||
|
||||
function handleGoHandle() {
|
||||
if (onGoHandle) onGoHandle();
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-page lc-page vpr-page vpr-collect-page vpr-contract-view-page' },
|
||||
React.createElement(
|
||||
'header',
|
||||
{ className: 'vpr-collect-topbar', 'data-annotation-id': 'vpr-contract-view-topbar' },
|
||||
React.createElement(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
className: 'vm-btn vm-btn-back',
|
||||
onClick: handleBack,
|
||||
},
|
||||
'返回列表',
|
||||
),
|
||||
),
|
||||
showHandle
|
||||
? React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-contract-view-actions' },
|
||||
React.createElement(
|
||||
Button,
|
||||
{
|
||||
type: 'primary',
|
||||
className: 'vm-btn-primary',
|
||||
onClick: handleGoHandle,
|
||||
},
|
||||
'办理未发布车辆',
|
||||
),
|
||||
)
|
||||
: null,
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-page-kpi-row', 'data-annotation-id': 'vpr-contract-view-kpi' },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-kpi-card' },
|
||||
React.createElement('div', { className: 'vpr-collect-kpi-card__label' }, '已发布单据'),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-kpi-card__value tabular-nums' },
|
||||
String(summary.publishedCount),
|
||||
React.createElement('span', { className: 'vpr-page-kpi-unit' }, ' 条'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-kpi-card' },
|
||||
React.createElement('div', { className: 'vpr-collect-kpi-card__label' }, '未发布车辆'),
|
||||
React.createElement(
|
||||
'div',
|
||||
{
|
||||
className:
|
||||
'vpr-collect-kpi-card__value tabular-nums'
|
||||
+ (summary.pendingCount ? ' vpr-page-kpi-value--warn' : ''),
|
||||
},
|
||||
String(summary.pendingCount),
|
||||
React.createElement('span', { className: 'vpr-page-kpi-unit' }, ' 辆'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-kpi-card' },
|
||||
React.createElement('div', { className: 'vpr-collect-kpi-card__label' }, '已发布合计应收'),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-kpi-card__value tabular-nums' },
|
||||
fmtMoneyYuan(summary.receivableTotal),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-kpi-card vpr-collect-kpi-card--primary' },
|
||||
React.createElement('div', { className: 'vpr-collect-kpi-card__label' }, '已发布合计实收'),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-kpi-card__value tabular-nums' },
|
||||
fmtMoneyYuan(summary.receivedTotal),
|
||||
),
|
||||
React.createElement('div', { className: 'vpr-collect-kpi-card__hint' }, '由收款记录导入反写'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
Card,
|
||||
{
|
||||
className: 'vpr-collect-card vpr-collect-card--primary',
|
||||
title: '合同项目信息',
|
||||
},
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-collect-info-grid' },
|
||||
renderInfoField('客户名称', project.customerName),
|
||||
renderInfoField('合同编码', project.contractCode),
|
||||
renderInfoField('项目名称', project.projectName),
|
||||
renderInfoField('付款周期', project.paymentCycle),
|
||||
renderInfoField('业务部门', project.businessDept),
|
||||
renderInfoField('业务负责人', project.businessPerson),
|
||||
renderInfoField('租车数', project.contractVehicleCount),
|
||||
renderInfoField('已提车数', project.pickedUpVehicleCount),
|
||||
),
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-page-callout', role: 'note' },
|
||||
React.createElement('strong', null, '说明:'),
|
||||
'本页汇总本合同全部已发布与未发布提车应收款。未发布车辆可点击上方「办理未发布车辆」进入办理流程。',
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'section',
|
||||
{ className: 'vpr-contract-view-section' },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-contract-view-section__head' },
|
||||
React.createElement('h2', { className: 'vpr-contract-view-section__title' }, '已发布提车应收款'),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'vpr-contract-view-section__count tabular-nums' },
|
||||
summary.publishedCount + ' 条',
|
||||
),
|
||||
),
|
||||
publishedRecords.length
|
||||
? React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-pickup-table-wrap vpr-page-table-wrap' },
|
||||
React.createElement(
|
||||
'table',
|
||||
{ className: 'vpr-pickup-native-table vpr-contract-view-table' },
|
||||
React.createElement(
|
||||
'thead',
|
||||
null,
|
||||
React.createElement(
|
||||
'tr',
|
||||
null,
|
||||
React.createElement('th', null, '提车收款单编码'),
|
||||
React.createElement('th', null, '发布状态'),
|
||||
React.createElement('th', null, '单据状态'),
|
||||
React.createElement('th', null, '交付时间'),
|
||||
React.createElement('th', { className: 'vpr-th--num' }, '车辆数'),
|
||||
React.createElement('th', { className: 'vpr-th--num' }, '合计应收'),
|
||||
React.createElement('th', { className: 'vpr-th--num' }, '合计实收'),
|
||||
React.createElement('th', null, '生成时间'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'tbody',
|
||||
null,
|
||||
publishedRecords.map(function (row) {
|
||||
var auditLabel = normalizeAuditStatus(row.auditStatus);
|
||||
return React.createElement(
|
||||
'tr',
|
||||
{ key: row.collectCode || row.seq, className: 'vpr-page-table-row' },
|
||||
React.createElement('td', { className: 'tabular-nums' }, displayText(row.collectCode)),
|
||||
React.createElement(
|
||||
'td',
|
||||
null,
|
||||
React.createElement(StatusTag, {
|
||||
label: row.publishStatus,
|
||||
tone: 'green',
|
||||
title: row.publishStatus,
|
||||
}),
|
||||
),
|
||||
React.createElement(
|
||||
'td',
|
||||
null,
|
||||
React.createElement(StatusTag, {
|
||||
label: auditLabel === '已驳回' ? '审批驳回' : auditLabel,
|
||||
tone: auditStatusTone(row.auditStatus),
|
||||
title: auditLabel,
|
||||
}),
|
||||
),
|
||||
React.createElement('td', { className: 'tabular-nums' }, displayText(row.pickupDate)),
|
||||
React.createElement('td', { className: 'vpr-td--num tabular-nums' }, String(row.vehicleCount || 0)),
|
||||
React.createElement('td', { className: 'vpr-money vpr-td--num tabular-nums' }, fmtMoneyYuan(row.receivableTotal)),
|
||||
React.createElement('td', { className: 'vpr-money vpr-td--num tabular-nums' }, fmtMoneyYuan(row.receivedTotal)),
|
||||
React.createElement('td', { className: 'tabular-nums' }, displayText(row.chargeTime)),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
: React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-contract-view-empty vpr-page-empty' },
|
||||
React.createElement('p', { className: 'vpr-page-empty__title' }, '暂无已发布记录'),
|
||||
React.createElement('p', { className: 'vpr-page-empty__desc' }, '办理提交后将在此展示已生成的提车应收款单据。'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'section',
|
||||
{ className: 'vpr-contract-view-section' },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-contract-view-section__head' },
|
||||
React.createElement('h2', { className: 'vpr-contract-view-section__title' }, '未发布提车应收款'),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'vpr-contract-view-section__count tabular-nums' },
|
||||
summary.pendingCount + ' 辆',
|
||||
),
|
||||
),
|
||||
pendingVehicles.length
|
||||
? React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-pickup-table-wrap vpr-page-table-wrap' },
|
||||
React.createElement(
|
||||
'table',
|
||||
{ className: 'vpr-pickup-native-table vpr-contract-view-table' },
|
||||
React.createElement(
|
||||
'thead',
|
||||
null,
|
||||
React.createElement(
|
||||
'tr',
|
||||
null,
|
||||
React.createElement('th', { className: 'vpr-th--num' }, '序号'),
|
||||
React.createElement('th', null, '发布状态'),
|
||||
React.createElement('th', null, '品牌'),
|
||||
React.createElement('th', null, '型号'),
|
||||
React.createElement('th', null, '车牌号'),
|
||||
React.createElement('th', null, '说明'),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
'tbody',
|
||||
null,
|
||||
pendingVehicles.map(function (vehicle) {
|
||||
return React.createElement(
|
||||
'tr',
|
||||
{
|
||||
key: (vehicle.seq || '') + '-' + (vehicle.plateNo || '') + '-' + (vehicle.model || ''),
|
||||
className: 'vpr-page-table-row',
|
||||
},
|
||||
React.createElement('td', { className: 'vpr-td--num tabular-nums' }, vehicle.seq),
|
||||
React.createElement(
|
||||
'td',
|
||||
null,
|
||||
React.createElement(StatusTag, {
|
||||
label: vehicle.publishStatus,
|
||||
tone: 'amber',
|
||||
title: vehicle.publishStatus,
|
||||
}),
|
||||
),
|
||||
React.createElement('td', null, displayText(vehicle.brand)),
|
||||
React.createElement('td', null, displayText(vehicle.model)),
|
||||
React.createElement('td', { className: 'tabular-nums' }, displayText(vehicle.plateNo)),
|
||||
React.createElement('td', { className: 'vpr-page-table-muted' }, '尚未办理,可发起提车应收款办理'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
: React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-contract-view-empty vpr-page-empty vpr-page-empty--success' },
|
||||
React.createElement('p', { className: 'vpr-page-empty__title' }, '本合同车辆均已发布'),
|
||||
React.createElement('p', { className: 'vpr-page-empty__desc' }, '所有车辆均已生成提车应收款,无需再次办理。'),
|
||||
),
|
||||
),
|
||||
showHandle
|
||||
? React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-contract-view-footer-cta' },
|
||||
React.createElement(
|
||||
Button,
|
||||
{
|
||||
type: 'primary',
|
||||
size: 'large',
|
||||
className: 'vm-btn-primary',
|
||||
onClick: handleGoHandle,
|
||||
},
|
||||
'办理 ',
|
||||
summary.pendingCount,
|
||||
' 辆未发布车辆',
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
};
|
||||
|
||||
export default Component;
|
||||
@@ -32,23 +32,12 @@ var ONEOS_ANT_TABLE_GLOBAL_FIX = [
|
||||
|
||||
var VPR_LIST_CHROME_OFFSET = 420;
|
||||
|
||||
var VPR_MAIN_TABLE_COL_RATIOS = [13, 7, 9.5, 6, 6, 6, 7.5, 6, 6, 6, 7.5, 6];
|
||||
var VPR_MAIN_TABLE_COL_RATIO_SUM = VPR_MAIN_TABLE_COL_RATIOS.reduce(function (sum, ratio) {
|
||||
return sum + ratio;
|
||||
}, 0);
|
||||
/** 主表列宽(px);scroll.x 为各列之和,表头/表体横向同步滚动 */
|
||||
var VPR_MAIN_TABLE_COLUMN_WIDTHS = [240, 120, 160, 100, 100, 100, 120, 100, 100, 100, 120, 140];
|
||||
|
||||
function buildVprMainTableColumnWidths(containerWidth) {
|
||||
var safeWidth = Math.max(960, Math.floor(containerWidth) || 960);
|
||||
var widths = VPR_MAIN_TABLE_COL_RATIOS.map(function (ratio) {
|
||||
return Math.max(52, Math.floor(safeWidth * ratio / VPR_MAIN_TABLE_COL_RATIO_SUM));
|
||||
});
|
||||
var sum = widths.reduce(function (acc, width) {
|
||||
return acc + width;
|
||||
}, 0);
|
||||
var delta = safeWidth - sum;
|
||||
if (delta) widths[widths.length - 1] += delta;
|
||||
return { widths: widths, scrollX: safeWidth };
|
||||
}
|
||||
var VPR_MAIN_TABLE_SCROLL_X = VPR_MAIN_TABLE_COLUMN_WIDTHS.reduce(function (sum, width) {
|
||||
return sum + width;
|
||||
}, 0);
|
||||
|
||||
var KPI_ICONS = {
|
||||
all: React.createElement(
|
||||
@@ -68,7 +57,7 @@ var KPI_ICONS = {
|
||||
React.createElement('path', { d: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z' }),
|
||||
React.createElement('path', { d: 'M14 2v4a2 2 0 0 0 2 2h4' }),
|
||||
),
|
||||
pending: React.createElement(
|
||||
notPickedUp: React.createElement(
|
||||
'svg',
|
||||
{
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
@@ -85,7 +74,7 @@ var KPI_ICONS = {
|
||||
React.createElement('circle', { cx: 12, cy: 12, r: 10 }),
|
||||
React.createElement('polyline', { points: '12 6 12 12 16 14' }),
|
||||
),
|
||||
inReview: React.createElement(
|
||||
partialPickedUp: React.createElement(
|
||||
'svg',
|
||||
{
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
@@ -102,7 +91,7 @@ var KPI_ICONS = {
|
||||
React.createElement('path', { d: 'M21 12a9 9 0 1 1-3-6.7' }),
|
||||
React.createElement('polyline', { points: '21 3 21 9 15 9' }),
|
||||
),
|
||||
approved: React.createElement(
|
||||
completed: React.createElement(
|
||||
'svg',
|
||||
{
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
@@ -118,7 +107,7 @@ var KPI_ICONS = {
|
||||
},
|
||||
React.createElement('path', { d: 'M20 6 9 17l-5-5' }),
|
||||
),
|
||||
rejected: React.createElement(
|
||||
refunded: React.createElement(
|
||||
'svg',
|
||||
{
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
@@ -132,28 +121,8 @@ var KPI_ICONS = {
|
||||
strokeLinejoin: 'round',
|
||||
'aria-hidden': true,
|
||||
},
|
||||
React.createElement('circle', { cx: 12, cy: 12, r: 10 }),
|
||||
React.createElement('path', { d: 'm15 9-6 6' }),
|
||||
React.createElement('path', { d: 'm9 9 6 6' }),
|
||||
),
|
||||
generated: React.createElement(
|
||||
'svg',
|
||||
{
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
width: 18,
|
||||
height: 18,
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
'aria-hidden': true,
|
||||
},
|
||||
React.createElement('path', { d: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z' }),
|
||||
React.createElement('polyline', { points: '14 2 14 8 20 8' }),
|
||||
React.createElement('line', { x1: 12, x2: 12, y1: 18, y2: 12 }),
|
||||
React.createElement('line', { x1: 9, x2: 15, y1: 15, y2: 15 }),
|
||||
React.createElement('path', { d: 'M3 7v6h6' }),
|
||||
React.createElement('path', { d: 'M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13' }),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -262,6 +231,7 @@ const Component = function (props) {
|
||||
contractCode: undefined,
|
||||
projectName: undefined,
|
||||
customerName: undefined,
|
||||
plateNo: undefined,
|
||||
businessDept: undefined,
|
||||
businessPerson: undefined,
|
||||
});
|
||||
@@ -272,6 +242,7 @@ const Component = function (props) {
|
||||
contractCode: undefined,
|
||||
projectName: undefined,
|
||||
customerName: undefined,
|
||||
plateNo: undefined,
|
||||
businessDept: undefined,
|
||||
businessPerson: undefined,
|
||||
});
|
||||
@@ -291,31 +262,9 @@ const Component = function (props) {
|
||||
[props && props.refreshToken],
|
||||
);
|
||||
|
||||
var tableColumnWidths = VPR_MAIN_TABLE_COLUMN_WIDTHS;
|
||||
var tableScrollX = VPR_MAIN_TABLE_SCROLL_X;
|
||||
var tableWrapRef = React.useRef(null);
|
||||
var tableLayoutState = React.useState(function () {
|
||||
return buildVprMainTableColumnWidths(1200);
|
||||
});
|
||||
var tableColumnWidths = tableLayoutState[0].widths;
|
||||
var tableScrollX = tableLayoutState[0].scrollX;
|
||||
var setTableLayout = tableLayoutState[1];
|
||||
|
||||
useEffect(function () {
|
||||
var el = tableWrapRef.current;
|
||||
if (!el || typeof ResizeObserver === 'undefined') return undefined;
|
||||
var syncWidth = function () {
|
||||
var nextWidth = el.clientWidth;
|
||||
if (!nextWidth) return;
|
||||
setTableLayout(buildVprMainTableColumnWidths(nextWidth));
|
||||
};
|
||||
syncWidth();
|
||||
var observer = new ResizeObserver(function () {
|
||||
syncWidth();
|
||||
});
|
||||
observer.observe(el);
|
||||
return function () {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
var contractPopoverKeyState = useState(null);
|
||||
var contractPopoverKey = contractPopoverKeyState[0];
|
||||
@@ -346,8 +295,8 @@ const Component = function (props) {
|
||||
}, [mainList, appliedFilters]);
|
||||
|
||||
var kpiCounts = useMemo(function () {
|
||||
return countKpiFromList(filteredByQuery, kpiTab);
|
||||
}, [filteredByQuery, kpiTab]);
|
||||
return countKpiFromList(filteredByQuery);
|
||||
}, [filteredByQuery]);
|
||||
|
||||
var filteredData = useMemo(function () {
|
||||
return filterByKpiTab(filteredByQuery, kpiTab);
|
||||
@@ -364,6 +313,45 @@ const Component = function (props) {
|
||||
.slice(start, start + pageSize);
|
||||
}, [filteredData, safePage, pageSize]);
|
||||
|
||||
useEffect(
|
||||
function () {
|
||||
var root = tableWrapRef.current;
|
||||
if (!root) return undefined;
|
||||
var body = root.querySelector('.ant-table-body');
|
||||
var header = root.querySelector('.ant-table-header');
|
||||
if (!body || !header) return undefined;
|
||||
|
||||
function applyLayoutSync() {
|
||||
var scrollbarWidth = Math.max(0, body.offsetWidth - body.clientWidth);
|
||||
header.style.paddingRight = scrollbarWidth ? scrollbarWidth + 'px' : '';
|
||||
header.style.boxSizing = 'border-box';
|
||||
header.scrollLeft = body.scrollLeft;
|
||||
}
|
||||
|
||||
function onBodyScroll() {
|
||||
header.scrollLeft = body.scrollLeft;
|
||||
}
|
||||
|
||||
applyLayoutSync();
|
||||
body.addEventListener('scroll', onBodyScroll, { passive: true });
|
||||
var resizeObserver =
|
||||
typeof ResizeObserver !== 'undefined'
|
||||
? new ResizeObserver(function () {
|
||||
applyLayoutSync();
|
||||
})
|
||||
: null;
|
||||
if (resizeObserver) resizeObserver.observe(body);
|
||||
|
||||
return function () {
|
||||
body.removeEventListener('scroll', onBodyScroll);
|
||||
if (resizeObserver) resizeObserver.disconnect();
|
||||
header.style.paddingRight = '';
|
||||
header.style.boxSizing = '';
|
||||
};
|
||||
},
|
||||
[pagedList, safePage, pageSize, tableScrollX],
|
||||
);
|
||||
|
||||
function handleQuery() {
|
||||
setAppliedFilters(Object.assign({}, filters));
|
||||
setPage(1);
|
||||
@@ -374,6 +362,7 @@ const Component = function (props) {
|
||||
contractCode: undefined,
|
||||
projectName: undefined,
|
||||
customerName: undefined,
|
||||
plateNo: undefined,
|
||||
businessDept: undefined,
|
||||
businessPerson: undefined,
|
||||
};
|
||||
@@ -388,6 +377,11 @@ const Component = function (props) {
|
||||
else message.info('跳转至办理页(原型)');
|
||||
}
|
||||
|
||||
function goContractView(record) {
|
||||
if (props && typeof props.onGoContractView === 'function') props.onGoContractView(record);
|
||||
else message.info('查看合同提车应收款(原型):' + (record.contractCode || ''));
|
||||
}
|
||||
|
||||
function goRefund(record) {
|
||||
message.success(
|
||||
'已发起退款流程(原型):' + (record.contractCode || '') + ' · 抄送财务复核',
|
||||
@@ -422,7 +416,49 @@ const Component = function (props) {
|
||||
if (!vehicle) return '';
|
||||
return [String(vehicle.brand || '').trim(), String(vehicle.model || '').trim()]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
.join('::');
|
||||
}
|
||||
|
||||
var PLATE_ACTUAL_DELIVERY_LABEL = '以实际交付为准';
|
||||
|
||||
function formatBrandModelLabel(brand, model) {
|
||||
return [brand, model]
|
||||
.filter(function (part) {
|
||||
return part && String(part).trim() && part !== '-';
|
||||
})
|
||||
.join(' · ') || '—';
|
||||
}
|
||||
|
||||
function findLeaseOrderVehicle(parent, deliveryVehicle) {
|
||||
if (!parent || !deliveryVehicle) return null;
|
||||
return (parent.leaseOrderVehicles || []).find(function (lv) {
|
||||
if (deliveryVehicle.seq != null && lv.seq === deliveryVehicle.seq) return true;
|
||||
var deliveryPlate = String(deliveryVehicle.plateNo || '').trim();
|
||||
var leasePlate = String(lv.plateNo || '').trim();
|
||||
return deliveryPlate && leasePlate && deliveryPlate === leasePlate;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function isLeaseSpecificVehicle(leaseVehicle) {
|
||||
if (!leaseVehicle) return false;
|
||||
if (leaseVehicle.plateMode === 'specific') return true;
|
||||
if (leaseVehicle.plateMode === 'actual') return false;
|
||||
var leasePlate = String(leaseVehicle.plateNo || '').trim();
|
||||
return Boolean(leasePlate && leasePlate !== PLATE_ACTUAL_DELIVERY_LABEL && leasePlate !== '-');
|
||||
}
|
||||
|
||||
function resolveSubTablePlateLabel(deliveryVehicle, parent) {
|
||||
if (!deliveryVehicle) return '—';
|
||||
var leaseVehicle = findLeaseOrderVehicle(parent, deliveryVehicle);
|
||||
var deliveryPlate = String(deliveryVehicle.plateNo || '').trim();
|
||||
if (isLeaseSpecificVehicle(leaseVehicle)) {
|
||||
var contractPlate = leaseVehicle ? String(leaseVehicle.plateNo || '').trim() : '';
|
||||
return contractPlate || deliveryPlate || '—';
|
||||
}
|
||||
if (deliveryPlate && deliveryPlate !== PLATE_ACTUAL_DELIVERY_LABEL) {
|
||||
return deliveryPlate;
|
||||
}
|
||||
return PLATE_ACTUAL_DELIVERY_LABEL;
|
||||
}
|
||||
|
||||
function buildPickupSubModelGroupRows(parent) {
|
||||
@@ -434,9 +470,12 @@ const Component = function (props) {
|
||||
batch: batch,
|
||||
parent: parent,
|
||||
modelGroup: null,
|
||||
vehicle: null,
|
||||
batchSeq: batch.seq,
|
||||
isFirstInBatch: true,
|
||||
batchRowSpan: 1,
|
||||
isFirstInModelGroup: true,
|
||||
modelGroupRowSpan: 1,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -455,14 +494,24 @@ const Component = function (props) {
|
||||
}
|
||||
groupMap[key].vehicles.push(vehicle);
|
||||
});
|
||||
groupOrder.forEach(function (key, idx) {
|
||||
rows.push({
|
||||
batch: batch,
|
||||
parent: parent,
|
||||
modelGroup: groupMap[key],
|
||||
batchSeq: batch.seq,
|
||||
isFirstInBatch: idx === 0,
|
||||
batchRowSpan: groupOrder.length,
|
||||
var batchVehicleCount = vehicles.length;
|
||||
var vehicleRowIndex = 0;
|
||||
groupOrder.forEach(function (key) {
|
||||
var modelGroup = groupMap[key];
|
||||
var groupVehicles = modelGroup.vehicles;
|
||||
groupVehicles.forEach(function (vehicle, vehicleIndex) {
|
||||
rows.push({
|
||||
batch: batch,
|
||||
parent: parent,
|
||||
modelGroup: modelGroup,
|
||||
vehicle: vehicle,
|
||||
batchSeq: batch.seq,
|
||||
isFirstInBatch: vehicleRowIndex === 0,
|
||||
batchRowSpan: batchVehicleCount,
|
||||
isFirstInModelGroup: vehicleIndex === 0,
|
||||
modelGroupRowSpan: groupVehicles.length,
|
||||
});
|
||||
vehicleRowIndex += 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -471,34 +520,25 @@ const Component = function (props) {
|
||||
|
||||
function renderModelGroupCell(modelGroup) {
|
||||
if (!modelGroup) return '—';
|
||||
var label = [modelGroup.brand, modelGroup.model].filter(Boolean).join(' ');
|
||||
var plates = (modelGroup.vehicles || [])
|
||||
.map(function (vehicle) {
|
||||
return String(vehicle.plateNo || '').trim();
|
||||
})
|
||||
.filter(Boolean);
|
||||
var label = formatBrandModelLabel(modelGroup.brand, modelGroup.model);
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-pickup-subtable__model-group' },
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-pickup-subtable__model-group-name', title: label || '—' },
|
||||
label || '—',
|
||||
),
|
||||
plates.length
|
||||
? React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: 'vpr-pickup-subtable__model-group-plates tabular-nums',
|
||||
title: plates.join('、'),
|
||||
},
|
||||
plates.join('、'),
|
||||
)
|
||||
: React.createElement(
|
||||
'div',
|
||||
{ className: 'vpr-pickup-subtable__model-group-plates is-empty' },
|
||||
'—',
|
||||
),
|
||||
{ className: 'vpr-pickup-subtable__model-group-name', title: label },
|
||||
label,
|
||||
);
|
||||
}
|
||||
|
||||
function renderPlateNoCell(vehicle, parent) {
|
||||
var label = resolveSubTablePlateLabel(vehicle, parent);
|
||||
var isPending = label === PLATE_ACTUAL_DELIVERY_LABEL;
|
||||
return React.createElement(
|
||||
'span',
|
||||
{
|
||||
className: 'vpr-pickup-subtable__plate-no tabular-nums'
|
||||
+ (isPending ? ' vpr-pickup-subtable__plate-no--pending' : ''),
|
||||
title: label,
|
||||
},
|
||||
label,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -592,60 +632,55 @@ const Component = function (props) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleProjectInfoClick(record, e) {
|
||||
if (e) e.stopPropagation();
|
||||
if (canShowHandle(record)) {
|
||||
goHandle(record);
|
||||
return;
|
||||
}
|
||||
message.info('当前合同暂无可办理的提车应收款');
|
||||
}
|
||||
|
||||
function renderProjectInfoCell(_, record) {
|
||||
var clickable = canShowHandle(record);
|
||||
var hasSubRows = Boolean(record && record.children && record.children.length);
|
||||
var stackContent = React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-stack' },
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'primary', title: displayText(record.customerName) },
|
||||
displayText(record.customerName),
|
||||
),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'code', title: displayText(record.contractCode) },
|
||||
displayText(record.contractCode),
|
||||
),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'sub', title: displayText(record.projectName) },
|
||||
displayText(record.projectName),
|
||||
),
|
||||
);
|
||||
|
||||
return React.createElement(
|
||||
'div',
|
||||
{ className: 'lc-project-info-merged-cell' },
|
||||
hasSubRows ? renderRowExpandIcon(record) : React.createElement('span', { className: 'vpr-row-expand-spacer', 'aria-hidden': true }),
|
||||
React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: 'lc-project-info-cell' + (clickable ? ' vpr-project-info-cell--clickable' : ''),
|
||||
role: clickable ? 'button' : undefined,
|
||||
tabIndex: clickable ? 0 : undefined,
|
||||
title: clickable ? '点击进入办理' : displayText(record.customerName),
|
||||
onClick: function (e) {
|
||||
handleProjectInfoClick(record, e);
|
||||
},
|
||||
onKeyDown: clickable
|
||||
? function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
handleProjectInfoClick(record, e);
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
React.createElement(
|
||||
'div',
|
||||
{ className: 'vm-stack' },
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'primary', title: displayText(record.customerName) },
|
||||
displayText(record.customerName),
|
||||
),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'code', title: displayText(record.contractCode) },
|
||||
displayText(record.contractCode),
|
||||
),
|
||||
React.createElement(
|
||||
'span',
|
||||
{ className: 'sub', title: displayText(record.projectName) },
|
||||
displayText(record.projectName),
|
||||
),
|
||||
),
|
||||
{ className: 'lc-project-info-cell' },
|
||||
clickable
|
||||
? React.createElement(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
className: 'lc-project-info-btn',
|
||||
title: '点击进入办理',
|
||||
onClick: function (e) {
|
||||
e.stopPropagation();
|
||||
goHandle(record);
|
||||
},
|
||||
},
|
||||
stackContent,
|
||||
)
|
||||
: React.createElement(
|
||||
'div',
|
||||
{ className: 'lc-project-info-cell__static' },
|
||||
stackContent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1124,35 +1159,22 @@ const Component = function (props) {
|
||||
|
||||
function renderMainActionCell(_, record) {
|
||||
var showHandle = canShowHandle(record);
|
||||
if (!showHandle) {
|
||||
return React.createElement('span', { className: 'vm-operation-actions vm-operation-actions--empty' }, '—');
|
||||
}
|
||||
return React.createElement(
|
||||
'div',
|
||||
{
|
||||
className: 'vm-row-actions vm-operation-actions',
|
||||
className: 'vm-row-actions vm-operation-actions vpr-main-action-cell',
|
||||
onClick: function (e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
},
|
||||
React.createElement(
|
||||
'button',
|
||||
{
|
||||
type: 'button',
|
||||
className: 'vm-op-action',
|
||||
'aria-label': '办理',
|
||||
onClick: function (e) {
|
||||
e.stopPropagation();
|
||||
goHandle(record);
|
||||
},
|
||||
},
|
||||
React.createElement(ClipboardCheck, {
|
||||
className: 'vm-op-action__icon',
|
||||
'aria-hidden': true,
|
||||
strokeWidth: 1.75,
|
||||
}),
|
||||
React.createElement('span', { className: 'vm-op-action__label' }, '办理'),
|
||||
),
|
||||
renderPrimaryOpButton('查看', Eye, function () {
|
||||
goContractView(record);
|
||||
}),
|
||||
showHandle
|
||||
? renderPrimaryOpButton('办理', ClipboardCheck, function () {
|
||||
goHandle(record);
|
||||
})
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1194,7 +1216,8 @@ const Component = function (props) {
|
||||
React.createElement('th', { style: { width: 88 } }, '单据状态'),
|
||||
React.createElement('th', { style: { width: 108 } }, '交付时间'),
|
||||
React.createElement('th', { style: { width: 120 } }, '交付地点'),
|
||||
React.createElement('th', { style: { width: 180 } }, '品牌型号'),
|
||||
React.createElement('th', { style: { width: 160 } }, '品牌型号'),
|
||||
React.createElement('th', { style: { width: 120 } }, '车牌号'),
|
||||
React.createElement('th', { style: { width: 64 } }, '数量'),
|
||||
React.createElement('th', { style: { width: 104 } }, '合计应收'),
|
||||
React.createElement('th', { style: { width: 104 } }, '合计实收'),
|
||||
@@ -1207,11 +1230,14 @@ const Component = function (props) {
|
||||
modelGroupRows.map(function (row) {
|
||||
var batch = row.batch;
|
||||
var modelGroup = row.modelGroup;
|
||||
var sampleVehicle = modelGroup && modelGroup.vehicles.length ? modelGroup.vehicles[0] : null;
|
||||
var vehicle = row.vehicle;
|
||||
var sampleVehicle = vehicle || (modelGroup && modelGroup.vehicles.length ? modelGroup.vehicles[0] : null);
|
||||
var auditStatus = normalizeAuditStatus(batch.auditStatus);
|
||||
var auditLabel = auditStatus === '已驳回' ? '审批驳回' : (auditStatus || '—');
|
||||
var batchTotals = getBatchAmountTotals(batch);
|
||||
var rowKey = (parent.contractCode || '') + '-' + batch.seq + '-' + (modelGroup ? modelGroup.key : 'empty');
|
||||
var rowKey = (parent.contractCode || '') + '-' + batch.seq + '-'
|
||||
+ (vehicle ? (vehicle.seq != null ? vehicle.seq : buildVehicleModelKey(vehicle)) : 'empty')
|
||||
+ '-' + (row.isFirstInModelGroup ? '0' : '1');
|
||||
var cells = [];
|
||||
|
||||
if (row.isFirstInBatch) {
|
||||
@@ -1265,8 +1291,26 @@ const Component = function (props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (row.isFirstInModelGroup) {
|
||||
cells.push(
|
||||
React.createElement(
|
||||
'td',
|
||||
{
|
||||
key: 'vehicleInfo',
|
||||
rowSpan: row.modelGroupRowSpan,
|
||||
className: 'vpr-pickup-subtable__model-group-cell',
|
||||
},
|
||||
renderModelGroupCell(modelGroup),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
cells.push(
|
||||
React.createElement('td', { key: 'vehicleInfo' }, renderModelGroupCell(modelGroup)),
|
||||
React.createElement(
|
||||
'td',
|
||||
{ key: 'plateNo', className: 'vpr-pickup-subtable__plate-cell' },
|
||||
vehicle ? renderPlateNoCell(vehicle, parent) : '—',
|
||||
),
|
||||
);
|
||||
|
||||
if (row.isFirstInBatch) {
|
||||
@@ -1361,6 +1405,7 @@ const Component = function (props) {
|
||||
title: '项目信息',
|
||||
key: 'projectInfo',
|
||||
width: colWidths[0],
|
||||
fixed: 'left',
|
||||
ellipsis: true,
|
||||
className: 'lc-project-info-col vpr-col-project',
|
||||
onHeaderCell: function () {
|
||||
@@ -1505,12 +1550,13 @@ const Component = function (props) {
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: colWidths[11],
|
||||
fixed: 'right',
|
||||
className: 'vpr-col-action',
|
||||
render: renderMainActionCell,
|
||||
},
|
||||
];
|
||||
},
|
||||
[expandedRowKeys, contractPopoverKey, mainList, tableColumnWidths],
|
||||
[expandedRowKeys, contractPopoverKey, mainList],
|
||||
);
|
||||
|
||||
var filterItems = [
|
||||
@@ -1571,6 +1617,25 @@ const Component = function (props) {
|
||||
},
|
||||
}),
|
||||
),
|
||||
React.createElement(
|
||||
'label',
|
||||
{ key: 'plateNo', className: 'vm-filter-field' },
|
||||
React.createElement('span', null, '车牌号'),
|
||||
React.createElement(Select, {
|
||||
value: filters.plateNo,
|
||||
options: filterOptions.plateNo,
|
||||
allowClear: true,
|
||||
placeholder: '请选择或输入车牌号',
|
||||
showSearch: true,
|
||||
filterOption: filterOption,
|
||||
className: 'lc-filter-select',
|
||||
onChange: function (v) {
|
||||
setFilters(function (p) {
|
||||
return Object.assign({}, p, { plateNo: v });
|
||||
});
|
||||
},
|
||||
}),
|
||||
),
|
||||
React.createElement(
|
||||
'label',
|
||||
{ key: 'businessDept', className: 'vm-filter-field' },
|
||||
@@ -1580,6 +1645,8 @@ const Component = function (props) {
|
||||
options: filterOptions.businessDept,
|
||||
allowClear: true,
|
||||
placeholder: '请选择业务部门',
|
||||
showSearch: true,
|
||||
filterOption: filterOption,
|
||||
className: 'lc-filter-select',
|
||||
onChange: function (v) {
|
||||
setFilters(function (p) {
|
||||
@@ -1672,7 +1739,7 @@ const Component = function (props) {
|
||||
{
|
||||
className: 'vm-kpi-row',
|
||||
role: 'group',
|
||||
'aria-label': '提车收款单统计',
|
||||
'aria-label': '提车应收款统计',
|
||||
'data-annotation-id': 'vpr-kpi',
|
||||
},
|
||||
PICKUP_KPI_CARDS.map(function (card) {
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
"fingerprint": "vpr-filter",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "合同编码、项目名称、客户名称、业务部门、业务负责人筛选。",
|
||||
"annotationText": "默认首行 4 项;超过 1 行时其余项收入「更多筛选」。支持重置与查询,交互对齐租赁合同列表页。",
|
||||
"aiPrompt": "合同编码、项目名称、客户名称、车牌号、业务部门、业务负责人筛选。",
|
||||
"annotationText": "默认首行 4 项:合同编码、项目名称、客户名称、车牌号(可输入模糊搜索);业务部门、业务负责人收入「更多筛选」。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#2563eb",
|
||||
"images": [],
|
||||
@@ -28,15 +28,15 @@
|
||||
{
|
||||
"id": "vpr-kpi",
|
||||
"index": 2,
|
||||
"title": "提车收款单 KPI",
|
||||
"title": "提车应收款 KPI",
|
||||
"pageId": "list",
|
||||
"locator": {
|
||||
"selectors": ["[data-annotation-id=\"vpr-kpi\"]"],
|
||||
"fingerprint": "vpr-kpi",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "全部 / 待审批 / 审批中 / 审批通过 / 已驳回统计卡片。",
|
||||
"annotationText": "点击 KPI 卡片筛选子表提车收款单;计数基于当前筛选结果内的明细条数。",
|
||||
"aiPrompt": "全部 / 未提车 / 部分提车 / 已完成 / 已退款统计卡片。",
|
||||
"annotationText": "点击 KPI 卡片按合同提车与收款闭环进度筛选列表;计数为当前筛选结果内的合同条数。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#0f766e",
|
||||
"images": [],
|
||||
@@ -90,7 +90,7 @@
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "主表按合同聚合;展开子表为提车收款单明细。",
|
||||
"annotationText": "主表操作列外显「办理」;租车数等于已提车数时不显示办理。退款仅在子表操作。展开子表按提车批次展示车辆清单,子表操作外显查看、关联收款记录,合同审批驳回时可退款。",
|
||||
"annotationText": "主表操作列外显「查看」「办理」:查看可浏览本合同全部已发布与未发布提车应收款;办理仅针对尚未操作的车辆。租车数等于已提车数时不显示办理。退款仅在子表操作。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#32a06e",
|
||||
"images": [],
|
||||
@@ -107,8 +107,8 @@
|
||||
"fingerprint": "vpr-collect-topbar",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "办理提车收款单的返回与标题区。",
|
||||
"annotationText": "返回列表;展示合同编码、项目名称;有未保存修改时显示「未保存」徽章。",
|
||||
"aiPrompt": "办理页顶栏,仅返回列表。",
|
||||
"annotationText": "仅保留「返回列表」;与表单页一致。未保存修改时底栏显示「未提交」。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#334155",
|
||||
"images": [],
|
||||
@@ -118,15 +118,15 @@
|
||||
{
|
||||
"id": "vpr-collect-project",
|
||||
"index": 7,
|
||||
"title": "项目信息",
|
||||
"title": "合同信息",
|
||||
"pageId": "collect",
|
||||
"locator": {
|
||||
"selectors": ["[data-annotation-id=\"vpr-collect-project\"]"],
|
||||
"fingerprint": "vpr-collect-project",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "合同与项目只读信息卡片。",
|
||||
"annotationText": "从办理上下文带出合同编码、客户、付款方式/周期、业务部门等;默认展开,可收起。",
|
||||
"aiPrompt": "合同只读信息卡片,字段与租赁合同模块关联。",
|
||||
"annotationText": "参照车辆资产详情页 hero:客户名称主标题,合同编码与项目名称;右侧签约公司、业务部门·负责人、合同类型;下方 stats 展示付款方式、周期、氢费等。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#64748b",
|
||||
"images": [],
|
||||
@@ -136,15 +136,15 @@
|
||||
{
|
||||
"id": "vpr-collect-form",
|
||||
"index": 8,
|
||||
"title": "提车日期与计费规则",
|
||||
"title": "提车应收款表单",
|
||||
"pageId": "collect",
|
||||
"locator": {
|
||||
"selectors": ["[data-annotation-id=\"vpr-collect-form\"]"],
|
||||
"fingerprint": "vpr-collect-form",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "提车日期、计费规则说明与应收付款节点。",
|
||||
"annotationText": "提车日期必填,变更后重算各车应收;展示提车前 2 日内应付与当月 25 日应付节点摘要。",
|
||||
"aiPrompt": "交付信息、计费规则与租赁订单明细表。",
|
||||
"annotationText": "交付信息提示「根据交付时间,自动计算提车应收款金额」;明细表含租赁车辆数、未交车辆数及租金/保证金/服务项/应收总额列。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#2563eb",
|
||||
"images": [],
|
||||
@@ -161,8 +161,8 @@
|
||||
"fingerprint": "vpr-collect-kpi",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "应收款总额、提车前应付、应开票总额。",
|
||||
"annotationText": "基于已勾选车辆汇总;前两项卡片可点击查看明细 Popover。",
|
||||
"aiPrompt": "已选车辆数、应收/租金/保证金/服务项总额。",
|
||||
"annotationText": "五列 vm-detail-stats:已选车辆数、应收总额、租金总额、保证金总额、服务项总额;填写交车数与交付时间后联动更新。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#0f766e",
|
||||
"images": [],
|
||||
@@ -179,8 +179,8 @@
|
||||
"fingerprint": "vpr-collect-table",
|
||||
"path": []
|
||||
},
|
||||
"aiPrompt": "勾选车辆、应收/实收金额与氢费预付款。",
|
||||
"annotationText": "点击行切换勾选;已完成车辆不可再选。支持全选/取消全选;实收由收款记录导入反写。",
|
||||
"aiPrompt": "租赁车辆数、未交车辆数、交车数及租金/保证金/服务项/应收总额。",
|
||||
"annotationText": "按品牌型号汇总;车牌号与列表子表规则一致;未交车辆数可点击填入;金额列按交付时间与本次交车数自动拆分。",
|
||||
"hasMarkdown": true,
|
||||
"color": "#32a06e",
|
||||
"images": [],
|
||||
@@ -208,17 +208,17 @@
|
||||
]
|
||||
},
|
||||
"markdownMap": {
|
||||
"vpr-filter": "# 筛选条件\n\n合同编码、项目名称、客户名称、业务部门、业务负责人等。\n\n| 操作 | 行为 |\n|---|---|\n| 查询 | 应用筛选并重算 KPI |\n| 重置 | 清空条件 |\n| 更多筛选 | 展开其余筛选项 |\n\n交互对齐租赁合同列表页。",
|
||||
"vpr-kpi": "# 提车收款单 KPI\n\n全部 / 待审批 / 审批中 / 审批通过 / 已驳回。\n\n点击卡片筛选子表提车收款单明细;计数基于当前筛选结果内的条数。",
|
||||
"vpr-filter": "# 筛选条件\n\n| 字段 | 说明 |\n|---|---|\n| 合同编码 | 可输入模糊搜索的下拉选择 |\n| 项目名称 | 可输入模糊搜索的下拉选择 |\n| 客户名称 | 可输入模糊搜索的下拉选择 |\n| 车牌号 | 首行第 4 项;可输入模糊搜索,匹配租赁订单或已提车明细中的车牌 |\n| 业务部门 | 「更多筛选」中展示 |\n| 业务负责人 | 「更多筛选」中展示 |\n\n| 操作 | 行为 |\n|---|---|\n| 查询 | 应用筛选并重算 KPI |\n| 重置 | 清空条件 |\n| 更多筛选 | 展开业务部门、业务负责人 |\n\n交互对齐租赁合同列表页。",
|
||||
"vpr-kpi": "# 提车应收款 KPI\n\n| 卡片 | 说明 |\n|---|---|\n| 全部 | 显示全部租赁合同对应的提车应收款信息 |\n| 未提车 | 没有任何车辆完成「提车应收款 + 收款记录关联」闭环 |\n| 部分提车 | 仅部分车辆完成闭环 |\n| 已完成 | 全部车辆均完成闭环 |\n| 已退款 | 合同审批终止且已完成退款与付款记录关联 |\n\n点击卡片筛选主表合同;计数基于当前筛选结果内的合同条数。",
|
||||
"vpr-col-pickup": "# 收款情况\n\n展示 **租车数** 与 **已提车**,交互对齐租赁合同列表。\n\n| 操作 | 行为 |\n|---|---|\n| 点击 **租车数** | Popover 展示品牌、型号及对应车辆数汇总表 |\n| 点击 **已提车** | 展开/收起本合同提车应收款子表(已提车 ≥ 0 均可点击) |\n\n## 提车应收款生成逻辑\n\n### A. 自动生成\n\n1. 租赁合同 **提交审批** 时立即生成主表任务。\n2. 合同标注 **交付时间** 时,在交付日 **前 2 天** 自动生成应收数据。\n\n### B. 手动办理\n\n未约定交付时间时,点击操作列 **办理**,勾选车辆并设置交付信息。\n\n## 计算规则\n\n以 **车辆交付时间** 为依据;当月 ≤15 日与 >15 日应付节点不同。详见目录「收款情况与生成规则」。",
|
||||
"vpr-toolbar": "# 列表工具栏\n\n| 按钮 | 说明 |\n|---|---|\n| 导出 | 导出当前筛选结果 |\n\n总条数由底部分页展示,不在工具栏重复计数。",
|
||||
"vpr-table": "# 主从嵌套列表\n\n**主表**:按合同聚合,展示收款情况、应收/实收金额等;操作列外显 **办理**(租车数 = 已提车数时不显示)。\n\n**子表**:按 **提车批次 + 品牌型号分组** 展示。\n\n- **序号**:一个序号对应一次提车办理批次\n- **同一批次多辆车**:交付时间、交付地点、数量、合计应收/实收与操作列按批次合并\n- **品牌型号**:同批次内相同品牌型号合并为一行,并在该单元格内列出全部车牌号;多个品牌型号各占一行\n- **子表操作**(外显):查看、关联收款记录;合同 **审批驳回** 时显示退款\n\n**数据来源**:租赁合同提交审核时同步生成主记录;办理提交后追加子表;实收由收款记录导入反写。",
|
||||
"vpr-collect-topbar": "# 办理页顶栏\n\n- **返回**:回到列表(有未保存修改时二次确认)\n- **标题**:办理提车收款单\n- **副标题**:合同编码 · 项目名称\n- **未保存**:表单有改动且未保存时显示徽章",
|
||||
"vpr-collect-project": "# 项目信息\n\n只读卡片,默认展开:\n\n提车收款单编码、合同编码/类型、项目名称、客户名称、付款方式与周期、合同起止时间、业务部门与负责人。",
|
||||
"vpr-collect-form": "# 提车日期与计费\n\n- **提车日期**(必填):变更后重算各车应收与 KPI\n- **计费规则**:按合同付款周期自动生成说明文案\n- **应收付款节点**:展示提车前 2 日内应付、当月 25 日应付(如有)\n\n实收金额由「收款记录」导入后反写,办理页只录应收。",
|
||||
"vpr-collect-kpi": "# 办理页 KPI\n\n| 指标 | 说明 |\n|---|---|\n| 应收款总额 | 已选车辆应收合计;可点击查看明细 |\n| 提车前应付 | 提车前须到账金额;可点击查看明细 |\n| 应开票总额 | 按实收租金与服务费计算 |",
|
||||
"vpr-collect-table": "# 提车车辆明细\n\n- 勾选列:点击行或复选框切换;已完成车辆标记「已完成」且不可选\n- 工具栏:全选/取消全选;未选时警告提示\n- 列:车牌、应收租金/保证金/服务费、实收、氢费应收等\n- **氢费预付款**:合同氢费支付方式相关只读/录入区\n\n金额展示使用等宽数字与千分位格式。",
|
||||
"vpr-collect-footer": "# 底栏操作\n\n| 按钮 | 行为 |\n|---|---|\n| 取消 | 返回列表,未保存时确认 |\n| 保存 | 保存草稿,清除「未保存」状态 |\n| 提交审核 | 至少勾选 1 辆车;确认后生成子表单据并抄送财务 |\n\n底栏汇总展示提车前应付与已选车辆数。"
|
||||
"vpr-table": "# 主从嵌套列表\n\n**主表**:按合同聚合,展示收款情况、应收/实收金额等。\n\n**主表操作列**:\n- **查看**:浏览本合同全部 **已发布** 与 **未发布** 提车应收款记录(只读)\n- **办理**:仅对 **尚未操作** 的未发布车辆发起办理;租车数 = 已提车数时不显示\n\n**项目信息列**:可点击进入办理的客户/合同/项目文案使用 **主题色** 链接样式;左侧展开图标同样使用主题色,提示可展开查看子表。\n\n**子表**:按 **提车批次 + 品牌型号分组** 展示。\n\n- **序号**:一个序号对应一次提车办理批次\n- **同一批次多辆车**:交付时间、交付地点、数量、合计应收/实收与操作列按批次合并\n- **品牌型号**:格式为「品牌·型号」;同批次内相同品牌型号合并单元格\n- **车牌号**:独立列;合同为「以实际交付为准」时先显示该文案,交车后显示实际车牌;「选择特定车辆」时显示合同车牌\n- **同一品牌型号多辆车**:品牌型号合并,车牌号各占一行\n- **子表操作**(外显):查看、关联收款记录;合同 **审批驳回** 时显示退款\n\n**数据来源**:租赁合同提交审核时同步生成主记录;办理提交后追加子表;实收由收款记录导入反写。",
|
||||
"vpr-collect-topbar": "# 办理页顶栏\n\n- **返回列表**:回到列表(有未提交修改时二次确认)\n- 与租赁合同等表单页一致,顶栏仅保留返回按钮\n- 合同编码、项目名称见下方「合同信息」模块;有未保存修改时在底栏显示「未提交」",
|
||||
"vpr-collect-project": "# 合同信息\n\n参照车辆资产详情页上方展示(`vm-detail-card` + `vm-detail-hero` + `vm-detail-stats`):\n\n- **Hero 左侧**:客户名称(主标题)、合同编码、项目名称\n- **Hero 右侧**:签约公司、业务部门·负责人、合同类型\n- **Stats 行**:签约公司、付款方式、付款周期、氢费支付方式;氢费为「预付款」时追加预付款金额",
|
||||
"vpr-collect-form": "# 提车应收款\n\n## 交付信息\n\n两列对齐面板(地点 + 时间,均必填);提示文案:**根据交付时间,自动计算提车应收款金额**。\n\n## 租赁订单明细表\n\n| 列 | 说明 |\n|---|---|\n| 品牌型号 | 格式「品牌·型号」;同型号多车合并单元格 |\n| 车牌号 | 与列表子表一致:「选择特定车辆」显示合同车牌;「以实际交付为准」显示对应文案 |\n| 未交车辆数 | 尚未办理提车的数量;点击可填入本次交车数 |\n| 本次交车数 | 本次办理数量,不超过未交车辆数 |\n| 租金总额 | 按交付时间与本次交车数计算的租金合计 |\n| 保证金总额 | 按交付时间与本次交车数计算的保证金合计 |\n| 服务项总额 | 按交付时间与本次交车数计算的服务费合计 |\n| 应收总额 | 租金总额 + 保证金总额 + 服务项总额 |\n\n## 计算规则\n\n- 提车日 ≤15 日:提车前 2 日内付保证金+当月剩余租金及服务费;当月 25 日付下周期\n- 提车日 >15 日:提车前 2 日内付保证金+当月剩余+下周期",
|
||||
"vpr-collect-kpi": "# 办理汇总\n\n五列 `vm-detail-stats` 统计条:\n\n| 指标 | 说明 |\n|---|---|\n| 已选车辆数 | 各品牌型号交车数合计 |\n| 应收总额 | 按交付时间自动计算并求和 |\n| 租金总额 | 提车前 + 当月 25 日节点租金合计 |\n| 保证金总额 | 提车前应付保证金 |\n| 服务项总额 | 提车前 + 当月 25 日节点服务费合计 |",
|
||||
"vpr-collect-table": "# 租赁订单明细表\n\n按品牌/型号汇总;车牌号列与列表子表一致(特定车辆显示车牌、以实际交付为准显示对应文案,同型号多车合并品牌型号单元格);含未交车辆数、本次交车数及租金/保证金/服务项/应收总额。",
|
||||
"vpr-collect-footer": "# 底栏操作\n\n| 按钮 | 行为 |\n|---|---|\n| 取消 | 返回列表,未提交时确认 |\n| 提交生成 | 至少 1 辆车且填写交付时间/地点;确认后生成子表并抄送财务 |"
|
||||
},
|
||||
"assetMap": {},
|
||||
"directory": {
|
||||
@@ -283,7 +283,7 @@
|
||||
"type": "markdown",
|
||||
"id": "vpr-doc-collect-flow",
|
||||
"title": "办理流程",
|
||||
"markdown": "## 办理流程\n\n1. 从列表点击「办理」进入\n2. 确认项目信息与提车日期\n3. 勾选本次提车车辆(默认预选未完成车辆)\n4. 核对 KPI 与明细表应收金额\n5. **保存** 草稿或 **提交审核**\n\n提交后生成子表单据,抄送财务;返回列表可看到新子表记录。"
|
||||
"markdown": "## 办理流程\n\n1. 列表点击 **办理** 进入办理页\n2. 确认顶部 **合同信息**(参照车辆资产详情页样式)\n3. 查看 **办理汇总** KPI(已选车辆数、应收/租金/保证金/服务项总额)\n4. 在 **提车应收款** 卡片填写交付地点与时间,并设置各品牌型号本次交车数量\n5. 系统自动按提车应收款规则计算应收\n6. **提交生成** 后写入子表并抄送财务"
|
||||
},
|
||||
{
|
||||
"type": "markdown",
|
||||
|
||||
@@ -10,7 +10,9 @@ import {
|
||||
formatDeliveryLocation,
|
||||
dedupePickupListByContractCode,
|
||||
pickSeedIdentityFields,
|
||||
kpiKeyFromPickupProgress,
|
||||
} from '../pickup-receivable-bridge.js';
|
||||
import { formatMoneyYuan as formatMoneyYuanCore } from '../../../common/format-number.js';
|
||||
|
||||
/**
|
||||
* 模拟「租赁合同提交 / 交付日前 2 天」自动生成的提车应收款子单
|
||||
@@ -194,13 +196,17 @@ export const PICKUP_RECEIVABLE_LIST_SEED = [
|
||||
contractType: '正式合同',
|
||||
projectName: '嘉兴氢能示范项目',
|
||||
customerName: '嘉兴某某物流有限公司',
|
||||
signingCompany: '嘉兴羚牛氢能科技有限公司',
|
||||
businessDept: '业务1部',
|
||||
businessPerson: '张经理',
|
||||
contractEffectiveDate: '2025-01-15',
|
||||
contractApprovalStatus: '审批通过',
|
||||
contractStatus: '合同进行中',
|
||||
paymentCycleMonths: 6,
|
||||
paymentMethod: '预付',
|
||||
paymentMethod: 'advance',
|
||||
hydrogenBearer: '客户',
|
||||
hydrogenPaymentMethod: 'prepay',
|
||||
hydrogenPrepay: 5000,
|
||||
createdFromLeaseSubmit: true,
|
||||
leaseSubmitAt: '2025-01-08 11:20',
|
||||
deliveryDate: '2025-03-01',
|
||||
@@ -245,13 +251,18 @@ export const PICKUP_RECEIVABLE_LIST_SEED = [
|
||||
contractType: '正式合同',
|
||||
projectName: '杭州城配租赁项目',
|
||||
customerName: '杭州某某租赁有限公司',
|
||||
signingCompany: '上海羚牛氢能科技有限公司',
|
||||
businessDept: '业务3部',
|
||||
businessPerson: '王专员',
|
||||
contractEffectiveDate: '2025-02-10',
|
||||
contractApprovalStatus: '审批驳回',
|
||||
contractStatus: '已结束',
|
||||
pickupRefundCompleted: true,
|
||||
refundPaymentLinked: true,
|
||||
paymentCycleMonths: 1,
|
||||
paymentMethod: '后付',
|
||||
paymentMethod: 'postpay',
|
||||
hydrogenBearer: '客户',
|
||||
hydrogenPaymentMethod: 'month',
|
||||
createdFromLeaseSubmit: true,
|
||||
leaseSubmitAt: '2025-02-11 09:30',
|
||||
allVehiclesReceivableCompleted: false,
|
||||
@@ -376,16 +387,33 @@ export const PICKUP_RECEIVABLE_LIST_SEED = [
|
||||
];
|
||||
|
||||
export const PICKUP_KPI_CARDS = [
|
||||
{ key: 'all', title: '全部', desc: '当前筛选范围内全部提车应收款单据' },
|
||||
{ key: 'generated', title: '已生成', desc: '办理提交后已生成、待财务跟进的提车应收款单据' },
|
||||
{ key: 'pending', title: '待审批', desc: '已保存但未提交审批的提车应收款单据' },
|
||||
{ key: 'inReview', title: '审批中', desc: '审批流程进行中的提车应收款单据' },
|
||||
{ key: 'approved', title: '审批通过', desc: '已完成最终审批的提车应收款单据' },
|
||||
{ key: 'rejected', title: '已驳回', desc: '审批被驳回、可重新编辑提交的提车应收款单据' },
|
||||
{
|
||||
key: 'all',
|
||||
title: '全部',
|
||||
desc: '显示全部租赁合同对应的提车应收款信息',
|
||||
},
|
||||
{
|
||||
key: 'notPickedUp',
|
||||
title: '未提车',
|
||||
desc: '显示租赁合同中没有完成任何车辆提车应收款并关联收款记录闭环的数据',
|
||||
},
|
||||
{
|
||||
key: 'partialPickedUp',
|
||||
title: '部分提车',
|
||||
desc: '显示租赁合同中只完成部分车辆提车应收款并关联收款记录闭环的数据',
|
||||
},
|
||||
{
|
||||
key: 'completed',
|
||||
title: '已完成',
|
||||
desc: '显示租赁合同中全部车辆都已完成提车应收款并关联收款记录闭环的数据',
|
||||
},
|
||||
{
|
||||
key: 'refunded',
|
||||
title: '已退款',
|
||||
desc: '显示租赁合同已被审批终止,且点击退款并完成付款记录关联的数据',
|
||||
},
|
||||
];
|
||||
|
||||
import { formatMoneyYuan as formatMoneyYuanCore } from '../../../common/format-number.js';
|
||||
|
||||
export function buildCollectCode(contractCode, seq) {
|
||||
const suffix = `TC${String(seq != null ? seq : '').padStart(4, '0')}`;
|
||||
return `${String(contractCode || '')}${suffix}`;
|
||||
@@ -455,6 +483,7 @@ export function buildFilterOptions(list) {
|
||||
const codes = [];
|
||||
const projects = [];
|
||||
const customers = [];
|
||||
const plateNos = [];
|
||||
const depts = [];
|
||||
const persons = [];
|
||||
(list || []).forEach((r) => {
|
||||
@@ -463,50 +492,74 @@ export function buildFilterOptions(list) {
|
||||
if (r.customerName && !customers.includes(r.customerName)) customers.push(r.customerName);
|
||||
if (r.businessDept && !depts.includes(r.businessDept)) depts.push(r.businessDept);
|
||||
if (r.businessPerson && !persons.includes(r.businessPerson)) persons.push(r.businessPerson);
|
||||
(r.leaseOrderVehicles || []).forEach((v) => {
|
||||
var plate = String(v.plateNo || '').trim();
|
||||
if (plate && !plateNos.includes(plate)) plateNos.push(plate);
|
||||
});
|
||||
(r.children || []).forEach((child) => {
|
||||
(child.deliveryVehicles || []).forEach((v) => {
|
||||
var childPlate = String(v.plateNo || '').trim();
|
||||
if (childPlate && !plateNos.includes(childPlate)) plateNos.push(childPlate);
|
||||
});
|
||||
});
|
||||
});
|
||||
plateNos.sort();
|
||||
return {
|
||||
contractCode: codes.map((v) => ({ value: v, label: v })),
|
||||
projectName: projects.map((v) => ({ value: v, label: v })),
|
||||
customerName: customers.map((v) => ({ value: v, label: v })),
|
||||
plateNo: plateNos.map((v) => ({ value: v, label: v })),
|
||||
businessDept: depts.map((v) => ({ value: v, label: v })),
|
||||
businessPerson: persons.map((v) => ({ value: v, label: v })),
|
||||
};
|
||||
}
|
||||
|
||||
function recordMatchesPlateNo(record, plateNo) {
|
||||
var target = String(plateNo || '').trim();
|
||||
if (!target) return true;
|
||||
var matched = (record.leaseOrderVehicles || []).some(function (v) {
|
||||
return String(v.plateNo || '').trim() === target;
|
||||
});
|
||||
if (matched) return true;
|
||||
return (record.children || []).some(function (child) {
|
||||
return (child.deliveryVehicles || []).some(function (v) {
|
||||
return String(v.plateNo || '').trim() === target;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function filterMainList(list, filters) {
|
||||
let rows = (list || []).filter(isValidPickupMainRecord);
|
||||
if (filters.contractCode) rows = rows.filter((r) => r.contractCode === filters.contractCode);
|
||||
if (filters.projectName) rows = rows.filter((r) => r.projectName === filters.projectName);
|
||||
if (filters.customerName) rows = rows.filter((r) => r.customerName === filters.customerName);
|
||||
if (filters.plateNo) rows = rows.filter((r) => recordMatchesPlateNo(r, filters.plateNo));
|
||||
if (filters.businessDept) rows = rows.filter((r) => r.businessDept === filters.businessDept);
|
||||
if (filters.businessPerson) rows = rows.filter((r) => r.businessPerson === filters.businessPerson);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export function countKpiFromList(list, kpiTab) {
|
||||
const counts = { all: 0, generated: 0, pending: 0, inReview: 0, approved: 0, rejected: 0 };
|
||||
(list || []).forEach((parent) => {
|
||||
(parent.children || []).forEach((child) => {
|
||||
counts.all += 1;
|
||||
const key = kpiKeyFromAuditStatus(child.auditStatus);
|
||||
if (key) counts[key] += 1;
|
||||
});
|
||||
export function countKpiFromList(list) {
|
||||
const counts = {
|
||||
all: 0,
|
||||
notPickedUp: 0,
|
||||
partialPickedUp: 0,
|
||||
completed: 0,
|
||||
refunded: 0,
|
||||
};
|
||||
(list || []).forEach(function (parent) {
|
||||
counts.all += 1;
|
||||
const key = kpiKeyFromPickupProgress(parent);
|
||||
if (key && counts[key] != null) counts[key] += 1;
|
||||
});
|
||||
return counts;
|
||||
}
|
||||
|
||||
export function filterByKpiTab(list, kpiTab) {
|
||||
if (!kpiTab || kpiTab === 'all') return list;
|
||||
return (list || [])
|
||||
.map((parent) => {
|
||||
const children = (parent.children || []).filter((child) => {
|
||||
const key = kpiKeyFromAuditStatus(child.auditStatus);
|
||||
return key === kpiTab;
|
||||
});
|
||||
if (!children.length) return null;
|
||||
return { ...parent, children };
|
||||
})
|
||||
.filter(Boolean);
|
||||
return (list || []).filter(function (parent) {
|
||||
return kpiKeyFromPickupProgress(parent) === kpiTab;
|
||||
});
|
||||
}
|
||||
|
||||
function mergeSeedWithStored(seed, stored) {
|
||||
|
||||
@@ -8,6 +8,11 @@ import {
|
||||
buildBatchPickupScheduleSummary,
|
||||
describePickupRule,
|
||||
} from './pickup-receivable-calc.js';
|
||||
import {
|
||||
formatHydrogenPaymentLabel,
|
||||
formatPaymentMethodLabel,
|
||||
formatPaymentPeriodLabel,
|
||||
} from '../lease-contract-management/lease-contract-list-data.js';
|
||||
|
||||
export {
|
||||
buildVehiclePickupSchedule,
|
||||
@@ -85,6 +90,10 @@ export function pickSeedIdentityFields(seedRow) {
|
||||
contractStatus: seedRow.contractStatus,
|
||||
paymentCycleMonths: seedRow.paymentCycleMonths,
|
||||
paymentMethod: seedRow.paymentMethod,
|
||||
signingCompany: seedRow.signingCompany,
|
||||
hydrogenBearer: seedRow.hydrogenBearer,
|
||||
hydrogenPaymentMethod: seedRow.hydrogenPaymentMethod,
|
||||
hydrogenPrepay: seedRow.hydrogenPrepay,
|
||||
deliveryDate: seedRow.deliveryDate,
|
||||
deliveryDateTbd: seedRow.deliveryDateTbd,
|
||||
deliveryRegion: seedRow.deliveryRegion,
|
||||
@@ -327,6 +336,61 @@ export function countPickedUpVehicles(record) {
|
||||
return collectPickedVehicleKeys(record.children).size;
|
||||
}
|
||||
|
||||
/** 子单是否已完成收款闭环(实收 ≥ 应收) */
|
||||
export function isBatchPaymentClosed(child) {
|
||||
var status = String(child && child.auditStatus || '');
|
||||
if (status === '已驳回' || status === '审批驳回') return false;
|
||||
var received = parseFloat(
|
||||
child && child.financeReceived != null ? child.financeReceived : child && child.actualTotal,
|
||||
) || 0;
|
||||
var receivable = parseFloat(child && child.receivableTotal) || 0;
|
||||
if (receivable <= 0) return false;
|
||||
return received + 0.009 >= receivable;
|
||||
}
|
||||
|
||||
/** 已完成提车应收款并关联收款记录闭环的车辆 */
|
||||
export function collectClosedLoopVehicleKeys(children) {
|
||||
var keys = new Set();
|
||||
(children || []).forEach(function (child) {
|
||||
if (!isBatchPaymentClosed(child)) return;
|
||||
(child.deliveryVehicles || []).forEach(function (v) {
|
||||
keys.add(vehicleMatchKey(v));
|
||||
});
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
export function countClosedLoopVehicles(record) {
|
||||
return collectClosedLoopVehicleKeys(record && record.children).size;
|
||||
}
|
||||
|
||||
export function isContractApprovalTerminated(record) {
|
||||
var approval = String(record && record.contractApprovalStatus || record && record.leaseApprovalStatus || '').trim();
|
||||
var status = String(record && record.contractStatus || '').trim();
|
||||
return approval === '审批驳回'
|
||||
|| approval === '已驳回'
|
||||
|| approval === '审批终止'
|
||||
|| status === '已终止'
|
||||
|| status === '已结束';
|
||||
}
|
||||
|
||||
export function isPickupRefundCompleted(record) {
|
||||
return Boolean(record && (record.pickupRefundCompleted || record.refundPaymentLinked));
|
||||
}
|
||||
|
||||
/** 列表 KPI:按合同提车与收款闭环进度分类 */
|
||||
export function kpiKeyFromPickupProgress(record) {
|
||||
if (isContractApprovalTerminated(record) && isPickupRefundCompleted(record)) {
|
||||
return 'refunded';
|
||||
}
|
||||
var total = record.contractVehicleCount || (record.leaseOrderVehicles || []).length;
|
||||
if (!total) return 'notPickedUp';
|
||||
var closed = countClosedLoopVehicles(record);
|
||||
if (closed <= 0) return 'notPickedUp';
|
||||
if (closed >= total) return 'completed';
|
||||
return 'partialPickedUp';
|
||||
}
|
||||
|
||||
export function calcReceivedTotal(children) {
|
||||
return (children || []).reduce(function (sum, child) {
|
||||
var status = String(child.auditStatus || '');
|
||||
@@ -727,6 +791,363 @@ export function toCollectPageVehicles(record, pickupDate) {
|
||||
});
|
||||
}
|
||||
|
||||
export function buildVehicleModelKey(vehicle) {
|
||||
if (!vehicle) return '';
|
||||
return [String(vehicle.brand || '').trim(), String(vehicle.model || '').trim()]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export var PLATE_ACTUAL_DELIVERY_LABEL = '以实际交付为准';
|
||||
|
||||
export function isLeaseSpecificVehicle(leaseVehicle) {
|
||||
if (!leaseVehicle) return false;
|
||||
if (leaseVehicle.plateMode === 'specific') return true;
|
||||
if (leaseVehicle.plateMode === 'actual') return false;
|
||||
var leasePlate = String(leaseVehicle.plateNo || '').trim();
|
||||
return Boolean(leasePlate && leasePlate !== PLATE_ACTUAL_DELIVERY_LABEL && leasePlate !== '-');
|
||||
}
|
||||
|
||||
/** 办理页表格:按租赁合同规则解析车牌展示文案 */
|
||||
export function resolveCollectPlateLabel(leaseVehicle) {
|
||||
if (!leaseVehicle) return '—';
|
||||
if (isLeaseSpecificVehicle(leaseVehicle)) {
|
||||
var contractPlate = String(leaseVehicle.plateNo || '').trim();
|
||||
return contractPlate || '—';
|
||||
}
|
||||
return PLATE_ACTUAL_DELIVERY_LABEL;
|
||||
}
|
||||
|
||||
export function buildCollectOrderTableRows(orderLines) {
|
||||
var rows = [];
|
||||
(orderLines || []).forEach(function (line) {
|
||||
var displayVehicles = (line.leaseVehicles && line.leaseVehicles.length)
|
||||
? line.leaseVehicles
|
||||
: (line.vehicles && line.vehicles.length)
|
||||
? line.vehicles
|
||||
: [{ seq: 0, brand: line.brand, model: line.model, plateNo: '' }];
|
||||
var rowSpan = displayVehicles.length;
|
||||
displayVehicles.forEach(function (vehicle, index) {
|
||||
rows.push({
|
||||
line: line,
|
||||
vehicle: vehicle,
|
||||
rowKey: line.lineKey + '::' + (vehicle.seq != null ? vehicle.seq : index),
|
||||
isFirstInModelGroup: index === 0,
|
||||
modelGroupRowSpan: rowSpan,
|
||||
});
|
||||
});
|
||||
});
|
||||
return rows;
|
||||
}
|
||||
|
||||
export function buildLeaseOrderLinesForCollect(record) {
|
||||
var enriched = enrichPickupRecord(Object.assign({}, record));
|
||||
var pending = getPendingPickupVehicles(enriched);
|
||||
var totalCountMap = {};
|
||||
var leaseVehiclesByKey = {};
|
||||
(enriched.leaseOrderVehicles || []).forEach(function (vehicle, index) {
|
||||
var totalKey = buildVehicleModelKey(vehicle) || '__line-' + index;
|
||||
totalCountMap[totalKey] = (totalCountMap[totalKey] || 0) + 1;
|
||||
if (!leaseVehiclesByKey[totalKey]) leaseVehiclesByKey[totalKey] = [];
|
||||
leaseVehiclesByKey[totalKey].push(vehicle);
|
||||
});
|
||||
var groupMap = {};
|
||||
var order = [];
|
||||
pending.forEach(function (vehicle, index) {
|
||||
var key = buildVehicleModelKey(vehicle) || '__line-' + index;
|
||||
if (!groupMap[key]) {
|
||||
groupMap[key] = {
|
||||
lineKey: key,
|
||||
brand: vehicle.brand || '',
|
||||
model: vehicle.model || '',
|
||||
monthRent: vehicle.monthRent || 0,
|
||||
deposit: vehicle.deposit || 0,
|
||||
serviceFeeTotal: vehicle.serviceFeeTotal || 0,
|
||||
vehicles: [],
|
||||
leaseVehicles: leaseVehiclesByKey[key] || [],
|
||||
leasedCount: totalCountMap[key] || 0,
|
||||
availableCount: 0,
|
||||
};
|
||||
order.push(key);
|
||||
}
|
||||
groupMap[key].vehicles.push(vehicle);
|
||||
groupMap[key].availableCount += 1;
|
||||
});
|
||||
return order.map(function (key) {
|
||||
var line = groupMap[key];
|
||||
if (!line.leasedCount) line.leasedCount = line.availableCount;
|
||||
return line;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizePaymentMethodCode(method) {
|
||||
var text = String(method || '').trim();
|
||||
if (text === '预付' || text === '先付后用') return 'advance';
|
||||
if (text === '后付' || text === '先用后付') return 'postpay';
|
||||
return text;
|
||||
}
|
||||
|
||||
function normalizeHydrogenPaymentCode(method) {
|
||||
var text = String(method || '').trim();
|
||||
if (text === '预付' || text === '预付款') return 'prepay';
|
||||
if (text === '月付款' || text === '按月结算') return 'month';
|
||||
if (text === '自行结算' || text === '自行解决') return 'self';
|
||||
return text;
|
||||
}
|
||||
|
||||
function isHydrogenPrepayMethod(method) {
|
||||
var code = normalizeHydrogenPaymentCode(method);
|
||||
return code === 'prepay';
|
||||
}
|
||||
|
||||
/** 氢费支付方式展示(与租赁合同选项对齐) */
|
||||
export function resolveHydrogenPaymentLabel(record) {
|
||||
var code = normalizeHydrogenPaymentCode(record && record.hydrogenPaymentMethod);
|
||||
if (code) return formatHydrogenPaymentLabel(code);
|
||||
var bearer = String((record && record.hydrogenBearer) || '').trim();
|
||||
if (bearer === '我方') return '我方承担';
|
||||
return '—';
|
||||
}
|
||||
|
||||
/** 氢气预付款金额;仅氢费支付方式为「预付款」时有值 */
|
||||
export function resolveHydrogenPrepayAmount(record) {
|
||||
if (!isHydrogenPrepayMethod(record && record.hydrogenPaymentMethod)) return null;
|
||||
var raw = record.hydrogenPrepay != null ? record.hydrogenPrepay : record.hydrogenPrepayAmount;
|
||||
if (raw == null || raw === '') return null;
|
||||
var amount = parseFloat(raw);
|
||||
return Number.isFinite(amount) ? amount : null;
|
||||
}
|
||||
|
||||
function applyLeaseContractFieldDefaults(record) {
|
||||
var next = Object.assign({}, record);
|
||||
if (!next.signingCompany) next.signingCompany = '嘉兴羚牛氢能科技有限公司';
|
||||
if (!next.hydrogenBearer) next.hydrogenBearer = '客户';
|
||||
next.paymentMethod = normalizePaymentMethodCode(next.paymentMethod || 'advance');
|
||||
next.hydrogenPaymentMethod = normalizeHydrogenPaymentCode(
|
||||
next.hydrogenPaymentMethod || (next.hydrogenBearer === '客户' ? 'prepay' : ''),
|
||||
);
|
||||
if (next.hydrogenPrepay == null && isHydrogenPrepayMethod(next.hydrogenPaymentMethod)) {
|
||||
next.hydrogenPrepay = 5000;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/** 列表「办理」页上下文:合同主信息 + 附件1 租赁订单可办明细 */
|
||||
export function buildCollectHandleContext(record) {
|
||||
var enriched = applyLeaseContractFieldDefaults(enrichPickupRecord(Object.assign({}, record)));
|
||||
var months = enriched.paymentCycleMonths || 1;
|
||||
var paymentMethodCode = normalizePaymentMethodCode(enriched.paymentMethod);
|
||||
var hydrogenMethodCode = normalizeHydrogenPaymentCode(enriched.hydrogenPaymentMethod);
|
||||
return {
|
||||
contractCode: enriched.contractCode,
|
||||
contractType: enriched.contractType || '正式合同',
|
||||
projectName: enriched.projectName,
|
||||
customerName: enriched.customerName,
|
||||
signingCompany: enriched.signingCompany || '',
|
||||
paymentMethod: paymentMethodCode,
|
||||
paymentMethodLabel: formatPaymentMethodLabel(paymentMethodCode),
|
||||
paymentCycleMonths: months,
|
||||
paymentCycle: formatPaymentPeriodLabel(months),
|
||||
paymentCycleLabel: formatPaymentPeriodLabel(months),
|
||||
hydrogenBearer: enriched.hydrogenBearer || '',
|
||||
hydrogenPaymentMethod: hydrogenMethodCode,
|
||||
hydrogenPaymentMethodLabel: formatHydrogenPaymentLabel(hydrogenMethodCode),
|
||||
hydrogenPrepayAmount: resolveHydrogenPrepayAmount(enriched),
|
||||
contractStart: enriched.contractEffectiveDate || '',
|
||||
contractEnd: enriched.contractEndDate || '',
|
||||
businessDept: enriched.businessDept || '',
|
||||
businessPerson: enriched.businessPerson || '',
|
||||
contractStatus: enriched.contractStatus || '',
|
||||
contractApprovalStatus: enriched.contractApprovalStatus || '',
|
||||
defaultDeliveryLocation: formatDeliveryLocation(enriched),
|
||||
defaultPickupDate: enriched.lastPickupDate || enriched.deliveryDate || '',
|
||||
leaseOrderLines: buildLeaseOrderLinesForCollect(enriched),
|
||||
};
|
||||
}
|
||||
|
||||
function vehicleScheduleParts(schedule) {
|
||||
if (!schedule) return { rent: 0, deposit: 0, service: 0, total: 0 };
|
||||
var before = schedule.items && schedule.items[0] ? schedule.items[0] : {};
|
||||
var month25 = schedule.items && schedule.items[1] ? schedule.items[1] : {};
|
||||
var rent = (before.rent || 0) + (month25.rent || 0);
|
||||
var service = (before.service || 0) + (month25.service || 0);
|
||||
var deposit = before.deposit || 0;
|
||||
return {
|
||||
rent: rent,
|
||||
deposit: deposit,
|
||||
service: service,
|
||||
total: schedule.totalReceivable || rent + deposit + service,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildCollectLinePreview(line, quantity, pickupDate, paymentCycleMonths) {
|
||||
var qty = Math.min(line.availableCount, Math.max(0, parseInt(quantity, 10) || 0));
|
||||
if (!qty || !pickupDate) {
|
||||
return {
|
||||
quantity: qty,
|
||||
unitTotal: 0,
|
||||
lineTotal: 0,
|
||||
rentTotal: 0,
|
||||
depositTotal: 0,
|
||||
serviceTotal: 0,
|
||||
schedule: null,
|
||||
};
|
||||
}
|
||||
var schedule = buildVehiclePickupSchedule({
|
||||
monthRent: line.monthRent,
|
||||
monthServiceFee: line.serviceFeeTotal,
|
||||
deposit: line.deposit,
|
||||
pickupDate: pickupDate,
|
||||
paymentCycleMonths: paymentCycleMonths,
|
||||
});
|
||||
var parts = vehicleScheduleParts(schedule);
|
||||
return {
|
||||
quantity: qty,
|
||||
unitTotal: parts.total,
|
||||
lineTotal: parts.total * qty,
|
||||
rentTotal: parts.rent * qty,
|
||||
depositTotal: parts.deposit * qty,
|
||||
serviceTotal: parts.service * qty,
|
||||
schedule: schedule,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildCollectSubmitPayload(handleContext, form) {
|
||||
var months = handleContext.paymentCycleMonths || 1;
|
||||
var pickupDate = form.deliveryDate;
|
||||
var deliveryLocation = form.deliveryLocation;
|
||||
var deliveryVehicles = [];
|
||||
var calcRows = [];
|
||||
|
||||
(handleContext.leaseOrderLines || []).forEach(function (line) {
|
||||
var qty = Math.min(
|
||||
line.availableCount,
|
||||
Math.max(0, parseInt(form.lineQuantities[line.lineKey], 10) || 0),
|
||||
);
|
||||
line.vehicles.slice(0, qty).forEach(function (vehicle) {
|
||||
var schedule = buildVehiclePickupSchedule({
|
||||
monthRent: line.monthRent,
|
||||
monthServiceFee: line.serviceFeeTotal,
|
||||
deposit: line.deposit,
|
||||
pickupDate: pickupDate,
|
||||
paymentCycleMonths: months,
|
||||
});
|
||||
var parts = vehicleScheduleParts(schedule);
|
||||
var nextVehicle = Object.assign({}, vehicle, {
|
||||
deliveryTime: pickupDate,
|
||||
deliveryLocation: deliveryLocation,
|
||||
receivableSchedule: schedule,
|
||||
receivableTotal: parts.total,
|
||||
receivableRent: parts.rent,
|
||||
receivableDeposit: parts.deposit,
|
||||
receivableService: parts.service,
|
||||
receivedTotal: 0,
|
||||
});
|
||||
deliveryVehicles.push(nextVehicle);
|
||||
calcRows.push({
|
||||
selected: true,
|
||||
receivableCompleted: false,
|
||||
monthRent: line.monthRent,
|
||||
serviceFeeTotal: line.serviceFeeTotal,
|
||||
deposit: line.deposit,
|
||||
receivableDeposit: line.deposit,
|
||||
receivableService: line.serviceFeeTotal,
|
||||
receivableSchedule: schedule,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var batchSchedule = buildBatchPickupScheduleSummary(calcRows, pickupDate, months);
|
||||
var breakdown = breakdownFromBatchSummary(batchSchedule);
|
||||
|
||||
return {
|
||||
pickupDate: pickupDate,
|
||||
deliveryLocation: deliveryLocation,
|
||||
deliveryVehicles: deliveryVehicles,
|
||||
receivableSchedule: batchSchedule,
|
||||
receivableRent: breakdown.rent,
|
||||
receivableDeposit: breakdown.deposit,
|
||||
receivableService: breakdown.service,
|
||||
receivableTotal: batchSchedule.totalReceivable,
|
||||
actualTotal: 0,
|
||||
receivedRent: 0,
|
||||
receivedDeposit: 0,
|
||||
receivedService: 0,
|
||||
creator: '当前用户',
|
||||
};
|
||||
}
|
||||
|
||||
export function getPendingPickupVehicles(record) {
|
||||
if (!record) return [];
|
||||
var pickedKeys = collectPickedVehicleKeys(record.children);
|
||||
return (record.leaseOrderVehicles || []).filter(function (vehicle) {
|
||||
return !pickedKeys.has(vehicleMatchKey(vehicle));
|
||||
});
|
||||
}
|
||||
|
||||
function buildPickupCollectCode(contractCode, seq) {
|
||||
return String(contractCode || '') + 'TC' + String(seq != null ? seq : '').padStart(4, '0');
|
||||
}
|
||||
|
||||
function summarizePublishedPickupRecord(child, contractCode) {
|
||||
if (!child) return null;
|
||||
var receivableTotal = parseFloat(child.receivableTotal);
|
||||
if (Number.isNaN(receivableTotal)) {
|
||||
receivableTotal = (child.deliveryVehicles || []).reduce(function (sum, vehicle) {
|
||||
return sum + (parseFloat(vehicle.receivableTotal) || 0);
|
||||
}, 0);
|
||||
}
|
||||
var receivedTotal = parseFloat(
|
||||
child.financeReceived != null ? child.financeReceived : child.actualTotal,
|
||||
) || 0;
|
||||
return {
|
||||
seq: child.seq,
|
||||
collectCode: buildPickupCollectCode(contractCode, child.seq),
|
||||
auditStatus: child.auditStatus || '已生成',
|
||||
publishStatus: '已发布',
|
||||
pickupDate: child.pickupDate || '—',
|
||||
deliveryLocation: child.deliveryLocation || '—',
|
||||
vehicleCount: (child.deliveryVehicles || []).length || child.deliveryCount || 0,
|
||||
receivableTotal: receivableTotal,
|
||||
receivedTotal: receivedTotal,
|
||||
chargeTime: child.chargeTime || '—',
|
||||
creator: child.creator || '—',
|
||||
};
|
||||
}
|
||||
|
||||
/** 列表「查看」页上下文:已发布子单 + 未发布待办理车辆 */
|
||||
export function buildContractViewContext(record) {
|
||||
var enriched = enrichPickupRecord(Object.assign({}, record));
|
||||
return {
|
||||
mode: 'contract',
|
||||
contractCode: enriched.contractCode,
|
||||
contractType: enriched.contractType,
|
||||
projectName: enriched.projectName,
|
||||
customerName: enriched.customerName,
|
||||
paymentMethod: enriched.paymentMethod,
|
||||
paymentCycleMonths: enriched.paymentCycleMonths,
|
||||
paymentCycle: (enriched.paymentCycleMonths || 1) + '个月',
|
||||
contractStart: enriched.contractEffectiveDate,
|
||||
contractEnd: enriched.contractEndDate || '',
|
||||
businessDept: enriched.businessDept,
|
||||
businessPerson: enriched.businessPerson,
|
||||
contractVehicleCount: enriched.contractVehicleCount,
|
||||
pickedUpVehicleCount: enriched.pickedUpVehicleCount,
|
||||
publishedRecords: (enriched.children || []).map(function (child) {
|
||||
return summarizePublishedPickupRecord(child, enriched.contractCode);
|
||||
}).filter(Boolean),
|
||||
pendingVehicles: getPendingPickupVehicles(enriched).map(function (vehicle, index) {
|
||||
return {
|
||||
seq: vehicle.seq != null ? vehicle.seq : index + 1,
|
||||
brand: vehicle.brand,
|
||||
model: vehicle.model,
|
||||
plateNo: vehicle.plateNo || '—',
|
||||
publishStatus: '未发布',
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function applyPickupDateToVehicles(vehicles, pickupDate, paymentCycleMonths) {
|
||||
return (vehicles || []).map(function (row) {
|
||||
if (row.receivableCompleted) return row;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user