where(['mall_id' => $this->mall_id]); $model->with([ // 'detail' => function ($query) { // $query->select('id, order_id, goods_attr_name, price, goods_price, pic_url, goods_name'); // }, 'order' => function ($query) { $query->select('id, region_name, address, receiver_name, shipping_money, goods_price, mobile'); }, 'refund' => function ($query) { $query->select('id, order_no, reason, refund_price, pic_list, type, remark, refund_status'); }, ]); // 查询 if (!empty($params['name'])) { $model->andWhere([ 'order_id' => OrderDetail::find()->where(['like', 'goods_name', $params['name']])->select('order_id'), ]); } if (!empty($params['order_no'])) { $model->andWhere([ 'order_refund_id' => OrderRefund::find()->where(['like', 'order_no', $params['order_no']])->select('id')->column(), ]); } // 订单编号 if (!empty($params['shop_order_no'])) { $model->andWhere(['order_no' => trim($params['shop_order_no'])]); } if (isset($params['refund_status']) && $params['refund_status'] !== '') { $model->andWhere(['order_id' => OrderRefund::find()->where(['refund_status' => $params['refund_status']]) ->andWhere(['mall_id' => $this->mall_id])->select('order_id')->column()]); } if (!empty($params['data'])) { list($start, $end) = $params['data']; $start = strtotime($start); $end = strtotime($end); $model->andFilterWhere(['between', 'created_at', $start, $end]); } $pagination = new Pagination(['totalCount' => $model->count(), 'pageSize' => $limit]); $model->limit($pagination->limit)->offset($pagination->offset); $list = $model->orderBy('id desc') ->asArray() ->all(); foreach ($list as $k => $v) { $list[$k]['status_text'] = OrderRefundEnum::getValue($v['status']); $list[$k]['data'] = Json::decode($v['data']); $list[$k]['refund']['pic_list'] = Json::decode($v['refund']['pic_list']); $list[$k]['refund']['type_text'] = RefundTypeEnum::getValue($v['refund']['type']); if(!empty($v['order_detail_id'])){ $field = ['id'=>$v['order_detail_id']]; }else{ $field = ['order_id'=>$v['order_id']]; } $list[$k]['detail'] = OrderDetail::find() ->where($field) ->select('id, order_id, goods_attr_name, price, goods_price, pic_url, goods_name') ->asArray() ->one(); } $data['list'] = $list; $data['page_count'] = $pagination->pageCount; $data['refund_status_list'] = RefundStatusEnum::getMap(); return $data; } /** * 获取售后类型 * * @return void */ public function getFeedbackTypeReason($feedback_type) { if (empty($feedback_type)) { return []; } foreach ($feedback_type as $k => $v) { $param = RefundTypeEnum::getReasonParam($v['id']); $res = ApiService::getConnect($this->mall_id, $this->supply_id)->afterSalesReason(...array_values($param)); if ($res === false) { return $this->error('获取获取售后原因失败'); } $feedback_type[$k]['reason'] = $this->formatReasons($res['reasons']); } return $feedback_type; } public function getAfterSalesTypeNameMap($order_item_id) { // 获取订单ID return ApiService::getConnect($this->mall_id, $this->supply_id)->getAfterSalesTypeNameMap(0, $order_item_id); } /** * 获取售后原因 * * @param integer $status * @return array|boolean */ public function afterSalesReason($status) { if (empty($params = $this->status_arr[$status])) { return $this->error(''); } $res = ApiService::getConnect($this->mall_id)->afterSalesReason(...array_values($params)); if ($res === false) { return $this->error('获取获取售后原因失败'); } return $res['reasons']; } /** * 格式化售后原因 * * @param array $reasons * @return array */ private function formatReasons($reasons) { $res = []; foreach ($reasons as $k => $v) { $res[] = [ 'id' => $k, 'value' => $v, ]; } return $res; } /** * 添加售后 * * @param array $params * @return MallOrderRefund */ public function doRefund($params) { $params2 = $params; $db = Yii::$app->db->beginTransaction(); try { //订单详情表 $order_detail = OrderDetail::findOne(['id' => $params['order_detail_id'], 'user_id' => $params['user_id']]); if (empty($order_detail)) throw new Exception('订单不存在'); if ($order_detail->order_status == OrderStatusEnum::ACCOMPLISH) throw new Exception('订单已过售后时间,无法申请售后'); if (!in_array($order_detail->order_status, [OrderStatusEnum::PAY, OrderStatusEnum::SHIPMENTS, OrderStatusEnum::SING])) { throw new Exception('该订单状态下,无法申请售后'); } // 订单是否聚合 $order = MallOrderModel::find()->where(['id' => $order_detail['order_id']])->select('id, order_no, mall_id, shipping_money, order_source, pay_money, order_status, out_trade_no')->one(); if($order->order_source == AddonsEnum::ADDONS_NAME_CARD_COUPONS){ $goods = Goods::findOne($order_detail['goods_id']); if(!$goods || $goods['goods_source'] != QiJuheEnum::PLUGIN_SIGN) throw new Exception('该订单不是聚合供应链订单'); }else{ if ($order->order_source != QiJuheEnum::PLUGIN_SIGN) throw new Exception('该订单不是聚合供应链订单'); } // 公共调用 // $order_detail = GlobalInvoke::exec(GlobalInvoke::ORDER_REFUND_SUBMIT, $this->mall_id, [ // 'order_detail' => $order_detail, // 'mall_id' => $this->mall_id, // ]) ?: $order_detail; $params['refund_way'] = StatusEnum::ENABLED; // 整单退 if ($params['type'] == RefundTypeEnum::ONLY_PART_MONEY) { // 部分退款 $params['type'] = RefundTypeEnum::ONLY_MONEY; // 修改成仅退款 $params['refund_way'] = StatusEnum::DISABLED; // 自定义金额 } elseif ($params['type'] != RefundTypeEnum::EXCHANGE_GOODS) { // 按数量退款 // 一共可以退款 $refund_total_money = bcadd($order_detail->goods_price, $order_detail->adjust_money, 2); // 待发货添加邮费 // if ($order_detail['order_status'] == OrderStatusEnum::PAY) { // $data['goods_info']['goods_price'] = bcadd($order_detail['goods_price'], $order->shipping_money, 2); // } // 计算单价 $goods_single_price = bcdiv($refund_total_money, $order_detail->num, 2); $params['refund_price'] = bcmul($params['num'], $goods_single_price, 2); if ($params['refund_price'] > $refund_total_money) { throw new Exception('最多可退款金额¥' . $refund_total_money); } } else { $params['refund_price'] = 0; // 如果是换货,则退款金额为0 } $order_detail->is_feedback = OrderStatusEnum::FEEDBACKING; $order_detail->num = intval($order_detail->num); if(!$order_detail->save()) throw new Exception($order_detail->getErrorMessage()); // 处理维权步骤 !empty($params['pic_list']) && $params['pic_list'] = Json::decode($params['pic_list']); MallOrderRefundModel::stepHandle($params); // 退款主表 $params2['is_feedback'] = $params['is_feedback'] = OrderStatusEnum::FEEDBACKING; $params2['order_id'] = $params['order_id'] = $order_detail->order_id; $params2['order_no'] = $params['order_no'] = Order::generateOrderNo('r'); $params['is_full'] = "{$order_detail->order_id}_{$order_detail->id}"; // 是否整单退 $order_refund_model = MallOrderRefundModel::setData($params); if(!$order_refund_model) throw new Exception(MallOrderRefundModel::getStaticError()); $params2['order_refund_id'] = $params['order_refund_id'] = $order_refund_model->id; $res = OrderRefundStep::setData($params); if(!$res) throw new Exception(OrderRefundStep::getStaticError()); //自动维护订单主表 $res = Order::autoUpdateStatus($order); $trade = PaymentTrade::findOne(['out_trade_no' => $order->out_trade_no]); $trade->addRefundItem($order_refund_model, $order); // 中台订单 $supply_order = OrderModel::find()->where(['order_id' => $order->id,'order_detail_id'=>$order_detail['id']])->one(); if (empty($supply_order)) { throw new Exception('供应链订单不存在'); } // 添加聚合售后 OrderRefundModel::add([ 'user_id' => $params['user_id'], 'order_no' => $order->order_no, 'mall_id' => $order->mall_id, 'supply_id' => $supply_order->supply_id, 'order_id' => $order_detail['order_id'], 'order_detail_id' => $order_detail['id'], 'order_refund_id' => $order_refund_model->id, 'num' => intval($params['num'] ?: $order_detail->num), 'data' => $this->getSendData($params, $order, $supply_order, $order_detail), ]); $db->commit(); // 触发售后统计 $refund_event = ['id' =>$order_refund_model->id, 'type' => EnumsRefundTypeEnum::EVENT_APPLY]; Yii::$app->services->rabbitMq->push( RabbitMqEnum::EXCHANGE_TASK, RabbitMqEnum::TASK_QUEUE, [ 'type' => EventNameEnum::ORDER_REFUND, 'params' => $refund_event, ], StatisticsHandle::class, 'handleSync' ); return $order_refund_model; } catch (Exception $e) { $db->rollback(); return $this->error($e->getMessage()); } } /** * 获取推送数据 * * @param array $params * @return array */ public function getSendData($params, $order, $supply_order, OrderDetail $order_detail) { $data = [ 'third_order_sn' => $order->order_no, // 订单id与采购端订单号两个必传一个 'order_item_id' => (int)$supply_order->getOrderItemId(), // 子订单id 'reason_type' => (int)$params['reason_id'], // 售后原因(售后原因API返回) 'reason' => $params['remark'], // 售后原因是 其他的的时候填写 'description' => $params['remark'], // 描述 'is_received' => (int)RefundTypeEnum::getReasonParam($params['type'])['is_received'], // 是否收到货 1是0否 'detail_images' => $params['pic_list'] ?? [], // 图片凭证 'refund_type' => (int)RefundTypeEnum::getReasonParam($params['type'])['after_sale_type'], // 0退款 1退货退款 'refund_way' => $params['refund_way'], // 0自定义金额 1个数 部分退款的情况下 这里只能填写0 // 'amount' => intval(bcmul($params['refund_price'], 100)), ]; $attr = GoodsSkuModel::findOne(['attr_id' => $order_detail->goods_attr_id]); if ($data['refund_type'] == 2) { // 换货 if (isset($data['num'])) unset($data['num']); $data['barter_sku_id'] = $attr->sku_id; // 换货中台规格id $data['barter_num'] = $params['num'] ?? intval($order_detail->num); // 换货数量 - 买了多少就是多少 // $data['amount'] = 0; // 换货则退款金额为0 } if ($params['refund_way'] == StatusEnum::DISABLED) { // 部分退款 $data['amount'] = intval(bcmul($params['refund_price'], 100)); // 中台退款金额 $data['apply_amount'] = $params['refund_price']; // 申请金额 } else { $data['num'] = $params['num'] ?? intval($order_detail->num); // 退款商品个数 refund_way=1必填 } return $data; } /** * 驳回申请 * * @param array $ids * @return integer */ public function reject($ids) { $list = OrderRefundModel::find()->where(['id' => $ids, 'status' => OrderRefundEnum::NOT_PUSHED])->all(); $num = 0; foreach ($list as $v) { $db = Yii::$app->db->beginTransaction(); try { self::refuse($v); $db->commit(); $num++; } catch (Exception $e) { $db->rollback(); $v->error_msg = $e->getMessage(); $v->save(); } } return $num; } /** * 同意申请 * * @param array $ids [1,2,3] * @return void */ public function agree(array $ids, $amount, $refund_way) { /** * @var $list OrderRefundModel[] */ $list = OrderRefundModel::find()->where(['id' => $ids, 'status' => OrderRefundEnum::NOT_PUSHED])->all(); $num = 0; foreach ($list as $v) { $db = Yii::$app->db->beginTransaction(); try { $data = Json::decode($v->data); if ($data['refund_way'] === 0 && $refund_way === '0' && $amount > 0) { $data['amount'] = intval(bcmul($amount, 100)); } $v->data = Json::encode($data); $res = $this->push($v); $v->pushOk($res); $num++; $db->commit(); } catch (Exception $e) { $db->rollback(); $v->error_msg = $e->getMessage(); $v->save(); } } return $num; } /** * 推送申请 * * @param OrderRefundModel $model * @return void */ public function push(OrderRefundModel $model) { $api = ApiService::getConnect($model->mall_id, $model->supply_id); if (false !== $api->afterSalesCreate(Json::decode($model->data))) { return []; } throw new Exception($api->error); } /** * 驳回 * * @param OrderRefundModel $model * @return void */ public static function refuse(OrderRefundModel $model) { $model->status = OrderRefundEnum::REFUSE; $res = $model->save(); if ($res === false) throw new Exception($model->getError()); $res = OrderRefundLogic::refuseRefundForChain($model->mall_id, $model->order_refund_id); if ($res === false) throw new Exception(OrderRefund::$static_error); } /** * 同意售后 * * @param string $order_no * @return boolean */ public function after_sales_pass($order_no, $after_sales_id, $order_item_id) { // $order_id = Order::find()->where(['order_no' => $order_no])->select('id')->scalar(); // $order_reufnd = OrderRefund::find() // ->where(['mall_id' => $this->mall_id]) // ->andWhere(['=', 'order_id', $order_id]) // ->andWhere(['>', 'step_status', 0]) // ->andWhere(['<>', 'refund_status', RefundStatusEnum::REVOKE]) // ->one(); // // // 插件售后订单 // $order_refund = OrderRefundModel::findOne(['order_no' => $order_no, 'after_sales_id' => new Expression("after_sales_id IS NULL"), 'status' => OrderRefundEnum::PUSHED]); /** * @var $order_refund OrderRefundModel */ $order_refund = OrderRefundModel::find()->where(['order_no' => $order_no])->andWhere(['status' => OrderRefundEnum::PUSHED]) ->andWhere(['like', 'data', new Expression('\'%"order_item_id":'.$order_item_id.',%\'')])->orderBy('id desc')->one(); if (empty($order_refund) || !empty($order_refund->after_sales_id)) return true; $order_reufnd = OrderRefund::findOne(['id' => $order_refund->order_refund_id]); // 更新插件订单供应链端售后ID $order_refund->after_sales_id = $after_sales_id; // 仅退款 if ($order_reufnd->type == RefundTypeEnum::ONLY_MONEY) { $order_refund->save(); return true; } // 退货退款 if (in_array($order_reufnd->type, [RefundTypeEnum::MONEY_AND_PRODUCT, RefundTypeEnum::EXCHANGE_GOODS])) { // 获取退货地址 $api = ApiService::getConnect($order_refund->mall_id, $order_refund->supply_id); if (false == ($address = $api->findShopAddressByOrderId($after_sales_id))) { throw new Exception($api->error); } // 修改退货地址ID $order_refund->shipping_address_id = $address['id']; $order_refund->save(); // 退货地址 $refund_address = [ 'consignee' => $address['contacts'], 'mobile' => $address['tel'], 'address' => $address['province_name']['name'] . $address['city_name']['name'] . $address['district_name']['name'] . $address['address'], ]; $logic = new OrderRefundLogic(); if ($logic->execute($this->mall_id, [ 'id' => $order_reufnd->id, 'address' => $refund_address, 'step_status' => $order_reufnd->type == RefundTypeEnum::MONEY_AND_PRODUCT ? RefundStatusEnum::STEP_2 : RefundStatusEnum::STEP_8, ])) { return true; } else { throw new Exception($logic->getError()); } } throw new Exception('退货退款'); } /** * 中台发货 * @param $order_no * @param $after_sales_id * @return true|void * @throws Exception */ public function after_sales_receiving($order_no, $after_sales_id) { /** * @var $order_refund OrderRefundModel */ $refund = OrderRefundModel::find()->where(['after_sales_id' => $after_sales_id])->orderBy('id desc')->one(); if (empty($refund)) return true; $order_refund = OrderRefund::findOne(['id' => $refund->order_refund_id]); if (empty($order_refund)) throw new Exception('退款订单不存在!'); // 这里处理退款订单信息 if (in_array($order_refund->type, [RefundTypeEnum::EXCHANGE_GOODS, RefundTypeEnum::MONEY_AND_PRODUCT])) { // 中台订单 /** * @var $supply_order OrderModel */ $supply_order = OrderModel::find()->where(['order_id' => $order_refund->order_id,'order_detail_id'=>$order_refund->order_detail_id])->one(); if (empty($supply_order)) { throw new Exception('供应链订单不存在'); } $api = ApiService::getConnect($order_refund->mall_id, $refund->supply_id); if (!$res = $api->getAfterSalesByOrderItemId($supply_order->getOrderItemId())) { throw new Exception($api->error); } $logic = new OrderRefundLogic(); $rLogic = $logic->execute($order_refund->mall_id, [ 'id' => $order_refund->id, 'step_status' => RefundStatusEnum::STEP_9, 'express' => [ 'saler_express' => $res['after_sales']['return_order_express']['company_name'] ?? '', 'saler_express_no' => $res['after_sales']['return_order_express']['express_no'] ?? '', ], ]); if ($rLogic) { return true; } throw new Exception('退货退款' . $logic->getError()); } } public function after_receipt($refund_id) { $refund = OrderRefundModel::findOne(['order_refund_id' => $refund_id]); if (empty($refund)) throw new Exception('退款订单不存在'); $api = ApiService::getConnect($refund->mall_id, $refund->supply_id); if (!$api->refundUserReceive($refund->after_sales_id)) { throw new Exception($api->error); } } /** * 售后确认退款 * * @param string $order_no * @return void */ public function after_sales_complete($order_no, $after_sales_id) { // $order_id = Order::find()->where(['order_no' => $order_no])->select('id')->scalar(); // $order_reufnd = OrderRefund::find() // ->where(['mall_id' => $this->mall_id]) // ->andWhere(['=', 'order_id', $order_id]) // ->andWhere(['>', 'step_status', 0]) // ->andWhere(['<>', 'refund_status', RefundStatusEnum::REVOKE]) // ->one(); /** * @var $order_refund OrderRefundModel */ $t_refund = OrderRefundModel::find()->where(['after_sales_id' => $after_sales_id])->orderBy('id desc')->one(); if (empty($t_refund)) return true; $order_reufnd = OrderRefund::findOne(['id' => $t_refund->order_refund_id]); // 仅退款 if ($order_reufnd->type == RefundTypeEnum::ONLY_MONEY) { $logic = new OrderRefundLogic(); if ($logic->execute($this->mall_id, [ 'id' => $order_reufnd->id, 'step_status' => RefundStatusEnum::STEP_6 ])) { return true; } throw new Exception('仅退款' . $logic->getError()); } // 退货退款 if ($order_reufnd->type == RefundTypeEnum::MONEY_AND_PRODUCT) { $logic = new OrderRefundLogic(); if ($logic->execute($this->mall_id, [ 'id' => $order_reufnd->id, 'step_status' => RefundStatusEnum::STEP_4 ])) { return true; } throw new Exception('退货退款' . $logic->getError()); } } /** * 驳回售后并关闭售后 * * @param string $order_no * @param int $after_sales_id * @return void */ public function after_sales_reject($order_no, $after_sales_id, $order_item_id) { $order_id = Order::find()->where(['order_no' => $order_no])->select('id')->scalar(); $order_reufnd = OrderRefund::find() ->where(['mall_id' => $this->mall_id]) ->andWhere(['=', 'order_id', $order_id]) ->andWhere(['>', 'step_status', 0]) ->andWhere(['<>', 'refund_status', RefundStatusEnum::REVOKE]) ->one(); $orderDetail = OrderRefundModel::find() ->where("JSON_CONTAINS(data, CAST(:q AS JSON), '$.order_item_id')", [':q' => $order_item_id]) ->select('order_detail_id') ->one(); if (empty($orderDetail)) { throw new Exception('未找到匹配的记录'); } if (empty($order_reufnd)) { throw new Exception('订单不存在或者已处理'); } $order_refund_model = OrderRefundModel::findOne(['order_refund_id'=>$order_reufnd['id']]); if (empty($order_refund_model)) { throw new Exception('七件事供应链售后订单不存在或者已处理'); } // 查询售后订单 $api = ApiService::getConnect($order_reufnd->mall_id, $order_refund_model->supply_id); $detail = $api->getAfterSales($after_sales_id); $reason = isset($detail['after_sales']['logs'][1]['content']) ? $detail['after_sales']['logs'][1]['content'] : '上游拒绝售后'; OrderRefundLogic::refuseRefundForChainQijuhe($order_reufnd->mall_id, $orderDetail->order_detail_id, $reason); } /** * 售后取消 * * @param integer $id * @return void */ public function cancel($id) { $order_refund = OrderRefundModel::find()->where(['order_refund_id' => $id])->one(); if (empty($order_refund)) { return true; } // 推送 if ($order_refund->status == OrderRefundEnum::PUSHED) { $api = ApiService::getConnect($order_refund->mall_id, $order_refund->supply_id); // 获取售后ID $sales_list = $api->afterSalesList($order_refund->order_no); if (empty($sales_list)) { return $this->error('供应链端无法查到售后信息'); } $salse_info = current($sales_list['list']); if (empty($salse_info)) { return $this->error('供应链端无法查到售后信息2'); } // 关闭售后订单 $api->afterSalesClose($salse_info['id']); } $order_refund->status = OrderRefundEnum::REFUSE; $order_refund->save(); return true; } public function thirdCancel($order_no, $after_sales_id) { /** * @var $order_refund OrderRefundModel */ $order_refund = OrderRefundModel::find()->where(['order_no' => $order_no]) ->andWhere(['after_sales_id' => $after_sales_id])->one(); if (empty($order_refund)) { return true; } $api = ApiService::getConnect($order_refund->mall_id, $order_refund->supply_id); // 获取售后ID $sales = $api->getAfterSales($after_sales_id); if (empty($sales)) { return $this->error('供应链端无法查到售后信息'); } $logic = new OrderRefundLogic(); $res = $logic->execute($order_refund->mall_id, [ 'step_status' => -1, 'id' => $order_refund->order_refund_id, 'refund_reason' => $sales['after_sales_audit']['cause'] ?? '未查询到理由', ]); if (!$res) return $this->error($logic->getError()); return true; } public static function isSupportExchange($goods_id) { $attr = GoodsSkuModel::findOne(['goods_id' => $goods_id]); if (empty($attr)) return false; $goods = GoodsSkuModel::findOne(['goods_id' => $attr->supply_goods_id]); if (empty($goods)) return false; return $goods->source == QiJuheEnum::SUPPLY_YIYATONG; } public function buyerDelivery($id) { $refund = OrderRefundModel::findOne(['order_refund_id' => $id]); if (empty($refund)) throw new Exception("退款不存在"); $order_refund = OrderRefund::findOne(['id' => $id]); if (empty($order_refund)) throw new Exception("退款不存在!"); $api = ApiService::getConnect($refund->mall_id, $refund->supply_id); $express = ExpressModel::findOne(['name' => $order_refund->express]); if (empty($express)) throw new Exception("快递公司不存在 {$order_refund->express} {$order_refund->order_no}"); $sales_list = $api->afterSalesSend([ 'after_sales_id' => $refund->after_sales_id, 'company_name' => $express->name, 'company_code' => $express->code, 'express_no' => $order_refund->express_no, // 'shipping_address_id' => '' ]); var_dump($sales_list); } }