quotation-details.component.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { FormsModule } from '@angular/forms';
  4. import { WorkHourService } from '../../../../../services/work-hour.service';
  5. import { QuotationApprovalService } from '../../../../../services/quotation-approval.service';
  6. import { SpaceType, ProjectStyle, AutoQuotationResult, PricingScript } from '../../../../../models/work-hour.model';
  7. export interface QuotationItem {
  8. id: string;
  9. category: string;
  10. name: string;
  11. quantity: number;
  12. unit: string;
  13. unitPrice: number;
  14. totalPrice?: number; // 添加总价属性
  15. description: string;
  16. }
  17. export interface QuotationData {
  18. items: QuotationItem[];
  19. totalAmount: number;
  20. materialCost: number;
  21. laborCost: number;
  22. designFee: number;
  23. managementFee: number;
  24. }
  25. @Component({
  26. selector: 'app-quotation-details',
  27. standalone: true,
  28. imports: [CommonModule, FormsModule],
  29. templateUrl: './quotation-details.component.html',
  30. styleUrls: ['./quotation-details.component.scss']
  31. })
  32. export class QuotationDetailsComponent implements OnInit {
  33. @Input() initialData?: QuotationData;
  34. @Input() projectData?: any; // 添加项目数据输入属性
  35. @Output() dataChange = new EventEmitter<QuotationData>();
  36. quotationData: QuotationData = {
  37. items: [],
  38. totalAmount: 0,
  39. materialCost: 0,
  40. laborCost: 0,
  41. designFee: 0,
  42. managementFee: 0
  43. };
  44. // 新增:自动报价相关
  45. showAutoQuotationModal = false;
  46. autoQuotationParams = {
  47. spaceType: '客餐厅' as SpaceType,
  48. projectStyle: '现代简约' as ProjectStyle,
  49. estimatedWorkDays: 3,
  50. projectArea: 100
  51. };
  52. // 报价话术库
  53. showScriptModal = false;
  54. pricingScripts: PricingScript[] = [];
  55. selectedScript: PricingScript | null = null;
  56. constructor(
  57. private workHourService: WorkHourService,
  58. private quotationApprovalService: QuotationApprovalService
  59. ) {}
  60. ngOnInit() {
  61. if (this.initialData) {
  62. this.quotationData = { ...this.initialData };
  63. }
  64. // 加载报价话术库
  65. this.loadPricingScripts();
  66. }
  67. // 添加报价项目
  68. addQuotationItem() {
  69. const newItem: QuotationItem = {
  70. id: Date.now().toString(),
  71. category: '客餐厅',
  72. name: '',
  73. quantity: 1,
  74. unit: '㎡',
  75. unitPrice: 0,
  76. description: ''
  77. };
  78. this.quotationData.items.push(newItem);
  79. this.calculateTotal();
  80. this.emitDataChange();
  81. }
  82. // 删除报价项目
  83. removeQuotationItem(index: number) {
  84. this.quotationData.items.splice(index, 1);
  85. this.calculateTotal();
  86. this.emitDataChange();
  87. }
  88. // 复制报价项目
  89. duplicateQuotationItem(index: number) {
  90. const originalItem = this.quotationData.items[index];
  91. const duplicatedItem: QuotationItem = {
  92. ...originalItem,
  93. id: Date.now().toString(),
  94. name: originalItem.name + ' (副本)'
  95. };
  96. this.quotationData.items.splice(index + 1, 0, duplicatedItem);
  97. this.calculateTotal();
  98. this.emitDataChange();
  99. }
  100. // 计算总金额
  101. calculateTotal() {
  102. const materialCost = this.quotationData.items.reduce((sum, item) => sum + (item.quantity * item.unitPrice), 0);
  103. this.quotationData.materialCost = materialCost;
  104. this.quotationData.laborCost = materialCost * 0.3; // 人工费按材料费30%计算
  105. this.quotationData.designFee = materialCost * 0.05; // 设计费按材料费5%计算
  106. this.quotationData.managementFee = materialCost * 0.08; // 管理费按材料费8%计算
  107. this.quotationData.totalAmount = this.quotationData.materialCost + this.quotationData.laborCost + this.quotationData.designFee + this.quotationData.managementFee;
  108. }
  109. // 获取总金额
  110. getTotalAmount(): number {
  111. return this.quotationData.totalAmount;
  112. }
  113. // 获取材料费用
  114. getMaterialCost(): number {
  115. return this.quotationData.materialCost;
  116. }
  117. // 获取人工费用
  118. getLaborCost(): number {
  119. return this.quotationData.laborCost;
  120. }
  121. // 格式化金额显示
  122. formatAmount(amount: number): string {
  123. return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  124. }
  125. // 项目数据变化处理
  126. onItemChange() {
  127. this.calculateTotal();
  128. this.emitDataChange();
  129. }
  130. // 发送数据变化事件
  131. private emitDataChange() {
  132. this.dataChange.emit({ ...this.quotationData });
  133. }
  134. // 加载报价话术库
  135. loadPricingScripts() {
  136. this.workHourService.getPricingScripts().subscribe(scripts => {
  137. this.pricingScripts = scripts;
  138. });
  139. }
  140. // 显示自动报价模态框
  141. showAutoQuotation() {
  142. this.showAutoQuotationModal = true;
  143. }
  144. // 关闭自动报价模态框
  145. hideAutoQuotation() {
  146. this.showAutoQuotationModal = false;
  147. }
  148. // 生成自动报价
  149. generateAutoQuotation() {
  150. this.workHourService.generateAutoQuotation(
  151. this.autoQuotationParams.spaceType,
  152. this.autoQuotationParams.projectStyle,
  153. this.autoQuotationParams.estimatedWorkDays
  154. ).subscribe({
  155. next: (result: AutoQuotationResult) => {
  156. // 将自动报价结果转换为报价项目
  157. const autoItem: QuotationItem = {
  158. id: Date.now().toString(),
  159. category: result.spaceType,
  160. name: `${result.projectStyle}${result.spaceType}设计`,
  161. quantity: result.estimatedWorkDays,
  162. unit: '人天',
  163. unitPrice: result.finalPrice / result.estimatedWorkDays,
  164. description: `包含服务:${result.serviceIncludes.join('、')}`
  165. };
  166. // 添加到报价项目列表
  167. this.quotationData.items.push(autoItem);
  168. this.calculateTotal();
  169. this.emitDataChange();
  170. this.hideAutoQuotation();
  171. },
  172. error: (error) => {
  173. console.error('自动报价生成失败:', error);
  174. }
  175. });
  176. }
  177. // 显示话术库模态框
  178. showScriptLibrary() {
  179. this.showScriptModal = true;
  180. }
  181. // 关闭话术库模态框
  182. hideScriptLibrary() {
  183. this.showScriptModal = false;
  184. this.selectedScript = null;
  185. }
  186. // 选择话术
  187. selectScript(script: PricingScript) {
  188. this.selectedScript = script;
  189. // 记录话术使用
  190. if (this.projectData?.customerInfo?.name) {
  191. this.quotationApprovalService.recordScriptUsage(
  192. 'current_quotation', // 这里应该是实际的报价ID
  193. script.id,
  194. 'current_user', // 这里应该是当前用户ID
  195. '当前用户' // 这里应该是当前用户名
  196. ).subscribe();
  197. }
  198. }
  199. // 复制话术内容
  200. copyScript(script: PricingScript) {
  201. navigator.clipboard.writeText(script.content).then(() => {
  202. console.log('话术内容已复制到剪贴板');
  203. // 记录话术使用
  204. if (this.projectData?.customerInfo?.name) {
  205. this.quotationApprovalService.recordScriptUsage(
  206. 'current_quotation',
  207. script.id,
  208. 'current_user',
  209. '当前用户'
  210. ).subscribe();
  211. }
  212. });
  213. }
  214. copyScriptContent() {
  215. if (this.selectedScript) {
  216. navigator.clipboard.writeText(this.selectedScript.content).then(() => {
  217. console.log('话术内容已复制到剪贴板');
  218. // 记录话术使用
  219. if (this.projectData?.customerInfo?.name) {
  220. this.quotationApprovalService.recordScriptUsage(
  221. 'current_quotation',
  222. this.selectedScript!.id,
  223. 'current_user',
  224. '当前用户'
  225. ).subscribe();
  226. }
  227. });
  228. }
  229. }
  230. // 提交报价审核
  231. submitForApproval() {
  232. if (!this.projectData?.customerInfo?.name || this.quotationData.totalAmount === 0) {
  233. console.warn('报价信息不完整,无法提交审核');
  234. return;
  235. }
  236. const submissionData = {
  237. quotationId: `q${Date.now()}`,
  238. projectId: this.projectData.projectId || `p${Date.now()}`,
  239. projectName: this.projectData.requirementInfo?.projectName || '未命名项目',
  240. customerName: this.projectData.customerInfo.name,
  241. submittedBy: '当前用户', // 这里应该是当前用户名
  242. totalAmount: this.quotationData.totalAmount,
  243. scriptUsed: this.selectedScript?.id
  244. };
  245. this.quotationApprovalService.submitQuotationForApproval(submissionData).subscribe({
  246. next: (approval) => {
  247. console.log('报价已提交审核:', approval);
  248. // 可以显示成功提示
  249. },
  250. error: (error) => {
  251. console.error('提交审核失败:', error);
  252. // 可以显示错误提示
  253. }
  254. });
  255. }
  256. // 获取空间类型选项
  257. getSpaceTypeOptions(): { value: SpaceType; label: string }[] {
  258. return [
  259. { value: '客餐厅', label: '客餐厅' },
  260. { value: '卧室', label: '卧室' },
  261. { value: '厨房', label: '厨房' },
  262. { value: '卫生间', label: '卫生间' },
  263. { value: '书房', label: '书房' },
  264. { value: '阳台', label: '阳台' },
  265. { value: '玄关', label: '玄关' },
  266. { value: '别墅', label: '别墅' }
  267. ];
  268. }
  269. // 获取项目风格选项
  270. getProjectStyleOptions(): { value: ProjectStyle; label: string }[] {
  271. return [
  272. { value: '现代简约', label: '现代简约' },
  273. { value: '北欧', label: '北欧' },
  274. { value: '新中式', label: '新中式' },
  275. { value: '美式', label: '美式' },
  276. { value: '欧式', label: '欧式' },
  277. { value: '日式', label: '日式' },
  278. { value: '工业风', label: '工业风' },
  279. { value: '轻奢', label: '轻奢' }
  280. ];
  281. }
  282. }