Example #1
0
    def settle_services_usage_from_client_credit(
            client: Client,
            services: List[Service],
            tax_rules: List[TaxRule] = None):
        LOG.info('Settling {} services for {}'.format(len(services), client))

        total_due = Decimal(0)
        current_date = utcnow()

        with db_transaction.atomic(
        ):  # create invoice and set the service next invoice due atomically
            for service in services:
                while service.next_due_date < current_date:
                    service_fixed_price = service.get_fixed_price(
                        currency=client.currency)
                    service_fixed_price = utils.cdecimal(
                        service_fixed_price, q='0.01')  # Convert to 2 decimals
                    service_dynamic_price = InvoiceUtils.get_dynamic_price_for_service(
                        service, service.next_due_date)
                    service_price = service_fixed_price + service_dynamic_price
                    total_due += service_price

                    # calculate taxes for invoice item
                    if service.product.taxable and not client.tax_exempt:
                        if tax_rules:
                            for tax_rule in tax_rules:
                                tax_amount = (service_price *
                                              tax_rule.rate) / 100
                                tax_amount = utils.cdecimal(tax_amount,
                                                            q='.01')
                                total_due += tax_amount

                    prev_due_date = service.next_due_date
                    InvoiceUtils.settle_dynamic_price_for_service(
                        service, service.next_due_date)
                    service.update_next_due_date()
                    if prev_due_date == service.next_due_date:
                        LOG.error('Next due date is not increasing, aborting')
                        break

            if total_due > 0:
                # settle only if total due is above zero
                client.withdraw_credit(total_due, client.currency)
                client_credit_account = client.credits.get(
                    client=client, currency=client.currency)
                Journal.objects.create(client_credit=client_credit_account,
                                       transaction=None,
                                       source_currency=client.currency,
                                       destination_currency=client.currency,
                                       source=JournalSources.credit,
                                       destination=JournalSources.settlement,
                                       source_amount=total_due,
                                       destination_amount=total_due)
Example #2
0
 def validate(self, attrs):
     # NOTE(tomo): Validate cycle is unique
     value = attrs.get('value')
     option = attrs.get('option')
     cycle = attrs.get('cycle')
     cycle_multiplier = attrs.get('cycle_multiplier')
     currency = attrs.get('currency')
     if not currency:
         raise serializers.ValidationError(detail=_('Currency is required'))
     extra_attrs = {}
     if value:
         extra_attrs['value'] = value
     elif option:
         extra_attrs['option'] = option
     else:
         extra_attrs['value__isnull'] = True
         extra_attrs['option__isnull'] = True
     exist_query = ConfigurableOptionCycle.objects
     if self.instance:
         # We edit the same instance, exclude it from query
         exist_query = exist_query.exclude(pk=self.instance.pk)
     if exist_query.filter(cycle=cycle,
                           cycle_multiplier=cycle_multiplier,
                           currency=currency,
                           **extra_attrs).exists():
         raise serializers.ValidationError(detail='Similar cycle already exists')
     # End cycle unique validation
     relative_pricing = attrs.get('is_relative_price', False)
     if relative_pricing:
         # Check if relative pricing is sent, modify pricing if we have a similar cycle in default currency
         # or just raise
         default_currency = get_default_currency()
         if attrs['currency'] != default_currency:
             def_cycle = ConfigurableOptionCycle.objects.filter(
                 option=option,
                 cycle=cycle,
                 cycle_multiplier=cycle_multiplier,
                 currency=default_currency
             ).first()
             if def_cycle is None:
                 raise serializers.ValidationError(detail=_('Unable to auto calculate prices'))
             converted_price = utils.convert_currency(price=def_cycle.price,
                                                      from_currency=def_cycle.currency,
                                                      to_currency=attrs['currency'])
             attrs['price'] = utils.cdecimal(converted_price, q='.01')
             converted_setup_fee = utils.convert_currency(price=def_cycle.setup_fee,
                                                          from_currency=def_cycle.currency,
                                                          to_currency=attrs['currency'])
             attrs['setup_fee'] = utils.cdecimal(converted_setup_fee, q='.01')
     return attrs
Example #3
0
    def validate(self, attrs):
        attrs = super(StaffProductCycleSerializer, self).validate(attrs)
        cycle = attrs.get('cycle')
        cycle_multiplier = attrs.get('cycle_multiplier')
        auto_calculate_price = attrs.get('is_relative_price', False)
        cycle_currency_code = attrs.get('currency')
        cycle_product = attrs.get('product')
        if cycle_product:
            # do not allow one-time cycles to go with recurring cycles
            other_cycles = ProductCycle.objects.filter(product=cycle_product)
            if cycle == CyclePeriods.onetime:
                for other_cycle in other_cycles:  # type: ProductCycle
                    if other_cycle.cycle != CyclePeriods.onetime:
                        raise serializers.ValidationError(detail=_(
                            'Cannot add one time cycle if the product has a recurring cycle.'
                        ))
            else:  # treat the case when the new cycle is a recurring one
                for other_cycle in other_cycles:  # type: ProductCycle
                    if other_cycle.cycle == CyclePeriods.onetime:
                        raise serializers.ValidationError(detail=_(
                            'Cannot add recurring cycle if the product has a one time cycle.'
                        ))
        if auto_calculate_price and cycle_product:
            # auto calculate prices
            default_currency = get_default_currency()
            if default_currency.code != cycle_currency_code:
                def_cycle = ProductCycle.objects.filter(
                    product=cycle_product,
                    cycle=cycle,
                    cycle_multiplier=cycle_multiplier,
                    currency=default_currency).first()
                if def_cycle is None:
                    c_msg = _(
                        'A cycle with {} currency is required to auto calculate price'
                    ).format(default_currency)
                    raise serializers.ValidationError(detail=c_msg)

                converted_price = utils.convert_currency(
                    price=def_cycle.fixed_price,
                    from_currency=def_cycle.currency,
                    to_currency=cycle_currency_code)
                attrs['fixed_price'] = utils.cdecimal(converted_price, q='.01')
                converted_setup_fee = utils.convert_currency(
                    price=def_cycle.setup_fee,
                    from_currency=def_cycle.currency,
                    to_currency=cycle_currency_code)
                attrs['setup_fee'] = utils.cdecimal(converted_setup_fee,
                                                    q='.01')
        return attrs
