diff --git a/src/app/carnet/checkout/checkout.component.ts b/src/app/carnet/checkout/checkout.component.ts index f8e1926..085d7da 100644 --- a/src/app/carnet/checkout/checkout.component.ts +++ b/src/app/carnet/checkout/checkout.component.ts @@ -9,6 +9,7 @@ import { NotificationService } from '../../core/services/common/notification.ser import { StorageService } from '../../core/services/common/storage.service'; import { finalize } from 'rxjs'; import { AngularMaterialModule } from '../../shared/module/angular-material.module'; +import { PaymentDetail } from '../../core/models/carnet/payment-details'; // This is necessary to tell TypeScript that a global 'paypal' object exists. declare var paypal: any; @@ -30,10 +31,7 @@ export class CheckoutComponent { @ViewChild('paypalcontrol', { static: true }) paypalElement!: ElementRef; - orderDetails = { - price: '', - description: '', - }; + orderDetails: PaymentDetail = {}; orderTotal = 0.00; currency = 'USD'; @@ -47,6 +45,8 @@ export class CheckoutComponent { constructor() { this.currentApplicationDetails = this.storageService.get<{ headerid: number, applicationName: string, applicationType: string }>('currentapplication') + this.orderDetails.headerid = this.currentApplicationDetails?.headerid; + this.orderDetails.applicationName = this.currentApplicationDetails?.applicationName; this.orderDetails.description = ` 'Carnet Application - ${this.currentApplicationDetails?.headerid} - ${this.currentApplicationDetails?.applicationName}` } @@ -99,7 +99,7 @@ export class CheckoutComponent { try { // Call the createOrder method from your service const orderId = await this.paymentService.createOrder(this.orderDetails); - // console.log('Order ID created by server:', orderId); + this.orderDetails.orderId = orderId; return orderId; } catch (error) { this.notificationService.showError('Failed to initiate the payment'); @@ -112,7 +112,7 @@ export class CheckoutComponent { onApprove: async (data: any, actions: any) => { try { // data.orderID is the ID of the transaction from PayPal - const captureDetails = await this.paymentService.completePayment(data.orderID); + await this.paymentService.completePayment(this.orderDetails); // You can now redirect the user or update the UI this.changeInProgress = true; @@ -139,11 +139,20 @@ export class CheckoutComponent { // 3. Handle errors onError: (error: any) => { this.notificationService.showError('Payment transaction failed'); + + this.orderDetails.paymentStatus = 'Failed'; + this.orderDetails.paymentErrorDetails = JSON.stringify(error); + this.paymentService.logTransactionDetails(this.orderDetails); console.error('An error occurred during the transaction:', error); }, // 4. Handle cancellation onCancel: (data: any) => { + this.orderDetails.paymentStatus = 'Cancelled'; + if (this.orderDetails.orderId) { + this.paymentService.logTransactionDetails(this.orderDetails); + } + this.onCancel(); } }).render(this.paypalElement.nativeElement) diff --git a/src/app/core/models/carnet/payment-details.ts b/src/app/core/models/carnet/payment-details.ts new file mode 100644 index 0000000..a056935 --- /dev/null +++ b/src/app/core/models/carnet/payment-details.ts @@ -0,0 +1,9 @@ +export interface PaymentDetail { + headerid?: number; + applicationName?: string; + price?: string; + description?: string; + orderId?: string; + paymentStatus?: string; + paymentErrorDetails?: string; +} diff --git a/src/app/core/models/holder-type.ts b/src/app/core/models/holder-type.ts new file mode 100644 index 0000000..c393c57 --- /dev/null +++ b/src/app/core/models/holder-type.ts @@ -0,0 +1,5 @@ +export interface HolderType { + name: string; + id: string; + value: string; +} diff --git a/src/app/core/models/service-provider/cargo-rate.ts b/src/app/core/models/service-provider/cargo-rate.ts new file mode 100644 index 0000000..712137f --- /dev/null +++ b/src/app/core/models/service-provider/cargo-rate.ts @@ -0,0 +1,12 @@ +export interface CargoRate { + id: number, + spid: number, + startSets: number, + endSets: number, + carnetType: string, + effectiveDate: Date, + rate: number, + dateCreated?: Date | null; + createdBy?: string | null; + expired?: boolean; +} diff --git a/src/app/core/models/service-provider/security-deposit.ts b/src/app/core/models/service-provider/security-deposit.ts index 755874e..1ad42b6 100644 --- a/src/app/core/models/service-provider/security-deposit.ts +++ b/src/app/core/models/service-provider/security-deposit.ts @@ -5,6 +5,7 @@ export interface SecurityDeposit { specialCommodity: string; specialCountry: string; rate: number; + rateInPercentage: number; effectiveDate: Date; spid: number; dateCreated?: Date | null; diff --git a/src/app/core/services/carnet/insurance.service.ts b/src/app/core/services/carnet/insurance.service.ts deleted file mode 100644 index 1162fe9..0000000 --- a/src/app/core/services/carnet/insurance.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable({ - providedIn: 'root' -}) -export class InsuranceService { - - constructor() { } -} diff --git a/src/app/core/services/carnet/payment.service.ts b/src/app/core/services/carnet/payment.service.ts index 0635983..6dad20b 100644 --- a/src/app/core/services/carnet/payment.service.ts +++ b/src/app/core/services/carnet/payment.service.ts @@ -2,6 +2,8 @@ import { inject, Injectable } from '@angular/core'; import { environment } from '../../../../environments/environment'; import { HttpClient } from '@angular/common/http'; import { firstValueFrom } from 'rxjs'; +import { PaymentDetail } from '../../models/carnet/payment-details'; +import { UserService } from '../common/user.service'; @Injectable({ providedIn: 'root' @@ -11,11 +13,14 @@ export class PaymentService { private apiDb = environment.apiDb; private http = inject(HttpClient); + private userService = inject(UserService); - createOrder(orderDetails: any): Promise { + createOrder(paymentDetails: PaymentDetail): Promise { const data = { - P_PRICE: orderDetails.price, - P_DESCRIPTION: orderDetails.description + P_APPLICATIONNAME: paymentDetails.applicationName, + P_PRICE: paymentDetails.price, + P_DESCRIPTION: paymentDetails.description, + P_USERID: this.userService.getUser() } return firstValueFrom( @@ -23,12 +28,29 @@ export class PaymentService { ).then(order => order.id); } - completePayment(orderId: string): Promise { + completePayment(paymentDetails: PaymentDetail): Promise { const data = { - P_ORDERID: orderId + P_ORDERID: paymentDetails.orderId, + P_APPLICATIONNAME: paymentDetails.applicationName, + P_PRICE: paymentDetails.price, + P_USERID: this.userService.getUser() } return firstValueFrom( - this.http.post(`${this.apiUrl}/oracle/CompletePayment`, data)); + this.http.post(`${this.apiUrl}/${this.apiDb}/CompletePayment`, data)); + } + + logTransactionDetails(paymentDetails: PaymentDetail): Promise { + const data = { + P_APPLICATIONNAME: paymentDetails.applicationName, + P_ORDERID: paymentDetails.orderId, + P_PAYMENTAMOUNT: paymentDetails.price, + P_STATUS: paymentDetails.paymentStatus, + P_USERID: this.userService.getUser(), + P_PAYMENTERROR: paymentDetails.paymentErrorDetails + } + + return firstValueFrom( + this.http.post(`${this.apiUrl}/${this.apiDb}/SaveHistory`, data)); } } \ No newline at end of file diff --git a/src/app/core/services/common/common.service.ts b/src/app/core/services/common/common.service.ts index 330b05a..fc03d7c 100644 --- a/src/app/core/services/common/common.service.ts +++ b/src/app/core/services/common/common.service.ts @@ -19,6 +19,7 @@ import { PaymentType } from '../../models/payment-type'; import { UnitOfMeasure } from '../../models/unitofmeasure'; import { ExtensionReason } from '../../models/extension-reason'; import { IndustryType } from '../../models/industry-type'; +import { HolderType } from '../../models/holder-type'; @Injectable({ providedIn: 'root' @@ -231,6 +232,18 @@ export class CommonService { ); } + getHolderTypes(): Observable { + return this.http.get(`${this.apiUrl}/${this.apiDb}/GetParamValues?P_PARAMTYPE=017&P_SPID=${this.userService.getUserSpid()}`).pipe( + map((response) => + response.map((item) => ({ + name: item.PARAMDESC, + id: item.PARAMID, + value: item.PARAMVALUE, + })) + ) + ); + } + formatUSDate(datetime: Date): string { const date = new Date(datetime); const month = String(date.getUTCMonth() + 1).padStart(2, '0'); diff --git a/src/app/core/services/preparer/basic-detail.service.ts b/src/app/core/services/preparer/basic-detail.service.ts index 2dd8381..51cc9d7 100644 --- a/src/app/core/services/preparer/basic-detail.service.ts +++ b/src/app/core/services/preparer/basic-detail.service.ts @@ -34,7 +34,7 @@ export class BasicDetailService { carnetIssuingRegion: basicDetails.ISSUINGREGION, revenueLocation: basicDetails.REVENUELOCATION, zip: basicDetails.ZIP, - industryType: ""// basicDetails.INDUSTRYTYPE + industryType: basicDetails.INDUSTRYTYPE }; } diff --git a/src/app/core/services/service-provider/cargo-rate.service.ts b/src/app/core/services/service-provider/cargo-rate.service.ts new file mode 100644 index 0000000..cb9b5db --- /dev/null +++ b/src/app/core/services/service-provider/cargo-rate.service.ts @@ -0,0 +1,70 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { map, Observable } from 'rxjs'; +import { environment } from '../../../../environments/environment'; +import { CommonService } from '../common/common.service'; +import { UserService } from '../common/user.service'; +import { CargoRate } from '../../models/service-provider/cargo-rate'; + +@Injectable({ + providedIn: 'root' +}) +export class CargoRateService { + private apiUrl = environment.apiUrl; + private apiDb = environment.apiDb; + + private http = inject(HttpClient); + private userService = inject(UserService); + private commonService = inject(CommonService); + + getCargoRates(spid: number): Observable { + return this.http.get(`${this.apiUrl}/${this.apiDb}/GetCargoRates/${spid}/ACTIVE`).pipe( + map(response => this.mapToCargoRates(response))); + } + + private mapToCargoRates(data: any[]): CargoRate[] { + return data.map(item => ({ + id: item.CARGORATESETUPID, + spid: item.SPID, + startSets: item.STARTSETS, + endSets: item.ENDSETS, + carnetType: item.CARNETTYPE, + effectiveDate: item.EFFDATE, + rate: item.RATE, + createdBy: item.CREATEDBY || null, + dateCreated: item.DATECREATED || null, + expired: item.EXPDATE ? new Date(item.EXPDATE) < new Date() : false + })); + } + + addCargoRate(spid: number, data: CargoRate): Observable { + + const cargoRate = { + P_SPID: spid, + P_STARTSETS: data.startSets, + P_ENDSETS: data.endSets, + P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate), + P_CARNETTYPE: data.carnetType, + P_RATE: data.rate, + P_USERID: this.userService.getUser() + } + + return this.http.post(`${this.apiUrl}/${this.apiDb}/CreateCargoRate`, cargoRate); + } + + updateCargoRate(id: number, data: CargoRate): Observable { + + const cargoRate = { + P_CARGORATESETUPID: id, + P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate), + P_RATE: data.rate, + P_USERID: this.userService.getUser() + } + + return this.http.patch(`${this.apiUrl}/${this.apiDb}//UpdateCargoRate`, cargoRate); + } + + // deleteCargo(id: string): Observable { + // return this.http.delete(`${this.apiUrl}/${this.apiDb}/InactivateSPContact/${id}`); + // } +} diff --git a/src/app/core/services/service-provider/security-deposit.service.ts b/src/app/core/services/service-provider/security-deposit.service.ts index 4f36307..15390ba 100644 --- a/src/app/core/services/service-provider/security-deposit.service.ts +++ b/src/app/core/services/service-provider/security-deposit.service.ts @@ -30,6 +30,7 @@ export class SecurityDepositService { specialCommodity: item.SPCLCOMMODITY, specialCountry: item.SPCLCOUNTRY, rate: item.RATE, + rateInPercentage: item.RATE * 100, effectiveDate: item.EFFDATE, spid: item.SPID, createdBy: item.CREATEDBY || null, @@ -48,6 +49,7 @@ export class SecurityDepositService { P_SPCLCOMMODITY: data.specialCommodity, P_SPCLCOUNTRY: data.specialCountry, P_RATE: data.rate, + P_PCT_VALUE: data.rateInPercentage.toString(), P_USERID: this.userService.getUser() } @@ -60,6 +62,7 @@ export class SecurityDepositService { P_BONDRATESETUPID: id, P_EFFDATE: this.commonService.formatUSDate(data.effectiveDate), P_RATE: data.rate, + P_PCT_VALUE: data.rateInPercentage.toString(), P_USERID: this.userService.getUser() } diff --git a/src/app/param/table/param-table.component.html b/src/app/param/table/param-table.component.html index 7f55792..573fb45 100644 --- a/src/app/param/table/param-table.component.html +++ b/src/app/param/table/param-table.component.html @@ -86,18 +86,18 @@ - diff --git a/src/app/service-provider/add/add-service-provider.component.html b/src/app/service-provider/add/add-service-provider.component.html index 12bef29..6e5d229 100644 --- a/src/app/service-provider/add/add-service-provider.component.html +++ b/src/app/service-provider/add/add-service-provider.component.html @@ -83,5 +83,14 @@ [userPreferences]="userPreferences"> + + + Cargo Rate Setup + + + + + \ No newline at end of file diff --git a/src/app/service-provider/add/add-service-provider.component.ts b/src/app/service-provider/add/add-service-provider.component.ts index a6dc710..e366662 100644 --- a/src/app/service-provider/add/add-service-provider.component.ts +++ b/src/app/service-provider/add/add-service-provider.component.ts @@ -13,11 +13,12 @@ import { SecurityDepositComponent } from '../security-deposit/security-deposit.c import { ContinuationSheetFeeComponent } from "../continuation-sheet-fee/continuation-sheet-fee.component"; import { UserPreferences } from '../../core/models/user-preference'; import { UserPreferencesService } from '../../core/services/user-preference.service'; +import { CargoRateComponent } from '../cargo-rate/cargo-rate.component'; @Component({ selector: 'app-add-service-provider', imports: [BasicDetailsComponent, AngularMaterialModule, CommonModule, ContactsComponent, CarnetSequenceComponent, - CarnetFeeComponent, BasicFeeComponent, CounterfoilFeeComponent, ExpeditedFeeComponent, SecurityDepositComponent, ContinuationSheetFeeComponent], + CarnetFeeComponent, BasicFeeComponent, CounterfoilFeeComponent, ExpeditedFeeComponent, SecurityDepositComponent, ContinuationSheetFeeComponent, CargoRateComponent], templateUrl: './add-service-provider.component.html', styleUrl: './add-service-provider.component.scss' }) @@ -37,6 +38,7 @@ export class AddServiceProviderComponent { continuationSheetFeeCompleted: boolean = false; expeditedFeeCompleted: boolean = false; securityDepositCompleted: boolean = false; + cargoRateCompleted: boolean = false; constructor(userPrefenceService: UserPreferencesService) { this.userPreferences = userPrefenceService.getPreferences(); @@ -79,6 +81,10 @@ export class AddServiceProviderComponent { this.securityDepositCompleted = event; } + onCargoRateSaved(event: boolean): void { + this.cargoRateCompleted = event; + } + onStepChange(event: StepperSelectionEvent): void { this.currentStep = event.selectedIndex; } diff --git a/src/app/service-provider/cargo-rate/cargo-rate.component.html b/src/app/service-provider/cargo-rate/cargo-rate.component.html new file mode 100644 index 0000000..13f6978 --- /dev/null +++ b/src/app/service-provider/cargo-rate/cargo-rate.component.html @@ -0,0 +1,192 @@ +
+
+ + Show Expired Records + + +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Carnet Type + {{ getOptionLabel(carnetTypes, item.carnetType) }} + Start Sets{{ item.startSets }}End Sets{{ item.endSets }}Rate{{ item.rate | currency }}Effective Date + {{ item.effectiveDate | date:'mediumDate':'UTC' }} + Actions + + +
+ info + No cargo rate setups available +
+ + +
+ + +
+
+
+

