60 minlesson

Phase 2: Shipment Creation & Carrier Management

Phase 2: Shipment Creation & Carrier Management

Person 1 Responsibility

Build the complete shipment creation vertical slice including UI forms, business logic, and integration with the team's event bus.

Learning Objectives

  • Implement Factory Method and Strategy patterns for carriers
  • Build Chain of Responsibility for validation
  • Use Builder pattern for complex object construction
  • Integrate with team event bus
  • Create type-safe React forms

Requirements

1. Multi-Step Shipment Creation Form

Create src/features/shipment-creation/components/CreateShipmentWizard.tsx:

typescript
1'use client'
2
3export function CreateShipmentWizard() {
4 const [step, setStep] = useState(1);
5 const [shipmentData, setShipmentData] = useState<Partial<Shipment>>({});
6
7 return (
8 <div className="max-w-4xl mx-auto">
9 <WizardProgress currentStep={step} totalSteps={4} />
10
11 {step === 1 && <PackageDetailsForm onNext={handlePackageNext} />}
12 {step === 2 && <AddressForm onNext={handleAddressNext} onBack={...} />}
13 {step === 3 && <CarrierSelectionForm onNext={handleCarrierNext} onBack={...} />}
14 {step === 4 && <ReviewAndConfirm onSubmit={handleSubmit} onBack={...} />}
15 </div>
16 );
17}

Required Form Steps:

  1. Package details (dimensions, weight, type, value)
  2. Origin and destination addresses
  3. Carrier and service level selection with pricing
  4. Review and confirm

2. Factory Method Pattern - Carrier Services

Create src/features/shipment-creation/services/carrier-factory.ts:

typescript
1// Abstract factory
2export interface CarrierServiceFactory {
3 createRateCalculator(): RateCalculator;
4 createLabelGenerator(): LabelGenerator;
5 getCarrierName(): CarrierName;
6}
7
8// Concrete factories
9export class USPSServiceFactory implements CarrierServiceFactory {
10 createRateCalculator(): RateCalculator {
11 return new USPSRateCalculator();
12 }
13
14 createLabelGenerator(): LabelGenerator {
15 return new USPSLabelGenerator();
16 }
17
18 getCarrierName(): CarrierName {
19 return 'USPS';
20 }
21}
22
23export class FedExServiceFactory implements CarrierServiceFactory {
24 // Similar implementation
25}
26
27// Factory selector
28export function getCarrierFactory(carrier: CarrierName): CarrierServiceFactory {
29 const factories: Record<CarrierName, CarrierServiceFactory> = {
30 USPS: new USPSServiceFactory(),
31 FedEx: new FedExServiceFactory(),
32 UPS: new UPSServiceFactory(),
33 DHL: new DHLServiceFactory(),
34 };
35
36 return factories[carrier];
37}

3. Strategy Pattern - Routing Strategies

Create src/features/shipment-creation/services/routing-strategies.ts:

typescript
1// Strategy interface
2export interface RoutingStrategy {
3 selectCarrier(
4 package: Package,
5 origin: Address,
6 destination: Address,
7 preferences: RoutingPreferences
8 ): CarrierRecommendation;
9}
10
11// Concrete strategies
12export class CheapestRouteStrategy implements RoutingStrategy {
13 selectCarrier(...): CarrierRecommendation {
14 // Calculate rates for all carriers and return cheapest
15 }
16}
17
18export class FastestRouteStrategy implements RoutingStrategy {
19 selectCarrier(...): CarrierRecommendation {
20 // Return carrier with fastest delivery
21 }
22}
23
24export class EcoFriendlyRouteStrategy implements RoutingStrategy {
25 selectCarrier(...): CarrierRecommendation {
26 // Prioritize carriers with carbon offset programs
27 }
28}
29
30// Context
31export class CarrierSelector {
32 constructor(private strategy: RoutingStrategy) {}
33
34 setStrategy(strategy: RoutingStrategy): void {
35 this.strategy = strategy;
36 }
37
38 recommend(...): CarrierRecommendation {
39 return this.strategy.selectCarrier(...);
40 }
41}

4. Chain of Responsibility - Address Validation

Create src/features/shipment-creation/services/validation-chain.ts:

typescript
1interface AddressValidator {
2 setNext(validator: AddressValidator): AddressValidator;
3 validate(address: Address): ValidationResult;
4}
5
6abstract class BaseAddressValidator implements AddressValidator {
7 private nextValidator: AddressValidator | null = null;
8
9 setNext(validator: AddressValidator): AddressValidator {
10 this.nextValidator = validator;
11 return validator;
12 }
13
14 validate(address: Address): ValidationResult {
15 const result = this.doValidation(address);
16
17 if (!result.isValid) {
18 return result;
19 }
20
21 if (this.nextValidator) {
22 return this.nextValidator.validate(address);
23 }
24
25 return result;
26 }
27
28 protected abstract doValidation(address: Address): ValidationResult;
29}
30
31// Concrete validators
32class RequiredFieldsValidator extends BaseAddressValidator {
33 protected doValidation(address: Address): ValidationResult {
34 const errors: ValidationError[] = [];
35
36 if (!address.street1) errors.push({ field: 'street1', message: 'Street address is required' });
37 if (!address.city) errors.push({ field: 'city', message: 'City is required' });
38 if (!address.state) errors.push({ field: 'state', message: 'State is required' });
39 if (!address.postalCode) errors.push({ field: 'postalCode', message: 'Postal code is required' });
40
41 return {
42 isValid: errors.length === 0,
43 errors,
44 };
45 }
46}
47
48class PostalCodeFormatValidator extends BaseAddressValidator {
49 protected doValidation(address: Address): ValidationResult {
50 const usZipRegex = /^\d{5}(-\d{4})?$/;
51
52 if (address.country === 'US' && !usZipRegex.test(address.postalCode)) {
53 return {
54 isValid: false,
55 errors: [{ field: 'postalCode', message: 'Invalid US ZIP code format' }],
56 };
57 }
58
59 return { isValid: true, errors: [] };
60 }
61}
62
63class StateCodeValidator extends BaseAddressValidator {
64 private validStates = ['AL', 'AK', 'AZ', /* ... all US states */];
65
66 protected doValidation(address: Address): ValidationResult {
67 if (address.country === 'US' && !this.validStates.includes(address.state)) {
68 return {
69 isValid: false,
70 errors: [{ field: 'state', message: 'Invalid state code' }],
71 };
72 }
73
74 return { isValid: true, errors: [] };
75 }
76}
77
78// Chain builder
79export function createAddressValidationChain(): AddressValidator {
80 const required = new RequiredFieldsValidator();
81 const postalCode = new PostalCodeFormatValidator();
82 const state = new StateCodeValidator();
83
84 required.setNext(postalCode).setNext(state);
85
86 return required;
87}

5. Builder Pattern - Shipment Construction

Create src/features/shipment-creation/services/shipment-builder.ts:

typescript
1export class ShipmentBuilder {
2 private shipment: Partial<Shipment> = {};
3
4 setPackage(pkg: Package): this {
5 this.shipment.package = pkg;
6 return this;
7 }
8
9 setOrigin(address: Address): this {
10 this.shipment.origin = address;
11 return this;
12 }
13
14 setDestination(address: Address): this {
15 this.shipment.destination = address;
16 return this;
17 }
18
19 setCarrier(carrier: CarrierName): this {
20 this.shipment.carrier = carrier;
21 return this;
22 }
23
24 setServiceLevel(serviceLevel: string): this {
25 this.shipment.serviceLevel = serviceLevel;
26 return this;
27 }
28
29 setCost(cost: number): this {
30 this.shipment.cost = cost;
31 return this;
32 }
33
34 setEstimatedDelivery(date: Date): this {
35 this.shipment.estimatedDelivery = date;
36 return this;
37 }
38
39 generateTrackingNumber(): this {
40 this.shipment.trackingNumber = `TRK${Date.now()}${Math.random().toString(36).substr(2, 9)}`.toUpperCase();
41 return this;
42 }
43
44 build(): Shipment {
45 // Validate all required fields are set
46 if (!this.shipment.package) throw new Error('Package is required');
47 if (!this.shipment.origin) throw new Error('Origin is required');
48 if (!this.shipment.destination) throw new Error('Destination is required');
49 if (!this.shipment.carrier) throw new Error('Carrier is required');
50
51 const now = new Date();
52
53 return {
54 id: crypto.randomUUID(),
55 orderId: `ORD${Date.now()}`,
56 package: this.shipment.package,
57 origin: this.shipment.origin,
58 destination: this.shipment.destination,
59 carrier: this.shipment.carrier!,
60 serviceLevel: this.shipment.serviceLevel || 'STANDARD',
61 trackingNumber: this.shipment.trackingNumber || '',
62 status: ShipmentStatus.CREATED,
63 estimatedDelivery: this.shipment.estimatedDelivery || new Date(now.getTime() + 86400000 * 3),
64 cost: this.shipment.cost || 0,
65 createdAt: now,
66 updatedAt: now,
67 createdBy: 'current-user-id', // Replace with actual user ID
68 } as Shipment;
69 }
70
71 reset(): void {
72 this.shipment = {};
73 }
74}
75
76// Usage
77const shipment = new ShipmentBuilder()
78 .setPackage(packageData)
79 .setOrigin(originAddress)
80 .setDestination(destinationAddress)
81 .setCarrier('FedEx')
82 .setServiceLevel('GROUND')
83 .setCost(25.50)
84 .generateTrackingNumber()
85 .build();

