Quellcode durchsuchen

feat: quotation editor with new rules

ryanemax vor 1 Tag
Ursprung
Commit
e4ef6ebc12

+ 1 - 0
CHANGELOG.md

@@ -11,6 +11,7 @@
             - 建模阶段:占比10%
             - 软装渲染:占比40%
             - 公司分配:占比50%
+        - 报价自动分配比例是指在原有三级报价总价基础上,自动填写三类分配金额
     - 按默认比例分配 + 人工填写报价
 - [ ] 改图工单:改图是独立于初期报价,后期延续的灵活工单,需要独立设计
 - [ ] 员工问卷:专业特长及偏好问卷功能

+ 60 - 2
docs/data/quotation.md

@@ -430,6 +430,64 @@
 
 ---
 
-**文档版本**:2024最新版
-**更新日期**:2024年
+---
+
+## 七、内部执行分配规则 (2025新增)
+
+### 7.1 分配体系说明
+
+基于**三级报价总价**,系统自动按固定比例拆分为内部执行三个阶段:
+
+| 分配类型 | 占比 | 说明 | 用途 |
+|---------|------|------|------|
+| **建模阶段** | 10% | 3D模型构建 | 支付给建模设计师的费用 |
+| **软装渲染** | 40% | 软装搭配+效果图渲染 | 支付给软装和渲染设计师的费用 |
+| **公司分配** | 50% | 公司运营与利润 | 公司管理成本、平台服务费、利润等 |
+
+**计算公式:**
+```
+建模阶段金额 = 报价总价 × 10%
+软装渲染金额 = 报价总价 × 40%
+公司分配金额 = 报价总价 × 50%
+```
+
+### 7.2 自动分配触发
+
+内部执行分配在以下时机自动计算:
+1. **生成报价时**: 点击"生成报价"按钮后自动计算
+2. **修改价格时**: 工序价格或数量变化导致总价变化时实时重新计算
+3. **保存报价时**: 保存报价前确保分配数据最新
+
+### 7.3 分配示例
+
+#### 示例1: 家装项目
+- **报价总价**: ¥60,000
+- **建模阶段**: ¥60,000 × 10% = ¥6,000
+- **软装渲染**: ¥60,000 × 40% = ¥24,000
+- **公司分配**: ¥60,000 × 50% = ¥30,000
+
+#### 示例2: 工装项目
+- **报价总价**: ¥85,000
+- **建模阶段**: ¥85,000 × 10% = ¥8,500
+- **软装渲染**: ¥85,000 × 40% = ¥34,000
+- **公司分配**: ¥85,000 × 50% = ¥42,500
+
+### 7.4 重要说明
+
+1. **自动计算**: 内部分配比例为系统固定规则,不支持手动调整
+2. **实时更新**: 报价总价变化时,分配金额自动同步更新
+3. **四舍五入**: 分配金额使用Math.round()四舍五入到整数
+4. **客户不可见**: 内部分配仅在系统内部显示,客户报价中不包含此信息
+
+### 7.5 技术实现
+
+内部分配功能已集成到报价编辑器组件,技术文档详见:
+- **配置文件**: `src/modules/project/config/quotation-rules.ts`
+- **PRD文档**: `docs/prd/功能-报价自动分配.md`
+- **组件实现**: `src/modules/project/components/quotation-editor.component.ts`
+
+---
+
+**文档版本**:v2.0 (新增内部执行分配规则)
+**更新日期**:2025-10-25
 **适用范围**:映三色视觉设计表现公司所有项目报价

+ 591 - 0
docs/prd/功能-报价自动分配.md

