抖音取消/退款申请

This commit is contained in:
jianghanbo 2024-12-06 16:34:59 +08:00
parent f86b7b23ec
commit faf26d8e2e
15 changed files with 434 additions and 46 deletions

View File

@ -245,6 +245,15 @@ export const asyncRoutes = [
roles: ["order_back", "editor"], roles: ["order_back", "editor"],
}, },
}, },
{
path: "afterSale",
component: () => import("@/views/order/afterSale"),
name: "afterSale",
meta: {
title: "抖音取消/退款申请",
roles: ["order_back", "editor"],
},
},
], ],
}, },
{ {

View File

@ -0,0 +1,136 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input
v-model="listQuery.sn"
placeholder="订单号"
style="width: 300px"
class="filter-item"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="getList"
>
搜索
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
border
fit
highlight-current-row
style="width: 100%"
>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
icon="el-icon-check"
@click="handle(scope.row, 1)"
>
同意
</el-button>
<el-button
type="primary"
size="small"
icon="el-icon-check"
@click="handle(scope.row, 2)"
>
拒绝
</el-button>
</template>
</el-table-column>
<el-table-column align="center" label="订单号" prop="order_id" />
<el-table-column align="center" label="售后类型" prop="after_sale_type_agg_name" />
<el-table-column align="center" label="售后原因" prop="after_sale_reason" />
<el-table-column align="center" label="退款金额" prop="refund_amount" />
<el-table-column align="center" label="申请时间" :formatter="formatTime" prop="after_sale_apply_time_ms"/>
<el-table-column align="center" label="到期时间" :formatter="formatTime" prop="audit_expire_time_ms" />
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
</div>
</template>
<script>
// import Pagination from '@/Wangeditor/Pagination'
import Pagination from "@/components/PaginationFixed";
export default {
name: "Orderlist",
components: { Pagination },
filters: {},
data() {
return {
list: [],
total: 0,
listLoading: true,
listQuery: {
page: 1,
limit: 10,
},
oss: {},
item: {},
};
},
created() {
this.getList();
},
methods: {
getList() {
this.$axios
.get("/admin/order/dyAfterSaleOrder", { params: this.listQuery })
.then((response) => {
this.list = response.data.data;
this.total = response.data.total;
this.oss = response.ext;
this.listLoading = false;
});
},
handle(item, audit_result) {
this.$axios
.post("/admin/order/dyAfterSaleOrderHandle", { id: item.id, audit_result:audit_result})
.then((res) => {
this.$message({
message: "处理成功",
type: "success",
});
this.item = {};
this.getList();
})
.catch((err) => {});
},
formatTime(row, column, cellValue){
const date = new Date(cellValue);
return date.toLocaleString();
},
},
};
</script>
<style scoped>
.app-container {
position: relative;
padding-bottom: 60px; /* 分页条的高度 */
}
.filter-container,
.el-table {
padding-bottom: 52px; /* 分页条的高度,以避免内容重叠 */
}
::v-deep .el-image-viewer__close {
background-color: #fff;
}
</style>

View File

@ -191,6 +191,15 @@
确认接单 确认接单
</el-button> </el-button>
<el-button
v-if="scope.row.is_direct_mode && scope.row.appointment_status == 2"
size="small"
icon="el-icon-thumb"
@click="confirmOrder(scope.row, 2)"
>
协助取消/退款
</el-button>
<el-button <el-button
v-if="scope.row.appointment_status == 1 && !scope.row.is_direct_mode" v-if="scope.row.appointment_status == 1 && !scope.row.is_direct_mode"
size="small" size="small"
@ -202,14 +211,6 @@
</el-button-group> </el-button-group>
</template> </template>
</el-table-column> </el-table-column>
<!-- <el-table-column
align="center"
fixed
label="电话"
width="140"
prop="mobile"
/> -->
<el-table-column align="center" fixed label="电话" width="140"> <el-table-column align="center" fixed label="电话" width="140">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.mobile }}</span> <span>{{ scope.row.mobile }}</span>
@ -332,12 +333,6 @@
prop="contact" prop="contact"
/> />
<!-- <el-table-column align="center" label="微信" width="80">
<template slot-scope="scope">
<i v-if="scope.row.is_wechat>0" class="el-icon-circle-check"></i>
</template>
</el-table-column> -->
<el-table-column width="138px" align="center" label="出行时间"> <el-table-column width="138px" align="center" label="出行时间">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ scope.row.travel_date | parseTime("{y}-{m}-{d}") }}</span> <span>{{ scope.row.travel_date | parseTime("{y}-{m}-{d}") }}</span>
@ -504,20 +499,6 @@
}}</el-radio> }}</el-radio>
</template> </template>
</el-form-item> </el-form-item>
<!-- <el-form-item label="快捷跟进" style="width: 600px;">
<el-select v-model="value" placeholder="请选择" @change="onChange">
<el-form-item style="display: inline-flex;text-align: left;width: 770px;">
<el-option
v-for="item in options"
:key="item.value"
style="width: 250px;display: inline-flex;word-break: break-all;"
:label="item.label"
:value="item.label"
/>
</el-form-item>
</el-select>
</el-form-item>-->
<el-form-item required pros="desc" label="跟进说明"> <el-form-item required pros="desc" label="跟进说明">
<el-input v-model="item.desc" type="textarea" /> <el-input v-model="item.desc" type="textarea" />
</el-form-item> </el-form-item>
@ -570,7 +551,7 @@
</el-tabs> </el-tabs>
</el-dialog> </el-dialog>
<el-dialog title="确认接单" :visible.sync="orderConfirmDialogVisible"> <el-dialog :title="confirmTitle" :visible.sync="orderConfirmDialogVisible">
<el-form label-width="130px" :model="confirmItem"> <el-form label-width="130px" :model="confirmItem">
<el-form-item label="产品名称"> <el-form-item label="产品名称">
{{ confirmItem.product_name }} {{ confirmItem.product_name }}
@ -639,10 +620,15 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div v-if="confirmItem.appointment_status == 1" slot="footer" class="dialog-footer">
<el-button type="primary" @click="dyOrderConfirm(confirmItem, 1)">确认接单</el-button> <el-button type="primary" @click="dyOrderConfirm(confirmItem, 1)">确认接单</el-button>
<el-button type="primary" @click="dyOrderConfirm(confirmItem, 2)">拒绝</el-button> <el-button type="primary" @click="dyOrderConfirm(confirmItem, 2)">拒绝</el-button>
</div> </div>
<div v-if="confirmItem.appointment_status == 2" slot="footer" class="dialog-footer">
<el-button type="primary" @click="dyOrderCancel(confirmItem, 2)">仅取消预约</el-button>
<el-button type="primary" @click="dyOrderCancel(confirmItem, 1)">取消预约并全额退款</el-button>
</div>
</el-dialog> </el-dialog>
<el-dialog title="纯核销" :visible.sync="dialog2Visible"> <el-dialog title="纯核销" :visible.sync="dialog2Visible">
@ -799,29 +785,22 @@ export default {
prop: null, // prop: null, //
order: null // order: null //
}, },
confirmTitle:"确认接单"
}; };
}, },
async created() { async created() {
// this.listQuery.status = this.$route.query.status || null
this.listQuery.zhubo = this.$route.query.zhubo || null; this.listQuery.zhubo = this.$route.query.zhubo || null;
if (this.$route.query.start && this.$route.query.end) { if (this.$route.query.start && this.$route.query.end) {
this.listQuery.times = [this.$route.query.start, this.$route.query.end]; this.listQuery.times = [this.$route.query.start, this.$route.query.end];
} }
// console.log(typeof this.$route.params.id);
this.setQuery("status"); this.setQuery("status");
this.setQuery("os_status"); this.setQuery("os_status");
this.setQuery("times"); this.setQuery("times");
this.setQuery("appointment_status"); this.setQuery("appointment_status");
// await this.getList();
// if (!this.$route.query.id) {
// await this.setOneClickRepair();
// await this.getList();
// }
this.getShortcutContent(); this.getShortcutContent();
this.getAdminList(); this.getAdminList();
}, },
mounted() { mounted() {
console.log(this.$route.query);
this.setMode(); this.setMode();
if (this.$route.query.id) { if (this.$route.query.id) {
this.onInfo({ id: this.$route.query.id }); this.onInfo({ id: this.$route.query.id });
@ -839,8 +818,6 @@ export default {
}, },
methods: { methods: {
handleSelectionChange(val) { handleSelectionChange(val) {
// console.log(val);
this.multipleSelection = val; this.multipleSelection = val;
const data = []; const data = [];
this.multipleSelection.map((item) => { this.multipleSelection.map((item) => {
@ -935,7 +912,13 @@ export default {
}) })
.catch((err) => {}); .catch((err) => {});
}, },
confirmOrder(item) { confirmOrder(item, type=1) {
if (type == 1) {
this.confirmTitle = '确认接单';
}
if (type == 2) {
this.confirmTitle = '协助取消/退款';
}
this.$axios this.$axios
.get("/admin/order/info", { params: { id: item.id } }) .get("/admin/order/info", { params: { id: item.id } })
.then((res) => { .then((res) => {
@ -1132,6 +1115,25 @@ export default {
// }); // });
}); });
}, },
dyOrderCancel(item, cancel_type) {
this.$axios
.post("/admin/order/dyOrderCancel", { id: item.id, cancel_type:cancel_type })
.then((res) => {
this.$notify({
title: "成功",
message: "短信已发送给客人",
type: "success",
});
this.orderConfirmDialogVisible = false;
this.getList();
})
.catch((err) => {
this.$notify.error({
title: "发送失败",
message: err,
});
});
},
dyOrderConfirm(item, confirm_result) { dyOrderConfirm(item, confirm_result) {
this.$axios this.$axios
.post("/admin/order/dyOrderConfirm", { id: item.id, confirm_result:confirm_result }) .post("/admin/order/dyOrderConfirm", { id: item.id, confirm_result:confirm_result })

View File

@ -7,8 +7,10 @@ use app\model\Backs;
use app\model\DyOrderProductAppointments; use app\model\DyOrderProductAppointments;
use app\model\DyOrderProducts; use app\model\DyOrderProducts;
use app\model\Follows; use app\model\Follows;
use app\model\OrderAfterSales;
use app\model\Orders; use app\model\Orders;
use app\model\Logs; use app\model\Logs;
use app\server\Douyin;
use app\server\DyApiService; use app\server\DyApiService;
use app\server\ThirdApiService; use app\server\ThirdApiService;
use app\server\Orders as ServerOrders; use app\server\Orders as ServerOrders;
@ -801,4 +803,93 @@ class OrderController extends base
return $this->error(2006, '出错了:' . $e->getMessage()); return $this->error(2006, '出错了:' . $e->getMessage());
} }
} }
/**
* 抖音取消订单
* @param Request $request
* @return \support\Response
*/
public function dyOrderCancel(Request $request) {
try {
$id = $request->post('id', 0);
$order = Orders::where('id', $id)->find();
if (empty($order)) {
return $this->error(2004, '记录没有找到.');
}
// 类型 1取消预约并退款2取消预约
if (!$request->post('cancel_type') || !in_array($request->post('cancel_type'), [1, 2])) {
return $this->error(2005, '确认状态错误');
}
$cancelType = $request->post('cancel_type', 1);
$bookInfo = DyOrderProductAppointments::query()->where(['source_order_id' => $order->sn])->order('id desc')->find();
if (empty($bookInfo)) {
return $this->error(2006, '预约记录未找到');
}
$douyinService = new Douyin(5);
$res = $douyinService->bookCancelSmsSend($cancelType, $bookInfo->dy_order_id, $bookInfo->source_order_id);
Log::info('dyOrderCancel:' . json_encode($res));
if ($res->status_code == 0) {
return $this->success($res);
}
return $this->error(2005, $res->status_msg ?? '取消失败');
}catch(\Exception $e) {
return $this->error(2006, '出错了:' . $e->getMessage());
}
}
/**
* 抖音售后订单
* @param Request $request
* @return \support\Response
*/
public function dyAfterSaleOrder(Request $request) {
$orderId = $request->get('order_id');
$builder = OrderAfterSales::join('orders', 'order_after_sales.order_id = orders.sn', 'left');
$builder->whereIn('orders.order_status', [1, 2, 5]);
$builder->where('orders.is_direct_mode', '=', 1);
$builder->where('order_after_sales.status', '=', 0);
if (!empty($orderId)) {
$builder->where('orders.sn', $orderId);
}
if(!$request->admin->is_super) {
$builder->where('orders.admin_id','=', $request->admin->id);
} else {
$builder->where('orders.admin_id', '>', 0);
}
$builder->field(['order_after_sales.*']);
$list = $builder->paginate($request->get('limit',10));
return $this->success($list);
}
/**
* 抖音售后处理
* @param Request $request
* @return \support\Response
*/
public function dyAfterSaleOrderHandle(Request $request) {
try {
$id = $request->post('id', 0);
$order = OrderAfterSales::where('id', $id)->find();
if (empty($order)) {
return $this->error(2004, '记录没有找到.');
}
if (!in_array($request->post('audit_result'), [1, 2])) {
return $this->error(2004, '审核状态异常.');
}
$auditResult = $request->post('audit_result');
$douyinService = new Douyin(5);
// $auditOrderId, $auditResult, $bookId, $bookOrderId
$res = $douyinService->audit($order->audit_order_id, $auditResult, $order->book_id, $order->order_id);
if ($res->status_code == 0) {
OrderAfterSales::query()->where('order_id', $order->order_id)->update(['status' => $auditResult]);
return $this->success($res);
}
return $this->error(2005, $res->status_msg ?? '操作失败');
}catch(\Exception $e) {
return $this->error(2006, '出错了:' . $e->getMessage());
}
}
} }

View File

@ -5,6 +5,7 @@ use app\model\Admins;
use app\model\DyOrderProductAppointments; use app\model\DyOrderProductAppointments;
use app\model\DyOrderProducts; use app\model\DyOrderProducts;
use app\model\DyOrders; use app\model\DyOrders;
use app\model\OrderAfterSales;
use app\model\Orders; use app\model\Orders;
use app\model\Products; use app\model\Products;
use support\Log; use support\Log;
@ -261,6 +262,7 @@ class DyNotifyController extends base {
// 取消预约 // 取消预约
$appoint = DyOrderProductAppointments::query()->where('dy_order_id', $params['order_id'])->find(); $appoint = DyOrderProductAppointments::query()->where('dy_order_id', $params['order_id'])->find();
Orders::query()->where('sn', $appoint['source_order_id'])->update(['appointment_status' => 0, 'travel_date' => null]); Orders::query()->where('sn', $appoint['source_order_id'])->update(['appointment_status' => 0, 'travel_date' => null]);
OrderAfterSales::query()->where('order_id', $appoint['source_order_id'])->update(['status' => 1]);
return $this->success([]); return $this->success([]);
} }
if (empty($order = Orders::query()->where(['sn' => $params['order_id']])->find())) { if (empty($order = Orders::query()->where(['sn' => $params['order_id']])->find())) {

View File

@ -0,0 +1,76 @@
<?php
namespace app\command;
use app\model\Admins;
use app\model\LiveRoomWorks;
use app\model\OrderAfterSales;
use app\model\Orders;
use app\model\Sales;
use app\model\Works;
use app\server\Douyin;
use Carbon\Carbon;
use support\Log;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use think\facade\Db;
class DyAfterSaleCommand extends Command
{
protected static $defaultName = 'DyAfterSaleCommand';
protected static $defaultDescription = '抖音售后列表';
protected function configure()
{
$this
->setName('DyAfterSaleCommand')
->setDescription('抖音售后列表')
->setDefinition(
new InputDefinition(array())
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$output->writeln('DyAfterSaleCommand start');
$douYinService = new Douyin(5);
$res = $douYinService->bookSearch('', 6, 1, 50);
$searchRes = json_decode(json_encode($res), true);
Log::info('DyAfterSaleCommand searchRes:' . json_encode($searchRes));
if (empty($searchRes) || $searchRes['status_code'] != 0) {
return 1;
}
if (isset($searchRes['order_list']) && is_array($searchRes['order_list'])) {
foreach ($searchRes['order_list'] as $order) {
$orderInfo = $order['order_info'];
$saleAfterInfo = $order['cancel_book_after_sale_info'];
$afterSaleData = [
'order_id' => $orderInfo['order_id'],
'book_id' => $order['book_info']['book_id'],
'audit_order_id' => $saleAfterInfo['audit_order_id'],
'after_sale_type' => $saleAfterInfo['after_sale_type'],
'after_sale_type_agg_name' => $saleAfterInfo['after_sale_type_agg_name'],
'refund_amount' => $saleAfterInfo['refund_amount'],
'refund_type' => $saleAfterInfo['refund_type'],
'after_sale_reason' => implode(',', $saleAfterInfo['after_sale_reason']['reason_list']),
'after_sale_apply_time_ms' => $saleAfterInfo['after_sale_apply_time_ms'], // 申请时间
'audit_expire_time_ms' => $saleAfterInfo['audit_expire_time_ms'], // 售后时间
];
$afterSale = OrderAfterSales::query()->where(['audit_order_id' => $afterSaleData['audit_order_id']])->find();
if (!empty($afterSale)) {
OrderAfterSales::query()->where(['audit_order_id' => $afterSaleData['audit_order_id']])->update($afterSaleData);
} else {
OrderAfterSales::query()->insert($afterSaleData);
}
}
}
$output->writeln('DyAfterSaleCommand end');
return 1;
}
}

View File

@ -0,0 +1,6 @@
<?php
namespace app\model;
class OrderAfterSales extends base {
}

View File

@ -540,4 +540,68 @@ class Douyin
} }
return $res; return $res;
} }
// https://life.douyin.com/life/trade_view/v1/travel_agency/book/search?root_life_account_id=7325036766565746698
public function bookSearch($orderId, $status = 3, $pageIndex = 1, $pageSize = 1) {
$params = [
"order_id" => $orderId,// 预约ID
"page_index" => $pageIndex,
"page_size" => $pageSize,
"status" => $status,
];
Log::info('bookSearch:' . json_encode($params));
return $this->_curl("/life/trade_view/v1/travel_agency/book/search?root_life_account_id=" . env('DY_ACCOUNT_ID'), $params, 'POST');
}
// https://life.douyin.com/life/trip/v2/travel_agency/book/cancel/sms/send?root_life_account_id=7325036766565746698
/**
* 取消预约/订单 发送短信
* @throws Exception
*/
public function bookCancelSmsSend($saleType, $bookId) {
$searchRes = $this->bookSearch($bookId);
$searchRes = json_decode(json_encode($searchRes), true);
if (empty($searchRes) || $searchRes['status_code'] != 0) {
throw new Exception('获取预约信息失败');
}
try {
// 预约ID
$bookId = $searchRes['order_list'][0]['book_info']['book_id'];
$bookOrderId = $searchRes['order_list'][0]['book_info']['book_order_id'];
} catch (Exception $exception) {
Log::info('bookInfo searchRes:' . json_encode($searchRes));
throw new Exception('获取预约信息失败');
}
$params = [
"after_sale_type" => $saleType,// 类型 1取消预约并退款2取消预约
"book_id" => $bookId,// 预约ID800010584572603545816150740
"book_order_id" => $bookOrderId,// 预约订单ID1076695371865940740
];
Log::info('orderCancel:' . json_encode($params));
return $this->_curl("/life/trip/v2/travel_agency/book/cancel/sms/send?root_life_account_id=" . env('DY_ACCOUNT_ID'), $params, 'POST');
}
/**
* // https://life.douyin.com/life/trip/v2/travel_agency/book/cancel/audit?root_life_account_id=7325036766565746698
* 售后审核
* @throws Exception
*/
public function audit($auditOrderId, $auditResult, $bookId, $bookOrderId) {
$params = [
"audit_order_id" => $auditOrderId,
"audit_result" => $auditResult,
"book_id" => $bookId,
"book_order_id" => $bookOrderId,
];
if ($auditResult == 2) {
$params['reject_reason_code'] = 1105;
$params['reject_reason_msg'] = '线下沟通';
}
Log::info('audit:' . json_encode($params));
return $this->_curl("/life/trip/v2/travel_agency/book/cancel/audit?root_life_account_id=" . env('DY_ACCOUNT_ID'), $params, 'POST');
}
} }

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-e3c84f38]{position:relative;padding-bottom:60px}.el-table[data-v-e3c84f38],.filter-container[data-v-e3c84f38]{padding-bottom:5px} .pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-23c8e4b2]{position:relative;padding-bottom:60px}.el-table[data-v-23c8e4b2],.filter-container[data-v-23c8e4b2]{padding-bottom:5px}

View File

@ -0,0 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-203394c6]{position:relative;padding-bottom:60px}.el-table[data-v-203394c6],.filter-container[data-v-203394c6]{padding-bottom:52px}[data-v-203394c6] .el-image-viewer__close{background-color:#fff}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long