manage preparer api integration updates

This commit is contained in:
Cyril Joseph 2025-06-14 20:42:26 -03:00
parent 0b2d45e16d
commit 38021b8979
14 changed files with 250 additions and 56 deletions

View File

@ -0,0 +1,4 @@
export interface ContactLogin {
clientContactId: number;
password: string;
}

View File

@ -15,6 +15,7 @@ export interface Contact {
createdBy?: string | null;
lastUpdatedBy?: string | null;
lastUpdatedDate?: Date | null;
isInactive?: boolean | null; // TODO
inactivatedDate?: Date | null; // TODO
isInactive?: boolean | null;
inactivatedDate?: Date | null;
hasLogin?: boolean; // Indicates if the contact has a login
}

View File

@ -12,6 +12,6 @@ export interface Location {
createdBy?: string | null;
lastUpdatedBy?: string | null;
lastUpdatedDate?: Date | null;
isInactive?: boolean | null; // TODO
inactivatedDate?: Date | null; // TODO
isInactive?: boolean | null;
inactivatedDate?: Date | null;
}

View File

@ -4,6 +4,7 @@ import { HttpClient } from '@angular/common/http';
import { environment } from '../../../../environments/environment';
import { map, Observable, of } from 'rxjs';
import { Contact } from '../../models/preparer/contact';
import { ContactLogin } from '../../models/preparer/contact-login';
@Injectable({
providedIn: 'root'
@ -38,7 +39,8 @@ export class ContactService {
lastUpdatedBy: contact.LASTUPDATEDBY || null,
lastUpdatedDate: contact.LASTUPDATEDDATE || null,
isInactive: contact.INACTIVEFLAG === 'Y' || false,
inactivatedDate: contact.INACTIVEDATE || null
inactivatedDate: contact.INACTIVEDATE || null,
hasLogin: contact.LOGINFLAG === 'Y' || false
}));
}
@ -81,7 +83,23 @@ export class ContactService {
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateClientContacts`, contact);
}
// deleteContact(clientContactId: string): Observable<any> {
// return this.http.post(`${this.apiUrl}/${this.apiDb}/InactivateSPContact?p_clientcontactid=${clientContactId}`, null);
// }
createContactLogin(data: ContactLogin): Observable<any> {
const contact = {
P_SPID: this.userService.getUserSpid(),
P_CLIENTCONTACTID: data.clientContactId,
P_ENABLEPASSWORDPOLICY: 'Y',
P_PASSWORD: data.password,
P_USERID: this.userService.getUser()
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateClientLogins`, contact);
}
inactivateContact(clientContactId: string): Observable<any> {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/InactivateClientContacts/${this.userService.getUserSpid()}/${clientContactId}/${this.userService.getUser()}`, null);
}
reactivateContact(clientContactId: string): Observable<any> {
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ReactivateClientContacts/${this.userService.getUserSpid()}/${clientContactId}/${this.userService.getUser()}`, null);
}
}

View File

@ -75,8 +75,4 @@ export class LocationService {
return this.http.put(`${this.apiUrl}/${this.apiDb}/UpdateClientLocations`, location);
}
// deleteLocation(clientContactId: string): Observable<any> {
// return this.http.post(`${this.apiUrl}/${this.apiDb}/InactivateSPContact?p_clientcontactid=${clientContactId}`, null);
// }
}

View File

@ -6,7 +6,8 @@
<mat-step [completed]="basicDetailsCompleted">
<ng-template matStepLabel>Basic Details</ng-template>
<app-basic-details [isEditMode]="isEditMode" (clientidCreated)="onBasicDetailsSaved($event)">
<app-basic-details [isEditMode]="isEditMode" (clientidCreated)="onBasicDetailsSaved($event)"
(showLocations)="onShowLocations($event)">
</app-basic-details>
</mat-step>
@ -20,7 +21,7 @@
</mat-step>
<!-- Location Step -->
<mat-step [completed]="locationCompleted" [editable]="!!clientid && contactsCompleted">
<mat-step [completed]="locationCompleted" [editable]="!!clientid && contactsCompleted" *ngIf="showLocation">
<ng-template matStepLabel>Locations</ng-template>
<app-location *ngIf="clientid" [clientid]="clientid" (hasLocations)="onLocationSaved($event)"

View File