@@ -0,0 +1,591 @@
+# 报价自动分配功能需求文档 (PRD)
+
+## 一、功能概述
+
+### 1.1 功能定位
+报价自动分配功能是映三色视觉设计表现公司报价系统的核心优化模块,实现从客户报价到内部执行的自动化价格拆分与分配。
+
+### 1.2 业务价值
+- **提升效率**: 自动化价格分配,减少人工计算时间
+- **标准化流程**: 统一内部执行报价标准,避免人为偏差
+- **透明管理**: 清晰展示价格构成,便于财务核算和绩效考核
+- **利润保障**: 确保公司分配占比,保障企业利润空间
+
+---
+
+## 二、核心业务规则
+
+### 2.1 内部执行报价三级分配体系
+
+基于**三级报价总价**(一级/二级/三级客户报价),自动按以下比例拆分:
+
+| 分配类型 | 占比 | 说明 | 用途 |
+|---------|------|------|------|
+| **建模阶段** | 10% | 3D模型构建阶段 | 支付给建模设计师的费用 |
+| **软装渲染** | 40% | 软装搭配+效果图渲染 | 支付给软装和渲染设计师的费用 |
+| **公司分配** | 50% | 公司运营与利润 | 公司管理成本、平台服务费、利润等 |
+
+**计算公式:**
+```
+建模阶段金额 = 三级报价总价 × 10%
+软装渲染金额 = 三级报价总价 × 40%
+公司分配金额 = 三级报价总价 × 50%
+```
+
+### 2.2 分配示例
+
+#### 示例1: 家装平层项目
+- **项目类型**: 家装平层(现代风格)
+- **报价等级**: 三级
+- **三级报价总价**: ¥60,000
+
+**自动分配结果:**
+```
+建模阶段: ¥60,000 × 10% = ¥6,000
+软装渲染: ¥60,000 × 40% = ¥24,000
+公司分配: ¥60,000 × 50% = ¥30,000
+```
+
+#### 示例2: 工装办公空间
+- **项目类型**: 工装办公空间
+- **报价等级**: 二级
+- **三级报价总价**: ¥85,000
+
+**自动分配结果:**
+```
+建模阶段: ¥85,000 × 10% = ¥8,500
+软装渲染: ¥85,000 × 40% = ¥34,000
+公司分配: ¥85,000 × 50% = ¥42,500
+```
+
+---
+
+## 三、数据结构设计
+
+### 3.1 报价数据结构扩展
+
+在现有 `quotation` 对象基础上,新增 `allocation` 字段:
+
+```typescript
+interface Quotation {
+  spaces: Space[];              // 产品设计产品列表
+  total: number;                // 报价总额
+  spaceBreakdown: SpaceBreakdown[];  // 产品占比明细
+
+  // 新增: 内部执行分配
+  allocation?: {
+    modeling: {
+      amount: number;           // 建模阶段金额 (10%)
+      percentage: 10;           // 固定占比
+      description: string;      // '3D模型构建'
+    };
+    decoration: {
+      amount: number;           // 软装渲染金额 (40%)
+      percentage: 40;           // 固定占比
+      description: string;      // '软装搭配+效果图渲染'
+    };
+    company: {
+      amount: number;           // 公司分配金额 (50%)
+      percentage: 50;           // 固定占比
+      description: string;      // '公司运营与利润'
+    };
+    updatedAt: Date;            // 分配计算时间
+  };
+
+  generatedAt: Date;
+  validUntil: Date;
+}
+```
+
+### 3.2 Product 报价数据扩展
+
+每个产品的 `quotation` 对象也需要支持分配信息:
+
+```typescript
+interface ProductQuotation {
+  price: number;                // 产品报价
+  currency: string;             // 货币类型
+  breakdown: ProcessBreakdown;  // 工序明细
+
+  // 新增: 产品级别的内部分配
+  allocation?: {
+    modeling: number;           // 该产品的建模阶段金额
+    decoration: number;         // 该产品的软装渲染金额
+    company: number;            // 该产品的公司分配金额
+  };
+
+  status: string;
+  validUntil: Date;
+}
+```
+
+---
+
+## 四、功能实现方案
+
+### 4.1 自动分配触发时机
+
+自动分配在以下时机触发:
+
+1. **生成报价时**: 点击"生成报价"按钮后,自动计算分配
+2. **修改价格时**: 工序价格或数量变化导致总价变化时,实时重新计算
+3. **保存报价时**: 保存报价前,确保分配数据最新
+
+### 4.2 分配计算逻辑
+
+#### 步骤1: 计算项目总价
+```typescript
+function calculateTotal(): number {
+  let total = 0;
+  for (const space of quotation.spaces) {
+    for (const processKey of Object.keys(space.processes)) {
+      const process = space.processes[processKey];
+      if (process.enabled) {
+        total += process.price * process.quantity;
+      }
+    }
+  }
+  return total;
+}
+```
+
+#### 步骤2: 自动分配内部执行金额
+```typescript
+function calculateAllocation(total: number) {
+  return {
+    modeling: {
+      amount: Math.round(total * 0.10),  // 10%
+      percentage: 10,
+      description: '3D模型构建'
+    },
+    decoration: {
+      amount: Math.round(total * 0.40),  // 40%
+      percentage: 40,
+      description: '软装搭配+效果图渲染'
+    },
+    company: {
+      amount: Math.round(total * 0.50),  // 50%
+      percentage: 50,
+      description: '公司运营与利润'
+    },
+    updatedAt: new Date()
+  };
+}
+```
+
+#### 步骤3: 产品级别分配
+```typescript
+function calculateProductAllocation(productPrice: number) {
+  return {
+    modeling: Math.round(productPrice * 0.10),
+    decoration: Math.round(productPrice * 0.40),
+    company: Math.round(productPrice * 0.50)
+  };
+}
+```
+
+### 4.3 UI展示设计
+
+#### 位置1: 报价汇总区域
+在现有的"报价汇总"模块中,新增"内部执行分配"折叠面板:
+
+```html
+<!-- 报价汇总 -->
+<div class="quotation-summary">
+  <!-- 现有的产品占比明细 -->
+  ...
+
+  <!-- 新增: 内部执行分配 -->
+  <div class="allocation-section">
+    <div class="allocation-header">
+      <h4>内部执行分配</h4>
+      <button (click)="toggleAllocation()">
+        {{ showAllocation ? '隐藏' : '显示' }}
+      </button>
+    </div>
+
+    @if (showAllocation && quotation.allocation) {
+      <div class="allocation-list">
+        <div class="allocation-item modeling">
+          <div class="allocation-info">
+            <span class="allocation-name">建模阶段</span>
+            <span class="allocation-desc">3D模型构建</span>
+          </div>
+          <div class="allocation-values">
+            <span class="allocation-percentage">10%</span>
+            <span class="allocation-amount">{{ formatPrice(quotation.allocation.modeling.amount) }}</span>
+          </div>
+        </div>
+
+        <div class="allocation-item decoration">
+          <div class="allocation-info">
+            <span class="allocation-name">软装渲染</span>
+            <span class="allocation-desc">软装搭配+效果图渲染</span>
+          </div>
+          <div class="allocation-values">
+            <span class="allocation-percentage">40%</span>
+            <span class="allocation-amount">{{ formatPrice(quotation.allocation.decoration.amount) }}</span>
+          </div>
+        </div>
+
+        <div class="allocation-item company">
+          <div class="allocation-info">
+            <span class="allocation-name">公司分配</span>
+            <span class="allocation-desc">公司运营与利润</span>
+          </div>
+          <div class="allocation-values">
+            <span class="allocation-percentage">50%</span>
+            <span class="allocation-amount">{{ formatPrice(quotation.allocation.company.amount) }}</span>
+          </div>
+        </div>
+      </div>
+    }
+  </div>
+
+  <!-- 报价总额 -->
+  <div class="total-section">
+    ...
+  </div>
+</div>
+```
+
+#### 位置2: 产品详情(可选)
+在每个产品卡片的工序明细下方,可展示该产品的分配明细:
+
+```html
+<div class="product-allocation-hint">
+  <svg class="icon" viewBox="0 0 512 512">...</svg>
+  <span>该产品内部分配: 建模 ¥{{ product.allocation.modeling }} / 软装渲染 ¥{{ product.allocation.decoration }} / 公司 ¥{{ product.allocation.company }}</span>
+</div>
+```
+
+---
+
+## 五、样式设计
+
+### 5.1 分配列表样式
+
+```scss
+.allocation-section {
+  margin-top: 20px;
+  padding-top: 20px;
+  border-top: 1px solid var(--ion-color-light-shade);
+
+  .allocation-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 16px;
+
+    h4 {
+      margin: 0;
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--ion-color-dark);
+    }
+  }
+
+  .allocation-list {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+
+    .allocation-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 12px 16px;
+      border-radius: 8px;
+      border-left: 4px solid;
+      background: var(--ion-color-light-tint);
+
+      &.modeling {
+        border-left-color: #8B5CF6; // 紫色 - 建模
+        background: rgba(139, 92, 246, 0.05);
+      }
+
+      &.decoration {
+        border-left-color: #F59E0B; // 橙色 - 软装渲染
+        background: rgba(245, 158, 11, 0.05);
+      }
+
+      &.company {
+        border-left-color: #10B981; // 绿色 - 公司分配
+        background: rgba(16, 185, 129, 0.05);
+      }
+
+      .allocation-info {
+        display: flex;
+        flex-direction: column;
+        gap: 4px;
+
+        .allocation-name {
+          font-size: 15px;
+          font-weight: 600;
+          color: var(--ion-color-dark);
+        }
+
+        .allocation-desc {
+          font-size: 12px;
+          color: var(--ion-color-medium);
+        }
+      }
+
+      .allocation-values {
+        display: flex;
+        align-items: baseline;
+        gap: 12px;
+
+        .allocation-percentage {
+          font-size: 14px;
+          font-weight: 500;
+          color: var(--ion-color-medium);
+          min-width: 40px;
+          text-align: right;
+        }
+
+        .allocation-amount {
+          font-size: 18px;
+          font-weight: 700;
+          color: var(--ion-color-dark);
+        }
+      }
+    }
+  }
+}
+```
+
+### 5.2 移动端适配
+
+```scss
+@media (max-width: 768px) {
+  .allocation-item {
+    flex-direction: column;
+    align-items: flex-start !important;
+    gap: 8px;
+
+    .allocation-values {
+      width: 100%;
+      justify-content: space-between;
+    }
+  }
+}
+```
+
+---
+
+## 六、权限控制
+
+### 6.1 可见性权限
+- **全部角色可见**: 内部执行分配信息对内部所有角色可见
+- **客户不可见**: 外部客户看到的报价中,不包含内部分配信息
+
+### 6.2 编辑权限
+- **系统自动计算**: 内部分配比例(10%-40%-50%)为系统固定,不支持手动修改
+- **管理员可调整**: 仅管理员可在系统配置中调整分配比例(未来扩展功能)
+
+---
+
+## 七、技术实现要点
+
+### 7.1 配置文件更新
+
+在 `quotation-rules.ts` 中新增分配规则配置:
+
+```typescript
+/**
+ * 内部执行分配规则
+ */
+export const ALLOCATION_RULES = {
+  modeling: {
+    percentage: 0.10,
+    label: '建模阶段',
+    description: '3D模型构建',
+    color: '#8B5CF6'
+  },
+  decoration: {
+    percentage: 0.40,
+    label: '软装渲染',
+    description: '软装搭配+效果图渲染',
+    color: '#F59E0B'
+  },
+  company: {
+    percentage: 0.50,
+    label: '公司分配',
+    description: '公司运营与利润',
+    color: '#10B981'
+  }
+};
+
+/**
+ * 计算内部执行分配
+ */
+export function calculateAllocation(totalPrice: number) {
+  return {
+    modeling: {
+      amount: Math.round(totalPrice * ALLOCATION_RULES.modeling.percentage),
+      percentage: ALLOCATION_RULES.modeling.percentage * 100,
+      description: ALLOCATION_RULES.modeling.description,
+      color: ALLOCATION_RULES.modeling.color
+    },
+    decoration: {
+      amount: Math.round(totalPrice * ALLOCATION_RULES.decoration.percentage),
+      percentage: ALLOCATION_RULES.decoration.percentage * 100,
+      description: ALLOCATION_RULES.decoration.description,
+      color: ALLOCATION_RULES.decoration.color
+    },
+    company: {
+      amount: Math.round(totalPrice * ALLOCATION_RULES.company.percentage),
+      percentage: ALLOCATION_RULES.company.percentage * 100,
+      description: ALLOCATION_RULES.company.description,
+      color: ALLOCATION_RULES.company.color
+    },
+    updatedAt: new Date()
+  };
+}
+```
+
+### 7.2 组件方法扩展
+
+在 `QuotationEditorComponent` 中新增方法:
+
+```typescript
+// UI 状态
+showAllocation: boolean = false;
+
+// 计算总价时同步更新分配
+calculateTotal() {
+  // ... 现有逻辑 ...
+
+  // 自动计算内部执行分配
+  this.quotation.allocation = calculateAllocation(this.quotation.total);
+  this.updateProductBreakdown();
+}
+
+// 切换分配显示
+toggleAllocation() {
+  this.showAllocation = !this.showAllocation;
+}
+
+// 更新产品级别的分配
+private updateProductAllocation(product: any, productPrice: number) {
+  const quotation = product.get('quotation') || {};
+  quotation.allocation = {
+    modeling: Math.round(productPrice * 0.10),
+    decoration: Math.round(productPrice * 0.40),
+    company: Math.round(productPrice * 0.50)
+  };
+  product.set('quotation', quotation);
+}
+```
+
+---
+
+## 八、测试用例
+
+### 8.1 单元测试
+
+#### 测试用例1: 分配计算准确性
+```typescript
+test('should calculate allocation correctly', () => {
+  const total = 60000;
+  const allocation = calculateAllocation(total);
+
+  expect(allocation.modeling.amount).toBe(6000);   // 10%
+  expect(allocation.decoration.amount).toBe(24000); // 40%
+  expect(allocation.company.amount).toBe(30000);    // 50%
+});
+```
+
+#### 测试用例2: 四舍五入处理
+```typescript
+test('should round allocation amounts', () => {
+  const total = 12345;
+  const allocation = calculateAllocation(total);
+
+  expect(allocation.modeling.amount).toBe(1235);   // 10% rounded
+  expect(allocation.decoration.amount).toBe(4938); // 40% rounded
+  expect(allocation.company.amount).toBe(6173);    // 50% rounded
+});
+```
+
+### 8.2 集成测试
+
+#### 测试用例3: 生成报价后自动计算分配
+```typescript
+test('should auto-calculate allocation after generating quotation', async () => {
+  const component = new QuotationEditorComponent(...);
+  await component.generateQuotationFromProducts();
+
+  expect(component.quotation.allocation).toBeDefined();
+  expect(component.quotation.allocation.modeling.amount).toBeGreaterThan(0);
+});
+```
+
+---
+
+## 九、用户使用流程
+
+### 9.1 报价生成流程
+
+1. 客服在项目详情页打开报价编辑器
+2. 点击"生成报价"按钮,系统自动:
+   - 根据产品列表生成工序明细
+   - 计算报价总额
+   - **自动计算内部执行分配 (10%-40%-50%)**
+   - 保存到项目数据
+3. 在"报价汇总"区域查看:
+   - 报价总额
+   - 产品占比明细
+   - **内部执行分配明细(新增)**
+
+### 9.2 分配信息查看流程
+
+1. 展开"报价汇总"模块
+2. 点击"内部执行分配"区域的"显示"按钮
+3. 查看三类分配及其金额:
+   - 建模阶段: 10% + 具体金额
+   - 软装渲染: 40% + 具体金额
+   - 公司分配: 50% + 具体金额
+
+---
+
+## 十、未来扩展
+
+### 10.1 动态比例配置(Phase 2)
+- 支持管理员在系统设置中调整分配比例
+- 支持不同项目类型设置不同分配规则
+- 支持历史分配规则版本管理
+
+### 10.2 设计师绩效对接(Phase 3)
+- 建模设计师绩效 = 所有项目的建模阶段金额总和
+- 软装渲染设计师绩效 = 所有项目的软装渲染金额总和
+- 自动生成设计师收入报表
+
+### 10.3 财务报表集成(Phase 4)
+- 自动生成内部分配财务报表
+- 支持按部门、按时间段统计分配金额
+- 支持导出Excel财务报表
+
+---
+
+## 十一、FAQ
+
+### Q1: 分配比例是否可以手动调整?
+**A:** 当前版本不支持。内部分配比例(10%-40%-50%)为系统固定规则,确保所有项目的一致性和公平性。
+
+### Q2: 如果总价有小数,如何处理分配金额?
+**A:** 使用 `Math.round()` 四舍五入到整数。例如: ¥12345 × 10% = ¥1234.5 → ¥1235
+
+### Q3: 客户是否能看到内部分配信息?
+**A:** 不能。内部分配仅在系统内部显示,客户看到的报价只包含产品和工序明细,不包含内部分配。
+
+### Q4: 分配信息何时更新?
+**A:** 每次报价总额发生变化时(生成报价、修改工序价格、保存报价),系统自动重新计算分配。
+
+---
+
+**文档版本**: v1.0
+**创建日期**: 2025-10-25
+**作者**: Claude Code AI
+**审核状态**: 待审核

