小票机管理
parent
653bd1cc13
commit
f38b5773d4
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Actions\Store;
|
||||
|
||||
use Dcat\Admin\Grid\RowAction;
|
||||
use App\Models\Store\Desk;
|
||||
|
||||
class RowDeskWxCode extends RowAction
|
||||
{
|
||||
protected $title = '生成小程序码';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$model = Desk::findOrFail($this->getKey());
|
||||
$result = $model->generateWxCode();
|
||||
if ($result !== true) {
|
||||
return $this->response()->error(data_get($result, 'errmsg'))->refresh();
|
||||
}
|
||||
return $this->response()->success('操作成功')->refresh();
|
||||
}
|
||||
|
||||
public function confirm()
|
||||
{
|
||||
return ['是否确定?'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers\Store;
|
||||
|
||||
use Dcat\Admin\{Form, Grid, Admin, Show};
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use App\Models\Store\{Store, Desk};
|
||||
use App\Models\{ProductCategory};
|
||||
|
||||
class DeskController extends AdminController
|
||||
{
|
||||
protected $translation = 'store-desk';
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(Desk::with(['store']), function (Grid $grid) {
|
||||
$grid->model()->orderBy('created_at', 'desc');
|
||||
|
||||
$grid->column('id');
|
||||
$grid->column('store_id')->display(fn() => data_get($this->store, 'title'));
|
||||
$grid->column('name');
|
||||
// $grid->column('status')->switch();
|
||||
$grid->column('wxcode')->image('', 100, 100);
|
||||
$grid->column('remarks')->editable();
|
||||
|
||||
$user = Admin::user();
|
||||
|
||||
$grid->showViewButton($user->can('dcat.admin.store.desk.show'));
|
||||
$grid->showCreateButton($user->can('dcat.admin.store.desk.create'));
|
||||
$grid->showEditButton($user->can('dcat.admin.store.desk.edit'));
|
||||
$grid->showDeleteButton($user->can('dcat.admin.store.desk.destroy'));
|
||||
|
||||
$grid->filter(function (Grid\Filter $filter) {
|
||||
$filter->panel();
|
||||
$filter->equal('store_id')->select('api/store')->width(3);
|
||||
$filter->like('name')->width(3);
|
||||
});
|
||||
|
||||
$grid->actions(new \App\Admin\Actions\Store\RowDeskWxCode());
|
||||
});
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new Desk(), function (Form $form) {
|
||||
$form->select('store_id')->options(Store::pluck('title', 'id'))->required();
|
||||
$form->text('name')->required();
|
||||
// $form->switch('status')->default(1);
|
||||
$form->text('remarks');
|
||||
|
||||
$form->embeds('extra', function (Form\EmbeddedForm $form) {
|
||||
$form->select('category_id')->options(ProductCategory::selectOptions());
|
||||
});
|
||||
|
||||
$form->disableCreatingCheck();
|
||||
$form->disableEditingCheck();
|
||||
$form->disableDeleteButton();
|
||||
$form->disableResetButton();
|
||||
|
||||
$form->saved(function (Form $form, $result) {
|
||||
// 新增桌号, 生成小程序码
|
||||
if ($form->isCreating()) {
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function detail($id)
|
||||
{
|
||||
return Show::make($id, Desk::with(['store']), function (Show $show) {
|
||||
$show->field('id');
|
||||
$show->field('store_id')->as(fn() => data_get($this->store, 'name'));
|
||||
$show->field('name');
|
||||
// $show->field('status')->bool();
|
||||
$show->field('wxcode');
|
||||
$show->field('remarks');
|
||||
|
||||
$model = $show->model();
|
||||
$category_id = data_get($model->extra, 'category_id');
|
||||
if ($category_id) {
|
||||
$category = ProductCategory::find($category_id);
|
||||
$show->field('category_id')->as(fn() => $category ? $category->name : $category_id);
|
||||
}
|
||||
|
||||
$show->disableEditButton();
|
||||
$show->disableDeleteButton();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers\Store;
|
||||
|
||||
use Dcat\Admin\{Form, Grid, Admin, Show};
|
||||
use Dcat\Admin\Http\Controllers\AdminController;
|
||||
use App\Models\Store\{Store, Device, DeviceRecord};
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class DeviceController extends AdminController
|
||||
{
|
||||
protected $translation = 'store-device';
|
||||
|
||||
protected function grid()
|
||||
{
|
||||
return Grid::make(Device::with(['store']), function (Grid $grid) {
|
||||
$grid->model()->orderBy('id', 'desc');
|
||||
|
||||
$grid->column('store_id')->display(fn () => data_get($this->store, 'title'));
|
||||
$grid->column('name');
|
||||
$grid->column('device_no');
|
||||
$grid->column('status')->switch();
|
||||
$grid->column('remarks')->editable();
|
||||
|
||||
$user = Admin::user();
|
||||
|
||||
$grid->showViewButton($user->can('dcat.admin.store.device.show'));
|
||||
$grid->showCreateButton($user->can('dcat.admin.store.device.create'));
|
||||
$grid->showEditButton($user->can('dcat.admin.store.device.edit'));
|
||||
$grid->showDeleteButton($user->can('dcat.admin.store.device.destroy'));
|
||||
|
||||
$grid->filter(function (Grid\Filter $filter) {
|
||||
$filter->panel();
|
||||
$filter->equal('store_id')->select('api/store')->width(3);
|
||||
$filter->where('name', function ($q) {
|
||||
$input = $this->input;
|
||||
$q->where(fn($q1) => $q1->where('name', 'like', '%'.$input.'%')->orWhere('device_no', 'like', '%'.$input.'%'));
|
||||
})->placeholder('名称或者设备编号')->width(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function form()
|
||||
{
|
||||
return Form::make(new Device(), function (Form $form) {
|
||||
$unique = Rule::unique('store_devices', 'device_no');
|
||||
if ($form->isEditing()) {
|
||||
$unique->ignore($form->getKey());
|
||||
}
|
||||
|
||||
$form->select('store_id')->options(Store::pluck('title', 'id'))->required();
|
||||
$form->text('name')->required();
|
||||
$form->text('device_no')->rules([$unique])->required();
|
||||
$form->switch('status')->default(1);
|
||||
$form->text('remarks');
|
||||
|
||||
$form->disableCreatingCheck();
|
||||
$form->disableEditingCheck();
|
||||
$form->disableDeleteButton();
|
||||
$form->disableResetButton();
|
||||
|
||||
$form->deleted(function (Form $form, $result) {
|
||||
$data = array_column($form->model()->toArray(), 'id');
|
||||
// 删除设备记录
|
||||
DeviceRecord::whereIn('device_id', $data)->delete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected function detail($id)
|
||||
{
|
||||
return Show::make($id, Device::with(['store']), function (Show $show) {
|
||||
$show->field('id');
|
||||
$show->field('store_id');
|
||||
$show->field('name');
|
||||
$show->field('device_no');
|
||||
$show->field('status')->bool();
|
||||
$show->field('remarks');
|
||||
|
||||
$show->disableEditButton();
|
||||
$show->disableDeleteButton();
|
||||
|
||||
$show->relation('records', function ($model) {
|
||||
$grid = new Grid(new DeviceRecord());
|
||||
$grid->model()->where('device_id', $model->id)->orderBy('created_at', 'desc');
|
||||
|
||||
$grid->column('status')->using(DeviceRecord::$statusMap)->label(DeviceRecord::$statusColor);
|
||||
$grid->column('data')->display(fn($v) => json_encode($v, JSON_UNESCAPED_UNICODE));
|
||||
// $grid->column('result')->display(fn($v) => json_encode($v, JSON_UNESCAPED_UNICODE));
|
||||
$grid->column('created_at');
|
||||
|
||||
$grid->disableActions();
|
||||
$grid->disableCreateButton();
|
||||
|
||||
return $grid;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -194,8 +194,9 @@ class OrderController extends AdminController
|
|||
$show->field('pay_way')->as(function ($v) {
|
||||
return $this->pay_way?->mallText();
|
||||
})->circleDot(PayWay::colors());
|
||||
$show->field('pay_at');
|
||||
$show->field('created_at');
|
||||
$show->field('pay_at');
|
||||
$show->field('completed_at');
|
||||
|
||||
$show->panel()->tools(function ($tools) {
|
||||
$tools->disableEdit();
|
||||
|
|
|
|||
|
|
@ -164,4 +164,21 @@ class OrderPackageService
|
|||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单全部发货
|
||||
*
|
||||
* @param Order $order
|
||||
*
|
||||
* @return OrderPackage $package
|
||||
*/
|
||||
public function createAll(Order $order, $params = [])
|
||||
{
|
||||
$shipping_company = data_get($params, 'shipping_company');
|
||||
$shipping_number = data_get($params, 'shipping_number');
|
||||
$products = [];
|
||||
foreach($order->products as $item) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,6 +194,8 @@ Route::group([
|
|||
$router->resource('store/order', 'Store\OrderController')->only(['index', 'show'])->names('store.order');
|
||||
$router->resource('store/stock', 'Store\StockController')->only(['index', 'create', 'store', 'show'])->names('store.stock');
|
||||
$router->resource('store/batch', 'Store\StockBatchController')->names('store.batch');
|
||||
$router->resource('store/device', 'Store\DeviceController')->names('store.device');
|
||||
$router->resource('store/desk', 'Store\DeskController')->names('store.desk');
|
||||
|
||||
$router->resource('profit', 'OrderProfitController');
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ class OrderController extends Controller
|
|||
'product.sku_id' => ['bail', 'required', 'int'],
|
||||
'product.quantity' => ['bail', 'required', 'int', 'min:1'],
|
||||
],
|
||||
$request->filled('store_id') => [
|
||||
'products.*.sku_id' => ['bail', 'required', 'int'],
|
||||
'products.*.quantity' => ['bail', 'required', 'int', 'min:1'],
|
||||
],
|
||||
default => [
|
||||
'shopping_cart' => ['bail', 'required', 'array'],
|
||||
]
|
||||
|
|
@ -77,6 +81,16 @@ class OrderController extends Controller
|
|||
$orderService = new OrderService();
|
||||
|
||||
return match (true) {
|
||||
$request->filled('store_id') => $orderService->createOrderByStore(
|
||||
$user,
|
||||
$request->input('store_id'),
|
||||
$request->input('products'),
|
||||
[
|
||||
'desk' => $request->input('desk'),
|
||||
'note' => $request->input('note'),
|
||||
'coupon_id' => $request->input('coupon_id'),
|
||||
]
|
||||
),
|
||||
$request->filled('order_pre') => $orderService->createOrderByPre($user, OrderPre::findOrFail($request->input('order_pre')), $request->input('coupon_id'), $request->input('note')),
|
||||
$request->filled('product') => $orderService->createQuickOrder(
|
||||
$user,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ use App\Models\Store\ProductSku as StoreProductSku;
|
|||
use App\Endpoint\Api\Http\Resources\{StoreResource, StoreProductSkuResource, StoreProductSpuResource, ProductSpuResource};
|
||||
use App\Endpoint\Api\Http\Resources\ProductFeatureResource;
|
||||
use App\Events\ProductSkuViewed;
|
||||
use App\Models\Store\Desk;
|
||||
use App\Endpoint\Api\Http\Resources\DeskResource;
|
||||
|
||||
class StoreController extends Controller
|
||||
{
|
||||
|
|
@ -149,4 +151,10 @@ class StoreController extends Controller
|
|||
|
||||
return StoreProductSkuResource::make($sku);
|
||||
}
|
||||
|
||||
public function desk($id)
|
||||
{
|
||||
$info = Desk::with(['store'])->findOrFail($id);
|
||||
return DeskResource::make($info);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Endpoint\Api\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DeskResource extends JsonResource
|
||||
{
|
||||
public function toArray($request)
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'extra' => $this->extra,
|
||||
'name' => $this->name,
|
||||
'remarks' => $this->remarks,
|
||||
'status' => $this->status,
|
||||
'store_id' => $this->store_id,
|
||||
'wxcode' => $this->wxcode,
|
||||
'store' => StoreResource::make($this->whenLoaded('store')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -233,6 +233,9 @@ Route::group([
|
|||
Route::get('store/{store}/product-sku/{id}', [StoreController::class, 'productSku']);
|
||||
Route::apiResource('store', StoreController::class)->only(['index', 'show']);
|
||||
|
||||
// 门店桌号信息
|
||||
Route::get('store/desk/{id}', [StoreController::class, 'desk']);
|
||||
|
||||
// 门店下单
|
||||
Route::group(['middleware' => ['auth:api', \App\Endpoint\Api\Http\Middleware\CheckUserStatus::class]], function () {
|
||||
Route::get('order-pre/{id}', [App\Endpoint\Api\Http\Controllers\Order\OrderPreController::class, 'show']);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Models\Order;
|
||||
use App\Models\Store\{Desk, DeviceRecord};
|
||||
use App\Services\PrintService;
|
||||
|
||||
/**
|
||||
* 打印订单小票
|
||||
*/
|
||||
class OrderPrint
|
||||
{
|
||||
public function handle($event)
|
||||
{
|
||||
$order = $event->order;
|
||||
// 门店订单, 桌号信息
|
||||
if ($order->store_id && $order->source_type == Desk::class) {
|
||||
$store = $order->store;
|
||||
$desk = $order->source;
|
||||
|
||||
// 门店设备
|
||||
$devices = $store->devices()->where('status', 1)->get();
|
||||
$templateId = app_settings('custom.key_value.order_desk_print_template');
|
||||
if ($devices->count() > 0 && $templateId) {
|
||||
$service = PrintService::make();
|
||||
$products = [];
|
||||
foreach($order->products as $item) {
|
||||
array_push($products, [
|
||||
'name' => $item->name,
|
||||
'price' => round($item->sell_price / 100, 2, PHP_ROUND_HALF_DOWN),
|
||||
'amount' => $item->quantity,
|
||||
'money' => round($item->total_amount / 100, 2, PHP_ROUND_HALF_DOWN),
|
||||
]);
|
||||
}
|
||||
$data = [
|
||||
'name' => $store->title,
|
||||
'sn' => $order->sn,
|
||||
'time' => $order->created_at->format('Y-m-d H:i:s'),
|
||||
'desk' => $desk->name,
|
||||
'products' => $products,
|
||||
'total' => round($order->total_amount, 2, PHP_ROUND_HALF_DOWN),
|
||||
'remarks' => $order->note,
|
||||
];
|
||||
foreach($devices as $item) {
|
||||
$result = $service->template($item->device_no, $templateId, $data);
|
||||
$status = data_get($result, 'code') == 0 ? DeviceRecord::STATUS_SUCCESS : DeviceRecord::STATUS_FAIL;
|
||||
// 添加打印的日志记录
|
||||
$item->records()->create([
|
||||
'data' => $data,
|
||||
'device_id' => $item->id,
|
||||
'result' => $result,
|
||||
'resource_id' => $order->id,
|
||||
'resource_type' => $order->getMorphClass(),
|
||||
'status' => $status,
|
||||
'store_id' => $store->id
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Store;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
use EasyWeChat\Kernel\Http\StreamResponse;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Desk extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
protected $table = 'store_desks';
|
||||
|
||||
protected $fillable = ['extra', 'name', 'remarks', 'status', 'store_id', 'wxcode'];
|
||||
|
||||
protected $casts = [
|
||||
'extra' => 'json'
|
||||
];
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->belongsTo(Store::class, 'store_id');
|
||||
}
|
||||
|
||||
public function generateWxCode()
|
||||
{
|
||||
$model = $this;
|
||||
$scene = http_build_query([
|
||||
'd' => $model->id,
|
||||
'c' => data_get($model->extra, 'category_id'),
|
||||
's' => $model->store_id
|
||||
]);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
$url = 'https://ui-avatars.com/api/?name=pd&scene=' . urlencode($scene);
|
||||
} else {
|
||||
// 生成小程序码
|
||||
$app = \EasyWeChat\Factory::miniProgram(config('wechat.mini_program.default'));
|
||||
$response = $app->app_code->getUnlimit($scene, [
|
||||
'page' => 'pages/welcome/index',
|
||||
'check_path' => false,
|
||||
// release: 正式版, develop: 开发版, trial: 体验版
|
||||
'env_version' => config('app.debug') ? 'trial' : 'release',
|
||||
'width' => 200,
|
||||
]);
|
||||
|
||||
// 保存小程序码
|
||||
if ($response instanceof StreamResponse) {
|
||||
$disk = Storage::disk('public');
|
||||
$filepath = 'store-desk';
|
||||
$filename = $model->id . '.png';
|
||||
$response->saveAs($disk->path($filepath), $filename);
|
||||
$url = $disk->url($filepath . '/' . $filename);
|
||||
} else {
|
||||
logger('store-desk 小程序码生成失败', $response);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
$model->update(['wxcode' => $url]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Store;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
|
||||
class Device extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
protected $table = 'store_devices';
|
||||
|
||||
protected $fillable = ['device_no', 'extra', 'name', 'remarks', 'status', 'store_id', 'type'];
|
||||
|
||||
protected $casts = [
|
||||
'extra' => 'json'
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'type' => 'print',
|
||||
'status' => 1
|
||||
];
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->belongsTo(Store::class, 'store_id');
|
||||
}
|
||||
|
||||
public function records()
|
||||
{
|
||||
return $this->hasMany(DeviceRecord::class, 'device_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models\Store;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Dcat\Admin\Traits\HasDateTimeFormatter;
|
||||
|
||||
class DeviceRecord extends Model
|
||||
{
|
||||
use HasDateTimeFormatter;
|
||||
|
||||
public const STATUS_PROCESSING = 0;
|
||||
public const STATUS_SUCCESS = 1;
|
||||
public const STATUS_FAIL = 2;
|
||||
|
||||
protected $table = 'store_device_records';
|
||||
|
||||
protected $fillable = ['data', 'device_id', 'result', 'resource_id', 'resource_type', 'status', 'store_id'];
|
||||
|
||||
protected $casts = [
|
||||
'result' => 'json',
|
||||
'data' => 'json',
|
||||
];
|
||||
|
||||
public static $statusMap = [
|
||||
self::STATUS_PROCESSING => '处理中',
|
||||
self::STATUS_SUCCESS => '成功',
|
||||
self::STATUS_FAIL => '失败',
|
||||
];
|
||||
|
||||
public static $statusColor = [
|
||||
self::STATUS_PROCESSING => 'primary',
|
||||
self::STATUS_SUCCESS => 'success',
|
||||
self::STATUS_FAIL => 'danger',
|
||||
];
|
||||
|
||||
public function device()
|
||||
{
|
||||
return $this->belongsTo(Device::class, 'device_id');
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
return $this->belongsTo(Store::class, 'store_id');
|
||||
}
|
||||
|
||||
public function resource()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,16 @@ class Store extends Model
|
|||
return $this->hasMany(StockLog::class, 'store_id');
|
||||
}
|
||||
|
||||
public function desks()
|
||||
{
|
||||
return $this->hasMany(Desk::class, 'store_id');
|
||||
}
|
||||
|
||||
public function devices()
|
||||
{
|
||||
return $this->hasMany(Device::class, 'store_id');
|
||||
}
|
||||
|
||||
public function scopeEffective($q)
|
||||
{
|
||||
return $q->where('status', 1);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ class EventServiceProvider extends ServiceProvider
|
|||
\App\Listeners\OrderPaidNotify::class,
|
||||
\App\Listeners\SendCoupons::class,
|
||||
\App\Listeners\OrderPackage::class,
|
||||
\App\Listeners\OrderDistribute::class
|
||||
\App\Listeners\OrderDistribute::class,
|
||||
\App\Listeners\OrderPrint::class,
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use App\Models\ProductSku;
|
|||
use App\Models\ShippingAddress;
|
||||
use App\Models\SocialiteUser;
|
||||
use App\Models\{User, OrderPre, Tag};
|
||||
use App\Models\Store\Store;
|
||||
use App\Models\Store\{Store, Desk};
|
||||
use App\Models\UserCoupon;
|
||||
use App\Services\Payment\WxpayService;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
|
@ -171,8 +171,7 @@ class OrderService
|
|||
}
|
||||
|
||||
/**
|
||||
* 添加店铺订单
|
||||
*
|
||||
* 预订单, 扫码下单
|
||||
*/
|
||||
public function createOrderByPre(User $user, OrderPre $order_pre, $coupon_id = null, $note = null)
|
||||
{
|
||||
|
|
@ -236,6 +235,100 @@ class OrderService
|
|||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 门店直接下单
|
||||
*
|
||||
* @param User $user
|
||||
* @param int $storeId
|
||||
* @param array $products 商品信息 [{sku_id, quantity}]
|
||||
* @param array $params {coupon_id: 优惠券id, desk: 桌号id}
|
||||
*
|
||||
* @return Order
|
||||
* @throws BizException
|
||||
*/
|
||||
public function createOrderByStore(User $user, $storeId, array $products, array $params = [])
|
||||
{
|
||||
$store = Store::find($storeId);
|
||||
if (!$store) {
|
||||
throw new BizException('门店不存在');
|
||||
}
|
||||
if (!$store->status) {
|
||||
throw new BizException('门店: '.$store->title.' 已关闭');
|
||||
}
|
||||
$coupon_id = data_get($params, 'coupon_id');
|
||||
$note = data_get($params, 'note');
|
||||
$sourceType = Store::class;
|
||||
$sourceId = $store->id;
|
||||
|
||||
// 桌号下单
|
||||
if ($deskId = data_get($params, 'desk')) {
|
||||
$desk = $store->desks()->find($deskId);
|
||||
if (!$desk) {
|
||||
throw new BizException('门店桌号不存在');
|
||||
}
|
||||
if (!$desk->status) {
|
||||
throw new BizException('门店桌号: '.$desk->name.' 已关闭');
|
||||
}
|
||||
$sourceType = Desk::class;
|
||||
$sourceId = $desk->id;
|
||||
}
|
||||
|
||||
// 检查门店商品库存
|
||||
$skuList = $store->productSkus;
|
||||
foreach($products as &$item) {
|
||||
$sku = $skuList->firstWhere('id', $item['sku_id']);
|
||||
if (!$sku) {
|
||||
throw new BizException('门店商品: '.$item->name.' 不存在');
|
||||
}
|
||||
if ($sku->pivot->amount < $item['quantity']) {
|
||||
throw new BizException('门店商品: '.$item->name.' 库存不足');
|
||||
}
|
||||
$item['sku'] = $sku;
|
||||
}
|
||||
|
||||
// 优惠券
|
||||
$coupon = null;
|
||||
|
||||
if ($coupon_id) {
|
||||
$coupon = $user->coupons()->onlyAvailable()->lockForUpdate()->findOrFail($coupon_id);
|
||||
}
|
||||
|
||||
$mapProducts = $this->mapProducts($user, $products, $coupon);
|
||||
|
||||
list($productsTotalAmount, $vipDiscountAmount, $couponDiscountAmount, $salesValue) = $this->calculateFees($mapProducts);
|
||||
|
||||
$order = $this->storeOrder(
|
||||
$user,
|
||||
$productsTotalAmount,
|
||||
$couponDiscountAmount,
|
||||
$vipDiscountAmount,
|
||||
0,
|
||||
$salesValue,
|
||||
null,
|
||||
$note,
|
||||
$coupon,
|
||||
null,
|
||||
$mapProducts
|
||||
);
|
||||
$order->update([
|
||||
'store_id' => $store->id,
|
||||
'source_type' => $sourceType,
|
||||
'source_id' => $sourceId,
|
||||
]);
|
||||
$this->storeOrderProducts($order, $mapProducts);
|
||||
|
||||
// 将优惠券标记为已使用
|
||||
$coupon?->markAsUse();
|
||||
|
||||
if ($order->total_amount === 0) {
|
||||
$this->pay($order, PayWay::Balance);
|
||||
|
||||
$order->refresh();
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存订单
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
/**
|
||||
* 佳博云打印
|
||||
* https://dev.poscom.cn/docs
|
||||
*/
|
||||
class PrintService
|
||||
{
|
||||
protected $memberCode;
|
||||
protected $apiKey;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->memberCode = config('postcom.memberCode');
|
||||
$this->apiKey = config('postcom.apiKey');
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过模板打印
|
||||
* https://dev.poscom.cn/openapi?templetPrint
|
||||
*
|
||||
* @param string $deviceID
|
||||
* @param string $templetID
|
||||
* @param array $data
|
||||
*
|
||||
* @return array {code: 0(成功), msg: '错误信息'}
|
||||
*/
|
||||
public function template($deviceID, $templetID, $data)
|
||||
{
|
||||
$result = ['code' => 0, 'msg' => ''];
|
||||
if (config('app.env') === 'local') {
|
||||
return $result;
|
||||
}
|
||||
$url = 'https://api.poscom.cn/apisc/templetPrint';
|
||||
$params = $this->params(['deviceID' => $deviceID]);
|
||||
$params['templetID'] = $templetID;
|
||||
$params['tData'] = json_encode($data);
|
||||
|
||||
$response = Http::asForm()->post($url, $params);
|
||||
if ($response->successful()) {
|
||||
$result = $response->json();
|
||||
} else {
|
||||
$result['code'] = 1;
|
||||
$result['msg'] = '请求失败: ' . $response->status();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造请求参数
|
||||
*
|
||||
* @param array $data {deviceID: 终端编号}
|
||||
* @return array
|
||||
*/
|
||||
protected function params($data = [])
|
||||
{
|
||||
$reqTime = $this->getMillisecond();
|
||||
$deviceID = data_get($data, 'deviceID');
|
||||
$memberCode = $this->memberCode;
|
||||
$msgNo = '';
|
||||
$securityCode = md5($memberCode.$deviceID.$msgNo.$reqTime.$this->apiKey);
|
||||
return [
|
||||
'reqTime' => $reqTime,
|
||||
'securityCode' => $securityCode,
|
||||
'memberCode' => $memberCode,
|
||||
'deviceID' => $deviceID,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getMillisecond()
|
||||
{
|
||||
list($t1, $t2) = explode(' ', microtime());
|
||||
return (float)sprintf('%.0f',(floatval($t1)+floatval($t2))*1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PrintService
|
||||
*/
|
||||
public static function make(...$params)
|
||||
{
|
||||
return new static(...$params);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'member_code' => env('POSTCOM_MEMBER_CODE'),
|
||||
'api_key' => env('POSTCOM_API_KEY'),
|
||||
];
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use App\Models\Store\{Store, Device};
|
||||
|
||||
class StoreDeviceFactory extends Factory
|
||||
{
|
||||
protected $model = Device::class;
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
do {
|
||||
$code = $this->faker->isbn10();
|
||||
} while(Device::where('device_no', $code)->exists());
|
||||
return [
|
||||
'device_no' => $code,
|
||||
'name' => $code,
|
||||
'store_id' => Store::inRandomOrder()->value('id')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use App\Models\Store\{Device, DeviceRecord};
|
||||
|
||||
class StoreDeviceRecordFactory extends Factory
|
||||
{
|
||||
protected $model = DeviceRecord::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
$device = Device::inRandomOrder()->first();
|
||||
$status = $this->faker->randomElement([DeviceRecord::STATUS_SUCCESS, DeviceRecord::STATUS_FAIL]);
|
||||
$data = [
|
||||
'title' => $this->faker->word(),
|
||||
'description' => $this->faker->sentence(),
|
||||
'content' => $this->faker->paragraph(),
|
||||
];
|
||||
$result_success = ['code' => 0, 'msg' => '成功'];
|
||||
$result_fail = ['code' => 1, 'msg' => '请求超时'];
|
||||
return [
|
||||
'data' => $data,
|
||||
'device_id' => $device->id,
|
||||
'result' => $status ? $result_success : $result_fail,
|
||||
'status' => $status,
|
||||
'store_id' => $device->store_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreatePrintDeskTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('store_devices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('store_id')->comment('门店 stores.id');
|
||||
$table->string('type')->default('print')->comment('设备类型(print: 小票打印机)');
|
||||
$table->string('name')->comment('设备名称');
|
||||
$table->string('device_no')->comment('设备编号');
|
||||
$table->unsignedTinyInteger('status')->default(0)->comment('状态(0: 不可用, 1: 可用)');
|
||||
$table->string('remarks')->nullable()->comment('备注');
|
||||
$table->json('extra')->nullable()->comment('其他配置');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('store_device_records', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('store_id')->comment('门店 stores.id');
|
||||
$table->unsignedBigInteger('device_id')->comment('设备 store_devices.id');
|
||||
$table->json('result')->nullable()->comment('返回的结果');
|
||||
$table->json('data')->nullable()->comment('请求内容');
|
||||
$table->unsignedTinyInteger('status')->default(0)->comment('状态(0: 处理中, 1: 成功, 2: 失败)');
|
||||
$table->nullableMorphs('resource');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('store_desks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('store_id')->comment('门店 stores.id');
|
||||
$table->string('name')->comment('名称');
|
||||
$table->unsignedTinyInteger('status')->default(0)->comment('状态(0: 不可用, 1: 可用)');
|
||||
$table->string('wxcode')->nullable()->comment('小程序码');
|
||||
$table->string('remarks')->nullable()->comment('备注');
|
||||
$table->json('extra')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('store_devices');
|
||||
Schema::dropIfExists('store_device_records');
|
||||
Schema::dropIfExists('store_desks');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Store\{Store, Device, DeviceRecord};
|
||||
use Database\Factories\{StoreDeviceFactory, StoreDeviceRecordFactory};
|
||||
|
||||
class StoreDeviceSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Device::truncate();
|
||||
DeviceRecord::truncate();
|
||||
(new StoreDeviceFactory())->count(10)->create();
|
||||
(new StoreDeviceRecordFactory())->count(100)->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'store' => '门店管理',
|
||||
'Desk' => '桌号管理',
|
||||
'desk' => '桌号管理',
|
||||
],
|
||||
'fields' => [
|
||||
'store_id' => '门店',
|
||||
'name' => '名称',
|
||||
'status' => '状态',
|
||||
'remarks' => '备注',
|
||||
'extra' => '其他',
|
||||
'wxcode' => '小程序码',
|
||||
'category_id' => '商品分类'
|
||||
],
|
||||
'options' => [
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'labels' => [
|
||||
'store' => '门店管理',
|
||||
'Device' => '设备管理',
|
||||
'device' => '设备管理',
|
||||
],
|
||||
'fields' => [
|
||||
'store_id' => '门店',
|
||||
'name' => '名称',
|
||||
'device_no' => '编号',
|
||||
'status' => '状态',
|
||||
'remarks' => '备注',
|
||||
'extra' => '其他',
|
||||
'records' => '日志',
|
||||
'data' => '发送',
|
||||
'result' => '接收',
|
||||
],
|
||||
'options' => [
|
||||
],
|
||||
];
|
||||
|
|
@ -2,11 +2,8 @@
|
|||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\OrderProfit;
|
||||
use App\Services\DistributeService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
use App\Services\PrintService;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
|
|
@ -17,13 +14,17 @@ class ExampleTest extends TestCase
|
|||
*/
|
||||
public function test_example()
|
||||
{
|
||||
$service = new DistributeService();
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$service->wechatTransfers(OrderProfit::whereIn('id', [1, 5])->get());
|
||||
DB::commit();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
}
|
||||
dd(PrintService::make()->template('00596456352258754', 'a4fcc59f40fd438d9afdd4fc2e9b99fc', [
|
||||
'name' => '乡村基',
|
||||
'sn' => '20230223133856127032',
|
||||
'time' => '2023-02-23 13:38:56',
|
||||
'desk' => '10号桌',
|
||||
'products' => [
|
||||
['name' => '香草拿铁(大杯)', 'price' => '32.00', 'amount' => '1', 'money' => '32.00'],
|
||||
['name' => '西瓜汁(大杯)', 'price' => '18.00', 'amount' => '1', 'money' => '18.00'],
|
||||
],
|
||||
'total' => '50.00',
|
||||
'remarks' => '打包'
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue