#!/usr/bin/perl -Ilibs

package API::Order::Shipping;

use strict;
use warnings;
use Data::Dumper;

use API::Utils::Scrub;

our $Config;
BEGIN {
	use API::Config;
	$Config = $API::Config::Config;
}
use lib ($Config->{'Website_libs'});

use ECS::Order ();
use ECS::Shipping;
use ECS::Shipping::EasyPost;
use ECS::Time;

use Carp;
use Clone 'clone';
use Data::Dumper;
use Math::Round;
use File::Spec;


# Not an API method
# Requires
# - $p->{'orderID'}
# Sets
# - $p->{'order'}
sub getShippingSummary {
	my $p = shift;
	
	return 0 if (not defined $p->{'orderID'});
	
	my $order = ECS::Order->load({
		'DBH'     => $p->{'DBHr'}||$p->{DBH},
		'orderID' => $p->{'orderID'}
	});
	
	return unless $order;
	
	# Copy Order-Shipping info into Order for ECS::Shipping $input support
	for my $k (
		'shippingServiceCode',
		'shipping_cost',
		'shipmentID',
		'tracking_number',
		'ignoreShipment',
	) {
		$order->{$k} = $order->shipping->{$k};
	}
	
	# Copy Order-Shipping-Shipment info into Order
	if (my $shipment = $order->shipping->shipment) {
		$order->{'shipmentKeyType'} = $shipment->{'keyType'};
	}
	
	$order->scrub;
	
	$p->{'order'} = $order;
	
	return 1;
}

# Not an API method
# Requires
# - $p->{'order'}
# - $p->{'order'}->{'shipping_country'}
sub getCountryName {
	my $p = shift;
	
	my $sql = "SELECT name FROM country WHERE countryCode = '".$p->{order}->{shipping_country}."'";
	my $rd = ECS::SQL::query({ DBH => $p->{DBHr}||$p->{DBH}, sql => $sql });
	
	$p->{'order'}->{'shipping_country_name'} = 'Unknown Country';
	$p->{'order'}->{'shipping_country_name'} = $rd->[0]->{name} if ($rd);
	
	return 1 if ($rd);
	return 0;
}

# Not an API method
# Gets the Courier object for an order
# Sets
# - $p->{'courier'}
sub getCourier {
	my $p = shift;
	
	$p->{'allRates'} = 0;

	ECS::Shipping::Courier::getCourierCode($p);
	ECS::Shipping::Courier::getCouriers($p); # fetches the single courier for this order/shipping service
	
	my $shippingCourierCode = $p->{'input'}->{'shippingCourierCode'};
	$p->{'courier'} = $p->{'couriers'}->{$shippingCourierCode};
	
	return $p->{'courier'} ? 1 : 0;
}

# Not an API method
sub getService {
	my $p = shift;
	my $order = $p->{'order'};
	
	$p->{'allowDisabledServices'} = 1;
    
    local $p->{ 'includeAllCouriers' } = 1;
	
	ECS::Shipping::Courier::getServices($p);
	
	$p->{'service'} = $p->{'courier'}->{'services'}->{$order->{'shippingServiceCode'}};
	
	return 1;
}

# Not an API method
# Gets the shipping service box limits for an order
# Requires
# - $p->{'order'}
# - $p->{'courier'} (from API::Order::Shipping::getCourier)
# - $p->{'service'} (from API::Order::Shipping::getService)
# Sets
# - $p->{'order'}->{'boxLimits'}
sub getBoxLimits {
	my $p = shift;
	my $order = $p->{'order'};
	
	my $box_limits = {};
	
	foreach my $k (keys %{ $p->{'service'}->{'boxLimits'} }) {
		$box_limits->{$k} = $p->{'service'}->{'boxLimits'}->{$k};
	}
	
	$order->{'boxLimits'} = $box_limits;
	
	return 1;
}

