PAYMENT_AND_QUOTATION_FIX.md 9.7 KB

订单分配总报价和尾款结算修复总结

📋 问题概述

问题1:订单分配阶段总报价不显示

现象:报价组件无法将总报价数据传递给订单分配组件 原因stage-order.component.ts 缺少接收报价数据的事件处理方法

问题2:尾款结算获取不到数据

现象

❌ 获取项目付款记录失败: Error: This user is not allowed to access non-existent class: ProjectPayment
❌ 计算付款统计失败: Error: This user is not allowed to access non-existent class: ProjectPayment
📋 找到 0 条尾款记录

原因:系统尝试访问不存在的 ProjectPayment


✅ 修复方案

修复1:订单分配阶段总报价显示

📝 文件:stage-order.component.ts

添加的方法:

/**
 * 处理报价组件的报价数据变化
 */
onQuotationChange(quotation: any): void {
  console.log('📊 [订单分配] 报价数据更新:', quotation);
  this.quotation = quotation;
  this.cdr.markForCheck();
}

/**
 * 处理报价组件的总价变化
 */
onTotalChange(total: number): void {
  console.log('💰 [订单分配] 总报价更新:', total);
  this.quotation.total = total;
  this.cdr.markForCheck();
}

/**
 * 处理报价组件的加载状态变化
 */
onQuotationLoadingChange(loading: boolean): void {
  console.log('⏳ [订单分配] 报价加载状态:', loading);
  this.cdr.markForCheck();
}

/**
 * 处理报价组件的产品列表变化
 */
onProductsChange(products: any[]): void {
  console.log('📦 [订单分配] 产品列表更新:', products.length, '个产品');
  this.cdr.markForCheck();
}

HTML模板已正确配置:

<app-quotation-editor
  [projectId]="projectId"
  [project]="project"
  [canEdit]="canEdit"
  [viewMode]="'card'"
  [currentUser]="currentUser"
  (quotationChange)="onQuotationChange($event)"
  (totalChange)="onTotalChange($event)"
  (loadingChange)="onQuotationLoadingChange($event)"
  (productsChange)="onProductsChange($event)">
</app-quotation-editor>

效果:

  • ✅ 报价组件生成报价后,总价会自动传递到订单分配组件
  • this.quotation.total 会实时更新
  • ✅ 可在订单分配页面显示总报价金额

修复2:尾款结算数据获取(降级方案)

📝 文件:aftercare-data.service.ts

问题根源:系统设计中不存在 ProjectPayment

解决方案:使用 Project.data 字段存储付款数据

修改的方法:

1. getProjectPayments() - 获取所有付款记录
async getProjectPayments(projectId: string): Promise<FmodeObject[]> {
  console.log(`📊 [降级方案] 从 Project.data.payments 获取付款记录...`);
  
  // 从 Project.data 读取支付记录
  const projectQuery = new Parse.Query('Project');
  const project = await projectQuery.get(projectId);
  const data = project.get('data') || {};
  const payments = data.payments || [];
  
  console.log(`✅ 获取项目 ${projectId} 的付款记录,共 ${payments.length} 条(来自Project.data)`);
  
  // 将普通对象转换为类似 Parse.Object 的结构
  return payments.map((p: any) => {
    const mockParseObject: any = {
      id: p.id || p.objectId || '',
      get: (key: string) => p[key],
      toJSON: () => p,
      createdAt: p.createdAt ? new Date(p.createdAt) : new Date(),
      updatedAt: p.updatedAt ? new Date(p.updatedAt) : new Date()
    };
    return mockParseObject;
  });
}
2. getFinalPayments() - 获取尾款记录
async getFinalPayments(projectId: string): Promise<FmodeObject[]> {
  console.log(`📊 [降级方案] 从 Project.data 获取尾款记录...`);
  
  const projectQuery = new Parse.Query('Project');
  const project = await projectQuery.get(projectId);
  const data = project.get('data') || {};
  
  // 尝试多个可能的字段名
  const finalPayments = data.finalPayments || data.final_payments || [];
  const payments = data.payments || [];
  
  // 过滤出尾款类型的付款记录
  const finalPaymentRecords = payments.filter((p: any) => 
    p.type === 'final' || p.paymentType === 'final'
  );
  const allFinalPayments = [...finalPayments, ...finalPaymentRecords];
  
  console.log(`✅ 获取项目 ${projectId} 的尾款记录,共 ${allFinalPayments.length} 条(来自Project.data)`);
  
  return allFinalPayments.map((p: any) => mockParseObject);
}
3. createPayment() - 创建付款记录
async createPayment(paymentData: {...}): Promise<FmodeObject> {
  console.log('📊 [降级方案] 保存付款记录到 Project.data.payments...');
  
  const projectQuery = new Parse.Query('Project');
  const project = await projectQuery.get(paymentData.projectId);
  const data = project.get('data') || {};
  
  // 确保 payments 数组存在
  if (!data.payments) {
    data.payments = [];
  }
  
  // 创建新的付款记录对象
  const newPayment: any = {
    id: `payment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
    objectId: `payment_${Date.now()}`,
    projectId: paymentData.projectId,
    type: paymentData.type,
    amount: paymentData.amount,
    status: 'pending',
    // ... 其他字段
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString()
  };
  
  // 添加到 payments 数组
  data.payments.push(newPayment);
  
  // 保存项目
  project.set('data', data);
  await project.save();
  
  console.log(`✅ 创建付款记录成功(保存到Project.data.payments):`, newPayment.id);
  
  return mockParseObject;
}

📊 数据存储结构

Project.data 字段结构

Project.data = {
  // 所有付款记录
  payments: [
    {
      id: "payment_1234567890_abc123",
      objectId: "payment_1234567890",
      projectId: "项目ID",
      type: "advance" | "milestone" | "final" | "refund",
      stage: "aftercare",
      method: "支付方式",
      amount: 金额,
      currency: "CNY",
      status: "pending" | "paid" | "overdue" | "cancelled",
      recordedById: "记录人ID",
      recordedDate: "2024-11-15T09:00:00.000Z",
      paidById: "支付人ID",
      description: "描述",
      notes: "备注",
      productId: "产品ID",
      dueDate: "截止日期",
      percentage: 百分比,
      isDeleted: false,
      createdAt: "2024-11-15T09:00:00.000Z",
      updatedAt: "2024-11-15T09:00:00.000Z"
    }
  ],
  
  // 尾款专用记录(可选,与payments重复存储以提高查询效率)
  finalPayments: [
    // 同上,但只包含 type === 'final' 的记录
  ],
  
  // 其他已有字段
  quotation: {...},
  unifiedSpaces: [...],
  deliveryCompletedAt: Date,
  // ...
}

🔄 数据流

1. 订单分配阶段 - 报价数据流

QuotationEditorComponent
    ↓ (报价生成)
calculateTotal()
    ↓ (emit事件)
totalChange.emit(total)
    ↓ (父组件接收)
StageOrderComponent.onTotalChange(total)
    ↓ (更新本地数据)
this.quotation.total = total
    ↓ (显示在页面)
总报价:¥{{quotation.total}}

2. 尾款结算阶段 - 付款数据流

创建付款记录
    ↓
aftercareDataService.createPayment()
    ↓ (降级方案)
保存到 Project.data.payments[]
    ↓
项目保存成功
    ↓
读取付款记录
    ↓
aftercareDataService.getProjectPayments()
    ↓ (降级方案)
从 Project.data.payments[] 读取
    ↓
转换为 mockParseObject
    ↓
显示在尾款结算页面

✅ 验证步骤

验证订单分配总报价显示

  1. 打开项目的订单分配阶段
  2. 在报价管理区域添加/修改产品
  3. 查看控制台日志:

    💰 [订单分配] 总报价更新: 12800
    📊 [订单分配] 报价数据更新: {...}
    
  4. 验证页面显示正确的总报价金额

验证尾款结算数据获取

  1. 打开项目的售后归档阶段
  2. 查看控制台日志:

    📊 [降级方案] 从 Project.data.payments 获取付款记录...
    ✅ 获取项目 xxx 的付款记录,共 N 条(来自Project.data)
    📊 [降级方案] 从 Project.data 获取尾款记录...
    ✅ 获取项目 xxx 的尾款记录,共 N 条(来自Project.data)
    
  3. 验证不再出现 ProjectPayment 表不存在的错误

  4. 验证尾款数据正常显示


🔧 兼容性说明

报价组件兼容性

  • ✅ 报价组件(QuotationEditorComponent)已有 totalChange 事件
  • ✅ 订单分配组件已添加接收方法
  • ✅ 不影响其他使用报价组件的地方

付款数据兼容性

  • ✅ 新增数据保存到 Project.data.payments 数组
  • ✅ 旧数据可从 ProjectFile (payment_voucher) 降级读取
  • ✅ 支持多种字段名:finalPayments, final_payments, payments
  • ✅ 返回模拟 Parse.Object,与现有代码兼容

📝 注意事项

订单分配阶段

  1. 确保报价组件正确加载产品数据
  2. 总报价会在产品变化时自动更新
  3. 保存草稿和确认订单时都会保存报价数据

尾款结算阶段

  1. 所有新建的付款记录都保存在 Project.data.payments 数组中
  2. 不再依赖 ProjectPayment
  3. 查询效率略低于专用表,但在项目规模内可接受
  4. 建议定期清理已删除的付款记录(isDeleted: true

🎯 后续优化建议

  1. 订单分配阶段

    • 添加总报价的显著显示区域(如顶部卡片)
    • 添加报价历史记录功能
    • 支持报价版本对比
  2. 尾款结算阶段

    • 添加付款记录的搜索和筛选
    • 支持批量导入付款凭证
    • 添加付款统计图表
  3. 性能优化

    • 考虑将频繁查询的付款数据缓存
    • 优化 Project.data 的数据结构
    • 添加索引以加快查询速度

📅 修复完成时间

2024年11月15日 09:45

👨‍💻 修复人员

Cascade AI Assistant