$parcels * @param list $services Codes to ask for. * @return list One per service, for all parcels together. * @throws \BlueBrainMedia\MapleParcel\Api\ApiException When Canada Post cannot be asked or says no. */ public function rate( Destination $destination, array $parcels, array $services, bool $coverage = false, bool $signature = false ): array { $groups = array(); foreach ( $parcels as $parcel ) { $key = $parcel->key( $coverage ); if ( isset( $groups[ $key ] ) ) { ++$groups[ $key ]['count']; } else { $groups[ $key ] = array( 'parcel' => $parcel, 'count' => 1, ); } } $sets = array(); foreach ( $groups as $group ) { $scenario = $this->scenario( $destination, $group['parcel'], $services, $coverage, $signature ); $quotes = $this->cache->get( $scenario ); if ( null === $quotes ) { $quotes = $this->client->prices( $scenario ); $this->cache->put( $scenario, $quotes ); } $sets[] = array( $quotes, $group['count'] ); } return self::merge( $sets ); } /** * The mailingScenario for one parcel. * * @param list $services * @return array */ public function scenario( Destination $destination, Parcel $parcel, array $services, bool $coverage = false, bool $signature = false ): array { $scenario = array( 'quoteType' => $this->settings->quoteType() ); if ( Settings::QUOTE_COMMERCIAL === $scenario['quoteType'] ) { $scenario['customerNumber'] = $this->settings->customerNumber(); // Solutions for Small Business members must leave contractId out entirely. if ( '' !== $this->settings->contractId() ) { $scenario['contractId'] = $this->settings->contractId(); } } $options = array(); if ( $signature ) { $options[] = array( 'optionCode' => 'SO' ); } if ( $coverage && $parcel->value > 0 ) { $options[] = array( 'optionCode' => 'COV', 'optionAmount' => round( min( $parcel->value, self::MAX_COVERAGE ), 2 ), ); } if ( array() !== $options ) { $scenario['options'] = $options; } $scenario['parcelCharacteristics'] = $parcel->toApi(); if ( array() !== $services ) { $scenario['services'] = array_slice( $services, 0, RatingClient::MAX_SERVICES ); } $scenario['originPostalCode'] = $this->settings->originPostalCode(); $scenario['destination'] = $destination->toApi(); return $scenario; } /** * @param list, 1: int}> $sets Each distinct parcel's quotes, and how many of that parcel. * @return list */ public static function merge( array $sets ): array { $merged = null; foreach ( $sets as [ $quotes, $count ] ) { $byCode = array(); foreach ( $quotes as $quote ) { $byCode[ $quote->serviceCode ] = self::times( $quote, $count ); } if ( null === $merged ) { $merged = $byCode; continue; } $next = array(); foreach ( $merged as $code => $sofar ) { if ( isset( $byCode[ $code ] ) ) { $next[ $code ] = self::plus( $sofar, $byCode[ $code ] ); } } $merged = $next; } return array_values( $merged ?? array() ); } private static function times( Quote $quote, int $count ): Quote { if ( 1 === $count ) { return $quote; } return new Quote( $quote->serviceCode, $quote->serviceName, $quote->base * $count, $quote->due * $count, $quote->transitDays, $quote->deliveryDate, $quote->guaranteed ); } private static function plus( Quote $a, Quote $b ): Quote { $transit = null === $a->transitDays || null === $b->transitDays ? ( $a->transitDays ?? $b->transitDays ) : max( $a->transitDays, $b->transitDays ); $date = null === $a->deliveryDate || null === $b->deliveryDate ? ( $a->deliveryDate ?? $b->deliveryDate ) : max( $a->deliveryDate, $b->deliveryDate ); return new Quote( $a->serviceCode, $a->serviceName, $a->base + $b->base, $a->due + $b->due, $transit, $date, $a->guaranteed && $b->guaranteed ); } }