Example #4
0
    def update_usage(self,
                     skip_collecting: bool = False,
                     skip_compute_current: bool = False):
        if not skip_collecting:
            usage_settings = UsageSettings(
                billing_settings=self.billing_settings)

            # compute total unpaid usage for all services associated with the client
            LOG.debug('Updating usage for client {}'.format(self.client))
            for service in self.client.services.all():
                billing_module = module_factory.get_module_instance(
                    service=service)
                billing_module.collect_usage(service=service,
                                             usage_settings=usage_settings)

        if not skip_compute_current:
            # compute current client usage
            self.__client_usage = self.__compute_client_usage()

        # update uptodate credit for client
        uptodate_credit = cdecimal(
            self.client.get_remaining_credit(self.client_usage.unpaid_usage,
                                             self.client.currency.code))
        self.client.set_uptodate_credit(uptodate_credit=uptodate_credit)

        self.update_outofcredit_status()

        # log to summary
        self.summary.update_uptodate_credit(self.uptodate_credit)
Example #5
0
 def get_dynamic_price_for_service(service: Service, end_datetime: datetime) -> Decimal:
     LOG.info('Getting dynamic price for service {}'.format(service))
     try:
         billing_module = module_factory.get_module_instance(service=service)
         unsettled_usage = billing_module.get_unsettled_usage(service, end_datetime)
         return cdecimal(unsettled_usage.total_cost)
     except ModuleNotFoundException:
         return Decimal(0)
Example #6
0
 def get_price_display(instance):
     if instance.service:
         return '{} {}'.format(
             cdecimal(instance.service.get_fixed_price()),
             instance.service.cycle.currency.code,
         )
     else:
         return None
Example #7
0
 def get_fee(self, amount: decimal.Decimal) -> decimal.Decimal:
     fee = decimal.Decimal('0.0')
     if self.fixed_fee:
         fee = self.fixed_fee
     if self.percent_fee:
         fee += amount * (self.percent_fee / decimal.Decimal(100))
     fee = utils.cdecimal(
         fee
     )  # call cdecimal since we require 2 decimal places for this model
     return fee
Example #8
0
 def get_percent_per_location(location_cost: dict,
                              total_revenue: decimal.Decimal):
     revenue_per_location = {
     }  # dict {location_name: {cost: x, percent: y, alloted: z}}
     number_of_locations = len(location_cost.keys())
     if number_of_locations > 0:
         location_percent = cdecimal(100 / number_of_locations, q='0.01')
     else:
         location_percent = 100
     for location, cost in location_cost.items():
         if location not in revenue_per_location:
             revenue_per_location[location] = {
                 'cost': cost,
                 'percent': decimal.Decimal('0.00'),
                 'alloted': decimal.Decimal('0.00')
             }
         revenue_per_location[location]['percent'] = location_percent
         alloted = location_percent / 100 * total_revenue
         alloted = cdecimal(alloted, q='0.01')
         revenue_per_location[location]['alloted'] = alloted
     return revenue_per_location
Example #9
0
def get_client_taxes_amount_by_price(price, client=None, taxable=False):
    if client is None or client.tax_exempt or not taxable:
        # If this is a tax exempted client, just return
        # or item not taxable, continue to the next one
        return []
    taxes = []
    tax_rules = TaxRule.for_country_and_state(country=client.country_name,
                                              state=client.state)
    for tax_rule in tax_rules:
        tax_amount = cdecimal(price * (tax_rule.rate / 100), q='0.01')
        tax_name = tax_rule.name
        taxes.append({'name': tax_name, 'amount': tax_amount})
    return taxes
Example #10
0
 def get_invoice_items_percent(invoice: Invoice):
     items_percent = {}
     for item in invoice.items_with_taxes_amount():
         item_total = item.taxes_amount + item.amount
         item_taxes_amount = item.taxes_amount
         if item_total == 0:
             item_percent = 0
         else:
             item_percent = item_total * 100 / invoice.total
         if invoice.total == 0:
             tax_percent = 0
         else:
             tax_percent = item_taxes_amount * 100 / invoice.total
         if item_percent < 0:
             # Always make the percent positive
             item_percent *= -1
         items_percent[item.id] = {
             'percent': cdecimal(item_percent, q='.01'),
             'taxes_amount': cdecimal(item.taxes_amount, q='.01'),
             'taxes_percent': cdecimal(tax_percent, q='.01')
         }
     return items_percent
Example #11
0
    def calculate_fixed_price_and_taxes(client: Client, price, taxable: bool):
        """Calculate total price with taxes and return the taxes applied"""
        taxes_applied = {}
        total_price = price

        if taxable:
            tax_rules = TaxRule.for_country_and_state(
                country=client.country_name, state=client.state) or []
            for tax_rule in tax_rules:
                tax_amount = (price * tax_rule.rate) / 100
                tax_amount = utils.cdecimal(tax_amount, q='.01')
                if tax_rule.name in taxes_applied:
                    taxes_applied[tax_rule.name] += tax_amount
                else:
                    taxes_applied[tax_rule.name] = tax_amount
                total_price += tax_amount
        return total_price, taxes_applied
Example #12
0
 def add_credit_invoice(self, request):
     serializer = AddCreditSerializer(data=request.data,
                                      context={'request': request})
     serializer.is_valid(raise_exception=True)
     try:
         client = request.user.clients.get(
             pk=serializer.validated_data['client'])
     except Client.DoesNotExist:
         raise ValidationError({'client': _('Client not found')})
     credit = serializer.validated_data['credit']
     # Look for unpaid invoices already containing credit addition
     credit_invoices_unpaid = Invoice.objects.filter(
         client=client).unpaid().for_credit()
     if credit_invoices_unpaid.count() > 0:
         raise ValidationError(
             {'detail': _('An unpaid credit invoice already exists')})
     item_description_msg = _('Add {} {} to credit balance').format(
         credit, client.currency.code)
     item_taxes = []
     if client.billing_settings.add_tax_for_credit_invoices and not client.tax_exempt:
         client_tax_rules = TaxRule.for_country_and_state(
             country=client.country_name, state=client.state)
         if client_tax_rules:
             for tax_rule in client_tax_rules:
                 tax_amount = (credit * tax_rule.rate) / 100
                 tax_amount = utils.cdecimal(tax_amount, q='.01')
                 item_taxes.append({
                     'name': tax_rule.name,
                     'amount': tax_amount,
                     'tax_rule': tax_rule.id
                 })
     invoice_id = tasks.invoice_create(
         client.pk,
         items=[{
             'item_type': BillingItemTypes.credit,
             'amount': credit,
             'description': item_description_msg,
             'taxed': True if len(item_taxes) else False,
             'taxes': item_taxes,
         }],
         currency=client.currency.code,
         issue_date=now().isoformat(),
         due_date=now().isoformat())
     return Response({'id': invoice_id})
