diff --git a/angular.json b/angular.json index b37f8af..81ea9ec 100644 --- a/angular.json +++ b/angular.json @@ -34,6 +34,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss" ], "server": "src/main.server.ts", @@ -110,6 +111,7 @@ } ], "styles": [ + "@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss" ] } diff --git a/package-lock.json b/package-lock.json index bdd4ef5..c6bc257 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@angular/router": "^20.0.0", "@angular/ssr": "^20.0.4", "chart.js": "^4.3.0", + "date-fns": "^4.1.0", "express": "^5.1.0", "ng2-charts": "^8.0.0", "ngx-cookie-service": "^20.0.1", @@ -4464,6 +4465,16 @@ "dev": true, "license": "MIT" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", diff --git a/package.json b/package.json index e2a482a..1b9bc13 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@angular/router": "^20.0.0", "@angular/ssr": "^20.0.4", "chart.js": "^4.3.0", + "date-fns": "^4.1.0", "express": "^5.1.0", "ng2-charts": "^8.0.0", "ngx-cookie-service": "^20.0.1", diff --git a/src/app/carnet/edit/edit-carnet.component.html b/src/app/carnet/edit/edit-carnet.component.html index 49d6cfa..f64a2d1 100644 --- a/src/app/carnet/edit/edit-carnet.component.html +++ b/src/app/carnet/edit/edit-carnet.component.html @@ -39,7 +39,7 @@ Shipping & Payment - + diff --git a/src/app/carnet/goods/goods.component.html b/src/app/carnet/goods/goods.component.html index 9457a02..2b66aeb 100644 --- a/src/app/carnet/goods/goods.component.html +++ b/src/app/carnet/goods/goods.component.html @@ -17,9 +17,9 @@
- Road Vehicles + Road Vehicles used? - Horse used? + Horse used?
diff --git a/src/app/carnet/shipping/shipping.component.html b/src/app/carnet/shipping/shipping.component.html index 6c8ec95..79a3731 100644 --- a/src/app/carnet/shipping/shipping.component.html +++ b/src/app/carnet/shipping/shipping.component.html @@ -4,11 +4,11 @@

Insurance & Bond

- Do you need Bond from + Do you need Bond from us? - Do you need insurance for your + Do you need insurance for your goods? - Do you need Lost document + Do you need Lost document protection?
@@ -24,11 +24,18 @@ -

{{getAddressLabel()}}

+
+
+

{{getAddressLabel()}}

+

{{getContactLabel()}}

+
+
+ +
+
- - -

Shipping Address

@@ -229,12 +236,16 @@
+ +
+ +
-

Delivery Method

+

Delivery