+ 74 - 0
src/modules/project/components/quotation-editor.component.html

@@ -341,6 +341,80 @@
         </div>
       }
 
+      <!-- 内部执行分配 -->
+      @if (quotation.allocation) {
+        <div class="allocation-section">
+          <div class="allocation-header">
+            <h4>内部执行分配</h4>
+            <button class="btn-text" (click)="toggleAllocation()">
+              {{ showAllocation ? '隐藏' : '显示' }}
+            </button>
+          </div>
+
+          @if (showAllocation) {
+            <div class="allocation-list">
+              <!-- 建模阶段 -->
+              <div class="allocation-item modeling">
+                <div class="allocation-icon">
+                  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                    <path fill="currentColor" d="M234.5 5.709C248.4 .7377 263.6 .7377 277.5 5.709L469.5 74.28C494.1 83.38 512 107.5 512 134.6C512 161.7 494.1 185.8 469.5 194.9L277.5 263.5C263.6 268.5 248.4 268.5 234.5 263.5L42.47 194.9C17.05 185.8 0 161.7 0 134.6C0 107.5 17.05 83.38 42.47 74.28L234.5 5.709zM256 65.98L82.34 128L256 190L429.7 128L256 65.98zM288 434.6L469.5 346.9C494.1 337.8 512 313.7 512 286.6V214.3C512 194.1 492.9 180.1 473.6 185.8C462.4 189.2 454.8 200.3 454.8 212.8L454.8 286.6C454.8 295.9 449.1 304.3 440.8 308.3L288 375.4V434.6zM170.4 308.3C162.1 304.3 156.3 295.9 156.3 286.6V212.8C156.3 200.3 148.6 189.2 137.4 185.8C118.1 180.1 99.13 194.1 99.13 214.3V286.6C99.13 313.7 116.2 337.8 141.6 346.9L223.9 375.4V434.6C223.9 454.9 242.1 469.8 261.4 464.1C272.6 460.7 280.2 449.6 280.2 437.1V375.4L170.4 308.3z"/>
+                  </svg>
+                </div>
+                <div class="allocation-info">
+                  <span class="allocation-name">{{ allocationRules.modeling.label }}</span>
+                  <span class="allocation-desc">{{ allocationRules.modeling.description }}</span>
+                </div>
+                <div class="allocation-values">
+                  <span class="allocation-percentage">{{ quotation.allocation.modeling.percentage }}%</span>
+                  <span class="allocation-amount">{{ formatPrice(quotation.allocation.modeling.amount) }}</span>
+                </div>
+              </div>
+
+              <!-- 软装渲染 -->
+              <div class="allocation-item decoration">
+                <div class="allocation-icon">
+                  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                    <path fill="currentColor" d="M384 160C366.3 160 352 145.7 352 128C352 110.3 366.3 96 384 96C401.7 96 416 110.3 416 128C416 145.7 401.7 160 384 160zM384 64C348.7 64 320 92.65 320 128C320 163.3 348.7 192 384 192C419.3 192 448 163.3 448 128C448 92.65 419.3 64 384 64zM456 288H384V216C384 207.2 376.8 200 368 200H144C135.2 200 128 207.2 128 216V288H56C42.75 288 32 298.8 32 312V472C32 485.3 42.75 496 56 496H144H368H456C469.3 496 480 485.3 480 472V312C480 298.8 469.3 288 456 288zM144 464H64V320H144V464zM336 464H176V232H336V464zM448 464H368V320H448V464z"/>
+                  </svg>
+                </div>
+                <div class="allocation-info">
+                  <span class="allocation-name">{{ allocationRules.decoration.label }}</span>
+                  <span class="allocation-desc">{{ allocationRules.decoration.description }}</span>
+                </div>
+                <div class="allocation-values">
+                  <span class="allocation-percentage">{{ quotation.allocation.decoration.percentage }}%</span>
+                  <span class="allocation-amount">{{ formatPrice(quotation.allocation.decoration.amount) }}</span>
+                </div>
+              </div>
+
+              <!-- 公司分配 -->
+              <div class="allocation-item company">
+                <div class="allocation-icon">
+                  <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                    <path fill="currentColor" d="M320 32c-8.1 0-16.1 1.4-23.7 4.1L15.8 137.4C6.3 140.9 0 149.9 0 160s6.3 19.1 15.8 22.6l57.9 20.9C57.3 229.3 48 259.8 48 291.9v28.1c0 28.4-10.8 57.7-22.3 80.8c-6.5 13-13.9 25.8-22.5 37.6C0 442.7-.9 448.3 .9 453.4s6 8.9 11.2 10.2l64 16c4.2 1.1 8.7 .3 12.4-2s6.3-6.1 7.1-10.4c8.6-42.8 4.3-81.2-2.1-108.7C90.3 344.3 86 329.8 80 316.5V295.6c0-4.2 .3-8.4 1-12.4L288.1 406.6c8.4 3.2 17.4 4.8 26.6 4.8H448c17.7 0 32-14.3 32-32V256c0-17.7-14.3-32-32-32H448c-17.7 0-32-14.3-32-32s14.3-32 32-32h32c9.7 0 18.5-5.8 22.2-14.8s1.7-19.3-5.2-26.2l-64-64c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8h32c17.7 0 32 14.3 32 32s-14.3 32-32 32H320c-8.8 0-16 7.2-16 16v64c0 8.8 7.2 16 16 16h128v64H320z"/>
+                  </svg>
+                </div>
+                <div class="allocation-info">
+                  <span class="allocation-name">{{ allocationRules.company.label }}</span>
+                  <span class="allocation-desc">{{ allocationRules.company.description }}</span>
+                </div>
+                <div class="allocation-values">
+                  <span class="allocation-percentage">{{ quotation.allocation.company.percentage }}%</span>
+                  <span class="allocation-amount">{{ formatPrice(quotation.allocation.company.amount) }}</span>
+                </div>
+              </div>
+            </div>
+
+            <div class="allocation-note">
+              <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+                <path fill="currentColor" d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208c-17.7 0-32 14.3-32 32s14.3 32 32 32s32-14.3 32-32s-14.3-32-32-32z"/>
+              </svg>
+              <span>内部执行分配为系统自动计算,基于报价总额按固定比例拆分</span>
+            </div>
+          }
+        </div>
+      }
+
       <div class="total-section">
         <div class="total-row">
           <span class="total-label">报价总额</span>

