UserOrder.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <?php
  2. namespace app\common\model;
  3. use app\service\pay_user\PayUser;
  4. use think\db\Query;
  5. use think\Model;
  6. use think\model\Collection;
  7. use traits\model\SoftDelete;
  8. /**
  9. *@property array send_detail 运送详情
  10. *@property float cal_refund 退款手续费
  11. *@property float refund_money 退款金额
  12. *@property Collection currentSenderOrder
  13. *@property float settle_amount
  14. *@property array senders
  15. *@property array settle_info
  16. *@property float profit
  17. *@property boolean is_same_city
  18. *@property User user
  19. *@method Query|self expired()
  20. */
  21. class UserOrder extends Model
  22. {
  23. use SoftDelete;
  24. protected $hidden=[
  25. 'status_bak',
  26. 'deleted_at',
  27. ];
  28. protected $autoWriteTimestamp=true;
  29. protected $createTime = 'created_at';
  30. protected $updateTime = 'updated_at';
  31. protected $deleteTime='deleted_at';
  32. protected $type=[
  33. 'has_cage'=>'boolean',
  34. 'from_addr'=>'json',
  35. 'to_addr'=>'json',
  36. 'refund_images'=>'array',
  37. ];
  38. const FREIGHT_FAST='fast';#快车
  39. const FREIGHT_AIR='air';#空运
  40. const FREIGHT_SPECIAL='special';#专车
  41. public static $freights=[
  42. self::FREIGHT_FAST=>'快车',
  43. self::FREIGHT_AIR=>'空运',
  44. self::FREIGHT_SPECIAL=>'专车',
  45. ];
  46. const STATUS_WAIT_PAY=0;
  47. const STATUS_WAIT_GET=1;
  48. const STATUS_GETTING=10;
  49. const STATUS_SENDING=20;
  50. const STATUS_GIVING=30;
  51. const STATUS_GIVED=40;
  52. const STATUS_COMPLETE=50;
  53. const STATUS_SET=60;
  54. const STATUS_SET_REJECT=65;
  55. const STATUS_REFUND=70;
  56. const STATUS_CANCEL=80;
  57. public static $statusList=[
  58. self::STATUS_WAIT_PAY=>'待支付',
  59. self::STATUS_WAIT_GET=>'待接单',
  60. self::STATUS_GETTING=>'已接单,取宠中',
  61. self::STATUS_SENDING=>'已取宠,运输中',
  62. self::STATUS_GIVING=>'送宠中',
  63. self::STATUS_GIVED=>'送宠完成,待用户确认',
  64. self::STATUS_COMPLETE=>'已完成,待结算',
  65. self::STATUS_SET=>'已结算',
  66. self::STATUS_SET_REJECT=>'已拒绝结算',
  67. self::STATUS_REFUND=>'退款处理中',
  68. self::STATUS_CANCEL=>'已取消',
  69. ];
  70. const REFUND_STATUS_DEFAULT=0;
  71. const REFUND_STATUS_PASS=1;
  72. const REFUND_STATUS_REJECT=2;
  73. public static $refundStatus=[
  74. self::REFUND_STATUS_DEFAULT=>'审核中',
  75. self::REFUND_STATUS_PASS=>'审核通过',
  76. self::REFUND_STATUS_REJECT=>'驳回',
  77. ];
  78. protected $append=[
  79. 'protect_valid',
  80. ];
  81. public function user(){
  82. return $this->belongsTo(User::class);
  83. }
  84. public function log(){
  85. return $this->hasMany(UserOrderLog::class,'order_id')->order('id','desc');
  86. }
  87. public static function payed($params){
  88. $id=$params['id'];
  89. $order=self::find($id);
  90. $order['pay_time']=time();
  91. $order['status']=self::STATUS_WAIT_GET;
  92. $order->save();
  93. }
  94. public function setImagesAttr($v){
  95. if(!$v){
  96. $v=[];
  97. }
  98. return json_encode($v);
  99. }
  100. public function getImagesAttr($v){
  101. return array_filter(json_decode($v));
  102. }
  103. /**
  104. * 取消
  105. *user.type 1未支付2驳回3退款
  106. *user.reason
  107. */
  108. const CT_NOT_PAY=1;
  109. const CT_REJECT=2;
  110. const CT_REFUND=3;
  111. public function cancel($type=self::CT_NOT_PAY,$force=false){
  112. if($this['status']!==self::STATUS_WAIT_PAY && !$force){
  113. throw_user('非待付款订单无法取消');
  114. }
  115. $this['status']=self::STATUS_CANCEL;
  116. $this['cancel_at']=time();
  117. $this['cancel_type']=$type;
  118. $this->save();
  119. if($this['coupon_id']){
  120. UserCoupon::setUse($this['coupon_id'],0);
  121. }
  122. }
  123. public function pay(){
  124. if($this['status']!=self::STATUS_WAIT_PAY){
  125. throw_user('状态有误');
  126. }
  127. if($this['expired_at']<time()){
  128. $this->cancel();
  129. throw_user('该订单已超时');
  130. }
  131. if($this['pay_type']==1){
  132. $params='';
  133. $this->user->money(bcsub(0,$this['real_amount'],2),$this['user_id'],MoneyLog::TYPE_ORDER_PAY,"订单[{$this['no']}]付款");
  134. self::payed(['id'=>$this['id']]);
  135. }else {
  136. $params = Payment::pay($this->user, $this['real_amount'], self::class, 'payed', ['id' => $this['id']]);
  137. }
  138. return $params;
  139. }
  140. public function complete(){
  141. $this->refunding();
  142. if($this['status']!=self::STATUS_GIVED){
  143. throw_user('非已送达状态无法确认完成');
  144. }
  145. $this['status']=self::STATUS_COMPLETE;
  146. $this['completed_at']=time();
  147. if(!$this->save()){
  148. throw_user('保存状态失败');
  149. }
  150. }
  151. public function refund($reason,$images){
  152. if(!in_array($this['status'],[self::STATUS_WAIT_GET,self::STATUS_GETTING,self::STATUS_SENDING])){
  153. throw_user('当前无法申请退款');
  154. }
  155. $this['refund_reason']=$reason;
  156. $this['refund_apply_at']=time();
  157. $this['refund_amount']=$this->refund_money;
  158. $this['refund_images']=$images;
  159. $this['refund_status']=self::REFUND_STATUS_DEFAULT;
  160. $this['status_bak']=$this['status'];
  161. $this['status']=self::STATUS_REFUND;
  162. if(!$this->save()){
  163. throw_user('保存失败');
  164. }
  165. }
  166. public function refundCancel(){
  167. if(!in_array($this['refund_status'],[self::REFUND_STATUS_DEFAULT])){
  168. throw_user('当前无法取消申请退款');
  169. }
  170. $this['refund_status']=-1;
  171. $this['status']=$this['status_bak'];
  172. if(!$this->save()){
  173. throw_user('保存失败');
  174. }
  175. }
  176. public function refunding(bool $throw=true){
  177. $is=in_array($this['refund_status'],[self::REFUND_STATUS_DEFAULT,self::REFUND_STATUS_PASS]);
  178. if($throw && $is){
  179. throw_user('当前正在申请退款或已退款,您无法操作');
  180. }
  181. return $is;
  182. }
  183. /**
  184. *处理退款申请
  185. * @status 1同意2拒绝
  186. */
  187. public function dealRefund($data){
  188. if($data['status']==1){
  189. $this['refund_status']=self::REFUND_STATUS_PASS;
  190. $this['refund_at']=time();
  191. PayUser::getClass()->payment($this['refund_amount'],$this['user'],MoneyLog::TYPE_REFUND,"订单退款",['order_id'=>$this['id']]);
  192. $this->cancel(self::CT_REFUND,true);
  193. }elseif($data['status']==2){
  194. $this['refund_status']=self::REFUND_STATUS_REJECT;
  195. $this['status']=$this['status_bak'];
  196. }
  197. if(!$this->save()){
  198. throw_user('保存失败');
  199. }
  200. }
  201. /**
  202. *处理结算申请
  203. * @status 1同意2拒绝
  204. */
  205. public function dealSettle($data){
  206. if($this['status']!=self::STATUS_COMPLETE){
  207. throw_user(sprintf('非待结算订单无法结算[%s]',self::$statusList[$this['status']]));
  208. }
  209. if($data['status']==1){
  210. $this['status']=self::STATUS_SET;
  211. foreach ($this->currentSenderOrder as $senderOrder){
  212. if(is_null($senderOrder['fee_total'])){
  213. throw_user('有配送员尚未申请结算,请稍后再试');
  214. }
  215. PayUser::getClass()->payment($senderOrder['fee_total'],$senderOrder['user'],MoneyLog::TYPE_SETTLE,"订单[{$this['no']}]结算",['order_id'=>$this['id']]);
  216. }
  217. #加一个盈利记录
  218. User::money($this->profit,0,MoneyLog::TYPE_PROFIT,"订单[{$this['no']}]盈利",['order_id'=>$this['id']],false);
  219. }elseif($data['status']==2){
  220. $this['status']=self::STATUS_SET_REJECT;
  221. }
  222. $this['settled_at']=time();
  223. if(!$this->save()){
  224. throw_user('保存失败');
  225. }
  226. }
  227. public function getCalRefundAttr($v,$data){
  228. if($this['freight']==self::FREIGHT_AIR){
  229. $fee=bcdiv(config('site.order_refund_air'),100,4);
  230. }else{
  231. $fee=bcdiv(config('site.order_refund_land'),100,4);
  232. }
  233. $amount=bcmul($this['real_amount'],$fee);
  234. return $amount;
  235. }
  236. public function getRefundMoneyAttr($v,$data){
  237. return bcsub($this['real_amount'],$this->cal_refund);
  238. }
  239. public function getSendDetailAttr(){
  240. $log=$this->log()->select();
  241. $res['addr']=$this['to_addr']['address'];
  242. $res['log']=$log;
  243. return $res;
  244. }
  245. public function remove(){
  246. if(!in_array($this['status'],[self::STATUS_CANCEL,self::STATUS_COMPLETE,self::STATUS_SET,self::STATUS_SET_REJECT])){
  247. throw_user('非已取消或完成订单无法删除');
  248. }
  249. if($this['user_deleted_at']){
  250. throw_user('该订单已删除');
  251. }
  252. $this['user_deleted_at']=time();
  253. $this->save();
  254. }
  255. public function submit($data){
  256. $this['status']=self::STATUS_WAIT_PAY;
  257. $res=$this->allowField(true)->save($data);
  258. if(!$res){
  259. throw_user('保存失败');
  260. }
  261. if($this['coupon_id']){
  262. UserCoupon::setUse($this['coupon_id']);
  263. }
  264. }
  265. /** 待接单 */
  266. public static function wait(User $user){
  267. $q=self::alias('a');
  268. $city=$user->area()->column('name');
  269. if(empty($city)){
  270. $q->where('a.id',0);
  271. return $q;
  272. }
  273. $q
  274. ->join('sender_order b','a.id=b.user_order_id and b.now=1','LEFT')
  275. ->whereIn('a.from_city|a.to_city',$city)
  276. ->whereBetween('a.status',[self::STATUS_WAIT_GET,self::STATUS_GIVING])
  277. ->group('a.id')
  278. ->having('COUNT(b.id)<2')
  279. ->order('a.id','desc');
  280. return $q;
  281. }
  282. public function getProtectValidAttr($v,$d){
  283. if(!isset($this['status'])){
  284. return false;
  285. }
  286. return $this['status']>=self::STATUS_WAIT_GET && $this['status']<=self::STATUS_GIVED;
  287. }
  288. public function senderOrder(){
  289. return $this->hasMany(SenderOrder::class);
  290. }
  291. /** 当前的配送员订单 */
  292. public function currentSenderOrder($user_id=null){
  293. $q=$this->senderOrder()->where('now',1);
  294. if($user_id){
  295. $q->where('user_id',$user_id);
  296. }
  297. return $q;
  298. }
  299. /** 是否能接单 */
  300. public function checkGet(User $user){
  301. if(count($this->currentSenderOrder)>1){
  302. throw_user('订单已被接单,您暂时无法接单(1)');
  303. }
  304. $type=$user->getSendType($this);
  305. #相同城市直接返回未接单的那个类型
  306. if($this->is_same_city && $this->currentSenderOrder->count()===1){
  307. $typeArr=$this->currentSenderOrder->column('type');
  308. $array = array_diff(User::$sendType, $typeArr);
  309. return end($array);
  310. }
  311. if($user->isSendGet($type)) {
  312. $can = $this['status'] == self::STATUS_WAIT_GET;
  313. if (!$can) {
  314. throw_user("订单已被接单,您暂时无法接单(2)");
  315. }
  316. }
  317. return $type;
  318. }
  319. /** 接单 */
  320. public function accept(User $user){
  321. $type=$this->checkGet($user);
  322. if($user->isSendGet($type)) {
  323. $this['status'] = self::STATUS_GETTING;
  324. }
  325. $sender_order=$this->senderOrder()->save([
  326. 'user_id'=>$user['id'],
  327. 'status' =>$this['status'],
  328. 'type' =>$type,
  329. 'now' =>1,
  330. 'get_at' =>time(),
  331. ]);
  332. if(!$this['get_at']){
  333. $this['get_at']=time();
  334. }
  335. if(!$sender_order){
  336. throw_user('保存配送员订单失败');
  337. }
  338. $this->save();
  339. }
  340. /** 更新状态 */
  341. public function updateStatus($status,User $user){
  342. if(!$status){
  343. throw_user('状态有误');
  344. }
  345. $this->refunding();
  346. $senderS=[
  347. self::STATUS_GETTING=>self::STATUS_SENDING,
  348. self::STATUS_SENDING=>self::STATUS_GIVING,
  349. ];
  350. $recS=[
  351. self::STATUS_GIVING=>self::STATUS_GIVED,
  352. ];
  353. $arr=$senderS+$recS;
  354. if($this['status']!=$status){
  355. throw_user('状态'.self::$statusList[$this['status']].'有误');
  356. }
  357. if(!$this->is_same_user) {
  358. $type = $user->getSendType($this);
  359. if ($user->isSendGet($type) && !isset($senderS[$status])) {
  360. throw_user('您是取宠人,无法更新状态' . self::$statusList[$status]);
  361. } elseif ($user->isSendSend($type) && !isset($recS[$status])) {
  362. throw_user('您是送宠人,无法更新状态' . self::$statusList[$status]);
  363. }
  364. }
  365. $this['status']=$arr[$status];
  366. if(!$this->save()){
  367. throw_user('更新失败');
  368. }
  369. return $this['status'];
  370. }
  371. /** 进行中的订单 */
  372. public function scopeRunning(Query $query){
  373. $query->whereIn('status',[
  374. self::STATUS_WAIT_GET,
  375. self::STATUS_GETTING,
  376. self::STATUS_SENDING,
  377. self::STATUS_GIVING,
  378. self::STATUS_GIVED,
  379. self::STATUS_REFUND,
  380. ]);
  381. }
  382. /** 计算收益 */
  383. public static function calcProfit($date=null){
  384. $profit=UserOrder::withTrashed()->profit($date)->select();
  385. return bcadd(array_sum($profit->column('profit')),0);
  386. }
  387. /** 结算金额 */
  388. public function getSettleAmountAttr($v,$data){
  389. $sender_order=$this->currentSenderOrder;
  390. $amount=0;
  391. foreach ($sender_order as $order){
  392. $amount=bcadd($amount,$order['fee_total']);
  393. }
  394. return $amount;
  395. }
  396. /** 配送员 */
  397. public function getSendersAttr($v,$data){
  398. $sender_order=$this->currentSenderOrder;
  399. foreach ($sender_order as $item){
  400. $item->append(['user']);
  401. }
  402. return array_column($sender_order->toArray(),null,'type');
  403. }
  404. /** 结算总的金额 */
  405. public function getSettleInfoAttr($v,$data){
  406. $arr=$this->senders;
  407. $total=[
  408. 'fee_total'=>0
  409. ];
  410. foreach ($arr as $item){
  411. for ($i=1;$i<=6;$i++) {
  412. $total["fee_$i"] = bcadd($total["fee_$i"]??0,$item["fee_$i"]);
  413. }
  414. $total['fee_total']=bcadd($total['fee_total']??0,$item['fee_total']);
  415. }
  416. return $total;
  417. }
  418. /** 结算总的金额 */
  419. public function getProfitAttr($v,$data){
  420. return bcsub($this['real_amount'],$this->settle_info['fee_total']);
  421. }
  422. /** 是否相同城市 */
  423. public function getIsSameCityAttr(){
  424. return $this['from_city']==$this['to_city'];
  425. }
  426. /** 是否相同接单人 */
  427. public function getIsSameUserAttr(){
  428. return $this->currentSenderOrder->count()==2 && count(array_unique($this->currentSenderOrder->column('user_id')))==1;
  429. }
  430. /** init */
  431. protected static function init()
  432. {
  433. self::beforeInsert(function (self $order){
  434. $order['no']=order_no();
  435. $order['expired_at']=strtotime("+10minutes");
  436. });
  437. self::afterInsert(function (self $order){
  438. if(!$order->user->address()->exists($order['from_addr'])->find()) {
  439. $addr=$order['from_addr'];
  440. $addr['user_id']=$order->user_id;
  441. (new UserAddress())->allowField(true)->save($addr);
  442. }
  443. });
  444. self::afterUpdate(function (self $order){
  445. #更新配送订单状态
  446. $data=$order->getChangedData();
  447. if(isset($data['status'])){
  448. $order->senderOrder()->update(['status'=>$data['status']]);
  449. }
  450. });
  451. }
  452. /** scope */
  453. /**
  454. * 待结算
  455. * @param Query $query
  456. */
  457. public function scopeSettle(Query $query){
  458. $query->where('status',self::STATUS_COMPLETE);
  459. }
  460. /**
  461. * 待处理退款
  462. * @param Query $query
  463. */
  464. public function scopeWaitRefund(Query $query){
  465. $query->where('status',self::STATUS_REFUND);
  466. }
  467. /**
  468. * 售后
  469. * @param Query $query
  470. */
  471. public function scopeService(Query $query){
  472. $query->where('refund_status','>',-1);
  473. }
  474. /**
  475. * 总营业额
  476. * @param Query $query
  477. * @param null|array $date
  478. */
  479. public function scopeTurnover(Query $query,$date='today'){
  480. $query->whereBetween($this->getTable().'.status', [self::STATUS_WAIT_GET, self::STATUS_SET]);
  481. if($date=='today') {
  482. $query->whereTime('created_at', $date);
  483. }elseif(is_array($date)){
  484. list($type,$time)=$date;
  485. if($type=='day'){
  486. $query->whereTime('created_at',$time);
  487. }elseif ($type=='month'){
  488. if (substr_count($time,'-')==1){
  489. $time="$time-01 00:00:00";
  490. }
  491. $query->whereBetween('created_at',[strtotime($time),strtotime("+1month",strtotime($time))]);
  492. }elseif ($type=='year'){
  493. $query->whereBetween('created_at',[strtotime("$time-01-01"),strtotime(($time+1).'-01-01')]);
  494. }
  495. }
  496. }
  497. /**
  498. * 盈利统计
  499. * @param null $date
  500. * @param bool $detail
  501. * @return string
  502. * @throws \think\db\exception\DataNotFoundException
  503. * @throws \think\db\exception\ModelNotFoundException
  504. * @throws \think\exception\DbException
  505. */
  506. public function scopeProfit(Query $query,$date=null){
  507. $profit=$query->alias('a')
  508. ->join('sender_order b','a.id=b.user_order_id and b.now=1')
  509. ->where('a.status',self::STATUS_SET)
  510. ->group('a.id')
  511. ->fieldRaw("(a.real_amount-SUM(b.fee_total)) AS profit");
  512. if(is_null($date)){
  513. $profit->whereTime('a.created_at','today');
  514. }else{
  515. list($type,$time)=$date;
  516. if($type=='day'){
  517. $query->whereTime('a.created_at',$time);
  518. }elseif ($type=='month'){
  519. if (substr_count($time,'-')==1){
  520. $time="$time-01 00:00:00";
  521. }
  522. $query->whereBetween('a.created_at',[strtotime($time),strtotime("+1month",strtotime($time))]);
  523. }elseif ($type=='year'){
  524. $query->whereBetween('a.created_at',[strtotime("$time-01-01"),strtotime(($time+1).'-01-01')]);
  525. }
  526. }
  527. }
  528. /** 已付款 */
  529. public function scopeWaitGet(Query $query){
  530. $query->where('status',self::STATUS_WAIT_GET);
  531. }
  532. /** 已完成 */
  533. public function scopeSuccess(Query $query){
  534. $query->whereIn('status',[
  535. self::STATUS_COMPLETE,
  536. ]);
  537. }
  538. /** 已结算 */
  539. public function scopeSettled(Query $query){
  540. $query->whereIn('status',[self::STATUS_SET,self::STATUS_SET_REJECT]);
  541. }
  542. /** 已取消 */
  543. public function scopeCanceled(Query $query){
  544. $query->where('status',self::STATUS_CANCEL)->show();
  545. }
  546. /** 已超时 */
  547. public function scopeExpired(Query $query,$limit=10){
  548. $query->where('expired_at','<',time())
  549. ->where('status',self::STATUS_WAIT_PAY)
  550. ->order('id','desc')
  551. ->limit($limit);
  552. }
  553. /** 已完成 */
  554. public function scopeOver(Query $query){
  555. $query
  556. ->show()
  557. ->whereBetween('status',[self::STATUS_COMPLETE,self::STATUS_SET_REJECT]);
  558. }
  559. /** 展示的 */
  560. public function scopeShow(Query $query){
  561. $query->whereNull('user_deleted_at');
  562. }
  563. }