def test_first_visit_discount_zero_demand(self): current_demand = [0 for x in range(0, PRICE_BLOCK_SIZE)] DISCOUNT = 20 discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = DISCOUNT discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = False regular_price = 1000 * random() prices = calc_client_prices(self.tz, discounts, None, [ regular_price, ], current_demand) assert len(prices) == PRICE_BLOCK_SIZE # Full discount on all days because of zero demand for price in prices: assert price.price == Decimal(regular_price * (1 - DISCOUNT / 100.0)).quantize( 1, ROUND_HALF_UP) assert price.applied_discount == DiscountType.FIRST_BOOKING
def test_no_applicable_discount(self): # Zero demand on all days current_demand = [0 for x in range(0, PRICE_BLOCK_SIZE)] DISCOUNT1 = 20 DISCOUNT2 = 30 DISCOUNT3 = 40 discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = DISCOUNT1 discounts.revisit_within_1week_percentage = DISCOUNT3 discounts.revisit_within_2week_percentage = DISCOUNT2 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = False last_visit_date = date(2018, 5, 25) regular_price = 1000 * random() prices = calc_client_prices(self.tz, discounts, last_visit_date, [ regular_price, ], current_demand) assert len(prices) == PRICE_BLOCK_SIZE # No discount on any days for i in range(0, PRICE_BLOCK_SIZE): assert prices[i].price == Decimal(regular_price).quantize( 1, ROUND_HALF_UP) assert prices[i].applied_discount is None
def test_multiple_applicable_discount(self): # Zero demand on all days current_demand = [0 for x in range(0, PRICE_BLOCK_SIZE)] DISCOUNT1 = 30 DISCOUNT2 = 40 discounts = DiscountSettings() discounts.weekday_discounts = {Weekday.SATURDAY: DISCOUNT1} discounts.first_visit_percentage = DISCOUNT2 discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = False regular_price = 1000 * random() prices = calc_client_prices(self.tz, discounts, None, [ regular_price, ], current_demand) assert len(prices) == PRICE_BLOCK_SIZE # First time discount on all days for i in range(0, PRICE_BLOCK_SIZE): assert prices[i].price == Decimal( regular_price * (1 - DISCOUNT2 / 100.0)).quantize( 1, ROUND_HALF_UP) assert prices[i].applied_discount == DiscountType.FIRST_BOOKING
def generate_prices_for_stylist_service( services: List[StylistService], client: Optional[Client], exclude_fully_booked: bool = False, exclude_unavailable_days: bool = False) -> Iterable[PriceOnDate]: """ Generate prices for given stylist, client and service for PRICE_BLOCK_SIZE days ahead :param service: Service to generate prices for :param client: (optional) Client object, if omitted no client-specific discounts will apply :param exclude_fully_booked: whether or not remove fully booked dates :param exclude_unavailable_days: whether or not remove unavailable dates :return: Iterator over (date, CalculatedPrice, fully_booked boolean) """ stylist = services[0].stylist last_visit_date = get_last_visit_date_for_client( stylist, client) if client else None today = stylist.get_current_now().date() dates_list = [ today + datetime.timedelta(days=i) for i in range(0, PRICE_BLOCK_SIZE) ] demand_on_dates = generate_demand_list_for_stylist(stylist=stylist, dates=dates_list) demand_list = [x.demand for x in demand_on_dates] discounts = generate_discount_settings_for_stylist(stylist) prices_list = calc_client_prices( stylist.salon.timezone, discounts, last_visit_date, [float(x.regular_price) for x in services], demand_list) is_fully_booked_list = [d.is_fully_booked for d in demand_on_dates] is_working_day_list = [d.is_working_day for d in demand_on_dates] prices_on_dates: Iterable[PriceOnDate] = [ PriceOnDate._make(x) for x in zip( dates_list, prices_list, is_fully_booked_list, is_working_day_list) ] if exclude_fully_booked: # remove dates where demand is equal to COMPLETELY_BOOKED_DEMAND prices_on_dates = compress(prices_on_dates, is_fully_booked_list) if exclude_unavailable_days: # remove dates where demand is equal to UNAVAILABLE_DEMAND prices_on_dates = compress(prices_on_dates, is_working_day_list) return prices_on_dates
def test_invalid_arguments(self): # Missing demand data with pytest.raises(ValueError): discounts = DiscountSettings() discounts.weekday_discounts = {} calc_client_prices(self.tz, discounts, None, [0.0], []) # Invalid demand values with pytest.raises(ValueError): discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = 0 discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = True current_demand = [2 for x in range(0, PRICE_BLOCK_SIZE)] # Invalid calc_client_prices(self.tz, discounts, None, [0.0], current_demand) # Invalid discount values with pytest.raises(ValueError): discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = 200 # Invalid discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = True current_demand = [0 for x in range(0, PRICE_BLOCK_SIZE)] calc_client_prices(self.tz, discounts, None, [0.0], current_demand) # Invalid discount values with pytest.raises(ValueError): discounts = DiscountSettings() discounts.weekday_discounts = {Weekday.MONDAY: -10} # Invalid discounts.first_visit_percentage = 0 discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = True current_demand = [0 for x in range(0, PRICE_BLOCK_SIZE)] calc_client_prices(self.tz, discounts, None, [0.0], current_demand)
def test_revisit_within_1and2week(self): # Zero demand on all days current_demand = [0 for x in range(0, PRICE_BLOCK_SIZE)] DISCOUNT1 = 50 DISCOUNT2 = 40 DISCOUNT3 = 30 discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = DISCOUNT1 discounts.revisit_within_1week_percentage = DISCOUNT2 discounts.revisit_within_2week_percentage = DISCOUNT3 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = False last_visit_date = date(2018, 6, 2) regular_price = 1000 * random() prices = calc_client_prices(self.tz, discounts, last_visit_date, [ regular_price, ], current_demand) assert len(prices) == PRICE_BLOCK_SIZE # revisit_within_1week_percentage discount on first day assert prices[0].price == Decimal(regular_price * (1 - DISCOUNT2 / 100.0)).quantize( 1, ROUND_HALF_UP) assert prices[0].applied_discount == DiscountType.REVISIT_WITHIN_1WEEK # revisit_within_2week_percentage discount on next 7 days for i in range(1, min(8, PRICE_BLOCK_SIZE)): assert prices[i].price == Decimal( regular_price * (1 - DISCOUNT3 / 100.0)).quantize( 1, ROUND_HALF_UP) assert prices[ i].applied_discount is DiscountType.REVISIT_WITHIN_2WEEK # No discount for the rest of days for i in range(8, PRICE_BLOCK_SIZE): assert prices[i].price == Decimal(regular_price).quantize( 1, ROUND_HALF_UP) assert prices[i].applied_discount is None
def test_first_visit_discount_specific_day_demand(self): # Full demand on all days except first and second current_demand = [1 for x in range(0, PRICE_BLOCK_SIZE)] current_demand[0] = 0 PARTIAL_DEMAND = 0.5 current_demand[1] = PARTIAL_DEMAND DISCOUNT = 20 discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = DISCOUNT discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = False regular_price = 1000 * random() prices = calc_client_prices(self.tz, discounts, None, [ regular_price, ], current_demand) assert len(prices) == PRICE_BLOCK_SIZE # Full discount on zero demand day assert prices[0].price == Decimal( regular_price * (1 - DISCOUNT / 100.0)).quantize(1, ROUND_HALF_UP) assert prices[0].applied_discount == DiscountType.FIRST_BOOKING # Partial discount on partial demand day assert prices[1].price == Decimal( regular_price * (1 - DISCOUNT / 100.0 * PARTIAL_DEMAND)).quantize( 1, ROUND_HALF_UP) assert prices[1].applied_discount == DiscountType.FIRST_BOOKING # No discount on all other days for price in prices[2:]: assert price.price == Decimal(regular_price).quantize( 1, ROUND_HALF_UP) assert price.applied_discount is None
def test_no_input_discount(self): current_demand = [random() for x in range(0, PRICE_BLOCK_SIZE)] discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = 0 discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = True regular_price = 1000 * random() prices = calc_client_prices(self.tz, discounts, None, [ regular_price, ], current_demand) assert len(prices) == PRICE_BLOCK_SIZE # No discount on any day because no discounts are defined for price in prices: assert price.price == Decimal(regular_price).quantize( 1, ROUND_HALF_UP) assert price.applied_discount is None
def test_partial_demand_with_max_discount(self): current_demand = [0] * PRICE_BLOCK_SIZE current_demand[0] = 0.11 current_demand[1] = 0.22 current_demand[2] = 0.33 current_demand[3] = 0.44 current_demand[4] = 0.55 current_demand[5] = 0.95 current_demand[6] = 1 DISCOUNT = 20 discounts = DiscountSettings() discounts.weekday_discounts = {} discounts.first_visit_percentage = DISCOUNT discounts.revisit_within_1week_percentage = 0 discounts.revisit_within_2week_percentage = 0 discounts.maximum_discount = 20 discounts.is_maximum_discount_enabled = True regular_price = 1000 * random() prices = calc_client_prices(self.tz, discounts, None, [ regular_price, ], current_demand) assert len(prices) == PRICE_BLOCK_SIZE assert prices[0].price == Decimal( _calculate_discount(regular_price, 0.89, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) assert prices[1].price == Decimal( _calculate_discount(regular_price, 0.78, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) assert prices[2].price == Decimal( _calculate_discount(regular_price, 0.67, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) assert prices[3].price == Decimal( _calculate_discount(regular_price, 0.56, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) assert prices[4].price == Decimal( _calculate_discount(regular_price, 0.45, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) assert prices[5].price == Decimal( _calculate_discount(regular_price, 0.05, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) assert prices[6].price == Decimal( _calculate_discount(regular_price, 0, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) # Full discount on the rest of days because of zero demand for price in prices[7:]: assert price.price == Decimal( _calculate_discount(regular_price, 1, DISCOUNT, discounts.maximum_discount)).quantize( 1, ROUND_HALF_UP) assert price.applied_discount == DiscountType.FIRST_BOOKING