6. Event Bus Integration

Emit ShipmentCreated event:

typescript
1// After creating shipment
2import { eventBus } from '@/services/event-bus';
3import { EventType } from '@/shared/types/events';
4
5async function createShipment(data: CreateShipmentRequest): Promise<Shipment> {
6 // Build shipment
7 const shipment = new ShipmentBuilder()
8 .setPackage(data.package)
9 // ... set other fields
10 .build();
11
12 // Save to storage
13 await saveShipment(shipment);
14
15 // Emit event for Person 2 (Tracking)
16 await eventBus.publish({
17 type: EventType.SHIPMENT_CREATED,
18 payload: { shipment },
19 timestamp: new Date(),
20 source: 'shipment-creation',
21 });
22
23 return shipment;
24}

7. Address Book Management

Create src/features/shipment-creation/components/AddressBook.tsx:

  • Save frequently used addresses
  • Quick-select for origin/destination
  • Edit and delete saved addresses
  • LocalStorage persistence

Deliverables

  • Multi-step shipment creation form (4 steps)
  • Factory Method pattern (4+ carrier factories)
  • Strategy pattern (3+ routing strategies)
  • Chain of Responsibility (3+ validators)
  • Builder pattern for shipments
  • Event bus integration (emit SHIPMENT_CREATED)
  • Address book with CRUD operations
  • Unit tests (70%+ coverage)
  • Integration with shared types

Testing Requirements

typescript
1describe('CarrierFactory', () => {
2 it('should create correct services for each carrier', () => {
3 const factory = getCarrierFactory('USPS');
4 expect(factory.createRateCalculator()).toBeInstanceOf(USPSRateCalculator);
5 });
6});
7
8describe('RoutingStrategies', () => {
9 it('should select cheapest carrier', () => {
10 const strategy = new CheapestRouteStrategy();
11 const result = strategy.selectCarrier(...);
12 expect(result.carrier).toBe('USPS'); // Assuming USPS is cheapest
13 });
14});
15
16describe('AddressValidationChain', () => {
17 it('should reject invalid addresses', () => {
18 const chain = createAddressValidationChain();
19 const result = chain.validate({ street1: '', city: '' } as Address);
20 expect(result.isValid).toBe(false);
21 });
22});
23
24describe('ShipmentBuilder', () => {
25 it('should build complete shipment', () => {
26 const shipment = new ShipmentBuilder()
27 .setPackage(mockPackage)
28 .setOrigin(mockOrigin)
29 .setDestination(mockDest)
30 .setCarrier('FedEx')
31 .build();
32
33 expect(shipment.id).toBeDefined();
34 expect(shipment.status).toBe(ShipmentStatus.CREATED);
35 });
36});

Integration Points

Provides to Person 2:

  • Shipment creation via EventBus
  • Shipment data structure

Provides to Person 3:

  • API endpoint for creating shipments (for reorder feature)

Estimated Time

20-25 hours for Person 1


Next Steps

Once complete, coordinate with Person 2 to test the SHIPMENT_CREATED event integration.