UserPerformance.php 7.4 KB

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