1 package BSE::TB::Coupon;
4 our @ISA = qw/Squirrel::Row/;
5 use BSE::TB::CouponTiers;
7 our $VERSION = "1.007";
11 BSE::TB::Coupon - shop coupon objects
17 my $coupon = BSE::TB::Coupons->make(...);
21 Represents shop coupons.
30 return qw/id code description release expiry discount_percent campaign last_modified untiered
39 require BSE::Util::SQL;
42 last_modified => BSE::Util::SQL::now_sqldatetime(),
44 discount_percent => undef,
50 Return the tier ids for a coupon.
52 This includes an entry for tier "0" if the coupon is untiered.
61 ( $self->untiered ? ( 0 ) : () ),
62 BSE::TB::CouponTiers->getColumnBy
66 coupon_id => $self->id
71 return wantarray ? @tiers : \@tiers;
76 Return tier objects for each of the tiers this coupon is valid for.
83 require BSE::TB::PriceTiers;
84 return BSE::TB::PriceTiers->getSpecial(forCoupon => $self->id);
87 =item set_tiers(\@tiers)
89 Set the tiers for a coupon.
94 my ($self, $tiers) = @_;
96 my @tiers = grep $_, @$tiers;
97 $self->set_untiered((grep $_ == 0, @$tiers) ? 1 : 0);
99 my %current = map { $_->tier_id => $_ }
100 BSE::TB::CouponTiers->getBy2
103 coupon_id => $self->id
107 my %keep = map { $_->tier_id => $_ } grep $_, delete @current{@tiers};
109 $_->remove for values %current;
111 for my $tier_id (grep !$keep{$_}, @tiers) {
112 BSE::TB::CouponTiers->make
114 coupon_id => $self->id,
128 my @tiers = BSE::TB::CouponTiers->getBy2
131 coupon_id => $self->id
134 $_->remove for @tiers;
136 $self->SUPER::remove();
142 my $data = $self->data_only;
143 $data->{tiers} = [ $self->tiers ];
144 $data->{config_obj} = $self->config_obj;
145 delete @$data{qw/config discount_percent/};
152 Returns true if the coupon has expired.
159 require BSE::Util::SQL;
160 return BSE::Util::SQL::now_sqldate() gt $self->expiry;
165 Returns true if the coupon has been released.
172 require BSE::Util::SQL;
173 return $self->release le BSE::Util::SQL::now_sqldate();
178 Returns true if the coupon is both released and unexpired.
185 return $self->is_released && !$self->is_expired;
190 Return true if the coupon can be removed.
197 require BSE::TB::Orders;
198 return !BSE::TB::Orders->getExists([ coupon_id => $self->id ]);
203 Return true if the name can be changed.
205 This is currently equivalent to is_removable().
212 return $self->is_removable;
217 Returns a list of (is active, message) for the given cart.
219 Wrapper around is_active() for the behaviour.
221 my ($active, $msg) = $coupon->is_active($cart);
226 my ($self, $cart) = @_;
228 return $self->behaviour->is_active($self, $cart);
233 Return the discount in cents for the given cart.
235 Must only be called if is_active() returned the coupon as active.
237 Wrapper around discount() for the behaviour.
239 my ($cents) = $coupon->discount($cart);
244 my ($self, $cart) = @_;
246 return $self->behaviour->discount($self, $cart);
251 Return true if the given cart item is valid for the coupon.
253 Only relevant for cart-wide coupons.
258 my ($self, $cart, $index) = @_;
260 return $self->behaviour->product_valid($self, $cart, $index);
263 =item product_discount
265 Return the product specific discount per unit for the given row
266 (counting from zero) in the cart.
268 Must only be called if is_active() returned the coupon as active.
270 Returns zero if the coupon discount is for the cart as a whole.
272 Wrapper around product_discount() for the behaviour.
274 my $cents = $coupon->product_discount($cart, $index);
278 sub product_discount {
279 my ($self, $cart, $index) = @_;
281 return $self->behaviour->product_discount($self, $cart, $index);
284 =item product_discount_units
286 Return the number of units a product specific discount applies to the given row
287 (counting from zero) in the cart.
289 Must only be called if is_active() returned the coupon as active.
291 Returns zero if the coupon discount is for the cart as a whole.
293 Wrapper around product_discount_units() for the behaviour.
295 my $cents = $coupon->product_discount_units($cart, $index);
299 sub product_discount_units {
300 my ($self, $cart, $index) = @_;
302 return $self->behaviour->product_discount_units($self, $cart, $index);
307 Describe the behaviour of the coupon briefly.
314 $self->behaviour->describe;
317 =item cart_wide($cart)
319 Returns true if the discount provided by the behaviour applies to the
325 my ($self, $cart) = @_;
327 return $self->behaviour->cart_wide($cart);
330 =item set_code($code)
332 Set the coupon code. Requires that is_renamable() be true.
337 my ($self, $code) = @_;
342 $self->{code} = $code;
348 my $bclasses = BSE::TB::Coupons->behaviour_classes;
354 description => "Coupon Code",
359 rules => "dh_one_line;coupon_code",
363 description => "Description",
367 rules => "dh_one_line",
371 description => "Release Date",
380 description => "Expiry Date",
389 description => "Campaign",
393 rules => "dh_one_line",
397 description => "Price Tiers",
398 htmltype => "multicheck",
402 require BSE::TB::PriceTiers;
405 { id => 0, description => "Untiered" },
406 BSE::TB::PriceTiers->getColumnsBy
408 [ qw(id description) ],
410 { order => "display_order asc" },
415 label => "description",
420 description => "Coupon Class",
421 htmltype => "select",
428 sort { lc $a->{label} cmp lc $b->{label} }
432 label => $bclasses->{$_}->class_description,
434 sort { lc $bclasses->{$a}->class_description cmp lc $bclasses->{$b}->class_description}
441 if (ref $self && !$self->is_renamable) {
442 $fields{code}{readonly} = 1;
445 require BSE::Validate;
446 return BSE::Validate::bse_configure_fields(\%fields, BSE::Cfg->single, "bse coupon validation");
454 match => qr/\A[a-zA-Z0-9]+\z/,
455 error => '$n can only contain letters and digits',
463 my $config = $self->config;
464 $config = "{}" if $config eq "";
467 my $obj = JSON->new->decode($config);
468 $obj->{discount_percent} = $self->discount_percent;
474 my ($self, $obj) = @_;
476 $self->set_discount_percent(delete $obj->{discount_percent});
479 $self->set_config(JSON->new->encode($obj));
485 delete $self->{_behaviour}
486 if $self->{_classid} ne $self->classid
487 || $self->{_config} ne $self->config;
489 $self->{_behaviour} ||=
490 BSE::TB::Coupons->behaviour_class($self->classid)->new($self->config_obj);
491 $self->{_classid} = $self->classid;
492 $self->{_config} = $self->config;
503 Tony Cook <tony@develop-help.com>