后台订单状态管理 发货快递单号添加

添加订单邮编

添加订单邮编

前后台订单详情页添加地址邮编

添加发货单号

优化订单详情页 添加快递公司配置

fixed shipment

发货功能

fixed order shippment

添加前后台订单详情发货信息

优化后台快递公司配置

fixed order shipment

修改 express_company 为 express_code

add code

发货设置
This commit is contained in:
pushuo 2022-12-16 17:48:44 +08:00 committed by Edward Yang
parent b91373a093
commit 88160552c4
31 changed files with 420 additions and 26 deletions

View File

@ -12,6 +12,7 @@
namespace Beike\Admin\Http\Controllers;
use Beike\Models\Order;
use Beike\Services\ShipmentService;
use Illuminate\Http\Request;
use Beike\Repositories\OrderRepo;
use Beike\Services\StateMachineService;
@ -67,7 +68,7 @@ class OrderController extends Controller
*/
public function show(Request $request, Order $order)
{
$order->load(['orderTotals', 'orderHistories']);
$order->load(['orderTotals', 'orderHistories', 'orderShipments']);
$data = [
'order' => $order,
'statuses' => StateMachineService::getInstance($order)->nextBackendStatuses()
@ -89,8 +90,11 @@ class OrderController extends Controller
$status = $request->get('status');
$comment = $request->get('comment');
$notify = $request->get('notify');
$shipment = ShipmentService::handleShipment(\request('express_code'), \request('express_number'));
$stateMachine = new StateMachineService($order);
$stateMachine->changeStatus($status, $comment, $notify);
$stateMachine->setShipment($shipment)->changeStatus($status, $comment, $notify);
return json_success(trans('common.updated_success'));
}
}

View File

@ -20,9 +20,9 @@ class Order extends Base
'email', 'calling_code', 'telephone', 'total', 'locale', 'currency_code', 'currency_value', 'ip', 'user_agent',
'status', 'shipping_method_code', 'shipping_method_name', 'shipping_customer_name', 'shipping_calling_code',
'shipping_telephone', 'shipping_country', 'shipping_zone', 'shipping_city', 'shipping_address_1',
'shipping_address_2', 'payment_method_code', 'payment_method_name', 'payment_customer_name',
'shipping_zipcode', 'shipping_address_2', 'payment_method_code', 'payment_method_name', 'payment_customer_name',
'payment_calling_code', 'payment_telephone', 'payment_country', 'payment_zone', 'payment_city',
'payment_address_1', 'payment_address_2',
'payment_address_1', 'payment_address_2', 'payment_zipcode',
];
protected $appends = ['status_format', 'total_format'];
@ -42,6 +42,11 @@ class Order extends Base
return $this->hasMany(OrderHistory::class);
}
public function orderShipments(): HasMany
{
return $this->hasMany(OrderShipment::class);
}
public function getStatusFormatAttribute()
{
return trans('order.' . $this->status);

View File

@ -0,0 +1,20 @@
<?php
/**
* OrderShipment.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-12-16 18:37:24
* @modified 2022-12-16 18:37:24
*/
namespace Beike\Models;
class OrderShipment extends Base
{
protected $fillable = [
'order_id', 'express_code', 'express_company', 'express_number'
];
}

View File

@ -215,6 +215,7 @@ class OrderRepo
'shipping_city' => $shippingAddress->city,
'shipping_address_1' => $shippingAddress->address_1,
'shipping_address_2' => $shippingAddress->address_2,
'shipping_zipcode' => $shippingAddress->zipcode,
'payment_method_code' => $paymentMethodCode,
'payment_method_name' => trans($paymentMethodCode),
'payment_customer_name' => $paymentAddress->name,
@ -225,6 +226,7 @@ class OrderRepo
'payment_city' => $paymentAddress->city,
'payment_address_1' => $paymentAddress->address_1,
'payment_address_2' => $paymentAddress->address_2,
'payment_zipcode' => $paymentAddress->zipcode,
]);
$order->saveOrFail();