Example #13
0
 def generate_report(self) -> dict:
     entire_report = {
         'revenue_report': [],
         'total_revenue_per_location': [],
         'total_revenue': decimal.Decimal('0.00'),
         'currency_code': self.get_report_currency(),
         'start_date': str(self.start_date),
         'end_date': str(self.end_date)
     }
     locations_totals = {}
     for client in self.clients_queryset():
         client_report = self.generate_client_report(client=client)
         entire_report['revenue_report'].append(client_report)
         for revenue_per_location in client_report.get(
                 'revenue_per_location'):
             location_name = revenue_per_location.get('name')
             if location_name not in locations_totals:
                 locations_totals[location_name] = revenue_per_location.get(
                     'revenue')
             else:
                 locations_totals[
                     location_name] += revenue_per_location.get('revenue')
     for location_name, revenue in locations_totals.items():
         # Go through each location and set the final revenue per location
         # necessary since we used a dict prior to allow for faster calculation of totals
         entire_report['total_revenue_per_location'].append({
             'name':
             location_name,
             'revenue':
             revenue
         })
         # Calculate the total revenue
         entire_report['total_revenue'] += revenue
     entire_report['total_revenue'] = cdecimal(
         entire_report['total_revenue'])
     return entire_report
Example #14
0
 def get_price_by_cycle_quantity_and_choice(self,
                                            cycle_name,
                                            cycle_multiplier,
                                            quantity,
                                            currency,
                                            choice_value=None,
                                            option_value=None):
     zero = decimal.Decimal('0.00')
     if self.widget == ConfigurableOptionWidget.yesno and option_value != 'yes':
         return zero, zero, zero
     if self.widget in ConfigurableOptionWidget.WITHOUT_CHOICES:
         cycle = self.cycles.filter(cycle=cycle_name,
                                    cycle_multiplier=cycle_multiplier,
                                    currency=currency).first()
         if cycle:
             price = cycle.price * quantity
             if cycle.setup_fee_entire_quantity:
                 setup_fee = cycle.setup_fee
             else:
                 setup_fee = quantity * cycle.setup_fee
             return (utils.cdecimal(cycle.price, q='0.01'),
                     utils.cdecimal(price, q='0.01'),
                     utils.cdecimal(setup_fee, q='0.01'))
     elif self.widget in ConfigurableOptionWidget.WITH_CHOICES:
         value = self.choices.filter(choice=choice_value).first()
         if value:
             cycle = value.cycles.filter(cycle=cycle_name,
                                         cycle_multiplier=cycle_multiplier,
                                         currency=currency).first()
             if cycle is None:
                 return zero, zero, zero
             else:
                 price = cycle.price * quantity
                 if cycle.setup_fee_entire_quantity:
                     setup_fee = cycle.setup_fee
                 else:
                     setup_fee = quantity * cycle.setup_fee
                 return (utils.cdecimal(cycle.price, q='0.01'),
                         utils.cdecimal(price, q='0.01'),
                         utils.cdecimal(setup_fee, q='0.01'))
     return zero, zero, zero
Example #15
0
    def estimate_new_service_cycle_cost(service: Service,
                                        product: Product,
                                        cycle: ProductCycle,
                                        start_date,
                                        configurable_options=None):
        client = service.client
        zero = decimal.Decimal('0.00')
        opt_sum = []
        seconds_estimate = ServiceManager.estimate_new_service_cycle_seconds(
            service=service,
            product=product,
            cycle=cycle,
            start_date=start_date)
        if cycle and not product.is_free:
            new_cycle_cost = utils.convert_currency(cycle.fixed_price,
                                                    cycle.currency,
                                                    client.currency)
            if seconds_estimate['new_cycle_seconds'] > 0:
                new_cycle_cost_per_second = new_cycle_cost / seconds_estimate[
                    'new_cycle_seconds']
                new_cycle_remaining_cost = new_cycle_cost_per_second * seconds_estimate[
                    'new_cycle_remaining_seconds']
            else:
                new_cycle_remaining_cost = zero
                new_cycle_cost = zero
        else:
            new_cycle_cost = zero
            new_cycle_remaining_cost = zero
        if service.product.is_free or service.cycle and service.cycle.cycle == CyclePeriods.onetime:
            remaining_cost = zero
        elif not service.product.is_free and service.cycle:
            current_cycle_cost = service.get_fixed_price_without_configurable_options(
                currency=client.currency)
            if seconds_estimate['old_cycle_seconds'] > 0:
                current_cycle_cost_per_second = current_cycle_cost / seconds_estimate[
                    'old_cycle_seconds']
                remaining_cost = current_cycle_cost_per_second * seconds_estimate[
                    'old_cycle_remaining_seconds']
            else:
                remaining_cost = zero
            # Handle configurable options
            if configurable_options:
                opt_sum = ServiceManager.estimate_new_config_options_cost(
                    service=service,
                    cycle=cycle,
                    configurable_options=configurable_options,
                    seconds_estimate=seconds_estimate)
        else:
            remaining_cost = zero
        prod_upgrade_cost = utils.cdecimal(new_cycle_remaining_cost -
                                           remaining_cost,
                                           q='.01')
        total_upgrade_cost = prod_upgrade_cost
        for opt in opt_sum:
            total_upgrade_cost += opt['upgrade_cost']
        taxable = not client.tax_exempt and product.taxable
        total_price, taxes_applied = SettlementManager.calculate_fixed_price_and_taxes(
            client=client, price=total_upgrade_cost, taxable=taxable)
        new_cycle_cost = utils.cdecimal(new_cycle_cost, q='.01')
        remaining_cost = utils.cdecimal(remaining_cost, q='.01')

        return {
            'service_remaining_cost':
            remaining_cost,
            'new_product_price':
            new_cycle_cost,
            'upgrade_price':
            total_upgrade_cost,
            'product_upgrade_price':
            prod_upgrade_cost,
            'taxes_applied':
            taxes_applied,
            'total_due':
            total_price,
            'service_id':
            service.pk,
            'product_id':
            product.pk,
            'cycle_id':
            cycle.pk,
            'configurable_options':
            opt_sum,
            'display_name':
            '{} => {} ({} {} / {})'.format(service.display_name, product.name,
                                           new_cycle_cost, client.currency,
                                           cycle.display_name),
            'currency':
            client.currency.code
        }
