duplicate, copy and additional sets

This commit is contained in:
Cyril Joseph 2025-08-15 17:04:59 -03:00
parent d31084fad3
commit a62273476d
21 changed files with 602 additions and 52 deletions

View File

@ -8,6 +8,9 @@ export const serverRoutes: ServerRoute[] = [
{ path: ':appId/add-carnet', renderMode: RenderMode.Client }, { path: ':appId/add-carnet', renderMode: RenderMode.Client },
{ path: ':appId/edit-carnet/:headerid', renderMode: RenderMode.Client }, { path: ':appId/edit-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/view-carnet/:headerid', renderMode: RenderMode.Client }, { path: ':appId/view-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/duplicate-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/extend-carnet/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/additional-sets/:headerid', renderMode: RenderMode.Client },
{ path: ':appId/add-holder', renderMode: RenderMode.Client }, { path: ':appId/add-holder', renderMode: RenderMode.Client },
{ path: ':appId/edit-holder/:holderid', renderMode: RenderMode.Client }, { path: ':appId/edit-holder/:holderid', renderMode: RenderMode.Client },
{ path: ':appId/terms', renderMode: RenderMode.Client }, { path: ':appId/terms', renderMode: RenderMode.Client },

View File

@ -18,6 +18,9 @@ export const routes: Routes = [
{ path: 'add-holder', loadComponent: () => import('./holder/add/add-holder.component').then(m => m.AddHolderComponent) }, { path: 'add-holder', loadComponent: () => import('./holder/add/add-holder.component').then(m => m.AddHolderComponent) },
{ path: 'edit-holder/:holderid', loadComponent: () => import('./holder/edit/edit-holder.component').then(m => m.EditHolderComponent) }, { path: 'edit-holder/:holderid', loadComponent: () => import('./holder/edit/edit-holder.component').then(m => m.EditHolderComponent) },
{ path: 'view-carnet/:headerid', loadComponent: () => import('./carnet/view/view-carnet.component').then(m => m.ViewCarnetComponent) }, { path: 'view-carnet/:headerid', loadComponent: () => import('./carnet/view/view-carnet.component').then(m => m.ViewCarnetComponent) },
{ path: 'duplicate-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ path: 'extend-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ path: 'additional-sets/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) },
{ path: 'terms', loadComponent: () => import('./carnet/terms-conditions/terms-conditions.component').then(m => m.TermsConditionsComponent) }, { path: 'terms', loadComponent: () => import('./carnet/terms-conditions/terms-conditions.component').then(m => m.TermsConditionsComponent) },
{ path: '', redirectTo: 'home', pathMatch: 'full' } { path: '', redirectTo: 'home', pathMatch: 'full' }
], ],

View File

@ -4,36 +4,36 @@
[selectedIndex]="currentStep"> [selectedIndex]="currentStep">
<!-- Application Name Step --> <!-- Application Name Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.applicationDetail" <mat-step [completed]="stepsCompleted.applicationDetail" [editable]="stepsCompleted.applicationDetail">
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Application Name</ng-template> <ng-template matStepLabel>Application Name</ng-template>
<app-application [isEditMode]="isEditMode" [applicationName]="applicationName"> <app-application [isEditMode]="isEditMode" [applicationName]="applicationName">
</app-application> </app-application>
</mat-step> </mat-step>
<!-- Holder Selection Step --> <!-- Holder Selection Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.holderSelection" <mat-step [completed]="stepsCompleted.holderSelection" [editable]="stepsCompleted.applicationDetail">
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Holder Selection</ng-template> <ng-template matStepLabel>Holder Selection</ng-template>
<app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid" <app-holder (completed)="onHolderSelectionSaved($event)" [headerid]="headerid"
[applicationName]="applicationName" (updated)="onHolderSelectionUpdated($event)"> [isViewMode]="applicationType !== 'edit'" [applicationName]="applicationName"
(updated)="onHolderSelectionUpdated($event)">
</app-holder> </app-holder>
</mat-step> </mat-step>
<!-- Goods Section Step --> <!-- Goods Section Step -->
<mat-step *ngIf="applicationType === 'new'" [completed]="stepsCompleted.goodsSection" <mat-step [completed]="stepsCompleted.goodsSection" [editable]="stepsCompleted.applicationDetail">
[editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Goods Section</ng-template> <ng-template matStepLabel>Goods Section</ng-template>
<app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode" <app-goods (completed)="onGoodsSectionSaved($event)" [headerid]="headerid"
[isEditMode]="isEditMode && applicationType === 'edit'" [isViewMode]="applicationType !== 'edit'"
[userPreferences]="userPreferences"> [userPreferences]="userPreferences">
</app-goods> </app-goods>
</mat-step> </mat-step>
<!-- Travel Plan Step --> <!-- Travel Plan Step -->
<mat-step *ngIf="applicationType === 'new' || applicationType === 'extend' || applicationType === 'additional'" <mat-step [completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail">
[completed]="stepsCompleted.travelPlan" [editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Travel Plan</ng-template> <ng-template matStepLabel>Travel Plan</ng-template>
<app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="headerid"> <app-travel-plan (completed)="onTravelPlanSaved($event)" [headerid]="headerid"
[applicationType]="applicationType" (needsInsurance)="onNeedsInsurance($event)"
[isViewMode]="applicationType !== 'edit' && applicationType !== 'additional'">
</app-travel-plan> </app-travel-plan>
</mat-step> </mat-step>
@ -41,7 +41,8 @@
<mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail"> <mat-step [completed]="stepsCompleted.shipping" [editable]="stepsCompleted.applicationDetail">
<ng-template matStepLabel>Shipping & Payment</ng-template> <ng-template matStepLabel>Shipping & Payment</ng-template>
<app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode" <app-shipping (completed)="onShippingSaved($event)" [headerid]="headerid" [isEditMode]="isEditMode"
[applicationName]="applicationName" [enableSubmitButton]="allSectionsCompleted"> [applicationType]="applicationType" [applicationName]="applicationName"
[enableSubmitButton]="allSectionsCompleted">
</app-shipping> </app-shipping>
</mat-step> </mat-step>

View File

@ -22,7 +22,7 @@ import { UserPreferencesService } from '../../core/services/user-preference.serv
export class EditCarnetComponent { export class EditCarnetComponent {
currentStep = 0; currentStep = 0;
isLinear = false; isLinear = false;
applicationType: 'new' | 'additional' | 'duplicate' | 'extend' | null = 'new'; applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
isEditMode = true; // Set to true for edit mode isEditMode = true; // Set to true for edit mode
headerid: number = 0; headerid: number = 0;
userPreferences: UserPreferences; userPreferences: UserPreferences;
@ -55,10 +55,27 @@ export class EditCarnetComponent {
let returnTo = this.route.snapshot.queryParamMap.get('return'); let returnTo = this.route.snapshot.queryParamMap.get('return');
// set application type based on the route url
const urlSegments = this.route.snapshot.url.map(segment => segment.path);
if (urlSegments.includes('duplicate-carnet')) {
this.applicationType = 'duplicate';
} else if (urlSegments.includes('extend-carnet')) {
this.applicationType = 'extend';
}
else if (urlSegments.includes('additional-sets')) {
this.applicationType = 'additional';
} else {
this.applicationType = 'edit';
}
if (returnTo === 'holder') { if (returnTo === 'holder') {
this.currentStep = 1; this.currentStep = 1;
} else if (returnTo === 'shipping') { } else if (returnTo === 'shipping') {
this.currentStep = 4; this.currentStep = 4;
} else if (this.applicationType === 'additional') {
this.currentStep = 3;
} else if (this.applicationType === 'duplicate') {
this.currentStep = 4;
} }
} }
@ -94,6 +111,10 @@ export class EditCarnetComponent {
this.isAllSectionsCompleted(); this.isAllSectionsCompleted();
} }
onNeedsInsurance(data: boolean): void {
this.shippingComponent?.refreshInsuranceData(data);
}
isAllSectionsCompleted() { isAllSectionsCompleted() {
this.allSectionsCompleted = this.stepsCompleted.applicationDetail this.allSectionsCompleted = this.stepsCompleted.applicationDetail
&& this.stepsCompleted.holderSelection && this.stepsCompleted.goodsSection && this.stepsCompleted.holderSelection && this.stepsCompleted.goodsSection

View File

@ -107,7 +107,7 @@ export class GoodsComponent {
next: (goodsDetail: Goods) => { next: (goodsDetail: Goods) => {
if (goodsDetail) { if (goodsDetail) {
this.patchFormData(goodsDetail); this.patchFormData(goodsDetail);
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0); this.completed.emit((this.goodsForm.valid || this.goodsForm.disabled) && this.dataSource.data.length > 0);
} }
}, },
error: (error: any) => { error: (error: any) => {
@ -129,7 +129,7 @@ export class GoodsComponent {
})).subscribe({ })).subscribe({
next: (items: GoodsItem[]) => { next: (items: GoodsItem[]) => {
this.dataSource.data = items; this.dataSource.data = items;
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0); this.completed.emit((this.goodsForm.valid || this.goodsForm.disabled) && this.dataSource.data.length > 0);
}, },
error: (error: any) => { error: (error: any) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load goods items'); let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load goods items');
@ -238,7 +238,6 @@ export class GoodsComponent {
this.notificationService.showSuccess(`Goods ${this.isEditing ? 'updated' : 'added'} successfully`); this.notificationService.showSuccess(`Goods ${this.isEditing ? 'updated' : 'added'} successfully`);
this.loadGoodsItems(); this.loadGoodsItems();
this.cancelEdit(); this.cancelEdit();
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
}, },
error: (error) => { error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} goods items`); let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} goods items`);
@ -336,7 +335,6 @@ export class GoodsComponent {
this.notificationService.showSuccess(`Goods uploaded successfully`); this.notificationService.showSuccess(`Goods uploaded successfully`);
this.loadGoodsItems(); this.loadGoodsItems();
this.cancelEdit(); this.cancelEdit();
this.completed.emit(this.goodsForm.valid && this.dataSource.data.length > 0);
this.fileToUpload = null; this.fileToUpload = null;
}, },
error: (error) => { error: (error) => {

View File

@ -40,6 +40,7 @@ export class ShippingComponent {
@Input() isViewMode = false; @Input() isViewMode = false;
@Input() enableSubmitButton = false; @Input() enableSubmitButton = false;
@Input() applicationName: string = ''; @Input() applicationName: string = '';
@Input() applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
@Output() completed = new EventEmitter<boolean>(); @Output() completed = new EventEmitter<boolean>();
@ -61,6 +62,7 @@ export class ShippingComponent {
showAddressForm = false; showAddressForm = false;
showContactForm = false; showContactForm = false;
deliveryEstimate: string = ''; deliveryEstimate: string = '';
needsInsurance = true;
holderid: number = 0; holderid: number = 0;
holder: Holder | undefined | null = null; holder: Holder | undefined | null = null;
@ -126,7 +128,6 @@ export class ShippingComponent {
this.shippingForm.get('deliveryType')?.valueChanges.subscribe(() => { this.shippingForm.get('deliveryType')?.valueChanges.subscribe(() => {
this.calculateDeliveryEstimate(); this.calculateDeliveryEstimate();
}); });
} }
ngOnInit(): void { ngOnInit(): void {
@ -164,6 +165,21 @@ export class ShippingComponent {
shippingData.formOfSecurity = this.govFormOfSecurities?.[0].value; shippingData.formOfSecurity = this.govFormOfSecurities?.[0].value;
} }
if (this.applicationType === 'duplicate' && this.shippingFromDB) {
shippingData.needsInsurance = this.shippingFromDB.needsInsurance;
shippingData.needsLostDocProtection = this.shippingFromDB.needsLostDocProtection;
shippingData.formOfSecurity = this.shippingFromDB.formOfSecurity;
}
if (this.applicationType === 'additional' && this.shippingFromDB) {
shippingData.needsLostDocProtection = this.shippingFromDB.needsLostDocProtection;
shippingData.formOfSecurity = this.shippingFromDB.formOfSecurity;
if (!this.needsInsurance) {
shippingData.needsInsurance = this.shippingFromDB.needsInsurance;
}
}
this.shippingService.saveShippingDetails(this.headerid, shippingData).pipe(finalize(() => { this.shippingService.saveShippingDetails(this.headerid, shippingData).pipe(finalize(() => {
this.changeInProgress = false; this.changeInProgress = false;
})).subscribe({ })).subscribe({
@ -172,7 +188,11 @@ export class ShippingComponent {
this.completed.emit(true); this.completed.emit(true);
if (this.enableSubmitButton && this.headerid && submitApp) { if (this.enableSubmitButton && this.headerid && submitApp) {
let currentApplicationDetails = { headerid: this.headerid, applicationName: this.applicationName } let currentApplicationDetails = {
headerid: this.headerid,
applicationName: this.applicationName,
applicationType: this.applicationType
}
this.storageService.set("currentapplication", currentApplicationDetails); this.storageService.set("currentapplication", currentApplicationDetails);
this.navigationService.navigate(["terms"]) this.navigationService.navigate(["terms"])
@ -215,6 +235,17 @@ export class ShippingComponent {
} }
} }
public refreshInsuranceData(needsInsurance: boolean): void {
this.needsInsurance = needsInsurance;
if (this.applicationType === 'additional') {
if (this.needsInsurance) {
this.shippingForm.get('needsInsurance')?.enable();
} else {
this.shippingForm.get('needsInsurance')?.disable();
}
}
}
editAddressForm(): void { editAddressForm(): void {
this.showAddressForm = true; this.showAddressForm = true;
let shipTo = this.shippingForm.get('shipTo')?.value; let shipTo = this.shippingForm.get('shipTo')?.value;
@ -451,10 +482,15 @@ export class ShippingComponent {
if (this.isViewMode) { if (this.isViewMode) {
this.shippingForm.disable(); this.shippingForm.disable();
} }
if (this.applicationType === 'duplicate' || this.applicationType === 'additional') {
this.shippingForm.get('formOfSecurity')?.disable();
this.shippingForm.get('needsLostDocProtection')?.disable();
this.shippingForm.get('needsInsurance')?.disable();
}
} }
patchAddressContactData(): void { patchAddressContactData(): void {
if (!this.shippingFromDB) { if (!this.shippingFromDB) {
return return
} }
@ -467,7 +503,7 @@ export class ShippingComponent {
if (this.holder?.holderType.trim() === 'GOV') { if (this.holder?.holderType.trim() === 'GOV') {
formOfSecurityControl?.setValue(this.govFormOfSecurities?.[0].value); formOfSecurityControl?.setValue(this.govFormOfSecurities?.[0].value);
formOfSecurityControl?.disable(); formOfSecurityControl?.disable();
} else if (!this.isViewMode) { } else if (!this.isViewMode && this.applicationType === 'edit') {
formOfSecurityControl?.enable(); formOfSecurityControl?.enable();
} }

View File

@ -29,7 +29,7 @@ export class TermsConditionsComponent {
currentDate = new Date(); currentDate = new Date();
hasReadAllTerms = false; hasReadAllTerms = false;
currentApplicationDetails: { headerid: number, applicationName: string } | null = null; currentApplicationDetails: { headerid: number, applicationName: string, applicationType: string } | null = null;
changeInProgress: boolean = false; changeInProgress: boolean = false;
estimatedFees: Fees = {}; estimatedFees: Fees = {};
@ -40,7 +40,7 @@ export class TermsConditionsComponent {
private carnetService = inject(CarnetService); private carnetService = inject(CarnetService);
constructor() { constructor() {
this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string }>('currentapplication') this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string, applicationType: string }>('currentapplication')
} }
ngOnInit(): void { ngOnInit(): void {
@ -80,7 +80,18 @@ export class TermsConditionsComponent {
onDecline(): void { onDecline(): void {
this.storageService.removeItem('currentapplication'); this.storageService.removeItem('currentapplication');
this.navigationService.navigate(["edit-carnet", this.currentApplicationDetails?.headerid],
let route: string = 'edit-carnet';
if (this.currentApplicationDetails?.applicationType === 'duplicate') {
route = 'duplicate-carnet';
}
else if (this.currentApplicationDetails?.applicationType === 'extend') {
route = 'extend-carnet';
} else if (this.currentApplicationDetails?.applicationType === 'additional') {
route = 'additional-sets';
}
this.navigationService.navigate([route, this.currentApplicationDetails?.headerid],
{ {
state: { isEditMode: true }, state: { isEditMode: true },
queryParams: { queryParams: {

View File

@ -142,6 +142,12 @@
</div> </div>
</div> </div>
<div class="message-section" *ngIf="showAdditionalSetsValidationMessage">
<mat-error>
At least one of the sets to be entered. (either number of times entering & leaving, Foreign sets or
Transit sets)
</mat-error>
</div>
<button mat-raised-button color="primary" type="submit" *ngIf="!isViewMode" [disabled]="changeInProgress"> <button mat-raised-button color="primary" type="submit" *ngIf="!isViewMode" [disabled]="changeInProgress">
Save Save
</button> </button>

View File

@ -126,6 +126,10 @@
} }
} }
} }
.message-section {
font-size: 0.875rem;
}
} }
} }

View File

@ -20,8 +20,10 @@ import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/c
export class TravelPlanComponent { export class TravelPlanComponent {
@Input() headerid: number = 0; @Input() headerid: number = 0;
@Input() isViewMode = false; @Input() isViewMode = false;
@Input() applicationType: 'new' | 'edit' | 'additional' | 'duplicate' | 'extend' | null = 'edit';
@Output() completed = new EventEmitter<boolean>(); @Output() completed = new EventEmitter<boolean>();
@Output() needsInsurance = new EventEmitter<boolean>();
private fb = inject(FormBuilder); private fb = inject(FormBuilder);
private dialog = inject(MatDialog); private dialog = inject(MatDialog);
@ -33,6 +35,7 @@ export class TravelPlanComponent {
travelForm: FormGroup; travelForm: FormGroup;
isLoading = false; isLoading = false;
changeInProgress = false; changeInProgress = false;
showAdditionalSetsValidationMessage: boolean = false;
visitsCount = 1; visitsCount = 1;
transitsCount = 1; transitsCount = 1;
@ -109,6 +112,10 @@ export class TravelPlanComponent {
...this.selectedAvailableVisitCountry, ...this.selectedAvailableVisitCountry,
visits: this.visitsCount visits: this.visitsCount
}); });
if (this.applicationType === 'additional') {
this.needsInsurance.emit(this.selectedVisitCountries.length > 0);
}
} }
this.visitsCount = 1; this.visitsCount = 1;
@ -219,6 +226,10 @@ export class TravelPlanComponent {
this.selectedVisitCountries = this.selectedVisitCountries.filter( this.selectedVisitCountries = this.selectedVisitCountries.filter(
c => c.value !== this.selectedVisitCountry!.value c => c.value !== this.selectedVisitCountry!.value
); );
if (this.applicationType === 'additional') {
this.needsInsurance.emit(this.selectedVisitCountries.length > 0);
}
} }
// Remove country from transits // Remove country from transits
@ -276,17 +287,30 @@ export class TravelPlanComponent {
this.selectedVisitCountries = plan?.entries; this.selectedVisitCountries = plan?.entries;
this.selectedTransitCountries = plan?.transits; this.selectedTransitCountries = plan?.transits;
if (this.applicationType === 'additional') {
this.needsInsurance.emit(this.selectedVisitCountries.length > 0)
}
// Patch form values // Patch form values
this.travelForm.patchValue({ this.travelForm.patchValue({
usaEntries: plan?.usSets, usaEntries: plan?.usSets === 0 ? '' : plan?.usSets,
visitsInput: 1, visitsInput: 1,
transitsInput: 1 // Reset to default transitsInput: 1 // Reset to default
}); });
this.completed.emit(this.travelForm.valid);
if (this.isViewMode) { if (this.isViewMode) {
this.travelForm.disable(); this.travelForm.disable();
} }
if (this.applicationType === 'additional') {
const usentriesControl = this.travelForm.get('usaEntries');
usentriesControl?.setValidators([Validators.min(1), Validators.max(99)]);
usentriesControl?.updateValueAndValidity();
}
this.completed.emit(this.travelForm.valid || this.travelForm.disabled
|| (this.applicationType === 'additional' && (this.travelForm.value.usaEntries
|| this.selectedVisitCountries.length > 0 || this.selectedTransitCountries.length > 0)));
} }
onSubmit(): void { onSubmit(): void {
@ -295,6 +319,15 @@ export class TravelPlanComponent {
return; return;
} }
this.showAdditionalSetsValidationMessage = false;
if (this.applicationType === 'additional' && (!this.travelForm.value.usaEntries &&
this.selectedVisitCountries.length === 0 &&
this.selectedTransitCountries.length === 0
)) {
this.showAdditionalSetsValidationMessage = true;
return;
}
const travelPlan: TravelPlan = { const travelPlan: TravelPlan = {
usSets: this.travelForm.value.usaEntries, usSets: this.travelForm.value.usaEntries,
entries: this.selectedVisitCountries, entries: this.selectedVisitCountries,

View File

@ -56,4 +56,62 @@ export class CarnetService {
return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}//ProcessCarnet`, appData); return this.http.patch<any>(`${this.apiUrl}/${this.apiDb}//ProcessCarnet`, appData);
} }
deleteCarnet(headerid: number): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_HEADERID: headerid
}
return this.http.delete(`${this.apiUrl}/${this.apiDb}/DeleteCarnet`, { body: data });
}
printCarnet(headerid: number): Observable<any> {
const data = {
P_SPID: this.userService.getUserSpid(),
P_HEADERID: headerid,
P_PRINTGL: 'N'
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/PrintCarnet`, data);
}
printGeneralList(headerid: number): Observable<any> {
const data = {
P_SPID: this.userService.getUserSpid(),
P_HEADERID: headerid
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/PrintGL`, data);
}
duplicateCarnet(carnetNumber: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_CARNETNO: carnetNumber
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/DuplicateCarnet`, data);
}
extendCarnet(carnetNumber: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_CARNETNO: carnetNumber
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/ExtendCarnet`, data);
}
additionalSets(carnetNumber: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_CARNETNO: carnetNumber
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/AddlSets`, data);
}
copyCarnet(headerid: number, applicationName: string): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_HEADERID: headerid,
P_APPLICATIONNAME: applicationName
}
return this.http.patch(`${this.apiUrl}/${this.apiDb}/CopyCarnet`, data);
}
} }

View File

@ -36,7 +36,12 @@ export class ApiErrorHandlerService {
return this.genericErrorMessage; return this.genericErrorMessage;
} }
return messageString.slice(0, -1); // slice off the last pipe character only if the last character is a pipe
if (messageString.endsWith('|')) {
return messageString.slice(0, -1);
}
return messageString;
// .split('|') // .split('|')
// .map(msg => msg.trim()) // .map(msg => msg.trim())
// .filter(msg => msg.length > 0); // .filter(msg => msg.length > 0);

View File

@ -41,21 +41,4 @@ export class HomeService {
headerid: item.HEADERID, headerid: item.HEADERID,
})); }));
} }
deleteCarnet(headerid: number): Observable<any> {
const data = {
P_USERID: this.userService.getUser(),
P_HEADERID: headerid
}
return this.http.delete(`${this.apiUrl}/${this.apiDb}/DeleteCarnet`, { body: data });
}
printGeneralList(headerid: number): Observable<any> {
const data = {
P_SPID: this.userService.getUserSpid(),
P_HEADERID: headerid,
P_PRINTGL: 'Y'
}
return this.http.post(`${this.apiUrl}/${this.apiDb}/PrintCarnet`, data);
}
} }

View File

@ -176,7 +176,7 @@ export class ContactsComponent {
renderContacts(): void { renderContacts(): void {
if (this.showInactiveContacts) { if (this.showInactiveContacts) {
this.dataSource.data = this.contacts; this.dataSource.data = this.contacts.filter(contact => contact.isInactive);
} else { } else {
this.dataSource.data = this.contacts.filter(contact => !contact.isInactive); this.dataSource.data = this.contacts.filter(contact => !contact.isInactive);
} }

View File

@ -50,6 +50,7 @@
position: relative; position: relative;
overflow: auto; overflow: auto;
border-radius: 8px; border-radius: 8px;
margin-top: 0.5rem;
.loading-shade { .loading-shade {
position: absolute; position: absolute;

View File

@ -185,6 +185,8 @@ export class SearchHolderComponent {
renderHolders() { renderHolders() {
if (this.showInactiveHolders) { if (this.showInactiveHolders) {
this.dataSource.data = this.holders.filter((holder: any) => holder?.isInactive);
} else if (this.isViewMode) {
this.dataSource.data = this.holders; this.dataSource.data = this.holders;
} else { } else {
this.dataSource.data = this.holders.filter((holder: any) => !holder?.isInactive); this.dataSource.data = this.holders.filter((holder: any) => !holder?.isInactive);

View File

@ -0,0 +1,60 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { AngularMaterialModule } from '../shared/module/angular-material.module';
@Component({
selector: 'app-bor-dialog',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule],
template: `
<h2 mat-dialog-title>{{title}}</h2>
<mat-dialog-content>
This is NOT your carnet. Did you send BOR document to {{spName}}?
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button color="primary"
(click)="onSubmit()">
Yes
</button>
<button mat-button (click)="onCancel()">No</button>
</mat-dialog-actions>
`,
styles: [`
`]
})
export class BorDialogComponent {
carnetAction: any | null = null;
title: string = '';
spName: string = '';
constructor(
public dialogRef: MatDialogRef<BorDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { carnetAction: string, spName: string },
) {
this.carnetAction = data.carnetAction;
this.spName = data.spName;
}
ngOnInit(): void {
this.setTitle();
}
setTitle(): void {
if (this.carnetAction === 'duplicate') {
this.title = 'Duplicate Carnet';
} else if (this.carnetAction === 'extend') {
this.title = 'Extend Carnet';
} else if (this.carnetAction === 'additional') {
this.title = 'Additional Sets';
}
}
onSubmit(): void {
this.dialogRef.close(this.carnetAction);
}
onCancel(): void {
this.dialogRef.close('delete');
}
}

View File

@ -0,0 +1,87 @@
import { Component, inject, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CommonModule } from '@angular/common';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { AngularMaterialModule } from '../shared/module/angular-material.module';
@Component({
selector: 'app-carnet-dialog',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule],
template: `
<h2 mat-dialog-title>{{title}}</h2>
<mat-dialog-content>
<form [formGroup]="carnetForm">
<div class="form-row">
<!-- Carnet Number Field -->
<mat-form-field appearance="outline">
<mat-label>Carnet Number</mat-label>
<input matInput formControlName="carnetNumber" required>
<mat-error *ngIf="carnetForm.get('carnetNumber')?.errors?.['required']">
Carnet number is required
</mat-error>
</mat-form-field>
</div>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button color="primary" (click)="onSubmit()" [disabled]="!carnetForm.valid">
Ok
</button>
<button mat-button (click)="onCancel()">Cancel</button>
</mat-dialog-actions>
`,
styles: [`
.form-row {
display: flex;
gap: 16px;
margin-top: 0.5rem;
align-items: start;
mat-form-field {
flex: 1;
margin-bottom: 6px;
}
}
`]
})
export class CarnetDialogComponent {
carnetAction: any | null = null;
carnetForm: FormGroup;
title: string = '';
private fb = inject(FormBuilder);
constructor(
public dialogRef: MatDialogRef<CarnetDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { carnetAction: string }
) {
this.carnetAction = data.carnetAction;
this.carnetForm = this.fb.group({
carnetNumber: ['', Validators.required]
});
}
ngOnInit(): void {
this.setTitle();
}
setTitle(): void {
if (this.carnetAction === 'duplicate') {
this.title = 'Duplicate Carnet';
} else if (this.carnetAction === 'extend') {
this.title = 'Extend Carnet';
} else if (this.carnetAction === 'additional') {
this.title = 'Additional Sets';
}
}
onSubmit(): void {
this.dialogRef.close(this.carnetForm.value.carnetNumber);
}
onCancel(): void {
this.dialogRef.close();
}
}

View File

@ -0,0 +1,75 @@
import { Component, inject, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { CommonModule } from '@angular/common';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { AngularMaterialModule } from '../shared/module/angular-material.module';
@Component({
selector: 'app-copy-carnet-dialog',
imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule],
template: `
<h2 mat-dialog-title>Copy Carnet</h2>
<mat-dialog-content>
<form [formGroup]="carnetForm">
<div class="form-row">
<!-- Application Name Field -->
<mat-form-field appearance="outline">
<mat-label>Application Name</mat-label>
<input matInput formControlName="applicationName" required>
<mat-error *ngIf="carnetForm.get('applicationName')?.errors?.['required']">
Application name is required
</mat-error>
</mat-form-field>
</div>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-raised-button color="primary" (click)="onSubmit()" [disabled]="!carnetForm.valid">
Ok
</button>
<button mat-button (click)="onCancel()">Cancel</button>
</mat-dialog-actions>
`,
styles: [`
.form-row {
display: flex;
gap: 16px;
margin-top: 0.5rem;
align-items: start;
mat-form-field {
flex: 1;
margin-bottom: 6px;
}
}
`]
})
export class CopyCarnetDialogComponent {
headerid: number = 0;
carnetForm: FormGroup;
private fb = inject(FormBuilder);
constructor(
public dialogRef: MatDialogRef<CopyCarnetDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { headerid: number }
) {
this.headerid = data.headerid;
this.carnetForm = this.fb.group({
applicationName: ['', Validators.required]
});
}
onSubmit(): void {
const data = {
applicationName: this.carnetForm.value.applicationName,
headerid: this.headerid
}
this.dialogRef.close(data);
}
onCancel(): void {
this.dialogRef.close();
}
}

View File

@ -130,16 +130,20 @@
<mat-menu #carnetActions="matMenu"> <mat-menu #carnetActions="matMenu">
<button mat-menu-item (click)="viewCarnet(item)"> <button mat-menu-item (click)="viewCarnet(item)">
<mat-icon>article</mat-icon> <mat-icon>article</mat-icon>
<span>View Carnet</span> <span>View</span>
</button> </button>
<button mat-menu-item (click)="deleteCarnet(item.headerid)" <button mat-menu-item (click)="deleteCarnet(item.headerid)"
*ngIf="getCarnetStatusLabel(item.carnetStatus) === 'Not Submitted'"> *ngIf="getCarnetStatusLabel(item.carnetStatus) === 'Not Submitted'">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
<span>Delete Carnet</span> <span>Delete</span>
</button> </button>
<button mat-menu-item (click)="printGeneralList(item.headerid)"> <button mat-menu-item (click)="printGeneralList(item.headerid)">
<mat-icon>print</mat-icon> <mat-icon>print</mat-icon>
<span>Print Carnet</span> <span>Print</span>
</button>
<button mat-menu-item (click)="copyCarnet(item.headerid)">
<mat-icon>file_copy</mat-icon>
<span>Copy</span>
</button> </button>
</mat-menu> </mat-menu>
<!-- <!--

View File

@ -18,10 +18,15 @@ import { MatRadioChange } from '@angular/material/radio';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ConfirmDialogComponent } from '../shared/components/confirm-dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '../shared/components/confirm-dialog/confirm-dialog.component';
import { CarnetDialogComponent } from './carnet-dialog.component';
import { BorDialogComponent } from './bor-dialog.component';
import { FormsModule } from '@angular/forms';
import { CarnetService } from '../core/services/carnet/carnet.service';
import { CopyCarnetDialogComponent } from './copy-carnet-dialog.component';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
imports: [AngularMaterialModule, ChartComponent, CommonModule], imports: [AngularMaterialModule, ChartComponent, CommonModule, FormsModule],
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrl: './home.component.scss', styleUrl: './home.component.scss',
providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }], providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }],
@ -48,6 +53,7 @@ export class HomeComponent {
displayedColumns: string[] = ['applicationName', 'holderName', 'carnetNumber', 'usSets', 'foreignSets', 'transitSets', 'carnetValue', 'issueDate', 'expiryDate', 'orderType', 'carnetStatus', 'actions']; displayedColumns: string[] = ['applicationName', 'holderName', 'carnetNumber', 'usSets', 'foreignSets', 'transitSets', 'carnetValue', 'issueDate', 'expiryDate', 'orderType', 'carnetStatus', 'actions'];
private homeService = inject(HomeService); private homeService = inject(HomeService);
private carnetService = inject(CarnetService);
private errorHandler = inject(ApiErrorHandlerService); private errorHandler = inject(ApiErrorHandlerService);
private notificationService = inject(NotificationService); private notificationService = inject(NotificationService);
private commonService = inject(CommonService); private commonService = inject(CommonService);
@ -157,17 +163,143 @@ export class HomeComponent {
} }
additionalSets(event: MatRadioChange): void { additionalSets(event: MatRadioChange): void {
if (event.value) {
const dialogRef = this.dialog.open(CarnetDialogComponent, {
width: '500px',
data: { carnetAction: 'additional' },
});
dialogRef.afterClosed().subscribe(carnetNumber => {
if (carnetNumber) {
this.carnetService.additionalSets(carnetNumber).subscribe({
next: (data) => {
if (data && data.BORFLAG === 'Y') {
this.showBorDialog('additional', data.HEADERID, data.SPNAME, data.APPLICATIONNAME);
} else if (data) {
this.navigateTo(['additional-sets', data.HEADERID], {
queryParams: { applicationname: data.APPLICATIONNAME }
});
}
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to add additional sets');
this.notificationService.showError(errorMessage);
console.error('Error adding additional sets:', error);
}
});
}
});
}
} }
duplicateCarnet(event: MatRadioChange): void { duplicateCarnet(event: MatRadioChange): void {
if (event.value) {
const dialogRef = this.dialog.open(CarnetDialogComponent, {
width: '500px',
data: { carnetAction: 'duplicate' },
});
dialogRef.afterClosed().subscribe(carnetNumber => {
if (carnetNumber) {
this.carnetService.duplicateCarnet(carnetNumber).subscribe({
next: (data) => {
if (data && data.BORFLAG === 'Y') {
this.showBorDialog('duplicate', data.HEADERID, data.SPNAME, data.APPLICATIONNAME);
} else if (data) {
this.navigateTo(['duplicate-carnet', data.HEADERID], {
queryParams: { applicationname: data.APPLICATIONNAME }
});
}
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to duplicate carnet');
this.notificationService.showError(errorMessage);
console.error('Error duplicating carnet:', error);
}
});
}
});
}
} }
extendCarnet(event: MatRadioChange): void { extendCarnet(event: MatRadioChange): void {
if (event.value) {
const dialogRef = this.dialog.open(CarnetDialogComponent, {
width: '500px',
data: { carnetAction: 'extend' },
});
dialogRef.afterClosed().subscribe(carnetNumber => {
if (carnetNumber) {
this.carnetService.extendCarnet(carnetNumber).subscribe({
next: (data) => {
if (data && data.BORFLAG === 'Y') {
this.showBorDialog('extend', data.HEADERID, data.SPNAME, data.APPLICATIONNAME);
} else if (data) {
this.navigateTo(['extend-carnet', data.HEADERID], {
queryParams: { applicationname: data.APPLICATIONNAME }
});
}
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to extend carnet');
this.notificationService.showError(errorMessage);
console.error('Error extend carnet:', error);
}
});
}
});
}
}
showBorDialog(carnetAction: string, headerid: number, spName: string, applicationName: string): void {
const dialogRef = this.dialog.open(BorDialogComponent, {
width: '500px',
data: { carnetAction: carnetAction },
});
dialogRef.afterClosed().subscribe(carnetAction => {
if (carnetAction === 'delete') {
this.carnetService.deleteCarnet(headerid).subscribe({
next: () => {
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete carnet');
this.notificationService.showError(errorMessage);
console.error('Error deleting carnet:', error);
}
});
} else {
let route: string = 'edit-carnet';
if (carnetAction === 'duplicate') {
route = 'duplicate-carnet';
}
else if (carnetAction === 'extend') {
route = 'extend-carnet';
} else if (carnetAction === 'additional') {
route = 'additional-sets';
}
this.navigateTo([route, headerid], {
queryParams: { applicationname: applicationName }
});
}
});
} }
editCarnet(item: any): void { editCarnet(item: any): void {
if (item && item.headerid) { if (item && item.headerid) {
this.navigateTo(['edit-carnet', item.headerid], { let route: string = 'edit-carnet';
if (item.orderType === 'DUPLICATE') {
route = 'duplicate-carnet';
}
else if (item.orderType === 'REPLACE') {
route = 'extend-carnet';
} else if (item.orderType === 'ADDLSETS') {
route = 'additional-sets';
}
this.navigateTo([route, item.headerid], {
queryParams: { applicationname: item.applicationName } queryParams: { applicationname: item.applicationName }
}); });
} else { } else {
@ -187,7 +319,7 @@ export class HomeComponent {
printGeneralList(headerid: number): void { printGeneralList(headerid: number): void {
if (headerid) { if (headerid) {
this.homeService.printGeneralList(headerid).subscribe({ this.carnetService.printGeneralList(headerid).subscribe({
next: () => { next: () => {
}, },
@ -216,7 +348,7 @@ export class HomeComponent {
dialogRef.afterClosed().subscribe(result => { dialogRef.afterClosed().subscribe(result => {
if (result) { if (result) {
this.homeService.deleteCarnet(headerid).subscribe({ this.carnetService.deleteCarnet(headerid).subscribe({
next: () => { next: () => {
this.notificationService.showSuccess('Carnet deleted successfully'); this.notificationService.showSuccess('Carnet deleted successfully');
this.loadCarnetData(); this.loadCarnetData();
@ -234,6 +366,33 @@ export class HomeComponent {
} }
} }
copyCarnet(headerid: number): void {
if (headerid) {
const dialogRef = this.dialog.open(CopyCarnetDialogComponent, {
width: '500px',
data: { headerid: headerid },
});
dialogRef.afterClosed().subscribe(data => {
if (data) {
this.carnetService.copyCarnet(data.headerid, data.applicationName).subscribe({
next: (applicationData: any) => {
this.notificationService.showSuccess('Carnet copied successfully');
this.navigateTo(['edit-carnet', applicationData.HEADERID], {
queryParams: { applicationname: applicationData.APPLICATIONNAME }
});
},
error: (error) => {
let errorMessage = this.errorHandler.handleApiError(error, 'Failed to copy carnet');
this.notificationService.showError(errorMessage);
console.error('Error copying carnet:', error);
}
});
}
});
}
}
navigateTo(route: any[], extras?: any): void { navigateTo(route: any[], extras?: any): void {
this.navigationService.navigate(route, extras); this.navigationService.navigate(route, extras);
} }