예제 #1
0
class DepositTemplate(models.Model):

    CAPITALIZATION_YEARLY = 0
    CAPITALIZATION_DAILY = 1
    CAPITALIZATION_MONTHLY = 2
    CAPITALIZATION_QUARTERLY = 3
    CAPITALIZATION_CHOICES = [(CAPITALIZATION_YEARLY, 'Yearly'),
                              (CAPITALIZATION_DAILY, 'Daily'),
                              (CAPITALIZATION_MONTHLY, 'Monthly'),
                              (CAPITALIZATION_QUARTERLY, 'Quarterly')]

    CLOSING_ANYTIME = 0
    CLOSING_IN_END = 1
    CLOSING_CHOICES = [
        (CLOSING_ANYTIME, 'Anytime'),
        (CLOSING_IN_END, 'Only after end of term'),
    ]

    name = models.CharField(max_length=100)
    description = models.CharField(max_length=1000, null=True)
    currency = JSONField()
    capitalization = models.IntegerField(choices=CAPITALIZATION_CHOICES,
                                         default=CAPITALIZATION_MONTHLY)
    closing = models.IntegerField(choices=CLOSING_CHOICES,
                                  default=CLOSING_IN_END)
    min_amount = money_field()
    max_amount = money_field(null=True)
    prolongation = models.BooleanField(default=False)
    additional_contributions = models.BooleanField(default=False)
    detailed_info = models.TextField()

    class Meta:
        verbose_name = 'Deposit templates'
        verbose_name_plural = 'Deposit templates'
        permissions = (('view_deposittemplate', 'Can view deposit template'), )
예제 #2
0
class UserProfile(models.Model):

    user = models.OneToOneField(User, related_name='profile')
    identification_number = models.CharField(max_length=30)
    passport_number = models.CharField(max_length=20)
    passport_expires = models.DateField(null=False)
    address = models.CharField(max_length=300, null=True)
    phone = models.CharField(max_length=200, null=True)
    age = models.IntegerField(null=True, default=30)
    birth_date = models.DateField(null=True)
    family_status = models.TextField(null=True)
    dependants = models.IntegerField(default=3)
    income = money_field(default=1000)
    realty = models.TextField(null=True)
    job = models.TextField(null=True)
    number_of_times_90_more_days_late = models.IntegerField(default=0)
    number_of_times_30_59_days_late = models.IntegerField(default=0)
    number_of_times_60_89_days_late = models.IntegerField(default=0)

    def treat_days_late(self, days_late):
        if 30 <= days_late < 60:
            self.number_of_times_30_59_days_late += 1
        elif 60 <= days_late < 90:
            self.number_of_times_60_89_days_late += 1
        elif 90 <= days_late:
            self.number_of_times_90_more_days_late += 1
        self.save()

    class Meta:
        verbose_name = 'User profile'
        verbose_name_plural = 'User profiles'