Example #16
0
 def get_service_report(self, service, start_date, end_date):
     # TODO: see what to do with this for reseller
     report = {
         'name': 'OpenStack resources report',
         'locations': {},
         'service': None,
         'location_cost': {}
     }
     location_details = {}
     default_region = plugin_settings.DEFAULT_REGION
     client = service.client
     try:
         from fleio.osbilling.bin.collectorlib import service_usage
         from fleio.osbilling.bin.collectorlib import add_pricing
         from fleio.osbilling.bin.collectorlib import collect_project_metrics
         from fleio.osbilling.bin.collectorlib import collect_internal_usage
     except ImportError:
         return report
     usage_settings = UsageSettings(
         billing_settings=client.billing_settings)
     try:
         usage = service_usage(
             start_date=start_date,
             end_date=end_date,
             service_dynamic_usage=service.service_dynamic_usage,
         )
     except ObjectDoesNotExist:
         return report
     usage['metrics_details'] = collect_project_metrics(
         start_date,
         end_date,
         service_dynamic_usage=service.service_dynamic_usage,
     )
     if staff_active_features.is_enabled('openstack.instances.traffic'):
         # TODO: check for feature inside the collect method
         collect_internal_usage(
             usage_data=usage,
             start=start_date,
             end=end_date,
             service_dynamic_usage=service.service_dynamic_usage,
         )
     add_pricing(usage, client, usage_settings=usage_settings)
     project = usage.get('project', None)
     if not project:
         return report
     report['service'] = service.id
     total_cost = usage.get('price', Decimal(0))
     report['total_cost'] = total_cost
     for usage_detail in usage.get('usage_details', []):
         resource_type = usage_detail.get('resource_type')
         resource_name = usage_detail.get('resource_name')
         for rtype_usage in usage_detail.get('usage', []):
             region = rtype_usage.get('region', default_region)
             region = region or default_region
             price = Decimal(rtype_usage.get('price', 0))
             if region not in location_details:
                 location_details[region] = {
                     resource_type: {
                         'resource_name': resource_name,
                         'price': price,
                         'num_resources': 1
                     }
                 }
                 report['location_cost'][region] = price
             elif resource_type not in location_details[region]:
                 location_details[region][resource_type] = {
                     'resource_name': resource_name,
                     'price': price,
                     'num_resources': 1
                 }
                 report['location_cost'][region] += price
             else:
                 location_details[region][resource_type]['price'] += price
                 location_details[region][resource_type][
                     'num_resources'] += 1
                 report['location_cost'][region] += price
     report['locations'] = location_details
     report['total_cost'] = cdecimal(report['total_cost'], q='.01')
     return report
Example #17
0
    def get_domain_registrar_prices(domain: Domain,
                                    registrar: Registrar,
                                    years=None) -> SimpleNamespace or None:
        """Get the registrar prices for a domain"""
        # TODO(tomo): Check if domain is premium
        if years is None:
            years = domain.registration_period
        tld_name = domain.tld.name
        default_currency = get_default_currency()
        register_price = None
        transfer_price = None
        renew_price = None

        response = RegistrarPrices.objects.filter(
            tld_name=tld_name, connector=registrar.connector)
        if years > 1:
            # If we need a higher number of years, check if we have the answer cached
            # otherwise we need to calculate it
            response = response.filter(Q(years=1) | Q(years=years))
        # Prices can be in multiple currencies and with different years. At least for 1 year we should have the price
        price_currency_match = None
        for db_price in response:
            if db_price.currency == default_currency.code:
                if db_price.years == years:
                    price_currency_match = db_price
                elif db_price.years == 1 and not price_currency_match:
                    price_currency_match = db_price
        price_years_match = None
        if not price_currency_match:
            for db_price in response:
                if db_price.years == years:
                    price_years_match = db_price
                elif db_price.years == 1 and not price_years_match:
                    price_years_match = db_price
        if price_currency_match:
            if price_currency_match.years != years:
                register_price = cdecimal(price_currency_match.register_price *
                                          years)
                renew_price = cdecimal(price_currency_match.renew_price *
                                       years)
                transfer_price = cdecimal(price_currency_match.transfer_price
                                          )  # Transfers are on 1 year only
            else:
                register_price = cdecimal(price_currency_match.register_price)
                renew_price = cdecimal(price_currency_match.renew_price)
                transfer_price = cdecimal(price_currency_match.transfer_price)
        elif price_years_match:
            if price_years_match.years != years:
                pre_register_price = cdecimal(
                    price_years_match.register_price * years)
                pre_renew_price = cdecimal(price_years_match.renew_price *
                                           years)
                pre_transfer_price = cdecimal(price_years_match.transfer_price
                                              )  # Transfers are on 1 year only
            else:
                pre_register_price = cdecimal(price_years_match.register_price)
                pre_renew_price = cdecimal(price_years_match.register_price)
                pre_transfer_price = cdecimal(price_years_match.register_price)
            try:
                tld_currency = Currency.objects.get(
                    code=price_years_match.currency)
            except Currency.DoesNotExist:
                LOG.error(
                    'Registry currency {} does not exist in Fleio'.format(
                        price_years_match.currency))
                return None
            register_price = convert_currency(price=pre_register_price,
                                              from_currency=tld_currency,
                                              to_currency=default_currency)
            renew_price = convert_currency(price=pre_renew_price,
                                           from_currency=tld_currency,
                                           to_currency=default_currency)
            transfer_price = convert_currency(price=pre_transfer_price,
                                              from_currency=tld_currency,
                                              to_currency=default_currency)

        if register_price or renew_price or transfer_price:
            tld_prices = SimpleNamespace()
            tld_prices.register_price = cdecimal(register_price)
            tld_prices.renew_price = cdecimal(renew_price)
            tld_prices.transfer_price = cdecimal(transfer_price)
            tld_prices.currency = default_currency.code
            return tld_prices
        else:
            return None
Example #18
0
 def get_effective_credit_limit(obj):
     if obj.has_billing_agreement:
         return cdecimal(obj.billing_settings.credit_limit_with_agreement,
                         q='.01')
     else:
         return cdecimal(obj.billing_settings.credit_limit, q='.01')
Example #19
0
def convert_amount_from_api(amount, currency=None):
    assert currency is not None, 'Currency cannot be None'
    if currency.lower() in ZERO_DECIMAL_CURRENCIES:
        return cdecimal(amount)
    else:
        return cdecimal(amount / Decimal("100"))
