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:
typescript1'use client'23export function CreateShipmentWizard() {4 const [step, setStep] = useState(1);5 const [shipmentData, setShipmentData] = useState<Partial<Shipment>>({});67 return (8 <div className="max-w-4xl mx-auto">9 <WizardProgress currentStep={step} totalSteps={4} />1011 {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:
- Package details (dimensions, weight, type, value)
- Origin and destination addresses
- Carrier and service level selection with pricing
- Review and confirm
2. Factory Method Pattern - Carrier Services
Create src/features/shipment-creation/services/carrier-factory.ts:
typescript1// Abstract factory2export interface CarrierServiceFactory {3 createRateCalculator(): RateCalculator;4 createLabelGenerator(): LabelGenerator;5 getCarrierName(): CarrierName;6}78// Concrete factories9export class USPSServiceFactory implements CarrierServiceFactory {10 createRateCalculator(): RateCalculator {11 return new USPSRateCalculator();12 }1314 createLabelGenerator(): LabelGenerator {15 return new USPSLabelGenerator();16 }1718 getCarrierName(): CarrierName {19 return 'USPS';20 }21}2223export class FedExServiceFactory implements CarrierServiceFactory {24 // Similar implementation25}2627// Factory selector28export 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 };3536 return factories[carrier];37}
3. Strategy Pattern - Routing Strategies
Create src/features/shipment-creation/services/routing-strategies.ts:
typescript1// Strategy interface2export interface RoutingStrategy {3 selectCarrier(4 package: Package,5 origin: Address,6 destination: Address,7 preferences: RoutingPreferences8 ): CarrierRecommendation;9}1011// Concrete strategies12export class CheapestRouteStrategy implements RoutingStrategy {13 selectCarrier(...): CarrierRecommendation {14 // Calculate rates for all carriers and return cheapest15 }16}1718export class FastestRouteStrategy implements RoutingStrategy {19 selectCarrier(...): CarrierRecommendation {20 // Return carrier with fastest delivery21 }22}2324export class EcoFriendlyRouteStrategy implements RoutingStrategy {25 selectCarrier(...): CarrierRecommendation {26 // Prioritize carriers with carbon offset programs27 }28}2930// Context31export class CarrierSelector {32 constructor(private strategy: RoutingStrategy) {}3334 setStrategy(strategy: RoutingStrategy): void {35 this.strategy = strategy;36 }3738 recommend(...): CarrierRecommendation {39 return this.strategy.selectCarrier(...);40 }41}
4. Chain of Responsibility - Address Validation
Create src/features/shipment-creation/services/validation-chain.ts:
typescript1interface AddressValidator {2 setNext(validator: AddressValidator): AddressValidator;3 validate(address: Address): ValidationResult;4}56abstract class BaseAddressValidator implements AddressValidator {7 private nextValidator: AddressValidator | null = null;89 setNext(validator: AddressValidator): AddressValidator {10 this.nextValidator = validator;11 return validator;12 }1314 validate(address: Address): ValidationResult {15 const result = this.doValidation(address);1617 if (!result.isValid) {18 return result;19 }2021 if (this.nextValidator) {22 return this.nextValidator.validate(address);23 }2425 return result;26 }2728 protected abstract doValidation(address: Address): ValidationResult;29}3031// Concrete validators32class RequiredFieldsValidator extends BaseAddressValidator {33 protected doValidation(address: Address): ValidationResult {34 const errors: ValidationError[] = [];3536 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' });4041 return {42 isValid: errors.length === 0,43 errors,44 };45 }46}4748class PostalCodeFormatValidator extends BaseAddressValidator {49 protected doValidation(address: Address): ValidationResult {50 const usZipRegex = /^\d{5}(-\d{4})?$/;5152 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 }5859 return { isValid: true, errors: [] };60 }61}6263class StateCodeValidator extends BaseAddressValidator {64 private validStates = ['AL', 'AK', 'AZ', /* ... all US states */];6566 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 }7374 return { isValid: true, errors: [] };75 }76}7778// Chain builder79export function createAddressValidationChain(): AddressValidator {80 const required = new RequiredFieldsValidator();81 const postalCode = new PostalCodeFormatValidator();82 const state = new StateCodeValidator();8384 required.setNext(postalCode).setNext(state);8586 return required;87}
5. Builder Pattern - Shipment Construction
Create src/features/shipment-creation/services/shipment-builder.ts:
typescript1export class ShipmentBuilder {2 private shipment: Partial<Shipment> = {};34 setPackage(pkg: Package): this {5 this.shipment.package = pkg;6 return this;7 }89 setOrigin(address: Address): this {10 this.shipment.origin = address;11 return this;12 }1314 setDestination(address: Address): this {15 this.shipment.destination = address;16 return this;17 }1819 setCarrier(carrier: CarrierName): this {20 this.shipment.carrier = carrier;21 return this;22 }2324 setServiceLevel(serviceLevel: string): this {25 this.shipment.serviceLevel = serviceLevel;26 return this;27 }2829 setCost(cost: number): this {30 this.shipment.cost = cost;31 return this;32 }3334 setEstimatedDelivery(date: Date): this {35 this.shipment.estimatedDelivery = date;36 return this;37 }3839 generateTrackingNumber(): this {40 this.shipment.trackingNumber = `TRK${Date.now()}${Math.random().toString(36).substr(2, 9)}`.toUpperCase();41 return this;42 }4344 build(): Shipment {45 // Validate all required fields are set46 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');5051 const now = new Date();5253 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 ID68 } as Shipment;69 }7071 reset(): void {72 this.shipment = {};73 }74}7576// Usage77const 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:
typescript1// After creating shipment2import { eventBus } from '@/services/event-bus';3import { EventType } from '@/shared/types/events';45async function createShipment(data: CreateShipmentRequest): Promise<Shipment> {6 // Build shipment7 const shipment = new ShipmentBuilder()8 .setPackage(data.package)9 // ... set other fields10 .build();1112 // Save to storage13 await saveShipment(shipment);1415 // 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 });2223 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
typescript1describe('CarrierFactory', () => {2 it('should create correct services for each carrier', () => {3 const factory = getCarrierFactory('USPS');4 expect(factory.createRateCalculator()).toBeInstanceOf(USPSRateCalculator);5 });6});78describe('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 cheapest13 });14});1516describe('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});2324describe('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();3233 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.