diff --git a/api/assets/emails/html/dewar-dispatch-lite.html b/api/assets/emails/html/dewar-dispatch-lite.html new file mode 100644 index 000000000..ad3b16b2b --- /dev/null +++ b/api/assets/emails/html/dewar-dispatch-lite.html @@ -0,0 +1,30 @@ +

Dewar ready to leave diamond

+ +
+ Goods Handling, please arrange dispatch of the following Dewar. +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Proposal
Air Waybill
Dewar Facility Code
Dewar Barcode
Current Location
diff --git a/api/config_sample.php b/api/config_sample.php index 1267f8fe3..69bf049d7 100644 --- a/api/config_sample.php +++ b/api/config_sample.php @@ -264,6 +264,7 @@ # Shipping service details $use_shipping_service = null; $use_shipping_service_incoming_shipments = null; + $use_shipping_service_redirect = null; $shipping_service_api_url = null; $shipping_service_api_user = null; $shipping_service_api_password = null; diff --git a/api/index.php b/api/index.php index 2095ba7cc..a067449d5 100644 --- a/api/index.php +++ b/api/index.php @@ -70,7 +70,7 @@ function setupApplication($mode): Slim global $motd, $authentication_type, $cas_url, $cas_sso, $sso_url, $package_description, $facility_courier_countries, $facility_courier_countries_nde, $dhl_enable, $dhl_link, $scale_grid, $scale_grid_end_date, $preset_proposal, $timezone, - $valid_components, $enabled_container_types, $ifsummary, $synchweb_version; + $valid_components, $enabled_container_types, $ifsummary, $synchweb_version, $shipping_service_app_url, $use_shipping_service_redirect; $app->contentType('application/json'); $options = $app->container['options']; $app->response()->body(json_encode(array( @@ -91,7 +91,8 @@ function setupApplication($mode): Slim 'valid_components' => $valid_components, 'enabled_container_types' => $enabled_container_types, 'ifsummary' => $ifsummary, - 'synchweb_version' => $synchweb_version + 'synchweb_version' => $synchweb_version, + 'shipping_service_app_url' => $use_shipping_service_redirect ? $shipping_service_app_url : null, ))); }); return $app; diff --git a/api/src/Page/Shipment.php b/api/src/Page/Shipment.php index ca95ef552..459e4bdc6 100644 --- a/api/src/Page/Shipment.php +++ b/api/src/Page/Shipment.php @@ -924,6 +924,60 @@ function _transfer_dewar() $this->_output(1); } + function _dispatch_dewar_shipment_request($dewar) + { + if (!is_null($dewar['EXTERNALSHIPPINGIDFROMSYNCHROTRON'])) { + return $dewar['EXTERNALSHIPPINGIDFROMSYNCHROTRON']; + } + $server_port = ($_SERVER['SERVER_PORT']==='443') ? '' : ":{$_SERVER['SERVER_PORT']}"; + $shipment_request_info = array( + "proposal" => $dewar['PROPOSAL'], + "external_id" => (int) $dewar['DEWARID'], + "origin_url" => "https://{$_SERVER['SERVER_NAME']}{$server_port}/shipments/sid/{$dewar['SHIPPINGID']}", + "packages" => [ + [ + "external_id" => (int) $dewar['DEWARID'], + "shippable_item_type" => "CRYOGENIC_DRY_SHIPPER_CASE", + "line_items" => [ + [ + "shippable_item_type" => "CRYOGENIC_DRY_SHIPPER", + "quantity" => 1 + ], + [ + "shippable_item_type" => "SHELVED_UNI_PUCK_SHIPPING_CANE", + "quantity" => 1 + ], + ] + ] + ] + ); + if ((int) $dewar['NUM_PUCKS'] > 0) { + array_push( + $shipment_request_info["packages"][0]["line_items"], + [ + "shippable_item_type"=> "UNI_PUCK", + "quantity" => (int) $dewar['NUM_PUCKS'] + ] + ); + } + if ((int) $dewar['NUM_SAMPLES'] > 0) { + array_push( + $shipment_request_info["packages"][0]["line_items"], + [ + "shippable_item_type"=> "SPINE_SAMPLE_HOLDER", + "quantity" => (int) $dewar['NUM_SAMPLES'] + ] + ); + } + $response = $this->shipping_service->create_shipment_request($shipment_request_info); + $external_shipping_id = $response['shipmentRequestId']; + $this->db->pq( + "UPDATE dewar SET externalShippingIdFromSynchrotron=:1 WHERE dewarid=:2", + array($external_shipping_id, $dewar['DEWARID']) + ); + return $external_shipping_id; + } + function _dispatch_dewar_in_shipping_service($dispatch_info, $dewar) { global $facility_company; @@ -1000,6 +1054,8 @@ function _dispatch_dewar() global $dispatch_email_intl; global $use_shipping_service; global $shipping_service_links_in_emails; + global $use_shipping_service_redirect; + global $shipping_service_app_url; // Variable to store where the dewar is (Synchrotron or eBIC building) // Could map this to dewar storage locations in ISPyB to make more generic...? $dispatch_from_location = 'Synchrotron'; @@ -1014,10 +1070,12 @@ function _dispatch_dewar() } $dew = $this->db->pq( - "SELECT d.dewarid, d.barcode, d.storagelocation, d.dewarstatus, s.shippingid, CONCAT(p.proposalcode, p.proposalnumber) as proposal + "SELECT d.dewarid, d.barcode, d.storagelocation, d.dewarstatus, d.externalShippingIdFromSynchrotron, s.shippingid, CONCAT(p.proposalcode, p.proposalnumber) as proposal, count(distinct c.containerId) as num_pucks, count(b.blsampleId) as num_samples FROM dewar d INNER JOIN shipping s ON s.shippingid = d.shippingid INNER JOIN proposal p ON p.proposalid = s.proposalid + LEFT JOIN container c on c.dewarid = d.dewarid + LEFT JOIN BLSample b on b.containerId = c.containerId WHERE d.dewarid=:1 and p.proposalid=:2", array($this->arg('DEWARID'), $this->proposalid) ); @@ -1032,7 +1090,7 @@ function _dispatch_dewar() // If no location specified (i.e. deleted), then read from dewar transport history. // If no dewar transport history fall back to dewar location // We still update history based on provided location to record action from user - $dewar_location = $this->has_arg('LOCATION)') ? $this->arg('LOCATION') : ""; + $dewar_location = $this->has_arg('LOCATION') ? $this->arg('LOCATION') : ""; if (empty($dewar_location)) { // What was the last history entry for this dewar? @@ -1080,21 +1138,42 @@ function _dispatch_dewar() if (Utils::getValueOrDefault($use_shipping_service) && in_array($country, $facility_courier_countries)) { if ($terms_accepted) { - try { - $shipment_id = $this->_dispatch_dewar_in_shipping_service($data, $dew); + if (Utils::getValueOrDefault($use_shipping_service_redirect)) { + try { + $shipment_id = $this->_dispatch_dewar_shipment_request($dew); + } catch (Exception $e) { + error_log("Error returned from shipping service: " . $e . "\nDewar data: " . json_encode($dew)); + $error_response = json_decode($e->getMessage()); + $this->_error($error_response->content->detail, $error_response->status); + } + if (Utils::getValueOrDefault($shipping_service_links_in_emails)) { + $data['AWBURL'] = "{$shipping_service_app_url}/shipment-requests/{$shipment_id}/outgoing"; + } + } else { + try { + $shipment_id = $this->_dispatch_dewar_in_shipping_service($data, $dew); + } catch (Exception $e) { + error_log($e); + $data['AWBURL'] = ""; + } if (Utils::getValueOrDefault($shipping_service_links_in_emails)) { $data['AWBURL'] = $this->shipping_service->get_awb_pdf_url($shipment_id); } - } catch (Exception $e) { - error_log($e); - $data['AWBURL'] = ""; } } } # Prepare e-mail response for dispatch request - $subject_line = '*** Dispatch requested for Dewar ' . $dew['BARCODE'] . ' from ' . $dispatch_from_location . ' - Pickup Date: ' . $this->args['DELIVERYAGENT_SHIPPINGDATE'] . ' ***'; - $email = new Email('dewar-dispatch', $subject_line); + $use_dispatch_lite_template = ( + Utils::getValueOrDefault($use_shipping_service) + && in_array($country, $facility_courier_countries) + && $terms_accepted + && Utils::getValueOrDefault($use_shipping_service_redirect) + ); + $subject_pickup_date = $use_dispatch_lite_template ? '' : ' - Pickup Date: ' . $this->args['DELIVERYAGENT_SHIPPINGDATE']; + $subject_line = '*** Dispatch requested for Dewar ' . $dew['BARCODE'] . ' from ' . $dispatch_from_location . $subject_pickup_date . ' ***'; + $email_template = $use_dispatch_lite_template ? 'dewar-dispatch-lite' : 'dewar-dispatch'; + $email = new Email($email_template, $subject_line); // If a local contact is given, try to find their email address // First try LDAP, if unsuccessful look at the ISPyB person record for a matching staff user @@ -1120,6 +1199,7 @@ function _dispatch_dewar() $data['LOCALCONTACT'] = $local_contact; if (!array_key_exists('LCEMAIL', $data)) $data['LCEMAIL'] = ''; + $data['BARCODE'] = $dew['BARCODE']; $email->data = $data; if ($country != $facility_country && !is_null($dispatch_email_intl)) { @@ -1343,7 +1423,7 @@ function _get_dewars() $order = $cols[$this->arg('sort_by')] . ' ' . $dir; } - $dewars = $this->db->paginate("SELECT CONCAT(p.proposalcode, p.proposalnumber) as prop, CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number) as firstexperiment, r.labcontactid, se.beamlineoperator as localcontact, se.beamlinename, TO_CHAR(se.startdate, 'HH24:MI DD-MM-YYYY') as firstexperimentst, d.firstexperimentid, s.shippingid, s.shippingname, d.facilitycode, count(c.containerid) as ccount, (case when se.visit_number > 0 then (CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number)) else '' end) as exp, d.code, d.barcode, d.storagelocation, d.dewarstatus, d.dewarid, d.trackingnumbertosynchrotron, d.trackingnumberfromsynchrotron, s.deliveryagent_agentname, d.weight, d.deliveryagent_barcode, GROUP_CONCAT(c.code SEPARATOR ', ') as containers, s.sendinglabcontactid, s.returnlabcontactid, pe.givenname, pe.familyname + $dewars = $this->db->paginate("SELECT CONCAT(p.proposalcode, p.proposalnumber) as prop, CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number) as firstexperiment, r.labcontactid, se.beamlineoperator as localcontact, se.beamlinename, TO_CHAR(se.startdate, 'HH24:MI DD-MM-YYYY') as firstexperimentst, d.firstexperimentid, s.shippingid, s.shippingname, d.facilitycode, count(c.containerid) as ccount, (case when se.visit_number > 0 then (CONCAT(p.proposalcode, p.proposalnumber, '-', se.visit_number)) else '' end) as exp, d.code, d.barcode, d.storagelocation, d.dewarstatus, d.dewarid, d.trackingnumbertosynchrotron, d.trackingnumberfromsynchrotron, d.externalShippingIdFromSynchrotron, s.deliveryagent_agentname, d.weight, d.deliveryagent_barcode, GROUP_CONCAT(c.code SEPARATOR ', ') as containers, s.sendinglabcontactid, s.returnlabcontactid, pe.givenname, pe.familyname FROM dewar d LEFT OUTER JOIN container c ON c.dewarid = d.dewarid INNER JOIN shipping s ON d.shippingid = s.shippingid diff --git a/api/src/Shipment/ShippingService.php b/api/src/Shipment/ShippingService.php index 647fba717..a43e90363 100644 --- a/api/src/Shipment/ShippingService.php +++ b/api/src/Shipment/ShippingService.php @@ -151,4 +151,14 @@ function get_awb_pdf_url($shipment_id) { return $this->shipping_app_url . '/shipments/' . $shipment_id . '/awb'; } + + function create_shipment_request($shipment_request_data) + { + return $this->_send_request( + $this->shipping_api_url . '/shipment_requests/', + "POST", + $shipment_request_data, + 201 + ); + } } diff --git a/client/src/js/modules/shipment/models/dispatch.js b/client/src/js/modules/shipment/models/dispatch.js index bd4652cc0..36baa84dc 100644 --- a/client/src/js/modules/shipment/models/dispatch.js +++ b/client/src/js/modules/shipment/models/dispatch.js @@ -16,7 +16,9 @@ define(['backbone'], function(Backbone) { }, VISIT: { - required: true, + required: function() { + return this.visitRequired + }, pattern: 'visit', }, @@ -108,6 +110,7 @@ define(['backbone'], function(Backbone) { courierDetailsRequired: false, // We want to set this default to false unless 'DELIVERYAGENT_AGENTCODE' has a value in the shipment model postCodeRequired: false, + visitRequired: true, }) }) diff --git a/client/src/js/modules/shipment/views/dispatch.js b/client/src/js/modules/shipment/views/dispatch.js index cf74fb433..977a5f746 100644 --- a/client/src/js/modules/shipment/views/dispatch.js +++ b/client/src/js/modules/shipment/views/dispatch.js @@ -60,7 +60,8 @@ define(['marionette', 'views/form', 'click @ui.facc': 'showTerms', 'blur @ui.postCode': 'stripPostCode', 'blur @ui.addr': 'formatAddress', - 'change @ui.country': 'checkPostCodeRequired' + 'change @ui.country': 'checkPostCodeRequired', + 'change @ui.dispatchCountry': 'showDispatchForm' }, ui: { @@ -80,17 +81,24 @@ define(['marionette', 'views/form', ph: 'input[name=PHONENUMBER]', lab: 'input[name=LABNAME]', + submit: 'button[name=submit]', + facc: 'a.facc', accountNumber: 'input[NAME=DELIVERYAGENT_AGENTCODE]', courier: 'input[name=DELIVERYAGENT_AGENTNAME]', courierDetails: '.courierDetails', + dispatchDetails: '.dispatchDetails', facilityCourier: '.facilityCourier', awbNumber: 'input[name=AWBNUMBER]', useAnotherCourierAccount: 'input[name=USE_ANOTHER_COURIER_ACCOUNT]', - dispatchState: '.dispatch-state' + dispatchState: '.dispatch-state', + + dispatchCountry: 'select[name=DISPATCHCOUNTRY]', + courierSection: '.courierSection' }, labContactCountry : null, + dispatchCountry: null, templateHelpers: function() { return { @@ -112,6 +120,18 @@ define(['marionette', 'views/form', success: function() { app.trigger('shipment:show', this.getOption('dewar').get('SHIPPINGID')) + if ( + app.options.get("shipping_service_app_url") + && (Number(this.terms.get('ACCEPTED')) === 1) // terms.ACCPETED could be undefined, 1, or "1" + && app.options.get("facility_courier_countries").includes(this.dispatchCountry) + ) { + this.getOption('dewar').fetch().done((dewar) => { + const external_id = dewar.EXTERNALSHIPPINGIDFROMSYNCHROTRON; + window.location.assign( + `${app.options.get("shipping_service_app_url")}/shipment-requests/${external_id}/outgoing` + ) + }) + } }, failure: function() { @@ -120,6 +140,7 @@ define(['marionette', 'views/form', onRender: function() { this.date('input[name=DELIVERYAGENT_SHIPPINGDATE]') + this.$el.hide() var d = new Date() var today = (d.getDate() < 10 ? '0'+d.getDate() : d.getDate()) + '-' + (d.getMonth() < 9 ? '0'+(d.getMonth()+1) : d.getMonth()+1) + '-' + d.getFullYear() @@ -147,9 +168,20 @@ define(['marionette', 'views/form', doOnRender: function() { this.ui.exp.html(this.visits.opts()).val(this.model.get('VISIT')) this.updateLC() + this.populateDispatchCountries() this.populateCountries() this.stripPostCode() this.formatAddress() + this.$el.show() + this.hideDispatchForm(); + }, + + populateDispatchCountries: function () { + const dispatchCountryOptions = [...app.options.get("facility_courier_countries"), 'Other'] + .map((country) => ``) + .join(""); + this.ui.dispatchCountry.html(dispatchCountryOptions); + this.ui.dispatchCountry.val(''); }, populateCountries: function() { @@ -240,6 +272,28 @@ define(['marionette', 'views/form', } }, + hideDispatchForm: function () { + this.ui.courierSection.hide(); + this.ui.dispatchDetails.hide(); + this.ui.submit.hide(); + }, + + showDispatchForm: function() { + this.dispatchCountry = this.ui.dispatchCountry.val() + this.ui.courierSection.show(); + this.ui.dispatchDetails.show(); + this.ui.submit.show(); + if ( + this.terms.get("ACCEPTED") + && app.options.get("shipping_service_app_url") + && app.options.get("facility_courier_countries").includes(this.dispatchCountry) + ){ + this.model.visitRequired = false + this.ui.dispatchDetails.hide() + this.ui.submit.text("Proceed") + } + }, + showTerms: function() { var terms = new TCDialog({ model: this.terms }) this.listenTo(terms, 'terms:accepted', this.termsAccepted, this) @@ -266,6 +320,14 @@ define(['marionette', 'views/form', this.ui.courierDetails.hide() this.ui.facilityCourier.show() this.model.courierDetailsRequired = false + if ( + app.options.get("shipping_service_app_url") + && app.options.get("facility_courier_countries").includes(this.ui.dispatchCountry.val()) + ){ + this.model.visitRequired = false + this.ui.dispatchDetails.hide() + this.ui.submit.text("Proceed") + } }, checkPostCodeRequired: function() { diff --git a/client/src/js/templates/shipment/dewarlistrow.html b/client/src/js/templates/shipment/dewarlistrow.html index 47f737029..1be50e6e2 100644 --- a/client/src/js/templates/shipment/dewarlistrow.html +++ b/client/src/js/templates/shipment/dewarlistrow.html @@ -15,7 +15,12 @@ Add Container <% if (STORAGELOCATION != 'stores-out') { %> - Dispatch Dewar + <% if (app.options.get("shipping_service_app_url") && EXTERNALSHIPPINGIDFROMSYNCHROTRON) { %> + <% const link = `${app.options.get("shipping_service_app_url")}/shipment-requests/${EXTERNALSHIPPINGIDFROMSYNCHROTRON}/outgoing` %> + Dispatch Dewar + <% } else { %> + Dispatch Dewar + <% } %> Transfer Dewar <% } %> diff --git a/client/src/js/templates/shipment/dispatch.html b/client/src/js/templates/shipment/dispatch.html index b21cc8eec..d8e4cd634 100644 --- a/client/src/js/templates/shipment/dispatch.html +++ b/client/src/js/templates/shipment/dispatch.html @@ -23,6 +23,69 @@

Request Dewar Dispatch

<%-DEWAR.FACILITYCODE%> <% } %> + +
  • + + +
  • +
  • + + +
  • + + + + - + -