def test_includes_hourly_report_with_orders(self): create_order( total=prices.Price('GBP', excl_tax=D('34.05'), tax=D('0.00'))) create_order( total=prices.Price('GBP', excl_tax=D('21.90'), tax=D('0.00'))) report = IndexView().get_hourly_report() self.assertEqual(len(report['order_total_hourly']), 12) self.assertEqual(len(report['y_range']), 11) self.assertEqual(report['max_revenue'], D('60'))
def calculate(self, basket): if (self.free_shipping_threshold is not None and basket.total_incl_tax >= self.free_shipping_threshold): return prices.Price(currency=basket.currency, excl_tax=D('0.00'), incl_tax=D('0.00')) charge = self.price_per_order for line in basket.lines.all(): if line.product.is_shipping_required: charge += line.quantity * self.price_per_item # Zero tax is assumed... return prices.Price(currency=basket.currency, excl_tax=charge, incl_tax=charge)
def build_submission(self, **kwargs): """ Return a dict of data that contains everything required for an order submission. This includes payment details (if any). This can be the right place to perform tax lookups and apply them to the basket. """ shipping_kwargs = {} basket = kwargs.get('basket', self.request.basket) shipping_address = self.get_shipping_address(basket) shipping_method = self.get_shipping_method(basket, shipping_address) billing_address = self.get_billing_address(shipping_address) shipping_charge = prices.Price(currency=basket.currency, excl_tax=D('0.00'), incl_tax=D('0.00')) if not shipping_method: total = None else: shipping_kwargs = self.get_shipping_kwargs() # add shipping charge if only method has prepaid payment type set as true # or has not payment_type attr (for simple methods) if is_prepaid_shipping(shipping_method): shipping_charge = shipping_method.calculate( basket, shipping_kwargs or None) total = self.get_order_totals(basket, shipping_charge=shipping_charge) submission = { 'user': self.request.user, 'basket': basket, 'shipping_address': shipping_address, 'shipping_method': shipping_method, 'shipping_charge': shipping_charge, 'billing_address': billing_address, 'order_total': total, # Details for shipping charge 'order_kwargs': {}, 'payment_kwargs': {} } # If there is a billing address, add it to the payment kwargs as calls # to payment gateways generally require the billing address. Note, that # it normally makes sense to pass the form instance that captures the # billing address information. That way, if payment fails, you can # render bound forms in the template to make re-submission easier. if billing_address: submission['payment_kwargs']['billing_address'] = billing_address # Allow overrides to be passed in submission.update(kwargs) # Set guest email after overrides as we need to update the order_kwargs # entry. if (not submission['user'].is_authenticated() and 'guest_email' not in submission['order_kwargs']): email = self.checkout_session.get_guest_email() submission['order_kwargs']['guest_email'] = email return submission
def calculate(self, basket): base_charge = self.method.calculate(basket) discount = self.offer.shipping_discount(base_charge.incl_tax) incl_tax = base_charge.incl_tax - discount excl_tax = self.calculate_excl_tax(base_charge, incl_tax) return prices.Price(currency=base_charge.currency, excl_tax=excl_tax, incl_tax=incl_tax)
def calculate(self, basket, shipping_charge, **kwargs): excl_tax = basket.total_excl_tax + shipping_charge.excl_tax if basket.is_tax_known and shipping_charge.is_tax_known: incl_tax = basket.total_incl_tax + shipping_charge.incl_tax else: incl_tax = None return prices.Price(currency=basket.currency, excl_tax=excl_tax, incl_tax=incl_tax)
def test_returns_correct_totals_when_tax_is_not_known(self): basket = mock.Mock() basket.total_excl_tax = D('10.00') basket.is_tax_known = False shipping_charge = prices.Price(currency=basket.currency, excl_tax=D('5.00')) total = self.calculator.calculate(basket, shipping_charge) self.assertIsInstance(total, prices.Price) self.assertEqual(D('10.00') + D('5.00'), total.excl_tax) self.assertFalse(total.is_tax_known)
def calculate(self, basket): # Note, when weighing the basket, we don't check whether the item # requires shipping or not. It is assumed that if something has a # weight, then it requires shipping. scale = Scale(attribute_code=self.weight_attribute, default_weight=self.default_weight) weight = scale.weigh_basket(basket) charge = self.get_charge(weight) # Zero tax is assumed... return prices.Price(currency=basket.currency, excl_tax=charge, incl_tax=charge)
def validate(self, attrs): request = self.context['request'] if request.user.is_anonymous: if not settings.IZI_ALLOW_ANON_CHECKOUT: message = _('Anonymous checkout forbidden') raise serializers.ValidationError(message) if not attrs.get('guest_email'): # Always require the guest email field if the user is anonymous message = _('Guest email is required for anonymous checkouts') raise serializers.ValidationError(message) else: if 'guest_email' in attrs: # Don't store guest_email field if the user is authenticated del attrs['guest_email'] basket = attrs.get('basket') basket = assign_basket_strategy(basket, request) if basket.num_items <= 0: message = _('Cannot checkout with empty basket') raise serializers.ValidationError(message) shipping_method = self._shipping_method( request, basket, attrs.get('shipping_method_code'), attrs.get('shipping_address')) shipping_charge = shipping_method.calculate(basket) posted_shipping_charge = attrs.get('shipping_charge') if posted_shipping_charge is not None: posted_shipping_charge = prices.Price(**posted_shipping_charge) # test submitted data. if not posted_shipping_charge == shipping_charge: message = _('Shipping price incorrect %s != %s' % (posted_shipping_charge, shipping_charge)) raise serializers.ValidationError(message) posted_total = attrs.get('total') total = OrderTotalCalculator().calculate(basket, shipping_charge) if posted_total is not None: if posted_total != total.incl_tax: message = _('Total incorrect %s != %s' % (posted_total, total.incl_tax)) raise serializers.ValidationError(message) # update attrs with validated data. attrs['total'] = total attrs['shipping_method'] = shipping_method attrs['shipping_charge'] = shipping_charge attrs['basket'] = basket return attrs
def get_shipping_charge(self, basket): shipping_charge = prices.Price(currency=basket.currency, excl_tax=D('0.00'), incl_tax=D('0.00')) shipping_address = self.get_shipping_address(basket) shipping_method = self.get_shipping_method(basket, shipping_address) if shipping_method: shipping_kwargs = self.get_shipping_kwargs() shipping_charge = shipping_method.calculate( basket, shipping_kwargs or None) else: # It's unusual to get here as a shipping method should be set by # the time this skip-condition is called. In the absence of any # other evidence, we assume the shipping charge is zero. #shipping_charge = D('0.00') pass return shipping_charge
def skip_unless_payment_is_required(self, request): # Check to see if payment is actually required for this order. shipping_address = self.get_shipping_address(request.basket) shipping_method = self.get_shipping_method(request.basket, shipping_address) if shipping_method: shipping_charge = shipping_method.calculate(request.basket) else: # It's unusual to get here as a shipping method should be set by # the time this skip-condition is called. In the absence of any # other evidence, we assume the shipping charge is zero. shipping_charge = prices.Price(currency=request.basket.currency, excl_tax=D('0.00'), tax=D('0.00')) total = self.get_order_totals(request.basket, shipping_charge) if total.excl_tax == D('0.00'): raise exceptions.PassedSkipCondition( url=reverse('checkout:preview'))
def calculate(self, basket): return prices.Price(currency=basket.currency, excl_tax=self.charge_excl_tax, incl_tax=self.charge_incl_tax)
def calculate(self, basket): # If the charge is free then tax must be free (musn't it?) and so we # immediately set the tax to zero return prices.Price(currency=basket.currency, excl_tax=D('0.00'), tax=D('0.00'))
def calculate(self, basket, options=None): # TODO: move code to smth like ShippingCalculator class results = [] charge = D('0.0') self.messages = [] self.errors = [] # Note, when weighing the basket, we don't check whether the item # requires shipping or not. It is assumed that if something has a # weight, then it requires shipping. scale = Scale(attribute_code=self.weight_attribute, default_weight=self.default_weight) packer = Packer(self.containers, attribute_codes=self.size_attributes, weight_code=self.weight_attribute, default_weight=self.default_weight) weight = scale.weigh_basket(basket).quantize(weight_precision) # Should be a list of dicts { 'weight': weight, 'container' : container } packs = packer.pack_basket(basket) facade = self.facade if not self.destination: self.errors.append( _("ERROR! There is no shipping address for charge calculation!\n" )) else: self.messages.append( _(u"""Approximated shipping price for {weight} kg from {origin} to {destination}\n""").format( weight=weight, origin=self.origin, destination=self.destination.city)) # Assuming cases like http protocol suggests: # e=200 - OK. Result contains charge value and extra info such as Branch code, etc # e=404 - Result is empty, no destination found via API, redirect # to address form or prompt to API city-codes selector # e=503 - API is offline. Skip this method. # e=300 - Too many choices found, Result contains list of charges-codes. # Prompt to found dest-codes selector # an URL for AJAXed city-to-city charge lookup details_url = reverse_lazy('shipping:charge-details', kwargs={'slug': self.code}) # an URL for AJAXed code by city lookup using Select2 widget lookup_url = reverse_lazy('shipping:city-lookup', kwargs={'slug': self.code}) # if options set make a short call to API for final calculation if options: errors = None try: results, errors = facade.get_charge( options['senderCityId'], options['receiverCityId'], packs) except CalculationError as e: self.errors.append("Post-calculation error: %s" % e.errors) self.messages.append(e.title) except: raise if not errors: (charge, msg, err, self.extra_form) = facade.parse_results(results, options=options) if msg: self.messages.append(msg) if err: self.errors.append(err) else: raise CalculationError( "%s -> %s" % (options['senderCityId'], options['receiverCityId']), errors) else: try: results = facade.get_charges(weight, packs, self.origin, self.destination) except ApiOfflineError: self.errors.append( _(u"""%s API is offline. Can't calculate anything. Sorry!""") % self.name) self.messages.append( _(u"Please, choose another shipping method!")) except OriginCityNotFoundError as e: # Paranoid mode as ImproperlyConfigured should be raised by facade self.errors.append( _(u"""City of origin '%s' not found in the shipping company postcodes to calculate charge.""") % e.title) self.messages.append( _(u"""It seems like we couldn't find code for the city of origin (%s). Please, select it manually, choose another address or another shipping method. """) % e.title) except ImproperlyConfigured as e: # upraised error handling self.errors.append("ImproperlyConfigured error (%s)" % e.message) self.messages.append( "Please, select another shipping method or call site administrator!" ) except CityNotFoundError as e: self.errors.append( _(u"""Can't find destination city '{title}' to calculate charge. Errors: {errors}""").format( title=e.title, errors=e.errors)) self.messages.append( _(u"""It seems like we can't find code for the city of destination (%s). Please, choose another address or another shipping method. """) % e.title) if CHANGE_DESTINATION: self.messages.append( _("Also, you can choose city of destination manually" )) self.extra_form = facade.get_extra_form( origin=self.origin, lookup_url=lookup_url, details_url=details_url) except TooManyFoundError as e: self.errors.append( _(u"Found too many destinations for given city (%s)") % e.title) if CHANGE_DESTINATION: self.messages.append( _("Please refine your shipping address")) self.extra_form = facade.get_extra_form( origin=self.origin, choices=e.results, details_url=details_url) except CalculationError as e: self.errors.append( _(u"""Error occurred during charge calculation for given city (%s)""") % e.title) self.messages.append(_(u"API error was: %s") % e.errors) if CHANGE_DESTINATION: self.extra_form = facade.get_extra_form( origin=self.origin, details_url=details_url, lookup_url=lookup_url) except: raise else: (charge, msg, err, self.extra_form) = facade.parse_results( results, origin=self.origin, dest=self.destination, weight=weight, packs=packs) if msg: self.messages.append(msg) if err: self.errors.append(err) # Zero tax is assumed... return prices.Price(currency=basket.currency, excl_tax=charge, incl_tax=charge)