class ExchangeRate(models.Model): class Meta: verbose_name = _('Exchange Rate') verbose_name_plural = _('Exchange Rates') source_currency = CurrencyField() target_currency = CurrencyField() rate = models.DecimalField(max_digits=12, decimal_places=6) datetime = models.DateTimeField()
class Sheaf(models.Model): account = models.ForeignKey( verbose_name=_('account'), to=Account, related_name='sheaves' ) amount = models.DecimalField( verbose_name=_('sheaf'), max_digits=settings.MAX_DIGITS, decimal_places=settings.DECIMAL_PLACES ) currency = CurrencyField( verbose_name=_('currency'), price_field='amount', default=settings.BASE_CURRENCY ) def __str__(self): return '{amount} {currency} on {account}'.format( amount=self.amount, currency=self.currency, account=self.account ) def __eq__(self, other): return type(other) == Sheaf and \ self.account == other.account and \ self.amount == other.amount and \ self.currency == other.currency def __ne__(self, other): return not self.__eq__(other) class Meta: unique_together = ('account', 'currency')
class UserSettings(CommonInfo): """The user settings class.""" objects = UserSettingsManager() language = LanguageField( verbose_name=_("language"), null=True, blank=True, default=settings.LANGUAGE_CODE, choices=settings.APP_LANGUAGES, ) currency = CurrencyField( verbose_name=_("currency"), null=True, blank=True, choices=CURRENCY_CHOICES, ) user = models.OneToOneField( User, on_delete=models.CASCADE, related_name="settings", verbose_name=_("user"), ) is_last_name_hidden = models.BooleanField( default=False, help_text=_("Is the last name hidden from other users?"), verbose_name=_("is the last name hidden?"), ) units = UnitsField(verbose_name=_("units")) def __str__(self) -> str: """Return the string representation.""" return f"{self.user} settings"
class Organization(models.Model): name = models.CharField(max_length=255, default=None) subscription = models.CharField(max_length=1, choices=SUBSCRIPTION_TYPES) owner = models.ForeignKey(User, on_delete=models.PROTECT) number_scheme = models.CharField(max_length=1, choices=NUMBER_SCHEMES, default=NUMBER_SCHEME_SEMI_INTELLIGENT) number_class_code_len = models.PositiveIntegerField(default=NUMBER_CLASS_CODE_LEN_DEFAULT, validators=[MinValueValidator(NUMBER_CLASS_CODE_LEN_MIN), MaxValueValidator(NUMBER_CLASS_CODE_LEN_MAX)]) number_item_len = models.PositiveIntegerField(default=NUMBER_ITEM_LEN_DEFAULT, validators=[MinValueValidator(NUMBER_ITEM_LEN_MIN), MaxValueValidator(NUMBER_ITEM_LEN_MAX)]) number_variation_len = models.PositiveIntegerField(default=NUMBER_VARIATION_LEN_DEFAULT, validators=[MinValueValidator(NUMBER_VARIATION_LEN_MIN), MaxValueValidator(NUMBER_VARIATION_LEN_MAX)]) google_drive_parent = models.CharField(max_length=128, blank=True, default=None, null=True) currency = CurrencyField(max_length=3, choices=CURRENCY_CHOICES, default='USD') def number_cs(self): return "C" * self.number_class_code_len def number_ns(self): return "N" * self.number_item_len def number_vs(self): return "V" * self.number_variation_len def __str__(self): return u'%s' % self.name def seller_parts(self): return SellerPart.objects.filter(seller__organization=self) def save(self, *args, **kwargs): super(Organization, self).save() SellerPart.objects.filter(seller__organization=self).update(unit_cost_currency=self.currency, nre_cost_currency=self.currency)
class Rate(models.Model, metaclass=RateMetaclass): """ Main entity for representing rates of currencies. """ date_of_rate = models.DateTimeField(verbose_name='Rate date') base_currency = CurrencyField(default=settings.DEFAULT_CURRENCY, verbose_name='Default currency') created_at = models.DateTimeField( auto_now_add=True, verbose_name='Date of request to EU bank') @classmethod def sync(cls): """ Method for updating rate from EU CENTRAL BANK information. """ import xml.etree.ElementTree as ET response = requests.get(settings.EU_CENTRAL_BANK_URL) parsed = ET.fromstring(response.content) kwgs = { 'base_currency': 'EUR', 'EUR': 1, 'date_of_rate': parsed[2][0].attrib['time'] } for currency_info in parsed[2][0]: code = currency_info.attrib['currency'] rate = currency_info.attrib['rate'] if code in settings.CURRENCIES: kwgs[code] = rate cls.objects.create(**kwgs) def __str__(self): return f'Rate {self.date_of_rate.strftime("%d.%m.%Y %H:%M")}'
class Contribution(models.Model): _import = models.ForeignKey( ContributionImport, null=True, blank=True, on_delete=models.CASCADE, related_name="contributions", ) _import.verbose_name = _("Import") person = models.ForeignKey(Person, null=True, on_delete=models.SET_NULL, related_name="contributions") person.verbose_name = _("Person") date = models.DateField(_("Date")) note = models.CharField(_("Note"), max_length=255) debitaccount = models.CharField(_("Debit Account"), max_length=32) creditaccount = models.CharField(_("Credit Account"), max_length=32) amount = models.DecimalField(_("Amount"), max_digits=12, decimal_places=2) currency = CurrencyField(_("Currency"), choices=CURRENCY_CHOICES) currency.lazy_choices = lambda *args: settings.PREFERRED_CURRENCIES def amount_formatted(self): return Money(self.amount, self.currency) amount_formatted.verbose_name = _("Amount") def __str__(self): return f"{self.date}: {self.person} {self.amount_formatted()}" class Meta: verbose_name = _("Contribution") verbose_name_plural = _("Contributions") ordering = ["-date"]
class CurrencyRate(models.Model): class Meta: verbose_name = _('Currency Rate') verbose_name_plural = _('Currency Rates') source_currency = CurrencyField(_('source currency'), ) target_currency = CurrencyField( _('target currency'), default=moneyed.EUR, ) rate = models.DecimalField( _('rate'), max_digits=10, decimal_places=5, ) uploaded = models.DateTimeField(_('uploaded'), )
class Provider(Model): name = CharField(max_length=100) email = EmailField(blank=True, null=True) phone = PhoneNumberField(blank=True, null=True) cur = CurrencyField(default='usd') def __str__(self): return "{}({})".format(self.name, self.id)
class Revision(models.Model): version = models.CharField(max_length=10) country = models.ForeignKey(Country, on_delete=models.CASCADE) pay_period = models.PositiveIntegerField(choices=PayPeriod.choices) currency = CurrencyField() date = models.DateField() def __str__(self): return self.version
class ServiceProvider(models.Model): name = models.CharField(max_length=100) email = models.EmailField() phone = PhoneNumberField() language = LanguageField(default='en') currency = CurrencyField(default='USD') def __str__(self): return self.name
class Provider(Dated): name = models.CharField(max_length=255) email = models.EmailField(blank=True) phone = PhoneNumberField(blank=True) language = LanguageField(default='en') currency = CurrencyField(default='USD') def __str__(self): return self.name class Meta: ordering = ['id']
class Provider(models.Model): """Model definition for Provider.""" name = models.CharField("Provider's name.", max_length=50) phone = PhoneNumberField("Provider's phone number.") language = LanguageField("Provider's language.") currency = CurrencyField("Provider's currency.") user = models.OneToOneField(User, on_delete=models.CASCADE) def __str__(self): """Unicode representation of Provider.""" return self.name
class Account(CodeNaturalKeyAbstractModel): name = models.CharField(max_length=100, null=True, blank=True) owner = models.ForeignKey(Actor, on_delete=models.CASCADE, related_name='accounts') currency = CurrencyField() description = models.CharField(max_length=200, null=True, blank=True) def __str__(self): return f'{self.name} ({self.currency}) of {self.owner}' def balance(self): return reduce(lambda x, item: x + item.amount, self.payments_in.all(), Money(0, self.currency)) \ - reduce(lambda x, item: x + item.amount, self.payments_out.all(), Money(0, self.currency))
class Account(models.Model): type = models.IntegerField(choices=AccountType.choices) currency = CurrencyField() account_num = models.CharField(max_length=30) name = models.CharField(max_length=50) description = models.TextField() is_hidden = models.BooleanField(default=False) parent_id = models.IntegerField(null=True, blank=True) ending_balance_amt = models.DecimalField( max_digits=14, decimal_places=2, ) bank_number = models.CharField(max_length=25)
class Provider(Dated): name = models.CharField(max_length=255) email = models.EmailField(blank=True) phone = PhoneNumberField(blank=True) language = LanguageField(default='en') currency = CurrencyField(default='USD') def __str__(self): return self.name class Meta: ordering = ['id'] class ServiceArea(Dated): provider = models.ForeignKey(Provider) name = models.CharField(max_length=255) poly = models.PolygonField() price = MoneyField(max_digits=19, decimal_places=8) def __str__(self): return self.name class Meta: ordering = ['id']
class BankAccount(models.Model): """Bank account.""" account_number = models.TextField(unique=True, verbose_name=_('Account number')) account_name = models.TextField(blank=True, verbose_name=_('Account name')) currency = CurrencyField() class Meta: """Meta class.""" verbose_name = _('Bank account') verbose_name_plural = _('Bank accounts') def __str__(self): """Return string representation of bank account.""" return '{} {}'.format(self.account_name, self.account_number)
class Provider(models.Model): """ Provider's model """ name = models.CharField(_('Name'), max_length=256) email = models.EmailField(_('Email'), unique=True) phone_number = PhoneNumberField() language = LanguageField() currency = CurrencyField() created_at = models.DateTimeField(_('Created at'), auto_now_add=True) class Meta: verbose_name = _('Provider') verbose_name_plural = _('Providers') def __str__(self): return f'{self.name} Provider'
class Country(Place): code = models.CharField(max_length=2, db_index=True) population = models.IntegerField() continent = models.CharField(max_length=2) tld = models.CharField(max_length=5) languages = models.ManyToManyField(Language, related_name="countries") currency = CurrencyField() class Meta: ordering = ['name'] verbose_name_plural = "countries" @property def parent(self): return None def __unicode__(self): return force_unicode(self.name)
class UserProfile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) default_currency = CurrencyField(default=DEFAULT_CURRENCY, choices=CURRENCY_CHOICES) notif_invited_to_event = models.BooleanField(default=True) notif_requested_by_one_user = models.BooleanField(default=True) notif_upcoming_event = models.BooleanField(default=True) notif_edited_event = models.BooleanField(default=True) @classmethod def get_or_create(cls, user, *args, **kwargs): try: profile = cls.objects.get(user=user) return profile except cls.DoesNotExist: return cls(user=user, *args, **kwargs)
class Country(models.Model ): # could expand on pypi.python.org/pypi/django-countries id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=100, blank=True) code = models.CharField(max_length=3, blank=True) calling_code = models.CharField(max_length=3, blank=True) # assuming countries stick to one currency nationwide currency = CurrencyField(help_text='default currency for the country', null=True, blank=True) # MODEL PROPERTIES # MODEL FUNCTIONS def __str__(self): return str(self.code) class Meta: verbose_name_plural = 'countries'
class Account(Model): OPEN = 'OPEN' CLOSED = 'CLOSED' STATUS_CHOICES = ( (OPEN, _('Open')), (CLOSED, _('Closed')), ) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True) owner = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='billing_account', on_delete=PROTECT) currency = CurrencyField(db_index=True) status = FSMField(max_length=20, choices=STATUS_CHOICES, default=OPEN, db_index=True) objects = AccountQuerySet.as_manager() def balance(self, as_of: date = None): charges = Charge.objects.filter(account=self) transactions = Transaction.successful.filter(account=self) if as_of is not None: charges = charges.filter(created__lte=as_of) transactions = transactions.filter(created__lte=as_of) return total_amount(transactions) - total_amount(charges) @transition(field=status, source=OPEN, target=CLOSED) def close(self): pass @transition(field=status, source=CLOSED, target=OPEN) def reopen(self): pass def __str__(self): return str(self.owner)
class User(AbstractBaseUser, PermissionsMixin): """Custom user model that support using email instead of username""" email = models.EmailField(max_length=255, unique=True) name = models.CharField(max_length=255) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) main_currency = CurrencyField(verbose_name=_('Main currency')) objects = UserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ('name', ) def save(self, *args, **kwargs): created = self._state.adding super(User, self).save(*args, **kwargs) if created: from .services import (create_initial_income_categories, create_initial_expense_categories) create_initial_income_categories(self) create_initial_expense_categories(self)
class Shop(Timestampable, Locatable, Contactable, Translatable, Permalinkable, models.Model): owner = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.PROTECT, related_name="shop") name = models.CharField(max_length=50) description = models.TextField(default="", blank=True) logo_image = models.OneToOneField( 'common.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='shop_as_logo', help_text= 'if not square will be padded, like a social media profile pic') icon_image = models.OneToOneField( 'common.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='shop_as_icon', help_text='version of the logo that fits in a small square') pushover_user_key = models.CharField(max_length=30, default="", blank=True) pushover_device_name = models.CharField(max_length=25, default="", blank=True) # SOCIAL ACCOUNTS facebook_href = models.URLField(default="", blank=True) instagram_href = models.URLField(default="", blank=True) google_maps_href = models.URLField(default="", blank=True) trip_advisor_href = models.URLField(default="", blank=True) # SETTINGS is_ghost_location = models.BooleanField(default=False) currency = CurrencyField(default='THB') # INTERFACES: BOTS AND WEBSITES # line_channel = line_app.LineChannel # website = website.Website gramables = models.ManyToManyField('common.Image', blank=True) # INHERITED FIELDS: # address, latitude, longitude # contact_name, contact_phone, contact_email # base_language, language # HISTORY MANAGER # history = HistoricalRecords() # MODEL PROPERTIES @property def customer_line_channel(self): from apps.line_app.models.line_channel import LineChannel, CUSTOMER_CHANNEL return LineChannel.objects.filter( shop=self, channel_type=CUSTOMER_CHANNEL).first() @property def orders(self): from apps.shop.models import Order return Order.objects.filter( line_channel_membership__line_channel__shop_id=self.id) @property def is_open(self) -> bool: return any( [schedule.is_on_now for schedule in self.menu.schedules.all()]) @property def time_until_next_open(self) -> timedelta: return min([ schedule.time_until_next_on for schedule in self.menu.schedules.all() ]) # MODEL FUNCTIONS def setup_related_models(self): from apps.line_app.models.line_channel import LineChannel, CUSTOMER_CHANNEL line_channel, lc_created = LineChannel.objects.get_or_create( shop=self, channel_type=CUSTOMER_CHANNEL) from apps.shop.models import Menu, Schedule menu, m_created = Menu.objects.get_or_create(shop=self) if not Schedule.objects.filter(menu=menu).exists(): schedule = Schedule.objects.create(menu=menu, available_weekdays=list( range(7))) if not self.timezone: self.timezone = pytz.timezone('Asia/Bangkok') self.save() def __str__(self): return self.name or f"Shop {self.id}" def reset_slug(self): self.slug = slugify(self.name) # @models.permalink # def get_absolute_url(self): # url_kwargs = self.get_url_kwargs(slug=self.slug) # return (self.url_name, (), url_kwargs) def get_absolute_url(self): return reverse('shop:shop', kwargs={'shop_slug': self.slug}) # META class Meta: ordering = ('name', ) unique_together = ('owner', 'name')
class Order(TimeStampedModel, StatusModel): STATUS = Choices( 'cart', 'confirmed', 'paid', 'canceled', 'completed', 'refunded', ) billing_address = models.ForeignKey( Address, null=True, blank=True, related_name='+', ) shipping_address = models.ForeignKey( Address, null=True, blank=True, related_name='+', ) currency = CurrencyField() user = models.ForeignKey( settings.AUTH_USER_MODEL, blank=True, null=True, verbose_name=_('user'), related_name='orders', on_delete=models.SET_NULL, ) email = models.EmailField( _('email address'), blank=True, default='', ) custom = pg_fields.JSONField( _('custom order data'), blank=True, default=dict, ) def get_amount(self): zero = decimal.Decimal('0.00') return max((self.items.aggregate(sum=models.Sum( ((models.F('price') + models.F('vat') - models.Func(models.F('discount'), function='ABS')) * models.F('quantity')), output_field=models.DecimalField()))['sum'] or zero), zero) get_amount.short_description = _('amount') amount = property(get_amount) def get_order_no(self): return (self.id * 10) + int(luhn.calc_check_digit(self.id)) get_order_no.short_description = _('order number') order_no = property(get_order_no) def get_remainder(self): return self.amount - (self.invoices.filter( status__in=('authorized', 'captured')).aggregate( sum=models.Sum(models.F('paid')))['sum'] or decimal.Decimal('0.00')) get_remainder.short_description = _('remainder') remainder = property(get_remainder)
class Order(Document): DOCUMENT_NUMBER_TEMPLATE = 'CO-{number_month}' class State(TextChoices): NEW = 'NEW', "Новый" WAITING = 'WAITING', "Ожидает" CONFIRMED = 'CONFIRMED', "Подтверждён" COMPLETED = 'COMPLETED', "Выполнен" PROCESSING = 'PROCESSING', "Исполняется" CANCELLED = 'CANCELLED', "Отменён" FINAL_STATES = (State.COMPLETED, State.CANCELLED) class FulfillOn(TextChoices): CREATED = 'CREATED', "когда заказ создан" CONFIRMED = 'CONFIRMED', "когда заказ подтверждён" ORDER_PAYED_FULL = 'ORDER_PAYED_FULL', "когда счет оплачен полностью" ORDER_PAYED_PARTLY = 'ORDER_PAYED_PARTLY', "когда счет оплачен частично" state = models.CharField(max_length=200, choices=State.choices, null=True, blank=True) seller = models.ForeignKey(Actor, on_delete=models.CASCADE, null=True, blank=True, related_name='+') buyer = models.ForeignKey(Actor, on_delete=models.CASCADE, null=True, blank=True, related_name='+') currency = CurrencyField() valid_until = models.DateField(null=True, blank=True) comment = models.TextField(null=True, blank=True) fulfill_on = models.CharField(max_length=200, choices=FulfillOn.choices, null=True, blank=True) @property def amount(self): currency = self.currency if not currency: raise ValueError(f'No currency in order {self}') return reduce(lambda x, item: x + item.sum, self.items.all(), Money(0, currency)) def add_item(self, product: Product, quantity: int = 1): OrderItem.objects.create(product=product, order=self, price=product.price, quantity=quantity) def create_invoice(self) -> 'Invoice': return Invoice( order=self, amount=self.amount, seller=self.seller, buyer=self.buyer, ) def fulfill(self): states = Order.State assert self.state in ( None, states.NEW, states.CONFIRMED), f"Cannot fulfill from state {self.state}" self.set_state(states.PROCESSING)
class BankAccount(models.Model): """Bank account.""" account_number = models.TextField(verbose_name=_('Account number')) account_name = models.TextField(blank=True, verbose_name=_('Account name')) currency = CurrencyField()
class ExchangeRate(models.Model): date = models.DateField() currency = CurrencyField() base_currency = CurrencyField() exchange_rate = models.FloatField()
class Transaction(models.Model): UNITS = ( ('pcs', _('pieces')), ('kg', _('kilos')), ('g', _('grams')), ('l', _('liters')), ('gal', _('gallons')), ('p', _('pounds')) ) date = models.DateField( verbose_name=_('date') ) approved = models.BooleanField( verbose_name=_('approved'), default=True ) account = models.ForeignKey( verbose_name=_('account'), to=Account, related_name='transactions' ) amount = models.DecimalField( verbose_name=_('amount'), max_digits=settings.MAX_DIGITS, decimal_places=settings.DECIMAL_PLACES, ) currency = CurrencyField( verbose_name=_('currency'), default=settings.BASE_CURRENCY, price_field=amount ) quantity = models.DecimalField( verbose_name=_('good quantity'), max_digits=settings.MAX_DIGITS, decimal_places=settings.DECIMAL_PLACES, null=True, blank=True, default=None ) unit = models.CharField( verbose_name=_('unit of measurement'), max_length=255, choices=UNITS, null=True, blank=True, default=None ) invoice = models.ForeignKey( to=Invoice, related_name='transactions', blank=True, null=True, default=None ) comment = models.TextField( verbose_name=_('comment'), blank=True ) @property def price(self): if self.quantity: return self.amount / self.quantity def __str__(self): return ('{amount} {currency} @ {account} on {date} ({app}approved)' .format(amount=self.amount, currency=self.currency, account=self.account, date=self.date, app='not ' if not self.approved else '')) @transaction.atomic def save(self, *args, **kwargs): if not self.id: sheaf = self.account.sheaves.filter(currency=self.currency).first() if not sheaf: Sheaf.objects.create(account=self.account, currency=self.currency, amount=self.amount).save() else: sheaf.amount += self.amount sheaf.save() super(Transaction, self).save(*args, **kwargs) else: old_account = Transaction.objects.get(pk=self.pk).account super(Transaction, self).save(*args, **kwargs) self.account.recalculate_summary(atomic=False) if self.account != old_account: old_account.recalculate_summary(atomic=False) class Meta: ordering = ['-date']
class Task(models.Model): GOERLI = 'goerli' MUMBAI = 'mumbai' BINANCE_TESTNET = 'bsc_testnet' BINANCE_MAINNET = 'bsc_mainnet' CHAIN_CHOICES = ( (GOERLI, 'Goerli (ETH)'), (MUMBAI, 'Mumbai (MATIC)'), (BINANCE_TESTNET, 'bsc_testnet (BNB)'), (BINANCE_MAINNET, 'bsc_mainnet (BNB)'), ) title = models.CharField("Title", max_length=100) description = models.TextField("Description", max_length=100) chain = models.CharField("Chain", max_length=15, choices=CHAIN_CHOICES, default=GOERLI) uuid = models.UUIDField('Web3 Task Identifier', unique=True, null=True) website_link = models.CharField("Website Link", max_length=200, validators=[validators.URLValidator]) website_image = models.ImageField("Website Image", upload_to="task_website_image", null=True) contract_address = models.CharField("Contract address", max_length=42) og_image_link = models.URLField("OpenGraph Image Path", max_length=200, blank=True, null=True) time_duration = models.DurationField("Time duration", default=datetime.timedelta(seconds=30)) created = models.DateTimeField("Created", auto_now_add=True) # No show modified = models.DateTimeField("Modified", auto_now=True) # No show # XXX: rename `is_active` to `is_enabled` is_active = models.BooleanField("Is active", default=True) # Read-only on API, manageable by admin is_active_web3 = models.BooleanField("Is active on Web3", default=True) # Is active on Web 3 initial_tx_hash = models.CharField("Initial transaction hash", max_length=66, blank=True, default='') user = models.ForeignKey(User, related_name='tasks', on_delete=models.DO_NOTHING) warning_message = models.CharField("Warning message", max_length=100, blank=True) currency = CurrencyField( choices=settings.CURRENCY_CHOICES, default=None, editable=False, max_length=5, null=True ) reward_per_click = fields.MoneyField( "Reward per click", max_digits=18, decimal_places=10, default_currency=None, currency_field_name='currency' ) remaining_balance = fields.MoneyField( "Remaining balance for task", max_digits=18, decimal_places=10, null=True, default=None, currency_field_name='currency' ) initial_budget = fields.MoneyField( "Initial task budget", max_digits=18, decimal_places=10, null=True, default=None, currency_field_name='currency' ) objects = managers.TaskManager() class Meta: verbose_name_plural = "Task" def __str__(self): # pragma: no cover if self.title: return f"Task({self.title})" return "Task" def save(self, *args, **kwargs): if not self.currency or self.currency == 'USD': self.currency = self.chain_instance.currency for field in ( 'reward_per_click', 'remaining_balance', 'initial_budget', ): if getattr(self, field, False): value = Money(getattr(self, field).amount, 'USD') setattr(self, field, convert_money(value, self.chain_instance.currency)) return super(Task, self).save(*args, **kwargs) @property def chain_instance(self) -> 'Web3Config': if self.chain in settings.WEB3_CONFIG: return settings.WEB3_CONFIG[self.chain] else: raise ImproperlyConfigured(f'{self.chain} not found in {list(settings.WEB3_CONFIG.keys())}') @property def reward_usd_per_click(self): return convert_money(self.reward_per_click, 'USD') @property def remaining_balance_usd(self): return convert_money(self.remaining_balance, 'USD')
class Account(Model): OPEN = 'OPEN' CLOSED = 'CLOSED' STATUS_CHOICES = ( (OPEN, _('Open')), (CLOSED, _('Closed')), ) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True) owner = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='billing_account', on_delete=PROTECT) currency = CurrencyField(db_index=True) status = FSMField(max_length=20, choices=STATUS_CHOICES, default=OPEN, db_index=True) # An account is marked as delinquent when it is registered then when the user # registers a valid credit card it will be marked as compliant delinquent = models.BooleanField(default=True, db_index=True) objects = AccountQuerySet.as_manager() def __str__(self): return str(self.owner) @transition(field=status, source=OPEN, target=CLOSED) def close(self): pass @transition(field=status, source=CLOSED, target=OPEN) def reopen(self): pass def balance(self, as_of: date = None): charges = Charge.objects.filter(account=self) transactions = Transaction.successful.filter(account=self) if as_of is not None: charges = charges.filter(created__lte=as_of) transactions = transactions.filter(created__lte=as_of) return total_amount(transactions) - total_amount(charges) def is_solvent( self, currency_threshold_price_map: Dict[str, Decimal], account_valid_cc_map: Dict[UUID, bool] = None, account_balance_map: DefaultDict[UUID, DefaultDict[str, Decimal]] = None): """ Given a map of currency thresholds determines if the account is solvent An account is solvent when: * Has a valid and active credit card to pay OR * Has enough balance to pay In currency_threshold_price_map param you have to specify the amount threshold for each currency. If the account has enough of one of the currencies then is solvent. Ex: currency_threshold_price_map { 'CHF': Decimal(10.83), 'EUR': Decimal(10.), 'NOK': Decimal(103.97) } Note: account_valid_cc_map and account_balance_map can be passed from outside in order to improve the efficiency when we require to know if several accounts are solvent """ if not account_valid_cc_map: from .actions.accounts import get_account_valid_credit_card_map account_valid_cc_map = get_account_valid_credit_card_map( Account.objects.filter(id=self.id)) if not account_balance_map: from .actions.accounts import get_account_balance_map accounts = Account.objects.filter(id=self.id) account_balance_map = get_account_balance_map(accounts) return (account_valid_cc_map[self.id] or self.has_enough_balance( account_balance_map, currency_threshold_price_map)) def has_enough_balance( self, account_balance_map: DefaultDict[UUID, DefaultDict[str, Decimal]], currency_threshold_price_map: Dict[str, Decimal]) -> bool: for currency, balance in account_balance_map[self.id].items(): if balance >= currency_threshold_price_map[currency]: return True return False def has_usable_credit_cards(self) -> bool: credit_cards = CreditCard.objects.filter( account=self, status=CreditCard.ACTIVE).valid() return credit_cards.exists()