def response_role_based_access(self): """ Perform Role Based Access Downgrade - Archive custom roles. - Set user roles using custom roles to Read Only. - Reset initial roles to standard permissions. """ custom_roles = [r.get_id for r in UserRole.get_custom_roles_by_domain(self.domain.name)] if not custom_roles: return True if self.verbose: for role in custom_roles: log_accounting_info("Archiving Custom Role %s" % role) # temporarily disable this part of the downgrade until we # have a better user experience for notifying the downgraded user # read_only_role = UserRole.get_read_only_role_by_domain(self.domain.name) # web_users = WebUser.by_domain(self.domain.name) # for web_user in web_users: # if web_user.get_domain_membership(self.domain.name).role_id in custom_roles: # web_user.set_role(self.domain.name, read_only_role.get_qualified_id()) # web_user.save() # for cc_user in CommCareUser.by_domain(self.domain.name): # if cc_user.get_domain_membership(self.domain.name).role_id in custom_roles: # cc_user.set_role(self.domain.name, 'none') # cc_user.save() UserRole.archive_custom_roles_for_domain(self.domain.name) UserRole.reset_initial_roles_for_domain(self.domain.name) return True
def send_credits_on_hq_report(): if settings.SAAS_REPORTING_EMAIL and settings.IS_SAAS_ENVIRONMENT: yesterday = datetime.date.today() - datetime.timedelta(days=1) credits_report = CreditsAutomatedReport() credits_report.send_report(settings.SAAS_REPORTING_EMAIL) log_accounting_info("Sent credits on hq report as of {}".format( yesterday.isoformat()))
def _pay_invoice(self, invoice): log_accounting_info("[Autopay] Autopaying invoice {}".format(invoice.id)) amount = invoice.balance.quantize(Decimal(10) ** -2) if not amount: return auto_payer = invoice.subscription.account.auto_pay_user payment_method = StripePaymentMethod.objects.get(web_user=auto_payer) autopay_card = payment_method.get_autopay_card(invoice.subscription.account) if autopay_card is None: return try: with transaction.atomic(): payment_record = PaymentRecord.create_record(payment_method, 'temp_transaction_id', amount) invoice.pay_invoice(payment_record) invoice.subscription.account.last_payment_method = LastPayment.CC_AUTO invoice.account.save() transaction_id = payment_method.create_charge( autopay_card, amount_in_dollars=amount, description='Auto-payment for Invoice %s' % invoice.invoice_number, ) except stripe.error.CardError: self._handle_card_declined(invoice, payment_method) except payment_method.STRIPE_GENERIC_ERROR as e: self._handle_card_errors(invoice, e) else: payment_record.transaction_id = transaction_id payment_record.save() self._send_payment_receipt(invoice, payment_record)
def send_bookkeeper_email(month=None, year=None, emails=None): today = datetime.date.today() # now, make sure that we send out LAST month's invoices if we did # not specify a month or year. today = get_previous_month_date_range(today)[0] month = month or today.month year = year or today.year from corehq.apps.accounting.interface import InvoiceInterface request = HttpRequest() params = urlencode(( ('report_filter_statement_period_use_filter', 'on'), ('report_filter_statement_period_month', month), ('report_filter_statement_period_year', year), )) request.GET = QueryDict(params) request.couch_user = FakeUser( domain="hqadmin", username="******", ) invoice = InvoiceInterface(request) invoice.is_rendered_as_email = True first_of_month = datetime.date(year, month, 1) email_context = { 'month': first_of_month.strftime("%B"), } email_content = render_to_string( 'accounting/email/bookkeeper.html', email_context) email_content_plaintext = render_to_string( 'accounting/email/bookkeeper.txt', email_context) format_dict = Format.FORMAT_DICT[Format.CSV] excel_attachment = { 'title': 'Invoices_%(period)s.%(extension)s' % { 'period': first_of_month.strftime('%B_%Y'), 'extension': format_dict['extension'], }, 'mimetype': format_dict['mimetype'], 'file_obj': invoice.excel_response, } emails = emails or settings.BOOKKEEPER_CONTACT_EMAILS for email in emails: send_HTML_email( "Invoices for %s" % datetime.date(year, month, 1).strftime(USER_MONTH_FORMAT), email, email_content, email_from=settings.DEFAULT_FROM_EMAIL, text_content=email_content_plaintext, file_attachments=[excel_attachment], ) log_accounting_info( "Sent Bookkeeper Invoice Summary for %(month)s " "to %(emails)s." % { 'month': first_of_month.strftime(USER_MONTH_FORMAT), 'emails': ", ".join(emails) })
def pay_autopayable_invoices(self, date_due): """ Pays the full balance of all autopayable invoices on date_due """ autopayable_invoices = Invoice.autopayable_invoices(date_due) for invoice in autopayable_invoices: log_accounting_info("[Autopay] Autopaying invoice {}".format(invoice.id)) amount = invoice.balance.quantize(Decimal(10) ** -2) if not amount: continue auto_payer = invoice.subscription.account.auto_pay_user payment_method = StripePaymentMethod.objects.get(web_user=auto_payer) autopay_card = payment_method.get_autopay_card(invoice.subscription.account) if autopay_card is None: continue try: payment_record = payment_method.create_charge( autopay_card, amount_in_dollars=amount, description='Auto-payment for Invoice %s' % invoice.invoice_number, ) except stripe.error.CardError: self._handle_card_declined(invoice, payment_method) continue except payment_method.STRIPE_GENERIC_ERROR as e: self._handle_card_errors(invoice, e) continue else: with transaction.atomic(): invoice.pay_invoice(payment_record) invoice.subscription.account.last_payment_method = LastPayment.CC_AUTO invoice.account.save() self._send_payment_receipt(invoice, payment_record)
def generate_invoices(based_on_date=None): """ Generates all invoices for the past month. """ today = based_on_date or datetime.date.today() invoice_start, invoice_end = get_previous_month_date_range(today) log_accounting_info( "Starting up invoices for %(start)s - %(end)s" % { 'start': invoice_start.strftime(USER_DATE_FORMAT), 'end': invoice_end.strftime(USER_DATE_FORMAT), }) all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain = Domain.wrap(domain_doc) try: invoice_factory = DomainInvoiceFactory(invoice_start, invoice_end, domain) invoice_factory.create_invoices() log_accounting_info("Sent invoices for domain %s" % domain.name) except CreditLineError as e: log_accounting_error("There was an error utilizing credits for " "domain %s: %s" % (domain.name, e)) except InvoiceError as e: log_accounting_error("Could not create invoice for domain %s: %s" % (domain.name, e)) except InvoiceAlreadyCreatedError as e: log_accounting_error("Invoice already existed for domain %s: %s" % (domain.name, e)) except Exception as e: log_accounting_error("Error occurred while creating invoice for " "domain %s: %s" % (domain.name, e)) if not settings.UNIT_TESTING: _invoicing_complete_soft_assert(False, "Invoicing is complete!")
def send_bookkeeper_email(month=None, year=None, emails=None): today = datetime.date.today() # now, make sure that we send out LAST month's invoices if we did # not specify a month or year. today = get_previous_month_date_range(today)[0] month = month or today.month year = year or today.year from corehq.apps.accounting.interface import InvoiceInterface request = HttpRequest() params = urlencode(( ('report_filter_statement_period_use_filter', 'on'), ('report_filter_statement_period_month', month), ('report_filter_statement_period_year', year), )) request.GET = QueryDict(params) request.couch_user = FakeUser( domain="hqadmin", username="******", ) invoice = InvoiceInterface(request) invoice.is_rendered_as_email = True first_of_month = datetime.date(year, month, 1) email_context = { 'month': first_of_month.strftime("%B"), } email_content = render_to_string( 'accounting/bookkeeper_email.html', email_context) email_content_plaintext = render_to_string( 'accounting/bookkeeper_email_plaintext.html', email_context) format_dict = Format.FORMAT_DICT[Format.CSV] excel_attachment = { 'title': 'Invoices_%(period)s.%(extension)s' % { 'period': first_of_month.strftime('%B_%Y'), 'extension': format_dict['extension'], }, 'mimetype': format_dict['mimetype'], 'file_obj': invoice.excel_response, } emails = emails or settings.BOOKKEEPER_CONTACT_EMAILS for email in emails: send_HTML_email( "Invoices for %s" % datetime.date(year, month, 1).strftime(USER_MONTH_FORMAT), email, email_content, email_from=settings.DEFAULT_FROM_EMAIL, text_content=email_content_plaintext, file_attachments=[excel_attachment], ) log_accounting_info( "Sent Bookkeeper Invoice Summary for %(month)s " "to %(emails)s." % { 'month': first_of_month.strftime(USER_MONTH_FORMAT), 'emails': ", ".join(emails) })
def _pay_invoice(self, invoice): log_accounting_info("[Autopay] Autopaying invoice {}".format( invoice.id)) amount = invoice.balance.quantize(Decimal(10)**-2) if not amount: return auto_payer = invoice.subscription.account.auto_pay_user payment_method = StripePaymentMethod.objects.get(web_user=auto_payer) autopay_card = payment_method.get_autopay_card( invoice.subscription.account) if autopay_card is None: return try: with transaction.atomic(): payment_record = PaymentRecord.create_record( payment_method, 'temp_transaction_id', amount) invoice.pay_invoice(payment_record) invoice.subscription.account.last_payment_method = LastPayment.CC_AUTO invoice.account.save() transaction_id = payment_method.create_charge( autopay_card, amount_in_dollars=amount, description='Auto-payment for Invoice %s' % invoice.invoice_number, ) except stripe.error.CardError as e: self._handle_card_declined(invoice, e) except payment_method.STRIPE_GENERIC_ERROR as e: self._handle_card_errors(invoice, e) else: payment_record.transaction_id = transaction_id payment_record.save() self._send_payment_receipt(invoice, payment_record)
def _create_invoice_for_subscription(self, subscription): def _get_invoice_start(sub, date_start): return max(sub.date_start, date_start) def _get_invoice_end(sub, date_end): if sub.date_end is not None and sub.date_end <= date_end: # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. return sub.date_end - datetime.timedelta(days=1) else: return date_end if subscription.is_trial: # Don't create invoices for trial subscriptions log_accounting_info( "Skipping invoicing for Subscription %s because it's a trial." % subscription.pk) return if (subscription.skip_invoicing_if_no_feature_charges and not subscription.plan_version.feature_charges_exist_for_domain( self.domain)): log_accounting_info( "Skipping invoicing for Subscription %s because there are no feature charges." % subscription.pk) return invoice_start = _get_invoice_start(subscription, self.date_start) invoice_end = _get_invoice_end(subscription, self.date_end) with transaction.atomic(): invoice = self._generate_invoice(subscription, invoice_start, invoice_end) record = BillingRecord.generate_record(invoice) if record.should_send_email: try: if invoice.subscription.service_type == SubscriptionType.IMPLEMENTATION: if self.recipients: for email in self.recipients: record.send_email(contact_email=email) elif invoice.account.dimagi_contact: record.send_email( contact_email=invoice.account.dimagi_contact, cc_emails=[settings.ACCOUNTS_EMAIL]) else: record.send_email( contact_email=settings.ACCOUNTS_EMAIL) else: for email in self.recipients or invoice.contact_emails: record.send_email(contact_email=email) except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True else: record.skipped_email = True record.save() return invoice
def get_response(self): response = [] for priv in self.privileges: if self.verbose: log_accounting_info("Applying %s %s." % (priv, self.action_type)) message = getattr(self, 'response_%s' % priv) if message is not None: response.append(message) return response
def response_role_based_access(self): """ Perform Role Based Access Upgrade - Un-archive custom roles. """ if self.verbose: num_archived_roles = len(UserRole.by_domain(self.domain.name, is_archived=True)) if num_archived_roles: log_accounting_info("Re-Activating %d archived roles." % num_archived_roles) UserRole.unarchive_roles_for_domain(self.domain.name) return True
def handle(self, verbose=False, testing=False, *args, **options): log_accounting_info( 'Bootstrapping standard plans. Custom plans will have to be created via the admin UIs.' ) if testing: log_accounting_info("Initializing Plans and Roles for Testing") config = BOOTSTRAP_CONFIG_TESTING else: config = BOOTSTRAP_CONFIG ensure_plans(config, verbose=verbose, apps=default_apps)
def handle(self, dry_run=False, verbose=False, testing=False, *args, **options): log_accounting_info( 'Bootstrapping standard plans. Custom plans will have to be created via the admin UIs.' ) if testing: log_accounting_info("Initializing Plans and Roles for Testing") config = BOOTSTRAP_CONFIG_TESTING else: config = BOOTSTRAP_CONFIG ensure_plans(config, dry_run=dry_run, verbose=verbose, apps=default_apps)
def _create_invoice_for_subscription(self, subscription): def _get_invoice_start(sub, date_start): if sub.date_start > date_start: return sub.date_start else: return date_start def _get_invoice_end(sub, date_end): if sub.date_end is not None and sub.date_end <= date_end: # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. return sub.date_end - datetime.timedelta(days=1) else: return date_end if subscription.is_trial: # Don't create invoices for trial subscriptions log_accounting_info( "Skipping invoicing for Subscription %s because it's a trial." % subscription.pk ) return if ( subscription.skip_invoicing_if_no_feature_charges and not subscription.plan_version.feature_charges_exist_for_domain(self.domain) ): log_accounting_info( "Skipping invoicing for Subscription %s because there are no feature charges." % subscription.pk ) return invoice_start = _get_invoice_start(subscription, self.date_start) invoice_end = _get_invoice_end(subscription, self.date_end) with transaction.atomic(): invoice = self._generate_invoice(subscription, invoice_start, invoice_end) record = BillingRecord.generate_record(invoice) if record.should_send_email: try: record.send_email(contact_emails=self.recipients) except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True else: record.skipped_email = True record.save() return invoice
def _ensure_product_and_rate(monthly_fee, edition, verbose, apps): """ Ensures that all the necessary SoftwareProducts and SoftwareProductRates are created for the plan. """ SoftwareProduct = apps.get_model('accounting', 'SoftwareProduct') SoftwareProductRate = apps.get_model('accounting', 'SoftwareProductRate') if verbose: log_accounting_info('Ensuring Products and Product Rates') product = SoftwareProduct(name='CommCare %s' % edition) # TODO - remove after product_type column is dropped and migrations are squashed if hasattr(product, 'product_type'): product.product_type = SoftwareProductType.COMMCARE if edition == SoftwarePlanEdition.ENTERPRISE: product.name = "Dimagi Only %s" % product.name product_rate = SoftwareProductRate(monthly_fee=monthly_fee) try: product = SoftwareProduct.objects.get(name=product.name) if verbose: log_accounting_info( "Product '%s' already exists. Using existing product to add rate." % product.name) except SoftwareProduct.DoesNotExist: product.save() if verbose: log_accounting_info("Creating Product: %s" % product) if verbose: log_accounting_info("Corresponding product rate of $%d created." % product_rate.monthly_fee) product_rate.product = product return product, product_rate
def _ensure_product_and_rate(product_rate, edition, verbose, apps): """ Ensures that all the necessary SoftwareProducts and SoftwareProductRates are created for the plan. """ SoftwareProduct = apps.get_model('accounting', 'SoftwareProduct') SoftwareProductRate = apps.get_model('accounting', 'SoftwareProductRate') if verbose: log_accounting_info('Ensuring Products and Product Rates') product_type = SoftwareProductType.COMMCARE product = SoftwareProduct(name='%s %s' % (product_type, edition), product_type=product_type) if edition == SoftwarePlanEdition.ENTERPRISE: product.name = "Dimagi Only %s" % product.name product_rate = SoftwareProductRate(**product_rate) try: product = SoftwareProduct.objects.get(name=product.name) if verbose: log_accounting_info( "Product '%s' already exists. Using existing product to add rate." % product.name) except SoftwareProduct.DoesNotExist: product.save() if verbose: log_accounting_info("Creating Product: %s" % product) if verbose: log_accounting_info("Corresponding product rate of $%d created." % product_rate.monthly_fee) product_rate.product = product return product, product_rate
def _ensure_features(edition, dry_run, verbose, apps): """ Ensures that all the Features necessary for the plans are created. """ Feature = apps.get_model('accounting', 'Feature') if verbose: log_accounting_info('Ensuring Features for plan: %s' % edition) features = [] for feature_type in FEATURE_TYPES: feature = Feature(name='%s %s' % (feature_type, edition), feature_type=feature_type) if edition == SoftwarePlanEdition.ENTERPRISE: feature.name = "Dimagi Only %s" % feature.name if dry_run: log_accounting_info("[DRY RUN] Creating Feature: %s" % feature) else: try: feature = Feature.objects.get(name=feature.name) if verbose: log_accounting_info( "Feature '%s' already exists. Using existing feature to add rate." % feature.name) except Feature.DoesNotExist: feature.save() if verbose: log_accounting_info("Creating Feature: %s" % feature) features.append(feature) return features
def _ensure_features(edition, dry_run, verbose, apps): """ Ensures that all the Features necessary for the plans are created. """ Feature = apps.get_model('accounting', 'Feature') if verbose: log_accounting_info('Ensuring Features for plan: %s' % edition) features = [] for feature_type in FEATURE_TYPES: feature = Feature(name='%s %s' % (feature_type, edition), feature_type=feature_type) if edition == SoftwarePlanEdition.ENTERPRISE: feature.name = "Dimagi Only %s" % feature.name if dry_run: log_accounting_info("[DRY RUN] Creating Feature: %s" % feature) else: try: feature = Feature.objects.get(name=feature.name) if verbose: log_accounting_info( "Feature '%s' already exists. Using existing feature to add rate." % feature.name ) except Feature.DoesNotExist: feature.save() if verbose: log_accounting_info("Creating Feature: %s" % feature) features.append(feature) return features
def create_invoices(self): subscriptions = self._get_subscriptions() self._ensure_full_coverage(subscriptions) for subscription in subscriptions: try: if subscription.account.is_customer_billing_account: log_accounting_info("Skipping invoice for subscription: %s, because it is part of a Customer " "Billing Account." % (subscription)) elif should_create_invoice(subscription, self.domain, self.date_start, self.date_end): self._create_invoice_for_subscription(subscription) except InvoiceAlreadyCreatedError as e: log_accounting_error( "Invoice already existed for domain %s: %s" % (self.domain.name, e), show_stack_trace=True, )
def update_exchange_rates(app_id=settings.OPEN_EXCHANGE_RATES_API_ID): try: log_accounting_info("Updating exchange rates...") rates = json.load(urllib2.urlopen( 'https://openexchangerates.org/api/latest.json?app_id=%s' % app_id))['rates'] default_rate = float(rates[Currency.get_default().code]) for code, rate in rates.items(): currency, _ = Currency.objects.get_or_create(code=code) currency.rate_to_default = float(rate) / default_rate currency.save() log_accounting_info("Exchange rate for %(code)s updated %(rate)f." % { 'code': currency.code, 'rate': currency.rate_to_default, }) except Exception as e: log_accounting_error(e.message)
def _ensure_feature_rates(feature_rates, features, edition, verbose, apps): """ Ensures that all the FeatureRates necessary for the plans are created. """ FeatureRate = apps.get_model('accounting', 'FeatureRate') if verbose: log_accounting_info('Ensuring Feature Rates') db_feature_rates = [] for feature in features: feature_rate = FeatureRate(**feature_rates[feature.feature_type]) feature_rate.feature = feature if verbose: log_accounting_info("Creating rate for feature '%s': %s" % (feature.name, feature_rate)) db_feature_rates.append(feature_rate) return db_feature_rates
def get_response(self): log_accounting_info('get_response for %s' % str(self)) responses = [] for privilege in self.privileges: log_accounting_info('handling privilege=%s' % str(privilege)) try: response = self.privilege_to_response_function()[privilege](self.domain, self.new_plan_version) log_accounting_info('completed handling privilege=%s' % str(privilege)) except ResourceConflict: log_accounting_info('ResourceConflict handling privilege=%s' % str(privilege)) # Something else updated the domain. Reload and try again. self.domain = Domain.get_by_name(self.domain.name) response = self.privilege_to_response_function()[privilege](self.domain, self.new_plan_version) log_accounting_info('completed handling privilege=%s after ResourceConflict' % str(privilege)) if response is not None: responses.append(response) return responses
def ensure_full_coverage(self, subscriptions): plan_version = DefaultProductPlan.get_default_plan_by_domain( self.domain, edition=SoftwarePlanEdition.COMMUNITY ).plan.get_version() if not plan_version.feature_charges_exist_for_domain(self.domain): return community_ranges = self.get_community_ranges(subscriptions) if not community_ranges: return do_not_invoice = any([s.do_not_invoice for s in subscriptions]) account = BillingAccount.get_or_create_account_by_domain( self.domain.name, created_by=self.__class__.__name__, created_by_invoicing=True, entry_point=EntryPoint.SELF_STARTED, )[0] if account.date_confirmed_extra_charges is None: log_accounting_info( "Did not generate invoice because date_confirmed_extra_charges " "was null for domain %s" % self.domain.name ) do_not_invoice = True if not BillingContactInfo.objects.filter(account=account).exists(): # No contact information exists for this account. # This shouldn't happen, but if it does, we can't continue # with the invoice generation. raise BillingContactInfoError( "Project %s has incurred charges, but does not have their " "Billing Contact Info filled out." % self.domain.name ) # First check to make sure none of the existing subscriptions is set # to do not invoice. Let's be on the safe side and not send a # community invoice out, if that's the case. for c in community_ranges: # create a new community subscription for each # date range that the domain did not have a subscription community_subscription = Subscription( account=account, plan_version=plan_version, subscriber=self.subscriber, date_start=c[0], date_end=c[1], do_not_invoice=do_not_invoice, ) community_subscription.save() subscriptions.append(community_subscription)
def should_create_invoice(subscription, domain, invoice_start, invoice_end): if subscription.is_trial: log_accounting_info("Skipping invoicing for Subscription %s because it's a trial." % subscription.pk) return False if subscription.skip_invoicing_if_no_feature_charges and not \ subscription.plan_version.feature_charges_exist_for_domain(domain): log_accounting_info( "Skipping invoicing for Subscription %s because there are no feature charges." % subscription.pk ) return False if subscription.date_start > invoice_end: # No invoice gets created if the subscription didn't start in the previous month. return False if subscription.date_end and subscription.date_end <= invoice_start: # No invoice gets created if the subscription ended before the invoicing period. return False return True
def update_exchange_rates(app_id=settings.OPEN_EXCHANGE_RATES_API_ID): try: log_accounting_info("Updating exchange rates...") rates = json.load(urllib2.urlopen( 'https://openexchangerates.org/api/latest.json?app_id=%s' % app_id))['rates'] default_rate = float(rates[Currency.get_default().code]) for code, rate in rates.items(): currency, _ = Currency.objects.get_or_create(code=code) currency.rate_to_default = float(rate) / default_rate currency.save() log_accounting_info("Exchange rate for %(code)s updated %(rate)f." % { 'code': currency.code, 'rate': currency.rate_to_default, }) except Exception as e: log_accounting_error( "Error updating exchange rates: %s" % e.message, show_stack_trace=True, )
def _ensure_product_rate(monthly_fee, edition, verbose, apps): """ Ensures that all the necessary SoftwareProductRates are created for the plan. """ if verbose: log_accounting_info('Ensuring Product Rates') SoftwareProductRate = apps.get_model('accounting', 'SoftwareProductRate') product_name = 'CommCare %s' % edition if edition == SoftwarePlanEdition.ENTERPRISE: product_name = "Dimagi Only %s" % product_name product_rate = SoftwareProductRate(monthly_fee=monthly_fee) try: # TODO - remove after squashing migrations SoftwareProduct = apps.get_model('accounting', 'SoftwareProduct') product = SoftwareProduct(name=product_name, product_type=SoftwareProductType.COMMCARE) if hasattr(product, 'product_type'): product.product_type = SoftwareProductType.COMMCARE try: product = SoftwareProduct.objects.get(name=product.name) if verbose: log_accounting_info( "Product '%s' already exists. Using existing product to add rate." % product.name ) except SoftwareProduct.DoesNotExist: if verbose: log_accounting_info("Creating Product: %s" % product) product.save() product_rate.product = product if verbose: log_accounting_info("Corresponding product rate of $%d created." % product_rate.monthly_fee) return product, product_rate except LookupError: product_rate.name = product_name if verbose: log_accounting_info("Corresponding product rate of $%d created." % product_rate.monthly_fee) return None, product_rate # TODO - don't return tuple after squashing migrations
def response_outbound_sms(self): """ Reminder rules will be deactivated. """ try: for reminder in self._active_reminders: reminder.active = False reminder.save() if self.verbose: log_accounting_info( "Deactivated Reminder %s [%s]" % (reminder.nickname, reminder._id) ) except Exception: log_accounting_error( "Failed to downgrade outbound sms for domain %s." % self.domain.name ) return False return True
def _ensure_full_coverage(self, subscriptions): plan_version = DefaultProductPlan.get_default_plan_by_domain( self.domain, edition=SoftwarePlanEdition.COMMUNITY ).plan.get_version() if not plan_version.feature_charges_exist_for_domain(self.domain): return community_ranges = self._get_community_ranges(subscriptions) if not community_ranges: return # First check to make sure none of the existing subscriptions is set # to do not invoice. Let's be on the safe side and not send a # community invoice out, if that's the case. do_not_invoice = any([s.do_not_invoice for s in subscriptions]) account = BillingAccount.get_or_create_account_by_domain( self.domain.name, created_by=self.__class__.__name__, entry_point=EntryPoint.SELF_STARTED, )[0] if account.date_confirmed_extra_charges is None: log_accounting_info( "Did not generate invoice because date_confirmed_extra_charges " "was null for domain %s" % self.domain.name ) do_not_invoice = True for start_date, end_date in community_ranges: # create a new community subscription for each # date range that the domain did not have a subscription community_subscription = Subscription( account=account, plan_version=plan_version, subscriber=self.subscriber, date_start=start_date, date_end=end_date, do_not_invoice=do_not_invoice, ) community_subscription.save() subscriptions.append(community_subscription)
def _ensure_product_rate(monthly_fee, edition, verbose, apps): """ Ensures that all the necessary SoftwareProductRates are created for the plan. """ if verbose: log_accounting_info('Ensuring Product Rates') SoftwareProductRate = apps.get_model('accounting', 'SoftwareProductRate') product_name = 'CommCare %s' % edition if edition == SoftwarePlanEdition.ENTERPRISE: product_name = "Dimagi Only %s" % product_name product_rate = SoftwareProductRate(monthly_fee=monthly_fee) try: # TODO - remove after squashing migrations SoftwareProduct = apps.get_model('accounting', 'SoftwareProduct') product = SoftwareProduct(name=product_name, product_type='CommCare') try: product = SoftwareProduct.objects.get(name=product.name) if verbose: log_accounting_info( "Product '%s' already exists. Using existing product to add rate." % product.name ) except SoftwareProduct.DoesNotExist: if verbose: log_accounting_info("Creating Product: %s" % product) product.save() product_rate.product = product if verbose: log_accounting_info("Corresponding product rate of $%d created." % product_rate.monthly_fee) return product, product_rate except LookupError: product_rate.name = product_name if verbose: log_accounting_info("Corresponding product rate of $%d created." % product_rate.monthly_fee) return None, product_rate # TODO - don't return tuple after squashing migrations
def response_inbound_sms(self): """ All Reminder rules utilizing "survey" will be deactivated. """ try: surveys = filter(lambda x: x.method in [METHOD_IVR_SURVEY, METHOD_SMS_SURVEY], self._active_reminders) for survey in surveys: survey.active = False survey.save() if self.verbose: log_accounting_info( "Deactivated Survey %s [%s]" % (survey.nickname, survey._id) ) except Exception: log_accounting_error( "Failed to downgrade outbound sms for domain %s." % self.domain.name ) return False return True
def update_exchange_rates(): app_id = settings.OPEN_EXCHANGE_RATES_API_ID if app_id: try: log_accounting_info("Updating exchange rates...") rates = json.load(six.moves.urllib.request.urlopen( 'https://openexchangerates.org/api/latest.json?app_id=%s' % app_id))['rates'] default_rate = float(rates[Currency.get_default().code]) for code, rate in rates.items(): currency, _ = Currency.objects.get_or_create(code=code) currency.rate_to_default = float(rate) / default_rate currency.save() log_accounting_info("Exchange rate for %(code)s updated %(rate)f." % { 'code': currency.code, 'rate': currency.rate_to_default, }) except Exception as e: log_accounting_error( "Error updating exchange rates: %s" % six.text_type(e), show_stack_trace=True, )
def _create_invoice_for_subscription(self, subscription): def _get_invoice_start(sub, date_start): if sub.date_start > date_start: return sub.date_start else: return date_start def _get_invoice_end(sub, date_end): if ( sub.date_end is not None and sub.date_end <= date_end ): # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. return sub.date_end - datetime.timedelta(days=1) else: return date_end if subscription.is_trial: # Don't create invoices for trial subscriptions log_accounting_info( "Skipping invoicing for Subscription %s because it's a trial." % subscription.pk ) return invoice_start = _get_invoice_start(subscription, self.date_start) invoice_end = _get_invoice_end(subscription, self.date_end) with transaction.atomic(): invoice = self._generate_invoice(subscription, invoice_start, invoice_end) record = BillingRecord.generate_record(invoice) try: record.send_email() except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True return invoice
def _ensure_full_coverage(self, subscriptions): plan_version = DefaultProductPlan.get_default_plan( edition=SoftwarePlanEdition.COMMUNITY).plan.get_version() if not plan_version.feature_charges_exist_for_domain(self.domain): return community_ranges = self._get_community_ranges(subscriptions) if not community_ranges: return # First check to make sure none of the existing subscriptions is set # to do not invoice. Let's be on the safe side and not send a # community invoice out, if that's the case. do_not_invoice = any([s.do_not_invoice for s in subscriptions]) account = BillingAccount.get_or_create_account_by_domain( self.domain.name, created_by=self.__class__.__name__, entry_point=EntryPoint.SELF_STARTED, )[0] if account.date_confirmed_extra_charges is None: log_accounting_info( "Did not generate invoice because date_confirmed_extra_charges " "was null for domain %s" % self.domain.name) do_not_invoice = True for start_date, end_date in community_ranges: # create a new community subscription for each # date range that the domain did not have a subscription community_subscription = Subscription( account=account, plan_version=plan_version, subscriber=self.subscriber, date_start=start_date, date_end=end_date, do_not_invoice=do_not_invoice, ) community_subscription.save() subscriptions.append(community_subscription)
def generate_invoices(based_on_date=None): """ Generates all invoices for the past month. """ today = based_on_date or datetime.date.today() invoice_start, invoice_end = get_previous_month_date_range(today) log_accounting_info("Starting up invoices for %(start)s - %(end)s" % { 'start': invoice_start.strftime(USER_DATE_FORMAT), 'end': invoice_end.strftime(USER_DATE_FORMAT), }) all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain = Domain.wrap(domain_doc) try: invoice_factory = DomainInvoiceFactory( invoice_start, invoice_end, domain) invoice_factory.create_invoices() log_accounting_info("Sent invoices for domain %s" % domain.name) except CreditLineError as e: log_accounting_error( "There was an error utilizing credits for " "domain %s: %s" % (domain.name, e) ) except InvoiceError as e: log_accounting_error( "Could not create invoice for domain %s: %s" % (domain.name, e) ) except InvoiceAlreadyCreatedError as e: log_accounting_error( "Invoice already existed for domain %s: %s" % (domain.name, e) ) except Exception as e: log_accounting_error( "Error occurred while creating invoice for " "domain %s: %s" % (domain.name, e) ) if not settings.UNIT_TESTING: _invoicing_complete_soft_assert(False, "Invoicing is complete!")
def generate_invoices(based_on_date=None, check_existing=False, is_test=False): """ Generates all invoices for the past month. """ today = based_on_date or datetime.date.today() invoice_start, invoice_end = utils.get_previous_month_date_range(today) log_accounting_info("Starting up invoices for %(start)s - %(end)s" % { 'start': invoice_start.strftime(USER_DATE_FORMAT), 'end': invoice_end.strftime(USER_DATE_FORMAT), }) all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain = Domain.wrap(domain_doc) if (check_existing and Invoice.objects.filter( subscription__subscriber__domain=domain, date_created__gte=today).count() != 0): pass elif is_test: log_accounting_info("Ready to create invoice for domain %s" % domain.name) else: try: invoice_factory = DomainInvoiceFactory( invoice_start, invoice_end, domain) invoice_factory.create_invoices() log_accounting_info("Sent invoices for domain %s" % domain.name) except CreditLineError as e: log_accounting_error( "There was an error utilizing credits for " "domain %s: %s" % (domain.name, e) ) except BillingContactInfoError as e: log_accounting_error("BillingContactInfoError: %s" % e) except InvoiceError as e: log_accounting_error( "Could not create invoice for domain %s: %s" % ( domain.name, e )) except InvoiceAlreadyCreatedError as e: log_accounting_error( "Invoice already existed for domain %s: %s" % ( domain.name, e )) except Exception as e: log_accounting_error( "Error occurred while creating invoice for " "domain %s: %s" % (domain.name, e) )
def weekly_digest(): today = datetime.date.today() in_forty_days = today + datetime.timedelta(days=40) ending_in_forty_days = filter( lambda sub: not sub.is_renewed, Subscription.objects.filter( date_end__lte=in_forty_days, date_end__gte=today, is_active=True, is_trial=False, account__dimagi_contact__isnull=True, )) if not ending_in_forty_days: log_accounting_info( "Did not send summary of ending subscriptions because " "there are none.") return table = [[ "Project Space", "Account", "Plan", "Salesforce Contract ID", "Dimagi Contact", "Start Date", "End Date", "Receives Invoice", "Created By", ]] def _fmt_row(sub): try: created_by_adj = SubscriptionAdjustment.objects.filter( subscription=sub, reason=SubscriptionAdjustmentReason.CREATE).order_by( 'date_created')[0] created_by = dict(SubscriptionAdjustmentMethod.CHOICES).get( created_by_adj.method, "Unknown") except (IndexError, SubscriptionAdjustment.DoesNotExist): created_by = "Unknown" return [ sub.subscriber.domain, "%s (%s)" % (sub.account.name, sub.account.id), sub.plan_version.plan.name, sub.salesforce_contract_id, sub.account.dimagi_contact, sub.date_start, sub.date_end, "No" if sub.do_not_invoice else "YES", created_by, ] table.extend([_fmt_row(sub) for sub in ending_in_forty_days]) file_to_attach = StringIO() export_from_tables([['End in 40 Days', table]], file_to_attach, Format.XLS_2007) email_context = { 'today': today.isoformat(), 'forty_days': in_forty_days.isoformat(), } email_content = render_to_string('accounting/digest_email.html', email_context) email_content_plaintext = render_to_string('accounting/digest_email.txt', email_context) format_dict = Format.FORMAT_DICT[Format.XLS_2007] file_attachment = { 'title': 'Subscriptions_%(start)s_%(end)s.xls' % { 'start': today.isoformat(), 'end': in_forty_days.isoformat(), }, 'mimetype': format_dict['mimetype'], 'file_obj': file_to_attach, } from_email = "Dimagi Accounting <%s>" % settings.DEFAULT_FROM_EMAIL env = ("[{}] ".format(settings.SERVER_ENVIRONMENT.upper()) if settings.SERVER_ENVIRONMENT != "production" else "") email_subject = "{}Subscriptions ending in 40 Days from {}".format( env, today.isoformat()) send_HTML_email( email_subject, settings.ACCOUNTS_EMAIL, email_content, email_from=from_email, text_content=email_content_plaintext, file_attachments=[file_attachment], ) log_accounting_info("Sent summary of ending subscriptions from %(today)s" % { 'today': today.isoformat(), })
def ensure_plans(config, dry_run, verbose, apps): DefaultProductPlan = apps.get_model('accounting', 'DefaultProductPlan') SoftwarePlan = apps.get_model('accounting', 'SoftwarePlan') SoftwarePlanVersion = apps.get_model('accounting', 'SoftwarePlanVersion') Role = apps.get_model('django_prbac', 'Role') for product_type in PRODUCT_TYPES: for plan_key, plan_deets in config.iteritems(): edition, is_trial = plan_key features = _ensure_features(edition, dry_run, verbose, apps) try: role = _ensure_role(plan_deets['role'], apps) except Role.DoesNotExist: return product, product_rate = _ensure_product_and_rate( plan_deets['product_rate'], product_type, edition, dry_run=dry_run, verbose=verbose, apps=apps, ) feature_rates = _ensure_feature_rates( plan_deets['feature_rates'], features, edition, dry_run=dry_run, verbose=verbose, apps=apps, ) software_plan = SoftwarePlan( name='%s Edition' % product.name, edition=edition, visibility=SoftwarePlanVisibility.PUBLIC) if dry_run: log_accounting_info("[DRY RUN] Creating Software Plan: %s" % software_plan.name) else: try: software_plan = SoftwarePlan.objects.get( name=software_plan.name) if verbose: log_accounting_info( "Plan '%s' already exists. Using existing plan to add version." % software_plan.name) except SoftwarePlan.DoesNotExist: software_plan.save() if verbose: log_accounting_info("Creating Software Plan: %s" % software_plan.name) software_plan_version = SoftwarePlanVersion(role=role, plan=software_plan) # must save before assigning many-to-many relationship if hasattr(SoftwarePlanVersion, 'product_rates'): software_plan_version.save() product_rate.save() if hasattr(SoftwarePlanVersion, 'product_rates'): software_plan_version.product_rates.add(product_rate) elif hasattr(SoftwarePlanVersion, 'product_rate'): software_plan_version.product_rate = product_rate else: raise AccountingError( 'SoftwarePlanVersion does not have product_rate or product_rates field' ) # must save before assigning many-to-many relationship if hasattr(SoftwarePlanVersion, 'product_rate'): software_plan_version.save() for feature_rate in feature_rates: feature_rate.save() software_plan_version.feature_rates.add(feature_rate) software_plan_version.save() default_product_plan = DefaultProductPlan( product_type=product.product_type, edition=edition, is_trial=is_trial) if dry_run: log_accounting_info( "[DRY RUN] Setting plan as default for product '%s' and edition '%s'." % (product.product_type, default_product_plan.edition)) else: try: default_product_plan = DefaultProductPlan.objects.get( product_type=product.product_type, edition=edition, is_trial=is_trial) if verbose: log_accounting_info( "Default for product '%s' and edition '%s' already exists." % (product.product_type, default_product_plan.edition)) except DefaultProductPlan.DoesNotExist: default_product_plan.plan = software_plan default_product_plan.save() if verbose: log_accounting_info( "Setting plan as default for product '%s' and edition '%s'." % (product.product_type, default_product_plan.edition))
def generate_invoices(based_on_date=None): """ Generates all invoices for the past month. """ today = based_on_date or datetime.date.today() invoice_start, invoice_end = get_previous_month_date_range(today) log_accounting_info( "Starting up invoices for %(start)s - %(end)s" % { 'start': invoice_start.strftime(USER_DATE_FORMAT), 'end': invoice_end.strftime(USER_DATE_FORMAT), }) all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain_obj = Domain.wrap(domain_doc) if not domain_obj.is_active: continue try: invoice_factory = DomainInvoiceFactory(invoice_start, invoice_end, domain_obj) invoice_factory.create_invoices() log_accounting_info("Sent invoices for domain %s" % domain_obj.name) except CreditLineError as e: log_accounting_error( "There was an error utilizing credits for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except InvoiceError as e: log_accounting_error( "Could not create invoice for domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except Exception as e: log_accounting_error( "Error occurred while creating invoice for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) all_customer_billing_accounts = BillingAccount.objects.filter( is_customer_billing_account=True) for account in all_customer_billing_accounts: try: if account.invoicing_plan == InvoicingPlan.QUARTERLY: customer_invoice_start = invoice_start - relativedelta( months=2) elif account.invoicing_plan == InvoicingPlan.YEARLY: customer_invoice_start = invoice_start - relativedelta( months=11) else: customer_invoice_start = invoice_start invoice_factory = CustomerAccountInvoiceFactory( account=account, date_start=customer_invoice_start, date_end=invoice_end) invoice_factory.create_invoice() except CreditLineError as e: log_accounting_error( "There was an error utilizing credits for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except InvoiceError as e: log_accounting_error( "Could not create invoice for domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except Exception as e: log_accounting_error( "Error occurred while creating invoice for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) if not settings.UNIT_TESTING: _invoicing_complete_soft_assert(False, "Invoicing is complete!")
def ensure_plans(config, dry_run, verbose, apps): DefaultProductPlan = apps.get_model('accounting', 'DefaultProductPlan') SoftwarePlan = apps.get_model('accounting', 'SoftwarePlan') SoftwarePlanVersion = apps.get_model('accounting', 'SoftwarePlanVersion') Role = apps.get_model('django_prbac', 'Role') for product_type in PRODUCT_TYPES: for plan_key, plan_deets in config.iteritems(): edition, is_trial = plan_key features = _ensure_features(edition, dry_run, verbose, apps) try: role = _ensure_role(plan_deets['role'], apps) except Role.DoesNotExist: return product, product_rate = _ensure_product_and_rate( plan_deets['product_rate'], product_type, edition, dry_run=dry_run, verbose=verbose, apps=apps, ) feature_rates = _ensure_feature_rates( plan_deets['feature_rates'], features, edition, dry_run=dry_run, verbose=verbose, apps=apps, ) software_plan = SoftwarePlan( name='%s Edition' % product.name, edition=edition, visibility=SoftwarePlanVisibility.PUBLIC ) if dry_run: log_accounting_info("[DRY RUN] Creating Software Plan: %s" % software_plan.name) else: try: software_plan = SoftwarePlan.objects.get(name=software_plan.name) if verbose: log_accounting_info( "Plan '%s' already exists. Using existing plan to add version." % software_plan.name ) except SoftwarePlan.DoesNotExist: software_plan.save() if verbose: log_accounting_info("Creating Software Plan: %s" % software_plan.name) software_plan_version = SoftwarePlanVersion(role=role, plan=software_plan) # must save before assigning many-to-many relationship if hasattr(SoftwarePlanVersion, 'product_rates'): software_plan_version.save() product_rate.save() if hasattr(SoftwarePlanVersion, 'product_rates'): software_plan_version.product_rates.add(product_rate) elif hasattr(SoftwarePlanVersion, 'product_rate'): software_plan_version.product_rate = product_rate else: raise AccountingError('SoftwarePlanVersion does not have product_rate or product_rates field') # must save before assigning many-to-many relationship if hasattr(SoftwarePlanVersion, 'product_rate'): software_plan_version.save() for feature_rate in feature_rates: feature_rate.save() software_plan_version.feature_rates.add(feature_rate) software_plan_version.save() default_product_plan = DefaultProductPlan( product_type=product.product_type, edition=edition, is_trial=is_trial ) if dry_run: log_accounting_info( "[DRY RUN] Setting plan as default for product '%s' and edition '%s'." % (product.product_type, default_product_plan.edition) ) else: try: default_product_plan = DefaultProductPlan.objects.get(product_type=product.product_type, edition=edition, is_trial=is_trial) if verbose: log_accounting_info( "Default for product '%s' and edition '%s' already exists." % (product.product_type, default_product_plan.edition) ) except DefaultProductPlan.DoesNotExist: default_product_plan.plan = software_plan default_product_plan.save() if verbose: log_accounting_info( "Setting plan as default for product '%s' and edition '%s'." % (product.product_type, default_product_plan.edition) )
def weekly_digest(): today = datetime.date.today() in_forty_days = today + datetime.timedelta(days=40) ending_in_forty_days = filter( lambda sub: not sub.is_renewed, Subscription.objects.filter( date_end__lte=in_forty_days, date_end__gte=today, is_active=True, is_trial=False, account__dimagi_contact__isnull=True, )) if not ending_in_forty_days: log_accounting_info( "Did not send summary of ending subscriptions because " "there are none." ) return table = [[ "Project Space", "Account", "Plan", "Salesforce Contract ID", "Dimagi Contact", "Start Date", "End Date", "Receives Invoice", "Created By", ]] def _fmt_row(sub): try: created_by_adj = SubscriptionAdjustment.objects.filter( subscription=sub, reason=SubscriptionAdjustmentReason.CREATE ).order_by('date_created')[0] created_by = dict(SubscriptionAdjustmentMethod.CHOICES).get( created_by_adj.method, "Unknown") except (IndexError, SubscriptionAdjustment.DoesNotExist): created_by = "Unknown" return [ sub.subscriber.domain, "%s (%s)" % (sub.account.name, sub.account.id), sub.plan_version.plan.name, sub.salesforce_contract_id, sub.account.dimagi_contact, sub.date_start, sub.date_end, "No" if sub.do_not_invoice else "YES", created_by, ] table.extend([_fmt_row(sub) for sub in ending_in_forty_days]) file_to_attach = StringIO() export_from_tables( [['End in 40 Days', table]], file_to_attach, Format.XLS_2007 ) email_context = { 'today': today.isoformat(), 'forty_days': in_forty_days.isoformat(), } email_content = render_to_string( 'accounting/digest_email.html', email_context) email_content_plaintext = render_to_string( 'accounting/digest_email.txt', email_context) format_dict = Format.FORMAT_DICT[Format.XLS_2007] file_attachment = { 'title': 'Subscriptions_%(start)s_%(end)s.xls' % { 'start': today.isoformat(), 'end': in_forty_days.isoformat(), }, 'mimetype': format_dict['mimetype'], 'file_obj': file_to_attach, } from_email = "Dimagi Accounting <%s>" % settings.DEFAULT_FROM_EMAIL env = ("[{}] ".format(settings.SERVER_ENVIRONMENT.upper()) if settings.SERVER_ENVIRONMENT != "production" else "") email_subject = "{}Subscriptions ending in 40 Days from {}".format(env, today.isoformat()) send_HTML_email( email_subject, settings.ACCOUNTS_EMAIL, email_content, email_from=from_email, text_content=email_content_plaintext, file_attachments=[file_attachment], ) log_accounting_info( "Sent summary of ending subscriptions from %(today)s" % { 'today': today.isoformat(), })
def _ensure_product_and_rate(product_rate, product_type, edition, dry_run, verbose, apps): """ Ensures that all the necessary SoftwareProducts and SoftwareProductRates are created for the plan. """ SoftwareProduct = apps.get_model('accounting', 'SoftwareProduct') SoftwareProductRate = apps.get_model('accounting', 'SoftwareProductRate') if verbose: log_accounting_info('Ensuring Products and Product Rates') product = SoftwareProduct(name='%s %s' % (product_type, edition), product_type=product_type) if edition == SoftwarePlanEdition.ENTERPRISE: product.name = "Dimagi Only %s" % product.name product_rate = SoftwareProductRate(**product_rate) if dry_run: log_accounting_info("[DRY RUN] Creating Product: %s" % product) log_accounting_info("[DRY RUN] Corresponding product rate of $%d created." % product_rate.monthly_fee) else: try: product = SoftwareProduct.objects.get(name=product.name) if verbose: log_accounting_info( "Product '%s' already exists. Using existing product to add rate." % product.name ) except SoftwareProduct.DoesNotExist: product.save() if verbose: log_accounting_info("Creating Product: %s" % product) if verbose: log_accounting_info("Corresponding product rate of $%d created." % product_rate.monthly_fee) product_rate.product = product return product, product_rate
def generate_invoices(based_on_date=None): """ Generates all invoices for the past month. """ today = based_on_date or datetime.date.today() invoice_start, invoice_end = get_previous_month_date_range(today) log_accounting_info("Starting up invoices for %(start)s - %(end)s" % { 'start': invoice_start.strftime(USER_DATE_FORMAT), 'end': invoice_end.strftime(USER_DATE_FORMAT), }) all_domain_ids = [d['id'] for d in Domain.get_all(include_docs=False)] for domain_doc in iter_docs(Domain.get_db(), all_domain_ids): domain_obj = Domain.wrap(domain_doc) if not domain_obj.is_active: continue try: invoice_factory = DomainInvoiceFactory(invoice_start, invoice_end, domain_obj) invoice_factory.create_invoices() log_accounting_info("Sent invoices for domain %s" % domain_obj.name) except CreditLineError as e: log_accounting_error( "There was an error utilizing credits for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except InvoiceError as e: log_accounting_error( "Could not create invoice for domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except Exception as e: log_accounting_error( "Error occurred while creating invoice for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) all_customer_billing_accounts = BillingAccount.objects.filter(is_customer_billing_account=True) for account in all_customer_billing_accounts: try: if account.invoicing_plan == InvoicingPlan.QUARTERLY: customer_invoice_start = invoice_start - relativedelta(months=2) elif account.invoicing_plan == InvoicingPlan.YEARLY: customer_invoice_start = invoice_start - relativedelta(months=11) else: customer_invoice_start = invoice_start invoice_factory = CustomerAccountInvoiceFactory( account=account, date_start=customer_invoice_start, date_end=invoice_end ) invoice_factory.create_invoice() except CreditLineError as e: log_accounting_error( "There was an error utilizing credits for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except InvoiceError as e: log_accounting_error( "Could not create invoice for domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) except Exception as e: log_accounting_error( "Error occurred while creating invoice for " "domain %s: %s" % (domain_obj.name, e), show_stack_trace=True, ) if not settings.UNIT_TESTING: _invoicing_complete_soft_assert(False, "Invoicing is complete!")
def ensure_plans(config, verbose, apps): DefaultProductPlan = apps.get_model('accounting', 'DefaultProductPlan') SoftwarePlan = apps.get_model('accounting', 'SoftwarePlan') SoftwarePlanVersion = apps.get_model('accounting', 'SoftwarePlanVersion') Role = apps.get_model('django_prbac', 'Role') for plan_key, plan_deets in six.iteritems(config): edition, is_trial, is_report_builder_enabled = plan_key features = _ensure_features(edition, verbose, apps) try: role = _ensure_role(plan_deets['role'], apps) except Role.DoesNotExist: return product, product_rate = _ensure_product_rate( plan_deets['product_rate_monthly_fee'], edition, verbose=verbose, apps=apps, ) feature_rates = _ensure_feature_rates( plan_deets['feature_rates'], features, edition, verbose=verbose, apps=apps, ) software_plan = SoftwarePlan( name=( (('%s Trial' % product_rate.name) if is_trial else ('%s Edition' % product_rate.name)) if product is None else product.name # TODO - remove after squashing migrations ), edition=edition, visibility=SoftwarePlanVisibility.PUBLIC ) if is_report_builder_enabled: software_plan.name = '%s - Report Builder (5 Reports)' % software_plan.name try: software_plan = SoftwarePlan.objects.get(name=software_plan.name) if verbose: log_accounting_info( "Plan '%s' already exists. Using existing plan to add version." % software_plan.name ) except SoftwarePlan.DoesNotExist: software_plan.save() if verbose: log_accounting_info("Creating Software Plan: %s" % software_plan.name) product_rate.save() software_plan_version = SoftwarePlanVersion(role=role, plan=software_plan, product_rate=product_rate) software_plan_version.save() for feature_rate in feature_rates: feature_rate.save() software_plan_version.feature_rates.add(feature_rate) software_plan_version.save() try: default_product_plan = DefaultProductPlan.objects.get( edition=edition, is_trial=is_trial, is_report_builder_enabled=is_report_builder_enabled, ) if verbose: log_accounting_info( "Default for edition '%s' with is_trial='%s' already exists." % (default_product_plan.edition, is_trial) ) except DefaultProductPlan.DoesNotExist: default_product_plan = DefaultProductPlan( edition=edition, is_trial=is_trial, is_report_builder_enabled=is_report_builder_enabled, ) finally: default_product_plan.plan = software_plan default_product_plan.save() if verbose: log_accounting_info( "Setting plan as default for edition '%s' with is_trial='%s'." % (default_product_plan.edition, is_trial) ) _clear_cache(SoftwarePlan.objects.all())
def ensure_plans(config, verbose, apps): DefaultProductPlan = apps.get_model('accounting', 'DefaultProductPlan') SoftwarePlan = apps.get_model('accounting', 'SoftwarePlan') SoftwarePlanVersion = apps.get_model('accounting', 'SoftwarePlanVersion') Role = apps.get_model('django_prbac', 'Role') for plan_key, plan_deets in config.items(): edition, is_trial, is_report_builder_enabled = plan_key features = _ensure_features(edition, verbose, apps) try: role = _ensure_role(plan_deets['role'], apps) except Role.DoesNotExist: return product, product_rate = _ensure_product_rate( plan_deets['product_rate_monthly_fee'], edition, verbose=verbose, apps=apps, ) feature_rates = _ensure_feature_rates( plan_deets['feature_rates'], features, edition, verbose=verbose, apps=apps, ) software_plan = SoftwarePlan( name=((('%s Trial' % product_rate.name) if is_trial else ('%s Edition' % product_rate.name)) if product is None else product.name # TODO - remove after squashing migrations ), edition=edition, visibility=SoftwarePlanVisibility.PUBLIC) if is_report_builder_enabled: software_plan.name = '%s - Report Builder (5 Reports)' % software_plan.name try: software_plan = SoftwarePlan.objects.get(name=software_plan.name) if verbose: log_accounting_info( "Plan '%s' already exists. Using existing plan to add version." % software_plan.name) except SoftwarePlan.DoesNotExist: software_plan.save() if verbose: log_accounting_info("Creating Software Plan: %s" % software_plan.name) product_rate.save() software_plan_version = SoftwarePlanVersion(role=role, plan=software_plan, product_rate=product_rate) software_plan_version.save() for feature_rate in feature_rates: feature_rate.save() software_plan_version.feature_rates.add(feature_rate) software_plan_version.save() try: default_product_plan = DefaultProductPlan.objects.get( edition=edition, is_trial=is_trial, is_report_builder_enabled=is_report_builder_enabled, ) if verbose: log_accounting_info( "Default for edition '%s' with is_trial='%s' already exists." % (default_product_plan.edition, is_trial)) except DefaultProductPlan.DoesNotExist: default_product_plan = DefaultProductPlan( edition=edition, is_trial=is_trial, is_report_builder_enabled=is_report_builder_enabled, ) finally: default_product_plan.plan = software_plan default_product_plan.save() if verbose: log_accounting_info( "Setting plan as default for edition '%s' with is_trial='%s'." % (default_product_plan.edition, is_trial)) _clear_cache(SoftwarePlan.objects.all())
def ensure_plans(config, dry_run, verbose, apps): DefaultProductPlan = apps.get_model('accounting', 'DefaultProductPlan') SoftwarePlan = apps.get_model('accounting', 'SoftwarePlan') SoftwarePlanVersion = apps.get_model('accounting', 'SoftwarePlanVersion') Role = apps.get_model('django_prbac', 'Role') for plan_key, plan_deets in config.iteritems(): edition, is_trial, is_report_builder_enabled = plan_key features = _ensure_features(edition, dry_run, verbose, apps) try: role = _ensure_role(plan_deets['role'], apps) except Role.DoesNotExist: return product, product_rate = _ensure_product_and_rate( plan_deets['product_rate'], edition, dry_run=dry_run, verbose=verbose, apps=apps, ) feature_rates = _ensure_feature_rates( plan_deets['feature_rates'], features, edition, dry_run=dry_run, verbose=verbose, apps=apps, ) software_plan = SoftwarePlan( name='%s Edition' % product.name, edition=edition, visibility=SoftwarePlanVisibility.PUBLIC ) if is_report_builder_enabled: software_plan.name = '%s - Report Builder (5 Reports)' % software_plan.name if dry_run: log_accounting_info("[DRY RUN] Creating Software Plan: %s" % software_plan.name) else: try: software_plan = SoftwarePlan.objects.get(name=software_plan.name) if verbose: log_accounting_info( "Plan '%s' already exists. Using existing plan to add version." % software_plan.name ) except SoftwarePlan.DoesNotExist: software_plan.save() if verbose: log_accounting_info("Creating Software Plan: %s" % software_plan.name) software_plan_version = SoftwarePlanVersion(role=role, plan=software_plan) # TODO - squash migrations and remove this # must save before assigning many-to-many relationship if hasattr(SoftwarePlanVersion, 'product_rates'): software_plan_version.save() product_rate.save() # TODO - squash migrations and remove this if hasattr(SoftwarePlanVersion, 'product_rates'): software_plan_version.product_rates.add(product_rate) elif hasattr(SoftwarePlanVersion, 'product_rate'): software_plan_version.product_rate = product_rate else: raise AccountingError('SoftwarePlanVersion does not have product_rate or product_rates field') # TODO - squash migrations and remove this # must save before assigning many-to-many relationship if hasattr(SoftwarePlanVersion, 'product_rate'): software_plan_version.save() for feature_rate in feature_rates: feature_rate.save() software_plan_version.feature_rates.add(feature_rate) software_plan_version.save() default_product_plan = DefaultProductPlan( edition=edition, is_trial=is_trial ) if hasattr(default_product_plan, 'is_report_builder_enabled'): default_product_plan.is_report_builder_enabled = is_report_builder_enabled # TODO - squash migrations and remove this if hasattr(default_product_plan, 'product_type'): default_product_plan.product_type = SoftwareProductType.COMMCARE if dry_run: log_accounting_info( "[DRY RUN] Setting plan as default for edition '%s' with is_trial='%s'." % (default_product_plan.edition, is_trial) ) else: try: if not hasattr(default_product_plan, 'product_type'): if hasattr(default_product_plan, 'is_report_builder_enabled'): default_product_plan = DefaultProductPlan.objects.get( edition=edition, is_trial=is_trial, is_report_builder_enabled=is_report_builder_enabled, ) else: # TODO - squash migrations and remove this default_product_plan = DefaultProductPlan.objects.get( edition=edition, is_trial=is_trial, ) else: # TODO - squash migrations and remove this default_product_plan = DefaultProductPlan.objects.get( edition=edition, is_trial=is_trial, product_type=SoftwareProductType.COMMCARE ) if verbose: log_accounting_info( "Default for edition '%s' with is_trial='%s' already exists." % (default_product_plan.edition, is_trial) ) except DefaultProductPlan.DoesNotExist: default_product_plan.plan = software_plan default_product_plan.save() if verbose: log_accounting_info( "Setting plan as default for edition '%s' with is_trial='%s'." % (default_product_plan.edition, is_trial) )
def create_invoice_for_subscription(self, subscription): if subscription.is_trial: # Don't create invoices for trial subscriptions log_accounting_info( "Skipping invoicing for Subscription %s because it's a trial." % subscription.pk ) return if subscription.date_start > self.date_start: invoice_start = subscription.date_start else: invoice_start = self.date_start if (subscription.date_end is not None and subscription.date_end <= self.date_end): # Since the Subscription is actually terminated on date_end # have the invoice period be until the day before date_end. invoice_end = subscription.date_end - datetime.timedelta(days=1) else: invoice_end = self.date_end with transaction.atomic(): invoice, is_new_invoice = Invoice.objects.get_or_create( subscription=subscription, date_start=invoice_start, date_end=invoice_end, is_hidden=subscription.do_not_invoice, ) if not is_new_invoice: raise InvoiceAlreadyCreatedError("invoice id: {id}".format(id=invoice.id)) if subscription.subscriptionadjustment_set.count() == 0: # record that the subscription was created SubscriptionAdjustment.record_adjustment( subscription, method=SubscriptionAdjustmentMethod.TASK, invoice=invoice, ) self.generate_line_items(invoice, subscription) invoice.calculate_credit_adjustments() invoice.update_balance() invoice.save() total_balance = sum(invoice.balance for invoice in Invoice.objects.filter( is_hidden=False, subscription__subscriber__domain=invoice.get_domain(), )) should_set_date_due = ((total_balance > SMALL_INVOICE_THRESHOLD) or (invoice.account.auto_pay_enabled and total_balance > Decimal(0))) if should_set_date_due: days_until_due = DEFAULT_DAYS_UNTIL_DUE if subscription.date_delay_invoicing is not None: td = subscription.date_delay_invoicing - self.date_end days_until_due = max(days_until_due, td.days) invoice.date_due = self.date_end + datetime.timedelta(days_until_due) invoice.save() record = BillingRecord.generate_record(invoice) try: record.send_email() except InvoiceEmailThrottledError as e: if not self.logged_throttle_error: log_accounting_error(e.message) self.logged_throttle_error = True return invoice