Example #20
0
    def create_invoice_for_services(
        client: Client,
        services: List[Service],
        tax_rules: List[TaxRule] = None,
        manual_invoice: bool = False,
    ):
        LOG.info('Invoicing {} services for {}'.format(len(services), client))

        invoice_issue_date = utcnow()
        invoice_due_date = invoice_issue_date

        with db_transaction.atomic(
        ):  # create invoice and set the service next invoice due atomically
            invoice_items = list()
            for service in services:
                service_next_due = service.next_due_date if service.next_due_date else service.created_at
                while service.next_invoice_date is None or \
                        service.next_invoice_date < invoice_issue_date or manual_invoice:
                    item_taxes = []
                    service_fixed_price = service.get_fixed_price(
                        currency=client.currency)
                    service_fixed_price = utils.cdecimal(
                        service_fixed_price, q='0.01')  # Convert to 2 decimals

                    if service.is_price_overridden:
                        service_dynamic_price = Decimal(0)
                    else:
                        service_dynamic_price = InvoiceUtils.get_dynamic_price_for_service(
                            service,
                            service_next_due,
                        )
                    service_price = service_fixed_price + service_dynamic_price

                    # TODO: taxes are now recalculated on invoice creation in serializer, see if we need this code
                    # calculate taxes for invoice item
                    if service.product.taxable and not client.tax_exempt:
                        if tax_rules:
                            for tax_rule in tax_rules:
                                tax_amount = (service_price *
                                              tax_rule.rate) / 100
                                tax_amount = utils.cdecimal(tax_amount,
                                                            q='.01')
                                item_taxes.append({
                                    'name': tax_rule.name,
                                    'amount': tax_amount,
                                    'tax_rule': tax_rule.id
                                })

                    service_prev_due = service_next_due
                    service_next_due = service.get_next_due_date(
                        service_next_due)

                    prev_invoice_date = service.next_invoice_date
                    service.update_next_invoice_date(
                        previous_due_date=service_prev_due,
                        manual_invoice=manual_invoice)

                    if manual_invoice and service.next_invoice_date > invoice_issue_date:
                        invoice_due_date = prev_invoice_date if prev_invoice_date else service.next_invoice_date

                    if service_next_due and service_next_due != utils.DATETIME_MAX:
                        if manual_invoice:
                            # construct description to match invoice due date
                            while service_prev_due <= invoice_due_date:
                                service_prev_due = service_next_due
                                service_next_due = service.get_next_due_date(
                                    service_next_due)
                                if service_prev_due == service_next_due or service_next_due < invoice_due_date:
                                    # there is an issue within the loop, break to prevent infinite cycle
                                    break

                        service_next_due_display = service_next_due
                        service_prev_due_display = service_prev_due
                        if service.cycle.cycle in [
                                CyclePeriods.month, CyclePeriods.year
                        ]:
                            service_next_due_display -= timedelta(days=1)
                        datetime_fmt = '%d/%m/%Y'

                        previous_due_formatted = service_prev_due_display.strftime(
                            datetime_fmt)
                        next_due_formatted = service_next_due_display.strftime(
                            datetime_fmt)

                        description = '{} ({} - {})'.format(
                            service.display_name,
                            previous_due_formatted,
                            next_due_formatted,
                        )
                    else:
                        description = service.display_name

                    invoice_items.append({
                        'amount': service_price,
                        'description': description,
                        'item_type': BillingItemTypes.service,
                        'taxed': service.product.taxable,
                        'taxes': item_taxes,
                        'service': service.id
                    })

                    if prev_invoice_date == service.next_invoice_date:
                        # there is an issue within the loop, break to prevent infinite cycle
                        LOG.error('Invoice date not increasing, aborting')
                        break

                    if manual_invoice:
                        # this is a manual invoice generation, stopping
                        break

            invoice_id = invoice_create(
                client=client.id,
                items=invoice_items,
                issue_date=invoice_issue_date,
                currency=client.currency.code,
                due_date=invoice_due_date,
            )

        LOG.info('Invoice {} generated for client {}'.format(
            invoice_id, client))
        return invoice_id