예제 #3
0
class Credit(Product):

    STATUS_OPENED = 0
    STATUS_FINED = 1
    STATUS_PAID_OFF = 2
    STATUS_CLOSED = 3
    STATUS_REJECTED = 4
    STATUS_REQUESTED = 5
    STATUS_CHOICES = [
        (STATUS_OPENED, 'Opened'),
        (STATUS_FINED, 'Fined'),
        (STATUS_PAID_OFF, 'Paid off'),
        (STATUS_CLOSED, 'Closed'),
        (STATUS_REJECTED, 'Rejected'),
        (STATUS_REQUESTED, 'Requested'),
    ]

    INOPERABLE_STATUSES = [
        STATUS_CLOSED,
        STATUS_REJECTED,
        STATUS_REQUESTED,
    ]

    INTO_ACCOUNT = 0  # Your account or seller account
    IN_CASH = 1

    MONEY_DESTINATION = [
        (INTO_ACCOUNT, 'Into account'),
        (IN_CASH, 'In cash'),
    ]

    total_sum = money_field()
    residue = money_field()
    current_penalty = money_field()
    start_date = models.DateField()
    duration = models.IntegerField()  # in months
    next_payment_term = models.DateField(null=True)
    template = models.ForeignKey(CreditTemplate)
    status = models.IntegerField(choices=STATUS_CHOICES)
    annual_percentage_rate = percentage_field()
    fine_percentage = percentage_field()
    minimum_monthly_pay = money_field(null=True)
    current_month_pay = money_field(null=True)
    current_month_percents = money_field(
        null=True)  # money amount, should be payed, to avoid penalty
    method_of_ensuring = models.IntegerField(
        choices=CreditTemplate.ENSURING_CHOICES)
    money_destination = models.IntegerField(choices=MONEY_DESTINATION)
    target_account_id = models.IntegerField(
        default=-1)  # where money will be placed

    class Meta:
        verbose_name = 'Credit'
        verbose_name_plural = 'Credits'
        permissions = (('view_credit', 'Can view credit'),
                       ('make_payment_credit', 'Can make payment'),
                       ('open_online_credit', 'Can open online'),
                       ('leave_create_claim_credit', 'Can leave craate claim'),
                       ('confirm_create_claim_credit',
                        'Can confirm create claim'),
                       ('reject_create_claim_credit',
                        'Can reject create claim'), ('close_credit',
                                                     'Can close credit'))

    def close(self):
        """
        User can close credit without confirmation, if credit is paid_off
        Attempts to close the credit. Throws exceptions if the attempt fails
        """
        if self.status != Credit.STATUS_PAID_OFF:
            return False

        bank_confirmation = BankSystemProxy.credit_close(self.id)
        if not bank_confirmation:
            return False

        self.status = Credit.STATUS_CLOSED
        self.save()

        Transaction.objects.create(client=self.client,
                                   product=self,
                                   info='Кредит закрыт.',
                                   type=Transaction.TYPE_CREDIT_CLOSE)

        return True

    def current_percents(self):
        return Credit._get_current_percentage(self.residue.amount,
                                              self.annual_percentage_rate)

    def daily_update(self):
        """
        Updates the state of the model. Should be called daily
        """
        if self.status in Credit.INOPERABLE_STATUSES:
            return
        self._check_overdue()
        if self.status == Credit.STATUS_FINED:
            self._update_penalty()
            Transaction.objects.create(
                client=self.client,
                product=self,
                info='Начислен штраф. Текущий штраф: {}.'.format(
                    self.current_penalty),
                type=Transaction.TYPE_CREDIT_FINED)
        self.save()

    def pay(self, payment):
        """
        Processes the payment for the credit.
        Steps:
            0. assert credit != CLOSED
            1. If credit FINED, then
                a. Try to pay penalty
                b. If penalty payed, then change credit status to OPENED, then continue
            2. If credit OPENED or PAYED_OFF(client should explicitly close credit), then
                Every payment goes to pay for current credit percents and for decrease residue, so
                a. Pay for percents
                b. Decrease residue
                c. Inc current_month_pay
        We can multiple payments during one month.
        """
        if self.status in Credit.INOPERABLE_STATUSES:
            return False

        bank_confirmation = BankSystemProxy.credit_pay(self.id, float(payment))
        if not bank_confirmation:
            return False

        payment = Decimal(payment)
        self.total_sum.amount += payment
        if self.status == Credit.STATUS_FINED:
            if self.current_penalty.amount <= payment:
                days_late = (datetime.date.today() -
                             self.next_payment_term).days
                profile = self.client.profile
                profile.treat_days_late(days_late)
                payment, self.current_penalty.amount = payment - self.current_penalty.amount, 0
                self.status = Credit.STATUS_OPENED
            else:
                self.current_penalty.amount -= payment
                payment = 0
        self.current_month_pay.amount += payment  # inc current_month_pay
        cur_percents = self.current_month_percents.amount
        cur_percents, payment = max(0, cur_percents - payment), max(
            0, payment - cur_percents)
        self.current_month_percents = cur_percents
        self.residue.amount -= payment  # can be < 0
        if self.residue.amount <= 0:
            self.status = Credit.STATUS_PAID_OFF
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Внесен платеж по кредиту в размере: {}.'.format(payment),
            type=Transaction.TYPE_CREDIT_PAY)
        self.save()
        return True

    def _check_overdue(self):
        """
        Checks for overdue and updates accordingly. Should be called daily
        """
        cur_date = datetime.date.today()
        end_date = self.start_date + relativedelta(months=self.duration)
        if end_date <= cur_date:
            self.status = Credit.STATUS_FINED
        elif self.next_payment_term <= cur_date:
            if self.current_month_percents != 0:
                self.status = Credit.STATUS_FINED
            else:
                self.next_payment_term += relativedelta(months=1)
                self.current_month_percents = self.current_percents() + 0.1
                self.current_month_pay.amount = 0
        self.save()

    def _update_penalty(self):
        """
        Recalculates the penalty. Should be called daily
        """
        if self.status == Credit.STATUS_FINED:
            total = self.residue.amount + self.current_penalty.amount
            self.current_penalty = self.current_penalty.amount + \
                                   total * self.fine_percentage / Decimal(100.0)
        self.save()

    @classmethod
    def _credit_scoring(cls):
        return True

    @classmethod
    def create_online(cls,
                      client,
                      template,
                      money_amount,
                      duration,
                      account_id=None):
        bank_confirmation = BankSystemProxy.credit_create(
            client.id, template.id, money_amount, duration,
            float(template.annual_percentage_rate))
        if not bank_confirmation:
            return False, 'Отказано банком'

        success, scoring_result = client.get_scoring()
        if not success:
            return False, scoring_result
        elif scoring_result['level'] != 'success':
            return False, 'Ваша заявка отклонена кредитным скорингом. ' \
                          'У вас недостаточная платежеспособность'

        monthly_pay = cls._min_monthly_pay(money_amount,
                                           template.annual_percentage_rate,
                                           duration)
        if account_id is None:
            account = Account.create(True, client, 0)
            account_id = account.id

        credit = cls.objects.create(
            is_active=True,
            client=client,
            template=template,
            total_sum=0,
            residue=money_amount,
            current_penalty=0,
            start_date=datetime.date.today() + relativedelta(days=1),
            duration=duration,
            next_payment_term=datetime.date.today() + relativedelta(months=1),
            status=cls.STATUS_OPENED,
            annual_percentage_rate=template.annual_percentage_rate,
            fine_percentage=template.fine_percentage,
            minimum_monthly_pay=monthly_pay,
            current_month_pay=0,
            current_month_percents=0,
            method_of_ensuring=CreditTemplate.ENSURING_FINE,
            money_destination=cls.INTO_ACCOUNT,
            target_account_id=account_id)
        credit.save()

        target_account = Account.objects.get(pk=account_id)
        target_account.put_money(money_amount)
        target_account.save()

        info_text = 'Открыт онлайн кредит кредит.'
        Transaction.objects.create(client=client,
                                   product=credit,
                                   info=info_text,
                                   type=Transaction.TYPE_CREDIT_OPENED)
        return True, credit

    @classmethod
    def create_claim(cls,
                     client,
                     template,
                     money_amount,
                     duration,
                     ensuring_method,
                     money_destination=INTO_ACCOUNT,
                     account_id=None):
        """
        if Template.issue_online, then credit will be created as active
        Creates a credit with default parameters.
        Client gets 1 month, as buffered - no payment, then begin loan payments.
        """
        monthly_pay = cls._min_monthly_pay(money_amount,
                                           template.annual_percentage_rate,
                                           duration)
        # create account, if need
        if account_id is None and money_destination == cls.INTO_ACCOUNT:
            account = Account.create(False, client, 0)
            account_id = account.id
        if account_id is None:
            account_id = -1
        credit = cls.objects.create(
            is_active=False,
            client=client,
            template=template,
            total_sum=0,
            residue=money_amount,
            current_penalty=0,
            start_date=datetime.date.today() + relativedelta(days=1),
            duration=duration,
            next_payment_term=datetime.date.today() + relativedelta(months=1),
            status=cls.STATUS_REQUESTED,
            annual_percentage_rate=template.annual_percentage_rate,
            fine_percentage=template.fine_percentage,
            minimum_monthly_pay=monthly_pay,
            current_month_pay=0,
            current_month_percents=0,
            method_of_ensuring=ensuring_method,
            money_destination=money_destination,
            target_account_id=account_id)

        info_text = 'Подана заявка на кредит.'
        Transaction.objects.create(client=client,
                                   product=credit,
                                   info=info_text,
                                   type=Transaction.TYPE_CREDIT_REQUEST)
        return credit

    def confirm(self):
        if self.status != self.STATUS_REQUESTED:
            return False
        bank_confirmation = BankSystemProxy.credit_create(
            self.client.id, self.template.id, float(self.residue.amount),
            self.duration, float(self.annual_percentage_rate))
        if not bank_confirmation:
            self.reject('Отказано банком')
            return False

        if self.money_destination == Credit.INTO_ACCOUNT:
            account = Account.objects.get(pk=self.target_account_id)
            account.confirm()
            account.put_money(self.residue.amount)
            account.save()

        Transaction.objects.create(client=self.client,
                                   product=self,
                                   info='Заявка на кредит одобрена.',
                                   type=Transaction.TYPE_CREDIT_CONFIRM)

        Contract.objects.create(client=self.client, product=self)
        self.is_active = True
        self.status = Credit.STATUS_OPENED
        self.save()
        return True

    def reject(self, cause):
        if self.status != self.STATUS_REQUESTED:
            return False
        self.status = Credit.STATUS_REJECTED
        self.save()
        account = Account.objects.get(pk=self.target_account_id)
        if not account.is_active:
            account.reject(cause)
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Заявка на кредит отклонена. Причина: {}'.format(cause),
            type=Transaction.TYPE_CREDIT_REJECT)
        return True

    @staticmethod
    def _get_current_percentage(residue, percentage):
        """
        Percents for current residue from last month
        """
        # TODO: 30 days in a month and 365 in a year? #Yes!
        r = Decimal(residue)
        p = Decimal(percentage)
        return r * (p / Decimal(100.0)) * Decimal(30) / Decimal(365)

    @staticmethod
    def _min_monthly_pay(payment, percentage, duration):
        """
        This work in that way - func return value, allow you to payed credit till credit term,
        if you are not fall into the penalty
        The less each payment - the greater the overpayment.
        (yes, it can be sum of geom progression)
        """
        coef = Decimal(1.0) + percentage / Decimal(100.0) / Decimal(12.0)
        mult = Decimal(1.0) / coef
        for _ in range(duration):
            mult = (mult + Decimal(1.0)) / coef
        return payment / mult