+ 244 - 0
src/modules/project/components/quotation-editor.component.scss

@@ -783,6 +783,180 @@
       }
     }
 
+    // 内部执行分配区域
+    .allocation-section {
+      margin-top: 20px;
+      padding-top: 20px;
+      border-top: 1px solid var(--ion-color-light-shade);
+
+      .allocation-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 16px;
+
+        h4 {
+          margin: 0;
+          font-size: 16px;
+          font-weight: 600;
+          color: var(--ion-color-dark);
+        }
+
+        .btn-text {
+          background: none;
+          border: none;
+          color: var(--ion-color-primary);
+          font-size: 14px;
+          cursor: pointer;
+          padding: 4px 8px;
+          border-radius: 4px;
+          transition: background-color 0.2s;
+
+          &:hover {
+            background-color: var(--ion-color-primary-tint);
+          }
+        }
+      }
+
+      .allocation-list {
+        display: flex;
+        flex-direction: column;
+        gap: 12px;
+        margin-bottom: 16px;
+
+        .allocation-item {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+          padding: 16px;
+          border-radius: 10px;
+          border-left: 4px solid;
+          background: var(--ion-color-light-tint);
+          transition: all 0.2s ease;
+
+          &:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+          }
+
+          &.modeling {
+            border-left-color: #8B5CF6; // 紫色 - 建模
+            background: rgba(139, 92, 246, 0.05);
+
+            .allocation-icon {
+              background: rgba(139, 92, 246, 0.1);
+              color: #8B5CF6;
+            }
+          }
+
+          &.decoration {
+            border-left-color: #F59E0B; // 橙色 - 软装渲染
+            background: rgba(245, 158, 11, 0.05);
+
+            .allocation-icon {
+              background: rgba(245, 158, 11, 0.1);
+              color: #F59E0B;
+            }
+          }
+
+          &.company {
+            border-left-color: #10B981; // 绿色 - 公司分配
+            background: rgba(16, 185, 129, 0.05);
+
+            .allocation-icon {
+              background: rgba(16, 185, 129, 0.1);
+              color: #10B981;
+            }
+          }
+
+          .allocation-icon {
+            width: 48px;
+            height: 48px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            border-radius: 10px;
+            flex-shrink: 0;
+
+            .icon {
+              width: 24px;
+              height: 24px;
+            }
+          }
+
+          .allocation-info {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            gap: 4px;
+
+            .allocation-name {
+              font-size: 15px;
+              font-weight: 600;
+              color: var(--ion-color-dark);
+              line-height: 1.2;
+            }
+
+            .allocation-desc {
+              font-size: 12px;
+              color: var(--ion-color-medium);
+              line-height: 1.3;
+            }
+          }
+
+          .allocation-values {
+            display: flex;
+            flex-direction: column;
+            align-items: flex-end;
+            gap: 2px;
+
+            .allocation-percentage {
+              font-size: 13px;
+              font-weight: 500;
+              color: var(--ion-color-medium-shade);
+              background: rgba(0, 0, 0, 0.04);
+              padding: 2px 8px;
+              border-radius: 12px;
+              min-width: 45px;
+              text-align: center;
+            }
+
+            .allocation-amount {
+              font-size: 20px;
+              font-weight: 700;
+              color: var(--ion-color-dark);
+              line-height: 1.2;
+            }
+          }
+        }
+      }
+
+      .allocation-note {
+        display: flex;
+        align-items: flex-start;
+        gap: 10px;
+        padding: 12px 16px;
+        background: rgba(56, 128, 255, 0.08);
+        border-radius: 8px;
+        border-left: 3px solid var(--ion-color-primary);
+
+        .icon {
+          width: 18px;
+          height: 18px;
+          color: var(--ion-color-primary);
+          flex-shrink: 0;
+          margin-top: 2px;
+        }
+
+        span {
+          flex: 1;
+          font-size: 13px;
+          color: var(--ion-color-medium-shade);
+          line-height: 1.5;
+        }
+      }
+    }
+
     .breakdown-list {
       margin-bottom: 16px;
 
@@ -877,4 +1051,74 @@
   .space-content {
     @extend .product-content;
   }
+
+  // 移动端适配
+  @media (max-width: 768px) {
+    .allocation-section {
+      .allocation-list {
+        .allocation-item {
+          flex-wrap: wrap;
+          padding: 14px;
+
+          .allocation-icon {
+            width: 40px;
+            height: 40px;
+
+            .icon {
+              width: 20px;
+              height: 20px;
+            }
+          }
+
+          .allocation-info {
+            flex: 1;
+            min-width: 0;
+
+            .allocation-name {
+              font-size: 14px;
+            }
+
+            .allocation-desc {
+              font-size: 11px;
+            }
+          }
+
+          .allocation-values {
+            width: 100%;
+            flex-direction: row;
+            justify-content: space-between;
+            align-items: center;
+            margin-top: 8px;
+            padding-top: 8px;
+            border-top: 1px solid rgba(0, 0, 0, 0.06);
+
+            .allocation-percentage {
+              font-size: 12px;
+            }
+
+            .allocation-amount {
+              font-size: 18px;
+            }
+          }
+        }
+      }
+
+      .allocation-note {
+        padding: 10px 12px;
+
+        .icon {
+          width: 16px;
+          height: 16px;
+        }
+
+        span {
+          font-size: 12px;
+        }
+      }
+    }
+
+    .product-content .process-grid {
+      grid-template-columns: 1fr;
+    }
+  }
 }