Example #21
0
    def get_client_revenue(client: Client, start_date: datetime.datetime,
                           end_date: datetime.datetime):
        """Get all client revenue that should be included in the report"""
        services_report = {}
        report = {
            'client': client.id,
            'client_display_name': client.long_name,
            'services_report': services_report,
            'credit_in': decimal.Decimal('0.00'),
            'credit_out': decimal.Decimal('0.00'),
            'credit_available': decimal.Decimal('0.00')
        }
        client_main_credit_account = client.credits.filter(
            currency=client.currency).first()

        if client_main_credit_account:
            # set the last available credit for the given period
            last_journal_entry = Journal.objects.filter(
                Q(date_added__lt=end_date)
                & (Q(client_credit=client_main_credit_account)
                   | Q(invoice__client=client))).order_by('date_added').last()
            if (last_journal_entry and last_journal_entry.client_credit_left
                    and last_journal_entry.client_credit_left_currency.code
                    == client_main_credit_account.currency.code):
                report[
                    'credit_available'] = last_journal_entry.client_credit_left
            else:
                # TODO(manu): This conditional branch will not work when re-generating for older months if a fleio
                #   installation exists from a longer time (there are no journal entries that report the credit_
                #   available after that journal entry). To fix this, calculate client_credit_left for each journal
                #   entry since beginning of time in a migration
                report['credit_available'] = client_main_credit_account.amount
        # Add each service to the report and it's usage details given by it's billing module
        # if available.
        for service in client.services.filter(
                Q(terminated_at__isnull=True) | Q(terminated_at__lt=end_date)):
            fixed_monthly_price = cdecimal(
                service.get_fixed_price(),
                q='.01')  # returns fixed or overriden price
            services_report[service.id] = {
                'service_name':
                service.display_name,
                'service_id':
                service.id,
                'service_type':
                service.product.product_type,
                'service_last_cycle':
                JournalReport._get_next_due_date(service=service,
                                                 until_date=end_date),
                'entries': [],
                'fixed_monthly_price':
                fixed_monthly_price,  # fixed or overriden price
                'price_overridden':
                service.is_price_overridden,
                'total_revenue':
                decimal.Decimal('0.00'),
                'total_from_credit':
                decimal.Decimal('0.00')
            }

            # Get the report module for each service if it exists, in order to get a detailed location usage
            service_module = module_factory.get_module_instance(
                service=service)
            services_report[service.id][
                'usage_details'] = service_module.get_service_report(
                    service, start_date, end_date)
        # Gather journal entries and split out credit and direct service payments through invoices
        if client_main_credit_account:
            # All credit entries that need to appear on the report
            credit_qs = Journal.objects.filter(
                date_added__gte=start_date,
                date_added__lt=end_date,
                client_credit=client_main_credit_account)
            credit_in_qs = credit_qs.filter(destination=JournalSources.credit,
                                            source__in=[
                                                JournalSources.external,
                                                JournalSources.transaction
                                            ])
            credit_in_qs = credit_in_qs.aggregate(
                dest_amount=Coalesce(models.Sum('destination_amount'), 0))
            credit_amount_in = credit_in_qs.get('dest_amount', 0)
            credit_out_qs = credit_qs.filter(source=JournalSources.credit,
                                             destination__in=[
                                                 JournalSources.external,
                                                 JournalSources.transaction
                                             ])
            credit_out_qs = credit_out_qs.aggregate(
                source_amount=Coalesce(models.Sum('source_amount'), 0))
            credit_amount_out = credit_out_qs.get('source_amount', 0)
            report['credit_in'] += credit_amount_in
            report['credit_out'] += credit_amount_out
        # Revenue from invoices:
        invoice_journal_qs = Journal.objects.filter(
            invoice__client=client,
            date_added__gte=start_date,
            date_added__lt=end_date).order_by('date_added')
        invoice_journal_qs = invoice_journal_qs.filter(
            Q(source=JournalSources.invoice,
              destination__in=[
                  JournalSources.external, JournalSources.transaction
              ]) | Q(destination=JournalSources.invoice,
                     source__in=[
                         JournalSources.external,
                         JournalSources.transaction,
                         JournalSources.staff,
                     ])).all()
        for journal_entry in invoice_journal_qs:
            invoice = journal_entry.invoice
            items_percent = JournalReport.get_invoice_items_percent(
                invoice=invoice)
            for item in invoice.items.all():
                if item.service:
                    if item.service.id in services_report:
                        if items_percent[item.id]['percent'] != 0:
                            amount = items_percent[item.id][
                                'percent'] / 100 * journal_entry.destination_amount
                            taxamt = items_percent[item.id][
                                'taxes_percent'] / 100 * journal_entry.destination_amount
                            amount -= taxamt
                            if journal_entry.destination in [
                                    JournalSources.transaction,
                                    JournalSources.external
                            ]:
                                amount = -1 * amount
                            amount = cdecimal(amount, q='.01')
                            taxamt = cdecimal(taxamt, q='.01')
                            services_report[item.service.id]['entries'].append(
                                {
                                    'amount':
                                    amount,
                                    'item_type':
                                    item.item_type,
                                    'from_credit':
                                    False,
                                    'taxes_amount':
                                    taxamt,
                                    'taxes_percent':
                                    items_percent[item.id]['taxes_percent'],
                                    'source':
                                    journal_entry.source,
                                    'date':
                                    str(journal_entry.date_added)
                                })
                            services_report[
                                item.service.id]['total_revenue'] += amount
                    else:
                        amount = items_percent[item.id][
                            'percent'] / 100 * journal_entry.destination_amount
                        taxamt = items_percent[item.id][
                            'taxes_percent'] / 100 * journal_entry.destination_amount
                        amount -= taxamt
                        if journal_entry.destination in [
                                JournalSources.transaction,
                                JournalSources.external
                        ]:
                            amount = -1 * amount
                        amount = cdecimal(amount, q='.01')
                        taxamt = cdecimal(taxamt, q='.01')
                        services_report[item.service.id] = {
                            'entries': [{
                                'amount':
                                amount,
                                'item_type':
                                item.item_type,
                                'from_credit':
                                False,
                                'taxes_amount':
                                taxamt,
                                'taxes_percent':
                                items_percent[item.id]['taxes_percent'],
                                'source':
                                journal_entry.source,
                                'date':
                                str(journal_entry.date_added)
                            }],
                            'service':
                            item.service.display_name,
                            'total_revenue':
                            amount,
                            'total_from_credit':
                            decimal.Decimal('0.00')
                        }

        # Credit entries for each service
        cservice_qs = Journal.objects.filter(
            date_added__gte=start_date,
            date_added__lt=end_date,
            client_credit=client_main_credit_account)
        cservice_qs = cservice_qs.filter(
            Q(source=JournalSources.credit, destination=JournalSources.invoice)
            | Q(source=JournalSources.invoice,
                destination=JournalSources.credit)).all()

        for journal_entry in cservice_qs:
            invoice = journal_entry.invoice
            items_percent = JournalReport.get_invoice_items_percent(
                invoice=invoice)
            for item in invoice.items.all():
                if item.service:
                    if item.service.id in services_report:
                        if items_percent[item.id]['percent'] != 0:
                            amount = items_percent[item.id][
                                'percent'] / 100 * journal_entry.destination_amount
                            taxamt = items_percent[item.id][
                                'taxes_percent'] / 100 * journal_entry.destination_amount
                            amount -= taxamt
                            if journal_entry.destination == JournalSources.credit:
                                amount = -1 * amount
                            amount = cdecimal(amount, q='.01')
                            taxamt = cdecimal(taxamt, q='.01')
                            credit_entries = services_report[
                                item.service.id]['entries']
                            credit_entries.append({
                                'amount':
                                amount,
                                'item_type':
                                item.item_type,
                                'from_credit':
                                True,
                                'taxes_amount':
                                taxamt,
                                'taxes_percent':
                                items_percent[item.id]['taxes_percent'],
                                'source':
                                journal_entry.source,
                                'date':
                                str(journal_entry.date_added)
                            })
                            services_report[
                                item.service.id]['total_revenue'] += amount
                            services_report[
                                item.service.id]['total_from_credit'] += (
                                    amount + taxamt)
                    else:
                        amount = items_percent[item.id][
                            'percent'] / 100 * journal_entry.destination_amount
                        taxamt = items_percent[item.id][
                            'taxes_percent'] / 100 * journal_entry.destination_amount
                        amount -= taxamt
                        if journal_entry.destination == JournalSources.credit:
                            amount = -1 * amount
                        amount = cdecimal(amount, q='.01')
                        taxamt = cdecimal(taxamt, q='.01')
                        credit_entries = [{
                            'amount':
                            amount,
                            'item_type':
                            item.item_type,
                            'from_credit':
                            True,
                            'taxes_amount':
                            taxamt,
                            'taxes_percent':
                            items_percent[item.id]['taxes_percent'],
                            'source':
                            journal_entry.source,
                            'date':
                            str(journal_entry.date_added)
                        }]
                        services_report[item.service.id] = {
                            'entries': credit_entries,
                            'service': item.service.display_name,
                            'total_revenue': decimal.Decimal('0.00'),
                            'total_from_credit': amount + taxamt
                        }
                elif item.item_type == BillingItemTypes.credit:
                    if journal_entry.destination == JournalSources.invoice:
                        report['credit_out'] += item.amount
                    else:
                        report['credit_in'] += item.amount
        # Gether all credit used by services
        # The credit is split proportional to each service, based on it's usage report
        client_available_credit = report['credit_available']
        total_still_required_cost = decimal.Decimal('0.00')
        total_required_cost = decimal.Decimal('0.00')
        total_debt = decimal.Decimal('0.00')
        total_credit_alloted = decimal.Decimal('0.00')
        for service_id, service_report in services_report.items():
            price_overridden = service_report[
                'price_overridden'] if 'price_overridden' in service_report else False
            total_revenue = service_report['total_revenue']
            if price_overridden:
                # If pirice is overridden, the service total cost is the fixed price one - entries for it
                service_required_cost = service_report['fixed_monthly_price']
                service_report['service_required_cost'] = cdecimal(
                    service_required_cost, q='.01')
                cost_still_required = service_required_cost - total_revenue
                cost_still_required = cdecimal(cost_still_required, q='.01')
                service_report['cost_still_required'] = cost_still_required
            else:
                # Add here logic for dynamic but minimum fixed
                fixed_monthly_price = (service_report['fixed_monthly_price'] if
                                       'fixed_monthly_price' in service_report
                                       else decimal.Decimal('0.00'))
                usage_details = (service_report['usage_details']
                                 if 'usage_details' in service_report else {})
                service_required_cost = (
                    fixed_monthly_price +
                    usage_details.get('total_cost', decimal.Decimal('0.00')))
                cost_still_required = service_required_cost - total_revenue
                cost_still_required = cdecimal(cost_still_required, q='.01')
                service_report['service_required_cost'] = cdecimal(
                    service_required_cost, q='.01')
                service_report['cost_still_required'] = cost_still_required
            # Create a total amount so we can deduct from credit available and calculate the percentage
            total_required_cost += service_required_cost
            total_still_required_cost += cost_still_required

        # Calculate the percentage, debts, credit alloted of each service
        services_report, report = JournalReport._calculate_amount_for_services(
            client=client,
            services_report=services_report,
            report=report,
            total_still_required_cost=total_still_required_cost,
            client_available_credit=client_available_credit,
            total_debt=total_debt,
            total_credit_alloted=total_credit_alloted,
            end_date=end_date)
        # Go through each service to get the totals per region
        revenue_per_location = {}
        default_location = JournalReport.get_default_location()
        for service_id, service_report in services_report.items():
            # Set an equal percent for each location
            usage_details = service_report.get('usage_details', {})
            if type(usage_details) is dict and usage_details.keys():
                # Deal with services that have usage_details
                location_cost = usage_details.get('location_cost')
                total_revenue = service_report['alloted_from_credit']
                service_location_alloted = JournalReport.get_percent_per_location(
                    location_cost, total_revenue)
                for location_name, costs in service_location_alloted.items():
                    if location_name not in revenue_per_location:
                        revenue_per_location[location_name] = decimal.Decimal(
                            '0.00')
                    revenue_per_location[location_name] += costs['alloted']
            else:
                if default_location not in revenue_per_location:
                    revenue_per_location[default_location] = decimal.Decimal(
                        '0.00')
                total_revenue = service_report['alloted_from_credit']
                revenue_per_location[default_location] += total_revenue
        report['revenue_per_location'] = [{
            'name': name,
            'revenue': cdecimal(revenue, q='.01')
        } for name, revenue in revenue_per_location.items()]

        return report
