Jump to content
torinwalker

New UPS XML Shipping Module available

Recommended Posts

Stuart, I'm sorry but I cannot see anything faulty with this code:

$xmlResult = $this->_post($this->protocol, $this->host, $this->port, $this->path, $this->version, $this->timeout, $xmlRequest);

This line calls the function _post with all the parameters that are needed for _post:

for $protocol : $this->protocol

for $host : $this->host

for $port : $this->port

for $path : $this->path

for $version : $this->version etcetera

 

I think it might be more insightful to have the response of the UPS server after the call "time in transit" when it went wrong. Maybe it returned something that was not anticipated in the function and therefore crashed it. Just my two cents of course.

 

Not an easy job debugging something like this when it is not consistent....

Share this post


Link to post
Share on other sites

Jan;

 

You're correct, there is nothing wrong with that line (which is the "post" for the Rates and Services API).

 

Where I think the problem might be occuring is in the "post" for the Time In Transit API, which reads

$xmlTransitResult = $this->_post($this->protocol, $this->host, $this->port, $this->transitpath, $this->transitversion, $this->timeout, $xmlTransitRequest);
return $this->_transitparseResult($xmlTransitResult);

The variables $this->transitpath, $this->transitversion, $xmlTransitRequest

are not identified in the one instance of "function _post" which exists in the upsxml.php file. That instance reads

