UserOrder.php 17 KB

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