278 lines
11 KiB
TypeScript
278 lines
11 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { Actions, Effect, ofType } from '@ngrx/effects';
|
|
import { Observable, of } from 'rxjs';
|
|
import { map, switchMap, catchError, repeat } from 'rxjs/operators';
|
|
|
|
import { Action } from '@ngrx/store';
|
|
|
|
import * as userActions from '../actions/account.actions';
|
|
import { UserService } from '@app/domain/services/user.service';
|
|
import { AuthService } from '@app/domain/services/auth.service';
|
|
import { AppMessageService } from '@app/shared/app-message.service';
|
|
import { PartnerService } from '@app/partners/services/partner.service';
|
|
import { PartnerSystemUser } from '@app/accounts/models/user.model';
|
|
import { RoleIds, globals, KnownPartnerCodes } from '@app/shared/global';
|
|
|
|
@Injectable()
|
|
export class AccountEffects {
|
|
constructor(
|
|
private readonly actions$: Actions,
|
|
private readonly userSvc: UserService,
|
|
private readonly authSvc: AuthService,
|
|
private readonly msgSvc: AppMessageService,
|
|
private readonly partnerSvc: PartnerService
|
|
) {
|
|
}
|
|
|
|
@Effect()
|
|
loadUsers$: Observable<Action> = this.actions$.pipe(
|
|
ofType<userActions.Fetch>(userActions.FETCH),
|
|
switchMap(() =>
|
|
// All account types (including PARTNER_SYSTEM_USER) are returned by the backend
|
|
// /api/users/search endpoint — no separate /api/partners/systemUsers call needed.
|
|
this.userSvc.loadUsers({ byPuid: this.authSvc.user.parent }).pipe(
|
|
map(users => new userActions.FetchSuccess(users))
|
|
)
|
|
),
|
|
catchError(err => this.handleUserOperationError(err, 'load')),
|
|
repeat()
|
|
);
|
|
|
|
@Effect()
|
|
createUser$: Observable<Action> = this.actions$.pipe(
|
|
ofType<userActions.Create>(userActions.CREATE),
|
|
switchMap(({ payload }) => {
|
|
// Extract user data and partner config from payload
|
|
const { partnerConfig, ...userData } = payload;
|
|
|
|
// For partner system users, create them directly through PartnerService
|
|
if (partnerConfig && partnerConfig.vendorSystemType) {
|
|
return this.createPartnerSystemUser(userData, partnerConfig);
|
|
}
|
|
|
|
// For regular users, use UserService directly
|
|
return this.userSvc.saveUser(userData).pipe(
|
|
map((savedUser) => new userActions.CreateSuccess(savedUser))
|
|
);
|
|
}),
|
|
catchError(err => this.handleUserOperationError(err, 'create')),
|
|
repeat()
|
|
);
|
|
|
|
@Effect()
|
|
updateUser$: Observable<Action> = this.actions$.pipe(
|
|
ofType<userActions.Update>(userActions.UPDATE),
|
|
switchMap(({ payload }) => {
|
|
// Extract user data and partner config from payload
|
|
const { partnerConfig, ...userData } = payload;
|
|
|
|
// Case 1: User WITHOUT partner - use UserService directly + cleanup
|
|
if (!partnerConfig || !partnerConfig.vendorSystemType) {
|
|
return this.userSvc.saveUser(userData).pipe(
|
|
switchMap((savedUser) => {
|
|
// Clean up any existing partner system users for non-partner accounts
|
|
return this.cleanupPartnerSystemUsers(userData._id).pipe(
|
|
map(() => new userActions.UpdateSuccess(savedUser)),
|
|
catchError(err => {
|
|
console.error('Partner cleanup failed:', err);
|
|
// User update succeeded, cleanup failed is not critical
|
|
return of(new userActions.UpdateSuccess(savedUser));
|
|
})
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
// Case 2: User WITH partner - use PartnerService workflow completely
|
|
return this.updatePartnerUserWorkflow(userData, partnerConfig).pipe(
|
|
map((savedUser) => new userActions.UpdateSuccess(savedUser))
|
|
);
|
|
}),
|
|
catchError(err => this.handleUserOperationError(err, 'save')),
|
|
repeat()
|
|
);
|
|
|
|
@Effect()
|
|
deleteUser$: Observable<Action> = this.actions$.pipe(
|
|
ofType<userActions.Delete>(userActions.DELETE),
|
|
switchMap(({ payload }) => {
|
|
// Check if the user is a PARTNER_SYSTEM_USER
|
|
if (payload.kind === RoleIds.PARTNER_SYSTEM_USER) {
|
|
// Backend only disables partner system users (sets active=false), it does NOT remove them.
|
|
// Dispatch UpdateSuccess so the store reflects the disabled state in-place rather than
|
|
// removing the row — which would cause it to reappear on the next reload.
|
|
return this.partnerSvc.deleteSystemUser(payload._id).pipe(
|
|
map(() => new userActions.UpdateSuccess({ ...payload, active: false }))
|
|
);
|
|
} else {
|
|
// Use UserService for regular users
|
|
return this.userSvc.deleteUser(payload).pipe(
|
|
map(() => new userActions.DeleteSuccess(payload))
|
|
);
|
|
}
|
|
}),
|
|
catchError(err => this.handleUserOperationError(err, 'delete')),
|
|
repeat()
|
|
);
|
|
|
|
// Partner user workflow methods - use PartnerService exclusively
|
|
private createPartnerSystemUser(userData: any, partnerConfig: any): Observable<Action> {
|
|
// Get partner ID based on vendor type
|
|
return this.getPartnerByVendorType(partnerConfig.vendorSystemType).pipe(
|
|
switchMap(partnerId => {
|
|
if (!partnerId) {
|
|
throw new Error(`Failed to get partner for vendor type: ${partnerConfig.vendorSystemType}`);
|
|
}
|
|
|
|
// Create vendor-specific system user data
|
|
const createData = this.buildPartnerSystemUserData(userData, partnerConfig, partnerId);
|
|
|
|
return this.partnerSvc.createSystemUser(createData).pipe(
|
|
map((systemUser) => {
|
|
// ✅ FIX: Return the created system user with customerId/partnerId for post-save validation
|
|
// Merge the saved systemUser data with original userData to preserve all fields
|
|
return new userActions.CreateSuccess({
|
|
...userData,
|
|
...systemUser,
|
|
// Ensure we have the IDs for post-save validation
|
|
customer: systemUser.customer || createData.customerId,
|
|
partner: systemUser.partner || createData.partnerId
|
|
});
|
|
})
|
|
);
|
|
})
|
|
);
|
|
}
|
|
|
|
private updatePartnerUserWorkflow(userData: any, partnerConfig: any): Observable<any> {
|
|
// Use getSystemUserById to directly fetch the partner system user
|
|
return this.partnerSvc.getSystemUserById(userData._id).pipe(
|
|
switchMap(existingSystemUser => {
|
|
if (existingSystemUser) {
|
|
// Update existing partner system user with backend-compatible structure
|
|
const updateData = this.buildPartnerSystemUserData(userData, partnerConfig, existingSystemUser.partner._id);
|
|
|
|
return this.partnerSvc.updateSystemUser(existingSystemUser._id!, updateData).pipe(
|
|
map(() => userData) // Return the user data
|
|
);
|
|
} else {
|
|
// Partner system user doesn't exist, return error
|
|
throw new Error('Partner system user not found for update');
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Build partner system user data structure based on vendor type
|
|
* This method can be extended to support additional vendors
|
|
*/
|
|
private buildPartnerSystemUserData(userData: any, partnerConfig: any, partnerId: string): any {
|
|
return {
|
|
partnerId: partnerId,
|
|
customerId: userData.parent, // AgMission customer (main applicator account)
|
|
username: userData.username,
|
|
password: userData.password,
|
|
name: userData.name,
|
|
active: userData.active,
|
|
email: userData.email,
|
|
address: userData.address,
|
|
phone: userData.phone,
|
|
companyId: partnerConfig.vendorConfiguration.companyId || null,
|
|
apiKey: partnerConfig.vendorConfiguration.apiKey || null,
|
|
apiSecret: partnerConfig.vendorConfiguration.apiSecret || null
|
|
// NOTE: metadata intentionally omitted — partner identity is carried by
|
|
// partnerId (ObjectId). metadata.vendor was a fragile frontend-derived
|
|
// copy that could silently diverge from the partner document.
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get partner ID by vendor type
|
|
* This method can be extended to support additional vendors
|
|
*/
|
|
private getPartnerByVendorType(vendorType: string): Observable<string | null> {
|
|
return this.partnerSvc.getPartners().pipe(
|
|
map((partners: any[]) => {
|
|
let partner = null;
|
|
|
|
switch (vendorType) {
|
|
case KnownPartnerCodes.SATLOC:
|
|
partner = partners.find(p =>
|
|
p.partnerCode === KnownPartnerCodes.SATLOC.toUpperCase() ||
|
|
p.name?.toLowerCase().includes(KnownPartnerCodes.SATLOC)
|
|
);
|
|
break;
|
|
|
|
// Add additional vendors here as needed
|
|
// case 'other_vendor':
|
|
// partner = partners.find(p =>
|
|
// p.partnerCode === 'OTHER_VENDOR' ||
|
|
// p.name?.toLowerCase().includes('other_vendor')
|
|
// );
|
|
// break;
|
|
|
|
default:
|
|
// Fallback: try to find partner by name or code matching vendor type
|
|
partner = partners.find(p =>
|
|
p.partnerCode?.toLowerCase() === vendorType.toLowerCase() ||
|
|
p.name?.toLowerCase().includes(vendorType.toLowerCase())
|
|
);
|
|
break;
|
|
}
|
|
|
|
return partner ? partner._id : null;
|
|
}),
|
|
catchError(() => of(null))
|
|
);
|
|
}
|
|
|
|
private cleanupPartnerSystemUsers(userId: string): Observable<any> {
|
|
return this.partnerSvc.getSystemUsersForCustomer(userId).pipe(
|
|
switchMap((systemUsers: PartnerSystemUser[]) => {
|
|
if (systemUsers.length === 0) {
|
|
return of(null);
|
|
}
|
|
|
|
// Delete all system users for this customer
|
|
const deleteOperations = systemUsers.map(systemUser =>
|
|
this.partnerSvc.deleteSystemUser(systemUser._id!).pipe(
|
|
catchError(error => {
|
|
console.error('Failed to delete partner system user:', error);
|
|
return of(null);
|
|
})
|
|
)
|
|
);
|
|
|
|
// Wait for all delete operations to complete
|
|
return of(...deleteOperations);
|
|
}),
|
|
catchError(error => {
|
|
console.error('Failed to load partner system users for cleanup:', error);
|
|
return of(null);
|
|
})
|
|
);
|
|
}
|
|
|
|
// Centralized error handler for user operations following subscription.effects pattern
|
|
private handleUserOperationError(err: any, operation: 'create' | 'save' | 'delete' | 'load'): Observable<Action> {
|
|
const actionVerb = operation === 'create' ? globals.create :
|
|
operation === 'save' ? globals.save :
|
|
operation === 'delete' ? globals.delete : globals.load;
|
|
|
|
// For load operation, use 'accounts' (plural), for others use 'account' (singular)
|
|
const thingName = operation === 'load' ? globals.accounts : globals.account;
|
|
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', actionVerb).replace('#thing#', thingName));
|
|
|
|
if (operation === 'create') {
|
|
return of(new userActions.CreateFailed());
|
|
} else if (operation === 'save') {
|
|
return of(new userActions.UpdateFailed());
|
|
} else if (operation === 'delete') {
|
|
return of(new userActions.UpdateFailed()); // Note: There's no DeleteFailed action, using UpdateFailed
|
|
} else {
|
|
return of(new userActions.FetchError());
|
|
}
|
|
}
|
|
}
|