diff --git a/modules/dangerous/config/schema/commerce_fedex_dangerous.yml b/modules/dangerous/config/schema/commerce_fedex_dangerous.yml new file mode 100644 index 0000000..a709275 --- /dev/null +++ b/modules/dangerous/config/schema/commerce_fedex_dangerous.yml @@ -0,0 +1,7 @@ +commerce_fedex.fedex_plugin.plugin.dangerous: + type: commerce_fedex_service + label: 'Commerce Fedex Dry Ice Service' + mapping: + contact_number: + type: string + label: 'Contact Number' diff --git a/modules/dangerous/src/Plugin/Commerce/EntityTrait/PurchasableEntityDangerousGoods.php b/modules/dangerous/src/Plugin/Commerce/EntityTrait/PurchasableEntityDangerousGoods.php index 33fee43..363f0d0 100644 --- a/modules/dangerous/src/Plugin/Commerce/EntityTrait/PurchasableEntityDangerousGoods.php +++ b/modules/dangerous/src/Plugin/Commerce/EntityTrait/PurchasableEntityDangerousGoods.php @@ -2,7 +2,10 @@ namespace Drupal\commerce_fedex_dangerous\Plugin\Commerce\EntityTrait; -use Drupal\commerce_fedex_dangerous\PurchasableEntityHazardousBase; +use Drupal\commerce\BundleFieldDefinition; +use Drupal\commerce\Plugin\Commerce\EntityTrait\EntityTraitBase; +use Drupal\commerce_fedex\Plugin\Commerce\ShippingMethod\FedEx; +use NicholasCreativeMedia\FedExPHP\Enums\DangerousGoodsAccessibilityType; /** * Provides the "fedex_dangerous" trait. @@ -13,14 +16,22 @@ * entity_types = {"commerce_product_variation"} * ) */ -class PurchasableEntityDangerousGoods extends PurchasableEntityHazardousBase { +class PurchasableEntityDangerousGoods extends EntityTraitBase { /** * {@inheritdoc} */ public function buildFieldDefinitions() { - $fields = $this->baseFields(); + $id = $this->getPluginId(); + $fields[$id . '_accessibility'] = BundleFieldDefinition::create('list_string') + ->setLabel($this->t('Require Dangerous Goods/Hazardous Materials Shipping')) + ->setCardinality(1) + ->setSetting('allowed_values', [0 => "None"] + FedEx::enumToList(DangerousGoodsAccessibilityType::getValidValues())) + ->setDisplayOptions('form', [ + 'type' => 'options_select', + 'weight' => 95, + ]); return $fields; } diff --git a/modules/dangerous/src/Plugin/Commerce/FedEx/DangerousGoodsPlugin.php b/modules/dangerous/src/Plugin/Commerce/FedEx/DangerousGoodsPlugin.php new file mode 100644 index 0000000..358c7f8 --- /dev/null +++ b/modules/dangerous/src/Plugin/Commerce/FedEx/DangerousGoodsPlugin.php @@ -0,0 +1,114 @@ + ''] + parent::defaultConfiguration(); + + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + $form['contact_number'] = [ + '#type' => 'textfield', + '#title' => $this->t("Contact Number"), + '#description' => $this->t('Enter the Phone number of your dangerous goods/hazardous materials contact person'), + '#default_value' => $this->configuration['contact_number'], + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { + parent::submitConfigurationForm($form, $form_state); + $this->configuration['contact_number'] = $form_state->getValue('contact_number'); + } + + /** + * {@inheritdoc} + */ + public function adjustPackage(RequestedPackageLineItem $package, array $shipment_items, ShipmentInterface $shipment) { + $status = $this->getDangerousStatus(reset($shipment_items)); + + if ($status === static::NOT_DANGEROUS) { + return $package; + } + + $special_services_requested = $package->getSpecialServicesRequested(); + if (empty($special_services_requested)) { + $special_services_requested = new PackageSpecialServicesRequested(); + } + $special_services_requested->addToSpecialServiceTypes(PackageSpecialServiceType::VALUE_DANGEROUS_GOODS); + $dangerous_goods_detail = $special_services_requested->getDangerousGoodsDetail(); + if (empty($dangerous_goods_detail)) { + $dangerous_goods_detail = new DangerousGoodsDetail(); + } + $dangerous_goods_detail->setAccessibility($status); + $special_services_requested->setDangerousGoodsDetail($dangerous_goods_detail); + $package->setSpecialServicesRequested($special_services_requested); + + return $package; + } + + /** + * {@inheritdoc} + */ + public function splitPackage(array $shipment_items, ShipmentInterface $shipment) { + $packages = []; + foreach ($shipment_items as $shipment_item) { + $packages[$this->getDangerousStatus($shipment_item)][] = $shipment_item; + } + return array_values($packages); + } + + /** + * Returns the DG status of an item. + * + * @param \Drupal\commerce_shipping\ShipmentItem $shipment_item + * The item to check. + * + * @return mixed + * The DG status. + */ + protected function getDangerousStatus(ShipmentItem $shipment_item) { + $storage = \Drupal::entityTypeManager()->getStorage('commerce_order_item'); + /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ + $order_item = $storage->load($shipment_item->getOrderItemId()); + $purchased_entity = $order_item->getPurchasedEntity(); + if (!$purchased_entity->hasField('fedex_dangerous_accessibility') || $purchased_entity->get('fedex_dangerous_accessibility')->isEmpty()) { + return static::NOT_DANGEROUS; + } + return $purchased_entity->get('fedex_dangerous_accessibility')->value; + } + +} diff --git a/modules/dangerous/src/PurchasableEntityHazardousBase.php b/modules/dangerous/src/PurchasableEntityHazardousBase.php deleted file mode 100644 index d82404e..0000000 --- a/modules/dangerous/src/PurchasableEntityHazardousBase.php +++ /dev/null @@ -1,96 +0,0 @@ -getPluginId(); - $label = $this->getLabel(); - - $fields[$id] = BundleFieldDefinition::create('boolean') - ->setLabel($label) - ->setDisplayOptions('form', [ - 'type' => 'boolean_checkbox', - 'weight' => 95, - ]); - - $fields[$id . '_options'] = BundleFieldDefinition::create('list_string') - ->setLabel($label . ' ' . $this->t('options')) - ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) - ->setSetting('allowed_values', [ - HazardousCommodityOptionType::VALUE_BATTERY => $this->t('Battery'), - HazardousCommodityOptionType::VALUE_HAZARDOUS_MATERIALS => $this->t('Hazardous materials'), - HazardousCommodityOptionType::VALUE_LIMITED_QUANTITIES_COMMODITIES => $this->t('Limited quantities commodities'), - HazardousCommodityOptionType::VALUE_ORM_D => $this->t('ORD M'), - HazardousCommodityOptionType::VALUE_REPORTABLE_QUANTITIES => $this->t('Reportable quantities'), - HazardousCommodityOptionType::VALUE_SMALL_QUANTITY_EXCEPTION => $this->t('Small quantity exception'), - ]) - ->setDisplayOptions('form', [ - 'type' => 'options_buttons', - 'weight' => 95, - ]); - - $fields[$id . '_quantity'] = BundleFieldDefinition::create('float') - ->setLabel($label . ' ' . $this->t('quantity')) - ->setDescription($this->t('Specify the quantity of hazardous materials being shipped.')) - ->setDisplayOptions('form', [ - 'type' => 'number', - 'weight' => 95, - ]); - - $fields[$id . '_units'] = BundleFieldDefinition::create('list_string') - ->setLabel($label . ' ' . $this->t('quantity units')) - ->setDescription($this->t('Specify the unit of measurement to use for the quantity.')) - ->setSetting('allowed_values', [ - 'ml' => $this->t('ml'), - 'L' => $this->t('L'), - 'g' => $this->t('g'), - 'kg' => $this->t('kg'), - 'kg G' => $this->t('kg G'), - ]) - ->setDisplayOptions('form', [ - 'type' => 'options_select', - 'weight' => 95, - ]); - - $fields[$id . '_regulation_type'] = BundleFieldDefinition::create('list_string') - ->setLabel($label . ' ' . $this->t('regulation type')) - ->setDescription($this->t('Select the regulation type of the hazardous materials')) - ->setSetting('allowed_values', [ - HazardousCommodityRegulationType::VALUE_ADR => $this->t('ADR'), - HazardousCommodityRegulationType::VALUE_DOT => $this->t('DOT'), - HazardousCommodityRegulationType::VALUE_IATA => $this->t('IATA'), - HazardousCommodityRegulationType::VALUE_ORMD => $this->t('ORMD'), - ]) - ->setDisplayOptions('form', [ - 'type' => 'options_select', - 'weight' => 95, - ]); - - $fields[$id . '_description'] = BundleFieldDefinition::create('string') - ->setLabel($label . ' ' . $this->t('description')) - ->setDescription($this->t('Identify and describe this hazardous commodity.')) - ->setDisplayOptions('form', [ - 'type' => 'string_textfield', - 'weight' => 95, - ]); - - return $fields; - } - -} diff --git a/modules/dry_ice/config/schema/commerce_fedex_dry_ice.yml b/modules/dry_ice/config/schema/commerce_fedex_dry_ice.yml index 5a0556f..cada769 100644 --- a/modules/dry_ice/config/schema/commerce_fedex_dry_ice.yml +++ b/modules/dry_ice/config/schema/commerce_fedex_dry_ice.yml @@ -10,4 +10,15 @@ commerce_fedex.fedex_plugin.plugin.dry_ice: type: string label: 'Package Type' weight: - type: mapping + type: field.value.physical_measurement + label: 'Weight' + intl: + type: mapping + label: 'International' + mapping: + package_type: + type: string + label: 'Package Type' + weight: + type: field.value.physical_measurement + label: 'Weight' diff --git a/modules/dry_ice/src/Plugin/Commerce/FedEx/DryIcePlugin.php b/modules/dry_ice/src/Plugin/Commerce/FedEx/DryIcePlugin.php index a892b88..6f7b71c 100644 --- a/modules/dry_ice/src/Plugin/Commerce/FedEx/DryIcePlugin.php +++ b/modules/dry_ice/src/Plugin/Commerce/FedEx/DryIcePlugin.php @@ -2,10 +2,18 @@ namespace Drupal\commerce_fedex_dry_ice\Plugin\Commerce\FedEx; +use Drupal\address\Plugin\Field\FieldType\AddressItem; use Drupal\commerce_fedex\Plugin\Commerce\FedEx\FedExPluginBase; +use Drupal\commerce_fedex\Plugin\Commerce\ShippingMethod\FedEx; +use Drupal\commerce_shipping\Entity\ShipmentInterface; use Drupal\commerce_shipping\PackageTypeManagerInterface; +use Drupal\commerce_shipping\ShipmentItem; use Drupal\Core\Form\FormStateInterface; +use Drupal\physical\Weight; use Drupal\physical\WeightUnit; +use NicholasCreativeMedia\FedExPHP\Enums\PackageSpecialServiceType; +use NicholasCreativeMedia\FedExPHP\Structs\PackageSpecialServicesRequested; +use NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -14,12 +22,15 @@ * @CommerceFedExPlugin( * id = "dry_ice", * label = @Translation("FedEx Dry Ice"), - * options_label = @Translation("Dry Ice Shipment Options") - * options_description = @Translation(" + * options_label = @Translation("Dry Ice Shipment Options"), + * options_description = @Translation("Enter your global shipping options for dry ice shipments") * ) */ class DryIcePlugin extends FedExPluginBase { + const NOT_DRY_ICE = 0; + const DRY_ICE = 1; + /** * The Package Type Manager. * @@ -144,4 +155,128 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s } } + /** + * {@inheritdoc} + */ + public function adjustPackage(RequestedPackageLineItem $package, array $shipment_items, ShipmentInterface $shipment) { + $type = $this->getType($shipment); + if (!$this->verifyPackage($shipment_items, $type)) { + throw new \Exception("Package cannot be shipped, mix of Dry Ice and non Dry Ice Items"); + } + + if ($this->isDryIceItem(reset($shipment_items), $type)) { + $special_services_requested = $package->getSpecialServicesRequested(); + if (empty($special_services_requested)) { + $special_services_requested = new PackageSpecialServicesRequested(); + } + $dry_ice_weight = new Weight($this->configuration[$type]['weight']['number'], $this->configuration[$type]['weight']['unit']); + $special_services_requested->addToSpecialServiceTypes(PackageSpecialServiceType::VALUE_DRY_ICE); + $special_services_requested->setDryIceWeight(FedEx::physicalWeightToFedex($dry_ice_weight)); + /** @var \Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageType $package_type */ + $package_type = $this->packageTypeManager->createInstance($this->configuration[$type]['package_type']); + $package->setDimensions(Fedex::packageToFedexDimensions($package_type)); + $package->setWeight(FedEx::packageTotalWeight($shipment_items, $package_type, $dry_ice_weight)); + $package->setSpecialServicesRequested($special_services_requested); + } + $package = parent::adjustPackage($package, $shipment_items, $shipment); + return $package; + } + + /** + * {@inheritdoc} + */ + public function splitPackage(array $shipment_items, ShipmentInterface $shipment) { + $type = $this->getType($shipment); + $packages = [static::NOT_DRY_ICE => [], static::DRY_ICE => []]; + foreach ($shipment_items as $shipment_item) { + if ($this->isDryIceItem($shipment_item, $type)) { + $packages[static::DRY_ICE][] = $shipment_item; + } + else { + $packages[static::NOT_DRY_ICE][] = $shipment_item; + } + } + + return $packages; + } + + /** + * Verified a package has all dry ic eor non-dry ice items. + * + * @param array $shipment_items + * The shipment items to check. + * @param string $type + * The type, 'domestic' or 'intl' depending on the shipping distance. + * + * @return bool + * True if the package is internally consistant. + */ + protected function verifyPackage(array $shipment_items, string $type) { + $dryIceBox = $this->isDryIceBox($shipment_items, $type); + $storage = \Drupal::entityTypeManager()->getStorage('commerce_order_item'); + foreach ($shipment_items as $shipment_item) { + if ($dryIceBox xor $this->isDryIceItem($shipment_item, $type)) { + return FALSE; + } + + } + return TRUE; + } + + /** + * Determine whether a droup of shipment items requires dry ice shipping. + * + * @param array $shipment_items + * The shipment items to check. + * @param string $type + * The type, 'domestic' or 'intl' depending on the shipping distance. + * + * @return bool + * True if the package needs dry ice shipping. + */ + protected function isDryIceBox(array $shipment_items, string $type) { + return $this->isDryIceItem(reset($shipment_items), $type); + } + + /** + * Determine whether a shipment item requires dry ice shipping or not. + * + * @param \Drupal\commerce_shipping\ShipmentItem $shipment_item + * The shipment item. + * @param string $type + * The type, 'domestic' or 'intl' depending on the shipping distance. + * + * @return bool + * true if the shipment item requires dry ice shipping. + */ + protected function isDryIceItem(ShipmentItem $shipment_item, string $type) { + $storage = \Drupal::entityTypeManager()->getStorage('commerce_order_item'); + /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */ + $order_item = $storage->load($shipment_item->getOrderItemId()); + $purchased_entity = $order_item->getPurchasedEntity(); + return $purchased_entity->hasField('fedex_dry_ice_' . $type) && !$purchased_entity->get('fedex_dry_ice_' . $type)->isEmpty() && $purchased_entity->get('fedex_dry_ice_' . $type)->first()->getValue()['value'] == 1; + + } + + /** + * Determine if this is a domestic or international shipment. + * + * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment + * The shipment to check. + * + * @return string + * either 'domestic' or 'intl' + */ + protected function getType(ShipmentInterface $shipment) { + + /* @var AddressItem $shipping_address */ + $shipping_address = $shipment->getShippingProfile()->get('address')->first(); + + $domestic = ($shipping_address instanceof AddressItem) + ? $shipment->getOrder()->getStore()->getAddress()->getCountryCode() == $shipping_address->getCountryCode() + : FALSE; + + return $domestic ? 'domestic' : 'intl'; + } + } diff --git a/src/Annotation/CommerceFedExPlugin.php b/src/Annotation/CommerceFedExPlugin.php index bdcdd45..f62c7f2 100644 --- a/src/Annotation/CommerceFedExPlugin.php +++ b/src/Annotation/CommerceFedExPlugin.php @@ -48,4 +48,5 @@ class CommerceFedExPlugin extends Plugin { * @ingroup plugin_translatable */ public $options_description; + } diff --git a/src/Plugin/Commerce/FedEx/FedExPluginBase.php b/src/Plugin/Commerce/FedEx/FedExPluginBase.php index 05bfcf4..907c7e8 100644 --- a/src/Plugin/Commerce/FedEx/FedExPluginBase.php +++ b/src/Plugin/Commerce/FedEx/FedExPluginBase.php @@ -2,11 +2,13 @@ namespace Drupal\commerce_fedex\Plugin\Commerce\FedEx; +use Drupal\commerce_shipping\Entity\ShipmentInterface; use Drupal\Component\Plugin\PluginBase; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; +use NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -96,4 +98,28 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s return $form; } + /** + * Adjust a package based on the items, shipment and profile. + * + * @param \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem $package + * The package to adjust. + * @param array $items + * An array of shipment items. + * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment + * The shipment. + * + * @return \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem + * The adjusted Package. + */ + public function adjustPackage(RequestedPackageLineItem $package, array $items, ShipmentInterface $shipment) { + return $package; + } + + /** + * {@inheritdoc} + */ + public function splitPackage(array $shipment_items, ShipmentInterface $shipment) { + return [$shipment_items]; + } + } diff --git a/src/Plugin/Commerce/FedEx/FedExPluginInterface.php b/src/Plugin/Commerce/FedEx/FedExPluginInterface.php index 75098d0..43df6f3 100644 --- a/src/Plugin/Commerce/FedEx/FedExPluginInterface.php +++ b/src/Plugin/Commerce/FedEx/FedExPluginInterface.php @@ -2,9 +2,11 @@ namespace Drupal\commerce_fedex\Plugin\Commerce\FedEx; +use Drupal\commerce_shipping\Entity\ShipmentInterface; use Drupal\Component\Plugin\ConfigurablePluginInterface; use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Plugin\PluginFormInterface; +use NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem; /** * Defines the base interface for FedEx Service Plugins. @@ -13,4 +15,32 @@ */ interface FedExPluginInterface extends ConfigurablePluginInterface, PluginFormInterface, PluginInspectionInterface { + /** + * Function adjustPackage. + * + * @param \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem $package + * The package to adjust. + * @param array $items + * An array of Shipment Items. + * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment + * The Shipment. + * + * @return \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem + * The Adjusted Package + */ + public function adjustPackage(RequestedPackageLineItem $package, array $items, ShipmentInterface $shipment); + + /** + * Function splitPackage. + * + * @param array $shipment_items + * An Array of shipment items. + * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment + * The Shipment. + * + * @return array + * An array of arrays of shipment items. + */ + public function splitPackage(array $shipment_items, ShipmentInterface $shipment); + } diff --git a/src/Plugin/Commerce/ShippingMethod/FedEx.php b/src/Plugin/Commerce/ShippingMethod/FedEx.php index 6885d95..0615ac9 100644 --- a/src/Plugin/Commerce/ShippingMethod/FedEx.php +++ b/src/Plugin/Commerce/ShippingMethod/FedEx.php @@ -18,18 +18,20 @@ use Drupal\commerce_shipping\ShippingRate; use Drupal\Core\Form\SubformState; use Drupal\Core\Plugin\DefaultLazyPluginCollection; +use Drupal\physical\Volume; +use Drupal\physical\VolumeUnit; use Drupal\physical\Weight as PhysicalWeight; use Drupal\physical\WeightUnit as PhysicalWeightUnits; use Drupal\physical\LengthUnit as PhysicalLengthUnits; use Drupal\Core\Form\FormStateInterface; use NicholasCreativeMedia\FedExPHP\Enums\DropoffType; +use NicholasCreativeMedia\FedExPHP\Enums\LinearUnits; use NicholasCreativeMedia\FedExPHP\Enums\PhysicalPackagingType; use NicholasCreativeMedia\FedExPHP\Enums\RateRequestType; use NicholasCreativeMedia\FedExPHP\Services\RateService; use NicholasCreativeMedia\FedExPHP\Structs\Address; use NicholasCreativeMedia\FedExPHP\Structs\Dimensions; use NicholasCreativeMedia\FedExPHP\Structs\Money; -use NicholasCreativeMedia\FedExPHP\Structs\PackageSpecialServicesRequested; use NicholasCreativeMedia\FedExPHP\Structs\Party; use NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem; use NicholasCreativeMedia\FedExPHP\Structs\RequestedShipment; @@ -217,10 +219,7 @@ public function defaultConfiguration() { public function buildConfigurationForm(array $form, FormStateInterface $form_state) { /* @todo Remove hack when commerce issue #2859423 is resolved */ - if ($this->configuration == $this->defaultConfiguration()) { - $this->fixConfiguration($form_state); - } - + // $this->fixConfiguration($form_state); $form = parent::buildConfigurationForm($form, $form_state); // Select all services by default. @@ -243,10 +242,10 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta '#default_value' => $this->configuration['api_information']['api_key'], ]; $form['api_information']['api_password'] = [ - '#type' => 'textfield', + '#type' => 'password', '#title' => $this->t('API password'), '#description' => $this->t('Enter your FedEx API password only if you wish to change its value.'), - '#default_value' => $this->configuration['api_information']['api_password'], + '#default_value' => '', ]; $form['api_information']['account_number'] = [ '#type' => 'number', @@ -349,9 +348,13 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta $plugin = $this->plugins->get($plugin_id); $subform = [ '#type' => 'details', - '#title' => $definition['optionsLabel']->render(), + '#title' => $definition['options_label']->render(), + '#description' => $definition['options_description']->render(), ]; $form[$plugin_id] = $plugin->buildConfigurationForm($subform, $form_state); + if ($form[$plugin_id] == $subform) { + unset($form[$plugin_id]); + } } return $form; } @@ -361,14 +364,15 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta */ public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { /* @todo Remove hack when commerce issue #2859423 is resolved */ - $this->fixConfiguration($form_state); - + // $this->fixConfiguration($form_state); parent::validateConfigurationForm($form, $form_state); foreach ($this->fedExServiceManager->getDefinitions() as $plugin_id => $definition) { - /** @var \Drupal\commerce_fedex\Plugin\Commerce\FedEx\FedExPluginInterface $plugin */ - $plugin = $this->plugins->get($plugin_id); - $plugin->validateConfigurationForm($form[$plugin_id], SubformState::createForSubform($form[$plugin_id], $form_state->getCompleteForm(), $form_state)); + if (!empty($form[$plugin_id])) { + /** @var \Drupal\commerce_fedex\Plugin\Commerce\FedEx\FedExPluginInterface $plugin */ + $plugin = $this->plugins->get($plugin_id); + $plugin->validateConfigurationForm($form[$plugin_id], SubformState::createForSubform($form[$plugin_id], $form_state->getCompleteForm(), $form_state)); + } } } @@ -377,8 +381,7 @@ public function validateConfigurationForm(array &$form, FormStateInterface $form */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { /* @todo Remove hack when commerce issue #2859423 is resolved */ - $this->fixConfiguration($form_state); - + // $this->fixConfiguration($form_state); if (!$form_state->getErrors()) { $values = $form_state->getValue($form['#parents']); @@ -402,8 +405,11 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s foreach ($this->fedExServiceManager->getDefinitions() as $plugin_id => $definition) { /** @var \Drupal\commerce_fedex\Plugin\Commerce\FedEx\FedExPluginInterface $plugin */ $plugin = $this->plugins->get($plugin_id); - $plugin->submitConfigurationForm($form[$plugin_id], SubformState::createForSubform($form[$plugin_id], $form_state->getCompleteForm(), $form_state)); + if (!empty($form[$plugin_id])) { + $plugin->submitConfigurationForm($form[$plugin_id], SubformState::createForSubform($form[$plugin_id], $form_state->getCompleteForm(), $form_state)); + } $this->configuration['plugins'][$plugin_id]['configuration'] = $plugin->getConfiguration(); + } } parent::submitConfigurationForm($form, $form_state); @@ -422,8 +428,13 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s * Whether to skip the check to log or not. */ protected function logRequest($message, $data, $level = LogLevel::INFO, $skip_config_check = FALSE) { - if ($skip_config_check || $this->configuration['options']['log']['request']) { - $this->watchdog->log($level, "$message
@rate_request", [ + /* If we are using the old configuration */ + if (is_array($this->configuration['options']['log'])) { + + } + + if ($skip_config_check || @$this->configuration['options']['log']['request']) { + $this->watchdog->log($level, "$message
@rate_request
", [ '@rate_request' => var_export($data, TRUE), ]); } @@ -481,7 +492,7 @@ public function calculateRates(ShipmentInterface $shipment) { $price = new Price((string) $cost->getAmount(), $cost->getCurrency()); if ($multiplier != 1) { - $price = $price->multiply((string)$multiplier); + $price = $price->multiply((string) $multiplier); } $price = $this->rounder->round($price, $round); @@ -543,9 +554,9 @@ public function getPlugins() { * @return array * The options list. */ - protected static function enumToList(array $enums) { + public static function enumToList(array $enums) { return array_combine($enums, array_map(function ($d) { - return ucwords(str_replace('_', ' ', $d)); + return ucwords(strtolower(str_replace('_', ' ', $d))); }, $enums)); } @@ -652,30 +663,38 @@ protected function getCleanTitle(ShipmentItem $shipment_item) { * The package line items. */ protected function getRequestedPackageLineItemsAllInOne(ShipmentInterface $shipment) { - $requested_package_line_item = new RequestedPackageLineItem(); - $shipment_title = $shipment->getTitle(); - if (!is_string($shipment_title)) { - $shipment_title = $shipment_title->render(); - } + $packages = $this->splitPackages($shipment); - $requested_package_line_item - ->setSequenceNumber(1) - ->setGroupPackageCount(1) - ->setWeight($this->physicalWeightToFedex($shipment->getWeight())) - ->setDimensions($this->packageToFedexDimensions($shipment->getPackageType())) - ->setPhysicalPackaging(PhysicalPackagingType::VALUE_BOX) - ->setItemDescription($shipment_title) - ->setSpecialServicesRequested(new PackageSpecialServicesRequested()); + $requested_package_line_items = []; - if ($this->configuration['options']['insurance']) { - $requested_package_line_item->setInsuredValue(new Money( - $shipment->getTotalDeclaredValue()->getCurrencyCode(), - $shipment->getTotalDeclaredValue()->getNumber() - )); + foreach ($packages as $delta => $package) { + $requested_package_line_item = new RequestedPackageLineItem(); + $shipment_title = $shipment->getTitle(); + + if (!is_string($shipment_title)) { + $shipment_title = $shipment_title->render(); + } + + $requested_package_line_item + ->setSequenceNumber($delta + 1) + ->setGroupPackageCount(1) + ->setWeight(static::packageTotalWeight($package, $shipment->getPackageType())) + ->setDimensions($this->packageToFedexDimensions($shipment->getPackageType())) + ->setPhysicalPackaging(PhysicalPackagingType::VALUE_BOX) + ->setItemDescription($shipment_title); + + if ($this->configuration['options']['insurance']) { + $requested_package_line_item->setInsuredValue(new Money( + $shipment->getTotalDeclaredValue()->getCurrencyCode(), + $shipment->getTotalDeclaredValue()->getNumber() + )); + } + + $requested_package_line_items[] = $this->adjustPackage($requested_package_line_item, $package, $shipment); } - return [$requested_package_line_item]; + return $requested_package_line_items; } /** @@ -688,39 +707,18 @@ protected function getRequestedPackageLineItemsAllInOne(ShipmentInterface $shipm * The package line items. */ protected function getRequestedPackageLineItemsCalculate(ShipmentInterface $shipment) { - $package_volume = $this->getPackageVolume($shipment->getPackageType()); - $total_volume = $this->getShipmentTotalVolume($shipment); - - $count = ($total_volume == 0 || $total_volume < $package_volume) - ? 1 - : ceil($total_volume / $package_volume); - - $package_weight = $shipment->getWeight()->divide((string) $count); - - $shipment_title = $shipment->getTitle(); - if (!is_string($shipment_title)) { - $shipment_title = $shipment_title->render(); - } - - $requested_package_line_item = new RequestedPackageLineItem(); - - $requested_package_line_item - ->setGroupNumber(1) - ->setGroupPackageCount($count) - ->setWeight($this->physicalWeightToFedex($package_weight)) - ->setDimensions($this->packageToFedexDimensions($shipment->getPackageType())) - ->setPhysicalPackaging(PhysicalPackagingType::VALUE_BOX) - ->setItemDescription($shipment_title) - ->setSpecialServicesRequested(new PackageSpecialServicesRequested()); - - if ($this->configuration['options']['insurance']) { - $requested_package_line_item->setInsuredValue(new Money( - $shipment->getTotalDeclaredValue()->getCurrencyCode(), - $shipment->getTotalDeclaredValue()->getNumber() - )); + $requested_package_line_items = $this->getRequestedPackageLineItemsAllInOne($shipment); + $packages = $this->splitPackages($shipment); + foreach ($requested_package_line_items as &$requested_package_line_item) { + /** @var \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem $requested_package_line_item */ + $count = static::calculatePackageCount($requested_package_line_item, $packages[$requested_package_line_item->getSequenceNumber() - 1]); + if ($count) { + $requested_package_line_item->setGroupPackageCount($count); + $requested_package_line_item->getWeight()->setValue($requested_package_line_item->getWeight()->getValue() / $count); + } } - return [$requested_package_line_item]; + return $requested_package_line_items; } /** @@ -734,18 +732,27 @@ protected function getRequestedPackageLineItemsCalculate(ShipmentInterface $ship */ protected function getRequestedPackageLineItemsIndividual(ShipmentInterface $shipment) { $requested_package_line_items = []; - + $weightUnits = ''; foreach ($shipment->getItems() as $delta => $shipment_item) { + $qty = $shipment_item->getQuantity(); $requested_package_line_item = new RequestedPackageLineItem(); + if ($weightUnits == '') { + /* All packages must have the same Weight Unit */ + $weightUnits = $shipment_item->getWeight()->getUnit(); + } + + $weight = $shipment_item + ->getWeight() + ->divide($qty) + ->convert($weightUnits); $requested_package_line_item ->setSequenceNumber($delta + 1) - ->setGroupPackageCount(1) - ->setWeight($this->physicalWeightToFedex($shipment_item->getWeight())) + ->setGroupPackageCount($qty) + ->setWeight($this->physicalWeightToFedex($weight)) ->setDimensions($this->packageToFedexDimensions($shipment->getPackageType())) ->setPhysicalPackaging(PhysicalPackagingType::VALUE_BOX) - ->setItemDescription($this->getCleanTitle($shipment_item)) - ->setSpecialServicesRequested(new PackageSpecialServicesRequested()); + ->setItemDescription($this->getCleanTitle($shipment_item)); if ($this->configuration['options']['insurance']) { $requested_package_line_item->setInsuredValue(new Money( @@ -753,7 +760,7 @@ protected function getRequestedPackageLineItemsIndividual(ShipmentInterface $shi $shipment_item->getDeclaredValue()->getNumber() )); } - + $this->adjustPackage($requested_package_line_item, [$shipment_item], $shipment); $requested_package_line_items[] = $requested_package_line_item; } @@ -772,9 +779,10 @@ protected function getRequestedPackageLineItemsIndividual(ShipmentInterface $shi protected function getFedExShipment(ShipmentInterface $shipment) { $line_items = $this->getRequestedPackageLineItems($shipment); - $count = ($this->configuration['options']['packaging'] == static::PACKAGE_CALCULATE) - ? $line_items[0]->getGroupPackageCount() - : count($line_items); + $count = 0; + foreach ($line_items as $line_item) { + $count += $line_item->getGroupPackageCount(); + } /** @var \Drupal\address\AddressInterface $recipient_address */ $recipient_address = $shipment->getShippingProfile()->get('address')->first(); @@ -783,7 +791,6 @@ protected function getFedExShipment(ShipmentInterface $shipment) { $fedex_shipment = new RequestedShipment(); $fedex_shipment - ->setTotalWeight($this->physicalWeightToFedex($shipment->getWeight())) ->setShipper($this->getAddressForFedEx($shipper_address)) ->setRecipient($this->getAddressForFedEx($recipient_address)) ->setRequestedPackageLineItems($line_items) @@ -803,6 +810,51 @@ protected function getFedExShipment(ShipmentInterface $shipment) { return $fedex_shipment; } + /** + * Queries plugins to split shipments into fedex packages. + * + * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment + * The shipment to split. + * + * @return array + * An array of arrays or shipment items. + */ + protected function splitPackages(ShipmentInterface $shipment) { + $packages = [$shipment->getItems()]; + foreach ($this->fedExServiceManager->getDefinitions() as $plugin_id => $definition) { + /** @var \Drupal\commerce_fedex\Plugin\Commerce\FedEx\FedExPluginInterface $plugin */ + $plugin = $this->plugins->get($plugin_id); + $new_packages = []; + foreach ($packages as $package) { + $new_packages = array_merge($new_packages, $plugin->splitPackage($package, $shipment)); + } + $packages = array_filter($new_packages); + } + return $packages; + } + + /** + * Query plugins for package adjustments. + * + * @param \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem $requested_package_line_item + * Te package line item to be adjusted. + * @param array $shipment_items + * The shipment items in the package. + * @param \Drupal\commerce_shipping\Entity\ShipmentInterface $shipment + * The full shipment. + * + * @return \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem + * The adjusted line item. + */ + protected function adjustPackage(RequestedPackageLineItem $requested_package_line_item, array $shipment_items, ShipmentInterface $shipment) { + foreach ($this->fedExServiceManager->getDefinitions() as $plugin_id => $definition) { + /** @var \Drupal\commerce_fedex\Plugin\Commerce\FedEx\FedExPluginInterface $plugin */ + $plugin = $this->plugins->get($plugin_id); + $requested_package_line_item = $plugin->adjustPackage($requested_package_line_item, $shipment_items, $shipment); + } + return $requested_package_line_item; + } + /** * Gets a rate request object for FedEx. * @@ -826,6 +878,64 @@ protected function getRateRequest(RateService $rate_service, ShipmentInterface $ return $rate_request; } + /** + * Return the total volume of an array of shipment items in a given unit. + * + * @param array $shipment_items + * The array of shipment items. + * @param string $volume_unit + * The volume unit. + * + * @return \Drupal\physical\Volume + * The total Volume. + * + * @throws \Exception + */ + protected static function getPackageTotalVolume(array $shipment_items, string $volume_unit = VolumeUnit::CUBIC_CENTIMETER) { + switch ($volume_unit) { + case VolumeUnit::CUBIC_CENTIMETER: + $linear_unit = PhysicalLengthUnits::CENTIMETER; + break; + + case VolumeUnit::CUBIC_INCH: + $linear_unit = PhysicalLengthUnits::INCH; + break; + + default: + throw new \Exception("Invalid Units"); + } + $order_item_storage = \Drupal::entityTypeManager()->getStorage('commerce_order_item'); + $order_item_ids = []; + foreach ($shipment_items as $shipment_item) { + $order_item_ids[] = $shipment_item->getOrderItemId(); + } + + $order_items = $order_item_storage->loadMultiple($order_item_ids); + + $total_volume = 0; + foreach ($order_items as $order_item) { + /** @var \Drupal\commerce_order\Entity\OrderItem $order_item */ + /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $purchased_entity */ + $purchased_entity = $order_item->getPurchasedEntity(); + + if ($purchased_entity->hasField('dimensions') && !$purchased_entity->get('dimensions')->isEmpty()) { + /** @var \Drupal\physical\Plugin\Field\FieldType\DimensionsItem $dimensions */ + $dimensions = $purchased_entity->get('dimensions')->first(); + + $volume = $dimensions + ->getHeight() + ->convert($linear_unit) + ->multiply($dimensions->getWidth()->convert($linear_unit)->getNumber()) + ->multiply($dimensions->getLength()->convert($linear_unit)->getNumber()) + ->getNumber(); + + $total_volume += (float) $volume * $order_item->getQuantity(); + } + } + + return new Volume((string) $total_volume, $volume_unit); + } + /** * Gets the shipment's total volume in the same units as the package type. * @@ -897,7 +1007,7 @@ protected function isConfigured() { * * @throws \Exception */ - protected function physicalWeightToFedex(PhysicalWeight $weight) { + public static function physicalWeightToFedex(PhysicalWeight $weight) { if (!$weight instanceof PhysicalWeight) { throw new \Exception("Invalid units for weight"); } @@ -941,7 +1051,7 @@ protected function physicalWeightToFedex(PhysicalWeight $weight) { * * @throws \Exception */ - protected function packageToFedexDimensions(PackageTypeInterface $package) { + public static function packageToFedexDimensions(PackageTypeInterface $package) { $length = $package->getLength(); $width = $package->getWidth(); $height = $package->getHeight(); @@ -1044,4 +1154,93 @@ private function fixConfiguration(FormStateInterface $form_state) { } } + /** + * Function packageTotalWeight. + * + * @param array $shipment_items + * An array of shipment items. + * @param \Drupal\commerce_shipping\Plugin\Commerce\PackageType\PackageTypeInterface $package_type + * The commerce_shipping package type. + * @param \Drupal\physical\Weight|null $adjustment + * Any weight adjustment to add or subtract. + * + * @return \NicholasCreativeMedia\FedExPHP\Structs\Weight + * The total weight of the package. + */ + public static function packageTotalWeight(array $shipment_items, PackageTypeInterface $package_type, $adjustment = NULL) { + + if (empty($shipment_items)) { + return new FedexWeight('LB', 0); + } + + $storage = \Drupal::entityTypeManager()->getStorage('commerce_order_item'); + $unit = $storage->load(reset($shipment_items)->getOrderItemId())->getPurchasedEntity()->weight->first()->toMeasurement()->getUnit(); + $total_weight = new PhysicalWeight("0", $unit); + foreach ($shipment_items as $shipment_item) { + /* @var ShipmentItem $shipment_item */ + /* @var \Drupal\commerce_order\Entity\OrderItem $order_item */ + $order_item = $storage->load($shipment_item->getOrderItemId()); + $purchased_entity = $order_item->getPurchasedEntity(); + $order_item_weight = $purchased_entity->weight->first()->toMeasurement() + ->convert($unit) + ->multiply($order_item->getQuantity()); + $total_weight = $total_weight->add($order_item_weight); + } + $total_weight = $total_weight->add($package_type->getWeight()->convert($unit)); + if ($adjustment) { + $total_weight = $total_weight->add($adjustment->convert($unit)); + } + return static::physicalWeightToFedex($total_weight); + } + + /** + * Determines a package count given package and item volumes. + * + * @param \NicholasCreativeMedia\FedExPHP\Structs\RequestedPackageLineItem $requested_package_line_item + * The package line item containing the fedex package dimensions. + * @param array $shipment_items + * The array of shipment items. + * + * @return bool|float + * The number of needed packages, or False on error. + */ + public static function calculatePackageCount(RequestedPackageLineItem $requested_package_line_item, array $shipment_items) { + $package_volume = static::getFedexPackageVolume($requested_package_line_item->getDimensions()); + $items_volume = static::getPackageTotalVolume($shipment_items, $package_volume->getUnit()); + + if ($package_volume->getNumber() != 0) { + return ceil($items_volume->getNumber() / $package_volume->getNumber()); + } + return FALSE; + + } + + /** + * Determine the package volume from a fedex package dimensions item. + * + * @param \NicholasCreativeMedia\FedExPHP\Structs\Dimensions $dimensions + * The fexed package dimensions item. + * + * @return \Drupal\physical\Volume + * The total package volume. + * + * @throws \Exception + */ + public static function getFedexPackageVolume(Dimensions $dimensions) { + $volume_value = $dimensions->getHeight() * $dimensions->getLength() * $dimensions->getWidth(); + switch ($dimensions->getUnits()) { + case LinearUnits::VALUE_CM: + $volume_units = VolumeUnit::CUBIC_CENTIMETER; + break; + + case LinearUnits::VALUE_IN: + $volume_units = VolumeUnit::CUBIC_INCH; + break; + + default: + throw new \Exception("Invalid Dimensions"); + } + return new Volume((string) $volume_value, $volume_units); + } + } diff --git a/tests/src/Unit/FedExRequestTest.php b/tests/src/Unit/FedExRequestTest.php new file mode 100644 index 0000000..82422ac --- /dev/null +++ b/tests/src/Unit/FedExRequestTest.php @@ -0,0 +1,89 @@ +request = new FedExRequest(); + $this->configuration = [ + 'api_information' => [ + 'api_key' => 'testkey', + 'api_password' => 'testpass', + 'account_number' => '1234567', + 'meter_number' => '9876543', + 'mode' => 'test', + ], + ]; + } + + /** + * @covers ::getRateRequest + */ + public function testRateRequest() { + + $rate_request = $this->request->getRateRequest($this->configuration); + $this->assertInstanceOf("\NicholasCreativeMedia\FedExPHP\Structs\RateRequest", $rate_request); + + $client_detail = $rate_request->getClientDetail(); + $this->assertInstanceOf("\NicholasCreativeMedia\FedExPHP\Structs\ClientDetail", $client_detail); + $this->assertEquals('1234567', $client_detail->getAccountNumber()); + $this->assertEquals('9876543', $client_detail->getMeterNumber()); + + $web_authentication_detail = $rate_request->getWebAuthenticationDetail(); + $this->assertInstanceOf("\NicholasCreativeMedia\FedExPHP\Structs\WebAuthenticationDetail", $web_authentication_detail); + + $user_credential = $web_authentication_detail->getUserCredential(); + $this->assertInstanceOf("\NicholasCreativeMedia\FedExPHP\Structs\WebAuthenticationCredential", $user_credential); + + $this->assertEquals('testkey', $user_credential->getKey()); + $this->assertEquals('testpass', $user_credential->getPassword()); + } + + /** + * @covers ::getRateService + */ + public function testRateService() { + $rate_service = $this->request->getRateService($this->configuration); + $this->assertInstanceOf("\NicholasCreativeMedia\FedExPHP\Services\RateService", $rate_service); + + /** @var \NicholasCreativeMedia\FedExPHP\Structs\VersionId $version */ + $version = $rate_service->version; + $this->assertInstanceOf("\NicholasCreativeMedia\FedExPHP\Structs\VersionId", $version); + + $this->assertEquals(static::RATE_SERVICE_SERVICE_ID, $version->getServiceId()); + $this->assertEquals(static::RATE_SERVICE_MAJOR_VERSION, $version->getMajor()); + } + +}