def test_invalid_freq_type(self): with self.assertRaises(ValueError): next_transaction_datetime( started_at=datetime.datetime.utcnow(), frequency=999, period=0, interval=1, )
def test_invalid_freq_type(self): from billy.models.schedule import next_transaction_datetime with self.assertRaises(ValueError): next_transaction_datetime( started_at=datetime.datetime.utcnow(), frequency=999, period=0, interval=1, )
def test_invalid_interval(self): with self.assertRaises(ValueError): next_transaction_datetime( started_at=utc_now(), frequency=self.plan_model.frequencies.DAILY, period=0, interval=0, ) with self.assertRaises(ValueError): next_transaction_datetime( started_at=utc_now(), frequency=self.plan_model.frequencies.DAILY, period=0, interval=-1, )
def test_invalid_interval(self): with self.assertRaises(ValueError): next_transaction_datetime( started_at=datetime.datetime.utcnow(), frequency=self.plan_model.FREQ_DAILY, period=0, interval=0, ) with self.assertRaises(ValueError): next_transaction_datetime( started_at=datetime.datetime.utcnow(), frequency=self.plan_model.FREQ_DAILY, period=0, interval=-1, )
def test_invalid_interval(self): from billy.models.plan import PlanModel from billy.models.schedule import next_transaction_datetime with self.assertRaises(ValueError): next_transaction_datetime( started_at=datetime.datetime.utcnow(), frequency=PlanModel.FREQ_DAILY, period=0, interval=0, ) with self.assertRaises(ValueError): next_transaction_datetime( started_at=datetime.datetime.utcnow(), frequency=PlanModel.FREQ_DAILY, period=0, interval=-1, )
def assert_next_day(now_dt, expected): with freeze_time(now_dt): now = utc_now() next_dt = next_transaction_datetime( started_at=now, frequency=self.plan_model.frequencies.DAILY, period=1, ) self.assertEqual(next_dt, expected)
def assert_next_day(now_dt, expected): with freeze_time(now_dt): now = datetime.datetime.utcnow() next_dt = next_transaction_datetime( started_at=now, frequency=self.plan_model.FREQ_DAILY, period=1, ) self.assertEqual(next_dt, expected)
def assert_next_day(now_dt, expected): with freeze_time(now_dt): now = datetime.datetime.utcnow() next_dt = next_transaction_datetime( started_at=now, frequency=PlanModel.FREQ_DAILY, period=1, ) self.assertEqual(next_dt, expected)
def assert_schedule(self, started_at, frequency, interval, length, expected): result = [] for period in range(length): dt = next_transaction_datetime( started_at=started_at, frequency=frequency, period=period, interval=interval, ) result.append(dt) self.assertEqual(result, expected)
def yield_transactions(self, subscription_guids=None, now=None): """Generate new necessary transactions according to subscriptions we had return guid list :param subscription_guids: A list subscription guid to yield transaction_type from, if None is given, all subscriptions in the database will be the yielding source :param now: the current date time to use, now_func() will be used by default :return: a generated transaction guid list """ from sqlalchemy.sql.expression import not_ if now is None: now = tables.now_func() tx_model = TransactionModel(self.session) Subscription = tables.Subscription transaction_guids = [] # as we may have multiple new transactions for one subscription to # process, for example, we didn't run this method for a long while, # in this case, we need to make sure all transactions are yielded while True: # find subscriptions which should yield new transactions query = ( self.session.query(Subscription) .filter(Subscription.next_transaction_at <= now) .filter(not_(Subscription.canceled)) ) if subscription_guids is not None: query = query.filter(Subscription.guid.in_(subscription_guids)) subscriptions = query.all() # okay, we have no more transaction to process, just break if not subscriptions: self.logger.info('No more subscriptions to process') break for subscription in subscriptions: if subscription.plan.plan_type == PlanModel.TYPE_CHARGE: transaction_type = tx_model.TYPE_CHARGE elif subscription.plan.plan_type == PlanModel.TYPE_PAYOUT: transaction_type = tx_model.TYPE_PAYOUT else: raise ValueError('Unknown plan type {} to process' .format(subscription.plan.plan_type)) # when amount of subscription is given, we should use it # instead the one from plan if subscription.amount is None: amount = subscription.plan.amount else: amount = subscription.amount type_map = { tx_model.TYPE_CHARGE: 'charge', tx_model.TYPE_PAYOUT: 'payout', } self.logger.debug( 'Creating transaction for %s, transaction_type=%s, ' 'payment_uri=%s, amount=%s, scheduled_at=%s, period=%s', subscription.guid, type_map[transaction_type], subscription.payment_uri, amount, subscription.next_transaction_at, subscription.period, ) # create the new transaction for this subscription guid = tx_model.create( subscription_guid=subscription.guid, payment_uri=subscription.payment_uri, amount=amount, transaction_type=transaction_type, scheduled_at=subscription.next_transaction_at, ) self.logger.info( 'Created transaction for %s, guid=%s, transaction_type=%s, ' 'payment_uri=%s, amount=%s, scheduled_at=%s, period=%s', subscription.guid, guid, type_map[transaction_type], subscription.payment_uri, amount, subscription.next_transaction_at, subscription.period, ) # advance the next transaction time subscription.period += 1 subscription.next_transaction_at = next_transaction_datetime( started_at=subscription.started_at, frequency=subscription.plan.frequency, period=subscription.period, interval=subscription.plan.interval, ) self.session.add(subscription) self.session.flush() transaction_guids.append(guid) self.session.flush() return transaction_guids
def yield_invoices(self, subscriptions=None, now=None): """Generate new scheduled invoices from given subscriptions :param subscriptions: A list subscription to yield invoices from, if None is given, all subscriptions in the database will be the yielding source :param now: the current date time to use, now_func() will be used by default :return: a generated transaction guid list """ if now is None: now = tables.now_func() invoice_model = self.factory.create_invoice_model() Subscription = tables.Subscription subscription_guids = [] if subscriptions is not None: subscription_guids = [subscription.guid for subscription in subscriptions] invoices = [] # as we may have multiple new invoices for one subscription to # yield now, for example, we didn't run this method for a long while, # in this case, we need to make sure all transactions are yielded while True: # find subscriptions which should yield new invoices query = ( self.session.query(Subscription) .filter(Subscription.next_invoice_at <= now) .filter(not_(Subscription.canceled)) ) if subscription_guids: query = query.filter(Subscription.guid.in_(subscription_guids)) query = list(query) # okay, we have no more subscription to process, just break if not query: self.logger.info("No more subscriptions to process") break for subscription in query: if subscription.plan.plan_type == PlanModel.TYPE_CHARGE: transaction_type = "charge" elif subscription.plan.plan_type == PlanModel.TYPE_PAYOUT: transaction_type = "payout" else: raise ValueError("Unknown plan type {} to process".format(subscription.plan.plan_type)) amount = subscription.effective_amount # create the new transaction for this subscription invoice = invoice_model.create( subscription=subscription, funding_instrument_uri=subscription.funding_instrument_uri, amount=amount, scheduled_at=subscription.next_invoice_at, appears_on_statement_as=subscription.appears_on_statement_as, ) self.logger.info( "Created subscription invoice for %s, guid=%s, " "transaction_type=%s, funding_instrument_uri=%s, " "amount=%s, scheduled_at=%s, period=%s", subscription.guid, invoice.guid, transaction_type, invoice.funding_instrument_uri, invoice.amount, invoice.scheduled_at, subscription.invoice_count - 1, ) # advance the next invoice time subscription.next_invoice_at = next_transaction_datetime( started_at=subscription.started_at, frequency=subscription.plan.frequency, period=subscription.invoice_count, interval=subscription.plan.interval, ) self.logger.info( "Schedule next invoice of %s at %s (period=%s)", subscription.guid, subscription.next_invoice_at, subscription.invoice_count, ) self.session.flush() invoices.append(invoice) self.session.flush() return invoices
def yield_invoices(self, subscriptions=None, now=None): """Generate new scheduled invoices from given subscriptions :param subscriptions: A list subscription to yield invoices from, if None is given, all subscriptions in the database will be the yielding source :param now: the current date time to use, now_func() will be used by default :return: a generated transaction guid list """ if now is None: now = tables.now_func() invoice_model = self.factory.create_invoice_model() Subscription = tables.Subscription subscription_guids = [] if subscriptions is not None: subscription_guids = [ subscription.guid for subscription in subscriptions ] invoices = [] # as we may have multiple new invoices for one subscription to # yield now, for example, we didn't run this method for a long while, # in this case, we need to make sure all transactions are yielded while True: # find subscriptions which should yield new invoices query = ( self.session.query(Subscription) .filter(Subscription.next_invoice_at <= now) .filter(not_(Subscription.canceled)) ) if subscription_guids: query = query.filter(Subscription.guid.in_(subscription_guids)) query = list(query) # okay, we have no more subscription to process, just break if not query: self.logger.info('No more subscriptions to process') break for subscription in query: amount = subscription.effective_amount # create the new transaction for this subscription invoice = invoice_model.create( subscription=subscription, funding_instrument_uri=subscription.funding_instrument_uri, amount=amount, scheduled_at=subscription.next_invoice_at, appears_on_statement_as=subscription.appears_on_statement_as, ) self.logger.info( 'Created subscription invoice for %s, guid=%s, ' 'plan_type=%s, funding_instrument_uri=%s, ' 'amount=%s, scheduled_at=%s, period=%s', subscription.guid, invoice.guid, subscription.plan.plan_type, invoice.funding_instrument_uri, invoice.amount, invoice.scheduled_at, subscription.invoice_count - 1, ) # advance the next invoice time subscription.next_invoice_at = next_transaction_datetime( started_at=subscription.started_at, frequency=subscription.plan.frequency, period=subscription.invoice_count, interval=subscription.plan.interval, ) self.logger.info( 'Schedule next invoice of %s at %s (period=%s)', subscription.guid, subscription.next_invoice_at, subscription.invoice_count, ) self.session.flush() invoices.append(invoice) self.session.flush() return invoices