View File

@ -0,0 +1,58 @@
<?php
/**
* ShipmentService.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-12-20 17:23:51
* @modified 2022-12-20 17:23:51
*/
namespace Beike\Services;
class ShipmentService
{
/**
* 处理订单运单数据
*
* @param $expressCode
* @param $expressNumber
* @return array
*/
public static function handleShipment($expressCode, $expressNumber): array
{
if (empty($expressCode) || empty($expressNumber)) {
return [];
}
$expressCompany = self::handleExpressCompany($expressCode);
if (empty($expressCompany)) {
return [];
}
return [
'express_code' => $expressCode,
'express_company' => $expressCompany,
'express_number' => $expressNumber,
];
}
/**
* 根据快递公司编号获取快递公司名称
*
* @param $expressCode
* @return mixed
*/
public static function handleExpressCompany($expressCode): mixed
{
$expressCompanies = system_setting('base.express_company');
if (empty($expressCompanies)) {
return '';
}
$company = collect($expressCompanies)->where('code', $expressCode)->first();
return $company ? $company['name'] ?? '' : '';
}
}

View File

@ -14,6 +14,7 @@ namespace Beike\Services;
use Throwable;
use Beike\Models\Order;
use Beike\Models\OrderHistory;
use Beike\Models\OrderShipment;
class StateMachineService
{
@ -21,6 +22,7 @@ class StateMachineService
private int $orderId;
private string $comment;
private bool $notify;
private array $shipment;
const CREATED = 'created'; // 已创建
const UNPAID = 'unpaid'; // 待支付
@ -48,7 +50,7 @@ class StateMachineService
],
self::PAID => [
self::CANCELLED => ['updateStatus', 'addHistory'],
self::SHIPPED => ['updateStatus', 'addHistory'],
self::SHIPPED => ['updateStatus', 'addHistory', 'addShipment'],
self::COMPLETED => ['updateStatus', 'addHistory']
],
self::SHIPPED => [
@ -89,6 +91,18 @@ class StateMachineService
return $this;
}
/**
* 设置发货信息
*
* @param array $shipment
* @return $this
*/
public function setShipment(array $shipment = []): self
{
$this->shipment = $shipment;
return $this;
}
/**
* 获取所有订单状态列表
@ -254,6 +268,27 @@ class StateMachineService
}
/**
* 添加发货单号
*/
private function addShipment($oldCode, $newCode)
{
$shipment = $this->shipment;
$expressCode = $shipment['express_code'] ?? '';
$expressCompany = $shipment['express_company'] ?? '';
$expressNumber = $shipment['express_number'] ?? '';
if ($expressCode && $expressCompany && $expressNumber) {
$orderShipment = new OrderShipment([
'order_id' => $this->orderId,
'express_code' => $expressCode,
'express_company' => $expressCompany,
'express_number' => $expressNumber,
]);
$orderShipment->saveOrFail();
}
}
/**
* 恢复库存
*/

View File

