Exemplo n.º 1
0
def result():
    if g.user:
        report = Report.get_by_user_id(g.user.id)
        if not report or report.is_deprecated:
            wxplan = session.get('wxplan')
            if not wxplan:
                return redirect(url_for('.info'))
            plan = PlanData.from_dict(wxplan)
            plan = plan.assign_to_user(g.user.id)
            formula = Formula(plan=plan)
            report = formula.gen_report()
        else:
            plan = PlanData.get_by_user_id(g.user.id)
            if not plan:
                return redirect(url_for('.info'))
        weekly_annual_rates = []
        latest_rate = 0
        if report.pocket_money > 0:
            dashboard = PublicDashboard.today()
            weekly_annual_rates = [(unicode(r.date),
                                    round_half_up(r.annual_rate, 2))
                                   for r in dashboard.weekly_annual_rates]
            latest_rate = round_half_up(
                dashboard.latest_annual_rate.annual_rate, 2)
        return render_template(
            'plan/detail_result.html',
            report=report,
            plan=plan,
            weekly_annual_rates=weekly_annual_rates,
            latest_rate=latest_rate,
            monthly_mortgages=report.get_monthly_mortgages(),
            rent_data=report.get_rent_data())

    return redirect(url_for('.brief'))
Exemplo n.º 2
0
def xm_send_exit_sms(asset_id):
    """新米转出订单发送到期短信"""
    from core.models.sms import ShortMessage
    from core.models.sms.kind import savings_order_exited_sms
    from core.models.hoard.xinmi import XMAsset

    asset = XMAsset.get(asset_id)
    sms = ShortMessage.create(
        asset.user.mobile, savings_order_exited_sms, asset.user.id_,
        order_amount=str(round_half_up(asset.create_amount, 2)),
        profit=str(round_half_up(asset.current_interest, 2)))
    sms.send_async()
Exemplo n.º 3
0
def init():
    """Synchronize the annual rates."""
    try:
        rates = WalletAnnualRate.synchronize(zslib.client, provider.fund_code)
    except BusinessError as e:
        bcolors.fail('%s : %s : %s' % e.args)
        return

    for rate in rates:
        bcolors.success('{0}\t{1}\t{2}\t¥{3}'.format(
            rate.date,
            rate.fund_code,
            round_half_up(rate.annual_rate, 2),
            round_half_up(rate.ten_thousand_pieces_income, 2),
        ))
Exemplo n.º 4
0
 def get_display(cls, rebates):
     _rebates = dict()
     for rebate in sorted(rebates, key=attrgetter('type')):
         name = REBATE_TYPE_NAME_MAP.get(rebate.type)
         name = u'礼券福利' if u'礼券福利' in name else name
         _rebates[name] = '%s 元' % round_half_up(rebate.rebate_amount, 2)
     return _rebates
Exemplo n.º 5
0
def xm_contract(order_id):
    """新米购买合同.

    reqheader Authorization: OAuth 2.0 Bearer Token
    :status 200: 返回 :class:`.XinmiContractSchema`
    :status 403: 获取合同失败
    :status 404: 无相应产品
    """
    contract_schema = XinmiContractSchema(strict=True)
    order = obtain_xm_order(order_id)
    asset = XMAsset.get_by_order_code(order.order_code)
    if not asset or not g.xm_account:
        abort(401)
    identity = Identity.get(asset.user_id)
    upper_amount = num2chn(asset.create_amount)
    product = XMFixedDuedayProduct.get(asset.product_id)
    if not product:
        abort(404)
    expect_rate = 100
    if product.product_type is XMFixedDuedayProduct.Type.classic:
        expect_rate = round_half_up(
            (asset.actual_annual_rate * 90 / 365 + 1) * 100, 4)
    if not asset:
        abort(403, u'资产合同正在准备中')

    contract = render_template('savings/agreement_xinmi.html',
                               asset=asset,
                               identity=identity,
                               expect_rate=expect_rate,
                               product_name=product.name,
                               product_frozen_days=product.frozen_days,
                               upper_amount=upper_amount)
    data = {'contract': contract}

    return jsonify(success=True, data=contract_schema.dump(data).data)
Exemplo n.º 6
0
def transactions():
    start = request.args.get('start', type=int, default=0)
    limit = request.args.get('limit', type=int, default=5)

    ids = WalletTransaction.get_ids_by_account(g.wallet_account.id_)
    transactions = WalletTransaction.get_multi(ids[start:start + limit])

    data = {
        'collection': [{
            'type': ('deposit' if t.type_ is WalletTransaction.Type.purchase
                     else 'withdraw'),
            'bankcard': {
                'bank': {
                    'name': t.bankcard.bank.name
                },
                'display_card_number': t.bankcard.display_card_number
            },
            'amount':
            unicode(round_half_up(t.amount, 2)),
            'creation_date':
            unicode(t.creation_time.date()),
            'value_date':
            unicode(t.value_date) if t.value_date else None
        } for t in transactions
                       if t.status is WalletTransaction.Status.success],
        'start':
        start,
        'total':
        len(ids),
    }
    return jsonify(r=True, data=data)
