Jump to content

Postcode based delivery price


mrjiggles
 Share

Recommended Posts

Hi All, Long long long time lurker here! 

 

A little background to understand how this fits in

So I am building a local directory for my local area as part of a community project. Part of this there is the ability for local stores to sell online locally for people to have delivered or collect. So in the UK we have postcodes in the formats:

AB12 3CD,

A1 2BC

A12 3CD,

A1B 2CD

 

Most delivery pricing solutions only care about the full postcode as they are all about national delivery or at best only care about the first half. Due to the local nature we need more granularity to it so we have some rules: 

Delivery Available

EH => Price

EH3 => Price

EH3 1 => Price

EH3 1-4 => Price

 

Exceptions - No Delivery allowed

EH => null

EH3 => null

EH3 1 => null

EH3 1-4 => null

 

Can mix and match for example:

 

EH3 = £2.00

EH3 2 => £2.25

EH3 5 => null

 

This means that ALL EH3 address the delivery cost is £2 BUT If they are in EH3 2 then its £2.25 or if the are in EH3 5 then no delivery is possible.

 

I have came up with this monstrosity of code that for the most part works but also can throw the wrong delivery prices out due to bugs and issues that I can't seem to work out!  

 

Main Function:

function isDeliverable($postcode, $rules){
		
		$canDeliver = false;
		$deliveryValue = 0.00;
		$found = false;
			
		list($outward, $inward) = explode(' ', $postcode);
		$area = substr($outward, 0, 2);
		$district = substr($outward, 2);
		$sector = substr($inward, 0, 1);
		$unit = substr($inward, 1,1);
		
		$rulez = json_decode($rules, true);
		
		
		//RULE START - EH10 9RJ
		$pcFound = inRule($rulez, $postcode);
		
		if($pcFound){
			return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];
		}
		//RULE END - EH10 9RJ
		
		
		
		
		
		
		//RULE START - EH10 9R
		$pcFound = inRule($rulez, $area.$district.' '.$sector.$unit);
		
		if($pcFound && !strpos($pcFound['postcode'], "-")){
			return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];
		}
		//RULE END - EH10 9R
		
		
		
		
		
		
		//RULE START - EH10 9A-F
		$pcFound = inRule($rulez, $area.$district.' '.$sector.$unit, 1);
				
		if($pcFound){
			
			$postArray = postcodeExploder($pcFound, 1);
			
			$pcFound = inRule($postArray, $area.$district.' '.$sector.$unit);
		
			if($pcFound){
				return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];
			}			
		}
		//RULE END - EH10 9A-F
		
		
		
		
		
		
		//RULE START - EH10 9
		$pcFound = inRule($rulez, $area.$district.' '.$sector);
				
		if($pcFound && !strpos($pcFound['postcode'], "-")){
			return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];			
		}
		//RULE END - EH10 9
		
		
		
		
		
		//RULE START - EH10 1-4
		$pcFound = inRule($rulez, $area.$district.' '.$sector, 2);

		if($pcFound){
			$postArray = postcodeExploder($pcFound, 2);

			$pcFound = inRule($postArray, $area.$district.' '.$sector);
			
			if($pcFound){
				return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];
			}			
		}
		//RULE END - EH10 1-4
		
		
		
		
		
		//RULE START - EH10
		$pcFound = inRule($rulez, $area.$district);

		if($pcFound && !strpos($pcFound['postcode'], "-")){
			return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];			
		}
		//RULE END - EH20
		
		
		
		
		
		//RULE START - EH1-20
		$pcFound = inRule($rulez, $area.$district, 3);
		
		if($pcFound){

			$postArray = postcodeExploder($pcFound, 3);

			$pcFound = inRule($postArray, $area.$district);
		
			if($pcFound){
				return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];
			}			
		}
		//RULE END - EH1-20
		
		
		
		
		
		//RULE START - EH
		$pcFound = inRule($rulez, $area);
		
		if($pcFound && !strpos($pcFound['postcode'], "-")){
			return ['canDeliver' => $pcFound['deliverable'], 'deliveryValue' => $pcFound['price']];			
		}
		//RULE END - EH
		
	
		return ['canDeliver' => $canDeliver, 'deliveryValue' => $deliveryValue]; 
	}

 

