246 lines
8.8 KiB
TypeScript
246 lines
8.8 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { Observable, of } from 'rxjs';
|
|
import { map, switchMap, catchError } from 'rxjs/operators';
|
|
|
|
import { Action } from '@ngrx/store';
|
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
|
import { toJob } from '../models/job.model';
|
|
import * as jobActions from '../actions/job.actions';
|
|
|
|
import { JobService } from '@app/domain/services/job.service';
|
|
import { JobCacheService } from '@app/domain/services/job-cache.service';
|
|
import { AppMessageService } from '@app/shared/app-message.service';
|
|
|
|
import { globals } from '@app/shared/global';
|
|
import { GAService } from '@app/shared/ga.service';
|
|
|
|
@Injectable()
|
|
export class JobEffects {
|
|
constructor(
|
|
|
|
private readonly actions$: Actions,
|
|
private readonly jobSvc: JobService,
|
|
private readonly jobCache: JobCacheService,
|
|
private readonly msgSvc: AppMessageService,
|
|
private readonly gaSvc: GAService
|
|
) {
|
|
}
|
|
|
|
@Effect()
|
|
loadJobs$: Observable<Action> = this.actions$.pipe(
|
|
ofType<jobActions.Fetch>(jobActions.FETCH),
|
|
switchMap(({ payload }) =>
|
|
this.jobSvc.loadJobs(payload).pipe(
|
|
map(data => {
|
|
const jobs = [];
|
|
if (data && Array.isArray(data)) {
|
|
data.forEach(item => {
|
|
jobs.push(toJob(item));
|
|
});
|
|
}
|
|
return new jobActions.FetchSuccess(jobs);
|
|
}),
|
|
catchError(err => {
|
|
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.load).replace('#thing#', globals.jobs));
|
|
return of(new jobActions.FetchError());
|
|
})
|
|
)
|
|
)
|
|
);
|
|
|
|
@Effect()
|
|
createJob$: Observable<Action> = this.actions$.pipe(
|
|
ofType<jobActions.Create>(jobActions.CREATE),
|
|
switchMap(({ payload }) =>
|
|
this.jobSvc.createJob(payload).pipe(
|
|
map((job) => {
|
|
// Track job creation with GA4
|
|
this.gaSvc.trackJobCreated({
|
|
user_id: 'system',
|
|
platform: 'web',
|
|
job_type: this.normalizeJobType(payload.appType),
|
|
field_size_acres: payload.ttSprArea || 0,
|
|
crop_type: payload.crop?.name || 'unknown',
|
|
client_id: payload.client?._id?.toString() || 'unknown',
|
|
priority: 'medium' // Default priority
|
|
});
|
|
|
|
this.jobCache.invalidate();
|
|
return new jobActions.CreateSuccess(job);
|
|
}),
|
|
catchError(err => {
|
|
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.save).replace('#thing#', globals.job));
|
|
return of(new jobActions.CreateFailed())
|
|
})
|
|
)
|
|
)
|
|
);
|
|
|
|
@Effect()
|
|
updateJob$: Observable<Action> = this.actions$.pipe(
|
|
ofType<jobActions.Update>(jobActions.UPDATE),
|
|
switchMap(({ payload }) => {
|
|
const oldStatus = payload.job?.status;
|
|
|
|
return this.jobSvc.saveJob(payload).pipe(
|
|
map((data) => {
|
|
const updatedJob = toJob(data);
|
|
const newStatus = updatedJob.status;
|
|
|
|
// Check if status changed during update
|
|
if (oldStatus !== undefined && oldStatus !== newStatus) {
|
|
this.gaSvc.trackJobStatusChanged({
|
|
user_id: 'system',
|
|
platform: 'web',
|
|
job_id: payload.job?._id?.toString() || 'unknown',
|
|
old_status: this.mapStatusToString(oldStatus),
|
|
new_status: this.mapStatusToString(newStatus),
|
|
status_change_reason: 'api_update'
|
|
});
|
|
}
|
|
|
|
// Track general job update
|
|
this.gaSvc.trackJobUpdated({
|
|
user_id: 'system',
|
|
platform: 'web',
|
|
job_id: payload.job?._id?.toString() || 'unknown',
|
|
fields_modified: this.detectModifiedFields(payload, updatedJob),
|
|
change_magnitude: oldStatus !== newStatus ? 'major' : 'minor',
|
|
save_method: 'manual'
|
|
});
|
|
|
|
return new jobActions.UpdateSuccess(updatedJob)
|
|
}),
|
|
catchError(err => {
|
|
if (err?.error?.error['.tag'] == 'cannot_edit_job_have_invoice_opened') {
|
|
this.msgSvc.addFailedMsg($localize`:@@cannotEditInvoicedJob:Cannot edit job linked with an opened, paid or uncollectible invoice`);
|
|
} else {
|
|
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.save).replace('#thing#', globals.job));
|
|
}
|
|
return of(new jobActions.UpdateFailed());
|
|
})
|
|
)
|
|
})
|
|
);
|
|
|
|
@Effect()
|
|
deleteJob$: Observable<Action> = this.actions$.pipe(
|
|
ofType<jobActions.Delete>(jobActions.DELETE),
|
|
switchMap(({ payload }) =>
|
|
this.jobSvc.deleteJob(payload).pipe(
|
|
map(() => {
|
|
// Track job deletion with GA4
|
|
this.gaSvc.trackJobDeleted({
|
|
user_id: 'system', // Effects don't have direct user context
|
|
platform: 'web',
|
|
job_id: payload._id?.toString() || 'unknown',
|
|
job_type: payload.appType || 'unknown',
|
|
job_status: payload.status?.toString() || 'unknown',
|
|
deletion_reason: 'user_action',
|
|
deletion_method: 'api_call',
|
|
time_since_creation: payload.createdAt ?
|
|
Math.floor((new Date().getTime() - new Date(payload.createdAt).getTime()) / (1000 * 60 * 60)) : 0
|
|
});
|
|
|
|
this.jobCache.invalidate();
|
|
return new jobActions.DeleteSuccess(payload)
|
|
}),
|
|
catchError(err => {
|
|
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.delete).replace('#thing#', globals.job));
|
|
return of(new jobActions.UpdateFailed());
|
|
})
|
|
)
|
|
)
|
|
);
|
|
|
|
@Effect()
|
|
assignJob$: Observable<Action> = this.actions$.pipe(
|
|
ofType<jobActions.Assign>(jobActions.ASSIGN),
|
|
switchMap(({ payload }) =>
|
|
this.jobSvc.assign(payload).pipe(
|
|
map(() => {
|
|
this.msgSvc.addSuccessMsg($localize`:@@jobAssigned:Job assigned`);
|
|
|
|
// Track job assignment with GA4
|
|
this.gaSvc.trackJobAssigned({
|
|
user_id: 'system',
|
|
platform: 'web',
|
|
job_id: payload.jobId?.toString() || 'unknown',
|
|
assignee_id: payload.asUsers?.[0]?._id?.toString() || 'unknown',
|
|
assignee_role: 'applicator', // Updated to use valid role
|
|
assignment_method: 'manual'
|
|
});
|
|
|
|
return new jobActions.AssignSuccess({ _id: payload.jobId })
|
|
}),
|
|
catchError(err => {
|
|
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.assign).replace('#thing#', globals.job));
|
|
return of(new jobActions.AssignFailed());
|
|
})
|
|
)
|
|
)
|
|
);
|
|
|
|
@Effect()
|
|
saveMap$: Observable<Action> = this.actions$.pipe(
|
|
ofType<jobActions.SaveMap>(jobActions.SAVEMAP),
|
|
switchMap(({ payload }) =>
|
|
this.jobSvc.saveJobMapOps(payload).pipe(
|
|
map(() => {
|
|
this.msgSvc.addSuccessMsg($localize`:@@mapSaved:Map saved`);
|
|
return new jobActions.SaveMapSuccess()
|
|
}),
|
|
catchError(err => {
|
|
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.save).replace('#thing#', globals.map));
|
|
return of(new jobActions.SaveMapFailed());
|
|
})
|
|
)
|
|
)
|
|
);
|
|
|
|
// Helper method to normalize job type to GA4 enum values
|
|
private normalizeJobType(appType: string): 'spraying' | 'seeding' | 'fertilizing' | 'harvesting' {
|
|
const type = appType?.toLowerCase();
|
|
if (type?.includes('spray')) return 'spraying';
|
|
if (type?.includes('seed')) return 'seeding';
|
|
if (type?.includes('fertiliz')) return 'fertilizing';
|
|
if (type?.includes('harvest')) return 'harvesting';
|
|
return 'spraying'; // Default fallback
|
|
}
|
|
|
|
/**
|
|
* Map numeric status to string for GA4 tracking
|
|
*/
|
|
private mapStatusToString(status: number): 'new' | 'ready' | 'downloaded' | 'sprayed' | 'archived' {
|
|
switch (status) {
|
|
case 0: return 'new';
|
|
case 1: return 'ready';
|
|
case 2: return 'downloaded';
|
|
case 3: return 'sprayed';
|
|
case 9: return 'archived';
|
|
default: return 'new';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Detect which fields were modified in the job update
|
|
*/
|
|
private detectModifiedFields(payload: any, updatedJob: any): string[] {
|
|
const modifiedFields: string[] = [];
|
|
const originalJob = payload.job;
|
|
|
|
if (!originalJob) return ['unknown'];
|
|
|
|
// Check common fields that might change
|
|
if (originalJob.status !== updatedJob.status) modifiedFields.push('status');
|
|
if (originalJob.name !== updatedJob.name) modifiedFields.push('name');
|
|
if (originalJob.priority !== updatedJob.priority) modifiedFields.push('priority');
|
|
if (originalJob.startDate !== updatedJob.startDate) modifiedFields.push('startDate');
|
|
if (originalJob.endDate !== updatedJob.endDate) modifiedFields.push('endDate');
|
|
if (originalJob.operator?._id !== updatedJob.operator?._id) modifiedFields.push('operator');
|
|
if (originalJob.vehicle?._id !== updatedJob.vehicle?._id) modifiedFields.push('vehicle');
|
|
|
|
return modifiedFields.length > 0 ? modifiedFields : ['unknown'];
|
|
}
|
|
}
|