Exemplo n.º 7
0
def purchase(json_data, g):
    """选购产品, 创建理财单."""

    purchase_schema = PurchaseSchema(strict=True)
    response_schema = PurchaseResponseSchema()
    result = purchase_schema.load(json_data)

    product_id = result.data['product_id']
    bankcard = obtain_bankcard(result.data['bankcard_id'], g)
    g.bankcard_manager.set_default(bankcard)
    amount = result.data.get('amount', 0) or 0
    pocket_deduction_amount = result.data.get('pocket_deduction_amount',
                                              0) or 0
    coupon = obtain_coupon(result.data.get('coupon_id'), g.user)
    p = Product.get(product_id)
    if not p:
        raise OffShelfError()
    if amount < p.min_amount:
        raise OrderAmountTooSmallError(u'申购金额能少于{}元'.format(
            round_half_up(p.min_amount, 2)))

    order = sxb.subscribe_product(g.user, product_id, bankcard, amount, coupon,
                                  pocket_deduction_amount)

    inject_bankcard_amount_limit(Partner.xm, [order.bankcard])

    order._display_status = order.status.display_text

    return response_schema.dump(order).data
Exemplo n.º 8
0
 def get_redeemed_amount_by_user_today(cls, user_id):
     sql = (
         'select sum(amount) from {.table_name} where user_id=%s and direction=%s'
         ' and creation_time > %s').format(cls)
     params = (user_id, cls.Direction.redeem.value, datetime.today().date())
     rs = db.execute(sql, params)
     return round_half_up(
         rs[0][0], 2) if rs and rs[0] and rs[0][0] else Decimal('0.00')
Exemplo n.º 9
0
 def expected_profit(self):
     """预期的到期总收益."""
     order_info, order_status = self._fetch_order_info()
     if order_status == u'已转出':
         return decimal.Decimal(order_info['incomeAmount'])
     monthly_ratio = self.service.expected_income / 100 / 12
     result = monthly_ratio * self.order_amount * self.service.frozen_time
     return round_half_up(result, 2)
Exemplo n.º 10
0
 def get_totals_of_rebate_amount(cls, user_id, type=None, is_settled=None):
     rebates = cls.get_by_user(user_id)
     if type is not None:
         rebates = [r for r in rebates if r.type in type]
     if is_settled is not None:
         rebates = [r for r in rebates if not r.is_withdrawing]
         rebates = [r for r in rebates if r.is_settled == is_settled]
     amount = float(round_half_up(sum(r.rebate_amount for r in rebates), 2))
     return amount
Exemplo n.º 11
0
    def preprocess(self, order):
        if order.asset:
            order._expect_interest = order.asset.expect_interest
        else:
            order._expect_interest = order.expect_interest

        if order.woods_burning:
            order.display_redpacket_amount = u'减%s元' % round_half_up(
                order.woods_burning.amount, 2)
Exemplo n.º 12
0
def subscribe_product(user, product_id, bankcard, buy_amount, coupon=None,
                      pocket_deduction_amount=0):
    """申购产品"""
    #: TODO 检查礼券是否可用、返现账户抵扣是否可用、订单是否可建

    product = Product.get(product_id)
    if not product:
        raise SubscribeProductError(u'申购产品失败: 找不到指定的产品')

    assert product.ptype == Product.Type.unlimited
    vendor = Vendor.get_by_name(Provider.sxb)
    HoarderOrder.check_before_adding(vendor, user.id_, bankcard.id_, product.id_, buy_amount)
    identity = Identity.get(user.id_)
    order_code = sxb.gen_order_id()
    try:
        response = sxb.order_apply(
            order_id=order_code,
            product_id=product.remote_id, buy_amount=buy_amount,
            discount_fee=pocket_deduction_amount,
            user_id=user.id_, province=bankcard.province_id,
            city=bankcard.city_id, person_name=identity.person_name,
            person_ricn=identity.person_ricn,
            mobile=bankcard.mobile_phone, bank_code=bankcard.bank.xm_id,
            redeem_confirm=1, bank_account=bankcard.card_number,
            account_name=identity.person_name)
    except BusinessError as e:
        raise SubscribeProductError(u'申购产品失败: %s' % e)
    finally:
        hoarder_order_canceling.produce(order_code, delay=ORDER_TIME_OUT_SECONDS)

    assert buy_amount == round_half_up(response.buy_amount, 2)

    # 临时性起息日跳过周六日
    effect_day = product.effect_day
    # effect_day_unit = response.effect_day_unit 临时性只以日为单位计算起息日。
    if effect_day > 0:
        start_date = get_work_day(delta=effect_day)
    else:
        start_date = get_next_work_day()
    effect_date = start_date.strftime('%Y-%m-%d %H:%M:%S')
    order = HoarderOrder.add(
        user_id=user.id_,
        product_id=product.id_,
        bankcard_id=bankcard.id_,
        amount=buy_amount,
        pay_amount=response.buy_amount,
        expect_interest=response.return_amount,
        order_code=response.order_id,
        pay_code=response.pay_code,
        direction=HoarderOrder.Direction.save,
        status=HoarderOrder.Status.unpaid,
        remote_status=response.order_status,
        start_time=effect_date,
        due_time=None
    )
    return order
