| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 | import { Component, Input, computed, signal, OnInit } from '@angular/core';import { CommonModule, DatePipe } from '@angular/common';import { FormsModule } from '@angular/forms';import { MatButtonModule } from '@angular/material/button';import { MatIconModule } from '@angular/material/icon';import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';import { MatTooltipModule } from '@angular/material/tooltip';import { MatDialogModule, MatDialog } from '@angular/material/dialog';import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';import { Settlement } from '../../../models/project.model';import { AutoSettlementService } from '../../../services/auto-settlement.service';import { ProjectService } from '../../../services/project.service';export interface SettlementStats {  totalAmount: number;  pendingAmount: number;  overdueAmount: number;  completedAmount: number;  totalCount: number;  pendingCount: number;  overdueCount: number;  completedCount: number;}// 项目验收确认接口export interface ProjectAcceptance {  id: string;  projectId: string;  projectName: string;  technicianId: string;  technicianName: string;  acceptanceStatus: 'pending' | 'confirmed' | 'rejected';  acceptanceDate?: Date;  acceptanceNotes?: string;  customerNotified: boolean;}// 客服跟进提醒接口export interface CustomerServiceReminder {  id: string;  projectId: string;  projectName: string;  customerName: string;  reminderType: 'payment_follow_up' | 'acceptance_notification' | 'delivery_confirmation';  reminderStatus: 'pending' | 'in_progress' | 'completed';  assignedTo: string;  dueDate: Date;  notes?: string;  createdAt: Date;}// 企业微信群发图接口export interface WeChatImageDelivery {  id: string;  projectId: string;  groupId: string;  groupName: string;  imageUrls: string[];  deliveryStatus: 'pending' | 'sending' | 'completed' | 'failed';  deliveryDate?: Date;  errorMessage?: string;}@Component({  selector: 'app-settlement-card',  standalone: true,  imports: [    CommonModule,     DatePipe,    FormsModule,    MatButtonModule,    MatIconModule,    MatProgressSpinnerModule,    MatTooltipModule,    MatDialogModule,    MatSnackBarModule  ],  templateUrl: './settlement-card.html',  styleUrls: ['./settlement-card.scss']})export class SettlementCardComponent implements OnInit {  @Input() settlements: Settlement[] = [];  @Input() showAutomationControls = false;  @Input() projectAcceptances: ProjectAcceptance[] = [];  @Input() customerServiceReminders: CustomerServiceReminder[] = [];    // 筛选条件  statusFilter = signal<string>('all');  searchKeyword = signal<string>('');    // 自动化处理状态  isProcessing = signal(false);  processingSettlementId = signal<string | null>(null);    // 新增功能状态  acceptanceStatus = signal<{[key: string]: 'pending' | 'confirming' | 'confirmed'}>({});  reminderStatus = signal<{[key: string]: 'pending' | 'creating' | 'created'}>({});  isSendingImages = signal(false);  sendingProjectId = signal<string | null>(null);  constructor(    private autoSettlementService: AutoSettlementService,    private dialog: MatDialog,    private snackBar: MatSnackBar,    private projectService: ProjectService  ) { }  ngOnInit() {    // 启动定时自动化处理    this.autoSettlementService.startScheduledProcessing();        // 初始化项目验收状态    this.initializeAcceptanceStatus();        // 初始化客服提醒状态    this.initializeReminderStatus();  }  // 初始化项目验收状态  private initializeAcceptanceStatus(): void {    const status: {[key: string]: 'pending' | 'confirming' | 'confirmed'} = {};    this.settlements.forEach(settlement => {      const acceptance = this.projectAcceptances.find(a => a.projectId === settlement.projectId);      if (acceptance) {        status[settlement.projectId] = acceptance.acceptanceStatus === 'confirmed' ? 'confirmed' : 'pending';      } else {        status[settlement.projectId] = 'pending';      }    });    this.acceptanceStatus.set(status);  }  // 初始化客服提醒状态  private initializeReminderStatus(): void {    const status: {[key: string]: 'pending' | 'creating' | 'created'} = {};    this.settlements.forEach(settlement => {      const reminder = this.customerServiceReminders.find(r =>         r.projectId === settlement.projectId && r.reminderType === 'payment_follow_up'      );      if (reminder) {        status[settlement.projectId] = reminder.reminderStatus === 'completed' ? 'created' : 'pending';      } else {        status[settlement.projectId] = 'pending';      }    });    this.reminderStatus.set(status);  }  // 技术确认项目验收  confirmProjectAcceptance(settlement: Settlement): void {    const currentStatus = this.acceptanceStatus();    currentStatus[settlement.projectId] = 'confirming';    this.acceptanceStatus.set({...currentStatus});    // 模拟API调用    setTimeout(() => {      const updatedStatus = this.acceptanceStatus();      updatedStatus[settlement.projectId] = 'confirmed';      this.acceptanceStatus.set({...updatedStatus});            this.snackBar.open(`项目 ${settlement.projectName} 验收确认成功`, '关闭', {        duration: 3000,        horizontalPosition: 'center',        verticalPosition: 'top'      });      // 自动创建客服跟进提醒      this.createCustomerServiceReminder(settlement, 'acceptance_notification');      // 验收完成后,自动发起尾款结算申请      this.initiateFinalSettlement(settlement);    }, 1500);  }  // 创建客服跟进提醒  createCustomerServiceReminder(settlement: Settlement, type: 'payment_follow_up' | 'acceptance_notification' | 'delivery_confirmation'): void {    const currentStatus = this.reminderStatus();    currentStatus[settlement.projectId] = 'creating';    this.reminderStatus.set({...currentStatus});    // 模拟API调用创建提醒    setTimeout(() => {      const updatedStatus = this.reminderStatus();      updatedStatus[settlement.projectId] = 'created';      this.reminderStatus.set({...updatedStatus});            let message = '';      switch(type) {        case 'payment_follow_up':          message = `已创建尾款跟进提醒`;          break;        case 'acceptance_notification':          message = `已创建验收通知提醒`;          break;        case 'delivery_confirmation':          message = `已创建交付确认提醒`;          break;      }            this.snackBar.open(`${settlement.projectName} ${message}`, '关闭', {        duration: 3000,        horizontalPosition: 'center',        verticalPosition: 'top'      });    }, 1000);  }  // 一键发图到企业微信群  sendImagesToWeChatGroup(settlement: Settlement): void {    this.isSendingImages.set(true);    this.sendingProjectId.set(settlement.projectId);    // 模拟获取项目大图和发送到企业微信群    setTimeout(() => {      this.isSendingImages.set(false);      this.sendingProjectId.set(null);            this.snackBar.open(`项目 ${settlement.projectName} 大图已发送到企业微信群`, '关闭', {        duration: 4000,        horizontalPosition: 'center',        verticalPosition: 'top'      });      // 自动创建交付确认提醒      this.createCustomerServiceReminder(settlement, 'delivery_confirmation');    }, 3000);  }  // 获取项目验收状态  getAcceptanceStatus(projectId: string): 'pending' | 'confirming' | 'confirmed' {    return this.acceptanceStatus()[projectId] || 'pending';  }  // 获取客服提醒状态  getReminderStatus(projectId: string): 'pending' | 'creating' | 'created' {    return this.reminderStatus()[projectId] || 'pending';  }  // 检查是否可以发送图片  canSendImages(settlement: Settlement): boolean {    return settlement.status === '已结算' && this.getAcceptanceStatus(settlement.projectId) === 'confirmed';  }  // 检查是否正在发送图片  isSendingImagesForProject(projectId: string): boolean {    return this.isSendingImages() && this.sendingProjectId() === projectId;  }  // 处理单个结算  processSettlement(settlementId: string): void {    const settlement = this.settlements.find(s => s.id === settlementId);    if (!settlement) return;        // 如果是逾期状态,显示逾期处理弹窗    if (settlement.status === '逾期' || this.isOverdue(settlement)) {      this.handleOverdueSettlement(settlement);    } else {      this.processSettlementAutomation(settlement);    }  }    // 处理逾期结算  async handleOverdueSettlement(settlement: Settlement): Promise<void> {    const daysOverdue = this.getDaysOverdue(settlement);    const overdueMessage = `项目:${settlement.projectName}阶段:${settlement.stage}金额:¥${settlement.amount}逾期天数:${daysOverdue}天请选择处理方式:1. 立即催款2. 延期处理3. 标记为已结算    `.trim();        if (await window?.fmode?.confirm(overdueMessage + '\n\n点击"确定"发送催款提醒,点击"取消"查看更多选项')) {      // 发送催款提醒      this.sendOverdueReminder(settlement);    } else {      // 显示更多处理选项      await this.showOverdueOptions(settlement);    }  }    // 发送逾期催款提醒  private sendOverdueReminder(settlement: Settlement): void {    this.snackBar.open('正在发送催款提醒...', '', {      duration: 2000,      horizontalPosition: 'center',      verticalPosition: 'top'    });        setTimeout(() => {      // 创建客服跟进提醒      this.createCustomerServiceReminder(settlement, 'payment_follow_up');            // 模拟发送多渠道催款通知      const channels = ['短信', '微信', '电话'];      const channelText = channels.join('、');            this.snackBar.open(        `✅ 催款提醒已通过${channelText}发送给客户\n项目:${settlement.projectName}\n金额:¥${settlement.amount}`,        '关闭',        {          duration: 5000,          horizontalPosition: 'center',          verticalPosition: 'top'        }      );            // 记录催款历史      console.log('催款记录:', {        settlementId: settlement.id,        projectName: settlement.projectName,        amount: settlement.amount,        daysOverdue: this.getDaysOverdue(settlement),        reminderDate: new Date(),        channels: channels      });    }, 2000);  }    // 显示逾期处理选项  private async showOverdueOptions(settlement: Settlement): Promise<void> {    const option = await window?.fmode?.input(`逾期处理选项:1 - 延期7天2 - 延期15天3 - 延期30天4 - 标记为已结算5 - 取消结算请输入选项编号(1-5):    `.trim());        switch(option) {      case '1':        this.extendSettlementDeadline(settlement, 7);        break;      case '2':        this.extendSettlementDeadline(settlement, 15);        break;      case '3':        this.extendSettlementDeadline(settlement, 30);        break;      case '4':        this.markSettlementAsCompleted(settlement);        break;      case '5':        this.cancelSettlement(settlement);        break;      default:        if (option !== null) {          this.snackBar.open('无效的选项', '关闭', {            duration: 2000,            horizontalPosition: 'center',            verticalPosition: 'top'          });        }    }  }    // 延长结算期限  private extendSettlementDeadline(settlement: Settlement, days: number): void {    const currentDueDate = settlement.dueDate || new Date();    const newDeadline = new Date(currentDueDate);    newDeadline.setDate(newDeadline.getDate() + days);        settlement.dueDate = newDeadline;    settlement.status = '待结算';        this.snackBar.open(      `✅ 已将结算期限延长${days}天\n新期限:${newDeadline.toLocaleDateString()}`,      '关闭',      {        duration: 4000,        horizontalPosition: 'center',        verticalPosition: 'top'      }    );        console.log('延期记录:', {      settlementId: settlement.id,      projectName: settlement.projectName,      extendedDays: days,      newDeadline: newDeadline    });  }    // 标记为已结算  private async markSettlementAsCompleted(settlement: Settlement): Promise<void> {    const reason = await window?.fmode?.input('请输入标记为已结算的原因:');    if (reason && reason.trim()) {      settlement.status = '已结算';      settlement.paidDate = new Date();            this.snackBar.open(        `✅ 已标记为已结算\n项目:${settlement.projectName}\n原因:${reason}`,        '关闭',        {          duration: 4000,          horizontalPosition: 'center',          verticalPosition: 'top'        }      );            console.log('手动结算记录:', {        settlementId: settlement.id,        projectName: settlement.projectName,        reason: reason,        completedDate: new Date()      });    }  }    // 取消结算  private async cancelSettlement(settlement: Settlement): Promise<void> {    const reason = await window?.fmode?.input('请输入取消结算的原因:');    if (reason && reason.trim()) {      if (await window?.fmode?.confirm(`确定要取消此结算吗?\n项目:${settlement.projectName}\n金额:¥${settlement.amount}`)) {        settlement.status = '已取消';                this.snackBar.open(          `✅ 已取消结算\n项目:${settlement.projectName}\n原因:${reason}`,          '关闭',          {            duration: 4000,            horizontalPosition: 'center',            verticalPosition: 'top'          }        );                console.log('取消结算记录:', {          settlementId: settlement.id,          projectName: settlement.projectName,          reason: reason,          cancelledDate: new Date()        });      }    }  }  // 发送提醒  sendReminder(settlementId: string): void {    const settlement = this.settlements.find(s => s.id === settlementId);    if (settlement) {      this.createCustomerServiceReminder(settlement, 'payment_follow_up');    }  }  // 处理单个结算自动化  processSettlementAutomation(settlement: Settlement): void {    this.processingSettlementId.set(settlement.id);    this.isProcessing.set(true);        this.autoSettlementService.processSettlementAutomation(settlement).subscribe({      next: (processed) => {        if (processed) {          console.log(`结算 ${settlement.id} 已自动处理`);        }        this.processingSettlementId.set(null);        this.isProcessing.set(false);      },      error: (error) => {        console.error('自动化处理失败:', error);        this.processingSettlementId.set(null);        this.isProcessing.set(false);      }    });  }  // 项目验收确认相关方法  isProjectAccepted(projectId: string): boolean {    return this.projectAcceptances.some(acceptance =>       acceptance.projectId === projectId && acceptance.acceptanceStatus === 'confirmed'    );  }  // 客服跟进提醒相关方法  hasCustomerServiceReminder(projectId: string): boolean {    return this.customerServiceReminders.some(reminder =>       reminder.projectId === projectId && reminder.reminderStatus === 'completed'    );  }  // 企业微信发图相关方法  canSendToWeChat(projectId: string): boolean {    return this.isProjectAccepted(projectId) && !this.isSendingImagesForProject(projectId);  }  // 批量处理自动化  processAllAutomation(): void {    const pendingSettlements = this.settlements.filter(      s => s.status === '待结算' && !this.isOverdue(s)    );        this.isProcessing.set(true);        // 模拟批量处理    let processedCount = 0;    const processNext = () => {      if (processedCount >= pendingSettlements.length) {        this.isProcessing.set(false);        return;      }            const settlement = pendingSettlements[processedCount];      this.processingSettlementId.set(settlement.id);            this.autoSettlementService.processSettlementAutomation(settlement).subscribe({        next: () => {          processedCount++;          this.processingSettlementId.set(null);          setTimeout(processNext, 500); // 添加延迟以避免同时处理过多        },        error: () => {          processedCount++;          this.processingSettlementId.set(null);          setTimeout(processNext, 500);        }      });    };        processNext();  }    // 计算统计数据  stats = computed<SettlementStats>(() => {    const settlements = this.settlements || [];        return {      totalAmount: settlements.reduce((sum, s) => sum + (s.amount || 0), 0),      pendingAmount: settlements.filter(s => s.status === '待结算').reduce((sum, s) => sum + (s.amount || 0), 0),      overdueAmount: settlements.filter(s => this.isOverdue(s)).reduce((sum, s) => sum + (s.amount || 0), 0),      completedAmount: settlements.filter(s => s.status === '已结算').reduce((sum, s) => sum + (s.amount || 0), 0),      totalCount: settlements.length,      pendingCount: settlements.filter(s => s.status === '待结算').length,      overdueCount: settlements.filter(s => this.isOverdue(s)).length,      completedCount: settlements.filter(s => s.status === '已结算').length    };  });    // 筛选后的结算列表  filteredSettlements = computed(() => {    let filtered = this.settlements || [];        // 状态筛选    const status = this.statusFilter();    if (status !== 'all') {      if (status === 'overdue') {        filtered = filtered.filter(s => this.isOverdue(s));      } else {        filtered = filtered.filter(s => s.status === status);      }    }        // 关键词搜索    const keyword = this.searchKeyword().toLowerCase();    if (keyword) {      filtered = filtered.filter(s =>         (s.projectName || '').toLowerCase().includes(keyword) ||        (s.stage || '').toLowerCase().includes(keyword)      );    }        return filtered;  });    // 判断是否逾期  isOverdue(settlement: Settlement): boolean {    if (settlement.status === '已结算') return false;    // 简化逾期判断:如果是待结算状态且创建时间超过30天,则认为逾期    const thirtyDaysAgo = new Date();    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);    return settlement.createdAt < thirtyDaysAgo;  }    // 获取状态样式类  getStatusClass(settlement: Settlement): string {    if (this.isOverdue(settlement)) return 'overdue';    return settlement.status === '已结算' ? 'completed' : 'pending';  }    // 格式化金额  formatAmount(amount: number): string {    return new Intl.NumberFormat('zh-CN', {      style: 'currency',      currency: 'CNY',      minimumFractionDigits: 0,      maximumFractionDigits: 2    }).format(amount);  }    // 更新筛选条件  updateStatusFilter(status: string): void {    this.statusFilter.set(status);  }    updateSearchKeyword(keyword: string): void {    this.searchKeyword.set(keyword);  }  onSearchInput(event: Event): void {    const target = event.target as HTMLInputElement;    if (target) {      this.updateSearchKeyword(target.value);    }  }    // 计算逾期天数  getDaysOverdue(settlement: Settlement): number {    if (settlement.status === '已结算') return 0;    const thirtyDaysAgo = new Date();    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);    if (settlement.createdAt >= thirtyDaysAgo) return 0;        const today = new Date();    const diffTime = today.getTime() - thirtyDaysAgo.getTime();    return Math.max(0, Math.ceil(diffTime / (1000 * 60 * 60 * 24)));  }    // 验收完成后自动发起尾款结算申请  private initiateFinalSettlement(contextSettlement: Settlement): void {    // 查找当前项目的“尾款结算”记录    this.projectService.getSettlements().subscribe(settlements => {      const finalSettlement = settlements.find(s => s.projectId === contextSettlement.projectId && s.stage === '尾款结算');        if (finalSettlement) {        // 标记为已发起(更新时间)并触发自动化处理        finalSettlement.createdAt = new Date();        this.snackBar.open(`已自动发起尾款结算申请:${finalSettlement.projectName}`, '关闭', {          duration: 3000,          horizontalPosition: 'center',          verticalPosition: 'top'        });        // 触发自动化处理(提醒/折扣/自动确认等)        this.processSettlementAutomation(finalSettlement);      } else {        // 若不存在尾款结算记录,则创建一条默认记录并发起        const newSettlement: Settlement = {          id: `s${Date.now()}`,          projectId: contextSettlement.projectId,          projectName: contextSettlement.projectName,          stage: '尾款结算',          amount: 0,          percentage: 100,          status: '待结算',          createdAt: new Date()        };        this.projectService.addSettlement(newSettlement).subscribe(() => {          this.snackBar.open(`已创建并自动发起尾款结算申请:${newSettlement.projectName}`, '关闭', {            duration: 3000,            horizontalPosition: 'center',            verticalPosition: 'top'          });          this.processSettlementAutomation(newSettlement);        });      }    });  }}
 |