Revision 816 (by ahitrov, 2021/02/17 20:47:36) Discount issues

package webshop::Discount;

use strict;
use warnings 'all';

use Contenido::Globals;
use base "Contenido::Document";
use Data::Dumper;

sub extra_properties
{
	return (
		{ 'attr' => 'code',				'hidden' => 1, 'column' => undef },
		{ 'attr' => 'class',				'column' => undef },
		{ 'attr' => 'dtime',				'rusname' => 'Начало действия скидки' },
		{ 'attr' => 'etime',				'rusname' => 'Окончание действия скидки' },
		{ 'attr' => 'status',	'type' => 'status',     'rusname' => 'Статус',
			'cases' => [
					[0, 'Скидка не активна'],
					[1, 'Скидка активна'],
				],
		},
		{ 'attr' => 'mechanic',	'type' => 'status',     'rusname' => 'Механика',
			'cases' => [
					['simple', 'Простая скидка'],
					['two_as_one', 'Скидка на каждую вторую покупку'],
				],
		},
		{ 'attr' => 'uid',	'type' => 'status',	'rusname' => 'Доступность для пользователей',
			'cases' => [
					[0, 'Скидка доступна всем пользователям'],
					[1, 'Скидка доступна только зарегистрированным пользователям'],
				],
		},
		{ 'attr' => 'trigger_sections',			'rusname' => 'Триггер: группы товаров',
				lookup_opts => { class => $state->{webshop}->{item_section_class}, },
				allow_null => 1,
				rem	=> 'Список разделов, позиции из которых активируют скидку.',
		},
		{ 'attr' => 'groups',				'rusname' => 'Группы товаров',
				lookup_opts => { class => $state->{webshop}->{item_section_class}, },
				allow_null => 1,
				rem	=> 'Список разделов, на содержимое которых распространяется скидка по купону.',
		},
		{ 'attr' => 'min_sum',	'type' => 'string',	'rusname' => 'Порог суммы, с которого действует скидка',
				default => 0, column => 6, shortname => 'Сумма заказа' },
		{ 'attr' => 'field',	'type' => 'string',	'rusname' => 'Название ценовой колонки', shortname => 'Колонка',
				column => 7,
				rem => 'Используется название, отмеченное серым цветом в "name=priceNN"' },
		{ 'attr' => 'discount',	'type' => 'string',	'rusname' => 'Размер скидки (число или процент)', shortname => 'Скидка',
				default => 0, column => 8,
				rem => 'Данное поле будет задействовано, если не указана ценовая колонка.', },
	)
}

sub class_name
{
	return 'Webshop: скидка';
}

sub class_description
{
	return 'Webshop: скидка';
}

sub class_table
{
	return 'webshop::SQL::CouponsTable';
}

sub contenido_status_style
{
	my $self = shift;
	if ( $self->status == 3 ) {
		return 'color:black;';
	} elsif ( $self->status == 2 ) {
		return 'color:red;';
	} elsif ( $self->status == 4 ) {
		return 'color:olive;';
	}
}

sub table_links
{
	return [
#		{ name => 'Купоны', class => 'webshop::Coupon', filter => 'pid', field => 'pid' },
	];
}


sub get_discount
{
    my $self = shift;

    my (%opts) = @_;
    unless ( exists $opts{basket} || exists $opts{uid} && $opts{uid} || exists $opts{session} && $opts{session} ) {
	return wantarray ? (0, []) : 0;
    }
    unless ( $self->discount ) {
	return wantarray ? (0, []) : 0;
    }

    my $basket = delete $opts{basket};
    $basket = $keeper->{webshop}->get_basket ( %opts )		unless $basket;
    unless ( ref $basket eq 'ARRAY' && @$basket ) {
	return wantarray ? (0, []) : 0;
    }

    my @basket = grep { exists $_->{item} && $_->{item} } @$basket;
    unless ( @basket ) {
	return wantarray ? (0, []) : 0;
    }

    my ($number, $sum_total) = (0, 0);
    map { $number += $_->number; $sum_total += $_->number * $_->{item}->price } @basket;
    warn "BASKET: $number Items of $sum_total Value\n"		if $DEBUG;

    my %item_props = map { $_->{attr} => $_ } $basket[0]->{item}->structure;
    if ( exists $item_props{special_price} ) {
	@basket = grep { !$_->{item}->special_price } @basket;
	unless (@basket) {
		return wantarray ? (0, []) : 0;
	}
    }

    # check if any of the item are from trigger section(s)
    my %ts = map { $_ => 1 } $self->trigger_sections;
    if ( %ts ) {
	my $triggered;
	foreach my $bi ( @basket ) {
		my @s = ref $bi->{item} ? $bi->{item}->sections : ();
		next	unless @s;
		foreach my $s ( @s ) {
			if ( exists $ts{$s} ) {
				$triggered = 1;
				last;
			}
		}
		last	if $triggered;
	}
	unless ( $triggered ) {
		return wantarray ? (0, []) : 0;
	}
    }

    my $discount_counted = 0;
    my $items;
    if ( $self->groups ) {
	$items = $self->keeper->get_documents (
			s	=> [$self->groups],
			class	=> $state->{webshop}->{item_document_class},
			ids	=> 1,
			return_mode	=> 'array_ref',
		);
	unless ( ref $items eq 'ARRAY' && @$items ) {
		return wantarray ? (0, []) : 0;
	}
    } else {
	$items = $self->keeper->get_documents (
			class	=> $state->{webshop}->{item_document_class},
			lclass	=> 'webshop::DiscountItemLink',
			lsource	=> $self->id,
			ids	=> 1,
			return_mode	=> 'array_ref',
		);
    }

    if ( ref $items eq 'ARRAY' && @$items ) {
	my %items = map { $_ => 1 } @$items;
	@basket = grep { exists $items{$_->item_id} } @basket;
	unless ( @basket ) {
		return wantarray ? (0, []) : 0;
	}
    }
    if ( $self->mechanic eq 'two_as_one' ) {
	$discount_counted = $self->mechanic_two_as_one( \@basket );
    } else {
	$discount_counted = $self->mechanic_simple( \@basket );
    }

    return wantarray ? ($discount_counted, \@basket) : $discount_counted;
}