function _post($protocol, $host, $port, $path, $version, $timeout, $xmlRequest) {
 $url = $protocol."://".$host.":".$port.$path;

etc...


... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites

Hello Stuart Owens, I just have one simple question here. Does this contribution of yours work if the shipper is from Singapore?

 

Thank You

Share this post


Link to post
Share on other sites

Rezdwan;

 

Supposedly, it should... but I can't say for sure, as I've never tried to install it in a store with any other origin other than the US.

 

You can either try it or post a valid address in Singapore (some public street address - say for a post office or something similar with no company name) and I'll try it in my test store this weekend.

Edited by mugitty

... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites
Rezdwan;

 

Supposedly, it should... but I can't say for sure, as I've never tried to install it in a store with any other origin other than the US.

 

You can either try it or post a valid address in Singapore (some public street address - say for a post office or something similar with no company name) and I'll try it in my test store this weekend.

Sorry mugitty, I'm not sure if you understood what I meant. What I meant was, what if the 'Shipping Origin' is from Singapore? That would mean for the module settings below, I would have to fill in only Singapore addresses.

Shipping Origin (determines what products names are shown based on origin)

Origin City (required for some countries)

Origin State/Province (two letter ISO 3166 code)

Origin Country (two letter ISO 3166 code)

Origin Zip/Postal Code

So, will it work then?

 

Thank You

Share this post


Link to post
Share on other sites

Sorry, I got your reply wrong the first time. Disregard the abovementioned reply from me. Here's what you wanted. It is an example address you can try.

Origin City (required for some countries): None
Origin State/Province (two letter ISO 3166 code): Not sure about this one.
Origin Country (two letter ISO 3166 code): SG
Origin Zip/Postal Code: 510114

Share this post


Link to post
Share on other sites

I don't know how you can have an address without a city. Why don't you just try setting up the store and module with a Singapore origin and see if it works? That's exactly what I will do if you give me a complete valid address in Singapore (for the origin).


... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites

Torin, thank you ever so much for your UPS XML module. It's an absolute necessity for me. I'd been searching for dimensional support and multiple box sizes, too. Bless your dear heart.

 

I'd like to propose that your UPS XML module support frozen shipping.

 

There are a number of places using oscommerce that ship frozen products. The general method for this is dry ice (which UPS allows). The price of the styrofoam containers and the dry ice itself require that the tare price be calculated more exactly.

 

This could not be a separate module from the UPS XML module because:

  • The displacement of the ice effects the number of boxes shipped
  • The amount of ice is dependent on the number of days (a direct result of the shipping method choice).

I'd guess this is the stuff that would need added:

 

 

1. Field to products table - Dry ice shipping (Y/N)

 

ALTER TABLE products

ADD products_length DECIMAL(6,2) DEFAULT '12' NOT NULL,

ADD products_width DECIMAL(6,2) DEFAULT '12' NOT NULL,

ADD products_height DECIMAL(6,2) DEFAULT '12' NOT NULL,

ADD products_ready_to_ship INT(1) DEFAULT '0' NOT NULL,

ADD products_ice INT(1) DEFAULT '0' NOT NULL;

 

2. Dry ice information table.

 

DROP TABLE IF EXISTS ice;

CREATE TABLE ice (

? ice_id int NOT NULL auto_increment,

? ice_name varchar(64) NOT NULL,

? ice_description varchar(255) NOT NULL,

? ice_units_base DECIMAL(6,2) default '5' NOT NULL,

? ice_daily_add DECIMAL(6,2) default '5' NOT NULL,

? ice_cost DECIMAL(6,2) DEFAULT '0' NOT NULL,

? ice_unit_volume DECIMAL(6,2) DEFAULT '50' NOT NULL,

? PRIMARY KEY (tare_id)

);

 

3. Calculation for amount of ice. (must be made before number of boxes are determined)

if (frozen_shipping == "yes") { $ice_amount = (($numdays - 1) * $ice_daily_add) + $ice_units_base; }

 

4. Calculation for amount of boxes. (accounts for volume lost to ice. ice shipped products and non-ice shipped products shipped separately)

I'm still thinking about this one and I'm looking into your code to see how the number of boxes are determined. Any help you would be willing to give me to figure this out quickly would be great.

 

5. Calculation for ice price. (must be made after number of boxes are determined)

if (frozen_shipping == "yes") { $ice_price = $ice_amount * $ice_cost * boxes; }

Share this post


Link to post
Share on other sites

Oy... this is a nightmare! Here is what I am trying to get working and I assume should work fine.

 

I have a bunch of products... size and weight are both specified in the item properties. I want the UPSXML module to recognize that I added 5 items to my cart that weigh 5 LBs each and display the calculated weight based on each item individually.

 

5 x 25LBS total

 

Each package is 5LBS and according to UPS, One 5 LB box would cost $3.92 to ship to where I want. So $3.92 x 5 instead of shipping one 25 LB box. I need it to break down every package and calculate it on an each package basis.

 

Did I do something wrong? I assume this would be a fairly common / requested feature. Once you hit 70lbs UPS skyrockets in price.

Share this post


Link to post
Share on other sites
Hi Stuart,

 

I was wondering if you had gotten a chance to test out my ready to ship problem on your cart?

 

I know you have been trying to help Bob & Robin figure out a problem that they are having, so I thoroughly understand if you haven't had the chance. I would offer my help, but I don't seem to be having their problem and haven't been able to duplicate it.

 

Thanks again for any help you can give.

Katrina

Katrina;

 

Sorry about the long delay in responding...

 

I just noticed something this morning that "might" be causing the issue you mentioned (this also might be the problem that you are encountering, eTron):

 

In admin/categories.php, around line 637, the install instructions (in dimensions.txt) say to add a line that reads

<td class="main"><?php echo tep_draw_separator('pixel_trans.gif', '24', '15') . ' ' . tep_draw_input_field('products_ready_to_ship', $pInfo->products_ready_to_ship); ?></td>

I'm not sure where the instructions got changed, but this should be a checkbox, not an input field - it should read

<td class="main"><?php echo tep_draw_separator('pixel_trans.gif', '24', '15') . ' ' . tep_draw_checkbox_field('products_ready_to_ship', '1', $pInfo->products_ready_to_ship) . ' ' . TEXT_PRODUCTS_READY_TO_SHIP_SELECTION . ' ' ?></td>

I made this change to my test shop and, when adding an 2 of an item that has "Ready to ship" checked, the cart shows 2 packages. If I add another item or 2 more items to that same cart which is(are) not checked as "Ready to ship", the package count for UPS then shows 3 packages, which is what I would expect to see.

Edited by mugitty

... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites

I just got my cart working perfectly and now I'm getting this all the time. :( Any ideas? I see some of you have tried to contact UPS themselves.

 

 

United Parcel Service (XML)

Rating and Service 1.0001 0 An unknown error occured while attempting to contact the UPS gateway : Rating and Service 1.0001 0 An unknown error occured while attempting to contact the UPS gateway

Share this post


Link to post
Share on other sites

any updates on this last "bug" I came up with? I actually forgot all about this......then realized that I had disabled UPS shipping on my site which made me remember.

 

I tried several times and the shipping costs in my cart are still not matching UPS website. USPS is, but not UPS.

Share this post


Link to post
Share on other sites
any updates on this last "bug" I came up with? I actually forgot all about this......then realized that I had disabled UPS shipping on my site which made me remember.

 

I tried several times and the shipping costs in my cart are still not matching UPS website. USPS is, but not UPS.

 

 

Wish this was fixed a while back. Would have saved my ass a lot of time and stress. O well... I can't complain.

Share this post


Link to post
Share on other sites

Hi Stuart,

 

I really appreciate the time you took to test out my problem. I know how busy you must be. There's just one problem, you didn't test it correctly.

 

If you put the ready to ship items in the cart first it works perfectly.

 

It's when you place a non-ready to ship item in the cart first then a ready to ship item or if you place a ready to ship item in the cart, then a non-ready to ship item in, and then a ready to ship item in that I'm having problems with.

 

Would you be willing to give it another try, please? Your help is greatly appreciated. :D

 

Katrina

Share this post


Link to post
Share on other sites

Katrina;

 

I see the problem now (but not the answer). It seems that if you start with a non-ready-to-ship item, then it just adds the weight to the total when a ready-to-ship item is added. This, of course, is a problem because the rate that is being quoted is only for one package and substantially below what would be charged for 2 separate packages.

 

Not being the author of this contribution (other than some minor modifications) and not being very accomplished in PHP, I'm not sure whether I can isolate and correct this issue, but I will look.

 

Hopefully, someone with a much better knowledge of writing code will come forward with some help.

Edited by mugitty

... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites

Stuart,

 

Not that I think I can solve the problem, but perhaps what I saw might help you underway. The products that are packed are (I think) called with (around line 200):

  $productsArray = $cart->get_products();

 if (DIMENSIONS_SUPPORTED) {

Actually, the function get_products is found in /includes/classes/shopping_cart.php and returns a products_array:

          $products_array[] = array('id' => $products_id,
                                   'name' => $products['products_name'],
                                   'model' => $products['products_model'],
                                   'image' => $products['products_image'],
                                   'price' => $products_price,
                                   'quantity' => $this->contents[$products_id]['qty'],
                                   'weight' => $products['products_weight'],
                                   'final_price' => ($products_price + $this->attributes_price($products_id)),
                                   'tax_class_id' => $products['products_tax_class_id'],
                                   'attributes' => (isset($this->contents[$products_id]['attributes']) ? $this->contents[$products_id]['attributes'] : ''));

My thinking was that if you can sort that product array first, either kicking out the products not-ready-to-ship or sorting them in an order that doesn't cause any problems it would do the trick.

 

However, I can't figure out how the code knows that a product is not ready to ship so I can't get any further from here. Perhaps you or someone else can take it from here, provided this is indeed a point of departure.

Share this post


Link to post
Share on other sites

Thanks, Jan (you come to my rescue once again! :thumbsup: )

I'll take a look in that area and see if I can sort it out with a bit of playing around.

 

Stuart


... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites
I get the error message.

 

10002: The XML document is well formed but the document is not valid.

 

Is that because I do not input the right Access Key , username and password on the backend?

If you have not entered valid Access, User and Password on the backend, that very well could be the cause of that error.

 

Have you tried it with those entries being ones that you know to be correct? If not, I would think that must be your first step. Then, if you still get this error, enable logging and take a look at the submission and response to see if you can identify the part of the request that UPS doesn't like. That should narrow it down to whether it's the Rates & Services or Time In Transit API that has a problem - then we can troubleshoot from there.


... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites

I'm still no closer to solving the problem with the "ready-to-ship" packages not being properly identified in some instances, but I thought I'd isolate some of the packaging code and post it here in case someone might see why the situation that Katrina mentioned above occurs...

 

As Jan observed, the function get_products is created in catalog/includes/classes/shopping_cart.php and, when amended for this contribution, appears as:

    function get_products() {
     global $languages_id;

     if (!is_array($this->contents)) return false;

     $products_array = array();
     reset($this->contents);
     while (list($products_id, ) = each($this->contents)) {
       $products_query = tep_db_query("select p.products_id, pd.products_name, p.products_model, p.products_image, p.products_price, p.products_weight, p.products_length, p.products_width, p.products_height, p.products_ready_to_ship, p.products_tax_class_id from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd where p.products_id = '" . (int)$products_id . "' and pd.products_id = p.products_id and pd.language_id = '" . (int)$languages_id . "'");
       if ($products = tep_db_fetch_array($products_query)) {
         $prid = $products['products_id'];
         $products_price = $products['products_price'];

         $specials_query = tep_db_query("select specials_new_products_price from " . TABLE_SPECIALS . " where products_id = '" . (int)$prid . "' and status = '1'");
         if (tep_db_num_rows($specials_query)) {
           $specials = tep_db_fetch_array($specials_query);
           $products_price = $specials['specials_new_products_price'];
         }

         $products_array[] = array('id' => $products_id,
                                   'name' => $products['products_name'],
                                   'model' => $products['products_model'],
                                   'image' => $products['products_image'],
                                   'price' => $products_price,
                                   'quantity' => $this->contents[$products_id]['qty'],
                                   'weight' => $products['products_weight'],
                                   'length' => $products['products_length'],
                                   'width' => $products['products_width'],
                                   'height' => $products['products_height'],
                                   'ready_to_ship' => $products['products_ready_to_ship'],
                                   'final_price' => ($products_price + $this->attributes_price($products_id)),
                                   'tax_class_id' => $products['products_tax_class_id'],
                                   'attributes' => (isset($this->contents[$products_id]['attributes']) ? $this->contents[$products_id]['attributes'] : ''));
       }
     }

     return $products_array;
   }

Then, in upsxml.php, it is referenced in function quote($method = ''):

        $productsArray = $cart->get_products();
       if (DIMENSIONS_SUPPORTED) {
           //Use packing algoritm to return the number of boxes we'll ship
           $boxesToShip = $this->packProducts($productsArray);
           //quote for the number of boxes
           for ($i = 0; $i < count($boxesToShip); $i++) {
               $this->_addItem($boxesToShip[$i]['length'], $boxesToShip[$i]['width'], $boxesToShip[$i]['height'], $boxesToShip[$i]['current_weight']);
               $totalWeight += $boxesToShip[$i]['current_weight'];
           }
       } else {
           // The old method. Let osCommerce tell us how many boxes, plus the weight of each (or total?? might be sw / num boxes)
           for ($i = 0; $i < $shipping_num_boxes; $i++) {
               $this->_addItem (0, 0, 0, $shipping_weight);
           }
       }

The function packProducts is identified later in the same file

    function packProducts($productsArray) {

       $definedPackages = $this->getPackages();
       $emptyBoxesArray = array();
       for ($i = 0; $i < count($definedPackages); $i++) {
           $definedBox = $definedPackages[$i];
           $definedBox['remaining_volume'] = $definedBox['length'] * $definedBox['width'] * $definedBox['height'];
           $definedBox['current_weight'] = $definedBox['empty_weight'];
           $emptyBoxesArray[] = $definedBox;
       }
       $packedBoxesArray = array();
       $currentBox = NULL;
       // Get the product array and expand multiple qty items.
       $productsRemaining = array();
       for ($i = 0; $i < count($productsArray); $i++) {
           $product = $productsArray[$i];
           for ($j = 0; $j < $productsArray[$i]['quantity']; $j++) {
               $productsRemaining[] = $product;
           }
       }

with the only segment that I can see which references the "ready-to-ship" products occurring here

        // Worst case, you'll need as many boxes as products ordered.	
       while (count($productsRemaining)) {
           // Immediately set aside products that are already packed and ready.
           if ($productsRemaining[0]['ready_to_ship'] == '1') {
               $packedBoxesArray[] = array (
               'length' => $productsRemaining[0]['length'],
               'width' => $productsRemaining[0]['width'],
               'height' => $productsRemaining[0]['height'],
               'current_weight' => $productsRemaining[0]['weight']);
               $productsRemaining = array_slice($productsRemaining, 1);
               continue;
           }

...followed by the rest of the function

            //Cylcle through boxes, increasing box size if all doesn't fit.
           if (count($emptyBoxesArray) == 0) {
               print_r("ERROR: No boxes to ship unpackaged product<br>");
               break;
           }
           for ($b = 0; $b < count($emptyBoxesArray); $b++) {
               $currentBox = $emptyBoxesArray[$b];
               //Try to fit each product in box
               for ($p = 0; $p < count($productsRemaining); $p++) {
                   if ($this->fitsInBox($productsRemaining[$p], $currentBox)) {
                       //It fits. Put it in the box.
                       $currentBox = $this->putProductInBox($productsRemaining[$p], $currentBox);
                       if ($p == count($productsRemaining) - 1) {
                           $packedBoxesArray[] = $currentBox;
                           $productsRemaining = array_slice($productsRemaining, $p + 1);
                           break 2;
                       }
                   } else {
                       if ($b == count($emptyBoxesArray) - 1) {
                           //We're at the largest box already, and it's full. Keep what we've packed so far and get another box.
                           $packedBoxesArray[] = $currentBox;
                           $productsRemaining = array_slice($productsRemaining, $p + 1);
                           break 2;
                       }
                       // Not all of them fit. Stop packing remaining products and try next box.
                       break;
                   }
               }
           }
       }
   
       return $packedBoxesArray;
   }

If anyone sees something here that would cause the "ready-to-ship" items not to be always treated as separate packages from all other items in the cart, regardless of the sequence in which items are added to the cart, your input would be very welcome. :'(


... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites

Stuart,

 

I really don't see how ready-to-ship items are treated any different than not-ready-to-ship. Sure, the database is queried to see which of the items is ready-to-ship, but not much is done with as far as I can see. I might very well overlook something though.

 

I guess that if you really want to do it good, you should call the UPSXML shipping module twice: once for the products ready to ship, pack them and get a price and then the remaining products. But that is something for the shipping module, not the UPSXML module....

 

From the questions I understand that if you put the ready-to-ship items in first, there is no problem (or at least less of a problem). That should be able to be sorted out, since this info is in the products_array: 'ready_to_ship' => $products['products_ready_to_ship'],

 

I think there are two ways to solve this. The easy one, that might have unforeseen implications for other things/modules is to change the query to sort by:

? ? ? ?$products_query = tep_db_query("select p.products_id, pd.products_name, p.products_model, p.products_image, p.products_price, p.products_weight, p.products_length, p.products_width, p.products_height, p.products_ready_to_ship, p.products_tax_class_id from " . TABLE_PRODUCTS . " p, " . TABLE_PRODUCTS_DESCRIPTION . " pd where p.products_id = '" . (int)$products_id . "' and pd.products_id = p.products_id and pd.language_id = '" . (int)$languages_id . "' ORDER BY p.products_ready_to_ship DESC");

The more elegant approach in my view is to do the sorting in UPSXML. Easier said than done, since we are dealing with a multi-dimensional array.

 

One of my PHP books gives an example for this whereby you use the PHP function usort and add a small function that I don't really understand but does the job...

The function (adapted from the book to reflect the array keys):

function ready_to_shipCmp( $a, $b) {
if ( $a['ready_to_ship'] == $b['ready_to_ship'] )
return 0;
if ( $a['ready_to_ship'] > $b['ready_to_ship'] )
return -1;
return 1;
}

The function should be in the UPSXML code somewhere (at the bottom again?)

Then the sorting is called after the producstArray is acquired:

$productsArray = $cart->get_products();
// sort $productsArray according to ready-to-ship (first) and not-ready-to-ship (last)
usort($productsArray, ready_to_shipCmp);

I hope I didn't make any mistakes in this. Can't check it myself, sorry.

Edited by JanZ

Share this post


Link to post
Share on other sites

:D Jan to the rescue again :thumbsup:

With initial testing, it appears that Jan has again proven to be the CODEMASTER!

I've just run a couple of tests, but they appear to produce the correct results - I'll test more and hopefully upload a new version tomorrow (I think it's time to go to version 2.0 of UPSXML with all of the changes that have been made?)

 

Just for reference, here is the sequence and products I used in one test (if the weights and rates seem weird, keep in mind that these reflect the particular packaging setup and origin/destination that I used in my cart - all of the results were what I expected to see given those criteria. The rate shown is for UPS Ground only)

Add 1 non-RTS item @ 7# = 1 pkg  7.5#  $7.22

Add 1 RTS item @ 7# = 2 pkgs  14.5#  $14.29

Add 2nd non-RTS item @ 4.25# = 2 pkgs  18.75#  $14.78

Add 3rd non-RTS item @ 7# = 2 pkgs  25.94#  $15.99

Add 2nd RTS item @ 7# = 3 pkgs  32.94#  $23.07

Change 1st RTS item to quantity 2 = 4 pkgs  39.94#  30.14

Katrina and anyone else who might want to play yet tonight - here are the changes that were made:

 

In catalog/includes/modules/shipping/upsxml.php (about line 197), find

  $productsArray = $cart->get_products();

and add immediately below it:

        // sort $productsArray according to ready-to-ship (first) and not-ready-to-ship (last)
       usort($productsArray, ready_to_shipCmp);

Then, at the very bottom of the file, just BEFORE the final ?>, add

//******************************
function ready_to_shipCmp( $a, $b) {
   if ( $a['ready_to_ship'] == $b['ready_to_ship'] )
   return 0;
   if ( $a['ready_to_ship'] > $b['ready_to_ship'] )
   return -1;
   return 1;
}

If anyone tests this and finds that any combination of ready-to-ship and non-ready-to-ship items added to a cart in any sequence does not produce the expected number of packages, total weight and UPS rate, please post here with the specific sequence used in adding items to the cart so we can troubleshoot further.

 

Thank you, Jan! ;)


... if you want to REALLY see something that doesn't set up right out of the box without some tweaking,

try being a Foster Parent!

Share this post


Link to post
Share on other sites

Stuart,

 

Thanks for testing and helping. Good to hear things work as advertised ;)

It finally dawned on me, ready-to-ship items go in their own box. Forget about my remark about the shipping module should call UPSXML twice, I was thinking ready-to-ship had to do with things being out of stock and then need to be shipped/packaged separately... duuuh.

 

I think the sorting would work also when you put the "sorting line" a little further (after "if (DIMENSIONS_SUPPORTED)". This would save one less modification for someone who wants do dimensional support since the sorting routine is then only called when DIMENSIONS_SUPPORTED is true anyway:

? ? ? ?$productsArray = $cart->get_products();
? ? ? ?if (DIMENSIONS_SUPPORTED) {
? ? ? ?// sort $productsArray according to ready-to-ship (first) and not-ready-to-ship (last)
? ? ? ?usort($productsArray, ready_to_shipCmp);
? ? ? ? ? ?//Use packing algoritm to return the number of boxes we'll ship
? ? ? ? ? ?$boxesToShip = $this->packProducts($productsArray);

The function can be put in the "standard" UPSXML shipping without problem of course. If doesn't hurt someone who doesn't do dimensional support...

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×