예제 #4
0
class Deposit(Product):

    STATUS_ACTIVE = 0
    STATUS_CLOSED = 1
    STATUS_REJECTED = 2
    STATUS_REQUESTED_CREATING = 3
    STATUS_REQUESTED_CLOSING = 4
    STATUS_CHOICES = [
        (STATUS_ACTIVE, 'Active'),
        (STATUS_CLOSED, 'Closed'),
        (STATUS_REJECTED, 'Rejected'),
        (STATUS_REQUESTED_CREATING, 'Requested creating'),
        (STATUS_REQUESTED_CLOSING, 'Requested closing'),
    ]

    INOPERABLE_STATUSES = [
        STATUS_CLOSED,
        STATUS_REJECTED,
        STATUS_REQUESTED_CREATING,
    ]

    CAPITALIZATION_TIME_PERIOD = [
        relativedelta(years=1),
        relativedelta(days=1),
        relativedelta(months=1),
        relativedelta(months=3),
    ]

    CAPITALIZATION_COEFFICIENT = [
        Decimal(1.0),
        Decimal(1.0 / 365.0),
        Decimal(1.0 / 12.0),
        Decimal(3.0 / 12.0),
    ]

    amount = money_field()
    template = models.ForeignKey(DepositTemplate)
    status = models.IntegerField(choices=STATUS_CHOICES)
    start_date = models.DateField()
    percentage = percentage_field()
    capitalization = models.IntegerField(
        choices=DepositTemplate.CAPITALIZATION_CHOICES)
    closing = models.IntegerField(choices=DepositTemplate.CLOSING_CHOICES)
    duration = models.IntegerField(default=12)
    prolongation = models.BooleanField(default=False)
    additional_contributions = models.BooleanField(default=False)
    next_capitalize_term = models.DateField(null=True)
    currency = models.CharField(default='BYN', max_length=20)  # DUMMY
    source_account_id = models.IntegerField(default=-1)  # money source
    target_account_id = models.IntegerField(default=-1)  # money destination
    close_claim = models.BooleanField(default=False)

    class Meta:
        verbose_name = 'Deposit'
        verbose_name_plural = 'Deposits'
        permissions = (('view_deposit',
                        'Can view deposit'), ('leave_create_claim_deposit',
                                              'Can leave create claim'),
                       ('confirm_create_claim_deposit',
                        'Can confirm create claim'),
                       ('reject_create_claim_deposit',
                        'Can reject create claim'),
                       ('leave_close_claim_deposit', 'Can leave close claim'),
                       ('confirm_close_claim_deposit',
                        'Can confirm close claim'),
                       ('reject_close_claim_deposit',
                        'Can reject close claim'),
                       ('put_money_deposit', 'Can put money on deposit'))

    @classmethod
    def create(cls, client, template, money_amount, duration, percentage,
               currency, account_id):
        """
        Deposit claim, them admin should allow it
        Currencies: BYN, USD, EUR
        """
        need_byn = FinanceSettings.get_instance(
        ).exchange_rates[currency] * money_amount
        account = Account.objects.get(pk=account_id)
        if account.residue.amount >= need_byn:
            deposit = cls.objects.create(
                is_active=False,
                client=client,
                template=template,
                amount=money_amount,
                start_date=datetime.date.today() + relativedelta(days=1),
                status=cls.STATUS_REQUESTED_CREATING,
                percentage=percentage,
                currency=currency,
                capitalization=template.capitalization,
                closing=template.closing,
                prolongation=template.prolongation,
                additional_contributions=template.additional_contributions,
                next_capitalize_term=datetime.date.today() +
                cls.CAPITALIZATION_TIME_PERIOD[template.capitalization],
                duration=duration,
                source_account_id=account_id)
            deposit.save()
            account.status = Account.STATUS_DISABLED
            account.save()
            Transaction.objects.create(
                client=client,
                product=deposit,
                info='Подана заявка на депозит.',
                type=Transaction.TYPE_DEPOSIT_REQUEST_CREATE)
            return deposit
        return None

    def confirm(self):
        """
        Confirm deposit, defreeze account and transfer money
        """
        if self.status != Deposit.STATUS_REQUESTED_CREATING:
            return False

        bank_confirmation = BankSystemProxy.deposit_creation(
            self.client.id, self.template.id, self.duration,
            float(self.percentage), float(self.amount.amount), self.currency)
        if not bank_confirmation:
            self.reject('Отказано банком.')
            return False

        self.is_active = True
        self.status = Deposit.STATUS_ACTIVE
        account = Account.objects.get(pk=self.source_account_id)
        money_in_byn = FinanceSettings.get_instance().exchange_rates[self.currency] * \
                       float(self.amount.amount)
        account.status = Account.STATUS_ACTIVE
        account.get_money(money_in_byn)
        account.save()
        self.save()
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Депозит открыт.',
            type=Transaction.TYPE_DEPOSIT_CONFIRM_CREATE)
        Contract.objects.create(client=self.client, product=self)
        return True

    def reject(self, cause):
        if self.status != self.STATUS_REQUESTED_CREATING:
            return False
        self.status = Deposit.STATUS_REJECTED
        account = Account.objects.get(pk=self.source_account_id)
        account.status = Account.STATUS_ACTIVE
        account.save()
        self.save()
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Заявка на депозит отклонена. Причина: {}'.format(cause),
            type=Transaction.TYPE_DEPOSIT_REJECT_CREATE)
        return True

    def leave_close_claim(self, target_account_id):
        """
        Close deposit and transfer money to account
        If deposit.closing = CLOSING_ANYTIME  - just transfer current amount to account_id
        if deposit.closing = CLOSING_IN_END then
        deposit can be closed in period +-1 week about and date
        """
        if self.status in Deposit.INOPERABLE_STATUSES:
            return False, 'Операции с кредитом невозможны.'
        if self.closing == DepositTemplate.CLOSING_IN_END:
            cur_date = datetime.date.today()
            end_date = self.start_date + relativedelta(months=self.duration)
            if end_date + relativedelta(
                    days=-7) < cur_date < end_date + relativedelta(days=7):
                self.close_claim = True
                self.target_account_id = target_account_id
                self.status = Deposit.STATUS_REQUESTED_CLOSING
                self.save()
                return True, 'Ваша заявка будет рассмотрена'
            return False, 'Вы можете забрать депозит только начиная с {}'.\
                format(str(end_date + relativedelta(days=-7)))
        else:
            self.close_claim = True
            self.status = Deposit.STATUS_REQUESTED_CLOSING
            self.target_account_id = target_account_id
            self.save()
            return True, 'Вы можете забрать депозит в данный момент. Ваша заявка будет рассмотрена'

    def close_confirm(self):
        if self.status != self.STATUS_REQUESTED_CLOSING:
            return False
        bank_confirmation = BankSystemProxy.deposit_closing(self.id)
        if not bank_confirmation:
            self.reject('Отказано банком.')
            return False
        cur_date = datetime.date.today()
        end_date = self.start_date + relativedelta(months=self.duration)
        if self.next_capitalize_term < min(
                end_date, cur_date):  # last capitalize, if need
            self._update_amount()
        money_in_byn = FinanceSettings.get_instance().exchange_rates[
            self.currency] * float(self.amount.amount)
        account = Account.objects.get(pk=self.target_account_id)
        account.put_money(money_in_byn)
        self.status = self.STATUS_CLOSED
        self.save()
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Депозит закрыт. Деньги в размере {} BYN переведены на счет {}'
            .format(money_in_byn, self.target_account_id),
            type=Transaction.TYPE_DEPOSIT_CONFIRM_CLOSE)
        return True

    def close_reject(self, cause):
        if self.status != Deposit.STATUS_REQUESTED_CLOSING:
            return False
        self.close_claim = False
        self.status = self.STATUS_ACTIVE
        self.save()
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Отказано в закрытие депозита. Причина: {}.'.format(cause),
            type=Transaction.TYPE_DEPOSIT_REJECT_CLOSE)
        return True

    def additional_contribution(self, money_amount):
        if self.status in Credit.INOPERABLE_STATUSES:
            return False
        if not self.additional_contributions:
            return False
        money_in_cur = float(money_amount) / \
                       FinanceSettings.get_instance().exchange_rates[self.currency]
        self.amount.amount += Decimal(money_in_cur)
        self.save()
        Transaction.objects.create(client=self.client,
                                   product=self,
                                   info='Начислены деньги на депозит.',
                                   type=Transaction.TYPE_DEPOSIT_ADD_MONEY)
        return True

    def daily_update(self):
        """
        Recalculate(capitalize) money amount and set new capitalize term
        If deposit duration has ended --> close or prolongate deposit
        Give 1 week buffer for client before longation
        """
        cur_date = datetime.date.today()
        if self.start_date + relativedelta(
                months=self.duration) + relativedelta(days=7) < cur_date:
            if self.prolongation:
                self.start_date += relativedelta(months=self.duration)
                self.next_capitalize_term += Deposit.CAPITALIZATION_TIME_PERIOD[
                    self.capitalization]
                Transaction.objects.create(
                    client=self.client,
                    product=self,
                    info='Совершено пролонгирование депозита.',
                    type=Transaction.TYPE_DEPOSIT_PROLONGATION)
            else:
                self.status = Deposit.STATUS_CLOSED
        elif self.next_capitalize_term <= cur_date:
            self._update_amount()
            Transaction.objects.create(
                client=self.client,
                product=self,
                info='Начисление процентов. Новая сумма депозита: {}.'.format(
                    self.amount),
                type=Transaction.TYPE_DEPOSIT_CAPITALIZE)
        self.save()

    def _update_amount(self):
        self.next_capitalize_term += Deposit.CAPITALIZATION_TIME_PERIOD[
            self.capitalization]
        coef = Deposit.CAPITALIZATION_COEFFICIENT[self.capitalization]
        self.amount.amount += self.amount.amount * (self.percentage /
                                                    Decimal(100.0)) * coef
        self.save()