+ 35 - 0
src/modules/project/components/quotation-editor.component.ts

@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { FmodeParse } from 'fmode-ng/parse';
 import { Subscription } from 'rxjs';
+import { calculateAllocation, calculateProductAllocation, ALLOCATION_RULES, type Allocation } from '../config/quotation-rules';
 
 const Parse = FmodeParse.with('nova');
 
@@ -55,6 +56,7 @@ export class QuotationEditorComponent implements OnInit, OnChanges, OnDestroy {
     spaces: [], // 兼容旧格式,现在基于products
     total: 0,
     spaceBreakdown: [], // 产品占比明细
+    allocation: null as Allocation | null, // 内部执行分配
     generatedAt: null,
     validUntil: null
   };
@@ -72,6 +74,10 @@ export class QuotationEditorComponent implements OnInit, OnChanges, OnDestroy {
 
   // UI状态
   showBreakdown: boolean = false;
+  showAllocation: boolean = false; // 显示/隐藏内部执行分配
+
+  // 分配规则配置
+  allocationRules = ALLOCATION_RULES;
 
   // 报价配置
   priceTable: any = {};
@@ -502,10 +508,32 @@ export class QuotationEditorComponent implements OnInit, OnChanges, OnDestroy {
       }
     }
     this.quotation.total = total;