{{ isEditing ? 'Edit Cargo Rate Setup' : 'Add New Cargo Rate Setup' }}

+
+ +
+ Carnet Type + + + {{ type.label }} + + + + Carnet type is required + +
+ +
+ + Start Sets + + + Start sets is required + + + Must be a valid number + + + Must be greater than 0 + + + + + End Sets + + + End sets is required + + + Must be a valid number + + + Must be greater than 0 + + +
+ + + End sets must be greater than start sets + + +
+ + Rate + + + + Rate is required + + + Must be a valid dollar amount + + + Must be 0 or greater + + + + + Effective Date + + + + + Effective date is required + + +
+ +
+
+
+ +
+ +
+ {{readOnlyFields.lastChangedBy || 'N/A'}} +
+
+
+
+ +
+ +
+ {{(readOnlyFields.lastChangedDate | date:'mediumDate':'UTC') || 'N/A'}} +
+
+
+
+
+ +
+ + +
+
+
+
\ No newline at end of file diff --git a/src/app/service-provider/cargo-rate/cargo-rate.component.scss b/src/app/service-provider/cargo-rate/cargo-rate.component.scss new file mode 100644 index 0000000..9ef5645 --- /dev/null +++ b/src/app/service-provider/cargo-rate/cargo-rate.component.scss @@ -0,0 +1,191 @@ +.cargorate-container { + padding: 0.5rem; + display: flex; + flex-direction: column; + gap: 24px; + + .actions-bar { + clear: both; + margin-bottom: -16px; + + mat-slide-toggle { + transform: scale(0.8); + margin-left: -0.5rem; + } + + button { + float: right; + } + } + + .table-container { + position: relative; + overflow: auto; + border-radius: 8px; + + .loading-shade { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.7); + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + } + + mat-table { + width: 100%; + + mat-icon { + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + transform: scale(1.1); + } + } + + .mat-column-actions { + width: 120px; + text-align: center; + } + } + + .no-data-message { + text-align: center; + padding: 0.9rem; + color: rgba(0, 0, 0, 0.54); + + mat-icon { + font-size: 1rem; + width: 1rem; + height: 1rem; + margin-bottom: -3px; + } + } + + mat-paginator { + border-top: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 0 0 8px 8px; + padding-top: 4px; + } + } + + .form-container { + background-color: white; + padding: 24px; + border-radius: 8px; + margin-top: 16px; + + .form-header { + // margin-bottom: 24px; + + h3 { + margin: 0; + color: var(--mat-sys-primary); + font-weight: 500; + } + } + + form { + display: flex; + flex-direction: column; + gap: 16px; + + .form-row { + display: flex; + gap: 16px; + align-items: start; + + mat-form-field { + flex: 1; + } + + mat-label { + font-size: 0.875rem; + } + } + + .form-error { + margin-top: -16px; + margin-bottom: 8px; + display: block; + color: var(--mat-form-field-error-text-color, var(--mat-sys-error)); + font-size: 12px; + } + + .form-actions { + display: flex; + justify-content: flex-end; + gap: 16px; + margin-top: 16px; + } + + .horizontal-group { + display: flex; + gap: 16px; + flex-wrap: wrap; + } + + ::ng-deep .mat-radio-button.mat-accent .mat-radio-outer-circle { + border-color: #3f51b5; + } + + ::ng-deep .mat-radio-button.mat-accent .mat-radio-inner-circle { + background-color: #3f51b5; + } + + .dollar-prefix { + padding-left: 4px; + } + + .readonly-section { + padding-top: 0.5rem; + border-top: 1px solid #eee; + + .readonly-fields { + display: flex; + gap: 2rem; + + .field-column { + flex: 1; + display: flex; + flex-direction: column; + gap: 1.5rem; + } + } + + .readonly-field { + label { + display: block; + font-size: 0.875rem; + color: #666; + margin-bottom: 0.25rem; + } + + .readonly-value { + padding: 0.25rem; + font-size: 0.875rem; + display: flex; + align-items: center; + } + } + } + } + } +} + +// Responsive adjustments +@media (max-width: 768px) { + .cargorate-container { + padding: 16px; + + .form-row { + flex-direction: column; + gap: 16px !important; + } + } +} \ No newline at end of file diff --git a/src/app/service-provider/cargo-rate/cargo-rate.component.ts b/src/app/service-provider/cargo-rate/cargo-rate.component.ts new file mode 100644 index 0000000..80df456 --- /dev/null +++ b/src/app/service-provider/cargo-rate/cargo-rate.component.ts @@ -0,0 +1,244 @@ +import { Component, EventEmitter, inject, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator'; +import { MatSort } from '@angular/material/sort'; +import { MatTableDataSource } from '@angular/material/table'; +import { AngularMaterialModule } from '../../shared/module/angular-material.module'; +import { CommonModule } from '@angular/common'; +import { CustomPaginator } from '../../shared/custom-paginator'; +import { UserPreferences } from '../../core/models/user-preference'; +import { ApiErrorHandlerService } from '../../core/services/common/api-error-handler.service'; +import { NotificationService } from '../../core/services/common/notification.service'; +import { finalize } from 'rxjs'; +import { CargoRate } from '../../core/models/service-provider/cargo-rate'; +import { CargoRateService } from '../../core/services/service-provider/cargo-rate.service'; + +@Component({ + selector: 'app-cargo-rate', + imports: [AngularMaterialModule, CommonModule, ReactiveFormsModule], + templateUrl: './cargo-rate.component.html', + styleUrl: './cargo-rate.component.scss', + providers: [{ provide: MatPaginatorIntl, useClass: CustomPaginator }], +}) +export class CargoRateComponent implements OnInit { + @ViewChild(MatPaginator) paginator!: MatPaginator; + @ViewChild(MatSort) sort!: MatSort; + + displayedColumns: string[] = ['carnetType', 'startSets', 'endSets', 'rate', 'effectiveDate', 'actions']; + dataSource = new MatTableDataSource(); + cargoRateForm: FormGroup; + isEditing = false; + currentCargoRateId: number | null = null; + isLoading = false; + changeInProgress = false; + showForm = false; + showExpiredRecords = false; + cargoRates: CargoRate[] = []; + + readOnlyFields: any = { + lastChangedDate: null, + lastChangedBy: null + }; + + carnetTypes = [ + { label: 'Original', value: 'ORIGINAL' }, + { label: 'Re-order', value: 'REORDER' }, + { label: 'Replacement', value: 'REPLACE' } + ]; + + @Input() isEditMode = false; + @Input() spid: number = 0; + @Input() userPreferences!: UserPreferences; + @Output() hasCargoRates = new EventEmitter(); + + private fb = inject(FormBuilder); + private cargoRateService = inject(CargoRateService); + private notificationService = inject(NotificationService); + private errorHandler = inject(ApiErrorHandlerService); + + constructor() { + this.cargoRateForm = this.fb.group({ + carnetType: ['ORIGINAL', Validators.required], + startSets: ['', [ + Validators.required, + Validators.pattern('^[0-9]*$'), + Validators.min(1) + ]], + endSets: ['', [ + Validators.required, + Validators.pattern('^[0-9]*$'), + Validators.min(1) + ]], + rate: ['', [ + Validators.required, + Validators.pattern(/^\d+\.?\d{0,2}$/), + Validators.min(0) + ]], + effectiveDate: ['', Validators.required] + }, { validator: this.validateSetsRange }); + } + + ngOnInit(): void { + this.loadCargoRates(); + } + + ngAfterViewInit() { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + private validateSetsRange(group: FormGroup): { [key: string]: any } | null { + const start = +group.get('startSets')?.value; + const end = +group.get('endSets')?.value; + return start && end && start >= end ? { invalidRange: true } : null; + } + + loadCargoRates(): void { + if (!this.spid) return; + + this.isLoading = true; + this.cargoRateService.getCargoRates(this.spid).pipe(finalize(() => { + this.isLoading = false; + })) + .subscribe({ + next: ( + cargoRates: CargoRate[]) => { + this.cargoRates = cargoRates; + this.dataSource.data = this.cargoRates; + this.renderRecods(); + }, + error: (error: any) => { + let errorMessage = this.errorHandler.handleApiError(error, 'Failed to load cargo rates'); + this.notificationService.showError(errorMessage); + console.error('Error loading cargo rates:', error); + } + }); + } + + toggleShowExpiredRecords(): void { + this.showExpiredRecords = !this.showExpiredRecords; + this.renderRecods(); + } + + renderRecods(): void { + if (this.showExpiredRecords) { + this.dataSource.data = this.cargoRates.filter(record => record.expired); + } else { + this.dataSource.data = this.cargoRates.filter(record => !record.expired); + } + } + + // applyFilter(event: Event): void { + // const filterValue = (event.target as HTMLInputElement).value; + // this.dataSource.filter = filterValue.trim().toLowerCase(); + + // if (this.dataSource.paginator) { + // this.dataSource.paginator.firstPage(); + // } + // } + + addNewCargoRate(): void { + this.showForm = true; + this.isEditing = false; + this.currentCargoRateId = null; + this.cargoRateForm.reset({ + carnetType: 'ORIGINAL' + }); + + this.cargoRateForm.get('carnetType')?.enable(); + this.cargoRateForm.get('startSets')?.enable(); + this.cargoRateForm.get('endSets')?.enable(); + } + + editCargoRate(cargoRate: any): void { + this.showForm = true; + this.isEditing = true; + this.currentCargoRateId = cargoRate.id; + this.cargoRateForm.patchValue({ + carnetType: cargoRate.carnetType, + startSets: cargoRate.startSets, + endSets: cargoRate.endSets, + rate: cargoRate.rate, + effectiveDate: new Date(cargoRate.effectiveDate) + }); + + this.readOnlyFields.lastChangedDate = cargoRate.dateCreated; + this.readOnlyFields.lastChangedBy = cargoRate.createdBy; + + this.cargoRateForm.get('carnetType')?.disable(); + this.cargoRateForm.get('startSets')?.disable(); + this.cargoRateForm.get('endSets')?.disable(); + } + + saveCargoRate(): void { + if (this.cargoRateForm.invalid) { + this.cargoRateForm.markAllAsTouched(); + return; + } + + const cargoRateData = { + ...this.cargoRateForm.value, + spid: this.spid + }; + + const saveObservable = this.isEditing && this.currentCargoRateId + ? this.cargoRateService.updateCargoRate(this.currentCargoRateId, cargoRateData) + : this.cargoRateService.addCargoRate(this.spid, cargoRateData); + + this.changeInProgress = true; + saveObservable.pipe(finalize(() => { + this.changeInProgress = false; + })).subscribe({ + next: () => { + this.notificationService.showSuccess(`Cargo Rate ${this.isEditing ? 'updated' : 'added'} successfully`); + this.loadCargoRates(); + this.cancelEdit(); + this.hasCargoRates.emit(true); + }, + error: (error) => { + let errorMessage = this.errorHandler.handleApiError(error, `Failed to ${this.isEditing ? 'update' : 'add'} cargo rate`); + this.notificationService.showError(errorMessage); + console.error('Error saving cargo rate:', error); + } + }); + } + + // deleteCargoRate(cargoRateId: string): void { + // const dialogRef = this.dialog.open(ConfirmDialogComponent, { + // width: '350px', + // data: { + // title: 'Confirm Delete', + // message: 'Are you sure you want to delete this cargoRate setup?', + // confirmText: 'Delete', + // cancelText: 'Cancel' + // } + // }); + + // dialogRef.afterClosed().subscribe(result => { + // if (result) { + // this.cargoRateService.deleteCargoRate(cargoRateId).subscribe({ + // next: () => { + // this.notificationService.showSuccess('CargoRate deleted successfully'); + // this.loadCargoRates(); + // }, + // error: (error) => { + // this.notificationService.showError('Failed to delete cargoRate'); + // } + // }); + // } + // }); + // } + + cancelEdit(): void { + this.showForm = false; + this.isEditing = false; + this.currentCargoRateId = null; + this.cargoRateForm.reset({ + carnetType: 'ORIGINAL' + }); + } + + getOptionLabel(options: any[], value: string): string { + return options.find(opt => opt.value === value)?.label || value; + } +} \ No newline at end of file diff --git a/src/app/service-provider/edit/edit-service-provider.component.html b/src/app/service-provider/edit/edit-service-provider.component.html index 5a5a958..1b444c4 100644 --- a/src/app/service-provider/edit/edit-service-provider.component.html +++ b/src/app/service-provider/edit/edit-service-provider.component.html @@ -73,4 +73,12 @@ + + + + Cargo Rate Setup + + + + \ No newline at end of file diff --git a/src/app/service-provider/edit/edit-service-provider.component.ts b/src/app/service-provider/edit/edit-service-provider.component.ts index ea44f38..6f8c720 100644 --- a/src/app/service-provider/edit/edit-service-provider.component.ts +++ b/src/app/service-provider/edit/edit-service-provider.component.ts @@ -14,10 +14,11 @@ import { SecurityDepositComponent } from "../security-deposit/security-deposit.c import { UserPreferences } from '../../core/models/user-preference'; import { CommonModule } from '@angular/common'; import { UserPreferencesService } from '../../core/services/user-preference.service'; +import { CargoRateComponent } from '../cargo-rate/cargo-rate.component'; @Component({ selector: 'app-edit-service-provider', - imports: [AngularMaterialModule, BasicDetailsComponent, ContactsComponent, CarnetSequenceComponent, CarnetFeeComponent, BasicFeeComponent, CounterfoilFeeComponent, ContinuationSheetFeeComponent, ExpeditedFeeComponent, SecurityDepositComponent, CommonModule], + imports: [AngularMaterialModule, BasicDetailsComponent, ContactsComponent, CarnetSequenceComponent, CarnetFeeComponent, BasicFeeComponent, CounterfoilFeeComponent, ContinuationSheetFeeComponent, ExpeditedFeeComponent, SecurityDepositComponent, CommonModule, CargoRateComponent], templateUrl: './edit-service-provider.component.html', styleUrl: './edit-service-provider.component.scss' }) diff --git a/src/app/service-provider/security-deposit/security-deposit.component.html b/src/app/service-provider/security-deposit/security-deposit.component.html index f086d3a..aacb563 100644 --- a/src/app/service-provider/security-deposit/security-deposit.component.html +++ b/src/app/service-provider/security-deposit/security-deposit.component.html @@ -48,6 +48,12 @@ {{ deposit.rate | currency }} + + + Rate In Percentage + {{ deposit.rateInPercentage | percent }} + + Effective Date @@ -97,7 +103,7 @@ Holder Type - {{ type.label }} + {{ type.name }} @@ -147,6 +153,18 @@ + + Rate % + + + + Rate % is required + + + Rate % must be positive + + + Effective Date diff --git a/src/app/service-provider/security-deposit/security-deposit.component.scss b/src/app/service-provider/security-deposit/security-deposit.component.scss index b9fa0d1..6847309 100644 --- a/src/app/service-provider/security-deposit/security-deposit.component.scss +++ b/src/app/service-provider/security-deposit/security-deposit.component.scss @@ -12,7 +12,7 @@ transform: scale(0.8); margin-left: -0.5rem; } - + button { float: right; } diff --git a/src/app/service-provider/security-deposit/security-deposit.component.ts b/src/app/service-provider/security-deposit/security-deposit.component.ts index d6cf92b..f6c5541 100644 --- a/src/app/service-provider/security-deposit/security-deposit.component.ts +++ b/src/app/service-provider/security-deposit/security-deposit.component.ts @@ -14,6 +14,7 @@ import { CommonService } from '../../core/services/common/common.service'; import { NotificationService } from '../../core/services/common/notification.service'; import { SecurityDepositService } from '../../core/services/service-provider/security-deposit.service'; import { finalize } from 'rxjs'; +import { HolderType } from '../../core/models/holder-type'; @Component({ selector: 'app-security-deposit', @@ -26,7 +27,7 @@ export class SecurityDepositComponent implements OnInit { @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; - displayedColumns: string[] = ['holderType', 'uscibMember', 'specialCommodity', 'specialCountry', 'rate', 'effectiveDate', 'actions']; + displayedColumns: string[] = ['holderType', 'uscibMember', 'specialCommodity', 'specialCountry', 'rate', 'rateInPercentage', 'effectiveDate', 'actions']; dataSource = new MatTableDataSource(); depositForm: FormGroup; isEditing = false; @@ -43,18 +44,13 @@ export class SecurityDepositComponent implements OnInit { }; countries: Country[] = []; + holderTypes: HolderType[] = []; @Input() isEditMode = false; @Input() spid: number = 0; @Input() userPreferences!: UserPreferences; @Output() hasSecurityDeposits = new EventEmitter(); - holderTypes = [ - { value: 'CORP', label: 'Corporation' }, - { value: 'INDIVIDUAL', label: 'Individual' }, - { value: 'GOVERNMENT', label: 'Government Agency' } - ]; - yesNoOptions = [ { value: 'Y', label: 'Yes' }, { value: 'N', label: 'No' } @@ -73,6 +69,7 @@ export class SecurityDepositComponent implements OnInit { specialCommodity: [''], specialCountry: [''], rate: [0, [Validators.required, Validators.min(0)]], + rateInPercentage: [0, [Validators.required, Validators.min(0)]], effectiveDate: ['', Validators.required] }); } @@ -80,6 +77,7 @@ export class SecurityDepositComponent implements OnInit { ngOnInit(): void { this.loadSecurityDeposits(); this.loadCountries(); + this.loadHolderTypes(); } ngAfterViewInit() { @@ -129,6 +127,16 @@ export class SecurityDepositComponent implements OnInit { }); } + loadHolderTypes(): void { + this.commonService.getHolderTypes().subscribe({ + next: (holderTypes) => { + this.holderTypes = holderTypes; + }, + error: (error) => { + console.error('Error loading holder types:', error); + } + }); + } // applyFilter(event: Event): void { // const filterValue = (event.target as HTMLInputElement).value; // this.dataSource.filter = filterValue.trim().toLowerCase(); @@ -147,7 +155,7 @@ export class SecurityDepositComponent implements OnInit { uscibMember: 'N', specialCountry: '', }); - this.depositForm.patchValue({ rate: 0 }); + this.depositForm.patchValue({ rate: 0, rateInPercentage: 0 }); this.depositForm.get('holderType')?.enable(); this.depositForm.get('uscibMember')?.enable(); @@ -165,6 +173,7 @@ export class SecurityDepositComponent implements OnInit { specialCommodity: deposit.specialCommodity, specialCountry: deposit.specialCountry, rate: deposit.rate, + rateInPercentage: deposit.rateInPercentage, effectiveDate: deposit.effectiveDate }); @@ -251,7 +260,7 @@ export class SecurityDepositComponent implements OnInit { getHolderTypeLabel(value: string): string { const type = this.holderTypes.find(t => t.value === value); - return type ? type.label : value; + return type ? type.name : value; } getCountryName(code: string): string {