Exemplo n.º 13
0
 def test_calculation(self):
     order = PlaceboOrder.add(user_id=self.user.id_,
                              product_id=self.product.id_,
                              bankcard_id=self.bankcard.id_,
                              amount=None)
     order.creation_time = datetime.datetime(2009, 10, 11, 12, 13, 14)
     assert order.start_date == datetime.date(2009, 10, 11)
     assert order.due_date.date() == datetime.date(2009, 10, 21)
     assert unicode(round_half_up(order.calculate_profit_amount(),
                                  2)) == '0.33'
Exemplo n.º 14
0
def zhiwang_send_exit_sms(asset_id):
    """指旺转出订单发送到期短信"""
    from core.models.sms import ShortMessage
    from core.models.sms.kind import savings_order_exited_sms, savings_first_asset_exited_sms
    from core.models.hoard.zhiwang import ZhiwangAsset

    asset = ZhiwangAsset.get(asset_id)
    first_asset = ZhiwangAsset.get_first_redeemed_asset_by_user_id(
        asset.user_id)

    sms_kind = (savings_first_asset_exited_sms if first_asset
                and first_asset.id_ == asset.id_ else savings_order_exited_sms)

    sms = ShortMessage.create(
        asset.user.mobile,
        sms_kind,
        order_amount=str(round_half_up(asset.create_amount, 2)),
        profit=str(round_half_up(asset.current_interest, 2)))

    sms.send_async()
Exemplo n.º 15
0
def pay_for_order(order):
    payment_time = datetime.datetime.now().strftime(YXPAY_DATETIME_FORMAT)
    payment_amount = round_half_up(order.calculate_profit_amount(), 2)
    identity = Identity.get(order.user_id)
    notify_url = url_for('savings.hook.yxpay_placebo_notify', _external=True)
    return yxpay.query.single_pay(
        YXPAY_ACQ_ID, order.biz_id, YXPAY_PAYMENT_TYPE, YXPAY_BUSINESS_TYPE,
        YXPAY_ACCOUNT_FLAG, payment_time, order.bankcard.bank.yxpay_id,
        payment_amount, YXPAY_INDENTITY_TYPE, identity.person_ricn,
        identity.person_name, order.bankcard.card_number,
        order.bankcard.province_id, order.bankcard.city_id,
        YXPAY_PAYMENT_CHANNEL_TYPE, notify_url)
Exemplo n.º 16
0
def annual_rate():
    """万份收益

    :reqheader Authorization: OAuth 2.0 Bearer Token
    :status 200: 返回  :class:`.AnnualRateListSchema`
    :query offset: 可选参数, 开始条数,按交易列表请求数限制返回结果.
    :query count: 可选参数, 每页数量,按交易列表请求数限制返回结果.
    """
    annual_rate_schema = AnnualRateListSchema(strict=True)
    offset = request.args.get('offset', type=int, default=0)
    count = request.args.get('count', type=int, default=20)
    ids = (WalletAnnualRate.get_ids_by_date_range(
        date_from=datetime.date.today() - datetime.timedelta(days=30),
        date_to=datetime.date.today(), fund_code=g.wallet_provider.fund_code))
    annual_rate_list = WalletAnnualRateList(ids)
    items = annual_rate_list.get_multi(offset, offset + count)
    average = annual_rate_list.average()
    data = {'items': items,
            'average_annual_rate': round_half_up(average.annual_rate, 2),
            'average_ttp': round_half_up(average.ttp, 2)}
    return jsonify(success=True, data=annual_rate_schema.dump(data).data)
Exemplo n.º 17
0
def redeem_confirm(data):
    order_id = data.get('app_order_id')
    app_redeem_id = data.get('app_redeem_id')
    redeem_amount = float(data.get('redeem_amount'))

    order = XMOrder.get_by_order_code(order_code=order_id)
    if not order:
        return jsonify(app_order_id=order_id,
                       app_redeem_id=app_redeem_id,
                       confirm_status=0,
                       remark=u'获取订单失败')

    local_redeem_amount = order.amount + order.amount * order.original_annual_rate / 100 / 365
    if round_half_up(local_redeem_amount, 3) != round_half_up(
            redeem_amount, 3):
        remark = (u'订单赎回金额错误, 订单ID: {0}, 本地计算: {1}, 远端传回: {2}').format(
            order.id_, local_redeem_amount, redeem_amount)
        return jsonify(app_order_id=order_id,
                       app_redeem_id=app_redeem_id,
                       confirm_status=0,
                       remark=remark)

    rate_bonus = order.actual_annual_rate - order.original_annual_rate
    if rate_bonus > Decimal('5.0'):
        remark = (u'订单加息超限, 订单ID: {0}, 加息: {1}').format(order.id_, rate_bonus)
        return jsonify(app_order_id=order_id,
                       app_redeem_id=app_redeem_id,
                       confirm_status=0,
                       remark=remark)

    add_amount = order.amount * float(
        rate_bonus) * order.profit_period.value / 100 / 365

    return jsonify(app_order_id=order_id,
                   app_redeem_id=app_redeem_id,
                   redeem_amount=redeem_amount,
                   add_amount=add_amount,
                   confirm_status=1), 200