+
+    // 自动计算内部执行分配
+    this.quotation.allocation = calculateAllocation(total);
+
+    // 更新产品级别的分配
+    this.updateProductsAllocation();
+
     this.quotationChange.emit(this.quotation);
     this.totalChange.emit(total);
   }
 
+  /**
+   * 更新所有产品的内部分配
+   */
+  private updateProductsAllocation() {
+    for (const space of this.quotation.spaces) {
+      const product = this.products.find(p => p.id === space.productId);
+      if (product) {
+        const productPrice = this.calculateSpaceSubtotal(space);
+        const quotation = product.get('quotation') || {};
+        quotation.allocation = calculateProductAllocation(productPrice);
+        product.set('quotation', quotation);
+      }
+    }
+  }
+
   /**
    * 计算空间小计(保持兼容性)
    */
@@ -878,6 +906,13 @@ export class QuotationEditorComponent implements OnInit, OnChanges, OnDestroy {
     return breakdown?.percentage || 0;
   }
 
+  /**
+   * 切换内部执行分配显示
+   */
+  toggleAllocation() {
+    this.showAllocation = !this.showAllocation;
+  }
+
   /**
    * 从传入的项目对象初始化数据
    */

+ 103 - 0
src/modules/project/config/quotation-rules.ts

@@ -423,3 +423,106 @@ export function getDefaultProcesses(projectType: '家装' | '工装', finalPrice
     }
   };
 }