Delivery Type @@ -246,6 +257,9 @@ Delivery Type is required + + {{ deliveryEstimate }} + Delivery Method @@ -259,11 +273,14 @@
-
+ +
Courier Account Number - Required when using customer courier + + Required when using customer courier +
diff --git a/src/app/carnet/shipping/shipping.component.scss b/src/app/carnet/shipping/shipping.component.scss index e1973a7..b8ffe35 100644 --- a/src/app/carnet/shipping/shipping.component.scss +++ b/src/app/carnet/shipping/shipping.component.scss @@ -20,13 +20,13 @@ } .mat-mdc-card-content:first-child { - padding: 0 16px; + padding: 0 16px; } } .checkbox-group { display: flex; - flex-direction: column; + flex-direction: row; gap: 8px; } @@ -60,6 +60,22 @@ gap: 16px; margin-top: 16px; } + + .presaved-address { + padding: 10px; + display: flex; + flex-direction: row; + gap: 8px; + + p { + margin: 0; + margin-bottom: 4px; + } + + .presaved-address-actions { + margin-top: 8px; + } + } } @media (max-width: 768px) { diff --git a/src/app/carnet/shipping/shipping.component.ts b/src/app/carnet/shipping/shipping.component.ts index 4cb0ade..0ec348d 100644 --- a/src/app/carnet/shipping/shipping.component.ts +++ b/src/app/carnet/shipping/shipping.component.ts @@ -14,6 +14,7 @@ import { Subject, takeUntil } from 'rxjs'; import { DeliveryType } from '../../core/models/delivery-type'; import { DeliveryMethod } from '../../core/models/delivery-method'; import { PaymentType } from '../../core/models/payment-type'; +import { format, addDays, isAfter, isWeekend } from 'date-fns'; @Component({ selector: 'app-shipping', @@ -36,6 +37,7 @@ export class ShippingComponent { isLoading = false; showAddressForm = false; showContactForm = false; + deliveryEstimate: string = ''; countriesHasStates = ['US', 'CA', 'MX']; countries: Country[] = []; @@ -59,6 +61,7 @@ export class ShippingComponent { } preparerAddress = { + companyName: 'ABC Company', address1: '123 Main St', address2: 'Suite 100', city: 'Anytown', @@ -68,6 +71,7 @@ export class ShippingComponent { }; holderAddress = { + companyName: 'XYZ Company', address1: '456 Holder St', address2: 'Apt 200', city: 'Othertown', @@ -94,22 +98,22 @@ export class ShippingComponent { needsLostDocProtection: [false], shipTo: ['preparer', Validators.required], address: this.fb.group({ - address1: ['', [Validators.required, Validators.maxLength(100)]], - address2: ['', [Validators.maxLength(100)]], - city: ['', [Validators.required, Validators.maxLength(50)]], - state: ['', Validators.required], - country: ['', Validators.required], - zip: ['', [Validators.required, ZipCodeValidator('country')]], + address1: [''], + address2: [''], + city: [''], + state: [''], + country: [''], + zip: [''], }), contact: this.fb.group({ - firstName: ['', [Validators.required, Validators.maxLength(50)]], - lastName: ['', [Validators.required, Validators.maxLength(50)]], - middleInitial: ['', [Validators.maxLength(1)]], - title: ['', [Validators.required, Validators.maxLength(100)]], - phone: ['', [Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]], - mobile: ['', [Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]], - fax: ['', [Validators.pattern(/^[0-9]{10,15}$/)]], - email: ['', [Validators.required, Validators.email, Validators.maxLength(100)]], + firstName: [''], + lastName: [''], + middleInitial: [''], + title: [''], + phone: [''], + mobile: [''], + fax: [''], + email: [''], refNumber: [''], notes: [''] }), @@ -117,17 +121,17 @@ export class ShippingComponent { deliveryMethod: ['', Validators.required], courierAccount: [''], paymentMethod: ['', Validators.required], - notes: [''] + paymentNotes: [''] }); // Update form validation based on selections - // this.shippingForm.get('shipTo')?.valueChanges.subscribe(value => { - // this.updateShippingValidation(value); - // }); + this.shippingForm.get('shipTo')?.valueChanges.subscribe(value => { + this.updateShippingValidation(value); + }); this.shippingForm.get('deliveryMethod')?.valueChanges.subscribe(value => { const courierControl = this.shippingForm.get('courierAccount'); - if (value === 'customerCourier') { + if (value === 'CLC') { courierControl?.setValidators([Validators.required]); } else { courierControl?.clearValidators(); @@ -135,9 +139,13 @@ export class ShippingComponent { courierControl?.updateValueAndValidity(); }); - this.shippingForm.valueChanges.subscribe(() => { - this.completed.emit(this.shippingForm.valid); + this.shippingForm.get('deliveryType')?.valueChanges.subscribe(() => { + this.calculateDeliveryEstimate(); }); + + // this.shippingForm.valueChanges.subscribe(() => { + // this.completed.emit(this.shippingForm.valid); + // }); } ngOnInit(): void { @@ -267,41 +275,99 @@ export class ShippingComponent { needsInsurance: shipping.needsInsurance, needsLostDocProtection: shipping.needsLostDocProtection, shipTo: shipping.shipTo, - address: shipping.address, - contact: shipping.contact, deliveryType: shipping.deliveryType, deliveryMethod: shipping.deliveryMethod, courierAccount: shipping.courierAccount, - paymentMethod: shipping.paymentMethod, - notes: shipping.notes + paymentMethod: shipping.paymentMethod }); if (shipping.address?.country) { this.loadStates(shipping.address?.country); } + + if (shipping.shipTo === 'thirdParty') { + this.updateShippingValidation(shipping.shipTo); + this.showAddressForm = true; + this.showContactForm = true; + + const addressGroup = this.shippingForm.get('address') as FormGroup; + const contactGroup = this.shippingForm.get('contact') as FormGroup; + + addressGroup.patchValue({ + addressid: shipping.address?.addressid, + address1: shipping.address?.address1, + address2: shipping.address?.address2, + city: shipping.address?.city, + state: shipping.address?.state, + zip: shipping.address?.zip, + country: shipping.address?.country + }) + + contactGroup.patchValue({ + contactid: shipping.contact?.contactid, + firstName: shipping.contact?.firstName, + lastName: shipping.contact?.lastName, + middleInitial: shipping.contact?.middleInitial, + title: shipping.contact?.title, + phone: shipping.contact?.phone, + mobile: shipping.contact?.mobile, + fax: shipping.contact?.fax, + email: shipping.contact?.email, + refNumber: shipping.contact?.refNumber, + notes: shipping.contact?.notes, + }) + } + + this.calculateDeliveryEstimate(); } updateShippingValidation(shipTo: string): void { - // const addressGroup = this.shippingForm.get('address') as FormGroup; - // const contactGroup = this.shippingForm.get('contact') as FormGroup; + const addressGroup = this.shippingForm.get('address') as FormGroup; + const contactGroup = this.shippingForm.get('contact') as FormGroup; - // if (shipTo === 'thirdParty') { - // Object.keys(addressGroup.controls).forEach(key => { - // addressGroup.get(key)?.setValidators(Validators.required); - // }); - // Object.keys(contactGroup.controls).forEach(key => { - // if (key !== 'middleInitial' && key !== 'fax' && key !== 'notes' && key !== 'refNumber') { - // contactGroup.get(key)?.setValidators(Validators.required); - // } - // }); - // } else { - // Object.keys(addressGroup.controls).forEach(key => { - // addressGroup.get(key)?.clearValidators(); - // }); - // Object.keys(contactGroup.controls).forEach(key => { - // contactGroup.get(key)?.clearValidators(); - // }); - // } + if (shipTo === 'thirdParty') { + Object.keys(addressGroup.controls).forEach(key => { + if (key === 'address1') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]); + } else if (key === 'address2') { + addressGroup.get(key)?.setValidators([Validators.maxLength(100)]); + } else if (key === 'city') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]); + } else if (key === 'state') { + addressGroup.get(key)?.setValidators(Validators.required); + } else if (key === 'country') { + addressGroup.get(key)?.setValidators(Validators.required); + } else if (key === 'zip') { + addressGroup.get(key)?.setValidators([Validators.required, ZipCodeValidator('country')]); + } + }); + Object.keys(contactGroup.controls).forEach(key => { + if (key === 'firstName') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]); + } else if (key === 'lastName') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(50)]); + } else if (key === 'middleInitial') { + addressGroup.get(key)?.setValidators([Validators.maxLength(1)]); + } else if (key === 'title') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.maxLength(100)]); + } else if (key === 'phone') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]); + } else if (key === 'mobile') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.pattern(/^[0-9]{10,15}$/)]); + } else if (key === 'fax') { + addressGroup.get(key)?.setValidators([Validators.pattern(/^[0-9]{10,15}$/)]); + } else if (key === 'email') { + addressGroup.get(key)?.setValidators([Validators.required, Validators.email, Validators.maxLength(100)]); + } + }); + } else { + Object.keys(addressGroup.controls).forEach(key => { + addressGroup.get(key)?.clearValidators(); + }); + Object.keys(contactGroup.controls).forEach(key => { + contactGroup.get(key)?.clearValidators(); + }); + } // addressGroup.updateValueAndValidity(); // contactGroup.updateValueAndValidity(); @@ -334,11 +400,31 @@ export class ShippingComponent { let shipTo = this.shippingForm.get('shipTo')?.value; if (shipTo === 'preparer') { - return `${this.preparerContact.firstName} ${this.preparerContact.lastName}, ${this.preparerAddress.address1}, ${this.preparerAddress.city}, ${this.preparerAddress.state}, ${this.preparerAddress.zip}, ${this.preparerAddress.country}`; + return `${this.preparerAddress.companyName}, ${this.preparerAddress.address1}, + ${this.preparerAddress.city}, ${this.preparerAddress.state}, ${this.preparerAddress.zip}, + ${this.preparerAddress.country}`; } if (shipTo === 'holder') { - return `${this.holderContact.firstName} ${this.holderContact.lastName}, ${this.holderAddress.address1}, ${this.holderAddress.city}, ${this.holderAddress.state}, ${this.holderAddress.zip}, ${this.holderAddress.country}`; + return `${this.holderAddress.companyName}, ${this.holderAddress.address1}, + ${this.holderAddress.city}, ${this.holderAddress.state}, ${this.holderAddress.zip}, + ${this.holderAddress.country}`; + } + + return ''; + } + + getContactLabel(): string { + let shipTo = this.shippingForm.get('shipTo')?.value; + + if (shipTo === 'preparer') { + return `${this.preparerContact.firstName} ${this.preparerContact.middleInitial} ${this.preparerContact.lastName}, + ${this.preparerContact.email}, ${this.preparerContact.phone}`; + } + + if (shipTo === 'holder') { + return `${this.holderContact.firstName} ${this.holderContact.middleInitial} ${this.holderContact.lastName}, + ${this.holderContact.email}, ${this.holderContact.phone}`; } return ''; } @@ -373,4 +459,56 @@ export class ShippingComponent { this.showContactForm = true; } } + + private calculateDeliveryEstimate(): void { + const deliveryType = this.shippingForm.get('deliveryType')?.value; + const deliveryTypeObj = this.deliveryTypes.find(dt => dt.value === deliveryType); + const daysToDelivery: number = Number(deliveryTypeObj?.daysToDelivery) !== 0 ? Number(deliveryTypeObj?.daysToDelivery) : 3; + const cutOffTime: number = Number(deliveryTypeObj?.cutOffTime) !== 0 ? Number(deliveryTypeObj?.cutOffTime) : 16; + + const now = new Date(); + const cutoffTime = new Date(); + cutoffTime.setHours(cutOffTime, 0, 0, 0); + + let deliveryDate: Date; + let message = 'Estimated delivery: '; + + switch (deliveryType) { + case 'SAME': + if (isAfter(now, cutoffTime)) { + // After cutoff time, deliver next business day + deliveryDate = addDays(now, 1); + while (isWeekend(deliveryDate)) { + deliveryDate = addDays(deliveryDate, 1); + } + message += format(deliveryDate, 'EEEE, MMMM do, yyyy'); + } else { + message += 'Today by end of day'; + } + break; + + case 'STD': + // Standard is 3 business days + deliveryDate = addDays(now, daysToDelivery); + while (isWeekend(deliveryDate)) { + deliveryDate = addDays(deliveryDate, 1); + } + message += format(deliveryDate, 'EEEE, MMMM do, yyyy'); + break; + + case 'NBD': + // Next business day for pickup + deliveryDate = addDays(now, daysToDelivery); + while (isWeekend(deliveryDate)) { + deliveryDate = addDays(deliveryDate, 1); + } + message += format(deliveryDate, 'EEEE, MMMM do, yyyy'); + break; + + default: + message = ''; + } + + this.deliveryEstimate = message; + } } \ No newline at end of file diff --git a/src/app/core/models/carnet/shipping-address.ts b/src/app/core/models/carnet/shipping-address.ts index ea647c4..0904f00 100644 --- a/src/app/core/models/carnet/shipping-address.ts +++ b/src/app/core/models/carnet/shipping-address.ts @@ -1,4 +1,5 @@ export interface ShippingAddress { + addressid: number; address1: string; address2?: string | null; city: string; diff --git a/src/app/core/models/carnet/shipping-contact.ts b/src/app/core/models/carnet/shipping-contact.ts index 39d532f..97ee33d 100644 --- a/src/app/core/models/carnet/shipping-contact.ts +++ b/src/app/core/models/carnet/shipping-contact.ts @@ -1,4 +1,5 @@ export interface ShippingContact { + contactid: number; firstName: string; lastName: string; middleInitial?: string | null; diff --git a/src/app/core/models/carnet/shipping.ts b/src/app/core/models/carnet/shipping.ts index a191c7b..d227984 100644 --- a/src/app/core/models/carnet/shipping.ts +++ b/src/app/core/models/carnet/shipping.ts @@ -19,5 +19,5 @@ export interface Shipping { // Payment details paymentMethod: string; - notes?: string; + paymentNotes?: string; } diff --git a/src/app/core/models/delivery-type.ts b/src/app/core/models/delivery-type.ts index 60823fb..db58640 100644 --- a/src/app/core/models/delivery-type.ts +++ b/src/app/core/models/delivery-type.ts @@ -2,4 +2,6 @@ export interface DeliveryType { name: string; id: string; value: string; + daysToDelivery: string; + cutOffTime: string; } diff --git a/src/app/core/services/carnet/goods.service.ts b/src/app/core/services/carnet/goods.service.ts index 2bc707d..dd18452 100644 --- a/src/app/core/services/carnet/goods.service.ts +++ b/src/app/core/services/carnet/goods.service.ts @@ -109,19 +109,9 @@ export class GoodsService { deleteGoodsItem(headerid: number, item: GoodsItem): Observable { - const goodsItem = { - ITEMNO: item.itemNumber, - ITEMDESCRIPTION: item.description, - NOOFPIECES: item.pieces, - ITEMWEIGHT: item.weight, - ITEMWEIGHTUOM: item.unitOfMeasure, - ITEMVALUE: item.value, - GOODSORIGINCOUNTRY: item.countryOfOrigin, - } - const goodsItemsObj = { P_HEADERID: headerid, - P_GLTABLE: [goodsItem], + P_ITEMNO: item.itemNumber, P_USERID: this.userService.getUser(), }; diff --git a/src/app/core/services/carnet/shipping.service.ts b/src/app/core/services/carnet/shipping.service.ts index b045e2e..4a0a279 100644 --- a/src/app/core/services/carnet/shipping.service.ts +++ b/src/app/core/services/carnet/shipping.service.ts @@ -30,7 +30,6 @@ export class ShippingService { courierAccount: shippingDetails.CUSTCOURIERNO, paymentMethod: shippingDetails.PAYMENTMETHOD, - notes: shippingDetails.NOTES, needsBond: shippingDetails.INSPROTECTION === 'Y', needsInsurance: shippingDetails.LDIPROTECTION === 'Y', needsLostDocProtection: shippingDetails.LDIPROTECTION === 'Y' @@ -38,6 +37,7 @@ export class ShippingService { if (shippingDetails.address) { shippingData.address = { + addressid: shippingDetails.SHIPADDRESSID, address1: shippingDetails.ADDRESS1, address2: shippingDetails.ADDRESS2, city: shippingDetails.CITY, @@ -49,6 +49,7 @@ export class ShippingService { if (shippingDetails.contact) { shippingData.contact = { + contactid: shippingDetails.SHIPCONTACTID, firstName: shippingDetails.FIRSTNAME, lastName: shippingDetails.LASTNAME, middleInitial: shippingDetails.MIDDLEINITIAL, @@ -58,6 +59,7 @@ export class ShippingService { fax: shippingDetails.FAXNO, email: shippingDetails.EMAILADDRESS, refNumber: shippingDetails.REFNO, + notes: shippingDetails.NOTES, } } @@ -73,28 +75,30 @@ export class ShippingService { P_DELIVERYMETHOD: shippingData.deliveryMethod, P_CUSTCOURIERNO: shippingData.courierAccount, P_PAYMENTMETHOD: shippingData.paymentMethod, - P_NOTES: shippingData.notes, + P_FORMOFSECURITY: shippingData.needsBond ? 'Y' : 'N', P_INSPROTECTION: shippingData.needsInsurance ? 'Y' : 'N', P_LDIPROTECTION: shippingData.needsLostDocProtection ? 'Y' : 'N', - address1: shippingData.address?.address1 || '', - address2: shippingData.address?.address2 || null, - city: shippingData.address?.city || '', - state: shippingData.address?.state || '', - zip: shippingData.address?.zip || '', - country: shippingData.address?.country || '', + P_SHIPCONTACTID: shippingData.address?.addressid || 0, + P_ADDRESS1: shippingData.address?.address1 || null, + P_ADDRESS2: shippingData.address?.address2 || null, + P_CITY: shippingData.address?.city || null, + P_STATE: shippingData.address?.state || null, + P_ZIP: shippingData.address?.zip || null, + P_COUNTRY: shippingData.address?.country || null, - firstName: shippingData.contact?.firstName || '', - lastName: shippingData.contact?.lastName || '', - middleInitial: shippingData.contact?.middleInitial || null, - title: shippingData.contact?.title || '', - phone: shippingData.contact?.phone || '', - mobile: shippingData.contact?.mobile || '', - fax: shippingData.contact?.fax || null, - emailAddress: shippingData.contact?.email || '', - refNo: shippingData.contact?.refNumber || '', + P_FIRSTNAME: shippingData.contact?.firstName || '', + P_LASTNAME: shippingData.contact?.lastName || '', + P_MIDDLEINITIAL: shippingData.contact?.middleInitial || null, + P_TITLE: shippingData.contact?.title || '', + P_PHONENO: shippingData.contact?.phone || '', + P_MOBILENO: shippingData.contact?.mobile || '', + P_FAXNO: shippingData.contact?.fax || null, + P_EMAILADDRESS: shippingData.contact?.email || '', + P_REFNO: shippingData.contact?.refNumber || '', + P_NOTES: shippingData.contact?.notes, P_USERID: this.userService.getUser() } diff --git a/src/app/core/services/common/common.service.ts b/src/app/core/services/common/common.service.ts index 3e93f0e..e9f0a07 100644 --- a/src/app/core/services/common/common.service.ts +++ b/src/app/core/services/common/common.service.ts @@ -72,7 +72,9 @@ export class CommonService { response.map((item) => ({ name: item.PARAMDESC, id: item.PARAMID, - value: item.PARAMVALUE + value: item.PARAMVALUE, + daysToDelivery: item.ADDLPARAMVALUE1, + cutOffTime: item.ADDLPARAMVALUE2, })) ) ); diff --git a/src/index.html b/src/index.html index b28b971..c3002e8 100644 --- a/src/index.html +++ b/src/index.html @@ -1,5 +1,6 @@ + ClientApp @@ -9,7 +10,9 @@ + - + + \ No newline at end of file diff --git a/src/styles.scss b/src/styles.scss index 8f5ac64..61ae96d 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -2,8 +2,7 @@ @use '@angular/material' as mat; html { - @include mat.theme((density: -3, - )); + @include mat.theme((density: -4)); } html,