TaxOrder.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. <?php
  2. namespace addons\AvoidTax\common\models;
  3. use addons\AvoidTax\common\enums\OrderTypeEnum;
  4. use addons\AvoidTax\common\enums\StatusEnum;
  5. use addons\AvoidTax\common\models\UserWithdrawLog as ModelsUserWithdrawLog;
  6. use addons\AvoidTax\common\service\TaxConfigService;
  7. use common\components\export\CsvTool;
  8. use common\enums\DownloadEnum;
  9. use common\models\common\Download;
  10. use common\models\user\UserWithdrawLog;
  11. use Yii;
  12. use yii\data\Pagination;
  13. use yii\db\ActiveQuery;
  14. use yii\helpers\Json;
  15. /**
  16. * This is the model class for table "{{%addons_tax_order}}".
  17. *
  18. * @property int $id
  19. * @property int $mall_id
  20. * @property int|null $account_id 记账方ID
  21. * @property int|null $user_id 用户ID
  22. * @property string|null $job_id
  23. * @property string|null $job_detail_id
  24. * @property int $withdraw_id 提现ID
  25. * @property string|null $title 标题
  26. * @property string|null $batch_num 批次号
  27. * @property string|null $name 汇款方名称
  28. * @property string|null $mobile 汇款方账户
  29. * @property float|null $amount 汇款金额
  30. * @property string|null $remarks
  31. * @property int $type 汇款订单类型 1默认 2商城提现
  32. * @property int $sub_type 1用户订单2税务方订单
  33. * @property int $account_type 1 打款 2 出款
  34. * @property int $status 0 未打款 1 已打款记帐本 2 打款成功
  35. * @property string|null $alipay_response 支付响应
  36. * @property string|null $error 错误信息
  37. * @property int|null $updated_at
  38. * @property int|null $created_at
  39. * @property string|null $order_sn
  40. * @property int|null $pay_fee 手续费
  41. */
  42. class TaxOrder extends \yii\db\ActiveRecord
  43. {
  44. const PREFIX = 'no_';
  45. const ORDER_TYPE_MANUAL = 1; // 后台手动打款
  46. const ORDER_TYPE_SHOP = 2; // 商城提现
  47. const SUB_TYPE_USER = 1; // 用户订单
  48. const SUB_TYPE_TAX = 2; // 税务方手续费订单
  49. const TAX_ORDER_PRE = 'TAX_';// 税务方订单前缀
  50. const TAX_ALI_ACCOUNT_RATE = 6; // 税务方收取手续费比例,千分之六
  51. /**
  52. * {@inheritdoc}
  53. */
  54. public static function tableName()
  55. {
  56. return '{{%addons_tax_order}}';
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. public function rules()
  62. {
  63. return [
  64. [['mall_id','user_id', 'account_id', 'withdraw_id', 'type', 'sub_type', 'account_type', 'status', 'updated_at', 'created_at'], 'integer'],
  65. [['amount','pay_fee'], 'number'],
  66. [['error', 'alipay_response'], 'string'],
  67. [['title', 'remarks'], 'string', 'max' => 255],
  68. [['batch_num','order_sn'], 'string', 'max' => 32],
  69. [['name', 'mobile','job_id','job_detail_id'], 'string', 'max' => 64],
  70. ];
  71. }
  72. /**
  73. * {@inheritdoc}
  74. */
  75. public function attributeLabels()
  76. {
  77. return [
  78. 'id' => 'ID',
  79. 'mall_id' => 'Mall ID',
  80. 'user_id' => 'user id',
  81. 'account_id' => 'Account ID',
  82. 'withdraw_id' => 'Withdraw ID',
  83. 'title' => 'Title',
  84. 'batch_num' => 'Batch Num',
  85. 'name' => 'Name',
  86. 'mobile' => 'Account Num',
  87. 'amount' => 'Amount',
  88. 'remarks' => 'Remarks',
  89. 'type' => 'Type',
  90. 'account_type' => 'Account Type',
  91. 'status' => 'Status',
  92. 'alipay_response' => 'Alipay Response',
  93. 'error' => 'Error',
  94. 'updated_at' => 'Updated At',
  95. 'created_at' => 'Created At',
  96. ];
  97. }
  98. public function getUserAuth()
  99. {
  100. return $this->hasOne(TaxUserAuth::class,['user_id'=>'user_id']);
  101. }
  102. public function beforeSave($insert)
  103. {
  104. if (parent::beforeSave($insert)) {
  105. $insert
  106. ? $this->created_at = $this->updated_at = time()
  107. : $this->updated_at = time();
  108. return true;
  109. } else {
  110. return false;
  111. }
  112. }
  113. /**
  114. * 子订单
  115. *
  116. * @return ActiveQuery hasMany($class, array $link) see [[BaseActiveRecord::hasMany()]] for more info
  117. */
  118. public function getList()
  119. {
  120. return $this->hasMany(self::class, ['id' => 'order_id']);
  121. }
  122. /**
  123. * 商城提现
  124. *
  125. * @return ActiveQuery hasMany($class, array $link) see [[BaseActiveRecord::hasMany()]] for more info
  126. */
  127. public function getWithdraw()
  128. {
  129. return $this->hasOne(UserWithdrawLog::class, ['id' => 'withdraw_id']);
  130. }
  131. /**
  132. * 商城提现
  133. *
  134. * @return ActiveQuery hasMany($class, array $link) see [[BaseActiveRecord::hasMany()]] for more info
  135. */
  136. public function getWithdraw2()
  137. {
  138. return $this->hasOne(ModelsUserWithdrawLog::class, ['id' => 'withdraw_id']);
  139. }
  140. /**
  141. * 获取关联记帐本
  142. *
  143. * @return ActiveQuery
  144. */
  145. public function getAccount()
  146. {
  147. return $this->hasOne(TaxAccount::class, ['id' => 'account_id']);
  148. }
  149. /**
  150. * 获取关联记帐本
  151. *
  152. * @return ActiveQuery
  153. */
  154. public function getBook()
  155. {
  156. return $this->hasOne(TaxAccountBook::class, ['account_id' => 'account_id']);
  157. }
  158. /**
  159. * 获取用户订单
  160. *
  161. * @return ActiveQuery hasMany($class, array $link) see [[BaseActiveRecord::hasMany()]] for more info
  162. */
  163. public function getUserLog()
  164. {
  165. return $this->hasOne(TaxOrder::class, ['batch_num' => 'batch_num'])->where(['sub_type' => static::SUB_TYPE_USER]);
  166. }
  167. /**
  168. * 关联用户
  169. * @Author: lun
  170. * @DateTime: 2023/6/9 0009 15:48
  171. * @Copyright: copyright (c) 2021 广东七件事集团
  172. * @return ActiveQuery
  173. */
  174. public function getUser()
  175. {
  176. return $this->hasOne(User::class, ['id' => 'user_id'])->select('id,nickname,mobile');
  177. }
  178. /**
  179. * 新增一条交易记录
  180. *
  181. * @param array $data
  182. * @return boolean
  183. */
  184. public function add($data)
  185. {
  186. $this->load($data, '');
  187. return $this->save();
  188. }
  189. /**
  190. * 获取一个批次号
  191. *
  192. * @return string
  193. */
  194. public static function getBatchNum()
  195. {
  196. srand();
  197. $num = self::PREFIX . date('YmdHis') . rand(1000, 9909);
  198. return self::numExists($num)
  199. ? $num
  200. : self::getBatchNum();
  201. }
  202. /**
  203. * 查询批次号是否存在
  204. *
  205. * @param string $num
  206. * @return boolean
  207. */
  208. public static function numExists($num)
  209. {
  210. return empty(self::find()->select('id')->where(['batch_num' => $num])->one())
  211. ? true
  212. : false;
  213. }
  214. /**
  215. * 回款成功
  216. *
  217. * @return bool
  218. */
  219. public static function transSuccess($batch_num = null,$order_sn = null )
  220. {
  221. $batch_num && $where['batch_num'] = $batch_num;
  222. $order_sn && $where['order_sn'] = $order_sn;
  223. if(empty($where)) return false;
  224. return self::updateAll(['status' => StatusEnum::STATUS_OUT_OK, 'error' => '', 'updated_at' => time()], $where);
  225. }
  226. /**
  227. * 订单完成
  228. *
  229. * @return boolean
  230. */
  231. public function taxOrderSuccess()
  232. {
  233. $this->error = '';
  234. $this->status = StatusEnum::STATUS_OUT_OK;
  235. return $this->save();
  236. }
  237. /**
  238. * 订单失败
  239. * @param string $msg
  240. *
  241. * @return boolean
  242. */
  243. public function taxOrderFail(string $msg = '')
  244. {
  245. $this->error = $msg;
  246. $this->status = StatusEnum::STATUS_FAIL;
  247. return $this->save();
  248. }
  249. /**
  250. * 修改订单错误信息
  251. * @param null $batch_num 批次号
  252. * @param null $order_sn 订单号
  253. * @param string $msg 错误信息
  254. * @param null $status 状态
  255. *
  256. * @return bool|int
  257. */
  258. public static function transError($batch_num = null, $order_sn = null, $msg = '')
  259. {
  260. $batch_num && $where['batch_num'] = $batch_num;
  261. $order_sn && $where['order_sn'] = $order_sn;
  262. if(empty($where)) return false;
  263. return self::updateAll(['status' => StatusEnum::STATUS_FAIL, 'error' => $msg, 'updated_at' => time()], $where);
  264. }
  265. /**
  266. * 手续费打款失败
  267. *
  268. * @return boolean
  269. */
  270. public function transTaxFeeError(string $msg)
  271. {
  272. $this->error = $msg;
  273. return $this->save();
  274. }
  275. /**
  276. * 通过批次号获取用户id
  277. */
  278. public static function byBatchNumGetUserId($batch_num):array {
  279. $orders = TaxOrder::find()->select('withdraw_id')->where(['batch_num'=>$batch_num])->asArray()->all();
  280. if ( empty($orders) ) return [];
  281. $userids = UserWithdrawLog::find()
  282. ->select("user_id")
  283. ->where(['id'=>array_column($orders,'withdraw_id')])
  284. ->asArray()
  285. ->indexBy("user_id")
  286. ->all();
  287. return $userids;
  288. }
  289. public static function changStatus($batch_num,$param)
  290. {
  291. $param['updated_at'] = time();
  292. return self::updateAll($param,['batch_num'=>$batch_num]);
  293. }
  294. /**
  295. * 获取分页数据
  296. *
  297. * @param integer $page
  298. * @param integer $limit
  299. * @param callable $callback
  300. * @param string $order_by
  301. * @return array
  302. */
  303. public static function getPageList(
  304. int $page, int $limit, callable $callback, string $order_by = 'id'
  305. ) :array
  306. {
  307. $model = static::find();
  308. if (!empty($callback)) call_user_func($callback, $model);
  309. $pagination = new Pagination(['totalCount' => $model->count(), 'pageSize' => $limit]);
  310. $model->limit($pagination->limit)->offset($pagination->offset);
  311. $list = $model->asArray()->orderBy($order_by)->all();
  312. $data['list'] = $list;
  313. $data['page_count'] = $pagination->pageCount;
  314. $data['current_page'] = $page + 1;
  315. return $data;
  316. }
  317. /**
  318. * 添加支付响应
  319. *
  320. * @param array $response
  321. * @return boolean
  322. */
  323. public function addAlipayResponse($response)
  324. {
  325. $this->alipay_response = Json::encode($response);
  326. return $this->save();
  327. }
  328. /**
  329. * 添加用户订单
  330. *
  331. * @param string $batch_num
  332. * @param float $fee
  333. * @param UserWithdrawLog $withdraw_log
  334. * @return TaxOrder|false
  335. */
  336. public static function addUserOrder(
  337. string $batch_num,
  338. int $account_id,
  339. float $fee,
  340. UserWithdrawLog $withdraw_log
  341. )
  342. {
  343. $extra = Json::decode($withdraw_log->extra);
  344. $name = $extra['name'];
  345. $mobile = $extra['mobile'];
  346. $model = new static();
  347. $model->mall_id = $withdraw_log->mall_id;
  348. $model->account_id = $account_id;
  349. $model->withdraw_id = $withdraw_log->id;
  350. $model->title = '商城销售分润';
  351. $model->batch_num = $batch_num;
  352. $model->order_sn = $withdraw_log->order_no . '_' . rand(100, 999);
  353. $model->name = (string)$name;
  354. $model->mobile = (string)$mobile;
  355. $model->amount = $withdraw_log->actual_num;
  356. $model->pay_fee = $fee;
  357. $model->user_id = $withdraw_log->user_id;
  358. $model->type = OrderTypeEnum::TYPE_2;
  359. $model->sub_type = TaxOrder::SUB_TYPE_USER;
  360. $model->remarks = '';
  361. return $model->save() ? $model : false;
  362. }
  363. /**
  364. * 添加手续费订单
  365. *
  366. * @param string $batch_num
  367. * @param float $fee
  368. * @param UserWithdrawLog $withdraw_log
  369. * @param TaxConfigService $service
  370. * @return false|static
  371. */
  372. public static function addTaxOrder(
  373. string $batch_num,
  374. int $account_id,
  375. float $fee,
  376. UserWithdrawLog $withdraw_log,
  377. TaxConfigService $service
  378. )
  379. {
  380. $model = new static();
  381. $config = $service->getTaxConfig();
  382. $model->mall_id = $withdraw_log->mall_id;
  383. $model->account_id = $account_id;
  384. $model->withdraw_id = $withdraw_log->id;
  385. $model->title = '商城销售分润';
  386. $model->batch_num = $batch_num;
  387. $model->order_sn = TaxOrder::TAX_ORDER_PRE . $withdraw_log->order_no . '_' . rand(100, 999);
  388. $model->name = $config['taxAccountName'];
  389. $model->mobile = $config['taxAccount'];
  390. $model->amount = $fee;
  391. $model->type = OrderTypeEnum::TYPE_2;
  392. $model->sub_type = TaxOrder::SUB_TYPE_TAX;
  393. $model->remarks = '手续费';
  394. return $model->save() ? $model : false;
  395. }
  396. /**
  397. * @param string $batch_num
  398. * @return static|array|null a single row of query result. Depending on the setting of [[asArray]],
  399. */
  400. public static function getFeeDeatilByNum(string $batch_num)
  401. {
  402. return static::find()
  403. ->where(['batch_num' => $batch_num, 'sub_type' => static::SUB_TYPE_TAX])
  404. ->one();
  405. }
  406. /**
  407. * @param string $batch_num
  408. * @return static|array|null a single row of query result. Depending on the setting of [[asArray]],
  409. */
  410. public static function getOrderDetailByNum(string $batch_num)
  411. {
  412. return static::find()
  413. ->where(['batch_num' => $batch_num, 'sub_type' => static::SUB_TYPE_USER])
  414. ->one();
  415. }
  416. /**
  417. * 统计
  418. * @Author: lun
  419. * @DateTime: 2023/6/9 0009 11:32
  420. * @Copyright: copyright (c) 2021 广东七件事集团
  421. * @param $mall_id
  422. * @param array $date 日期选择
  423. * @return array
  424. */
  425. public static function statistic($mall_id, $date = [])
  426. {
  427. // 用户实际提现金额(未被平台扣除手续费)
  428. $withdraw = UserWithdrawLog::find()->where(['mall_id' => $mall_id, 'type' => 'alipay_tax', 'status' => UserWithdrawLog::STATUS_ALREADY_TRANS]);
  429. if (!empty($date)) {
  430. $withdraw->andWhere(['>=', 'created_at', strtotime($date[0])]);
  431. $withdraw->andWhere(['<=', 'created_at', strtotime($date[1])]);
  432. }
  433. $withdraw_total = $withdraw->sum('num');
  434. // 打款成功统计
  435. $success = self::find()->select('sum(amount) amount_total,sum(pay_fee) pay_fee_total')->where([
  436. 'mall_id' => $mall_id,
  437. 'sub_type' => self::SUB_TYPE_USER,
  438. 'status' => StatusEnum::STATUS_OUT_OK,
  439. ]);
  440. if (!empty($date)) {
  441. $success->andWhere(['>=', 'created_at', strtotime($date[0])]);
  442. $success->andWhere(['<=', 'created_at', strtotime($date[1])]);
  443. }
  444. $success_total = $success->asArray()->one();
  445. // 实发服务费
  446. $fee = self::find()->where([
  447. 'mall_id' => $mall_id,
  448. 'sub_type' => self::SUB_TYPE_TAX,
  449. 'status' => StatusEnum::STATUS_OUT_OK,
  450. ]);
  451. if (!empty($date)) {
  452. $fee->andWhere(['>=', 'created_at', strtotime($date[0])]);
  453. $fee->andWhere(['<=', 'created_at', strtotime($date[1])]);
  454. }
  455. $fee_total = $fee->sum('amount') ?? 0;
  456. return [
  457. 'payment_total' => $success_total['amount_total'] ?? 0,
  458. 'payment_fee_total' => $success_total['pay_fee_total'] ?? 0,
  459. 'fee_total' => $fee_total,
  460. 'withdraw_total' => $withdraw_total,
  461. ];
  462. }
  463. /**
  464. * 导出字段
  465. * @Author: lun
  466. * @DateTime: 2023/6/9 0009 15:24
  467. * @Copyright: copyright (c) 2021 广东七件事集团
  468. * @param null $field
  469. * @return array|string[]
  470. */
  471. public static function fieldsList($sub_type = self::SUB_TYPE_USER, $field = null){
  472. $fields = [
  473. 'account_id' => '记账方ID',
  474. 'user_id' => '用户ID',
  475. 'nickname' => '用户昵称',
  476. 'user_mobile' => '用户手机号',
  477. 'batch_num' => '批次号',
  478. 'order_sn' => '订单号',
  479. 'name' => '收款名称',
  480. 'mobile' => '收款账户',
  481. 'amount' => '金额',
  482. 'pay_fee' => '手续费',
  483. 'remarks' => '备注',
  484. 'status' => '状态',
  485. 'error' => '错误信息',
  486. 'created_at' => '时间',
  487. ];
  488. if ($sub_type == self::SUB_TYPE_TAX) {
  489. unset($fields['user_id'], $fields['nickname'], $fields['user_mobile'], $fields['pay_fee']);
  490. }
  491. if ($field === null) return $fields;
  492. $temp = [];
  493. foreach ($field as $val) {
  494. if (isset($fields[$val])) $temp[$val] = $fields[$val];
  495. }
  496. return $temp;
  497. }
  498. /**
  499. * 批量导出
  500. * @Author: lun
  501. * @DateTime: 2023/6/9 0009 15:24
  502. * @Copyright: copyright (c) 2021 广东七件事集团
  503. * @param array $data
  504. * @return bool|string
  505. */
  506. public static function exportHandle(array $data) {
  507. date_default_timezone_set('Asia/Shanghai');
  508. $download_id = $data['download_id'] ?? 0;
  509. $download = Download::findOne(['id' => $download_id]);
  510. if ($download) {
  511. try {
  512. $params = json_decode($download->params,true);
  513. $filename = $download->name;
  514. $type = $download->type;
  515. $model = self::find();
  516. $model->where(['mall_id' => $params['mall_id']]);
  517. if(!empty($params['sub_type'])) $model->andWhere(['sub_type' => $params['sub_type']]);
  518. if(!empty($params['status'])) $model->andWhere(['status' => $params['status']]);
  519. if(!empty($params['batch_num'])) $model->andWhere(['batch_num' => $params['batch_num']]);
  520. if(!empty($params['order_sn'])) $model->andWhere(['order_sn' => $params['order_sn']]);
  521. if(!empty($params['name'])) $model->andWhere(['name' => $params['name']]);
  522. if(!empty($params['mobile'])) $model->andWhere(['mobile' => $params['mobile']]);
  523. if (!empty($params['date'][0])) {
  524. $model->andWhere(['>=', 'created_at', strtotime($params['date'][0])]);
  525. }
  526. if (!empty($params['date'][1])) {
  527. $model->andWhere(['<=', 'created_at', strtotime($params['date'][1])]);
  528. }
  529. $model->with(['user' => function($query) {
  530. $query->select('id,nickname,username,mobile');
  531. }]);
  532. $fields = $params['fields'];
  533. $status_map = StatusEnum::getMap();
  534. $fields_arr = self::fieldsList();
  535. $have_select_field_arr = [];
  536. foreach ($fields as $value){
  537. if(isset($fields_arr[$value])) $have_select_field_arr[] = $fields_arr[$value];
  538. }
  539. $csv = new CsvTool();
  540. $csv->filename = $filename;
  541. $csv->file_dirname = DownloadEnum::getTypeStorageDirMap($type);
  542. $csv->export_mode = CsvTool::EXPORT_MODE_ASYNC;
  543. $csv->header = $have_select_field_arr;
  544. foreach ($model->batch() as $detail) {
  545. $csv->data = array_map(function($detail)use($fields, $status_map){
  546. $row = [];
  547. foreach ($fields as $field){
  548. switch (true){
  549. case (in_array($field,['created_at'])):
  550. $row[] = !empty($detail[$field]) ? date('Y-m-d H:i:s', $detail[$field]) : '';
  551. break;
  552. case ($field=='status'):
  553. $row[] = !empty($status_map[$detail[$field]]) ? $status_map[$detail[$field]] : '';
  554. break;
  555. case ($field=='user_mobile'):
  556. $row[] = $detail['user']['mobile'] ??'';
  557. break;
  558. case ($field=='nickname'):
  559. $row[] = $detail['user']['nickname'] ??'';
  560. break;
  561. default:
  562. $row[] = !empty($detail[$field]) ? $detail[$field] : '';
  563. }
  564. }
  565. return $row;
  566. }, $detail);
  567. $csv->export();
  568. }
  569. $csv->updateDown($download,$params['mall_id']);
  570. return true;
  571. } catch (\Exception $e) {
  572. $download->status = 3;
  573. $download->save();
  574. echo '导出失败:'.$e->getMessage().$e->getFile().$e->getLine();
  575. return true;
  576. }
  577. }
  578. }
  579. }