bourse stock
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
16 KiB

<?php
namespace app\home\service;
use app\model\FeeSettingModel;
use app\model\HistoryFundStockModel;
use app\model\PreFundStockModel;
use app\model\PreFundStockReferModel;
use app\model\StockMarketModel;
use app\model\UserFundPreStockOrderModel;
use app\model\UserStockFundInterestReceiptModel;
use app\model\UserModel;
use app\model\UserStockFundLogModel;
use app\model\UserStockFundModel;
use DatePeriod;
use DateTime;
use DateInterval;
use think\facade\Db;
class FundService extends BaseHomeService
{
// 基金列表
public function index(array $param): array
{
try {
if (empty($param['page']) || !is_numeric($param['page']) || empty($param['limit']) || !is_numeric($param['limit'])) {
$param['page'] = 1;
$param['limit'] = 10;
}
$openStatusOrder = [2, 1, 3]; // 指定的用户ID顺序
// 构建排序字符串
$orderString = 'FIELD(open_status, ' . implode(',', $openStatusOrder) . ')';
$where = [];
// $where[] = ['open_status', '=', PreFundStockModel::OPEN_STATUS_HAD];
$where[] = ['is_delete', '=', PreFundStockModel::IS_DELETE_NO];
$rows = PreFundStockModel::where($where)->field('id,stock_code,min_price,stock_type,stock_name,tag,cycle,rate,stock_info,open_status')->append(['stock_type_text', 'open_status_text', 'stock_type_en_text', 'open_status_en_text'])->orderRaw($orderString)->order('sort', 'asc')->page($param['page'], $param['limit'])->select();
$total = PreFundStockModel::where($where)->count();
return $this->toData('0', 'SUCCESS', ['list' => $rows, 'total' => $total]);
} catch (\Exception $exception) {
return $this->toData('400', 'System error', [$exception->getMessage()]);
}
}
// 基金详情
public function detail($param): array
{
try {
if (empty($param['id']) || !is_numeric($param['id'])) {
return $this->toData('1', 'Params error', []);
}
$fund = PreFundStockModel::where('id', $param['id'])->where('is_delete', PreFundStockModel::IS_DELETE_NO)->find();
if (empty($fund)) {
return $this->toData('1', 'Params error', []);
}
$fund->product_file = json_decode($fund->product_file, true);
$fund->current_manager = json_decode($fund->current_manager, true);
return $this->toData('0', 'SUCCESS', ['data' => $fund, 'interest' => $fund->show_interest]);
} catch (\Exception $exception) {
return $this->toData('400', 'System error', [$exception->getMessage()]);
}
}
// 基金历史走势
public function history(array $param): array
{
try {
if (empty($param['stock_code']) || !is_string($param['stock_code'])) {
return $this->toData('1', 'Params error', []);
}
if (empty($param['type']) || !in_array($param['type'], [1, 2, 3])) {
$param['type'] = 1;
}
$historyData = HistoryFundStockModel::where('stock_code', $param['stock_code'])->find();
if (empty($historyData)) {
return $this->toData('1', 'Params error', []);
}
$history = [];
$historyInfo = json_decode($historyData->history_info, true);
$start = new DateTime($historyData->start_date);
$end = new DateTime($historyData->end_date);
switch ($param['type']) {
case 1: //日
$history = $historyInfo;
break;
case 2: //月
// 创建 DateInterval 对象,表示一个月的时间间隔
$interval = new DateInterval('P1M');
// 创建 DatePeriod 对象,表示两个日期之间的月份
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $date) {
$month = $date->format('Y-m');
$firstDay = new DateTime("$month-01");
$nextMonth = clone $firstDay;
$nextMonth->modify('+1 month');
$lastDay = $nextMonth->modify('-1 day');
$monthlastDay = $lastDay->format('Y-m-d');
if ($lastDay > $end) {
$monthlastDay = $end->format('Y-m-d');
}
$history[$month] = $historyInfo[$monthlastDay];
}
break;
case 3: //年
$currentDate = clone $start;
$startYear = $currentDate->format('Y');
$currentYear = new DateTime("$startYear-01-01");
while ($currentYear <= $end) {
$lastDayOfYear = new DateTime($currentYear->format('Y') . "-12-31");
$lastDayOfYearStr = $lastDayOfYear->format('Y-m-d');
if ($lastDayOfYear > $end) {
$lastDayOfYearStr = $end->format('Y-m-d');
}
$history[$currentYear->format('Y')] = $historyInfo[$lastDayOfYearStr];
$currentYear->modify('+1 year');
}
break;
}
$year = [];
if ($param['type'] == 3) {
$year = $history;
} else {
$currentDate = clone $start;
$startYear = $currentDate->format('Y');
$currentYear = new DateTime("$startYear-01-01");
while ($currentYear <= $end) {
$lastDayOfYear = new DateTime($currentYear->format('Y') . "-12-31");
$lastDayOfYearStr = $lastDayOfYear->format('Y-m-d');
if ($lastDayOfYear > $end) {
$lastDayOfYearStr = $end->format('Y-m-d');
}
$year[$currentYear->format('Y')] = $historyInfo[$lastDayOfYearStr];
$currentYear->modify('+1 year');
}
}
$refer = PreFundStockReferModel::select();
$referResult = [];
foreach ($refer as $v) {
$referResult[$v['name']][$v['year']] = $v['rate'];
}
return $this->toData('0', 'SUCCESS', ['data' => $history, 'year' => $year, 'refer' => $referResult]);
} catch (\Exception $exception) {
return $this->toData('400', 'System error', [$exception->getMessage()]);
}
}
// 下单
public function order($param, $userId)
{
try {
// 需要完成实名认证之后才能下单
$user = UserModel::where('user_id', $userId)->find();
if (empty($user) || $user->is_real != 1) {
return $this->toData('405', 'Only after completing real-name authentication can you place an order.');
}
if (empty($userId)) {
return $this->toData('1', 'Login fail');
}
if (empty($param['id']) || !is_numeric($param['id'])) {
return $this->toData('1', 'Params error2', []);
}
$fund = PreFundStockModel::where('id', $param['id'])
->where('open_status', PreFundStockModel::OPEN_STATUS_HAD)
->where('is_delete', PreFundStockModel::IS_DELETE_NO)
->whereTime('start_time', '<=', date('Y-m-d H:i:s'))
->whereTime('end_time', '>=', date('Y-m-d H:i:s'))
->find();
if (empty($fund)) {
return $this->toData('1', 'Params error3', []);
}
// 下单参数
if (empty($param['amount']) || !is_numeric($param['amount']) || ceil($param['amount']) != $param['amount'] || $param['amount'] <= 0) {
return $this->toData('1', 'Params error', []);
}
if ($param['amount'] < $fund->min_price) {
return $this->toData('1', 'Params num error4', []);
}
$amount = $param['amount'];
// 计算手续费
$purchaseFee = FeeSettingModel::where('market_type', StockMarketModel::STOCK_MARKET_FUND)->value('purchase_fee');
if (empty($purchaseFee)) {
$purchaseFee = 0;
$fee = 0;
} else {
$purchaseFee = number_format($purchaseFee, 18, '.', '');
$fee = bcmul($amount, $purchaseFee, 18);
}
Db::startTrans();
// 生成订单
$orderNo = $this->generateOrderNumber(20);
$order = new UserFundPreStockOrderModel;
$order->user_id = $userId;
$order->pre_stock_id = $fund->id;
$order->status = UserFundPreStockOrderModel::STATUS_ONE;
$order->order_no = $orderNo;
$order->amount = $amount;
$order->phase_count = $fund->phase_count;
$order->stock_rate = $fund->rate;
$order->stock_cycle_type = $fund->cycle_type;
$order->stock_cycle = $fund->cycle;
$order->fee = $fee;
$order->fee_rate = $purchaseFee; // 手续费比率
$bool = $order->save();
if (!$bool) {
Db::rollback();
return $this->toData('1', 'create fund order error', []);
}
// 扣除用户资产
$userStockName = (new UserStockFundModel())->getName();
$beforeAmount = Db::name($userStockName)->where('stock_id', 'USD')->where('user_id', $userId)->value('usable_num');
if ($beforeAmount < $amount) {
Db::rollback();
return $this->toData('1', 'assert not enough', []);
}
$updateNum = Db::name($userStockName)->where('stock_id', 'USD')->where('user_id', $userId)
->where('usable_num', '>=', $amount)->dec('usable_num', $amount)
->update(['update_time' => date('Y-m-d H:i:s')]);
if ($updateNum <= 0) {
Db::rollback();
return $this->toData('1', 'Update user amount error', []);
}
// 生成流水
$userStockLog = new UserStockFundLogModel();
$userStockLog->user_id = $userId;
$userStockLog->change_type = 3;
$userStockLog->stock_id = 'USD';
$userStockLog->before_num = $beforeAmount;
$userStockLog->change_num = '-' . $amount;
$userStockLog->order_id = $orderNo;
$userStockLog->create_time = date('Y-m-d H:i:s');
$userStockLog->update_time = date('Y-m-d H:i:s');
$updateNum2 = $userStockLog->save();
if ($updateNum2 <= 0) {
Db::rollback();
return $this->toData('1', 'create user log error', []);
}
// 扣除手续费
if ($fee > 0) {
$beforeFee = Db::name($userStockName)->where('stock_id', 'USD')->where('user_id', $userId)->value('usable_num');
if ($beforeFee < $fee) {
Db::rollback();
return $this->toData('1', 'assert not enough', []);
}
$updateNum = Db::name($userStockName)->where('stock_id', 'USD')->where('user_id', $userId)
->where('usable_num', '>=', $fee)->dec('usable_num', $fee)
->inc('frozen_num', $fee)
->update(['update_time' => date('Y-m-d H:i:s')]);
if ($updateNum <= 0) {
Db::rollback();
return $this->toData('1', 'Update user amount error', []);
}
// 生成流水
$userStockLog = new UserStockFundLogModel;
$userStockLog->user_id = $userId;
$userStockLog->change_type = 14; //扣手续费
$userStockLog->stock_id = 'USD';
$userStockLog->before_num = $beforeFee;
$userStockLog->change_num = '-' . $fee;
$userStockLog->order_id = $orderNo;
$userStockLog->create_time = date('Y-m-d H:i:s');
$userStockLog->update_time = date('Y-m-d H:i:s');
$updateNum3 = $userStockLog->save();
if ($updateNum3 <= 0) {
Db::rollback();
return $this->toData('1', 'create user fee log error', []);
}
}
// 生成基金返款记录
$userStockFundInterestReceipt = new UserStockFundInterestReceiptModel;
$userStockFundInterestReceipt->user_id = $userId;
$userStockFundInterestReceipt->order_id = $order->id;
$userStockFundInterestReceipt->pre_stock_id = $fund->id;
$userStockFundInterestReceipt->order_no = $this->generateOrderNumber(20);
$userStockFundInterestReceipt->capital = $amount;
$userStockFundInterestReceipt->interest = bcmul($amount, $fund->rate / 100, 18);
$userStockFundInterestReceipt->phase_time = 1;
$userStockFundInterestReceipt->unit = 'USD';
$currentDate = new DateTime();
$addDay = $fund->cycle + 1;
$currentDate->modify("+$addDay days");
$userStockFundInterestReceipt->return_date = $currentDate->format('Y-m-d');
$userStockFundInterestReceiptBool = $userStockFundInterestReceipt->save();
if (!$userStockFundInterestReceiptBool) {
Db::rollback();
return $this->toData('1', 'update fund interest error', []);
}
//更新基金 已购买金额
$fundHadNum = PreFundStockModel::where('id', $fund->id)->inc('had_get_price', $amount)->update(['update_time' => date('Y-m-d H:i:s')]);
if ($fundHadNum <= 0) {
Db::rollback();
return $this->toData('1', 'Update fund had_get_price error', []);
}
Db::commit();
return $this->toData('0', 'SUCCESS', []);
} catch (\Exception $exception) {
return $this->toData('0', 'The system is busy. Please try again later.', [$exception->getMessage()]);
}
}
public function userFund($param, $userId)
{
try {
if (empty($param['page']) || !is_numeric($param['page']) || empty($param['limit']) || !is_numeric($param['limit'])) {
$param['page'] = 1;
$param['limit'] = 10;
}
$oderTableName = (new UserFundPreStockOrderModel())->getName();
$oderTableName = 'bot_' . $oderTableName;
$total = UserFundPreStockOrderModel::where("user_id", $userId)->count();
$rows = UserFundPreStockOrderModel::where($oderTableName . ".user_id", $userId)->with('interest_arr')->withJoin(['fund' => ['stock_name', 'cycle', 'rate', 'stock_info', 'stock_type']])->append(['status_en_text'])->order('status', 'asc')->order('id', 'desc')->page($param['page'], $param['limit'])->select()->toArray();
foreach ($rows as &$v) {
$capitalArr = array_column($v['interest_arr'], 'capital');
$interestArr = array_column($v['interest_arr'], 'interest');
$v['capital'] = array_sum($capitalArr);
$v['interest'] = array_sum($interestArr);
$v['fund']['stock_type_en_text'] = PreFundStockModel::$stockTypeEnList[$v['fund']['stock_type']];
}
return $this->toData('0', 'SUCCESS', ['list' => $rows, 'total' => $total]);
} catch (\Exception $exception) {
return $this->toData('400', 'System error', [$exception->getMessage()]);
}
}
}