예제 #5
0
class Account(Product):

    STATUS_ACTIVE = 0
    STATUS_CLOSED = 1
    STATUS_DISABLED = 2
    STATUS_REJECTED = 3
    STATUS_REQUESTED_CREATING = 4
    STATUS_CHOICES = [
        (STATUS_ACTIVE, 'Active'),
        (STATUS_CLOSED, 'Closed'),
        (STATUS_DISABLED, 'Disabled'),
        (STATUS_REJECTED, 'Rejected'),
        (STATUS_REQUESTED_CREATING, 'Create claim leaved'),
    ]

    INOPERABLE_STATUSES = [
        STATUS_DISABLED,
        STATUS_REJECTED,
        STATUS_CLOSED,
        STATUS_REQUESTED_CREATING,
    ]

    number = models.CharField(max_length=30, default='0' * 13)
    residue = money_field()
    status = models.IntegerField(choices=STATUS_CHOICES)
    close_claim = models.BooleanField(default=False)
    target_account_id = models.IntegerField(default=-1)

    class Meta:
        verbose_name = 'Checking account'
        verbose_name_plural = 'Checking accounts'
        permissions = (('view_account',
                        'Can view account'), ('leave_create_claim_account',
                                              'Can leave create claim'),
                       ('confirm_create_claim_account',
                        'Can confirm create claim'),
                       ('reject_create_claim_account',
                        'Can reject create claim'), ('unassign_account',
                                                     'Can unassign account'),
                       ('assign_account', 'Can assign account'))

    @classmethod
    def get_last_digit(cls, twelve_digits):
        weight_coefs = [713, 371, 371, 371, 371, 3]
        number = '111' + twelve_digits + '0'
        sum = 0
        for i in range(0, len(number), 3):
            piece = int(number[i:min(i + 3, len(number))])
            sum += (piece * weight_coefs[i / 3]) % 10
        return str((sum * 3) % 10)

    @classmethod
    def _create_new_number(cls):
        new_number = '3014' + str(random.randrange(10000000, 99999999))
        new_number += cls.get_last_digit(new_number)
        while cls.objects.filter(number=new_number):
            new_number = '3014' + str(random.randrange(10000000, 99999999))
            new_number += cls.get_last_digit(new_number)
        return new_number

    @classmethod
    def create(cls, is_active, client, first_contribution):
        """
        account created already with money - dummy
        Also, if credit opened, then money can be puted into created account
        """
        status = Account.STATUS_ACTIVE if is_active else Account.STATUS_REQUESTED_CREATING
        account = Account.objects.create(is_active=is_active,
                                         number=cls._create_new_number(),
                                         residue=first_contribution,
                                         status=status,
                                         client=client)
        account.save()
        info_text = 'Создан счет' if is_active else 'Подана заявка на создание счета'
        t_type = Transaction.TYPE_ACCOUNT_CREATED if is_active else Transaction.TYPE_ACCOUNT_REQUEST_CREATE
        Transaction.objects.create(client=client,
                                   product=account,
                                   info=info_text,
                                   type=t_type)
        return account

    @classmethod
    def assign(cls, client, account_number):
        """
        assign account by account_number to client
        """
        if Account.objects.filter(number=account_number):
            account = Account.objects.filter(number=account_number).first()
            if account.status == Account.STATUS_CLOSED:
                account.status = Account.STATUS_ACTIVE
                account.save()
            else:
                return False, 'Этот счет уже используется.'
        else:
            res, money_amount = BankSystemProxy.account_assign_to_user(
                client, account_number)
            if not res:
                return False, 'Отказано банком. Проверте счет.'

            account = Account.objects.create(number=account_number,
                                             is_active=True,
                                             residue=money_amount,
                                             status=Account.STATUS_ACTIVE,
                                             client=client)
            account.save()

        info_text = 'Счет успешно привязан.'
        Transaction.objects.create(client=client,
                                   product=account,
                                   info=info_text,
                                   type=Transaction.TYPE_ACCOUNT_ASSIGNED)
        return True, info_text

    def confirm(self):
        """
        Confirm account
        """
        if self.status != self.STATUS_REQUESTED_CREATING:
            return False

        bank_confirmation = BankSystemProxy.account_creation(
            self.number, self.client.id, float(self.residue.amount))
        if not bank_confirmation:
            self.reject('Отклонено банком')
            return False

        self.is_active = True
        self.status = Account.STATUS_ACTIVE
        self.save()
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Счет подтвержден.',
            type=Transaction.TYPE_ACCOUNT_CONFIRM_CREATE)
        Contract.objects.create(client=self.client, product=self)
        return True

    def reject(self, cause):
        if self.status != self.STATUS_REQUESTED_CREATING:
            return False
        self.status = Account.STATUS_REJECTED
        self.save()
        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Заявка на создание счета отклонена. Причина: {}'.format(
                cause),
            type=Transaction.TYPE_ACCOUNT_REJECT_CREATE)
        return True

    def put_money(self, contribution):
        """
        In BYN only
        """
        if self.status in Account.INOPERABLE_STATUSES:
            return False

        bank_confirmation = BankSystemProxy.account_put_money(
            self.id, float(contribution))
        if not bank_confirmation:
            return False

        Transaction.objects.create(
            client=self.client,
            product=self,
            info='Зачисление в размере {} BYN'.format(contribution),
            type=Transaction.TYPE_ACCOUNT_PUT_MONEY)
        self.residue.amount += Decimal(contribution)
        self.save()
        return True

    def get_money(self, required_quantity):
        """
        In BYN only
        """
        if self.status in Account.INOPERABLE_STATUSES:
            return False

        if self.residue.amount >= Decimal(required_quantity):
            self.residue.amount -= Decimal(required_quantity)
            self.save()

            bank_confirmation = BankSystemProxy.account_get_money(
                self.id, required_quantity)
            if not bank_confirmation:
                return False

            Transaction.objects.create(
                client=self.client,
                product=self,
                info='Снятие в размере {} BYN'.format(required_quantity),
                type=Transaction.TYPE_ACCOUNT_PUT_MONEY)
            return True
        return False

    def unassign(self):
        """
        :param target_account_id:
        account_id, where money will be sent
        """
        if self.status in Account.INOPERABLE_STATUSES:
            return False
        self.close_claim = True
        self.status = Account.STATUS_CLOSED
        self.save()
        return True