CookieMonster's avatar

how to calculate the courier rate based on weight and postcode?

I am currently trying to figure out a way to calculate my parcel rate based on a few factors. So I have different couriers (A, B, C, etc). And each courier service has it's own rate to deliver a parcel. To give an idea, here's a screenshot of my layout:

https://ibb.co/tZCXQz5

I can select the weight of my parcel (in kg) and for now I limit it from 1-30kg. I can also select parcel size(box size,rectangular size,etc). The troublesome part is I have to select a postcode for delivery and pickup.

After selecting everything and proceed, it will display me all available couriers along with the rates. For example,

2.30$.  Courier A

5.00$.  Courier B

1.30$. Courier C 

etc

I do have a list of rate with me like:

Weight(kg).         Price
1							8.00
2							9.10
3							11.10
4							13.10
5							15.10
......

I'm kinda stuck on this issue, do I need to use some kind of package/library?

how would I go about this?

0 likes
11 replies
geraintp's avatar

it's a pretty simple collection pipeline,

$packageWeight = 2.1;

$courierRates = [
    [ 'weight' => 1, 'cost' => 8.00 ],
    [ 'weight' => 2, 'cost' => 9.10 ],
    [ 'weight' => 3, 'cost' => 11.10 ],
    [ 'weight' => 4, 'cost' => 13.10 ],
    [ 'weight' => 5, 'cost' => 15.10 ],
];

$rate = collect($courierRates)
    ->sortBy('weight') // optional: ensures the rates are in weight order
    ->first(fn($rate) =>  $rate['weight'] >= $packageWeight);

$rate['cost']; // = 11.10
CookieMonster's avatar

So I don't need a table to keep track of the rates(from different couriers)?

The rates are all fixed, so just keep track in the controller?

Do you mind elaborating your answer?

Package weight represents the weight I selected, what if I have 10 different couriers each with their own rates?

Also, how do you keep track say:

['weight' => 1, 'cost' => 8.00(west penisular) , 'cost' => 10.00(east penisular) ]

The cost varies depending on the location.

geraintp's avatar

Where you store that data is entirely up to you, if it makes sense to have it in a model/DB you can. or you can create your own simple courier class /factory whatever you need, with the rates hardcoded (not ideal) or loaded from a file, csv, json whatever.

so you could do

$packageWeight = 1.1;
$destination = 'b';

$courierRates = [
    [ 'weight' => 1, 'cost' => 8.00, 'location' => 'a' ],
    [ 'weight' => 1, 'cost' => 10.00, 'location' => 'b' ],
    [ 'weight' => 2, 'cost' => 9.10, 'location' => 'a' ],
    [ 'weight' => 2, 'cost' => 11.10, 'location' => 'b' ],
	...
];

$rate = collect($courierRates)
	->filter(fn($rate) => $rate['location'] == $destination)
    ->sortBy('weight') // optional: ensures the rates are in weight order
    ->first(fn($rate) =>  $rate['weight'] >= $packageWeight);

$rate['cost']; // = 11.10

or if you've got eloquent models for the couriers, rates & locations you could do something like this.

$shippingCosts = Courier::with('rates.location')->get()
    ->mapWithKeys(function($courier) use ($destination, $weight) {
        return [ 
            $courier->name => $courier->rates
                                        ->filter(fn($rate) => $rate->location->name == $destination)
                                        ->sort('weight')
                                        ->first(fn($rate) => $rate->weigth >= $weight)->cost
        ];
    });

$shippingCosts == [
    'Courier A' => 3.00,
    'Courier B' => 3.00,
    'Courier C' => 3.00,
];

N.B. that's also a pretty bad eloquent query you'd probably want to query / scope it by the location so you only load the relevant rates not all of them if you have hundreds..

class Rate extends Model
{
    protected $fillable = [
        'courier_id',
        'weight',
        'cost',
        'location_id',
    ];

    public function location()
    {
        return $this->belongsTo(Location::class);
    }

    public function courier()
    {
        return $this->belongsTo(Courier::class);
    }
}
class Courier extends Model
{
    public function rates()
    {
        return $this->hasMany(Rate::class);
    }

    public function locations()
    {
        return $this->hasManyThrough(Location::class, Rate::class);
    }
}
class Location extends Model
{

    protected $fillable = [
        'name',
    ];
}
CookieMonster's avatar

Thanks for the write up. I just felt that storing everything in the database is a bit taxing later on.

Cause based on the screenshot, as I input the postcode which determines the district (city). However, just to let you know, I have 11 states in my country and west states consist of 9 states while east states consist of 2 states. So the rate in any of the the west states(based on the postcode) is always fixed unless it's the east states where the rate is slightly higher. Regardless if the postcode pickup and postcode delivery is within the west states, then the rate is fixed (say 8$ for 3kg parcel).

I thought of inputing all the postcode in my country in a table to cross check but there's like way too many to do so. When I submit the form, I surely need to cross check the postcode value to see which area it is to follow the west state rate or the east state rate. I hope I am making sense :).

So you think best is to create a few eloquent relationship models?

Edit:

In my Location model, perhaps I thought I could implement something like:

location table

region.      			
west states           
east states

and using relationship to check the weight and courier to get the rate.

jlrdw's avatar

At a medium sized trucking and logistics company I worked for we had rate tables, lookup tables.

CookieMonster's avatar

So did you save the rates in db and form eloquent relationships?

jlrdw's avatar

This was a while back in my Java days. We basically had a series of look up tables where you know that a load of pecans (example) would be haulded by a certain carrier for x amount per pound or ton, etc. But yes set up some basic relationships to help you. Surely each carrier has various rates I'm guessing. And of course length to the destination must be considered.

Remember also many larger carriers have apis you can tap into or updated rates you can download and import one of the other.

Another thing to consider is negotiated rates that remain stable x amount of time.

CookieMonster's avatar

As far as I am concerned some of the couriers here do not have API. So does the answer above using relationship seem like the right logic?

martinbean's avatar

@nickywan123 What is it you’re actually asking? If you know the weight of your parcel and the postcode it’s going to, and you query individual couriers with this information to get prices for that weight and postcode, what is it you’re actually struggling with?

CookieMonster's avatar

I'm struggling with forming the relationships to get the final rate. So maybe I can rephrase the problem in a better way. From the screenshot, I can enter my weight as well as postcode for pick up and postcode for delivery. Cause each postcode correspond to an area. I have 13 states in my country. Out of the 13 states, 11 states are in west region and the remaining 2 states are in east region. The west region postcodes are all the same rate(depending on the couriers) while the east region postcodes are slightly at a higher rate (depending on the couriers).

So when I submit the form (as shown in the screenshot), how do I cross check the postcode I entered that matches with the region and display the quotation from all the couriers?

jlrdw's avatar

If there were no computers, ask yourself how would you do it. You would probably have a rate sheet from various carriers that you get out and you look up the rate. So basically workout some relationships that make sense.

1 like

Please or to participate in this conversation.