Example #22
0
    def _calculate_amount_for_services(client, services_report, report,
                                       total_still_required_cost,
                                       client_available_credit, total_debt,
                                       total_credit_alloted, end_date):
        try:
            from fleio.osbilling.bin.collectorlib import service_usage
            from fleio.osbilling.bin.collectorlib import add_pricing
            from fleio.osbilling.bin.collectorlib import collect_project_metrics
            from fleio.osbilling.bin.collectorlib import collect_internal_usage
        except ImportError:
            return services_report, report
        total_credit_alloted_for_os = decimal.Decimal('0.00')
        for service_id, service_report in services_report.items():
            cost_still_required = service_report['cost_still_required']
            service_required_cost = service_report['service_required_cost']
            if service_required_cost != 0:
                if total_still_required_cost > 0:
                    sr_percent = (
                        (service_report['cost_still_required'] * 100) /
                        total_still_required_cost)
                else:
                    sr_percent = 100
                sr_percent = cdecimal(sr_percent, q='.01')
                service_report['cost_required_percent'] = sr_percent

                if service_report.get('service_type',
                                      '') == ProductType.openstack:
                    # TODO(low priority): for multiple openstack services/client, debt and revenue should be
                    #  calculated using sr_percent

                    # calculate client_consumed_credit which represents the credit consumed in a month that was
                    # actually paid using the following steps:
                    #   get unpaid usage from last cycle to end_date (firs of month, when report is generated)
                    #   using that information, calculate uptodate credit at that moment:
                    #       (available_credit at that moment - unpaid usage)
                    #   after that, calculate debt using information from above and consumption in that month
                    db_service = Service.objects.filter(id=service_id).first()
                    if not db_service:
                        unpaid_usage = decimal.Decimal('0.00')
                    else:
                        try:
                            unpaid_usage_dict = service_usage(
                                start_date=service_report[
                                    'service_last_cycle'],
                                end_date=end_date,
                                service_dynamic_usage=db_service.
                                service_dynamic_usage,
                            )
                            unpaid_usage_dict[
                                'metrics_details'] = collect_project_metrics(
                                    start=service_report['service_last_cycle'],
                                    end=end_date,
                                    service_dynamic_usage=db_service.
                                    service_dynamic_usage,
                                )
                            if staff_active_features.is_enabled(
                                    'openstack.instances.traffic'):
                                # TODO: check for feature inside the collect method
                                collect_internal_usage(
                                    usage_data=unpaid_usage_dict,
                                    service_dynamic_usage=db_service.
                                    service_dynamic_usage,
                                    start=service_report['service_last_cycle'],
                                    end=end_date,
                                )
                            usage_settings = UsageSettings(
                                billing_settings=client.billing_settings)
                            add_pricing(unpaid_usage_dict,
                                        client,
                                        usage_settings=usage_settings)
                            unpaid_usage = unpaid_usage_dict.get(
                                'price', decimal.Decimal('0.00'))
                        except (Exception, AttributeError):
                            unpaid_usage = decimal.Decimal('0.00')
                    if unpaid_usage is None:
                        LOG.error(
                            'Something went wrong when collecting unpaid usage for the period between service '
                            'last cycle date and the end of month for report ({}). Report for client {} may not '
                            'reflect reality for that period.'.format(
                                str(end_date), client.id))
                        unpaid_usage = decimal.Decimal('0.00')

                    client_utd_credit_at_the_moment = client_available_credit - unpaid_usage
                    client_consumed_credit = client_available_credit - client_utd_credit_at_the_moment
                    # calculations for debt then revenue
                    if client_utd_credit_at_the_moment < 0:
                        service_debt = client_utd_credit_at_the_moment * (-1)
                    elif client_consumed_credit >= cost_still_required:
                        service_debt = client_consumed_credit - cost_still_required
                    else:
                        service_debt = client_consumed_credit
                        if client_available_credit > 0:
                            service_debt *= (-1)
                else:
                    # handle services not related to openstack
                    service_debt = cost_still_required - service_report[
                        'total_revenue']
                    if service_debt > 0:
                        service_debt = cost_still_required  # invoice is not paid, thus service "debt" is cost still req
                # NOTE: service debt will result in:
                # negative value: means an old debt was paid before, will be added to alloted_from_credit (revenue)
                # positive value: means there is debt, for non-os services this will be however zeroed
                # zero value: there is no debt
                partial_debt = cdecimal(
                    service_debt, q='0.01'
                )  # used to calculate revenue (alloted_from_credit)
                if service_debt > 0:
                    # Only take into account positive debt
                    if service_report.get('service_type',
                                          '') == ProductType.openstack:
                        service_debt = cdecimal(service_debt, q='0.01')
                    else:
                        # for non-os services, debt does not exist
                        service_debt = decimal.Decimal('0.00')
                else:
                    service_debt = decimal.Decimal('0.00')
                service_report['debt'] = service_debt
                total_debt += service_debt

                alloted_from_credit = cost_still_required - partial_debt
                # recalculate alloted from credit based on other services
                no_of_services_with_cost = 0
                latest_service_with_cost = None

                if service_report.get('service_type',
                                      '') == ProductType.openstack:
                    for service_id_helper, service_report_helper in services_report.items(
                    ):
                        if (service_report_helper.get('usage_details', {}).get(
                                'total_cost', decimal.Decimal('0.00')) >
                                decimal.Decimal('0.00')):
                            latest_service_with_cost = service_id_helper
                            no_of_services_with_cost = no_of_services_with_cost + 1

                    if service_id != latest_service_with_cost:
                        service_percent = cdecimal(str(
                            100 / no_of_services_with_cost / 100),
                                                   q='.001')
                        alloted_from_credit = alloted_from_credit * service_percent
                    else:
                        alloted_from_credit = alloted_from_credit - total_credit_alloted_for_os  # until now
                    total_credit_alloted_for_os += alloted_from_credit

                service_report['alloted_from_credit'] = cdecimal(
                    alloted_from_credit, q='.01')
                total_credit_alloted += alloted_from_credit
            else:
                service_report['cost_required_percent'] = decimal.Decimal(
                    '0.00')
                service_report['alloted_from_credit'] = decimal.Decimal('0.00')
                service_report['debt'] = decimal.Decimal('0.00')
        report['total_debt'] = cdecimal(total_debt, q='.01')
        report['total_alloted_from_credit'] = cdecimal(total_credit_alloted,
                                                       q='.01')
        return services_report, report