@ -24,6 +24,7 @@ export class AddPreparerComponent {
basicDetailsCompleted: boolean = false;
contactsCompleted: boolean = false;
locationCompleted: boolean = false;
showLocation: boolean = false;
constructor(userPrefenceService: UserPreferencesService) {
this.userPreferences = userPrefenceService.getPreferences();
@ -42,6 +43,10 @@ export class AddPreparerComponent {
this.locationCompleted = event;
}
onShowLocations(event: boolean): void {
this.showLocation = event;
}
onStepChange(event: StepperSelectionEvent): void {
this.currentStep = event.selectedIndex;
}

View File

@ -138,6 +138,14 @@
</mat-form-field>
</div>
<!-- Additional Locations -->
<div class="form-row" *ngIf="!isEditMode">
<mat-checkbox formControlName="hasAdditionalLocations"
(change)="onHasAdditionalLocationsChange($event)">
Do you have more branch offices?
</mat-checkbox>
</div>
<div class="form-actions">
<button mat-raised-button color="primary" type="submit" [disabled]="basicDetailsForm.invalid">
Save

View File

@ -12,6 +12,8 @@ import { ApiErrorHandlerService } from '../../core/services/common/api-error-han
import { BasicDetailService } from '../../core/services/preparer/basic-detail.service';
import { BasicDetail } from '../../core/models/preparer/basic-detail';
import { ZipCodeValidator } from '../../shared/validators/zipcode-validator';
import { LocationService } from '../../core/services/preparer/location.service';
import { Location } from '../../core/models/preparer/location';
@Component({
selector: 'app-basic-details',
@ -24,6 +26,7 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
@Input() clientid: number = 0;
@Output() clientidCreated = new EventEmitter<string>();
@Output() clientName = new EventEmitter<string>();
@Output() showLocations = new EventEmitter<boolean>();
basicDetailsForm: FormGroup;
countries: Country[] = [];
@ -39,6 +42,7 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
private fb: FormBuilder,
private commonService: CommonService,
private basicDetailService: BasicDetailService,
private locationService: LocationService,
private notificationService: NotificationService,
private errorHandler: ApiErrorHandlerService) {
this.basicDetailsForm = this.createForm();
@ -83,7 +87,8 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
country: ['', Validators.required],
zip: ['', [Validators.required, ZipCodeValidator('country')]],
carnetIssuingRegion: ['', Validators.required],
revenueLocation: ['', Validators.required]
revenueLocation: ['', Validators.required],
hasAdditionalLocations: [false]
});
}
@ -167,11 +172,13 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
if (this.isEditMode) {
this.basicDetailsForm.get('carnetIssuingRegion')?.disable();
this.basicDetailsForm.get('lookupCode')?.disable();
}
}
onCountryChange(country: string): void {
this.basicDetailsForm.get('state')?.reset();
this.basicDetailsForm.get('revenueLocation')?.reset();
if (country) {
this.loadStates(country);
@ -180,6 +187,11 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
this.basicDetailsForm.get('zip')?.updateValueAndValidity();
}
onHasAdditionalLocationsChange(event: any): void {
const hasAdditionalLocations = event.checked;
this.showLocations.emit(hasAdditionalLocations);
}
saveBasicDetails(): void {
if (this.basicDetailsForm.invalid) {
this.basicDetailsForm.markAllAsTouched();
@ -195,6 +207,7 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
// non editable fields values
if (this.isEditMode) {
basicDetailData.carnetIssuingRegion = this.basicDetailsForm.get('carnetIssuingRegion')?.value;
basicDetailData.lookupCode = this.basicDetailsForm.get('lookupCode')?.value;
}
const saveObservable = this.isEditMode && this.clientid > 0
@ -206,10 +219,11 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
this.notificationService.showSuccess(`Basic details ${this.isEditMode ? 'updated' : 'added'} successfully`);
if (!this.isEditMode) {
this.clientidCreated.emit(basicData.clientId);
this.clientidCreated.emit(basicData.CLIENTID);
this.saveLocation(basicData.CLIENTID, basicDetailData);
}
if( this.isEditMode) {
if (this.isEditMode) {
this.clientName.emit(basicDetailData.name);
}
},
@ -221,6 +235,31 @@ export class BasicDetailsComponent implements OnInit, OnDestroy {
});
}
saveLocation(clientid: number, basicDetail: BasicDetail): void {
const locationData: Location = {
clientid: clientid,
name: basicDetail.name,
address1: basicDetail.address1,
address2: basicDetail.address2,
city: basicDetail.city,
country: basicDetail.country,
state: basicDetail.state,
zip: basicDetail.zip,
locationid: 0, // Default to 0 for new location
}
this.locationService.createLocation(clientid, locationData).subscribe({
next: () => {
//this.notificationService.showSuccess('Location added successfully');
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to add location');
this.notificationService.showError(errorMessage);
console.error('Error saving location:', error);
}
});
}
// Convenience getter for easy access to form fields
get f() {
return this.basicDetailsForm.controls;

View File

@ -68,17 +68,19 @@
<button mat-icon-button color="primary" (click)="editContact(contact)" matTooltip="Edit">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="primary" (click)="createLogin()" matTooltip="Login">
<button mat-icon-button color="primary" *ngIf="!contact.isInactive || !contact.hasLogin"
[hidden]="contact.isInactive || contact.hasLogin" (click)="createLogin(contact)"
matTooltip="Login">
<mat-icon>person</mat-icon>
</button>
<button mat-icon-button color="warn" *ngIf="!contact.defaultContact || !contact.isInactive" (click)="
deleteContact(contact.clientContactid)" [hidden]="contact.defaultContact || contact.isInactive"
matTooltip="Inactivate">
inactivateContact(contact.clientContactId)"
[hidden]="contact.defaultContact || contact.isInactive" matTooltip="Inactivate">
<mat-icon>delete</mat-icon>
</button>
<button mat-icon-button color="warn" *ngIf="!contact.isInactive" (click)="
deleteContact(contact.clientContactid)" [hidden]="contact.isInactive" matTooltip="Inactivate">
<mat-icon>delete</mat-icon>
<button mat-icon-button color="warn" *ngIf="contact.isInactive" (click)="
reactivateContact(contact.clientContactId)" [hidden]="!contact.isInactive" matTooltip="Reactivate">
<mat-icon>how_to_reg</mat-icon>
</button>
<!-- <button mat-icon-button (click)="setDefaultContact(contact.contactId)"
[color]="contact.defaultContact ? 'primary' : ''" matTooltip="Set as default">
@ -213,7 +215,7 @@
<div class="form-row">
<mat-checkbox formControlName="defaultContact">Default Contact</mat-checkbox>
</div> -->
<div *ngIf="isEditing" class="readonly-section">
<div *ngIf="isEditing" class="readonly-section top-divider">
<div class="readonly-fields">
<div class="field-column">
<!-- Last Changed By -->
@ -260,4 +262,45 @@
</div>
</form>
</div>
<!-- Contact Login Form -->
<div class="form-container" *ngIf="showLoginForm">
<form [formGroup]="contactLoginForm" (ngSubmit)="saveLogin()">
<div class="form-header">
<h3>Create Login</h3>
</div>
<div *ngIf="isEditing" class="readonly-section bottom-divider">
<div class="readonly-fields">
<div class="field-column">
<!-- Email -->
<div class="readonly-field">
<label>Email</label>
<div class="readonly-value">
{{contactLoginReadOnlyFields.email}}
</div>
</div>
</div>
</div>
</div>
<div class="form-row">
<mat-form-field appearance="outline">
<mat-label>Password</mat-label>
<input matInput formControlName="password" required>
<mat-icon matSuffix>lock</mat-icon>
<mat-error *ngIf="contactLoginForm.get('password')?.errors?.['required']">
Password is required
</mat-error>
</mat-form-field>
</div>
<div class="form-actions">
<button mat-button type="button" (click)="cancelEdit()">Cancel</button>
<button mat-raised-button color="primary" type="submit" [disabled]="contactLoginForm.invalid">
Save
</button>
</div>
</form>
</div>
</div>

View File

@ -120,9 +120,17 @@
margin-top: 16px;
}
.readonly-section {
.top-divider {
padding-top: 0.5rem;
border-top: 1px solid #eee;
}
.bottom-divider {
padding-bottom: 0.5rem;
border-bottom: 1px solid #eee;
}
.readonly-section {
.readonly-fields {
display: flex;

View File

@ -14,6 +14,7 @@ import { MatDialog } from '@angular/material/dialog';
import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service';
import { Contact } from '../../core/models/preparer/contact';
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
import { ContactLogin } from '../../core/models/preparer/contact-login';
@Component({
selector: 'app-contacts',
@ -29,10 +30,12 @@ export class ContactsComponent {
displayedColumns: string[] = ['firstName', 'lastName', 'title', 'phone', 'mobile', 'email', 'defaultContact', 'actions'];
dataSource = new MatTableDataSource<any>();
contactForm: FormGroup;
contactLoginForm: FormGroup;
isEditing = false;
currentContactId: number | null = null;
isLoading = false;
showForm = false;
showLoginForm = false
showInactiveContacts = false;
contacts: Contact[] = [];
contactReadOnlyFields: any = {
@ -42,6 +45,10 @@ export class ContactsComponent {
inactivatedDate: null
};
contactLoginReadOnlyFields: any = {
email: null
};
@Input() clientid: number = 0;
@Input() userPreferences: UserPreferences = {};
@Output() hasContacts = new EventEmitter<boolean>();
@ -64,6 +71,10 @@ export class ContactsComponent {
email: ['', [Validators.required, Validators.email, Validators.maxLength(100)]],
defaultContact: [false]
});
this.contactLoginForm = this.fb.group({
password: ['', [Validators.required]]
});
}
ngOnInit(): void {
@ -95,6 +106,7 @@ export class ContactsComponent {
addNewContact(): void {
this.showForm = true;
this.showLoginForm = false;
this.isEditing = false;
this.currentContactId = null;
this.contactForm.reset();
@ -103,6 +115,7 @@ export class ContactsComponent {
editContact(contact: Contact): void {
this.showForm = true;
this.showLoginForm = false;
this.isEditing = true;
this.currentContactId = contact.clientContactId;
this.contactForm.patchValue({
@ -165,42 +178,103 @@ export class ContactsComponent {
}
}
deleteContact(contactId: string): void {
// const dialogRef = this.dialog.open(ConfirmDialogComponent, {
// width: '350px',
// data: {
// title: 'Confirm Delete',
// message: 'Are you sure you want to delete this contact?',
// confirmText: 'Delete',
// cancelText: 'Cancel'
// }
// });
inactivateContact(contactId: string): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '350px',
data: {
title: 'Confirm Inactivation',
message: 'Are you sure you want to inactivate this contact?',
confirmText: 'Yes',
cancelText: 'Cancel'
}
});
// dialogRef.afterClosed().subscribe(result => {
// if (result) {
// this.contactService.deleteContact(contactId).subscribe({
// next: () => {
// this.notificationService.showSuccess('Contact deleted successfully');
// this.loadContacts();
// },
// error: (error) => {
// let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete contact');
// this.notificationService.showError(errorMessage);
// console.error('Error deleting contact:', error);
// }
// });
// }
// });
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.contactService.inactivateContact(contactId).subscribe({
next: () => {
this.notificationService.showSuccess('Contact inactivated successfully');
this.loadContacts();
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to inactivate contact');
this.notificationService.showError(errorMessage);
console.error('Error inactivating contact:', error);
}
});
}
});
}
createLogin(): void {
reactivateContact(contactId: string): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
width: '350px',
data: {
title: 'Confirm Reactivation',
message: 'Are you sure you want to reactivate this contact?',
confirmText: 'Yes',
cancelText: 'Cancel'
}
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.contactService.reactivateContact(contactId).subscribe({
next: () => {
this.notificationService.showSuccess('Contact reactivated successfully');
this.loadContacts();
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to reactivate contact');
this.notificationService.showError(errorMessage);
console.error('Error reactivating contact:', error);
}
});
}
});
}
createLogin(contact: Contact): void {
this.showForm = false;
this.showLoginForm = true;
this.isEditing = true;
this.currentContactId = contact.clientContactId;
this.contactLoginReadOnlyFields.email = contact.email;
}
saveLogin(): void {
if (this.contactLoginForm.invalid) {
this.contactLoginForm.markAllAsTouched();
return;
}
const contactLoginData: ContactLogin = {
clientContactId: this.currentContactId!,
password: this.contactLoginForm.value.password
};
this.contactService.createContactLogin(contactLoginData).subscribe({
next: () => {
this.notificationService.showSuccess(`Login created successfully`);
this.loadContacts();
this.cancelEdit();
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to create login`);
this.notificationService.showError(errorMessage);
console.error('Error saving login:', error);
}
});
}
cancelEdit(): void {
this.showForm = false;
this.showLoginForm = false;
this.isEditing = false;
this.currentContactId = null;
this.contactForm.reset();
this.contactLoginForm.reset();
}
// setDefaultContact(contactId: string): void {

View File

@ -72,7 +72,7 @@
<!-- Address Column -->
<ng-container matColumnDef="address">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Address</th>
<td mat-cell *matCellDef="let client">{{ getAddressLabel(client.address1, client.address2, client.zip)}}
<td mat-cell *matCellDef="let client">{{ getAddressLabel(client.address1, client.address2)}}
</td>
</ng-container>

View File

@ -162,14 +162,11 @@ export class ManagePreparerComponent implements OnInit, OnDestroy, AfterViewInit
});
}
getAddressLabel(address1: string, address2?: string, zip?: string): string {
getAddressLabel(address1: string, address2?: string): string {
let addressLabel = address1;
if (address2) {
addressLabel += `, ${address2}`;
}
if (zip) {
addressLabel += `, ${zip}`;
}
return addressLabel;
}