Basket-Calc

 view release on metacpan or  search on metacpan

lib/Basket/Calc.pm  view on Meta::CPAN

package Basket::Calc;

use 5.010001;
use Mouse;
use experimental 'smartmatch';

# ABSTRACT: Basket/Cart calculation library with support for currency conversion, discounts and tax

our $VERSION = '0.5'; # VERSION

use Scalar::Util qw(looks_like_number);
use Finance::Currency::Convert::Yahoo;
use Carp;


has 'debug' => (
    is      => 'rw',
    isa     => 'Bool',
    trigger => \&_set_debug,
    lazy    => 1,
    default => sub { 0 },
);

has 'items' => (
    is      => 'rw',
    isa     => 'ArrayRef',
    clearer => 'empty_items',
);

has 'discount' => (
    is      => 'rw',
    isa     => 'HashRef',
    clearer => 'no_discount',
);


has 'currency' => (
    is       => 'rw',
    isa      => 'Str',
    required => 1,
);


has 'tax' => (
    is      => 'rw',
    isa     => 'Num',
    lazy    => 1,
    default => sub { 0 },
);


sub add_item {
    my ($self, $item) = @_;

    # make sure the input is sane
    unless (ref $item eq 'HASH') {
        carp "parameter has to be a HASHREF";
        return;
    }

    foreach my $key ('price') {
        unless (exists $item->{$key} and $item->{$key}) {
            carp "$key missing";
            return;
        }
    }

    unless (looks_like_number($item->{price})) {
        carp "'price' is not a number";
        return;
    }

    # calculate amount from quantity and price
    if (exists $item->{quantity}) {
        if (!looks_like_number($item->{quantity}) || ($item->{quantity} < 0)) {
            carp "'quantity' is not a number or smaller than 0";
            return;
        }

        $item->{amount} = $item->{price} * $item->{quantity};
    }
    else {
        $item->{amount}   = $item->{price};
        $item->{quantity} = 1;
    }

    $item->{currency} = $self->currency
        unless (exists $item->{currency} and $item->{currency});

    # convert currency if needed
    if ($item->{currency} ne $self->currency) {
        my $amount =
            Finance::Currency::Convert::Yahoo::convert($item->{amount},
            $item->{currency}, $self->currency);

        unless ($amount) {
            carp "could not get "
                . $item->{amount} . " "
                . $item->{currency}
                . " converted to "
                . $self->currency;
            return;
        }

        $item->{orig_amount}   = $item->{amount};
        $item->{orig_currency} = $item->{currency};

        $item->{amount}   = $amount;
        $item->{currency} = $self->currency;
    }

    print __PACKAGE__ . ' added item: ' . join(' ', %$item) . $/
        if $self->debug;

    $self->items([ @{ $self->items || [] }, $item ]);

    return $item;
}


sub add_discount {
    my ($self, $discount) = @_;

    # make sure the input is sane
    unless (ref $discount eq 'HASH') {
        carp "parameter has to be a HASHREF";
        return;
    }

    foreach my $key ('type', 'value') {
        unless (exists $discount->{$key} and $discount->{$key}) {
            carp "'$key' missing";
            return;
        }
    }

    unless ($discount->{type} =~ m/^(percent|amount)$/x) {
        carp "'type' has to be either percent, or amount";
        return;
    }

    unless (looks_like_number($discount->{value})) {
        carp "'value' is not a number";
        return;
    }

    given ($discount->{type}) {
        when ('percent') {
            if ($discount->{value} <= 0 or $discount->{value} > 1) {
                carp "'percent' has to be a decimal between 0 and 1";
                return;
            }
        }
        when ('amount') {
            $discount->{currency} = $self->currency
                unless exists $discount->{currency};

            # convert currency if needed
            if ($discount->{currency} ne $self->currency) {
                my $amount = Finance::Currency::Convert::Yahoo::convert(
                    $discount->{value}, $discount->{currency}, $self->currency);

                unless ($amount) {
                    carp "could not get "
                        . $discount->{value} . " "
                        . $discount->{currency}
                        . " converted to "
                        . $self->currency;
                    return;
                }

                $discount->{orig_value}    = $discount->{value};
                $discount->{orig_currency} = $discount->{currency};

                $discount->{value}    = $amount;
                $discount->{currency} = $self->currency;
            }
        }
    }

    print __PACKAGE__ . ' added discount: ' . join(' ', %$discount) . $/
        if $self->debug;

    $self->discount($discount);

    return $self->discount;
}


sub calculate {
    my ($self) = @_;

    unless ($self->items) {
        carp "no items added";
        return;
    }

    my $total = {
        value      => 0,
        net        => 0,
        tax_amount => 0,
        discount   => 0,



( run in 5.676 seconds using v1.01-cache-2.11-cpan-a5162978ef8 )