@ -0,0 +1,43 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (!Schema::hasTable('order_shipments')) {
Schema::create('order_shipments', function (Blueprint $table) {
$table->id();
$table->integer('order_id')->comment('订单ID')->index('order_id');
$table->string('express_code')->comment('快递公司编码');
$table->string('express_company')->comment('快递公司名称');
$table->string('express_number')->comment('运单号');
$table->timestamps();
});
}
if (!Schema::hasColumn('orders', 'shipping_zipcode')) {
Schema::table('orders', function (Blueprint $table) {
$table->string('shipping_zipcode')->after('shipping_address_2')->comment('配送地址邮编');
$table->string('payment_zipcode')->after('payment_address_2')->comment('发票地址邮编');
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('order_shipments');
}
};

View File

@ -336,6 +336,28 @@ table.table thead th, .fw-bold, h1,h2,h3, h4, h5, h6, b, strong, .card .card-hea
}
// 处理 el-form 表单在非中文情况下的 显示问题
@media (min-width: 768px) {
body:not(.zh_cn) {
.el-form-item {
display: flex;
align-items: center;
.el-form-item__label {
line-height: 1.3;
font-size: 13px;
& + .el-form-item__content {
margin-left: 0 !important;
}
}
.el-form-item__content {
flex: 1;
}
}
}
}
@media (max-width: 768px) {
.wp-200.text-end {
text-align: left !important;

View File

@ -26,7 +26,7 @@
{{-- <x-analytics /> --}}
</head>
<body class="@yield('body-class')">
<body class="@yield('body-class') {{ locale() }}">
<x-admin-header />
<div class="main-content">

View File

@ -60,18 +60,28 @@
<tbody>
<tr>
<td>
<div>{{ $order->shipping_customer_name }} ({{ $order->shipping_telephone }})</div>
{{ $order->shipping_country }}
{{ $order->shipping_zone }}
{{ $order->shipping_city }}
{{ $order->shipping_address_1 }}
<div>{{ __('address.name') }}{{ $order->shipping_customer_name }} ({{ $order->shipping_telephone }})</div>
<div>
{{ __('address.address') }}
{{ $order->shipping_address_1 }}
{{ $order->shipping_address_2 }}
{{ $order->shipping_city }}
{{ $order->shipping_zone }}
{{ $order->shipping_country }}
</div>
<div>{{ __('address.post_code') }}{{ $order->shipping_zipcode }}</div>
</td>
<td>
<div>{{ $order->payment_customer_name }} ({{ $order->payment_telephone }})</div>
{{ $order->payment_country }}
{{ $order->payment_zone }}
{{ $order->payment_city }}
{{ $order->payment_address_1 }}
<div>{{ __('address.name') }}{{ $order->payment_customer_name }} ({{ $order->payment_telephone }})</div>
<div>
{{ __('address.address') }}
{{ $order->payment_address_1 }}
{{ $order->payment_address_2 }}
{{ $order->payment_city }}
{{ $order->payment_zone }}
{{ $order->payment_country }}
</div>
<div>{{ __('address.post_code') }}{{ $order->payment_zipcode }}</div>
</td>
</tr>
</tbody>
@ -89,7 +99,7 @@
</el-form-item>
@if ($order->status != 'completed')
<el-form-item label="{{ __('order.change_to_status') }}" prop="status">
<el-select size="small" v-model="form.status" placeholder="{{ __('common.please_choose') }}">
<el-select class="wp-200" size="small" v-model="form.status" placeholder="{{ __('common.please_choose') }}">
<el-option
v-for="item in statuses"
:key="item.status"
@ -98,10 +108,20 @@
</el-option>
</el-select>
</el-form-item>
{{-- <el-form-item label="通知客户">
<el-switch v-model="form.notify">
</el-switch>
</el-form-item> --}}
<el-form-item label="{{ __('order.express_company') }}" v-if="form.status == 'shipped'" prop="express_company">
<el-select class="wp-200" size="small" v-model="form.express_code" placeholder="{{ __('common.please_choose') }}">
<el-option
v-for="item in source.express_company"
:key="item.code"
:label="item.name"
:value="item.code">
</el-option>
</el-select>
<a href="{{ admin_route('settings.index') }}?tab=tab-express-company" target="_blank" class="ms-2">{{ __('common.to_setting') }}</a>
</el-form-item>
<el-form-item label="{{ __('order.express_number') }}" v-if="form.status == 'shipped'" prop="express_number">
<el-input class="w-max-500" v-model="form.express_number" size="small" v-if="form.status == 'shipped'" placeholder="{{ __('order.express_number') }}"></el-input>
</el-form-item>
<el-form-item label="{{ __('order.comment') }}">
<textarea class="form-control w-max-500" v-model="form.comment"></textarea>
</el-form-item>
@ -158,6 +178,34 @@
</div>
</div>
@if ($order->orderShipments)
<div class="card mb-4">
<div class="card-header"><h6 class="card-title">{{ __('order.order_shipments') }}</h6></div>
<div class="card-body">
<div class="table-push">
<table class="table ">
<thead class="">
<tr>
<th>{{ __('order.express_company') }}</th>
<th>{{ __('order.express_number') }}</th>
<th>{{ __('order.history_created_at') }}</th>
</tr>
</thead>
<tbody>
@foreach ($order->orderShipments as $ship)
<tr>
<td>{{ $ship->express_company }}</td>
<td>{{ $ship->express_number }}</td>
<td>{{ $ship->created_at }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
<div class="card mb-4">
<div class="card-header"><h6 class="card-title">{{ __('order.action_history') }}</h6></div>
<div class="card-body">
@ -174,8 +222,8 @@
@foreach ($order->orderHistories as $orderHistory)
<tr>
<td>{{ $orderHistory->status_format }}</td>
<td><span class="fw-bold">{{ $orderHistory->comment }}</span></td>
<td><span class="fw-bold">{{ $orderHistory->created_at }}</span></td>
<td>{{ $orderHistory->comment }}</td>
<td>{{ $orderHistory->created_at }}</td>
</tr>
@endforeach
</tbody>
@ -196,12 +244,20 @@
statuses: @json($statuses ?? []),
form: {
status: "",
express_number: '',
express_code: '',
notify: false,
comment: '',
},
source: {
express_company: @json(system_setting('base.express_company', [])),
},
rules: {
status: [{required: true, message: '{{ __('admin/order.error_status') }}', trigger: 'blur'}, ],
express_code: [{required: true,message: '{{ __('common.error_required', ['name' => __('order.express_company')]) }}',trigger: 'blur'}, ],
express_number: [{required: true,message: '{{ __('common.error_required', ['name' => __('order.express_number')]) }}',trigger: 'blur'}, ],
}
},

View File

@ -20,6 +20,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-image">{{ __('admin/setting.picture_settings') }}</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-express-company">{{ __('order.express_company') }}</a>
</li>
</ul>
<div class="tab-content">
@ -102,6 +105,25 @@
<div class="help-text font-size-12 lh-base">{{ __('admin/setting.placeholder_image_info') }}</div>
</x-admin-form-image>
</div>
<div class="tab-pane fade" id="tab-express-company">
<x-admin::form.row title="{{ __('order.express_company') }}">
<table class="table table-bordered w-max-500">
<thead><th>{{ __('order.express_company') }}</th><th>Code</th><th></th></thead>
<tbody>
<tr v-for="item, index in express_company" :key="index">
<td><input type="text" :name="'express_company['+ index +'][name]'" v-model="item.name" class="form-control"></td>
<td><input type="text" :name="'express_company['+ index +'][code]'" v-model="item.code" class="form-control"></td>
<td><i @click="express_company.splice(index, 1)" class="bi bi-x-circle fs-4 text-danger cursor-pointer"></i></td>
</tr>
<tr>
<td colspan="2"><input v-if="!express_company.length" name="express_company" class="d-none"></td>
<td><i class="bi bi-plus-circle cursor-pointer fs-4" @click="addCompany"></i></td>
</tr>
</tbody>
</table>
</x-admin::form.row>
</div>
</div>
<x-admin::form.row title="">
@ -147,6 +169,29 @@
});
});
</script>
<script>
new Vue({
el: '#tab-express-company',
data: {
express_company: @json(old('express_company', system_setting('base.express_company', []))),
},
methods: {
addCompany() {
if (typeof this.express_company == 'string') {
this.express_company = [];
}
this.express_company.push({name: '', code: ''})
}
}
});
const tab = bk.getQueryString('tab');
if (tab) {
$(`a[href="#${bk.getQueryString('tab')}"]`)[0].click()
}
</script>
@endpush