# Not an API method
# Gets all available shipping boxes for an order
# based on the shipping parameters (shipping service, destination country etc.)
# Requires
# - $p->{'order'}
# - $p->{'courier'} (from getCourier)
# - $p->{'order'}->{'intl'}
# Sets
# - $p->{'order'}->{'shippingBoxes'}
sub getAvailableBoxes {
	my $p = shift;
	my $order = $p->{'order'};
	
	my $shipping_boxes = {};
	
	$order->{'shippingBoxes'} = $shipping_boxes;
	
	my $resize_box_heights = {};
	
	ECS::Shipping::Box::getBoxes($p);
	ECS::Shipping::Courier::getBoxes($p);
	
	# Only for the cut-down boxes; group the cut-down heights by the parent box
	foreach my $shippingBoxID (keys %{ $p->{'courier'}->{'boxes'} }) {
		my $box = $p->{'boxes'}->{$shippingBoxID};
		
		next if (!$box->{'resized'});
		
		$resize_box_heights->{$box->{'barcode'}} = {} if (!defined $resize_box_heights->{$box->{'barcode'}});
		$resize_box_heights->{$box->{'barcode'}}->{int($box->{'height'})} = $box->{'shippingBoxID'};
	}
	
	# Only for the parent boxes or the custom boxes
	foreach my $shippingBoxID (keys %{ $p->{'courier'}->{'boxes'} }) {
		my $box = $p->{'boxes'}->{$shippingBoxID};
		my $courier_box = $p->{'courier'}->{'boxes'}->{$shippingBoxID};
		
		next if ($box->{'resized'});
		
		foreach my $k (keys %{ $courier_box }) {
			$box->{$k} = $courier_box->{$k};
		}
		
		next if (
			$order->{'intl'}
			&& (
				!defined $box->{'allowIntl'}
				|| !$box->{'allowIntl'}
			)
		);
		
		my $max_weight = $box->{'maxBoxWeight'};
		$max_weight = $box->{'maxBoxWeightIntl'} if ($p->{intl});
		
		$max_weight = ($max_weight + 0) if (defined $max_weight);
		
		$shipping_boxes->{$box->{'barcode'}} = {
			'shippingBoxID' => $box->{'shippingBoxID'},
			'boxLabel' => $box->{'boxLabel'},
			'boxLabelFormat' => $box->{'boxLabelFormat'},
			'barcode' => $box->{'barcode'},
			'length' => $box->{'length'},
			'width' => $box->{'width'},
			'height' => $box->{'height'},
			'maxWeight' => $max_weight,
			'cardboardType' => $box->{'cardboardType'},
			'resizable' => $box->{'resizable'},
			'resizedHeights' => $resize_box_heights->{$box->{'barcode'}}
		};
	}

	return 1;
}

# Not an API method
# Requires
# - $p->{'input'}->{'orderID'}
# - $p->{'input'}->{'packProducts'}
# Sets
# - $p->{'shipment'}
sub getWarehouseShipment {
	my $p = shift;
	
    local $p->{'input'}->{'typeID'} = "orderID";
	local $p->{'cartItems'} = $p->{'input'}->{'packProducts'};
	
	# force to check prior committed when calculating qtyAvailable at the packing stations
	# because typeID gets overwritten in getShipments (this fixes OnHand Dropship items)
	local $p->{'checkPriorCommitted'} = 1;
   
	# Fetch the warehouse shipment for this order
	if (ECS::Shipping::Shipment::getShipments($p)) {
		foreach my $shipment (@{ $p->{shipments} }) {
			if (!$shipment->{dropship}) {
				$p->{'shipment'} = $shipment;
				ECS::Shipping::Shipment::groupProducts($p);
				return 1;
			}
		}
	}
	
	return 0; # This line should never run, but just in case. All products were marked as drop-ship, but some are being shipped from the warehouse?
}

