#!perl -wc
################################################################
#
#  ActinicDiscounts.pm - Discounting related functions 
#
#  Zoltan Magyar, Tuesday, October 28, 2003
#
#  Copyright (c) Actinic Software Plc 2003
#
################################################################
package ActinicDiscounts;
require 5.002;

push (@INC, "cgi-bin");
<Actinic:Variable Name="IncludePathAdjustment"/>

use strict;
#
# Constants definition
#
require <Actinic:Variable Name="ActinicConstantsPackage"/>;
use Time::Local;
#
# Version
#
$ActinicDiscounts::prog_name = 'ActinicDiscounts.pm';				# Program Name
$ActinicDiscounts::prog_name = $ActinicDiscounts::prog_name;	# remove compiler warning
$ActinicDiscounts::prog_ver = '$Revision: 18819 $ ';					# program version
$ActinicDiscounts::prog_ver = substr($ActinicDiscounts::prog_ver, 11); 	# strip the revision information
$ActinicDiscounts::prog_ver =~ s/ \$//;								# and the trailers

################################################################
#
# CalculateProductAdjustment - Calculate a product adjustment
#
# Input:		$phashCartItems		- pointer to cart detail
#
# Returns:	$nReturn					- return code ($::SUCCESS, $::FAILURE)
#				$sError					- error mesage if any
#				\@arrAdjustments		- ref to array of product adjustments
#
################################################################

sub CalculateProductAdjustment 
	{
	my ($phashCartItems) = @_;
	my @arrAdjustments;
	my $nTotalAdjustments = 0;
	my ($pItem, $pCartItem, $nGroupID);
	my $bGotOneDiscount  = $::FALSE;
	my %hUsedGroups;
	#
	# The cart items for discounts should be processed in order
	# from the most expensive to cheapest. Therefore we should
	# order the items first
	#
	my %hCartTotals;
	foreach $nGroupID (keys %{$phashCartItems})
		{
		my @Array = sort 
			{
			$$b[$::eDCartLineTotal] / $$b[$::eDCartLineQuantity] <=> 
			$$a[$::eDCartLineTotal] / $$a[$::eDCartLineQuantity]
			} (@{$$phashCartItems{$nGroupID}});
		$$phashCartItems{$nGroupID} = \@Array;
		
		map {$hCartTotals{$$_[$::eDCartLineIndex]} = $$_[$::eDCartLineTotal]} (@{$$phashCartItems{$nGroupID}});
		}
	#
	# The discounts should be checked in the order of definition
	#
	foreach $pItem (@{$$::g_pDiscountBlob{PRODUCT_LEVEL}})
		{
		#
		# Check if there is any group for this discount
		#
		$nGroupID = $$pItem{'GROUPID'};
		if (!defined $$phashCartItems{$nGroupID})
			{
			next;												# take next item if no discount for this group
			}
		#
		# Check if the discount is ok from restrictions POV
		#
		if (!IsDateValid(\$pItem) ||					# dates are not valid
			 !IsCustomerValid(\$pItem) ||				# or customers are not valid
			 !IsCouponValid(\$pItem))					# or the coupon 
			{
			next;												# take next then
			}
		#
		# Check if the group level "one only" flag is on
		#
		if ($$::g_pDiscountBlob{'PRODUCT_GROUPS'}->{$nGroupID}->[0] &&	# only one discount in this group
		    $hUsedGroups{$nGroupID})					# and we already got discount in this group
			{
			next;												# nothing to do with this item then
			}
		#
		# Determine the basis and the reward of the discount
		#
		my @arrThisAdjustments = CalculateProductRewards($phashCartItems, $nGroupID, $pItem);
		#
		# See if anything can be added to the list of adjustments
		#
		if ((scalar @arrThisAdjustments) != 0)
			{
			push @arrAdjustments, @arrThisAdjustments;	# add the item to the ist
			$hUsedGroups{$nGroupID} = 1;				# remember that we got discount in this group
			#
			# Now check the one only flag on site level
			#
			if ($$::g_pDiscountBlob{'ONE_PRODUCT_DISCOUNT'}) 
				{
				last;											# we got our only one discount, so say godbye now
				}
			}
		}
	#
	# Finally sort out the adjustments resulting negative cart value
	#
	my %hTotals;
	foreach $pItem (@arrAdjustments)
		{
		#
		# Now check if the new andjutment total is more than the cart total
		#
		my $nNewTotal = $$pItem[$::eAdjIdxAmount] + $hTotals{$$pItem[$::eAdjIdxCartIndex]};
		if (0 < $nNewTotal + $hCartTotals{$$pItem[$::eAdjIdxCartIndex]})	# it isn't more
			{													# record the new total 
			$hTotals{$$pItem[$::eAdjIdxCartIndex]} = $nNewTotal;
			next;												# and take next
			}
		else													# the adjustment total is higher than the cart total
			{												
			#
			# Calculate the new adjustment value which results zero difference
			# and update the totals
			#
			$$pItem[$::eAdjIdxAmount] = -($hCartTotals{$$pItem[$::eAdjIdxCartIndex]} + $hTotals{$$pItem[$::eAdjIdxCartIndex]});
			$hTotals{$$pItem[$::eAdjIdxCartIndex]} += $$pItem[$::eAdjIdxAmount];
			}
		}
		
	return($::SUCCESS, '', \@arrAdjustments);		# return the array
	}
	
################################################################
#
# GetRewardString 
#
# Input:		0 - the list of cart items
#				1 - the currently processed discount item
#
# Returns:	0 - the array of adjustment lines
#
################################################################