Helper Functions:

function inRule($rules, $postcode, $type = null){
		
		foreach($rules as $key => $rule){
			
			if(substr_count($rule['postcode'], '-') !== 0 && strlen($postcode) > 2){
				$pEX = postcodeExploder($rule, $type);

				foreach($pEX as $r){
					
					if($r['postcode'] == $postcode){
						return $rules[$key];
					}
					//$rule['postcode'] = trim(substr($rule['postcode'], 0, strpos($rule['postcode'], "-")-1));
				}
			}		
			
		   
			if ( $rule['postcode'] == $postcode )
		    	return $rules[$key];
		}
		
		return false;
	}
	
	function postcodeExploder($rule, $type){
		$out = [];
		
		$r = explode(' ', $rule['postcode']);
    	$count = count($r);
    					    	
    	//It must be in the form AB1 1C
    	//$first = AB1
    	//$last = 1C-G
    	if($count == 2){
    		list($first, $last) = $r;
    	} else {
	    	$last = $r[0];
    	}
		
		list($left, $right) = explode('-', $last);
			   	
	    $sec = $left[0];

    	$leftInward = substr($last, strpos($last, '-')-1,1);
    	$rightInward = substr($last, strpos($last, '-')+1,1);
    	
		$range = range($leftInward, $rightInward);	

		foreach($range as $key => $ra){
			
			if($type == 1){
			
				$out['a'.$key] = [
					'postcode' => $first.' '.$sec.$ra,
					'deliverable' => $rule['deliverable'],
					'price' => $rule['price']
				];
				
			} else if($type == 2){
				
				$out['a'.$key] = [
					'postcode' => $first.' '.$ra,
					'deliverable' => $rule['deliverable'],
					'price' => $rule['price']
				];
				
			} else {
				
				$out['a'.$key] = [
					'postcode' => preg_replace('/\PL/u', '', $left).$ra,
					'deliverable' => $rule['deliverable'],
					'price' => $rule['price']
				];
				
			}
		}
		
		return $out;
		
	}

Stores Rules:

	$rules = '{"a1":{"postcode":"EH9","deliverable":true,"price":"1.50"},"a2":{"postcode":"EH9 7","deliverable":true,"price":"1.60"},"a3":{"postcode":"EH9 7A","deliverable":true,"price":"1.70"},"a4":{"postcode":"EH9 7AY","deliverable":true,"price":"1.80"},"a5":{"postcode":"EH1-2","deliverable":true,"price":"1.90"},"a6":{"postcode":"EH1 2-3","deliverable":true,"price":"2.00"},"a7":{"postcode":"EH4 5A-N","deliverable":true,"price":"2.10"},"a8":{"postcode":"EH","deliverable":true,"price":"1.40"},"a9":{"postcode":"TD14","deliverable":true,"price":"2.00"},"a10":{"postcode":"TD14 5DC","deliverable":false,"price":null}}';

 

Link to comment
Share on other sites

That would be the easiest but during our research we found that the local takeaways for example don't do it based on distance but postcode! Also those distances would be as the crow flies meaning places that are considered close by that method are longer to get to/ further to drive to than somewhere that's further. Sadly the easy way out isn't right annoyingly. 

Link to comment
Share on other sites

i don't get any logic in that

 

Quote

EH => Price

EH3 => Price

EH3 1 => Price

EH3 1-4 => Price

 

Exceptions - No Delivery allowed

EH => null

EH3 => null

EH3 1 => null

EH3 1-4 => null

 

Can mix and match for example:

 

EH3 = £2.00

EH3 2 => £2.25

EH3 5 => null

so why is EH totally missing, EH3 2 is undefined but gets a price and EH3 gets a price even that it's an exeption? Makes no sense for me.

Edited by chhorn
Link to comment
Share on other sites

44 minutes ago, chhorn said:

i don't get any logic in that

 

so why is EH totally missing, EH3 2 is undefined but gets a price and EH3 gets a price even that it's an exeption? Makes no sense for me.

Sorry that wasn't an example but a formatting and structure example. 

A real example of the rules in the code. (Will remove that from my post as It is confusing.)

Link to comment
Share on other sites

I have been playing around with a possible database solution to your problem

Given that a postcode such as "EH12 3AB" breaks down into four parts viz

+------+----------+--------+------+
| area | district | sector | unit |
+------+----------+--------+------+
|  EH  |    12    |    3   |  AB  |
+------+----------+--------+------+

... I was toying with this table structure

CREATE TABLE `postcode` (
  `pc_id` int(11) NOT NULL AUTO_INCREMENT,
  `seller` int(11) DEFAULT NULL,
  `area` varchar(2) DEFAULT NULL,
  `district` varchar(2) DEFAULT NULL,
  `sector_min` char(1) DEFAULT NULL,
  `sector_max` char(1) DEFAULT NULL,
  `unit_min` char(2) DEFAULT NULL,
  `unit_max` char(2) DEFAULT NULL,
  `deliverable` tinyint(4) DEFAULT NULL,
  `price` decimal(8,2) DEFAULT NULL,
  PRIMARY KEY (`pc_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

+-------+--------+------+----------+------------+------------+----------+----------+-------------+-------+
| pc_id | seller | area | district | sector_min | sector_max | unit_min | unit_max | deliverable | price |
+-------+--------+------+----------+------------+------------+----------+----------+-------------+-------+
|     1 |      1 | EH   | 1        | 1          | 4          | AA       | ZZ       |           1 |  1.50 |
|     2 |      1 | EH   | 1        | 5          | 5          | AA       | BZ       |           1 |  1.80 |
|     3 |      1 | EH   | 1        | 5          | 5          | CA       | ZZ       |           0 |  2.00 |
|     4 |      1 | EH   | 2        | 1          | 9          | AA       | ZZ       |           1 |  2.25 |
|     5 |      1 | EH   | 3        | 1          | 9          | AA       | PZ       |           1 |  2.50 |
+-------+--------+------+----------+------------+------------+----------+----------+-------------+-------+

My code was

    $postcodes = [ 'EH1 2DB',
                   'eh15bg' ,
                   'eh1 5ba',
                   'eh15dg',
                   'EH2 7HJ',
                   'EH3 2PT',
                   'EH3 8SX',
                   'EH146DE'
                   ];
    
    echo '<pre>';               
    foreach ($postcodes as $pc) {
        vprintf('%s%s %s%s : %s<br>', deliveryPrice($db, $pc));
    }
    echo '</pre>';

function deliveryPrice($db, $pcode)
{
    $pcode = strtoupper(str_replace(' ', '', $pcode));
    $area = $district = '';
    $sector = substr($pcode,-3, 1);
    $unit = substr($pcode, -2);
    $l = strlen($pcode);
    $first = str_split(substr($pcode, 0, $l-3));
    foreach ($first as $c) {
        if (ctype_digit($c)) {
            $district .= $c;
        }
        else {
            $area .= $c;
        }
    }
    $res = $db->prepare("SELECT price
                         FROM postcode
                         WHERE area = ?
                           AND district = ?
                           AND ? between sector_min AND sector_max
                           AND ? BETWEEN unit_min AND unit_max
                           AND deliverable
                        ");
    $res->execute( [ $area, $district, $sector, $unit ] );
    $p = $res->fetchColumn();
    $price = $p ? number_format($p, 2) : 'N/A';
    return [$area, $district, $sector, $unit, $price ];
}
    

RESULTS:

EH1 2DB : 1.50
EH1 5BG : 1.80
EH1 5BA : 1.80
EH1 5DG : N/A
EH2 7HJ : 2.25
EH3 2PT : 2.50
EH3 8SX : N/A
EH14 6DE : N/A

 

Edited by Barand
  • Like 2
Link to comment
Share on other sites

This thread is more than a year old.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.