Exemplo n.º 18
0
def sxb_purchase():
    """选购产品, 创建理财单.

    :request: :class:`.PurchaseSchema`

    :reqheader Authorization: OAuth 2.0 Bearer Token
    :status 403: 因为未完成实名认证或第三方方面原因, 购买请求被拒
    :status 400: 产品或金额无效, 其中产品无效可能是因为停售或售罄
    :status 201: 订单已创建, 返回 :class:`.ProductSchema`
    """
    # TODO: 需要增加服务器维护时间段。

    purchase_schema = PurchaseSchema(strict=True)
    response_schema = PurchaseResponseSchema()
    result = purchase_schema.load(request.get_json(force=True))

    product_id = result.data['product_id']
    bankcard = obtain_bankcard(result.data['bankcard_id'])
    g.bankcard_manager.set_default(bankcard)
    amount = result.data.get('amount', 0) or 0
    pocket_deduction_amount = result.data.get('pocket_deduction_amount',
                                              0) or 0
    coupon = obtain_coupon(result.data.get('coupon_id'))
    p = Product.get(product_id)
    if not p:
        abort(400, u'产品已下架')
    if amount < p.min_amount:
        abort(400, u'申购金额不能少于%s元' % round_half_up(p.min_amount, 2))
    try:
        order = sxb.subscribe_product(request.oauth.user, product_id, bankcard,
                                      amount, coupon, pocket_deduction_amount)
        inject_bankcard_amount_limit(Partner.xm, [order.bankcard])
    except NotFoundEntityError as e:
        warning('随心宝未找到相应实体', exception=e)
        abort(400, '未知错误')
    except (SubscribeProductError, UnboundAccountError) as e:
        abort(403, unicode(e))
    except (CouponError, CouponUsageError) as e:
        abort(403, unicode(e))
    except (SoldOutError, SuspendedError, OffShelfError, OutOfRangeError,
            InvalidIdentityError, ExceedBankAmountLimitError) as e:
        abort(403, unicode(e))
    except FirewoodException:
        abort(403, u'红包出现错误,请稍后再试或联系客服')

    order._display_status = order.status.display_text

    return jsonify(success=True, data=response_schema.dump(order).data), 201
Exemplo n.º 19
0
def brief():
    wxplan = session.get('wxplan')
    if not wxplan:
        return redirect(url_for('.info'))

    plan = PlanData.from_dict(wxplan)
    formula = Formula(plan=plan)
    raise_quota = round_half_up(formula.get_raise_quota() * 100, 2)
    norm_dist = [min(99, int(n * 100)) for n in formula.get_ten_norm_dist()]
    income_msg = formula.get_income_msg()
    province = ProvinceSalary.get_by_province_code(plan.province_code)

    return render_template('plan/brief_result.html',
                           province=province,
                           raise_quota=raise_quota,
                           norm_dist=norm_dist,
                           income_msg=income_msg)
Exemplo n.º 20
0
def asset_contract(asset_no):
    asset = XMAsset.get_by_asset_no(asset_no)
    if not asset or not asset.is_owner(g.user):
        abort(401)
    identity = Identity.get(asset.user_id)
    upper_amount = num2chn(asset.create_amount)
    product = XMFixedDuedayProduct.get(asset.product_id)
    if not product:
        abort(404)
    expect_rate = 100
    if product.product_type is XMFixedDuedayProduct.Type.classic:
        expect_rate = round_half_up(
            (asset.annual_rate * product.frozen_days / 365 + 1) * 100, 4)
    return render_template('savings/agreement_xinmi.html',
                           asset=asset,
                           identity=identity,
                           expect_rate=expect_rate,
                           product_name=product.name,
                           product_frozen_days=product.frozen_days,
                           upper_amount=upper_amount)
Exemplo n.º 21
0
def pre_redeem(product_id):
    """赎回资产前获取用户相关的产品赎回信息

    :reqheader Authorization: OAuth 2.0 Bearer Token
    :status 200: 请求成功, 返回 :class:`.PreRedeemResponseSchema`
    :status 400: 用户没有该产品的资产
    :status 403: 找不到可赎回产品
    """

    assets = Asset.gets_by_user_id_with_product_id(request.oauth.user.id_,
                                                   product_id)
    if not assets:
        abort(400, u'用户没有可赎回资产')

    current_asset = assets[0]

    product = Product.get(product_id)
    if not product:
        abort(403, u'找不到可赎回产品')

    bankcard = obtain_bankcard(current_asset.bankcard_id)
    current_asset.bankcard_desc = '{0}({1})'.format(bankcard.bank_name,
                                                    bankcard.tail_card_number)

    current_asset.redeem_rule_url = url_for('hybrid.rules.sxb_withdraw',
                                            _external=True)
    current_asset._min_redeem_amount = product.min_redeem_amount
    current_asset._service_fee_rate = current_asset.service_fee_rate * 100
    if current_asset.residual_redemption_times > 0:
        current_asset._service_fee_desc = u'本月还可免费提现 %s 次' % \
                                          current_asset.residual_redemption_times
    else:
        current_asset._service_fee_desc = u'提现费率为 {}%'.format(
            round_half_up(current_asset.service_fee_rate * 100, 2))

    pre_redeem_schema = PreRedeemResponseSchema(strict=True)
    data, errors = pre_redeem_schema.dump(current_asset)
    conditional_for(json.dumps(data))
    return jsonify(success=True, data=data)