sub mechanic_simple {
    my $self = shift;
    my $basket = shift;
    return 0	unless ref $basket eq 'ARRAY' && @$basket;

    my $discount = 0;
    my $price_field = $self->field;
    $price_field =~ s/\s//sg;
    $price_field ||= 'price';
    my $value = $self->discount;
    $value =~ s/\s//sg;
    return 0	unless $value;
    my ($proc, $fixed);
    if ( $value =~ /([\d\.]+)\s*%/ ) {
	$proc = $1;
    } elsif ( $value =~ /^\s*([\d\.]+)\s*$/ ) {
	$fixed = $1;
    } else {
	return 0;
    }

    foreach my $bi ( @$basket ) {
	my $item_discount = 0;
	if ( $proc ) {
		$item_discount += $bi->{item}->$price_field / 100 * $proc;
	} else {
		$item_discount += $bi->{item}->$price_field > $fixed ? $fixed : 0;
	}
	my $sum = ($bi->{item}->$price_field - $item_discount) * $bi->number;
	$bi->{sum_discounted}{$self->id}{sum_show} = $sum;
	$bi->{sum_discounted}{$self->id}{sum_bill} = $sum;
	$bi->{sum_discounted}{$self->id}{discount} = $item_discount * $bi->number;
	$discount += $bi->{sum_discounted}{$self->id}{discount};
    }

    return $discount;
}

sub mechanic_two_as_one {
    my $self = shift;
    my $basket = shift;
    return 0	unless ref $basket eq 'ARRAY';

    my @new_basket;
    foreach my $bi ( @$basket ) {
	$bi->{sum_discounted} = {};
	for ( my $i = 0; $i < $bi->number; $i++ ) {
		push @new_basket, $bi;
	}
    }
    return 0	if scalar @new_basket <= 1;

    my $price_field = $self->field;
    $price_field =~ s/\s//sg;
    $price_field ||= 'price';

    my $value = $self->discount;
    $value =~ s/\s//sg;
    $value ||= '100%';
    my ($proc, $fixed);
    if ( $value =~ /([\d\.]+)\s*%/ ) {
	$proc = $1;
    } elsif ( $value =~ /^\s*([\d\.]+)\s*$/ ) {
	$fixed = $1;
    } else {
	return 0;
    }

    my $discount = 0;
    my @sorted = sort { $b->{item}->$price_field <=> $a->{item}->$price_field } @new_basket;
    my $border = int(scalar(@sorted) / 2) + scalar(@sorted) % 2;

    my $sum = 0;
    for( my $i = 0; $i < scalar(@sorted); $i++ ) {
	my $bi = $sorted[$i];
	$sum += $bi->{item}->$price_field; 
	if ( $i >= $border ) {
		my $item_discount = 0;
		if ( $proc ) {
			$item_discount = $bi->{item}->$price_field * ($proc / 100);
		} else {
			$item_discount = $bi->{item}->$price_field > $fixed ? $fixed : 0;
		}
		$bi->{sum_discounted}{$self->id}{sum_show} += $bi->{item}->$price_field - $item_discount;
		$bi->{sum_discounted}{$self->id}{discount} += $item_discount;
		$discount += $item_discount;
	} else {
		$bi->{sum_discounted}{$self->id}{sum_show} += $bi->{item}->$price_field;
	} 
    }
    my $avg_discount = ($sum - $discount) / $sum;
    foreach my $bi ( @sorted ) {
	my $bill_price = $bi->{item}->$price_field * $avg_discount;
	$bi->{sum_discounted}{$self->id}{sum_bill} += $bill_price;
    }

    return $discount;
}

sub count_sum_discount
{
    my $self = shift;
    my $sum = shift;
    return 0	unless $sum;

    my $discount = $self->discount;
    my $count = 0;
    if ( $discount =~ /([\d\.]+)%/ ) {
	my $proc = $1;
	return 0	unless $proc;
	$count = $sum / 100 * $proc;
    } else {
	$count = $discount;
    }
    $count = 0		if $sum <= $count;
    return $count;
}



sub pre_store
{
	my $self = shift;

	my $default_section = $project->s_alias->{webshop_discounts}	if ref $project->s_alias eq 'HASH';
	if ( $default_section && !$self->sections ) {
		$self->sections($default_section);
	}

	if ( $self->discount ) {
		$self->{discount} =~ s/[^\d\.%]//sg;
	}

	return 1;
}


sub post_delete
{
    my $self = shift;

    my $sql = $self->keeper->SQL->prepare('DELETE FROM webshop_coupon_links where source_id = ?');
    $sql->execute( $self->id );

    1;
}

1;