sub getServicesForPackOptimization {
    my $p = shift;

    my $order = $p->{'order'};
    my $shipment = $p->{'shipment'};

    $p->{'servicesForPackOptimization'} = {};
    $p->{'isDealerPricing'} = ECS::Customer::Account::checkDealerPricing({
        'DBH' => $p->{'DBHr'}||$p->{DBH},
        'customerID' => $order->customerID,
        'site' => $order->site,
    });

    return if (! $order->packTableOptimizationEligible({
        'dbh' => $p->{'DBHr'}||$p->{DBH},
        'shipment' => $shipment,
        'isDealerPricing' => $p->{'isDealerPricing'},
    }));

    my $serviceGroup = defined $order->shipping->originalShippingServiceGroupID
        ? $order->shipping->originalServiceGroup
        : $order->shipping->shippingServiceGroupID
            ? $order->shipping->serviceGroup
            : undef;

    return if ! $serviceGroup;

    my $DIT = $serviceGroup->{'daysInTransit'};
    $DIT =~ s/([\d\-*])(\s+)(Business Days|Business Day)/$1/g;
    my( $ll, $ul ) = split '-', $DIT;
    my $DITToCompare = $ul || $ll;

    # if two services will always have the same DIT mapping, we can
    # use the courierDaysInTransitAlias to link one DIT table to another
    #
    # we are looking for 3 different classes of shipping service here:
    #   1. services which meet the DIT requirements
    #   2. the original shipping service on the order
    #   3. services which are DIT aliases for (1)
    my $checkServicesSQL = q{
        /* 1 */
        SELECT
            shippingService.shippingServiceCode,
            shippingService.markdownPercent
		FROM courierDaysInTransit 
		JOIN shippingService 
            ON (shippingService.shippingServiceCode = courierDaysInTransit.shippingServiceCode)
		WHERE
            daysInTransit <= ?
            AND zip = ?
            AND includeInPTCO = 1
        UNION ALL
        /* 2 */
        SELECT
            shippingService.shippingServiceCode,
            shippingService.markdownPercent
        FROM shippingService
        WHERE
            shippingService.shippingServiceCode = ?
        UNION ALL
        /* 3 */
        SELECT
            shippingService.shippingServiceCode,
            shippingService.markdownPercent
        FROM courierDaysInTransit
        JOIN courierDaysInTransitAlias
            ON courierDaysInTransit.shippingServiceCode
             = courierDaysInTransitAlias.ditShippingServiceCode
        JOIN shippingService
            ON courierDaysInTransitAlias.aliasShippingServiceCode
             = shippingService.shippingServiceCode
        WHERE
            daysInTransit <= ?
            AND zip = ?
            AND includeInPTCO = 1
	};
    
	my $dbhr = $p->{DBHr}||$p->{DBH};
    my $checkServices = $dbhr->prepare($checkServicesSQL);
    $checkServices->execute(
        $DITToCompare,
        $order->{'shipping_zip'},
        $order->{'shippingServiceCode'},
        $DITToCompare,
        $order->{'shipping_zip'},
    );

    my %servicesForOptimization;
    while (my $row = $checkServices->fetchrow_hashref()) {
        $servicesForOptimization{$row->{'shippingServiceCode'}} = $row;
    }
    $p->{'servicesForPackOptimization'} = \%servicesForOptimization;

    return 1;
}

sub buildRateRequests {
    my $p = shift;

    my $courier = $p->{'courier'};

    my @requests;
    if ($courier->{rate}->{cache}) {
        my $courier_requests = $courier->{rate}->{buildRequests}($p);
        foreach my $request (@{ $courier_requests }) {
            $p->{'rateRequests'}->{$courier->{shippingCourierCode}} ||= {};
            my $courierCode = $request->{shippingCourierCode} = $courier->{shippingCourierCode};
            $request->{services} = join('', (map { '[' . $_ . ']' } grep { $_ =~ /^$courierCode/ } keys %{ $p->{'servicesForPackOptimization'} }));
            $p->{'rateRequests'}->{$courier->{shippingCourierCode}}->{'allRequests'} ||= [];
            $p->{'rateRequests'}->{$courier->{shippingCourierCode}}->{'requests'} ||= [];
            push(@{$p->{'rateRequests'}->{$courier->{shippingCourierCode}}->{'allRequests'}}, $request);
            push(@{$p->{'rateRequests'}->{$courier->{shippingCourierCode}}->{'requests'}}, $request) if (!ECS::Shipping::Rate::getRatesCache($request));
        }
    }

    return 1;
}

