OrderRefund.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2017~2021 https://www.yiovo.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
  8. // +----------------------------------------------------------------------
  9. // | Author: 萤火科技 <admin@yiovo.com>
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace app\api\model;
  13. use app\api\model\Setting as SettingModel;
  14. use app\api\service\User as UserService;
  15. use app\api\model\OrderGoods as OrderGoodsModel;
  16. use app\common\model\order\RefundHis as RefundHisModel;
  17. use app\common\model\order\RefundHis;
  18. use app\common\model\OrderRefund as OrderRefundModel;
  19. use app\common\enum\order\refund\RefundType as RefundTypeEnum;
  20. use app\common\enum\order\refund\AuditStatus as AuditStatusEnum;
  21. use app\common\enum\order\refund\RefundStatus as RefundStatusEnum;
  22. use app\common\enum\order\refund\FinanceRefundStatus as FinanceRefundStatusEnum;
  23. use app\common\exception\BaseException;
  24. use app\common\service\Kuaidi as KuaidiService;
  25. use app\common\model\Express as ExpressModel;
  26. use app\store\model\OrderGoods;
  27. /**
  28. * 售后单模型
  29. * Class OrderRefund
  30. * @package app\api\model
  31. */
  32. class OrderRefund extends OrderRefundModel
  33. {
  34. /**
  35. * 隐藏字段
  36. * @var array
  37. */
  38. protected $hidden = [
  39. 'store_id',
  40. 'update_time'
  41. ];
  42. /**
  43. * 追加字段
  44. * @var array
  45. */
  46. protected $append = [
  47. 'state_text', // 售后单状态
  48. 'dfh_time', // 待用户发货倒计时
  49. ];
  50. /**
  51. * 售后单状态文字描述
  52. * @param $value
  53. * @param $data
  54. * @return string
  55. */
  56. public function getStateTextAttr($value, $data)
  57. {
  58. if($data['delivery_type']==10){
  59. // 已完成
  60. if ($data['status'] == RefundStatusEnum::COMPLETED) {
  61. return ['value'=>1,'name'=>'退款成功'];
  62. }
  63. // 已取消
  64. if ($data['status'] == RefundStatusEnum::CANCELLED) {
  65. return ['value'=>2,'name'=>'已取消'];
  66. }
  67. // 已拒绝
  68. if ($data['status'] == RefundStatusEnum::REJECTED) {
  69. return ['value'=>3,'name'=>'平台已拒绝'];
  70. }
  71. //已关闭 -用户超时未退货,系统自动关闭售后单
  72. if ($data['status'] == RefundStatusEnum::CLOSE) {
  73. return ['value'=>7,'name'=>'已关闭'];
  74. }
  75. // 进行中
  76. if ($data['status'] == RefundStatusEnum::NORMAL) {
  77. //退货退款
  78. if ($data['type'] == RefundTypeEnum::RETURN) {
  79. if (isset($data['audit_status_zg'])&&$data['audit_status_zg'] == AuditStatusEnum::WAIT) {
  80. return ['value'=>0,'name'=>'待审核'];
  81. }
  82. if(isset($data['is_user_send'])&&$data['is_user_send']){ //已发货
  83. if($data['is_receipt']==1){//已收货
  84. return ['value'=>6,'name'=>'待退款'];
  85. }
  86. if ($data['is_receipt']==2) {
  87. return ['value'=>8,'name'=>'仓库拒绝收货'];
  88. }else{
  89. return ['value'=>5,'name'=>'待仓库收货'];
  90. }
  91. }else{
  92. return ['value'=>4,'name'=>'待买家退货'];
  93. }
  94. }else{
  95. if (isset($data['audit_status'])&&$data['audit_status'] == AuditStatusEnum::WAIT) { //仅退款不需要主管审核
  96. return ['value'=>0,'name'=>'待审核'];
  97. }
  98. if($data['finance_refund'] == FinanceRefundStatusEnum::WAIT){
  99. return ['value'=>6,'name'=>'待退款'];
  100. }
  101. }
  102. }
  103. }
  104. if($data['delivery_type']==20){
  105. if ($data['status'] == RefundStatusEnum::COMPLETED) {
  106. return ['value'=>1,'name'=>'退款成功'];
  107. }
  108. //已关闭 -用户超时未退货,系统自动关闭售后单
  109. if ($data['status'] == RefundStatusEnum::CLOSE) {
  110. return ['value'=>7,'name'=>'已关闭'];
  111. }
  112. if ($data['status'] == RefundStatusEnum::NORMAL) {
  113. if (isset($data['audit_status'])&&$data['audit_status'] == AuditStatusEnum::WAIT) {
  114. return ['value'=>0,'name'=>'待审核'];
  115. }
  116. if ($data['audit_status'] == AuditStatusEnum::REVIEWED&&$data['finance_refund']==0) {
  117. return ['value'=>6,'name'=>'待退款'];
  118. }
  119. }
  120. }
  121. return $value;
  122. }
  123. /**
  124. * 待用户发货倒计时时间戳
  125. * @param $value
  126. * @param $data
  127. * @return int
  128. * @author: zjwhust
  129. * @Time: 2021/10/8 17:06
  130. */
  131. public function getDfhTimeAttr($value,$data){
  132. if (
  133. $data['type'] == RefundTypeEnum::RETURN
  134. && $data['audit_status'] == AuditStatusEnum::REVIEWED
  135. && $data['audit_status_zg'] == AuditStatusEnum::REVIEWED
  136. && $data['is_user_send'] == 0
  137. && $data['status'] == RefundStatusEnum::NORMAL
  138. ) {
  139. $now = time();
  140. $delay_time = 86400 * 7;//获取待发货时间
  141. if(env('SERVE_ENV')=='test'){
  142. $delay_time = 300;//测试超时退款5分钟
  143. }
  144. if(($now-$data['approved_time'])<$delay_time){ //
  145. return $data['approved_time']+$delay_time-$now;
  146. }
  147. }
  148. return 0;
  149. }
  150. /**
  151. * 获取用户售后单列表
  152. * @param int $state 售后单状态 -1为全部
  153. * @return \think\Paginator
  154. * @throws \app\common\exception\BaseException
  155. * @throws \think\db\exception\DbException
  156. */
  157. public function getList(int $state = -1)
  158. {
  159. // 检索查询条件
  160. $filter = [];
  161. // 售后单状态
  162. $state > -1 && $filter[] = ['status', '=', $state];
  163. // 当前用户ID
  164. $userId = UserService::getCurrentLoginUserId();
  165. // 查询列表记录
  166. return $this->with(['orderGoods.image', 'orderGoodsAll.image'])
  167. ->where($filter)
  168. ->where('user_id', '=', $userId)
  169. ->order(['create_time' => 'desc'])
  170. ->paginate(15)
  171. ->each(function($item) {
  172. if (!$item['order_goods_id']) {
  173. // 整单退款
  174. $orderGoods = $item['orderGoodsAll'];
  175. } else {
  176. $orderGoods = [$item['orderGoods']];
  177. }
  178. unset($item['orderGoods'], $item['orderGoodsAll']);
  179. $item['orderGoods'] = $orderGoods;
  180. });
  181. }
  182. /**
  183. * 获取数量
  184. * @return int
  185. * @throws BaseException
  186. */
  187. public function getCount()
  188. {
  189. // 当前用户ID
  190. $userId = UserService::getCurrentLoginUserId();
  191. return $this->where('user_id', '=', $userId)
  192. ->where('status', '=', 0)
  193. ->count();
  194. }
  195. /**
  196. * 获取当前用户的售后单详情
  197. * @param int $orderRefundId 售后单ID
  198. * @param bool $isWith 是否关联
  199. * @return static|null
  200. * @throws BaseException
  201. */
  202. public static function getDetail(int $orderRefundId, $isWith = false)
  203. {
  204. // 关联查询
  205. $with = $isWith ? ['orderGoods' => ['image'], 'orderGoodsAll' => ['image'], 'address','shops'] : [];
  206. // 获取记录
  207. $detail = static::detail([
  208. 'user_id' => UserService::getCurrentLoginUserId(),
  209. 'order_refund_id' => $orderRefundId
  210. ], $with);
  211. // $detail = static::where("order_refund_id",$orderRefundId)->find();
  212. if (empty($detail)) throwError('未找到该售后单');
  213. if (!$detail['order_goods_id']) {
  214. // 整单退款
  215. $orderGoods = $detail['orderGoodsAll'];
  216. } else {
  217. $orderGoods = [$detail['orderGoods']];
  218. }
  219. unset($detail['orderGoods'], $detail['orderGoodsAll']);
  220. $detail['orderGoods'] = $orderGoods;
  221. return $detail;
  222. }
  223. /**
  224. * 订单商品详情
  225. * @param int $orderGoodsId 订单商品ID
  226. * @return \app\common\model\OrderGoods|null
  227. * @throws BaseException
  228. */
  229. public function getRefundGoods(int $orderGoodsId)
  230. {
  231. $goods = OrderGoodsModel::detail($orderGoodsId);
  232. if (isset($goods['refund']) && !empty($goods['refund'])) {
  233. throwError('当前商品已申请售后');
  234. }
  235. return $goods;
  236. }
  237. /**
  238. * 用户撤销申请
  239. * @param $data
  240. * @return false|int
  241. */
  242. public function revoke()
  243. {
  244. $user = UserService::getCurrentLoginUser(true);
  245. $type = false;
  246. if($this['status'] == RefundStatusEnum::REJECTED){//已拒绝
  247. $type = true;
  248. }else{
  249. if($this['status'] == RefundStatusEnum::NORMAL){//进行中&待审核
  250. if($this['delivery_type']==10){
  251. if ($this['type'] == RefundTypeEnum::RETURN) {
  252. if ($this['audit_status_zg'] == AuditStatusEnum::WAIT) {
  253. $type = true;
  254. }
  255. }else{
  256. if ($this['audit_status'] == AuditStatusEnum::WAIT) { //仅退款不需要主管审核
  257. $type = true;
  258. }
  259. }
  260. }
  261. //自提单 当门店未审核时可以撤回
  262. if($this['delivery_type']==20){
  263. if ($this['audit_status'] == AuditStatusEnum::WAIT) { //仅退款不需要主管审核
  264. $type = true;
  265. }
  266. }
  267. }
  268. }
  269. if($type){
  270. return $this->transaction(function () use ($user) {
  271. if($this['delivery_type']==10){
  272. $this->save([
  273. 'status' => RefundStatusEnum::CANCELLED,
  274. ]);
  275. }
  276. //门店自提单自动关闭
  277. if($this['delivery_type']==20){
  278. $this->save([
  279. 'status' => RefundStatusEnum::CLOSE,
  280. ]);
  281. }
  282. //订单商品解冻
  283. OrderGoodsModel::updateBase(['frozen_status'=>0], ['order_goods_id'=>$this['order_goods_id']]);
  284. $json_str = json_encode(["买家已撤销申请,退款已关闭"]);
  285. (new RefundHisModel())->add($this['order_refund_id'],$user['nick_name'],4,$json_str,'','买家','撤销申请');
  286. return true;
  287. });
  288. }
  289. $this->error = '当前售后单不合法,不允许该操作';
  290. return false;
  291. }
  292. /**
  293. * 用户发货
  294. * @param $data
  295. * @return false|int
  296. */
  297. public function delivery(array $data)
  298. {
  299. if (
  300. $this['type'] != RefundTypeEnum::RETURN
  301. || $this['audit_status_zg'] != AuditStatusEnum::REVIEWED
  302. || $this['is_user_send'] != 0
  303. ) {
  304. $this->error = '当前售后单不合法,不允许该操作';
  305. return false;
  306. }
  307. if ($data['expressId'] <= 0) {
  308. $this->error = '请选择物流公司';
  309. return false;
  310. }
  311. if (empty($data['expressNo'])) {
  312. $this->error = '请填写物流单号';
  313. return false;
  314. }
  315. if (empty($data['expressCompany'])) {
  316. $this->error = '请填写物流公司名称';
  317. return false;
  318. }
  319. if (isset($data['expressDesc']) && mb_strlen($data['expressDesc'])>50) {
  320. $this->error = '退货字数上限超过了50字';
  321. return false;
  322. }
  323. $express = ExpressModel::detail($data['expressId']);
  324. // 请求快递100订阅接口
  325. $subscribe = KuaidiService::subscribe($data['expressNo'], 2,$express['kuaidi100_code']??'');
  326. if(!$subscribe->result){
  327. if(stripos($subscribe->message,"重复订阅") === false){ //重复订阅允许提交物流
  328. $this->error = '物流单号订阅异常:'.$subscribe->message;
  329. return false;
  330. }
  331. }
  332. $user = UserService::getCurrentLoginUser(true);
  333. return $this->transaction(function () use ($data,$user) {
  334. $this->save([
  335. 'is_user_send' => 1,
  336. 'send_time' => time(),
  337. 'express_id' => (int)$data['expressId'],
  338. 'express_no' => $data['expressNo'],
  339. 'express_company' => $data['expressCompany'],
  340. 'express_desc' => $data['expressDesc']??'',
  341. ]);
  342. $json_str = json_encode(["买家寄回商品","快递公司:".$data['expressCompany'],"物流单号:".$data['expressNo']]);
  343. (new RefundHisModel())->add($this['order_refund_id'],$user['nick_name'],7,$json_str,'','买家','买家填写物流单号');
  344. return true;
  345. });
  346. }
  347. /**
  348. * 门店审核售后单
  349. * @param $data
  350. * @return false|int
  351. */
  352. public function auditStatus($param,$user){
  353. // 验证订单是否合法
  354. // 条件1: 门店审核状态不在审核中
  355. // 条件2: 售后单状态不在进行中
  356. if ($this['audit_status'] != 0 || $this['status'] != 0) {
  357. $this->error = '该售后单已经处理';
  358. return false;
  359. }
  360. return $this->transaction(function () use ($param,$user) {
  361. // 更新售后单状态
  362. $status = $this->save([
  363. 'audit_status' => $param['audit_status'],
  364. 'refuse_desc' => $param['audit_status']==20 ? $param['refuse_desc'] : '',
  365. 'refuse_images' => $param['audit_status']==20 ? $param['refuse_images']??'' : '',
  366. 'status' => $param['audit_status']==20 ? 40 : 0,
  367. 'hx_refund_time' => time(),
  368. 'hx_refund_user_id' => UserService::getCurrentLoginUserId(),
  369. 'is_user_send' => 1,
  370. 'is_receipt' => 1,
  371. ]);
  372. if($this['order_goods_id']>0) {
  373. // OrderGoodsModel::where('order_goods_id', $this['order_goods_id'])->update(['frozen_status' => 0]);
  374. }
  375. if($param['audit_status']==20){
  376. $json_str = json_encode(["处理结果:拒绝","拒绝理由:".$param['refuse_desc'],"门店拒绝退货,退款单关闭"]);
  377. $his_type = 13;
  378. $name = $this['shops']['shop_name'];
  379. $pf_operator = $user['nick_name']."(".$user['mobile'].")";
  380. (new RefundHis())->add($this['order_refund_id'],$name,$his_type,$json_str,$param['refuse_images'],$pf_operator,'拒绝并关闭退货单');
  381. }else{
  382. $json_str = json_encode(["处理结果:同意","门店同意退货,等待财务退款"]);
  383. $his_type = 12;
  384. $name = $this['shops']['shop_name'];
  385. $pf_operator = $user['nick_name']."(".$user['mobile'].")";
  386. (new RefundHis())->add($this['order_refund_id'],$name,$his_type,$json_str,'',$pf_operator,'同意退货并确认收货');
  387. }
  388. return $status;
  389. });
  390. }
  391. /**
  392. * 新增售后单记录
  393. * @param int $orderGoodsId 订单商品ID
  394. * @param array $data 用户提交的表单数据
  395. * @return mixed
  396. * @throws BaseException
  397. */
  398. public function apply(int $orderGoodsId, array $data)
  399. {
  400. // 订单商品详情
  401. $goods = $this->getRefundGoods($orderGoodsId);
  402. return $this->transaction(function () use ($orderGoodsId, $data, $goods) {
  403. // 新增售后单记录
  404. $this->save([
  405. 'order_goods_id' => $orderGoodsId,
  406. 'order_id' => $goods['order_id'],
  407. 'user_id' => UserService::getCurrentLoginUserId(),
  408. 'type' => $data['type'],
  409. 'apply_desc' => $data['content'],
  410. 'audit_status' => AuditStatusEnum::WAIT,
  411. 'status' => 0,
  412. 'store_id' => self::$storeId
  413. ]);
  414. // 记录凭证图片关系
  415. if (isset($data['images']) && !empty($data['images'])) {
  416. $this->saveImages((int)$this['order_refund_id'], $data['images']);
  417. }
  418. return true;
  419. });
  420. }
  421. /**
  422. * 记录售后单图片
  423. * @param int $orderRefundId 售后单ID
  424. * @param array $images 图片列表
  425. * @return bool
  426. */
  427. private function saveImages(int $orderRefundId, array $images)
  428. {
  429. // 生成评价图片数据
  430. $data = [];
  431. foreach ($images as $imageId) {
  432. $data[] = [
  433. 'order_refund_id' => $orderRefundId,
  434. 'image_id' => $imageId,
  435. 'store_id' => self::$storeId
  436. ];
  437. }
  438. return !empty($data) && (new OrderRefundImage)->addAll($data) !== false;
  439. }
  440. }