Exemplo n.º 22
0
def hoard_yrd_sms_sender(order_id):
    """宜人贷转出订单发送到期短信"""
    from core.models.sms import ShortMessage
    from core.models.sms.kind import savings_order_exited_sms
    from core.models.hoard import HoardOrder, HoardProfile
    from core.models.hoard.profile import clear_account_info_cache

    order = HoardOrder.get(order_id)
    user = Account.get(order.user_id)
    profile = HoardProfile.get(order.user_id)
    if not user.has_mobile():
        return

    # 更新订单最新信息
    clear_account_info_cache(order.user_id)
    orders = profile.orders()
    profit = order.fetch_profit_until(datetime.date.today(), orders)

    # 发送短信
    sms = ShortMessage.create(user.mobile,
                              savings_order_exited_sms,
                              order_amount=int(order.order_amount),
                              profit=str(round_half_up(profit, 2)))
    sms.send_async()
Exemplo n.º 23
0
def orders():
    if not g.user:
        abort(401)

    limit = int(request.args.get('limit', 0))
    filtered = bool(request.args.get('filter'))
    info = {}
    savings_records = []

    yx_profile = HoardProfile.add(g.user.id_)
    zw_profile = ZhiwangProfile.add(g.user.id_)
    xm_profile = XMProfile.add(g.user.id_)
    yx_orders = yx_profile.orders(filter_due=filtered)
    zw_mixins = zw_profile.mixins(filter_due=filtered)
    xm_mixins = xm_profile.mixins(filter_due=filtered)

    placebo_order_ids = PlaceboOrder.get_ids_by_user(g.user.id_)
    placebo_orders = PlaceboOrder.get_multi(placebo_order_ids)
    if filtered:
        placebo_orders = [
            o for o in placebo_orders
            if o.status is not PlaceboOrder.Status.exited
        ]
    placebo_mixins = [(order, ) for order in placebo_orders]

    records = yx_orders + zw_mixins + xm_mixins + placebo_mixins
    if filtered:
        records = sorted(records, key=lambda x: x[0].due_date)
    else:
        records = sorted(records,
                         key=lambda x: x[0].creation_time,
                         reverse=True)
    saving_manager = SavingsManager(g.user.id_)

    info['plan_amount'] = yx_profile.plan_amount
    info['on_account_invest_amount'] = round_half_up(
        saving_manager.on_account_invest_amount, 2)
    info['fin_ratio'] = round_half_up(saving_manager.fin_ratio, 2)
    info['daily_profit'] = round_half_up(saving_manager.daily_profit, 2)
    info['total_profit'] = round_half_up(saving_manager.total_profit, 2)
    limit = limit if 0 < limit < len(records) else len(records)

    for record_info in records[:limit]:
        data = dict()
        base_record = record_info[0]
        exit_type = u'到期自动转回银行卡'

        if base_record.provider is yirendai:
            order, order_info, order_status = record_info
            rebates = HoardRebate.get_by_order_pk(order.id_)

            data['annual_rate'] = order.service.expected_income
            data['frozen_time'] = '%s 个月' % order.service.frozen_time
            data['order_status'] = order_status
            data['savings_money'] = order_info['investAmount']
            data['invest_date'] = order_info['investDate']
            data['exit_type'] = exit_type if order_info[
                'exitType'] == u'退回到划扣银行卡' else order_info['exitType']

            if rebates:
                data['rebates'] = HoardRebate.get_display(rebates)

            if order_status == u'确认中':
                data['interest_start_date'] = u'攒钱后1-3工作日'
            else:
                data['interest_start_date'] = order_info['startCalcDate']

            if order_status == u'已转出':
                data['income_amount'] = u'%s 元' % order_info['incomeAmount']
            else:
                data['expect_income_amount'] = u'%s 元' % order_info[
                    'expectedIncomeAmount']

            data['due_date'] = order.due_date.strftime('%Y-%m-%d')

            if order.bankcard:
                data['bankcard'] = u'%s (%s)' % (
                    order.bankcard.bank_name,
                    order.bankcard.display_card_number)
        elif base_record.provider is zhiwang:
            order, asset = record_info

            data['annual_rate'] = round_half_up(order.actual_annual_rate, 2)
            if order.profit_period.unit == 'day':
                data['frozen_time'] = '%s 天' % order.profit_period.value
            elif order.profit_period.unit == 'month':
                data['frozen_time'] = '%s 个月' % order.profit_period.value
            else:
                raise ValueError('invalid unit %s' % order.profit_period.unit)

            data[
                'order_status'] = asset.display_status if asset else order.display_status
            if order.profit_hikes:
                data['hikes'] = {
                    h.kind.label: h.display_text
                    for h in order.profit_hikes
                }
            data['savings_money'] = int(order.amount)
            data['invest_date'] = unicode(order.creation_time.date())
            data['exit_type'] = exit_type
            data['interest_start_date'] = order.start_date.strftime('%Y-%m-%d')
            if asset and asset.status == ZhiwangAsset.Status.redeemed:
                data['income_amount'] = u'%s 元' % round_half_up(
                    asset.current_interest, 2)
            else:
                # 尽可能显示已加息收益
                # FIXME (tonyseek) 这个做法太粗暴,有赖于资产的更新
                if order.asset:
                    expect_interest = order.asset.expect_interest
                else:
                    expect_interest = order.expect_interest
                data['expect_income_amount'] = u'%s 元' % round_half_up(
                    expect_interest, 2)
            data['due_date'] = order.due_date.strftime('%Y-%m-%d')

            data['contract_url'] = url_for(
                'savings.zhiwang.asset_contract',
                asset_no=asset.asset_no) if asset else ''
            if asset and asset.bankcard:
                # 指旺回款卡以资产的银行卡为准,可能会与订单中的不一致
                data['bankcard'] = u'%s (%s)' % (
                    asset.bankcard.bank.name,
                    asset.bankcard.display_card_number)
        elif base_record.provider is placebo:
            if base_record.status is PlaceboOrder.Status.failure:
                continue
            order = base_record
            profit_amount = order.calculate_profit_amount()
            profit_amount_text = u'%s 元' % round_half_up(profit_amount, 2)
            if base_record.status is PlaceboOrder.Status.exited:
                data['income_amount'] = profit_amount_text
            else:
                data['expect_income_amount'] = profit_amount_text
            data['annual_rate'] = round_half_up(order.profit_annual_rate, 2)
            data['frozen_time'] = order.profit_period.display_text
            data['order_status'] = order.status.display_text
            data['order_type'] = u'体验金'

            data['spring_festival'] = spring_promotion_switch.is_enabled

            data['savings_money'] = int(order.amount)
            data['invest_date'] = unicode(order.start_date)
            data['due_date'] = unicode(order.due_date.date())
            data['bankcard'] = u'%s (%s)' % (
                order.bankcard.bank.name, order.bankcard.display_card_number)
        elif base_record.provider is xmpay:
            order, asset = record_info

            data['annual_rate'] = round_half_up(order.actual_annual_rate, 2)
            if order.profit_period.unit == 'day':
                data['frozen_time'] = '%s 天' % order.profit_period.value
            elif order.profit_period.unit == 'month':
                data['frozen_time'] = '%s 个月' % order.profit_period.value
            else:
                raise ValueError('invalid unit %s' % order.profit_period.unit)

            data[
                'order_status'] = asset.display_status if asset else order.display_status
            if order.profit_hikes:
                data['hikes'] = {
                    h.kind.label: h.display_text
                    for h in order.profit_hikes
                }
            data['savings_money'] = int(order.amount)
            data['invest_date'] = unicode(order.creation_time.date())
            data['exit_type'] = exit_type
            data['interest_start_date'] = order.start_date.strftime('%Y-%m-%d')
            if asset and asset.status == XMAsset.Status.redeemed:
                data['income_amount'] = u'%s 元' % round_half_up(
                    asset.current_interest, 2)
            else:
                # 尽可能显示已加息收益
                if order.asset:
                    expect_interest = order.asset.expect_interest
                else:
                    expect_interest = order.expect_interest
                data['expect_income_amount'] = u'%s 元' % round_half_up(
                    expect_interest, 2)
            # 尽量使用第三方返回的到期日期。
            if order.asset:
                data['due_date'] = order.asset.interest_end_date.strftime(
                    '%Y-%m-%d')
            else:
                data['due_date'] = order.due_date.strftime('%Y-%m-%d')

            data['contract_url'] = url_for(
                'savings.xinmi.asset_contract',
                asset_no=asset.asset_no) if asset else ''
            if asset and asset.bankcard:
                # 投米回款卡以资产的银行卡为准,可能会与订单中的不一致
                data['bankcard'] = u'%s (%s)' % (
                    asset.bankcard.bank_name,
                    asset.bankcard.display_card_number)
        savings_records.append(data)
    return jsonify(r=True, records=savings_records, info=info)