sub getRates {
    my $p = shift;
    my @requests;
    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "Start of getRates in Shipping.pm");
    foreach my $courierCode (keys %{$p->{'rateRequests'}}) {
        
        next if (! scalar @{ $p->{'rateRequests'}->{$courierCode}->{'requests'} });
        push @requests, @{ $p->{'rateRequests'}->{$courierCode}->{'requests'} };
    }

    if (scalar @requests) {
        local $p->{'requestQueue'} = \@requests;
        local $SIG{__WARN__} = sub { 
            ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub getRates:processRequests ERROR: $_[0]"); 
            carp $_[0];
        };
        ECS::RequestQueue::processRequests($p);
        foreach my $request (@requests) {
            ECS::Shipping::Rate::setRatesCache($request);
        }
    }

    my %processedCouriers;
    SERVICE: foreach my $serviceCode (keys %{ $p->{'servicesForPackOptimization'} }) {
        next if (exists $p->{'shippingRates'}->{$serviceCode});

        local $p->{'order'}->{'shippingServiceCode'} = $serviceCode;
        local $p->{'input'}->{'shippingServiceCode'} = $serviceCode;
        getCourier($p);
        
        next if (exists $processedCouriers{$p->{'courier'}->{'shippingCourierCode'}});
        local $p->{shipment_courier}->{requests} = $p->{'rateRequests'}->{$p->{'courier'}->{shippingCourierCode}}->{'allRequests'};
        
        $p->{order}->{'intl'} = ECS::Shipping::Courier::isInternational($p);
        getService($p);
        getAvailableBoxes($p);
        $p->{shipment_courier}->{intl} = $p->{order}->{'intl'};

        $processedCouriers{$p->{'courier'}->{'shippingCourierCode'}} = 1;
        next SERVICE unless $p->{'courier'}->{'rate'}->{'getRates'};
        local $p->{'rates'};
        {
            local $SIG{__WARN__} = sub { 
                ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub getRates:getRates ERROR: $_[0]"); 
                carp $_[0];
            };
            $p->{'courier'}->{'rate'}->{'getRates'}($p);
        }


        
        next unless $p->{'rates'};

        foreach my $rate (@{$p->{'rates'}}) {

            
	        next if (! exists $p->{'servicesForPackOptimization'}->{$rate->{'shippingServiceCode'}});

            ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub getRates:Shipping Service Code: " . $rate->{'shippingServiceCode'});
            ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub getRates:Raw Cost Before Markdown: " . $rate->{'rawCost'});

            # check FedEx::getValidGroundService
            # skip fedex commercial if package weight is not above minimum limit
            next if (
                $rate->{'shippingServiceCode'} eq 'FEDEX11'
                && exists $p->{'courier'}->{'package'}->{'homeToCommercialWeightLimit'}
                && ! grep { 
                    $_->{'weight'} > $p->{'courier'}->{'package'}->{'homeToCommercialWeightLimit'} 
                } @{ $p->{'order'}->{'packages'} }
            );

            my $cost = $rate->{ 'rawCost' };
	        my $cost_prev;
            my $shippingServiceCode = $rate->{ 'shippingServiceCode' };
            my $service = $p->{ 'servicesForPackOptimization' }->{ $shippingServiceCode };
            my $markdownPercent = $service->{ 'markdownPercent' };
            $p->{ 'ratesBeforeMarkdown' }->{ $shippingServiceCode } = $cost;
            $p->{ 'shippingRates' }->{ $shippingServiceCode } = applyMarkdown($markdownPercent,$cost);
            
        }
    }

    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "End of getRates in shipping.pm");

    return 1;
}

