class TicketMessage(ModifiedMixin, PaginationMixin, DeclarativeBase): __tablename__ = 'ticket_message' id = Field(Integer(), primary_key=True) ticket_id = Field(Integer(), ForeignKey(Ticket.id)) member_id = Field(Integer(), ForeignKey('member.id')) text = Field(Unicode()) is_answer = Field(Boolean(), default=False) _attachment = Field(TicketAttachment.as_mutable(JSON), nullable=True, protected=True) ticket = relationship(Ticket, lazy='select', uselist=False, protected=True) @property def attachment(self): return self._attachment.locate() if self._attachment else None @attachment.setter def attachment(self, value): if value is not None: self._attachment = TicketAttachment.create_from(value) else: self._attachment = None def to_dict(self): result = super().to_dict() result['attachment'] = self.attachment return result
class City(FilteringMixin, DeclarativeBase): __tablename__ = 'city' id = Field(Integer(), primary_key=True) state_id = Field(Integer(), ForeignKey('state.id')) name = Field(Unicode(50)) state = relationship('State', uselist=False)
class State(FilteringMixin, DeclarativeBase): __tablename__ = 'state' id = Field(Integer(), primary_key=True) country_id = Field(Integer(), ForeignKey('country.id')) name = Field(Unicode(50)) country = relationship('Country', uselist=False)
class PaymentGateway(DeclarativeBase): __tablename__ = 'payment_gateway' name = Field(Unicode(30), primary_key=True) fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol')) # # TODO: Will be deprecated and replaced by tiers cashin_min = Field(DECIMAL(18, 8), default=Decimal('0.00001000')) cashin_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) cashin_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) cashin_commission_rate = Field(Unicode(10), default="0.000") cashin_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) # # TODO: Will be deprecated and replaced by tiers cashout_min = Field(DECIMAL(18, 8), default=Decimal('0.00010000')) cashout_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) cashout_static_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) cashout_commission_rate = Field(Unicode(10), default="0.005") cashout_max_commission = Field(DECIMAL(18, 8), default=Decimal('0.00000000')) fiat = relationship('Fiat') def to_dict(self): result = super().to_dict() # TODO: Get the current user's fiat_tier_policy about this currency # result['tirePolicy'] = {} result['cashoutMin'] = self.fiat.normalized_to_output(self.cashout_min) result['cashoutMax'] = self.fiat.normalized_to_output(self.cashout_max) result['cashoutStaticCommission'] = self.fiat.normalized_to_output(self.cashout_static_commission) result['cashoutMaxCommission'] = self.fiat.normalized_to_output(self.cashout_max_commission) result['cashinMin'] = self.fiat.normalized_to_output(self.cashin_min) result['cashinMax'] = self.fiat.normalized_to_output(self.cashin_max) result['cashinStaticCommission'] = self.fiat.normalized_to_output(self.cashin_static_commission) result['cashinMaxCommission'] = self.fiat.normalized_to_output(self.cashin_max_commission) return result def calculate_cashout_commission(self, amount: Decimal) -> Decimal: commission = self.cashout_static_commission if self.cashout_commission_rate != Decimal(0): commission += amount * Decimal(self.cashout_commission_rate) return min( commission, self.cashout_max_commission ) if self.cashout_max_commission != Decimal(0) else commission def calculate_cashin_commission(self, amount: Decimal) -> Decimal: commission = self.cashin_static_commission if self.cashin_commission_rate != Decimal(0): commission += amount * Decimal(self.cashin_commission_rate) return min( commission, self.cashin_max_commission ) if self.cashin_max_commission != Decimal(0) else commission
class BankingTransaction(ModifiedMixin, OrderingMixin, FilteringMixin, PaginationMixin, DeclarativeBase): __tablename__ = 'banking_transaction' id = Field(Integer(), primary_key=True) fiat_symbol = Field(Unicode(10), ForeignKey('fiat.symbol')) member_id = Field(Integer(), ForeignKey('member.id')) # FIXME: Change the name to `member_id` payment_gateway_name = Field(Unicode(30), ForeignKey('payment_gateway.name')) amount = Field(DECIMAL(18, 8)) # Value without commission commission = Field(DECIMAL(18, 8), default=Decimal(0)) error = Field(Unicode(), nullable=True) reference_id = Field(Unicode(260), nullable=True) banking_id_id = Field(Integer(), ForeignKey('banking_id.id'), protected=True) payment_gateway = relationship('PaymentGateway') banking_id = relationship('BankingId') type = Field(Unicode(50)) member = relationship('Member') __mapper_args__ = { 'polymorphic_identity': __tablename__, 'polymorphic_on': type }
class BankingId(TimestampMixin, DeclarativeBase): __tablename__ = 'banking_id' id = Field(Integer(), primary_key=True) client_id = Field(Integer(), ForeignKey('client.id')) is_verified = Field(Boolean(), default=False) error = Field(Unicode(), nullable=True) client = relationship('Client', lazy='select', protected=True) type = Field(Enum('bank_account', 'bank_card', name='banking_id_type')) __mapper_args__ = { 'polymorphic_identity': __tablename__, 'polymorphic_on': type }
class Ticket(ModifiedMixin, PaginationMixin, DeclarativeBase): __tablename__ = 'ticket' id = Field(Integer(), primary_key=True) title = Field(Unicode()) member_id = Field(Integer(), ForeignKey('member.id')) department_id = Field(Integer(), ForeignKey('ticket_department.id')) closed_at = Field(DateTime(), nullable=True) department = relationship('TicketDepartment', uselist=False) @hybrid_property def is_closed(self): return self.closed_at is not None @is_closed.setter def is_closed(self, value): self.closed_at = datetime.now() if value else None @is_closed.expression def is_closed(self): # noinspection PyUnresolvedReferences return self.closed_at.isnot(None) @classmethod def import_value(cls, column, v): # noinspection PyUnresolvedReferences if column.key == cls.is_closed.key and not isinstance(v, bool): return str(v).lower() == 'true' # noinspection PyUnresolvedReferences return super().import_value(column, v) def to_dict(self): result = super().to_dict() first_message = DBSession.query(TicketMessage) \ .filter(TicketMessage.ticket_id == self.id) \ .order_by(TicketMessage.created_at) \ .first() result['firstMessage'] = first_message.to_dict() if (first_message is not None) else None return result
class Market(OrderingMixin, FilteringMixin, DeclarativeBase): """ Currency pairs are sometimes then written by concatenating the ISO currency codes (ISO 4217) of the base currency and the counter currency, separating them with a slash character. Often the slash character is omitted, alternatively the slash may be replaced by and etc. A widely traded currency pair is the relation of the euro against the US dollar, designated as EUR/USD. The quotation EUR/USD 1.2500 means that one euro is exchanged for 1.2500 US dollars. Here, EUR is the base currency and USD is the quote currency(counter currency). This means that 1 Euro can be exchangeable to 1.25 US Dollars. Reference: https://en.wikipedia.org/wiki/Currency_pair """ __tablename__ = 'market' name = Field(Unicode(20), pattern=r'^[A-Z0-9]{1,10}_[A-Z0-9]{1,10}$', primary_key=True) # e.g. btc_usd base_currency_symbol = Field(Unicode(), ForeignKey('currency.symbol'), protected=True) quote_currency_symbol = Field(Unicode(), ForeignKey('currency.symbol'), protected=True) base_currency = relationship('Currency', foreign_keys=[base_currency_symbol]) quote_currency = relationship('Currency', foreign_keys=[quote_currency_symbol]) buy_amount_min = Field(DECIMAL(18, 8), default=Decimal('0.00000100')) buy_amount_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) sell_amount_min = Field(DECIMAL(18, 8), default=Decimal('0.00000100')) sell_amount_max = Field(DECIMAL(18, 8), default=Decimal('100.00000000')) taker_commission_rate = Field(Unicode(10), default='0.4') maker_commission_rate = Field(Unicode(10), default='0.1') # taker_static_commission = Field(BigInteger(), default=0) # taker_permille_commission = Field(Integer(), default=0) # taker_max_commission = Field(BigInteger(), default=0) # maker_static_commission = Field(BigInteger(), default=0) # maker_permille_commission = Field(Integer(), default=0) # maker_max_commission = Field(BigInteger(), default=0) def to_dict(self): result = super().to_dict() # TODO: Get the current user's wallet_tier_policy about this currency # result['tirePolicy'] = {} result['buyAmountMin'] = self.base_currency.normalized_to_output( self.buy_amount_min) result['buyAmountMax'] = self.base_currency.normalized_to_output( self.buy_amount_max) result['sellAmountMin'] = self.base_currency.normalized_to_output( self.sell_amount_min) result['sellAmountMax'] = self.base_currency.normalized_to_output( self.sell_amount_max) return result def get_last_price(self): try: return Decimal(stexchange_client.market_last(self.name)) except StexchangeException as e: raise stexchange_http_exception_handler(e) def validate_ranges(self, type_, total_amount, price=None): # TODO: Review and rewrite the price threshold validator # threshold = Decimal(settings.trader.price_threshold_permille) # price_rate = Decimal(1000 * (price or self.get_last_price()) / self.get_last_price()) - Decimal(1000) if type_ == 'buy': # if price_rate > threshold: # raise HttpBadRequest('Price not in valid range', 'price-not-in-range') if total_amount < self.buy_amount_min or \ (self.buy_amount_max != 0 and total_amount > self.buy_amount_max): raise HttpBadRequest('Amount not in range', 'amount-not-in-range') elif type_ == 'sell': # if price_rate < -threshold: # raise HttpBadRequest('Price not in valid range', 'price-not-in-range') if total_amount < self.sell_amount_min or \ (self.sell_amount_max != 0 and total_amount > self.sell_amount_max): raise HttpBadRequest('Amount not in range', 'amount-not-in-range')