Exemplo n.º 24
0
def subscribe_product(user,
                      product,
                      bankcard,
                      order_amount,
                      pay_amount,
                      due_date=None,
                      coupon=None,
                      pocket_deduction_amount=0):
    """申购产品"""
    # 检查礼券是否可用、返现账户抵扣是否可用、订单是否可建
    if coupon:
        coupon.check_before_use(product, order_amount)
    if pocket_deduction_amount > 0:
        FirewoodWorkflow(user.id_).check_deduction_enjoyable(
            product, order_amount, pocket_deduction_amount)
    XMOrder.check_before_adding(user.id_, bankcard.id_, product.product_id,
                                order_amount)

    # 获取订单优惠信息并检查合法性
    hike_list = collect_profit_hikes(user, coupon, pocket_deduction_amount)
    rate_bonus = max([h.annual_rate
                      for h in hike_list]) if hike_list else Decimal('0')
    discount_fee = sum([h.deduction_amount for h in hike_list])
    assert rate_bonus < Decimal('5.0')  # 新米最高加息限制
    assert order_amount - discount_fee == pay_amount
    # 新米使用加息券需要在赎回确认里加入
    redeem_confirm = u'1' if rate_bonus > Decimal('0.0') else None
    rate_fee = float(order_amount * rate_bonus * product.frozen_days / 100 /
                     365)

    order_code = XMOrder.gen_order_code()

    xm_cancel_order_prepare.produce(order_code, delay=TIME_OUT_SECONDS)

    identity = Identity.get(user.id_)

    buy_amount = order_amount
    try:
        # 向投米发起申购请求
        if DEBUG:
            # 测试环境要求 购买金额x100后为偶数 => 购买成功,否则失败。
            if int(buy_amount) % 2 != 0:
                buy_amount += round_half_up(0.01, 2)
            buy_amount = round_half_up(buy_amount, 2)
        response = xinmi.order_apply(product_id=product.product_id,
                                     order_id=order_code,
                                     buy_amount=buy_amount,
                                     discount_fee=discount_fee,
                                     user_id=user.id_,
                                     province=bankcard.province_id,
                                     city=bankcard.city_id,
                                     person_name=identity.person_name,
                                     person_ricn=identity.person_ricn,
                                     mobile=bankcard.mobile_phone,
                                     bank_code=bankcard.bank.xm_id,
                                     bank_account=bankcard.card_number,
                                     account_name=identity.person_name,
                                     redeem_confirm=redeem_confirm)

    except BusinessError as e:
        raise SubscribeProductError(u'申购产品失败: %s' % e)

    assert buy_amount == round_half_up(response.buy_amount, 2)

    if product.product_type == XMProduct.Type.classic:
        due_date = get_next_work_day(
            response.buy_time) + datetime.timedelta(days=product.frozen_days)
    # 创建订单
    order = XMOrder.add(user_id=user.id_,
                        product_id=product.product_id,
                        bankcard_id=bankcard.id_,
                        amount=buy_amount,
                        pay_amount=response['total_amount'],
                        expect_interest=response.return_amount + rate_fee,
                        start_date=get_next_work_day(response.buy_time),
                        due_date=due_date,
                        order_code=order_code,
                        pay_code=response.pay_code)
    # 创建优惠记录
    for hike in hike_list:
        # FIXME: the operation of hikes should be managed in one session
        Hike.add(user.id_, order.id_, hike.kind, hike.annual_rate,
                 hike.deduction_amount)
    # 订单预绑定礼券
    if coupon:
        CouponUsageRecord.add(coupon, user, provider_xinmi, order)
    # 创建抵扣使用记录
    if pocket_deduction_amount > 0:
        FirewoodBurning.add(user, pocket_deduction_amount,
                            FirewoodBurning.Kind.deduction, provider_xinmi,
                            order.id_)

    return order