sub optimizeShipping {
    my $p = shift;

    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:Start of optimizeShipping");
    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:Original Shipping Service Code: " . $p->{order}->{'shippingServiceCode'});

    # mimic shipment_courier for getting rates
    my $originalBoxes = clone($p->{'packBoxes'});
    my $packOriginalService = $p->{order}->{'shippingServiceCode'};
    $p->{order}->{shipmentID} = $p->{input}->{shipmentID} = 0;
    $p->{order}->{'packages'} = $originalBoxes;
    local $p->{'packageProductKey'} = 'products';
    API::Order::Shipping::Package::buildPackageProductDetails($p);
    $p->{shipment_courier}->{boxes} = $p->{order}->{'packages'};

    $p->{'shippingRates'} = {};
    $p->{'ratesBeforeMarkdown'} = {};
    $p->{'rateRequests'} = {};

    if ( $ENV{ 'EASYPOST_QUOTES_ENABLED' } // 0 ) {
        # gather the account numbers for these services
        my %carrier_accounts = ();
        my $easyPostConfig = $p->{ 'order' }->site->localConfig( "EasyPost" );
        for my $serviceCode ( keys %{ $p->{ 'servicesForPackOptimization' } } ) {
            my $ezService = $easyPostConfig->{ 'ecsServiceCodes' }->{ $serviceCode };
            next unless $ezService;
            my $ezCourier = $easyPostConfig->{ 'serviceLevels' }->{ $ezService };
            next unless $ezCourier;
            my $ezAccount = $easyPostConfig->{ 'carrierAccounts' }->{ $ezCourier };
            next unless $ezAccount;
            $carrier_accounts{ $ezAccount } = 1;
        }

        my $shipments = ECS::Shipping::EasyPost::shipmentsFromOrder( $p->{ 'order' } );
        my @rates = ();
        for my $shipment ( @{ $shipments } ) {
            # filter by these accounts for faster results
            $shipment->{ 'carrier_accounts' } = [ keys %carrier_accounts ];

            my $shipment_response = ECS::Shipping::EasyPost::create_shipment( %{ $shipment } );

            # if one of the boxes failed, we can't optimize shipping
            return unless $shipment_response->{ 'success' };
            return unless $shipment_response->{ 'decoded' };
            return unless $shipment_response->{ 'decoded' }->{ 'rates' };

            for my $rate ( @{ $shipment_response->{ 'decoded' }->{ 'rates' } } ) {
                my $serviceCode = ECS::Shipping::EasyPost::getEcsServiceCode(
                    $rate->{ 'service' },
                );
                next unless $serviceCode;

                my $service = $p->{ 'servicesForPackOptimization' }->{ $serviceCode };
                next unless $service;

                my $cost = $rate->{ 'rate' };
                my $markdownPercent = $service->{ 'markdownPercent' };
		

		$p->{ 'ratesBeforeMarkdown' }->{ $serviceCode } = $cost;
		
	        $p->{ 'shippingRates' }->{ $serviceCode } = applyMarkdown($markdownPercent,$cost);
                
            }
        }
    }
    else {
        # get and compare rates only if we have services for optimization
          ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:servicesForPackOptimization: " . Dumper($p->{servicesForPackOptimization}));
        foreach my $serviceCode (keys %{ $p->{'servicesForPackOptimization'} }) {
            local $p->{'order'}->{'shippingServiceCode'} = $serviceCode;
            local $p->{'input'}->{'shippingServiceCode'} = $serviceCode;
            getCourier($p);
            $p->{order}->{'intl'} = ECS::Shipping::Courier::isInternational($p);
            getService($p);
            getAvailableBoxes($p);

            local $p->{shipment_courier}->{intl} = $p->{order}->{'intl'};
            if (! exists $p->{'rateRequests'}->{$p->{'courier'}->{'shippingCourierCode'}}) {
                buildRateRequests($p);
            }
        }
        getRates($p);
    }

    my $packOriginalCost = $p->{'shippingRates'}->{$packOriginalService};
    my ($lowestCost, $lowestCostService);

      ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:Shipping Rates: " . Dumper( [ sr => $p->{shippingRates}]));
    
    foreach my $serviceCode (keys %{$p->{'shippingRates'}}) {

        next if (! exists $p->{'servicesForPackOptimization'}->{$serviceCode});

        my $serviceCost = $p->{ 'shippingRates' }->{ $serviceCode };
        if( not defined $lowestCost
            or $serviceCost < $lowestCost
            ) {
            $lowestCost        = $serviceCost;
            $lowestCostService = $serviceCode;
        }
    }

    if( defined $lowestCostService
        and $lowestCostService ne $packOriginalService
        ) {
            ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:Selected service: $lowestCostService, Original service: $packOriginalService");
            ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:lowest cost service: " . Dumper($lowestCostService));
            ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:pack original service: " . Dumper($packOriginalService));
        $p->{'order'}->{'shippingServiceCode'} = $lowestCostService;
        $p->{'input'}->{'shippingServiceCode'} = $lowestCostService;
        $p->{'order'}->{'packOriginalServiceCode'} = $packOriginalService;
        $p->{'order'}->{'packOriginalCostActual'} = $packOriginalCost;
    }

    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:Rates before markdown: " . Dumper($p->{'ratesBeforeMarkdown'}));
    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:Shipping rates: " . Dumper($p->{'shippingRates'}));
    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "sub optimizeShipping:Order ID: " . $p->{'input'}->{'orderID'});

    logOptimizedServices( {
        'DBH'                 => $p->{ 'DBH' },
        'ratesBeforeMarkdown' => $p->{ 'ratesBeforeMarkdown' },
        'shippingRates'       => $p->{ 'shippingRates' },
        'orderID'             => $p->{ 'input' }->{ 'orderID' },
    } );

    ECS::Order::log_ptco($p->{'order'}->{'orderID'}, "End of optimizeShipping\n\n\n\n\n\n\n");

    return 1;
}