sub GetRewardString
	{
	my $pDiscount = shift;
	my $nReward = $$pDiscount{'REWARD_TYPE'};
	my $nBasis	= $$pDiscount{'BASIS'};
	my $sMsg;
	
	if ($nReward == $::eRewardMoneyOff)
		{
		my $sPrice = (ActinicOrder::FormatPrice($$pDiscount{'REWARDS'}->{'MONEY_OFF'}, $::TRUE, $::g_pCatalogBlob))[2];
		$sMsg = ACTINIC::GetPhrase(-1, 2391, $sPrice, GetGroupLink($$pDiscount{'GROUPID'}));
		}			
	elsif ($nReward == $::eRewardPercentageOff)
		{
		if ($$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'} eq '100.00%')
			{
			$sMsg = ACTINIC::GetPhrase(-1, 2392, GetGroupLink($$pDiscount{'GROUPID'}));
			}
		else
			{
			$sMsg = ACTINIC::GetPhrase(-1, 2391, StripTrailingZero($$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'}), GetGroupLink($$pDiscount{'GROUPID'}));
			}
		}				
	elsif ($nReward == $::eRewardPercentageOffCheapest)
		{
		if ($$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'} eq '100.00%')
			{
			$sMsg = ACTINIC::GetPhrase(-1, 2394, GetGroupLink($$pDiscount{'GROUPID'}));
			}
		else
			{
			$sMsg = ACTINIC::GetPhrase(-1, 2393, StripTrailingZero($$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'}), GetGroupLink($$pDiscount{'GROUPID'}));
			}		
		}			
	elsif ($nReward == $::eRewardFixedPrice)
		{
		my $sPrice = (ActinicOrder::FormatPrice($$pDiscount{'REWARDS'}->{'MONEY_OFF'}, $::TRUE, $::g_pCatalogBlob))[2];
		$sMsg = ACTINIC::GetPhrase(-1, 2395, GetGroupLink($$pDiscount{'GROUPID'}), $sPrice);
		}
	elsif ($nReward == $::eRewardMoneyOffExtraProduct)
		{
		my $sPrice = (ActinicOrder::FormatPrice($$pDiscount{'REWARDS'}->{'MONEY_OFF'}, $::TRUE, $::g_pCatalogBlob))[2];
		$sMsg = ACTINIC::GetPhrase(-1, 2397, $sPrice, GetRewardProductLink($pDiscount));
		}
	elsif ($nReward == $::eRewardPercentageOffExtraProduct)
		{
		if ($$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'} eq '100.00%')
			{
			$sMsg = ACTINIC::GetPhrase(-1, 2392, GetRewardProductLink($pDiscount));
			}
		else
			{
			$sMsg = ACTINIC::GetPhrase(-1, 2397, StripTrailingZero($$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'}), GetRewardProductLink($pDiscount));
			}		
		}	
	return $sMsg;
	}
	
################################################################
#
# StripTrailingZero - format percentage display as below
# 		9.99% => 9.99%; 9.90% => 9.9% and 9.00% =>s 9%
#
# Input:		0 - the percentage to be formatted
#
# Returns:	0 - the formatted output
#
################################################################

sub StripTrailingZero
	{
	my $sIn = shift;
	$sIn =~ s/(.+\.+[^\.0]*)0*(%*)$/$1$2/;
	$sIn =~ s/\.(%*)$/$1/;
	return $sIn;
	}
	
################################################################
#
# GetRewardLink - get HTML link to reward product/group 
#
# Input:		0 - the discount item
#
# Returns:	0 - the HTML link
#
################################################################

sub GetRewardProductLink
	{
	my $pDiscount = shift;								# receive parameter
	my $sLink;
	
	if ($$pDiscount{'REWARDS'}->{'USE_GROUP'})	# product group?
		{
		$sLink = GetGroupLink($$pDiscount{'REWARDS'}->{'PRODUCT_GROUP'});
		}
	else
		{
		# 
		# Get the product first
		#
		my ($nStatus, $sMessage, $pProduct) = Cart::GetProduct($$pDiscount{'REWARDS'}->{'PRODUCT_REF'});
		if ($nStatus != $::SUCCESS)
			{
			return "";
			}
		my @Response = ACTINIC::ProcessEscapableText($$pProduct{'NAME'});	# format product name
		#
		# Build the link
		#
		$sLink = sprintf("<A HREF=\"$::g_sSearchScript?PRODREF=%s&NOLOGIN=1\">%s</A>", $$pDiscount{'REWARDS'}->{'PRODUCT_REF'}, $Response[1]);
		}	
	return $sLink;
	}
	
################################################################
#
# GetGroupLink - get HTML link to the specified product group 
#
# Input:		0 - the product group ID
#
# Returns:	0 - the HTML link
#
################################################################

sub GetGroupLink
	{
	my $nGroupID = shift;
	my $sLink;
	my $sShop = ($::g_InputHash{SHOP} ? '&SHOP=' . ACTINIC::EncodeText2($::g_InputHash{SHOP}, $::FALSE) : '');
	#
	# Check if single product in the group
	#
	if ($$::g_pDiscountBlob{'PRODUCT_GROUPS'}->{$nGroupID}->[2] ne '')
		{
		# 
		# Get the product first
		#
		my ($nStatus, $sMessage, $pProduct) = Cart::GetProduct($$::g_pDiscountBlob{'PRODUCT_GROUPS'}->{$nGroupID}->[2]);
		if ($nStatus != $::SUCCESS)
			{
			return "";
			}
		my @Response = ACTINIC::ProcessEscapableText($$pProduct{'NAME'});	# format product name
		$sLink = sprintf("<A HREF=\"$::g_sSearchScript?PRODREF=%s&NOLOGIN=1%s\">%s</A>", 
							$$::g_pDiscountBlob{'PRODUCT_GROUPS'}->{$nGroupID}->[2], 
							$sShop,
							$Response[1]);
		}
	else
		{
		$sLink = sprintf("<A HREF=\"$::g_sSearchScript?PG=%d&GROUPONLY=1%s\">%s</A>", 
							$nGroupID, 
							$sShop,
							$$::g_pDiscountBlob{'PRODUCT_GROUPS'}->{$nGroupID}->[1]);
		}
	return ($sLink);
	}
	
################################################################
#
# AddProductDiscountInfo - record information messages of product
# 				level discounts
#
# Input:		0 - the message
#
################################################################

sub AddProductDiscountInfo
	{
	my $sMessage = shift;
	if ($$::g_pSetupBlob{'SHOW_PRODUCT_DISCOUNT_INFO'})
		{
		$ACTINIC::B2B->AppendXML("DiscountInfo", $sMessage . "\n");
		}
	}
	
################################################################
#
# AddOrderDiscountInfo - record information messages of order
# 				level discounts
#
# Input:		0 - the message
#
################################################################

sub AddOrderDiscountInfo
	{
	my $sMessage = shift;
	if ($$::g_pSetupBlob{'SHOW_DISCOUNT_INFO'})
		{
		$ACTINIC::B2B->AppendXML("DiscountInfo", $sMessage . "\n");
		}
	}
	
################################################################
#
# CalculateProductRewards 
#
# Input:		0 - the list of cart items
#				1 - the currently processed discount group ID
#				2 - the current discount item
#
# Returns:	0 - the array of adjustment lines
#
################################################################

sub CalculateProductRewards
	{
	my $pItemList = shift;
	my $nGroupID  = shift;
	my $pDiscount = shift;
	my @arrAdjustments;
	my $nReward = $$pDiscount{'REWARD_TYPE'};
	my $nBasis	= $$pDiscount{'BASIS'};
	my $bPaymentMessageDisplayed = $::FALSE;
	my $bInactive = $::FALSE;
	my $nDiscountTotal;
	my %SavedItemList;
	#
	# Copy the cart content
	#
	foreach my $nGroup (keys %{$pItemList})
		{
		my @Array;
		map 
			{
			my @Array2;
			map {push @Array2, $_} (@{$_});
			push @Array, \@Array2;
			} (@{$$pItemList{$nGroup}});
		$SavedItemList{$nGroup} = \@Array;
		}
	#
	# Check how many adjustments can be formed
	#
	while ($::TRUE)
		{
		#
		# Check if there is any item to trigger the discount
		#
		my ($nRemained, @CartItems) = GetNextTriggeringIndex($$pItemList{$nGroupID}, $$pDiscount{'BASIS'}, $$pDiscount{'REWARDS'}->{'TRIGGER'});
		#
		# Check if payment restrictions are applied
		#
		if ($$pDiscount{RESTRICTED_TO_PAYMENT} &&
			 (scalar @CartItems) != 0)
			{
			if (IsPaymentValid(\$pDiscount))			# is the current method valid?
				{
				if (!$bPaymentMessageDisplayed)
					{
					if (!($nReward == $::eRewardMoneyOffExtraProduct ||
						 $nReward == $::eRewardPercentageOffExtraProduct))
						{
						my $sMessage = ACTINIC::GetPhrase(-1, 2362, $$pDiscount{'DESCRIPTION'}, GetPaymentMethodList(\$pDiscount));
						AddProductDiscountInfo($sMessage);
						$bPaymentMessageDisplayed = $::TRUE;	# make sure we do not get this message again
						}
					}
				}
			else
				{
				$bInactive = $::TRUE;					# recall, it is an inactive discount
				}
			}	
		#
		# Construct info message if required
		#
		if ((scalar @CartItems) == 0)					# no triggering items
			{
			#
			# Check if any item wich may trigger more discount by adding more
			#
			if ($nRemained > 0 &&
				 !$bInactive)
				{
				my $sReward = GetRewardString($pDiscount);
				my $sTrigger;
				if ($$pDiscount{'BASIS'} != $::eBasisQuantity)	
					{
					my $sPrice = (ActinicOrder::FormatPrice($nRemained, $::TRUE, $::g_pCatalogBlob))[2];
					if ($nReward == $::eRewardMoneyOffExtraProduct ||
						 $nReward == $::eRewardPercentageOffExtraProduct)
						{
						$sTrigger = ACTINIC::GetPhrase(-1, 2398, $sPrice, GetGroupLink($$pDiscount{'GROUPID'}));
						}
					else
						{
						$sTrigger = ACTINIC::GetPhrase(-1, 2415, $sPrice);
						}
					}
				else
					{
					if ($nReward == $::eRewardMoneyOffExtraProduct ||
						 $nReward == $::eRewardPercentageOffExtraProduct)
						{
						$sTrigger = ACTINIC::GetPhrase(-1, 2416,  
							$nRemained, 
							GetGroupLink($$pDiscount{'GROUPID'}),
							$nRemained == 1 ?  ACTINIC::GetPhrase(-1, 2403) : ACTINIC::GetPhrase(-1, 2404));
						}
					else
						{
						$sTrigger = ACTINIC::GetPhrase(-1, 2414, $nRemained);
						}					
					}
				my $sMessage = $sReward . $sTrigger;
				AddProductDiscountInfo($sMessage);
				}
			last;												# nothing to do then
			}		
		#
		# The rewards using extra product should be processed separately
		# as the adjustment is attached to the reward product rather than 
		# the triggering items
		#		
		if ($nReward == $::eRewardMoneyOffExtraProduct ||
			 $nReward == $::eRewardPercentageOffExtraProduct)
			{
			#
			# Check if there is anything in the cart what can be used as free product
			#
			my (@TempItems) = GetExtraProduct($pItemList, $pDiscount);
			if ((scalar @TempItems) == 0)				# no free product?
				{
				my $sReward = GetRewardString($pDiscount);
				my $sTrigger = ACTINIC::GetPhrase(-1, 2399, ACTINIC::GetPhrase(-1, $$pDiscount{'REWARDS'}->{'USE_GROUP'} ? 2412 : 2411));
				my $sMessage = $sReward . $sTrigger;
				AddProductDiscountInfo($sMessage);				
				last;											# the processing ends here then
				}	
			if ($$pDiscount{RESTRICTED_TO_PAYMENT} &&
				 IsPaymentValid(\$pDiscount))			# is the current method valid?
				{
				if (!$bPaymentMessageDisplayed)
					{
					my $sMessage = ACTINIC::GetPhrase(-1, 2362, $$pDiscount{'DESCRIPTION'}, GetPaymentMethodList(\$pDiscount));
					AddProductDiscountInfo($sMessage);
					$bPaymentMessageDisplayed = $::TRUE;	# make sure we do not get this message again
					}
				}				
			push @CartItems, $TempItems[0];			# add the free product to the list
			}					
		#
		# The % off discount uses all the remained group items
		# so lets look up all of them
		#
		if ($nReward == $::eRewardPercentageOff)
			{
			@CartItems = GetAllAvailableGroupItems($$pItemList{$nGroupID}, $$pDiscount{'BASIS'});
			}
		#
		# Count the total quantity used for this discount
		#
		my $pLine;
		my $nUsed;
		my $nTaxExclusiveItemTotal;
		my $nUsedValue;
		foreach $pLine (@CartItems)
			{		
			$nUsed += ($nBasis == $::eBasisQuantity) ? $$pLine[1] : $$pLine[2];
			$nUsedValue += ($nBasis == $::eBasisQuantity) ? $$pLine[3]->[$::eDCartLineItemCost] * $$pLine[1] : $$pLine[2];
			$nTaxExclusiveItemTotal += $$pLine[3]->[$::eDCartLineItemCost] * $$pLine[1];
			}
		#
		# Check the rewards
		#
		my $nIndex;
		my $nAdjTotal;
			
		foreach $pLine (@CartItems)
			{
			my $nAdjustment = 0;
			my $nContribution = 0;
			#
			# Calculate the contribution of this line to the discount
			#
#?	ACTINIC::ASSERT($nUsed != 0 , 'The used amount (quantity or value) may not be zero');
			unless ((($nBasis == $::eBasisQuantity) && 	# avoid divide by zero
				 ($$pLine[3]->[$::eDCartLineItemCost] * $$pLine[1] == 0)) ||
				 (($nBasis != $::eBasisQuantity) && 
				 ($$pLine[2] == 0)))
				{
				$nContribution = 1 / $nUsedValue * (($nBasis == $::eBasisQuantity) ? $$pLine[3]->[$::eDCartLineItemCost] * $$pLine[1] : $$pLine[2]);
				}
			
			#######################################
			#
			# Reward Money off
			#
			#######################################
			if ($nReward == $::eRewardMoneyOff)
				{
				#
				# The last item requires special handling to avoid not precise adjustment 
				# calculation. E.g. to have a 10.00 adjustment distributed as 3 * 3.33
				#
				if ((scalar @CartItems) == $nIndex + 1)	# is this the last item?
					{
					$nAdjustment = -$$pDiscount{'REWARDS'}->{'MONEY_OFF'} - $nAdjTotal;
					}				
				else
					{
					$nAdjustment = ActinicOrder::RoundTax(-($$pDiscount{'REWARDS'}->{'MONEY_OFF'} * $nContribution), $ActinicOrder::SCIENTIFIC_NORMAL);
					}
				}	
			#######################################
			#
			# Reward % off
			#
			#######################################				
			elsif ($nReward == $::eRewardPercentageOff)
				{
				if ($$pDiscount{'BASIS'} == $::eBasisQuantity)	
					{
					$nAdjustment = ActinicOrder::RoundTax(-($$pLine[1] * $$pLine[3]->[$::eDCartLineItemCost] / 100 * $$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'}), $ActinicOrder::SCIENTIFIC_NORMAL);
					}
				else
					{
					$nAdjustment = ActinicOrder::RoundTax(-($$pLine[2] / 100 * $$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'}), $ActinicOrder::SCIENTIFIC_NORMAL);
					}
				}	
			#######################################
			#
			# Reward % off cheapest
			#
			#######################################				
			elsif ($nReward == $::eRewardPercentageOffCheapest)
				{
				if ((scalar @CartItems) == $nIndex + 1)	# is this the last item?
					{
					$nAdjustment = ActinicOrder::RoundTax(-($$pLine[3]->[$::eDCartLineItemCost] / 100 * $$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'}), $ActinicOrder::SCIENTIFIC_NORMAL);
					}					
				}
			#######################################
			#
			# Reward fixed price
			#
			#######################################				
			elsif ($nReward == $::eRewardFixedPrice)
				{
#?	ACTINIC::ASSERT($$pDiscount{'BASIS'} == $::eBasisQuantity, 'Fixed price can not be used for value triggered discounts');
				my $nDiscount = $$pDiscount{'REWARDS'}->{'MONEY_OFF'} - $nTaxExclusiveItemTotal;
				if ((scalar @CartItems) == $nIndex + 1)	# is this the last item?
					{
					$nAdjustment = $nDiscount - $nAdjTotal;
					}				
				else
					{
					$nAdjustment = ActinicOrder::RoundTax(($nDiscount * $nContribution), $ActinicOrder::SCIENTIFIC_NORMAL);
					}				
				}				
			#######################################
			#
			# Reward Money off extra product
			#
			#######################################
			elsif ($nReward == $::eRewardMoneyOffExtraProduct)
				{
				#
				# The last item is always the reward product
				# 
				if ((scalar @CartItems) == $nIndex + 1)	# is this the last item?
					{
					$nAdjustment = -$$pDiscount{'REWARDS'}->{'MONEY_OFF'};
					}
				}					
			#######################################
			#
			# Reward % off extra product
			#
			#######################################
			elsif ($nReward == $::eRewardPercentageOffExtraProduct)
				{
				#
				# The last item is always the reward product
				# 				
				if ((scalar @CartItems) == $nIndex + 1)	# is this the last item?
					{				
					$nAdjustment = ActinicOrder::RoundTax(-($$pLine[3]->[$::eDCartLineItemCost] / 100 * $$pDiscount{'REWARDS'}->{'PERCENTAGE_OFF'}), $ActinicOrder::SCIENTIFIC_NORMAL);
					}
				}		
			#
			# See the usable value by this line
			#
			my $nUsedValue = ($nBasis == $::eBasisQuantity) ? $$pLine[3]->[$::eDCartLineItemCost] * $$pLine[1] : $$pLine[2];
			#
			# Summarise the adjustment
			#
			$nAdjTotal += $nAdjustment;				# calculate total adjustment value
			$nDiscountTotal += $nAdjustment;			# also grand total
			#
			# Mark the items as used
			#
			if (((scalar @CartItems) == $nIndex + 1) &&
				(($nReward == $::eRewardPercentageOffExtraProduct) ||
				 ($nReward == $::eRewardMoneyOffExtraProduct)))
				{
				$$pLine[3]->[$::eDcartLineQuantityUsed]++;					# mark one item as used
				$$pLine[3]->[$::eDcartLineValueUsed] += $$pLine[2];	
				#
				# For extra products the used value is always the item cost
				#
				$nUsedValue = $$pLine[3]->[$::eDCartLineItemCost];
				}
			else
				{
				$$pLine[3]->[$::eDcartLineQuantityUsed] += $$pLine[1];
				$$pLine[3]->[$::eDcartLineValueUsed] += $$pLine[2];			
				}
			#
			# Make sure the adjustment is not more than the line total
			#				
			if (($nAdjTotal < 0) &&
				 ($nUsedValue < abs($nAdjustment)))
				{
				$nAdjTotal -= $nAdjustment;
				$nDiscountTotal -= $nAdjustment;
				$nAdjustment = - $nUsedValue;
				$nAdjTotal += $nAdjustment;
				$nDiscountTotal += $nAdjustment;
				}				
			#
			# Now add it to the list of adjustments
			#				
			if (($nAdjustment != 0) &&					# we got adjustment
				 !$bInactive)								# and the discount is not inactive
				{
				my @arrAdjustment = 
					($$pLine[3]->[$::eDcartLineProductRef],	# the reference supposed to be here
					$$pDiscount{'DESCRIPTION'}, 
					$nAdjustment, 
					$::eAdjTaxProRata );					# package the details
				$arrAdjustment[$::eAdjIdxCouponCode] = $::g_PaymentInfo{COUPONCODE};	# we have to use the coupon code entered by the user. $$pDiscount{'COUPON_CODE'} is an MD5 hash of it
				$arrAdjustment[$::eAdjIdxCartIndex]	 = $$pLine[0];
				$arrAdjustment[$::eAdjIdxDiscountID] = $$pDiscount{'ID'};
				$arrAdjustment[$::eAdjIdxRewardType] = $nReward;
				#
				# If the previous discount was applied on the same cart line
				# then do not create new adjustment line but extend the 
				# existing one
				#
				if ((scalar @arrAdjustments) != 0 &&
					 $arrAdjustments[$#arrAdjustments][$::eAdjIdxCartIndex] == $$pLine[0] &&
					 $arrAdjustments[$#arrAdjustments][$::eAdjIdxDiscountID] == $$pDiscount{'ID'})
					{
					$arrAdjustments[$#arrAdjustments][2] += $nAdjustment;
					}
				else
					{
					push @arrAdjustments, \@arrAdjustment;	# add to the array	
					}
				}
			$nIndex++;
			}			
		#
		# See if we allow more than one of this
		#
		if ($$pDiscount{'ONE_PER_ORDER'})			# one only is on
			{
			last;
			}				
		}	
	#
	# Some special processing if the current discount is disabled due to
	# payment method restriction
	#
	if ($bInactive)
		{
		#
		# Add payment message 
		#
		if ($bInactive &&
			($nDiscountTotal != 0))
			{
			my $sPrice = (ActinicOrder::FormatPrice(abs($nDiscountTotal), $::TRUE, $::g_pCatalogBlob))[2];
			my $sMessage = ACTINIC::GetPhrase(-1, 2363, $sPrice, "adjustment", GetPaymentMethodList(\$pDiscount));
			AddProductDiscountInfo($sMessage);
			}			
		#
		# Restore the original cart content
		#
		foreach my $nGroup (keys %SavedItemList)
			{
			my @Array;
			map 
				{
				my @Array2;
				map {push @Array2, $_} (@{$_});
				push @Array, \@Array2;
				} (@{$SavedItemList{$nGroup}});
			$$pItemList{$nGroup} = \@Array;
			}		
		}
	return @arrAdjustments;
	}
	
################################################################
#
# GetExtraProduct - get the cart index of the extra product
# 				required for the discount
#
# Input:		0 - $pItemList	- array of the group items
#				1 - $pDiscount	- the discount item
#
# Returns:	0 - array of item limes
#
################################################################

sub GetExtraProduct
	{
	my $pItemList = shift;
	my $pDiscount = shift;
	
	if ($$pDiscount{'REWARDS'}->{'USE_GROUP'})
		{
		my ($nRemained, @Cart) = GetNextTriggeringIndex($$pItemList{$$pDiscount{'REWARDS'}->{'PRODUCT_GROUP'}}, $::eBasisQuantity, 1);
		return(@Cart);
		}
	else
		{
		my ($nRemained, @Cart) = GetNextTriggeringIndex($$pItemList{-1}, $::eBasisProductReference, $$pDiscount{'REWARDS'}->{'PRODUCT_REF'});
		return(@Cart);
		}
	}
	
################################################################
#
# GetAllAvailableGroupItems - get all "unused" items of the group
# 				from the cart
#
# Input:		0 - $pItemList	- array of the group items
#				1 - $nBasis		- the discount basis
#
# Returns:	0 - array of item limes
#
################################################################

sub GetAllAvailableGroupItems
	{
	my ($pItemList, $nBasis) = @_;
	my $pItem;
	my @Quantity;
	
	foreach $pItem (@{$pItemList})
		{
		my $nUsableQty = $$pItem[$::eDCartLineQuantity] - $$pItem[$::eDcartLineQuantityUsed];# the difference of the total and already used items
		my $nUsableValue = $$pItem[$::eDCartLineTotal] - $$pItem[$::eDcartLineValueUsed];
		#
		# Add this item to our list
		#
		my @Ret;
		if ($nBasis == $::eBasisQuantity)
			{
			@Ret = ($$pItem[0], $nUsableQty, 0, $pItem);
			}
		else
			{
			@Ret = ($$pItem[0], 0, $nUsableValue, $pItem);
			}
		push @Quantity, \@Ret;
		}

	return (@Quantity);
	}
	
################################################################
#
# GetNextTriggeringIndex - get the cart index of the next triggering
# 				cart item
#
# Input:		0 - $pItemList	- array of the group items
#				1 - $nBasis		- the discount basis
#				2 - $nTrigger	- the triggering value (amount or qty)
#
# Returns:	0 - array of item limes
#
################################################################

sub GetNextTriggeringIndex
	{
	my ($pItemList, $nBasis, $nTrigger) = @_;
	my $pItem;
	my $nActual = 0;
	my @Quantity;
	
	foreach $pItem (@{$pItemList})
		{
		#
		# Check if we have already counted this line
		#
		my $nUsableQty = $$pItem[$::eDCartLineQuantity] - $$pItem[$::eDcartLineQuantityUsed];# the difference of the total and already used items
		my $nUsableValue = $$pItem[$::eDCartLineTotal] - $$pItem[$::eDcartLineValueUsed];
		
		if (($nBasis == $::eBasisQuantity) &&		# quantity based
			 ($nUsableQty == 0) 							# and all items are used
			 ||												# OR
			 ($nBasis != $::eBasisQuantity) &&		# value based
			 ($nUsableValue == 0))						# or no usable value
			{
			next;												# check the next then
			}
		#
		# Add the current item line to the summary (starting from the 
		# remained from prev calc) depending on the discount basis
		#
		my $nTotalCost;
		my $nCurrentIncrement;
		my $nRatio;
		if ($nBasis == $::eBasisTaxExclusiveValue)
			{
			if (ActinicOrder::PricesIncludeTaxes())
				{
				$nRatio = ($$pItem[$::eDCartLineTotal] == 0) ? 1 :
					$$pItem[$::eDCartLineTotal] /
					($$pItem[$::eDCartLineTotal] - $$pItem[$::eDcartLineTax1Default] - $$pItem[$::eDcartLineTax2Default]);
				}
			else
				{
				$nRatio = 1;
				}
			$nCurrentIncrement = $nUsableValue / $nRatio;
			}
		elsif ($nBasis == $::eBasisValueIncludingDefaultTax)
			{
			if (ActinicOrder::PricesIncludeTaxes())
				{
				$nRatio = 1;
				}
			else
				{
				$nRatio = ($$pItem[$::eDCartLineTotal] + 
								$$pItem[$::eDcartLineTax1Default] + 
								$$pItem[$::eDcartLineTax2Default]) == 0 ? 1 : 
							$$pItem[$::eDCartLineTotal] / ($$pItem[$::eDCartLineTotal] + 
								$$pItem[$::eDcartLineTax1Default] + 
								$$pItem[$::eDcartLineTax2Default]);
				}
			$nCurrentIncrement = $nUsableValue / $nRatio;
			}
		elsif ($nBasis == $::eBasisValueIncludingActualTax)
			{
			my $nPriceInclActualTax = $$pItem[$::eDCartLineTotal];
			if (ActinicOrder::PricesIncludeTaxes())
				{
				#
				# If tax 1 is exempted by buyer or location, remove tax 1 from total
				#
				if ($::g_TaxInfo{'EXEMPT1'} ||
					!ActinicOrder::IsTaxApplicableForLocation('TAX_1'))
					{
					$nPriceInclActualTax -= $$pItem[$::eDcartLineTax1Default];
					}
				#
				# If tax 2 is exempted by buyer or location, remove tax 2 from total
				#
				if ($::g_TaxInfo{'EXEMPT2'} ||
					!ActinicOrder::IsTaxApplicableForLocation('TAX_2'))
					{
					$nPriceInclActualTax -= $$pItem[$::eDcartLineTax2Default];
					}
				$nRatio = ($$pItem[$::eDCartLineTotal] == 0) ? 1 :
					$$pItem[$::eDCartLineTotal] /
					($nPriceInclActualTax);
				}
			else
				{
				$nRatio = ($$pItem[$::eDCartLineTotal] + 
								$$pItem[$::eDcartLineTax1Actual] + 
								$$pItem[$::eDCartLineTax2Actual]) == 0 ? 1 : 
							$$pItem[$::eDCartLineTotal] / ($$pItem[$::eDCartLineTotal] + 
								$$pItem[$::eDcartLineTax1Actual] + 
								$$pItem[$::eDCartLineTax2Actual]);
				}
			$nCurrentIncrement = $nUsableValue / $nRatio;
			}
		elsif ($nBasis == $::eBasisQuantity)			
			{
			#
			# Can count only the qty not used by qty based discounts - qty not used by value based discounts
			#
			$nCurrentIncrement = $nUsableQty;
			}
		elsif ($nBasis == $::eBasisProductReference)
			{
			#
			# This function is also used for extra product lookup
			# This requires some special handling here
			#
			if ($nTrigger eq $$pItem[$::eDcartLineProductRef] &&			# if the product reference matches
				 $nUsableQty >= 1)						# and we have at least one free item
				{
				my @Ret = ($$pItem[0], 1, 0, $pItem);
				push @Quantity, \@Ret;
				return (0, @Quantity);				
				}
			next;
			}
			
		$nActual += $nCurrentIncrement;			
		#
		# Check if the this qualifies
		#
		if ($nActual >= $nTrigger)
			{
			#
			# Now update tha last to mark as used as many required
			# First we should calculate items used to trigger this discount
			#
			my $nUsedQty = 0;
			my $nUsedValue = 0;
			if ($nBasis == $::eBasisQuantity)		# it is easy for quantity
				{
				$nUsedQty = $nTrigger - ($nActual - $nCurrentIncrement);
				}			
			else												# but requires some calculation for value triggered discounts
				{
				$nUsedValue = ($nTrigger - ($nActual - $nCurrentIncrement)) * $nRatio;
				}
			#
			# Construct the return structure and return it
			#
			my @Ret = ($$pItem[0], $nUsedQty, $nUsedValue, $pItem);
			push @Quantity, \@Ret;
			return (0, @Quantity);
			}
		#
		# Add this item to our list
		#
		my @Ret;
		if ($nBasis == $::eBasisQuantity)
			{
			@Ret = ($$pItem[0], $nUsableQty, 0, $pItem);
			}
		else
			{
			@Ret = ($$pItem[0], 0, $nUsableValue, $pItem);
			}
		push @Quantity, \@Ret;
		}
	#
	# If we are here then there wasn't any item meeting the criteria
	# so return an empty array
	#
	return ($nActual == 0 ? 0 : $nTrigger - $nActual, ());				# no luck
	}
	
################################################################
#
# CalculateOrderAdjustment - Calculate an order adjustment
#
# Input:		$parrOrderTotals	- pointer to array of order totals
#				$parrAdjustmentRules	- ref to array of adjustment rules
#
# Returns:	$nReturn					- return code ($::SUCCESS, $::FAILURE)
#				$sError					- error mesage if any
#				\@arrAdjustments		- ref to array of product adjustments
#
################################################################

sub CalculateOrderAdjustment 
	{
	my ($parrOrderTotals) = @_;
	my @arrAdjustments;
	my $nTotalAdjustments = 0;
	my ($pItem);
	my $bGotOneDiscount  = $::FALSE;
	my $bGotOneSurcharge = $::FALSE;
	#
	# Check adjustment in discount BLOB
	#
	foreach $pItem (@{$$::g_pDiscountBlob{ORDER_LEVEL}})
		{
		#
		# Check if the discount is ok from restrictions POV
		#
		if (!IsDateValid(\$pItem) ||					# dates are not valid
			 !IsCustomerValid(\$pItem) ||				# or customers are not valid
			 !IsCouponValid(\$pItem))					# or the coupon 
			{
			next;												# take next then
			}
		#
		# Determine the basis of the discount
		#
		if (($pItem->{'BASIS'} <= 2 && scalar(@$parrOrderTotals) == 2))
			{
			my $nTotal;
			my $nAdjustable;
			if (defined $pItem->{'BASIS'})
				{
				if ($pItem->{'BASIS'} == $::eBasisTaxExclusiveValue)	# cart value excluding tax
					{											
					$nTotal = $parrOrderTotals->[1][0];			# use adjusted product total
					if (ActinicOrder::PricesIncludeTaxes())	# if prices include tax
						{
						$nTotal -= $parrOrderTotals->[1][3] +
							$parrOrderTotals->[1][4];				# remove tax 1 and 2
						}
					$nAdjustable = $nTotal;
					}
				elsif ($pItem->{'BASIS'} == $::eBasisValueIncludingDefaultTax)	# cart value incl default tax
					{
					$nTotal = $parrOrderTotals->[1][0];			# use adjusted product total
					if (!ActinicOrder::PricesIncludeTaxes())	# if we're in exclusive mode
						{
						$nTotal += $parrOrderTotals->[1][3] +
									$parrOrderTotals->[1][4];		# add tax1 & tax2
						}
					$nAdjustable = $parrOrderTotals->[1][0];
					}
				elsif ($pItem->{'BASIS'} == $::eBasisValueIncludingActualTax)	# cart value incl actual tax
					{
					if ($parrOrderTotals->[1][5])
						{
						$nTotal = $parrOrderTotals->[1][0];			# use adjusted product total
						if (!ActinicOrder::PricesIncludeTaxes())	# if we're in exclusive mode
							{
							$nTotal += $parrOrderTotals->[1][1] +
									 $parrOrderTotals->[1][2];			# add taxes that apply
							}
						else
							{
							#
							# If tax 1 is exempted by buyer or location, remove tax 1 from total
							#
							if ($::g_TaxInfo{'EXEMPT1'} ||
								!ActinicOrder::IsTaxApplicableForLocation('TAX_1'))
								{
								$nTotal -= $parrOrderTotals->[1][1];
								}
							#
							# If tax 2 is exempted by buyer or location, remove tax 2 from total
							#
							if ($::g_TaxInfo{'EXEMPT2'} ||
								!ActinicOrder::IsTaxApplicableForLocation('TAX_2'))
								{
								$nTotal -= $parrOrderTotals->[1][2];
								}
							}
						$nAdjustable = $parrOrderTotals->[1][0];
						}
					}						
				}
			#
			# Check the reward type
			#
			my $nAdjustment = CalculateOrderRewards(\$pItem, $nTotal, $nAdjustable, $bGotOneDiscount, $bGotOneSurcharge);

			#
			# Check if the total adjustment exceeds the cart total
			#
			if (($parrOrderTotals->[1][0] + $nTotalAdjustments + $nAdjustment) < 0)
				{
				$nAdjustment = - ($parrOrderTotals->[1][0] + $nTotalAdjustments);
				}
			$nTotalAdjustments += $nAdjustment;		# update the total adjustment amount
			#
			# Construct return structure
			#
			my @arrAdjustment = 
				($pItem->{'DESCRIPTION'}, 
				$nAdjustment, 
				$::eAdjTaxProRataAdjusted,
				"",
				$pItem->{'BASIS'},
				$::g_PaymentInfo{COUPONCODE});		# we have to use the coupon code entered by the user as this $pItem->{'COUPON_CODE'} is an MD5 hash of the code
			#
			# Add only if adjustment is greater than 0
			#
			if ($nAdjustment != 0)
				{
				if (((!$$::g_pDiscountBlob{ONE_ORDER_DISCOUNT} && $nAdjustment < 0)	||
					 ($nAdjustment < 0 && !$bGotOneDiscount)) ||
				    ((!$$::g_pDiscountBlob{ONE_ORDER_DISCOUNT_SURCHAGE} && $nAdjustment > 0) ||
					 ($nAdjustment > 0 && !$bGotOneSurcharge)))  
					{
					push @arrAdjustments, \@arrAdjustment;
					}
				if ($nAdjustment < 0)
					{
					$bGotOneDiscount = $::TRUE;
					}
				else
					{
					$bGotOneSurcharge = $::TRUE;
					}
				}
			}
		}
	return($::SUCCESS, '', \@arrAdjustments);
	}

################################################################
#
# CalculateOrderRewards - Calculate reward for order level discount
#
# Input:		0 - $phashDiscount - pointer to the discount item
#				1 - $nTotal 		 - the order total (triggering value)
#				2 - $nAdjustable	 - the adjustable value
#				3 - $bGotOneDiscount  - one only flag for discount
#				4 - $bGotOneSurcharge - one only flag for surcharge
#
# Returns:	The adjustment value
#
# Affects:	$ACTINIC::B2B XML member (sets 'DiscountInfo')
#
################################################################

sub CalculateOrderRewards
	{
	my ($phashDiscount, $nTotal, $nAdjustable, $bGotOneDiscount, $bGotOneSurcharge) = @_;
	my $nCartValue;		
	my $nAdjustment = 0;
	my $sAdjustment = 0;
	my $nNextBand;
	#
	# Check if we are over the limit of any band
	#
	foreach $nCartValue (sort {$b <=> $a} keys %{$$phashDiscount->{'REWARDS'}})
		{
		if ($nCartValue <= $nTotal)
			{
			$sAdjustment = $$phashDiscount->{'REWARDS'}->{$nCartValue};
			last;
			}
		$nNextBand = $nCartValue;						# note next applicable band	
		}
	#
	# Check if percentage should be calculated
	#
	$nAdjustment = $sAdjustment;
	if ($nAdjustment =~ /%$/)				# not a fixed value
		{											# calculate the percentage
		$nAdjustment =~ s/%$//;
		$nAdjustment = $nAdjustable * $nAdjustment / 100;
		my $nRound = ($nAdjustment < 0) ? -0.5 : 0.5;
		$nAdjustment = int($nAdjustment + $nRound);
		}	
		
	my $bDiscount = ($nAdjustment < 0) || ($$phashDiscount->{'REWARDS'}->{$nNextBand} < 0) ? $::TRUE : $::FALSE;
	if (($$::g_pDiscountBlob{ONE_ORDER_DISCOUNT} && 
		  $bDiscount &&
		  $bGotOneDiscount) ||
		 ($$::g_pDiscountBlob{ONE_ORDER_DISCOUNT_SURCHAGE} && 
		  !$bDiscount &&
		  $bGotOneSurcharge))
		{
		return 0;
		}
	#
	# Surcharge cannot be applied if cart total is zero
	#
	if (!$bDiscount &&									# this is a surcharge
		($nTotal == 0))									# and the cart is empty
		{
		return 0;
		}	
	#
	# Check if payment restrictions are applied
	#
	if ($$phashDiscount->{RESTRICTED_TO_PAYMENT} &&
	    $nAdjustment != 0)
		{
		my $sMessage;
		if (IsPaymentValid($phashDiscount))			# is the current method valid?
			{
			$sMessage = ACTINIC::GetPhrase(-1, 2362, $$phashDiscount->{'DESCRIPTION'}, GetPaymentMethodList($phashDiscount));
			}
		else
			{
			my $sAdjustment = ($sAdjustment =~ /%$/) ? StripTrailingZero($sAdjustment) : (ActinicOrder::FormatPrice($sAdjustment, $::TRUE, $::g_pCatalogBlob))[2];
			$sAdjustment =~ s/^-//;						# we do not want to display the - sign
			my $sDiscount = ACTINIC::GetPhrase(-1, ($nAdjustment < 0 ? 2364 : 2365));
			$sMessage = ACTINIC::GetPhrase(-1, 2363, $sAdjustment, $sDiscount, GetPaymentMethodList($phashDiscount));
			$nAdjustment = 0;								# the discount shouldn't be applied at this point
			}
		AddOrderDiscountInfo($sMessage);
		}				
	#
	# Check if more discounts message can be displayed
	#
	my $sNext = $$phashDiscount->{'REWARDS'}->{$nNextBand};
	if ($nNextBand > $nCartValue &&					# if there are more discounts
	 	 $sNext =~ /^-/)									# but not surcharges
		{
		$sNext =~ s/^-//;									# we do not want to display the - sign
		my $sPrice = (ActinicOrder::FormatPrice($nNextBand, $::TRUE, $::g_pCatalogBlob))[2];
		my $sOff = ($sNext =~ /%$/) ? StripTrailingZero($sNext) : (ActinicOrder::FormatPrice($sNext, $::TRUE, $::g_pCatalogBlob))[2];
		my $sMessage = ACTINIC::GetPhrase(-1, 2366, $sOff, $sPrice);
		AddOrderDiscountInfo($sMessage);
		}
	return $nAdjustment;
	}
	
				
################################################################
#
# GetPaymentMethodList - get list of payment method restrictions
#
# Input:		$phashDiscount	- pointer to the discount item
#
# Returns:	comma separated list of methods
#
################################################################

sub GetPaymentMethodList
	{
	my ($phashDiscount) = @_;
	#
	# Now check the restrictions one by one
	#
	my $sRestriction;
	my $sList;
	foreach $sRestriction (keys %{$$phashDiscount->{'RESTRICTIONS'}})
		{
		if ($sRestriction =~ /P_(\d+)/)
			{
			$sList .= $$::g_pPaymentList{$1}{'PROMPT'} . ", ";
			}
		}	
	my $sOr = " " . ACTINIC::GetPhrase(-1, 2367);
	$sList =~ s/, $//;									# strip trailing ,
	$sList =~ s/,([^,]*)$/$sOr$1/g;
	return $sList;
	}
	
################################################################
#
# IsDateValid - Checks if the passed in discount item is
# 				valid from date POV
#
# Input:		$phashDiscount	- pointer to the discount item
#
# Returns:	$::TRUE if the discount is valid
#
################################################################

sub IsDateValid
	{
	my ($phashDiscount) = @_;
	#
	# Check if from date is used
	#
	my $nNow = time;
	if ($$phashDiscount->{USE_FROM_DATE})
		{
		my @aFrom = split /\//, $$phashDiscount->{VALID_FROM};
		#
		# Discount starts at 00:00:00
		#
		my $nStart = timegm(0, 0, 0, $aFrom[2], $aFrom[1] - 1, $aFrom[0]);
		if ($nNow < $nStart)
			{
			return $::FALSE;
			}
		}
	#
	# Check if until date is used
	#
	if ($$phashDiscount->{USE_UNTIL_DATE})
		{
		my @aUntil = split /\//, $$phashDiscount->{VALID_UNTIL};
		#
		# Discount starts at 00:00:00
		#
		my $nExpiry = timegm(59, 59, 23, $aUntil[2], $aUntil[1] - 1, $aUntil[0]);
		if ($nNow > $nExpiry)
			{
			return $::FALSE;
			}		
		}
	#
	# This must be ok if we are here
	#
	return ($::TRUE);
	}
	
################################################################
#
# IsCustomerValid - Checks if the passed in discount item is
# 				valid from customer POV
#
# Input:		$phashDiscount	- pointer to the discount item
#
# Returns:	$::TRUE if the discount is valid
#
################################################################

sub IsCustomerValid
	{
	my ($phashDiscount) = @_;
	#
	# Check if customer restrictions are used
	#	
	if (!$$phashDiscount->{RESTRICTED_TO_CUSTOMER})
		{
		return $::TRUE;
		}
	#
	# If used then check the price schedule of the current customer
	#
	my $sDigest = $ACTINIC::B2B->Get('UserDigest');		# Check if there is a user
	my $nSchedule = ActinicOrder::GetScheduleID($sDigest);	
	#
	# Now check if this is defined
	#
	my $sKey = "C_" . $nSchedule;
	if ($$phashDiscount->{RESTRICTIONS}->{$sKey} == 1)
		{			
		return $::TRUE;
		}
	#
	# If we are here then the customer group is not found in the restrictions list
	#
	return $::FALSE;
	}
	
################################################################
#
# IsPaymentValid - Checks if the passed in discount item is
# 				valid from payment method POV
#
# Input:		$phashDiscount	- pointer to the discount item
#
# Returns:	$::TRUE if the discount is valid
#
################################################################

sub IsPaymentValid
	{
	my ($phashDiscount) = @_;
	#
	# Check if payment method restrictions are used
	#	
	if (!$$phashDiscount->{RESTRICTED_TO_PAYMENT})
		{
		return $::TRUE;
		}
	#
	# If used then check the price schedule of the current customer
	#
	my $nPaymentID = $::g_PaymentInfo{METHOD};
	#
	# Now check the restrictions one by one
	#
	my $sKey = "P_" . $nPaymentID;
	if ($$phashDiscount->{RESTRICTIONS}->{$sKey} == 1)
		{			
		return $::TRUE;
		}
	#
	# If we are here then the payment is not found in the restrictions list
	#
	return $::FALSE;
	}
	
################################################################
#
# IsCouponValid - Checks if the passed in discount item is
# 				valid from coupon code POV
#
# Input:		$phashDiscount	- pointer to the discount item
#
# Returns:	$::TRUE if the discount is valid
#
################################################################

sub IsCouponValid
	{
	my ($phashDiscount) = @_;
	#
	# Check if coupon code is required
	#	
	if (!$$phashDiscount->{REQUIRES_COUPON})
		{
		return $::TRUE;
		}
	#
	# check if the entered coupon is the one required for this discount
	#
	my $sCoupon = $::g_PaymentInfo{COUPONCODE};
	eval
		{
#&		ActinicProfiler::StartLoadRuntime('Digest::MD5');
		require Digest::MD5;								# Try loading MD5
#&		ActinicProfiler::EndLoadRuntime('Digest::MD5');
		import Digest::MD5 'md5_hex';
		};
	if ($@)
		{
#&		ActinicProfiler::StartLoadRuntime('DigestPerlMD5');
		require <Actinic:Variable Name="DigestPerlMD5"/>;
#&		ActinicProfiler::EndLoadRuntime('DigestPerlMD5');
		import Digest::Perl::MD5 'md5_hex';			# Use Perl version if not found
		}
	my $sMD5Coupon = md5_hex($sCoupon);
	if ($$phashDiscount->{COUPON_CODE} eq $sMD5Coupon)
		{			
		return $::TRUE;
		}
	#
	# If we are here then the coupon is not valid
	#
	return $::FALSE;
	}
	
################################################################
#
# IsCouponValid - Checks if the passed in discount item is
# 				valid from coupon code POV
#
# Input:		$phashDiscount	- pointer to the discount item
#
# Returns:	$::TRUE if the discount is valid
#
################################################################

sub ValidateCoupon
	{
	my $sCoupon = shift;
	my ($pItem);
	eval
		{
#&		ActinicProfiler::StartLoadRuntime('Digest::MD5');
		require Digest::MD5;								# Try loading MD5
#&		ActinicProfiler::EndLoadRuntime('Digest::MD5');
		import Digest::MD5 'md5_hex';
		};
	if ($@)
		{
#&		ActinicProfiler::StartLoadRuntime('DigestPerlMD5');
		require <Actinic:Variable Name="DigestPerlMD5"/>;
#&		ActinicProfiler::EndLoadRuntime('DigestPerlMD5');
		import Digest::Perl::MD5 'md5_hex';			# Use Perl version if not found
		}
	my $sMD5Coupon = md5_hex($sCoupon);
	#
	# Check adjustment in discount BLOB
	#
	my @Discounts = (@{$$::g_pDiscountBlob{ORDER_LEVEL}}, @{$$::g_pDiscountBlob{PRODUCT_LEVEL}});
	foreach $pItem (@Discounts)
		{
		if (!$pItem->{REQUIRES_COUPON})				# does it have coupon
			{
			next;												# if no then take next
			}
		if ($pItem->{COUPON_CODE} ne $sMD5Coupon)		# is it the one looked up?
			{
			next;												# no, take next
			}
		if ($pItem->{USE_UNTIL_DATE})
			{
			my @aUntil = split /\//, $pItem->{VALID_UNTIL};
			#
			# Discount starts at 00:00:00
			#
			my $nNow = time;
			my $nExpiry = timegm(59, 59, 23, $aUntil[2], $aUntil[1] - 1, $aUntil[0]);
			if ($nNow > $nExpiry)
				{
				my $sExpiry = ACTINIC::FormatDate($aUntil[2], $aUntil[1], $aUntil[0], $::FALSE);
				return ($::FAILURE, ACTINIC::GetPhrase(-1, 2358, $sExpiry), undef);
				}	
			}		
		return ($::SUCCESS, undef, undef);			
		}
	#
	# We are here? the coupon is not defined then
	# But before we report the failure just do a quick check to see if
	# there is our "hard coded" value in the field
	#
	if (ACTINIC::GetPhrase(-1, 2355) eq $sCoupon)
		{
		return ($::SUCCESS, undef, undef);	
		}
	return ($::FAILURE, ACTINIC::GetPhrase(-1, 2357, $sCoupon), undef);
	}
	
1;