View File

@ -65,6 +65,7 @@ return [
'sign_out' => 'Abmelden',
'menu' => 'Menü',
'whether_open' => 'ob öffnen',
'to_setting' => 'zu konfigurieren',
'id' => 'ID',
'created_at' => 'Erstellungszeit',

View File

@ -21,6 +21,9 @@ return [
'status' => 'Status',
'status_format' => 'status',
'total' => 'Bestellsumme',
'express_number' => 'Express-Bestellnummer',
'express_company' => 'express company',
'order_shipments' => 'Sendungsinformationen',
'address_info' => 'Adressinformationen',
'shipping_address' => 'Lieferadresse',

View File

@ -20,6 +20,6 @@ return [
'address_2' => 'Address 2',
'address' => 'Address',
'enter_city' => 'Enter city',
'post_code' => 'Post Code',
'post_code' => 'Zip Code',
'default' => 'Default',
];

View File

@ -66,6 +66,7 @@ return [
'menu' => 'Menu',
'whether_open' => 'Status',
'default' => 'Default',
'to_setting' => 'to configure',
'id' => 'ID',
'created_at' => 'Created At',

View File

@ -21,6 +21,9 @@ return [
'status' => 'Status',
'status_format' => 'Status',
'total' => 'Total',
'express_number' => 'Express Number',
'express_company' => 'Express Company',
'order_shipments' => 'shipment information',
'address_info' => 'Address Information',
'shipping_address' => 'Shipping Address',

View File

@ -65,6 +65,7 @@ return [
'sign_out' => 'desconectar',
'menu' => 'menú',
'whether_open' => 'Ya sea para abrir',
'to_setting' => 'para configurar',
'id' => 'ID',
'created_at' => 'tiempo de creación',

View File

@ -21,6 +21,9 @@ return [
'status' => 'estado',
'status_format' => 'estado',
'total' => 'orden total',
'express_number' => 'número de pedido urgente',
'express_company' => 'compañía exprés',
'order_shipments' => 'información del envío',
'address_info' => 'Datos del Domicilio',
'shipping_address' => 'dirección de entrega',

View File

@ -65,6 +65,7 @@ return [
'sign_out' => 'se déconnecter',
'menu' => 'menu',
'whether_open' => 'Que ce soit pour ouvrir',
'to_setting' => 'pour configurer',
'id' => 'ID',
'created_at' => 'temps de creation',

View File

@ -21,6 +21,9 @@ return [
'status' => 'Etat',
'status_format' => 'Etat',
'total' => 'commande totale',
'express_number' => 'numéro de commande express',
'express_company' => 'entreprise de messagerie',
'order_shipments' => 'informations sur l\'expédition',
'address_info' => 'Informations d\'adresse',
'shipping_address' => 'adresse de livraison',

View File

@ -65,6 +65,7 @@ return [
'sign_out' => 'disconnessione',
'menu' => 'menù',
'whether_open' => 'Se aprire',
'to_setting' => 'configurare',
'id' => 'ID',
'created_at' => 'tempo di creazione',

View File

@ -21,6 +21,9 @@ return [
'status' => 'stato',
'status_format' => 'stato',
'total' => 'ordine totale',
'express_number' => 'numero ordine espresso',
'express_company' => 'azienda espressa',
'order_shipments' => 'informazioni sulla spedizione',
'address_info' => 'Informazioni sull\'indirizzo',
'shipping_address' => 'indirizzo di consegna',

View File

@ -65,6 +65,7 @@ return [
'sign_out' => 'サインアウト',
'menu' => 'メニュー',
'whether_open' => '開くかどうか',
'to_setting' => '設定する',
'id' => 'ID',
'created_at' => '作成時間',

View File

@ -21,6 +21,9 @@ return [
'status' => '州',
'status_format' => '州',
'total' => '合計注文',
'express_number' => 'エクスプレス注文番号',
'express_company' => '運送会社',
'order_shipments' => '出荷情報',
'address_info' => '住所情報',
'shipping_address' => '配送先住所',

View File

@ -65,6 +65,7 @@ return [
'sign_out' => 'Выйти',
'menu' => 'меню',
'whether_open' => 'открывать ли',
'to_setting' => 'настроить',
'id' => 'ID',
'created_at' => 'время создания',

View File

@ -21,6 +21,9 @@ return [
'status' => 'государство',
'status_format' => 'государство',
'total' => 'общий заказ',
'express_number' => 'номер экспресс-заказа',
'express_company' => 'экспресс-компания',
'order_shipments' => 'информация об отправке',
'address_info' => 'информация об адресе',
'shipping_address' => 'адрес доставки',

View File

@ -66,6 +66,7 @@ return [
'menu' => '菜单',
'whether_open' => '是否开启',
'default' => '默认',
'to_setting' => '去配置',
'id' => 'ID',
'created_at' => '创建时间',

View File

@ -21,6 +21,10 @@ return [
'status' => '状态',
'status_format' => '状态',
'total' => '订单总额',
'express_number' => '快递单号',
'express_company' => '快递公司',
'order_shipments' => '发货信息',
'address_info' => '地址信息',
'shipping_address' => '配送地址',

View File

@ -65,6 +65,7 @@ return [
'sign_out' => '退出登錄',
'menu' => '菜單',
'whether_open' => '是否開啟',
'to_setting' => '去配置',
'id' => 'ID',
'created_at' => '創建時間',

View File

@ -21,6 +21,9 @@ return [
'status' => '狀態',
'status_format' => '狀態',
'total' => '訂單總額',
'express_number' => '快遞單號',
'express_company' => '快遞公司',
'order_shipments' => '發貨信息',
'address_info' => '地址信息',
'shipping_address' => '配送地址',

View File

@ -25,7 +25,6 @@
</div>
</div>
<div class="card-body">
<div class="bg-light p-2 table-responsive">
<table class="table table-borderless mb-0">
<thead>
@ -50,7 +49,47 @@
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header"><h6 class="card-title">{{ __('order.address_info') }}</h6></div>
<div class="card-body">
<table class="table ">
<thead class="">
<tr>
<th>{{ __('order.shipping_address') }}</th>
<th>{{ __('order.payment_address') }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div>{{ __('address.name') }}{{ $order->shipping_customer_name }} ({{ $order->shipping_telephone }})</div>
<div>
{{ __('address.address') }}
{{ $order->shipping_address_1 }}
{{ $order->shipping_address_2 }}
{{ $order->shipping_city }}
{{ $order->shipping_zone }}
{{ $order->shipping_country }}
</div>
<div>{{ __('address.post_code') }}{{ $order->shipping_zipcode }}</div>
</td>
<td>
<div>{{ __('address.name') }}{{ $order->payment_customer_name }} ({{ $order->payment_telephone }})</div>
<div>
{{ __('address.address') }}
{{ $order->payment_address_1 }}
{{ $order->payment_address_2 }}
{{ $order->payment_city }}
{{ $order->payment_zone }}
{{ $order->payment_country }}
</div>
<div>{{ __('address.post_code') }}{{ $order->payment_zipcode }}</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h6 class="card-title">{{ __('shop/account.order.order_info.order_items') }}</h6>
@ -111,6 +150,34 @@
</div>
@endif
@if ($order->orderShipments)
<div class="card mb-4">
<div class="card-header"><h6 class="card-title">{{ __('order.order_shipments') }}</h6></div>
<div class="card-body">
<div class="table-push">
<table class="table ">
<thead class="">
<tr>
<th>{{ __('order.express_company') }}</th>
<th>{{ __('order.express_number') }}</th>
<th>{{ __('order.history_created_at') }}</th>
</tr>
</thead>
<tbody>
@foreach ($order->orderShipments as $ship)
<tr>
<td>{{ $ship->express_company }}</td>
<td>{{ $ship->express_number }}</td>
<td>{{ $ship->created_at }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endif
@if ($order->orderHistories->count())
<div class="card mb-4">
<div class="card-header">