sub logOptimizedServices {
    my ( $p ) = @_;

    my $DBH                 = $p->{ 'DBH'                 } or croak "Missing DBH";
    my $ratesBeforeMarkdown = $p->{ 'ratesBeforeMarkdown' } or croak "Missing ratesBeforeMarkdown";
    my $shippingRates       = $p->{ 'shippingRates'       } or croak "Missing shippingRates";
    my $orderID             = $p->{ 'orderID'             } or croak "Missing orderID";

    return unless %{ $shippingRates };

    my $sqlPlaceholders = join ", ", ( "( ?, ?, ? )" ) x keys %{ $shippingRates };
    my $sqlPlaceholders2 = join ", ", ( "( ?, ?, ?, ? )" ) x keys %{ $shippingRates };

    my $sql = qq{
        DELETE FROM orderShippingAlternative WHERE orderID = ?
    };
    my $sth_delete = $DBH->prepare($sql);
    $sth_delete->execute($orderID);

    $sql = qq{
        INSERT INTO orderShippingAlternative
            ( orderID, shippingServiceCode, rate )
        VALUES
            $sqlPlaceholders
        ON DUPLICATE KEY UPDATE
            rate = VALUES( rate )
    };

    my $markdownSql = qq{
        INSERT INTO orderShippingAlternativeAfterMarkdown
            ( orderShippingAlternativeID, orderID, shippingServiceCode, rateAfterMarkdown )
        VALUES
            $sqlPlaceholders2
        ON DUPLICATE KEY UPDATE
            rateAfterMarkdown = VALUES(rateAfterMarkdown)
    };

    my $sth = $DBH->prepare( $sql );
    my $markdownSth = $DBH->prepare( $markdownSql );

    my @argv = ();
    for my $shippingServiceCode ( keys %{ $shippingRates } ) {
        my $rate = $ratesBeforeMarkdown->{ $shippingServiceCode }
                || $shippingRates->{ $shippingServiceCode };
        push( @argv,
            $orderID,
            $shippingServiceCode,
            $rate,
        );
    }

    $sth->execute( @argv );

    my @markdownArgs = ();
    for my $shippingServiceCode ( keys %{ $shippingRates } ) {
        my $rateAfterMarkdown = $shippingRates->{ $shippingServiceCode };

        # Select the orderShippingAlternativeID
        my $select_sql = qq{
            SELECT orderShippingAlternativeID FROM orderShippingAlternative
            WHERE orderID = ? AND shippingServiceCode = ?
        };
        my $sth_select = $DBH->prepare($select_sql);
        $sth_select->execute($orderID, $shippingServiceCode);
        my ($orderShippingAlternativeID) = $sth_select->fetchrow_array();

        push( @markdownArgs,
            $orderShippingAlternativeID,
            $orderID,
            $shippingServiceCode,
            $rateAfterMarkdown,
        );
    }

    $markdownSth->execute( @markdownArgs );

    return 1;
}


sub getAutoPackNotAllowedReason {
    my ( $p ) = @_;

    my $order = $p->{ 'order' } or croak "Missing order";

    return "Empty" unless @{ $order->{ 'packProducts' } };

    my $hasNonGiftCards = 0;
    for my $product ( @{ $order->{ 'packProducts' } } ) {
        # we can assume only gift cards are tax-exempt
        if ( not $product->{ 'taxExempt' } ) {
            $hasNonGiftCards = 1;
            last;
        }
    }
    return "Gift Card Only" unless $hasNonGiftCards;

    return 0;
}
sub applyMarkdown {
#apply markdown/markup to cost
#input:  markdownPercent, cost
#return: newCost with markdown applied or -1 if any error

#call exp.
#newCost = applyMarkdown(markdownPercent,cost);

    my $xMarkdwn = shift;
    my $xCost = shift;
    my $newCost;
	    
	    
    #if input errors, return inputted cost
    if($xMarkdwn &&  $xMarkdwn <= 0) {
        return $xCost;
    }

    if($xCost &&  $xCost  < 0) {
        return $xCost;
    }

    if($xMarkdwn < 1.0) {
       #markdown
       $newCost = Math::Round::nearest( 0.01, ($xCost * ( 1 - $xMarkdwn )));
    }elsif($xMarkdwn >= 1.0) {
       #markup
       $newCost = Math::Round::nearest( 0.01, ($xCost * $xMarkdwn) );
   }else {
      return $xCost;
   }
   
   return $newCost;
}   
