diff --git a/src/app/app.routes.server.ts b/src/app/app.routes.server.ts index 5d09e56..f5e8964 100644 --- a/src/app/app.routes.server.ts +++ b/src/app/app.routes.server.ts @@ -7,6 +7,7 @@ export const serverRoutes: ServerRoute[] = [ { path: ':appId/usersettings', renderMode: RenderMode.Client }, { path: ':appId/add-carnet', renderMode: RenderMode.Client }, { path: ':appId/edit-carnet/:headerid', renderMode: RenderMode.Client }, + { path: ':appId/view-carnet/:headerid', renderMode: RenderMode.Client }, { path: ':appId/add-holder', renderMode: RenderMode.Client }, { path: ':appId/edit-holder/:holderid', renderMode: RenderMode.Client }, { path: ':appId/terms', renderMode: RenderMode.Client }, diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 0948519..b2cdb30 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -17,6 +17,7 @@ export const routes: Routes = [ { path: 'edit-carnet/:headerid', loadComponent: () => import('./carnet/edit/edit-carnet.component').then(m => m.EditCarnetComponent) }, { 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: 'view-carnet/:headerid', loadComponent: () => import('./carnet/view/view-carnet.component').then(m => m.ViewCarnetComponent) }, { path: 'terms', loadComponent: () => import('./carnet/terms-conditions/terms-conditions.component').then(m => m.TermsConditionsComponent) }, { path: '', redirectTo: 'home', pathMatch: 'full' } ], diff --git a/src/app/carnet/shipping/shipping.component.html b/src/app/carnet/shipping/shipping.component.html index 247e96c..1cfff80 100644 --- a/src/app/carnet/shipping/shipping.component.html +++ b/src/app/carnet/shipping/shipping.component.html @@ -3,7 +3,7 @@ -
+

Insurance & Bond

diff --git a/src/app/carnet/shipping/shipping.component.ts b/src/app/carnet/shipping/shipping.component.ts index 4fdc336..d35fcea 100644 --- a/src/app/carnet/shipping/shipping.component.ts +++ b/src/app/carnet/shipping/shipping.component.ts @@ -143,7 +143,7 @@ export class ShippingComponent { this.destroy$.complete(); } - onSubmit(): void { + onSubmit(submitApp: boolean): void { if (this.shippingForm.invalid) { this.shippingForm.markAllAsTouched(); return; @@ -157,12 +157,23 @@ export class ShippingComponent { shippingData.contact = shippingData.shipTo === 'PREPARER' ? this.preparerContact ?? undefined : this.holderContact ?? undefined; } + if (this.holder?.holderType.trim() === 'GOV') { + shippingData.formOfSecurity = this.govFormOfSecurities?.[0].value; + } + this.shippingService.saveShippingDetails(this.headerid, shippingData).pipe(finalize(() => { this.changeInProgress = false; })).subscribe({ next: () => { this.notificationService.showSuccess('Shipping & payments information saved successfully'); this.completed.emit(true); + + if (this.enableSubmitButton && this.headerid && submitApp) { + let currentApplicationDetails = { headerid: this.headerid, applicationName: this.applicationName } + this.storageService.set("currentapplication", currentApplicationDetails); + + this.navigationService.navigate(["terms"]) + } }, error: (error) => { let errorMessage = this.errorHandler.handleApiError(error, 'Failed to save shipping and payment information'); @@ -678,12 +689,7 @@ export class ShippingComponent { } submitApplication(): void { - if (this.headerid) { - let currentApplicationDetails = { headerid: this.headerid, applicationName: this.applicationName } - this.storageService.set("currentapplication", currentApplicationDetails); - } - - this.navigationService.navigate(["terms"]) + this.onSubmit(true); } returnToHome(): void { diff --git a/src/app/carnet/terms-conditions/terms-conditions.component.html b/src/app/carnet/terms-conditions/terms-conditions.component.html index e18b6ba..0653a62 100644 --- a/src/app/carnet/terms-conditions/terms-conditions.component.html +++ b/src/app/carnet/terms-conditions/terms-conditions.component.html @@ -7,73 +7,10 @@ - -

1. Definitions

-

ATA Carnet: An international customs document that permits the tax-free and duty-free - temporary export and import of goods for up to one year.

-

Holder: The individual or entity in whose name the carnet is issued and who is - responsible for complying with all terms.

-

Goods: Merchandise, equipment, or products covered by the carnet.

- -

2. Carnet Usage

-

2.1 The carnet may be used for:

+

Agreement language will go here

+

Fees and Charges

+

The following fees apply:

    -
  • Commercial samples
  • -
  • Professional equipment
  • -
  • Goods for exhibitions and fairs
  • -
  • Goods for scientific, educational, or cultural purposes
  • -
- -

2.2 The carnet cannot be used for:

-
    -
  • Consumable or disposable items
  • -
  • Goods for processing or repair
  • -
  • Goods intended for sale or permanent export
  • -
  • Prohibited or restricted items under any applicable laws
  • -
- -

3. Holder Responsibilities

-

3.1 The holder must:

-
    -
  • Present the carnet to customs when crossing borders
  • -
  • Ensure all goods listed in the carnet are returned by the expiration date
  • -
  • Pay all applicable duties and taxes if goods are not re-exported
  • -
  • Notify the issuing association immediately of any lost or stolen carnets
  • -
- -

4. Validity Period

-

4.1 The carnet is valid for one year from the date of issue.

-

4.2 Goods must be re-exported before the carnet expires.

-

4.3 Extensions may be granted in exceptional circumstances with approval from all relevant customs - authorities.

- -

5. Customs Procedures

-

5.1 The holder must present the carnet to customs:

-
    -
  • When first exporting goods from the home country
  • -
  • When entering each foreign country
  • -
  • When re-exporting goods from each foreign country
  • -
  • When finally re-importing goods to the home country
  • -
- -

6. Security Requirements

-

6.1 The issuing association may require security up to 40% of the total value of goods.

-

6.2 Security will be refunded when the carnet is fully discharged.

-

6.3 The security may be forfeited if terms are violated.

- -

7. Liability and Insurance

-

7.1 The holder is solely responsible for:

-
    -
  • All customs duties and taxes if goods are not re-exported
  • -
  • Any damage to goods while in transit
  • -
  • Compliance with all import/export regulations
  • -
-

7.2 We recommend obtaining comprehensive insurance coverage for all goods.

- -

8. Fees and Charges

-

8.1 The following fees apply:

-
    -
  • Basic fee: {{estimatedFees.basicFee | currency}}
  • Counterfoil Fee: {{estimatedFees.counterFoilFee | currency}}
  • @@ -85,15 +22,6 @@
  • Cargo Premium: {{estimatedFees.cargoPremium | currency}}
  • LDI Premium: {{estimatedFees.ldiPremium | currency}}
- -

9. Dispute Resolution

-

9.1 Any disputes shall be resolved through arbitration in the jurisdiction of the issuing association. -

-

9.2 The holder agrees to be bound by the arbitration decision.

- -

10. Governing Law

-

These terms shall be governed by the laws of the country where the carnet was issued.

- I have read and agree to all terms and conditions above diff --git a/src/app/core/models/holder/holder-filter.ts b/src/app/core/models/holder/holder-filter.ts index 7397b2a..6648b7f 100644 --- a/src/app/core/models/holder/holder-filter.ts +++ b/src/app/core/models/holder/holder-filter.ts @@ -1,3 +1,4 @@ export interface HolderFilter { holderName?: string; + holderId?: number; } diff --git a/src/app/core/services/carnet/carnet.service.ts b/src/app/core/services/carnet/carnet.service.ts index 339e2b9..ab57530 100644 --- a/src/app/core/services/carnet/carnet.service.ts +++ b/src/app/core/services/carnet/carnet.service.ts @@ -54,6 +54,6 @@ export class CarnetService { P_HEADERID: headerid } - return this.http.post(`${this.apiUrl}/${this.apiDb}//ProcessCarnet`, appData); + return this.http.patch(`${this.apiUrl}/${this.apiDb}//ProcessCarnet`, appData); } } diff --git a/src/app/core/services/carnet/travel-plan.service.ts b/src/app/core/services/carnet/travel-plan.service.ts index 13d42da..ac4b452 100644 --- a/src/app/core/services/carnet/travel-plan.service.ts +++ b/src/app/core/services/carnet/travel-plan.service.ts @@ -16,7 +16,7 @@ export class TravelPlanService { private userService = inject(UserService); getCountriesAndMessages(): Observable { - return this.http.get(`${this.apiUrl}/${this.apiDb}/GetCountriesAndMessages`).pipe( + return this.http.get(`${this.apiUrl}/${this.apiDb}/GetCountriesAndMessages/${this.userService.getUserSpid()}`).pipe( map((response) => response.map((item) => ({ name: item.COUNTRYNAME, diff --git a/src/app/core/services/holder/holder.service.ts b/src/app/core/services/holder/holder.service.ts index b724f0a..60e9694 100644 --- a/src/app/core/services/holder/holder.service.ts +++ b/src/app/core/services/holder/holder.service.ts @@ -17,7 +17,17 @@ export class HolderService { private userService = inject(UserService); getHolders(filter: HolderFilter): Observable { - return this.http.get(`${this.apiUrl}/${this.apiDb}/SearchHolder/${this.userService.getUserSpid()}/${this.userService.getSafeUser()}?P_HOLDERNAME=${filter.holderName}`).pipe( + + // add query parameters to the URL based on the filter + let queryParams = ``; + if (filter.holderName) { + queryParams += `P_HOLDERNAME=${filter.holderName}`; + } + if (filter.holderId) { + queryParams += `P_HOLDERID=${filter.holderId}`; + } + + return this.http.get(`${this.apiUrl}/${this.apiDb}/SearchHolder/${this.userService.getUserSpid()}/${this.userService.getSafeUser()}?${queryParams}`).pipe( map(response => this.mapToHolders(response)) ) } diff --git a/src/app/core/services/home.service.ts b/src/app/core/services/home.service.ts index 9c71dad..82e9161 100644 --- a/src/app/core/services/home.service.ts +++ b/src/app/core/services/home.service.ts @@ -41,4 +41,21 @@ export class HomeService { headerId: item.HEADERID, })); } + + deleteCarnet(headerid: number): Observable { + 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 { + const data = { + P_SPID: this.userService.getUserSpid(), + P_HEADERID: headerid, + P_PRINTGL: 'Y' + } + return this.http.post(`${this.apiUrl}/${this.apiDb}/PrintCarnet`, data); + } } diff --git a/src/app/holder/search/search-holder.component.html b/src/app/holder/search/search-holder.component.html index 3ffe5dd..79ac7da 100644 --- a/src/app/holder/search/search-holder.component.html +++ b/src/app/holder/search/search-holder.component.html @@ -1,6 +1,6 @@
- -
+ +
@@ -22,7 +22,7 @@ Clear Search - @@ -35,7 +35,7 @@
-
+
Show Inactive Holders @@ -78,7 +78,7 @@
@@ -87,11 +87,11 @@ edit - - diff --git a/src/app/holder/search/search-holder.component.ts b/src/app/holder/search/search-holder.component.ts index 7323ff2..ce21071 100644 --- a/src/app/holder/search/search-holder.component.ts +++ b/src/app/holder/search/search-holder.component.ts @@ -19,6 +19,7 @@ import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/c import { MatDialog } from '@angular/material/dialog'; import { StorageService } from '../../core/services/common/storage.service'; import { finalize } from 'rxjs'; +import { environment } from '../../../environments/environment'; @Component({ selector: 'app-holder-search', @@ -51,6 +52,8 @@ export class SearchHolderComponent { @Output() holderSelectionCompleted = new EventEmitter(); + allowActions: boolean = environment.appType === 'client' && !this.isViewMode; + showInactiveHolders: boolean = false; holders: BasicDetail[] = [] isLoading: boolean = false; @@ -91,14 +94,24 @@ export class SearchHolderComponent { } ngOnInit(): void { - this.searchHolders(); + if (this.allowActions) { + this.searchHolders(); + } } ngOnChanges(changes: SimpleChanges): void { - if (changes['selectedHolderId'] && this.paginator) { - if (this.selectedHolderId && this.dataSource.data.length) { + if (changes['selectedHolderId']) { + if (this.selectedHolderId && this.paginator && this.dataSource.data.length && this.allowActions) { this.goToItemPage(this.selectedHolderId); } + + if (!this.allowActions && this.selectedHolderId) { + this.showSelectedHolder(this.selectedHolderId); + } + } + + if (changes['isViewMode'] && this.isViewMode) { + this.allowActions = environment.appType === 'client' && !this.isViewMode; } } @@ -130,6 +143,27 @@ export class SearchHolderComponent { this.renderHolders(); } + showSelectedHolder(holderid: number): void { + this.isLoading = true; + const filterData: HolderFilter = { + holderId: holderid + } + + this.holderService.getHolders(filterData).pipe(finalize(() => { + this.isLoading = false; + })).subscribe({ + next: (holders: BasicDetail[]) => { + this.dataSource.data = this.holders = holders; + this.renderHolders() + }, + error: (error: any) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load selected holder'); + this.notificationService.showError(errorMessage); + console.error('Error loading selected holder:', error); + } + }); + } + searchHolders(): void { this.isLoading = true; const filterData: HolderFilter = this.searchForm.value; diff --git a/src/app/home/home.component.html b/src/app/home/home.component.html index 01de758..0cbc0c7 100644 --- a/src/app/home/home.component.html +++ b/src/app/home/home.component.html @@ -118,12 +118,24 @@ Actions - +
+ + + + +
diff --git a/src/app/home/home.component.scss b/src/app/home/home.component.scss index e29d501..e0c866f 100644 --- a/src/app/home/home.component.scss +++ b/src/app/home/home.component.scss @@ -79,7 +79,7 @@ justify-content: center; } - mat-table { + table { width: 100%; mat-icon { @@ -90,6 +90,16 @@ transform: scale(1.1); } } + + .mat-column-actions { + width: 135px; + + .action-buttons { + width: 100%; + display: flex; + gap: 4px; + } + } } .no-data-message { diff --git a/src/app/home/home.component.ts b/src/app/home/home.component.ts index be98861..510da46 100644 --- a/src/app/home/home.component.ts +++ b/src/app/home/home.component.ts @@ -16,6 +16,8 @@ import { NotificationService } from '../core/services/common/notification.servic import { NavigationService } from '../core/services/common/navigation.service'; import { MatRadioChange } from '@angular/material/radio'; import * as XLSX from 'xlsx'; +import { MatDialog } from '@angular/material/dialog'; +import { ConfirmDialogComponent } from '../shared/components/confirm-dialog/confirm-dialog.component'; @Component({ selector: 'app-home', @@ -50,6 +52,7 @@ export class HomeComponent { private notificationService = inject(NotificationService); private commonService = inject(CommonService); private navigationService = inject(NavigationService); + private dialog = inject(MatDialog); constructor(userPrefenceService: UserPreferencesService) { this.userPreferences = userPrefenceService.getPreferences(); @@ -172,6 +175,65 @@ export class HomeComponent { } } + viewCarnet(item: any): void { + if (item && item.headerId) { + this.navigateTo(['view-carnet', item.headerId], { + queryParams: { applicationname: item.applicationName } + }); + } else { + this.notificationService.showError('Invalid carnet data'); + } + } + + printGeneralList(headerId: number): void { + if (headerId) { + this.homeService.printGeneralList(headerId).subscribe({ + next: () => { + + }, + error: (error) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to print general list'); + this.notificationService.showError(errorMessage); + console.error('Error printing general list:', error); + } + }); + } else { + this.notificationService.showError('Invalid carnet data'); + } + } + + deleteCarnet(headerId: number): void { + if (headerId) { + const dialogRef = this.dialog.open(ConfirmDialogComponent, { + width: '350px', + data: { + title: 'Delete Carnet', + message: 'Are you sure you want to delete the carnet?', + confirmText: 'Yes', + cancelText: 'Cancel' + } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.homeService.deleteCarnet(headerId).subscribe({ + next: () => { + this.notificationService.showSuccess('Carnet deleted successfully'); + this.loadCarnetData(); + }, + error: (error) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to delete carnet'); + this.notificationService.showError(errorMessage); + console.error('Error deleting carnet:', error); + } + }); + } + }); + } else { + this.notificationService.showError('Invalid carnet data'); + } + } + navigateTo(route: any[], extras?: any): void { this.navigationService.navigate(route, extras); }