| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- import { Injectable, signal } from '@angular/core';
- import { Observable, of, BehaviorSubject } from 'rxjs';
- import { delay, map } from 'rxjs/operators';
- // 报价审核状态
- export type QuotationApprovalStatus = 'pending' | 'approved' | 'rejected' | 'revision_required';
- // 报价审核记录
- export interface QuotationApproval {
- id: string;
- quotationId: string;
- projectId: string;
- projectName: string;
- customerName: string;
- submittedBy: string; // 提交人
- submittedAt: Date;
- totalAmount: number;
- status: QuotationApprovalStatus;
- approvedBy?: string; // 审批人
- approvedAt?: Date;
- rejectionReason?: string;
- revisionNotes?: string;
- scriptUsed?: string; // 使用的话术模板ID
- communicationLog: CommunicationRecord[];
- }
- // 沟通记录
- export interface CommunicationRecord {
- id: string;
- timestamp: Date;
- type: 'script_used' | 'customer_response' | 'internal_note' | 'approval_comment';
- content: string;
- scriptId?: string; // 如果是使用话术,记录话术ID
- userId: string;
- userName: string;
- }
- // 话术使用统计
- export interface ScriptUsageStats {
- scriptId: string;
- scriptTitle: string;
- usageCount: number;
- successRate: number; // 成功率(客户接受报价的比例)
- avgResponseTime: number; // 平均响应时间(小时)
- lastUsed: string;
- }
- @Injectable({
- providedIn: 'root'
- })
- export class QuotationApprovalService {
- private approvals = signal<QuotationApproval[]>([]);
- private scriptUsageStats = signal<ScriptUsageStats[]>([]);
-
- // 实时通知流
- private approvalUpdates$ = new BehaviorSubject<QuotationApproval | null>(null);
- constructor() {
- this.initializeMockData();
- }
- // 初始化模拟数据
- private initializeMockData(): void {
- const mockApprovals: QuotationApproval[] = [
- {
- id: 'qa001',
- quotationId: 'q001',
- projectId: 'p001',
- projectName: '现代简约客厅设计',
- customerName: '张先生',
- submittedBy: '李设计师',
- submittedAt: new Date('2024-01-15T10:30:00'),
- totalAmount: 85000,
- status: 'pending',
- scriptUsed: 'script001',
- communicationLog: [
- {
- id: 'comm001',
- timestamp: new Date('2024-01-15T10:30:00'),
- type: 'script_used',
- content: '使用了"高端设计报价说明"话术模板',
- scriptId: 'script001',
- userId: 'designer001',
- userName: '李设计师'
- }
- ]
- },
- {
- id: 'qa002',
- quotationId: 'q002',
- projectId: 'p002',
- projectName: '北欧风卧室设计',
- customerName: '王女士',
- submittedBy: '陈设计师',
- submittedAt: new Date('2024-01-14T14:20:00'),
- totalAmount: 65000,
- status: 'approved',
- approvedBy: '财务主管',
- approvedAt: new Date('2024-01-14T16:45:00'),
- scriptUsed: 'script002',
- communicationLog: [
- {
- id: 'comm002',
- timestamp: new Date('2024-01-14T14:20:00'),
- type: 'script_used',
- content: '使用了"中档设计报价说明"话术模板',
- scriptId: 'script002',
- userId: 'designer002',
- userName: '陈设计师'
- },
- {
- id: 'comm003',
- timestamp: new Date('2024-01-14T16:45:00'),
- type: 'approval_comment',
- content: '报价合理,符合市场标准,批准通过',
- userId: 'finance001',
- userName: '财务主管'
- }
- ]
- }
- ];
- const mockStats: ScriptUsageStats[] = [
- {
- scriptId: 'script001',
- scriptTitle: '高端设计报价说明',
- usageCount: 15,
- successRate: 0.73,
- avgResponseTime: 2.5,
- lastUsed: '2024-01-15T10:30:00'
- },
- {
- scriptId: 'script002',
- scriptTitle: '中档设计报价说明',
- usageCount: 28,
- successRate: 0.82,
- avgResponseTime: 1.8,
- lastUsed: '2024-01-14T14:20:00'
- },
- {
- scriptId: 'script003',
- scriptTitle: '基础设计报价说明',
- usageCount: 35,
- successRate: 0.89,
- avgResponseTime: 1.2,
- lastUsed: '2024-01-13T09:15:00'
- }
- ];
- this.approvals.set(mockApprovals);
- this.scriptUsageStats.set(mockStats);
- }
- // 获取报价审核列表
- getQuotationApprovals(): Observable<QuotationApproval[]> {
- return of(this.approvals()).pipe(delay(300));
- }
- // 根据状态筛选报价审核记录
- getApprovalsByStatus(status: QuotationApprovalStatus): Observable<QuotationApproval[]> {
- return of(this.approvals().filter(approval => approval.status === status)).pipe(delay(300));
- }
- // 获取单个报价审核记录
- getApprovalById(id: string): Observable<QuotationApproval | undefined> {
- return of(this.approvals().find(approval => approval.id === id)).pipe(delay(200));
- }
- // 提交报价审核
- submitQuotationForApproval(quotationData: {
- quotationId: string;
- projectId: string;
- projectName: string;
- customerName: string;
- submittedBy: string;
- totalAmount: number;
- scriptUsed?: string;
- }): Observable<QuotationApproval> {
- const newApproval: QuotationApproval = {
- id: `qa${Date.now()}`,
- ...quotationData,
- submittedAt: new Date(),
- status: 'pending',
- communicationLog: quotationData.scriptUsed ? [{
- id: `comm${Date.now()}`,
- timestamp: new Date(),
- type: 'script_used',
- content: `使用了话术模板进行报价说明`,
- scriptId: quotationData.scriptUsed,
- userId: 'current_user',
- userName: quotationData.submittedBy
- }] : []
- };
- const updatedApprovals = [...this.approvals(), newApproval];
- this.approvals.set(updatedApprovals);
-
- // 通知更新
- this.approvalUpdates$.next(newApproval);
-
- return of(newApproval).pipe(delay(500));
- }
- // 审批报价
- approveQuotation(approvalId: string, approvedBy: string, comment?: string): Observable<QuotationApproval> {
- const approvals = this.approvals();
- const approvalIndex = approvals.findIndex(a => a.id === approvalId);
-
- if (approvalIndex === -1) {
- throw new Error('报价审核记录不存在');
- }
- const updatedApproval = {
- ...approvals[approvalIndex],
- status: 'approved' as QuotationApprovalStatus,
- approvedBy,
- approvedAt: new Date(),
- communicationLog: [
- ...approvals[approvalIndex].communicationLog,
- {
- id: `comm${Date.now()}`,
- timestamp: new Date(),
- type: 'approval_comment' as const,
- content: comment || '报价已批准',
- userId: 'current_user',
- userName: approvedBy
- }
- ]
- };
- const updatedApprovals = [...approvals];
- updatedApprovals[approvalIndex] = updatedApproval;
- this.approvals.set(updatedApprovals);
-
- // 通知更新
- this.approvalUpdates$.next(updatedApproval);
-
- return of(updatedApproval).pipe(delay(300));
- }
- // 拒绝报价
- rejectQuotation(approvalId: string, rejectedBy: string, reason: string): Observable<QuotationApproval> {
- const approvals = this.approvals();
- const approvalIndex = approvals.findIndex(a => a.id === approvalId);
-
- if (approvalIndex === -1) {
- throw new Error('报价审核记录不存在');
- }
- const updatedApproval = {
- ...approvals[approvalIndex],
- status: 'rejected' as QuotationApprovalStatus,
- approvedBy: rejectedBy,
- approvedAt: new Date(),
- rejectionReason: reason,
- communicationLog: [
- ...approvals[approvalIndex].communicationLog,
- {
- id: `comm${Date.now()}`,
- timestamp: new Date(),
- type: 'approval_comment' as const,
- content: `报价被拒绝:${reason}`,
- userId: 'current_user',
- userName: rejectedBy
- }
- ]
- };
- const updatedApprovals = [...approvals];
- updatedApprovals[approvalIndex] = updatedApproval;
- this.approvals.set(updatedApprovals);
-
- // 通知更新
- this.approvalUpdates$.next(updatedApproval);
-
- return of(updatedApproval).pipe(delay(300));
- }
- // 要求修订报价
- requestRevision(approvalId: string, requestedBy: string, notes: string): Observable<QuotationApproval> {
- const approvals = this.approvals();
- const approvalIndex = approvals.findIndex(a => a.id === approvalId);
-
- if (approvalIndex === -1) {
- throw new Error('报价审核记录不存在');
- }
- const updatedApproval = {
- ...approvals[approvalIndex],
- status: 'revision_required' as QuotationApprovalStatus,
- revisionNotes: notes,
- communicationLog: [
- ...approvals[approvalIndex].communicationLog,
- {
- id: `comm${Date.now()}`,
- timestamp: new Date(),
- type: 'approval_comment' as const,
- content: `要求修订:${notes}`,
- userId: 'current_user',
- userName: requestedBy
- }
- ]
- };
- const updatedApprovals = [...approvals];
- updatedApprovals[approvalIndex] = updatedApproval;
- this.approvals.set(updatedApprovals);
-
- // 通知更新
- this.approvalUpdates$.next(updatedApproval);
-
- return of(updatedApproval).pipe(delay(300));
- }
- // 记录话术使用
- recordScriptUsage(approvalId: string, scriptId: string, userId: string, userName: string): Observable<void> {
- const approvals = this.approvals();
- const approvalIndex = approvals.findIndex(a => a.id === approvalId);
-
- if (approvalIndex !== -1) {
- const communicationRecord: CommunicationRecord = {
- id: `comm${Date.now()}`,
- timestamp: new Date(),
- type: 'script_used',
- content: '使用了话术模板进行沟通',
- scriptId,
- userId,
- userName
- };
- const updatedApproval = {
- ...approvals[approvalIndex],
- communicationLog: [...approvals[approvalIndex].communicationLog, communicationRecord]
- };
- const updatedApprovals = [...approvals];
- updatedApprovals[approvalIndex] = updatedApproval;
- this.approvals.set(updatedApprovals);
-
- // 更新话术使用统计
- this.updateScriptUsageStats(scriptId);
- }
-
- return of(void 0).pipe(delay(200));
- }
- // 记录客户响应
- recordCustomerResponse(approvalId: string, response: string, userId: string, userName: string): Observable<void> {
- const approvals = this.approvals();
- const approvalIndex = approvals.findIndex(a => a.id === approvalId);
-
- if (approvalIndex !== -1) {
- const communicationRecord: CommunicationRecord = {
- id: `comm${Date.now()}`,
- timestamp: new Date(),
- type: 'customer_response',
- content: response,
- userId,
- userName
- };
- const updatedApproval = {
- ...approvals[approvalIndex],
- communicationLog: [...approvals[approvalIndex].communicationLog, communicationRecord]
- };
- const updatedApprovals = [...approvals];
- updatedApprovals[approvalIndex] = updatedApproval;
- this.approvals.set(updatedApprovals);
- }
-
- return of(void 0).pipe(delay(200));
- }
- // 获取话术使用统计
- getScriptUsageStats(): Observable<ScriptUsageStats[]> {
- return of(this.scriptUsageStats()).pipe(delay(300));
- }
- // 获取实时更新流
- getApprovalUpdates(): Observable<QuotationApproval | null> {
- return this.approvalUpdates$.asObservable();
- }
- // 更新话术使用统计
- private updateScriptUsageStats(scriptId: string): void {
- const stats = this.scriptUsageStats();
- const statIndex = stats.findIndex(s => s.scriptId === scriptId);
-
- if (statIndex !== -1) {
- const updatedStats = [...stats];
- updatedStats[statIndex] = {
- ...updatedStats[statIndex],
- usageCount: updatedStats[statIndex].usageCount + 1,
- lastUsed: new Date().toISOString()
- };
- this.scriptUsageStats.set(updatedStats);
- }
- }
- // 获取审核统计数据
- getApprovalStats(): Observable<{
- total: number;
- pending: number;
- approved: number;
- rejected: number;
- revisionRequired: number;
- avgApprovalTime: number; // 平均审批时间(小时)
- }> {
- const approvals = this.approvals();
- const total = approvals.length;
- const pending = approvals.filter(a => a.status === 'pending').length;
- const approved = approvals.filter(a => a.status === 'approved').length;
- const rejected = approvals.filter(a => a.status === 'rejected').length;
- const revisionRequired = approvals.filter(a => a.status === 'revision_required').length;
-
- // 计算平均审批时间
- const completedApprovals = approvals.filter(a => a.approvedAt);
- const avgApprovalTime = completedApprovals.length > 0
- ? completedApprovals.reduce((sum, approval) => {
- const timeDiff = approval.approvedAt!.getTime() - approval.submittedAt.getTime();
- return sum + (timeDiff / (1000 * 60 * 60)); // 转换为小时
- }, 0) / completedApprovals.length
- : 0;
- return of({
- total,
- pending,
- approved,
- rejected,
- revisionRequired,
- avgApprovalTime
- }).pipe(delay(200));
- }
- }
|