Exemplo n.º 25
0
def get_sxb_products(user_id):
    vendor = Vendor.get_by_name(Provider.sxb)
    vendor_product_profile = PRODUCT_PROFILE[vendor.provider]
    products = [
        p for p in Product.get_products_by_vendor_id(vendor.id_)
        if p.kind is Product.Kind.father
    ]
    new_products = [
        p for p in NewComerProduct.get_products_by_vendor_id(vendor.id_)
        if p.kind is Product.Kind.child
    ]
    products.extend(new_products)

    for product in products:
        if product.kind is Product.Kind.child:
            father_product = NewComerProduct.get_father_product_by_vendor_id(
                product.vendor.id_)
            product_id = father_product.id_
        else:
            product_id = product.id_
        assets = Asset.gets_by_user_id_with_product_id(user_id, product_id)
        product.rest_hold_amount = round_half_up(product.max_amount, 2)
        if assets:
            rest_hold_amount = product.max_amount - sum(asset.total_amount
                                                        for asset in assets)
            product.rest_hold_amount = rest_hold_amount if rest_hold_amount > 0 else 0
            product.remaining_amount_today = sum(
                [asset.remaining_amount_today for asset in assets])
        if product.is_on_sale:
            product.button_display_text, product.button_click_text = (
                sale_display_text['on_sale'])
        elif product.is_taken_down:
            product.button_display_text, product.button_click_text = (
                sale_display_text['late_morning_off_sale']
                if product.kind is Product.Kind.father else
                sale_display_text['middle_morning_off_sale'])
        elif product.is_sold_out:
            product.button_display_text, product.button_click_text = (
                sale_display_text['late_morning_sold_out']
                if product.kind is Product.Kind.father else
                sale_display_text['late_morning_sold_out'])
        if product.kind is Product.Kind.child:
            product.rest_hold_amount = 10000
            product.max_amount = 10000
            product.introduction = vendor_product_profile['new_comer'][
                'product_introduction']
            product.title = vendor_product_profile['new_comer'][
                'product_title']
            product.activity_title = vendor_product_profile['new_comer'][
                'activity_title']
            product.activity_introduction = (
                vendor_product_profile['new_comer']['activity_introduction'])
            product.annual_rate = product.operation_num * 100
        else:
            product.introduction = vendor_product_profile['sxb'][
                'product_introduction']
            product.title = vendor_product_profile['sxb']['product_title']
            product.activity_title = vendor_product_profile['sxb'][
                'activity_title']
            product.activity_introduction = vendor_product_profile['sxb'][
                'activity_introduction']
            product.annual_rate = product.rate * 100
        # is_either_sold_out, unique_product_id 为兼容老产品字段
        product.is_either_sold_out = product.is_sold_out
        product.unique_product_id = product.remote_id
        product.is_able_purchased = product.is_on_sale
        product.check_benifit_date = product.value_date + timedelta(days=1)
        product.withdraw_rule = url_for('hybrid.rules.sxb_withdraw',
                                        _external=True)
        product.agreement = url_for('savings.landing.agreement_xinmi',
                                    _external=True)
        product._total_amount = 0
        product.annotations = []

    return products
