UserPerformance.php 7.6 KB

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