|
@@ -43,9 +43,11 @@ export class OperationsPayrollComponent implements OnInit{
|
|
|
@HostListener('document:keydown.escape') closeMonthPickerWithKeyboard():void{this.closeMonthPicker();}
|
|
@HostListener('document:keydown.escape') closeMonthPickerWithKeyboard():void{this.closeMonthPicker();}
|
|
|
toggleMonthPicker(event:Event):void{event.stopPropagation();if(this.monthPickerOpen()){this.closeMonthPicker();return;}this.monthPickerYear.set(Number(this.month().slice(0,4))||new Date().getFullYear());this.monthPickerOpen.set(true);}
|
|
toggleMonthPicker(event:Event):void{event.stopPropagation();if(this.monthPickerOpen()){this.closeMonthPicker();return;}this.monthPickerYear.set(Number(this.month().slice(0,4))||new Date().getFullYear());this.monthPickerOpen.set(true);}
|
|
|
shiftMonthPickerYear(delta:number,event:Event):void{event.stopPropagation();this.monthPickerYear.update(year=>year+delta);}
|
|
shiftMonthPickerYear(delta:number,event:Event):void{event.stopPropagation();this.monthPickerYear.update(year=>year+delta);}
|
|
|
- selectMonth(monthIndex:number):void{this.month.set(this.monthOptionValue(monthIndex));this.closeMonthPicker();}
|
|
|
|
|
- selectCurrentMonth():void{this.month.set(localDateKey(new Date()).slice(0,7));this.closeMonthPicker();}
|
|
|
|
|
- monthLabel():string{const [year,month]=this.month().split('-');return year&&month?`${year}年${Number(month)}月`:'选择月份';}
|
|
|
|
|
|
|
+ selectMonth(monthIndex:number):void{this.month.set(this.monthOptionValue(monthIndex));this.clearMonthContext();this.closeMonthPicker();}
|
|
|
|
|
+ selectCurrentMonth():void{this.month.set(localDateKey(new Date()).slice(0,7));this.clearMonthContext();this.closeMonthPicker();}
|
|
|
|
|
+ monthLabel():string{return this.monthLabelOf(this.month());}
|
|
|
|
|
+ private monthLabelOf(value:string):string{const [year,month]=value.split('-');return year&&month?`${year}年${Number(month)}月`:'选择月份';}
|
|
|
|
|
+ private clearMonthContext():void{this.message.set('');this.error.set('');this.closeDrawer();}
|
|
|
monthOptionValue(monthIndex:number):string{return `${this.monthPickerYear()}-${String(monthIndex+1).padStart(2,'0')}`;}
|
|
monthOptionValue(monthIndex:number):string{return `${this.monthPickerYear()}-${String(monthIndex+1).padStart(2,'0')}`;}
|
|
|
clearRewardCoach():void{this.rewardCoachId.set(0);this.openRewards();}isManager():boolean{return this.sessions.session()?.operationsRole==='ops-manager'||this.sessions.session()?.isSuperAdmin===true;}
|
|
clearRewardCoach():void{this.rewardCoachId.set(0);this.openRewards();}isManager():boolean{return this.sessions.session()?.operationsRole==='ops-manager'||this.sessions.session()?.isSuperAdmin===true;}
|
|
|
async load(refresh=false):Promise<void>{
|
|
async load(refresh=false):Promise<void>{
|
|
@@ -66,11 +68,11 @@ export class OperationsPayrollComponent implements OnInit{
|
|
|
|
|
|
|
|
async refreshLive():Promise<void>{if(this.tab()==='rewards'){this.openRewards();return;}await this.load(true);}
|
|
async refreshLive():Promise<void>{if(this.tab()==='rewards'){this.openRewards();return;}await this.load(true);}
|
|
|
async resetFilters():Promise<void>{this.resetRevision++;this.closeDrawer();this.search.set('');this.storeId.set(0);this.rewardCoachId.set(0);await this.applyFilters();}
|
|
async resetFilters():Promise<void>{this.resetRevision++;this.closeDrawer();this.search.set('');this.storeId.set(0);this.rewardCoachId.set(0);await this.applyFilters();}
|
|
|
- async applyFilters():Promise<void>{if(this.tab()==='batches')this.allBatchMonths.set(false);if(this.tab()==='rewards'){this.openRewards();return;}await this.load(true);}
|
|
|
|
|
|
|
+ async applyFilters():Promise<void>{this.message.set('');if(this.tab()==='batches')this.allBatchMonths.set(false);if(this.tab()==='rewards'){this.openRewards();return;}await this.load(true);}
|
|
|
async open(row:PayrollSummary):Promise<void>{const request=++this.openRequest;this.loading.set(true);this.error.set('');try{this.selectedCoachId.set(0);const detail=await this.operations.payrollDetail(row.objectId);if(request===this.openRequest)this.detail.set(detail);}catch(error){if(request===this.openRequest)this.error.set(error instanceof Error?error.message:'工资单加载失败');}finally{if(request===this.openRequest)this.loading.set(false);}}
|
|
async open(row:PayrollSummary):Promise<void>{const request=++this.openRequest;this.loading.set(true);this.error.set('');try{this.selectedCoachId.set(0);const detail=await this.operations.payrollDetail(row.objectId);if(request===this.openRequest)this.detail.set(detail);}catch(error){if(request===this.openRequest)this.error.set(error instanceof Error?error.message:'工资单加载失败');}finally{if(request===this.openRequest)this.loading.set(false);}}
|
|
|
coachLabel(name:string,coachId:number):string{const value=String(name||'').trim();return !value||value===String(coachId)?`陪练 ${coachId}`:value;}
|
|
coachLabel(name:string,coachId:number):string{const value=String(name||'').trim();return !value||value===String(coachId)?`陪练 ${coachId}`:value;}
|
|
|
openCoach(row:PayrollCoachSummary):void{this.selectedCoachId.set(row.coachId);this.selectedCoachName.set(this.coachLabel(row.coachName,row.coachId));this.detail.set(null);}closeDrawer():void{this.openRequest++;this.detail.set(null);this.selectedCoachId.set(0);this.selectedCoachName.set('');}
|
|
openCoach(row:PayrollCoachSummary):void{this.selectedCoachId.set(row.coachId);this.selectedCoachName.set(this.coachLabel(row.coachName,row.coachId));this.detail.set(null);}closeDrawer():void{this.openRequest++;this.detail.set(null);this.selectedCoachId.set(0);this.selectedCoachName.set('');}
|
|
|
- async create():Promise<void>{if(this.saving())return;if(!this.overview()?.sourceStatus.available){this.error.set('实时上课记录不可用,不能生成工资草稿');return;}this.saving.set(true);this.error.set('');try{const result=await this.payrollCommand<PayrollSummary>('ops/payroll/create','',`生成 ${this.month()} 月全公司工资草稿`,{month:this.month()});await this.load();await this.open(result);this.message.set('全公司工资草稿已生成,尚未审核或发放');this.tab.set('batches');}catch(error){this.error.set(error instanceof Error?error.message:'生成工资草稿失败');}finally{this.saving.set(false);}}
|
|
|
|
|
|
|
+ async create():Promise<void>{if(this.saving())return;if(!this.overview()?.sourceStatus.available){this.error.set('实时上课记录不可用,不能生成工资草稿');return;}const generatedMonth=this.month();this.saving.set(true);this.error.set('');try{const result=await this.payrollCommand<PayrollSummary>('ops/payroll/create','',`生成 ${generatedMonth} 月全公司工资草稿`,{month:generatedMonth});await this.load();await this.open(result);this.message.set(`${this.monthLabelOf(generatedMonth)}全公司工资草稿已生成,尚未审核或发放`);this.tab.set('batches');}catch(error){this.error.set(error instanceof Error?error.message:'生成工资草稿失败');}finally{this.saving.set(false);}}
|
|
|
async saveRate():Promise<void>{this.saving.set(true);this.error.set('');try{await this.operations.mutate('ops/pay-rates/save','',`调整课型 ${this.rateType()} 的计价规则`,{classType:this.rateType(),amount:this.rateAmount(),durationHours:this.rateHours(),effectiveFrom:this.effectiveFrom(),active:true});this.message.set('计价规则已保存');await this.load();}catch(error){this.error.set(error instanceof Error?error.message:'计价规则保存失败');}finally{this.saving.set(false);}}
|
|
async saveRate():Promise<void>{this.saving.set(true);this.error.set('');try{await this.operations.mutate('ops/pay-rates/save','',`调整课型 ${this.rateType()} 的计价规则`,{classType:this.rateType(),amount:this.rateAmount(),durationHours:this.rateHours(),effectiveFrom:this.effectiveFrom(),active:true});this.message.set('计价规则已保存');await this.load();}catch(error){this.error.set(error instanceof Error?error.message:'计价规则保存失败');}finally{this.saving.set(false);}}
|
|
|
creditCosts(categoryKey=this.ruleCategoryKey(),duration=this.ruleDuration()):Array<{account:TeachingCreditAccount;amount:number}>{if(categoryKey==='trial')return[{account:'UserExp',amount:1}];if(categoryKey==='review')return[{account:'UserPoint',amount:duration===30?.5:1}];return duration===30?[{account:'Purse',amount:1},{account:'UserPoint',amount:.5}]:[{account:'SilverCoin',amount:1},{account:'UserPoint',amount:1}];}
|
|
creditCosts(categoryKey=this.ruleCategoryKey(),duration=this.ruleDuration()):Array<{account:TeachingCreditAccount;amount:number}>{if(categoryKey==='trial')return[{account:'UserExp',amount:1}];if(categoryKey==='review')return[{account:'UserPoint',amount:duration===30?.5:1}];return duration===30?[{account:'Purse',amount:1},{account:'UserPoint',amount:.5}]:[{account:'SilverCoin',amount:1},{account:'UserPoint',amount:1}];}
|
|
|
creditSummary(rule:TeachingRule):string{return rule.creditCosts.map((item)=>`${item.label} -${item.amount}`).join('+');}
|
|
creditSummary(rule:TeachingRule):string{return rule.creditCosts.map((item)=>`${item.label} -${item.amount}`).join('+');}
|