Exemplo n.º 26
0
 def display_amount(self):
     return u'+%s元' % round_half_up(self.amount, 2)
Exemplo n.º 27
0
 def simplified_display_amount(self):
     return u'+%s' % round_half_up(self.amount, 2)
Exemplo n.º 28
0
 def get_cashables_by_user(cls, user_id):
     rebates = cls.get_by_user(user_id, UNSETTLED)
     rebates = [r for r in rebates if not r.is_withdrawing]
     amount = float(round_half_up(sum(r.rebate_amount for r in rebates), 2))
     return rebates, amount
Exemplo n.º 29
0
    def add(cls, product_info):
        pid = product_info.pop('product_id')
        ptype = product_info.pop('category')

        # 默认上架时间为开售时间。(上下架时间很可能不会传回来)
        start_sell_date = datetime.datetime.strptime(
            product_info.get('open_time'), '%Y-%m-%d %H:%M:%S')
        end_sell_date = datetime.datetime.strptime(
            product_info.get('expire_date'), '%Y-%m-%d %H:%M:%S')
        # 默认下架时间为开售1年后
        if not end_sell_date:
            end_sell_date = datetime.datetime.now() + datetime.timedelta(
                days=365)

        start_sell_date = start_sell_date.date()
        end_sell_date = end_sell_date.date()

        # 判断产品类型是否被支持
        try:
            ptype = cls.Type(ptype)
        except ValueError:
            raise InvalidProductError(ptype)

        quota = product_info.pop('quota', 0)
        product_name = product_info.pop('name', u'未知')

        # 将产品基本属性记入数据库
        existence = cls.get(pid)
        original_quota = 0
        if existence:
            original_quota = existence.quota
            checks = any([
                existence.start_sell_date != start_sell_date,
                existence.end_sell_date != end_sell_date,
            ])
            if checks:
                cls.update_table(pid, ptype, start_sell_date, end_sell_date)
                if existence.quota == quota:
                    return existence
        else:
            cls.update_table(pid, ptype, start_sell_date, end_sell_date)
            if bearychat.configured:
                bearychat.say(u'我们又加新产品啦 **{}** 额度: **{}** :clap:,请周知。'.format(
                    product_name, format_number(quota, locale='en_US')))

        instance = cls.get(pid)

        # 更新产品最新参数
        instance.product_id = pid
        instance._product_type = ptype.value
        instance.name = product_name
        instance.annual_rate = float(
            round_half_up(product_info.pop('return_rate', 0) * 100, 1))
        # 默认最小投资额为1元
        min_amount = product_info.pop('min_amount', 1)
        instance.min_amount = min_amount if min_amount > 1 else 1.00
        instance.max_amount = product_info.pop('max_amount', 0)

        instance.quota = quota
        instance.total_amount = product_info.pop('total_amount', 0)
        instance.sold_amount = product_info.pop('sold_amount', 0)

        # 临时性起息日跳过周六日
        effect_day = product_info.pop('effect_day', -1)
        effect_day_unit = product_info.pop('effect_day_unit', 1)
        if effect_day_unit != 1:
            raise InvalidProductError('未支持的起息日单位!')
        if effect_day > 0:
            start_date = get_work_day(delta=effect_day)
        else:
            start_date = get_next_work_day()
        instance.start_date = start_date.strftime('%Y-%m-%d %H:%M:%S')
        instance.due_date = product_info.pop('expire_date', None)
        instance.description = product_info.pop('remark', u'')
        instance.blank_contract_url = product_info.pop('blank_contract_url',
                                                       '')

        instance.expire_period_unit = product_info.pop('expire_period_unit',
                                                       PeriodUnit.day.value)
        instance.expire_period = product_info.pop('expire_period', 0)

        # 当销售周期内产品额度不足时,发出BC通知
        if existence:
            if bearychat.configured:
                quota_txt = format_number(quota, locale='en_US')
                original_quota_txt = format_number(original_quota,
                                                   locale='en_US')
                if quota < instance.min_amount:
                    txt = u'最近一笔:**¥{}**,当前额度:**¥{}**'.format(
                        original_quota_txt, quota_txt)
                    attachment = bearychat.attachment(title=None,
                                                      text=txt,
                                                      color='#ffa500',
                                                      images=[])
                    bearychat.say(
                        u'产品 **{}** **售罄** 啦,正在尝试释放未支付订单,请周知。'.format(
                            product_name),
                        attachments=[attachment])
                if quota > original_quota + int(instance.min_amount):
                    txt = u'更新前额度:**¥{}**, 当前额度:**¥{}**'.format(
                        original_quota_txt, quota_txt)
                    attachment = bearychat.attachment(title=None,
                                                      text=txt,
                                                      color='#a5ff00',
                                                      images=[])
                    bearychat.say(u'产品 **{}** **额度** 增加啦 :clap:,请周知。'.format(
                        product_name),
                                  attachments=[attachment])

        return instance