+
+/**
+ * 内部执行分配规则配置
+ *
+ * 报价细项优化: 将三级报价总价自动拆分为内部执行三个阶段
+ * - 建模阶段: 10%
+ * - 软装渲染: 40%
+ * - 公司分配: 50%
+ */
+export const ALLOCATION_RULES = {
+  modeling: {
+    percentage: 0.10,
+    label: '建模阶段',
+    description: '3D模型构建',
+    color: '#8B5CF6', // 紫色
+    icon: 'cube-outline'
+  },
+  decoration: {
+    percentage: 0.40,
+    label: '软装渲染',
+    description: '软装搭配+效果图渲染',
+    color: '#F59E0B', // 橙色
+    icon: 'color-palette-outline'
+  },
+  company: {
+    percentage: 0.50,
+    label: '公司分配',
+    description: '公司运营与利润',
+    color: '#10B981', // 绿色
+    icon: 'business-outline'
+  }
+} as const;
+
+/**
+ * 内部执行分配接口定义
+ */
+export interface AllocationItem {
+  amount: number;
+  percentage: number;
+  description: string;
+  color: string;
+}
+
+export interface Allocation {
+  modeling: AllocationItem;
+  decoration: AllocationItem;
+  company: AllocationItem;
+  updatedAt: Date;
+}
+
+/**
+ * 计算内部执行分配
+ *
+ * @param totalPrice 报价总价
+ * @returns 内部执行分配对象
+ *
+ * @example
+ * ```typescript
+ * const allocation = calculateAllocation(60000);
+ * // {
+ * //   modeling: { amount: 6000, percentage: 10, ... },
+ * //   decoration: { amount: 24000, percentage: 40, ... },
+ * //   company: { amount: 30000, percentage: 50, ... }
+ * // }
+ * ```
+ */
+export function calculateAllocation(totalPrice: number): Allocation {
+  return {
+    modeling: {
+      amount: Math.round(totalPrice * ALLOCATION_RULES.modeling.percentage),
+      percentage: ALLOCATION_RULES.modeling.percentage * 100,
+      description: ALLOCATION_RULES.modeling.description,
+      color: ALLOCATION_RULES.modeling.color
+    },
+    decoration: {
+      amount: Math.round(totalPrice * ALLOCATION_RULES.decoration.percentage),
+      percentage: ALLOCATION_RULES.decoration.percentage * 100,
+      description: ALLOCATION_RULES.decoration.description,
+      color: ALLOCATION_RULES.decoration.color
+    },
+    company: {
+      amount: Math.round(totalPrice * ALLOCATION_RULES.company.percentage),
+      percentage: ALLOCATION_RULES.company.percentage * 100,
+      description: ALLOCATION_RULES.company.description,
+      color: ALLOCATION_RULES.company.color
+    },
+    updatedAt: new Date()
+  };
+}
+
+/**
+ * 计算产品级别的内部分配
+ *
+ * @param productPrice 产品报价
+ * @returns 产品内部分配对象
+ */
+export function calculateProductAllocation(productPrice: number) {
+  return {
+    modeling: Math.round(productPrice * ALLOCATION_RULES.modeling.percentage),
+    decoration: Math.round(productPrice * ALLOCATION_RULES.decoration.percentage),
+    company: Math.round(productPrice * ALLOCATION_RULES.company.percentage)
+  };
+}