Example #23
0
    def estimate_new_config_options_cost(service: Service, cycle: ProductCycle,
                                         configurable_options,
                                         seconds_estimate):
        options_upgrade_summary = []
        zero = decimal.Decimal('0.00')
        client = service.client

        for config_option in configurable_options:
            new_option = config_option['option']
            old_option = service.configurable_options.filter(
                option=new_option).first()
            new_price_set = False
            choice_value = None
            has_price = True
            if config_option['option'].widget == 'text_in':
                has_price = False
            quantity = config_option.get('quantity')
            if config_option['option'].has_choices:
                choice_value = config_option['option_value']
            # filter out all configurable options that do not have the product cycles
            if not config_option['option'].has_cycle(
                    cycle=cycle.cycle,
                    cycle_multiplier=cycle.cycle_multiplier,
                    choice_value=choice_value,
                    currency=client.currency.code,
            ):
                continue
            if not new_price_set and has_price:
                unit_price, price, setupfee = config_option[
                    'option'].get_price_by_cycle_quantity_and_choice(
                        cycle_name=cycle.cycle,
                        cycle_multiplier=cycle.cycle_multiplier,
                        currency=client.currency,
                        quantity=quantity,
                        choice_value=choice_value,
                        option_value=config_option['option_value'],
                    )
                if seconds_estimate['new_cycle_seconds'] > 0:
                    unit_price_per_second = unit_price / seconds_estimate[
                        'new_cycle_seconds']
                    unit_remaining_price = (
                        unit_price_per_second *
                        seconds_estimate['new_cycle_remaining_seconds'])
                    remaining_price = unit_remaining_price * quantity
                else:
                    unit_remaining_price = remaining_price = zero
            else:
                unit_price, price, setupfee = zero, zero, zero  # noqa
                unit_remaining_price = remaining_price = zero

            if old_option:
                if has_price:
                    old_choice_value = old_option.option_value
                    (old_unit_price, old_price, old_setupfee
                     ) = new_option.get_price_by_cycle_quantity_and_choice(
                         cycle_name=service.cycle.cycle,
                         cycle_multiplier=service.cycle.cycle_multiplier,
                         currency=client.currency,
                         quantity=old_option.quantity,
                         choice_value=old_choice_value,
                         option_value=old_choice_value,
                     )
                    if seconds_estimate['old_cycle_seconds'] > 0:
                        current_cycle_cost_per_second = old_unit_price / seconds_estimate[
                            'old_cycle_seconds']
                        remaining_unit_cost = (
                            current_cycle_cost_per_second *
                            seconds_estimate['old_cycle_remaining_seconds'])
                        remaining_cost = remaining_unit_cost * old_option.quantity
                    else:
                        remaining_cost = remaining_unit_cost = zero
                    upgrade_unit_cost = utils.cdecimal(unit_remaining_price -
                                                       remaining_unit_cost,
                                                       q='.01')
                    upgrade_cost = utils.cdecimal(remaining_price -
                                                  remaining_cost,
                                                  q='.01')
                else:
                    upgrade_cost = upgrade_unit_cost = zero
                if config_option['option'].widget == 'yesno' and config_option[
                        'option_value'] != 'yes':
                    display_name = '{} => {}: {}'.format(
                        old_option.display, old_option.display,
                        config_option['option_value'])
                else:
                    display_name = '{} => {}: {}'.format(
                        old_option.display, new_option.description,
                        config_option['option_value'])
                upgrade_option = {
                    'display_name': display_name,
                    'is_free': not has_price,
                    'option': config_option['option'].pk,
                    'option_value': config_option['option_value'],
                    'price': upgrade_cost,
                    'upgrade_cost': upgrade_cost,
                    'has_price': has_price,
                    'taxable': True,
                    'unit_price': upgrade_unit_cost,
                    'quantity': quantity,
                    'setupfee': setupfee
                }
                options_upgrade_summary.append(upgrade_option)
            else:
                options_upgrade_summary.append({
                    'display_name':
                    '{}: {}'.format(new_option.description,
                                    config_option['option_value']),
                    'is_free':
                    not has_price,
                    'option_value':
                    config_option['option_value'],
                    'option':
                    config_option['option'].pk,
                    'price':
                    price,
                    'upgrade_cost':
                    utils.cdecimal(remaining_price, q='.01'),
                    'has_price':
                    has_price,
                    'taxable':
                    True,
                    'unit_price':
                    unit_price,
                    'quantity':
                    quantity,
                    'setupfee':
                    setupfee
                })
        return options_upgrade_summary