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'))
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()
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), ))
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
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)
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)
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
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')
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)
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
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)
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
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'
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()
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)
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)
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
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
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)
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)
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)
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()
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)
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
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
def display_amount(self): return u'+%s元' % round_half_up(self.amount, 2)
def simplified_display_amount(self): return u'+%s' % round_half_up(self.amount, 2)
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
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