UserPerformance.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <?php
  2. namespace common\models;
  3. use common\helpers\Date;
  4. use common\helpers\LoggerTool;
  5. use common\helpers\Tool;
  6. use common\libs\lock\RedisLock;
  7. use Yii;
  8. use common\libs\logging\operate\valueType\Config as ValueTypeConfig;
  9. use yii\base\Exception;
  10. use yii\db\Expression;
  11. /**
  12. * This is the model class for table "{{%USER_PERFORMANCE}}".
  13. *
  14. * @property string $ID
  15. * @property string $USER_ID
  16. * @property string $CASH
  17. * @property integer $UPDATED_AT
  18. * @property integer $CLEAR_BY_CLOSED_AT
  19. * @property User $user
  20. */
  21. class UserPerformance extends \common\components\ActiveRecord
  22. {
  23. const NEWS = 10;
  24. const USING = 20;
  25. const FINISHED = 30;
  26. const NULLIFY = 40;
  27. const EXPIRED = 50;
  28. const USER_PERFORMANCE_BALANCE_LOCK_KEY = 'userPerformanceLock';
  29. public static function getEffective(): array
  30. {
  31. return [self::NEWS, self::USING];
  32. }
  33. public static function getInvalid(): array
  34. {
  35. return [self::FINISHED, self::NULLIFY, self::EXPIRED];
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public static function tableName()
  41. {
  42. return '{{%USER_PERFORMANCE}}';
  43. }
  44. /**
  45. * @inheritdoc
  46. */
  47. public function rules()
  48. {
  49. return [
  50. [['USER_ID'], 'required'],
  51. [['AMOUNTS', 'ORIGINAL'], 'number'],
  52. [['UPDATED_AT', 'STATUS_ID'], 'integer'],
  53. [['ID','USER_ID'], 'string', 'max' => 32],
  54. [['USER_ID'], 'unique'],
  55. ];
  56. }
  57. /**
  58. * @inheritdoc
  59. */
  60. public function attributeLabels()
  61. {
  62. return [
  63. 'ID' => 'ID',
  64. 'USER_ID' => '用户id',
  65. 'AMOUNTS' => '当前余额',
  66. 'ORIGINAL' => '原始金额',
  67. 'UPDATED_AT' => '修改时间',
  68. ];
  69. }
  70. /**
  71. * 获取一名会员的余额
  72. * @param $userId
  73. * @return array|null
  74. */
  75. public static function getAmountByUserId($userId)
  76. {
  77. $data = UserPerformance::find()->select('SUM(AMOUNTS) AS AMOUNTS')->where('USER_ID=:USER_ID AND STATUS_ID<(:STATUS_ID)', [':USER_ID' => $userId, ':STATUS_ID' => self::FINISHED])->asArray()->one();
  78. if(!$data){
  79. $data = [
  80. 'USER_ID' => $userId,
  81. 'AMOUNTS' => 0,
  82. ];
  83. }
  84. return $data;
  85. }
  86. public static function getAmounts($userId)
  87. {
  88. $data = UserPerformance::find()->select('SUM(AMOUNTS) AS AMOUNTS')->where('USER_ID=:USER_ID AND STATUS_ID<(:STATUS_ID)', [':USER_ID' => $userId, ':STATUS_ID' => self::FINISHED])->asArray()->one();
  89. return $data['AMOUNTS'] ?? 0;
  90. }
  91. /**
  92. * @param $userId
  93. * @param $amount
  94. * @param string $orderId
  95. * @return true
  96. * @throws Exception
  97. * @throws \yii\db\Exception
  98. */
  99. public static function changeUserPerformance($userId, $amount, string $orderId = ''): bool
  100. {
  101. if ($amount == 0) {
  102. return true;
  103. }
  104. // 会员总绩效奖金
  105. $userPerformanceAmount = self::getAmountByUserId($userId);
  106. if ($amount > $userPerformanceAmount) {
  107. throw new Exception(Yii::t('app', 'applicantPrpShort'));
  108. }
  109. $period = Period::instance();
  110. $periodNum = $period->getNowPeriodNum();
  111. $calcYearMonth = $period->getYearMonth($periodNum);
  112. // redis加锁(防止并发余额数值不准确出错)
  113. $lockKey = self::USER_PERFORMANCE_BALANCE_LOCK_KEY . $userId;
  114. if (RedisLock::instance()->lock($lockKey)) {
  115. // 根据规则获取绩效奖金:过期时间(升序) -> 发放时间(升序)
  116. $records = self::find()
  117. ->where('USER_ID=:USER_ID AND AMOUNTS>0 AND STATUS_ID<:STATUS_ID', [':USER_ID' => $userId, ':STATUS_ID' => self::FINISHED])
  118. ->orderBy('EXPIRED_AT ASC, CREATED_AT ASC')
  119. ->asArray()
  120. ->all();
  121. $db = \Yii::$app->db;
  122. $transaction = $db->beginTransaction();
  123. try {
  124. // 循环扣除绩效奖金
  125. $surplus = $amount;
  126. foreach ($records as $record) {
  127. $balance = 0;
  128. if ($record['AMOUNTS'] > $surplus) {
  129. $balance = $record['AMOUNTS'] - $surplus;
  130. // 扣除奖金
  131. UserPerformance::updateAll(['AMOUNTS' => $balance, 'STATUS_ID' => self::USING], 'ID=:ID', [':ID' => $record['ID']]);
  132. // 写日志
  133. UserPerformanceLogs::changeAmountLogs($record['ID'], $surplus, $periodNum, $orderId);
  134. break;
  135. }
  136. if ($record['AMOUNTS'] == $surplus) {
  137. $balance = $record['AMOUNTS'] - $surplus;
  138. // 扣除奖金
  139. UserPerformance::updateAll(['AMOUNTS' => $balance, 'STATUS_ID' => self::FINISHED], 'ID=:ID', [':ID' => $record['ID']]);
  140. // 写日志
  141. UserPerformanceLogs::changeAmountLogs($record['ID'], $surplus, $periodNum, $orderId);
  142. break;
  143. }
  144. if ($record['AMOUNTS'] < $surplus) {
  145. $balance = $surplus - $record['AMOUNTS'];
  146. $surplus = $balance;
  147. // 扣除奖金
  148. UserPerformance::updateAll(['AMOUNTS' => 0, 'STATUS_ID' => self::FINISHED], 'ID=:ID', [':ID' => $record['ID']]);
  149. // 写日志
  150. UserPerformanceLogs::changeAmountLogs($record['ID'], $surplus, $periodNum, $orderId);
  151. }
  152. }
  153. $transaction->commit();
  154. } catch (\Exception $e) {
  155. $transaction->rollBack();
  156. throw new Exception($e->getMessage());
  157. }
  158. // 解除锁
  159. RedisLock::instance()->unlock($lockKey);
  160. } else {
  161. throw new Exception(Yii::t('app', 'flowCreateError'));
  162. }
  163. return true;
  164. }
  165. /**
  166. * 绩效奖金发放
  167. * @param $userId
  168. * @param $amount
  169. * @param $bountyPeriodNum
  170. * @return bool
  171. * @throws Exception
  172. */
  173. public static function sentUserPerformance($userId, $amount, $bountyPeriodNum): bool
  174. {
  175. if ($amount == 0) {
  176. return true;
  177. }
  178. // 会员国家
  179. $countryCode = Countries::getById(User::getEnCodeInfo($userId)['COUNTRY_ID'])['CODE'];
  180. $period = Period::instance();
  181. $periodNum = $period->getNowPeriodNum();
  182. $db = \Yii::$app->db;
  183. $transaction = $db->beginTransaction();
  184. try {
  185. // 奖金发放
  186. $id = 'EP' . Tool::generateUserPerformanceNo($countryCode);
  187. self::insertOne([
  188. 'ID' => $id,
  189. 'USER_ID' => $userId,
  190. 'AMOUNTS' => $amount,
  191. 'ORIGINAL' => $amount,
  192. 'STATUS_ID' => self::NEWS,
  193. 'EXPIRED_AT' => date('Y-m-d H:i:s', strtotime('+1 year', time())),
  194. 'CREATED_AT' => date('Y-m-d H:i:s', time()),
  195. 'UPDATED_AT' => date('Y-m-d H:i:s', time()),
  196. 'REMARK' => DealType::getDealTypeTagById(DealType::USER_PERFORMANCE_SEND),
  197. 'BOUNTY_PERIOD_NUM' => $bountyPeriodNum,
  198. 'PAID_PERIOD_NUM' => $periodNum,
  199. ]);
  200. // 写日志
  201. UserPerformanceLogs::changeAmountLogs($id, $amount, $periodNum, '', DealType::getDealTypeTagById(DealType::USER_PERFORMANCE_SEND));
  202. $transaction->commit();
  203. } catch (\Exception $e) {
  204. $transaction->rollBack();
  205. throw new Exception(sprintf('PB奖金挂网异常:File: %s, Line: {%s}, message: %s', $e->getFile(), $e->getLine(), $e->getMessage()));
  206. }
  207. return true;
  208. }
  209. /**
  210. * 绩效奖金发放
  211. * @param $userId
  212. * @param $amount
  213. * @param $bountyPeriodNum
  214. * @return bool
  215. * @throws Exception
  216. */
  217. public static function sentUserPerformanceApi($userId, $amount, $bountyPeriodNum): bool
  218. {
  219. if ($amount == 0) {
  220. return true;
  221. }
  222. // 会员国家
  223. $countryCode = Countries::getById(User::getEnCodeInfo($userId)['COUNTRY_ID'])['CODE'];
  224. $db = \Yii::$app->db;
  225. $transaction = $db->beginTransaction();
  226. try {
  227. // 奖金发放
  228. $id = 'EP' . Tool::generateUserPerformanceNo($countryCode);
  229. self::insertOne([
  230. 'ID' => $id,
  231. 'USER_ID' => $userId,
  232. 'AMOUNTS' => $amount,
  233. 'ORIGINAL' => $amount,
  234. 'STATUS_ID' => self::NEWS,
  235. 'EXPIRED_AT' => date('Y-m-d H:i:s', strtotime('+1 year', time())),
  236. 'CREATED_AT' => date('Y-m-d H:i:s', time()),
  237. 'UPDATED_AT' => date('Y-m-d H:i:s', time()),
  238. 'REMARK' => DealType::getDealTypeTagById(DealType::USER_PERFORMANCE_SEND),
  239. 'BOUNTY_PERIOD_NUM' => $bountyPeriodNum,
  240. 'PAID_PERIOD_NUM' => $bountyPeriodNum+1,
  241. ]);
  242. // 写日志
  243. UserPerformanceLogs::changeAmountLogs($id, $amount, $bountyPeriodNum+1, '', DealType::getDealTypeTagById(DealType::USER_PERFORMANCE_SEND));
  244. $transaction->commit();
  245. } catch (\Exception $e) {
  246. $transaction->rollBack();
  247. throw new Exception(sprintf('PB奖金挂网异常:File: %s, Line: {%s}, message: %s', $e->getFile(), $e->getLine(), $e->getMessage()));
  248. }
  249. return true;
  250. }
  251. }