6
0
Fork 0
* 调整签约发货
* 调整签约进货设置本地库存问题
* 优化订单商品导出
* 【后台】导出商城订单商品
* Update
* 调整云仓库存不足信息提示
* 修改撤销订单发货bug
* 调试取消提货单撤回订单已发的本地货
* 微信支付JSAPI
* 调整取消支付
* 补充撤销支付,并调整日志排序
* 调整签约 云仓发货
* 调整签约发货订单库存问题
* WIP
* 调整经销商发货报错
* 调整签约发货
* 调整签约发货内容
* 添加签约云仓发货
* Fix
* 【后台】签约渠道补贴
* 【后台】进货补贴优化
* Update
* 经销商商品变更数量格式化
* 【后台】优化管理者津贴和管理津贴
* 【后台】优化支付状态显示
* 【后台】优化管理津贴状态显示
* 【后台】优化管理者津贴状态显示
* 提货单支付回调处理
* 批零商品变更记录
* 【后台】云仓提货单
* 添加圆点扩展
* 云仓运费配置
* 取消提货单
* 更新进货业绩缓存有效期
* 修改支付完成云仓库发货
* WIP
* 提货单列表和详情
* 创建提货单,提货单付款
* 处理线下付款增加库存
* 修改经销商配置返回手续费率
* 添加经销商配置返回手续费率
* 完善进货云库存
* 处理合并
* 添加后台操作打款补贴时的支付方式
* 取消批零提现时间间隔限制
* 添加自定义配置管理
* 用户商品托管库存
* 新增用户商品托管库存字段
release
李静 2022-03-17 06:50:46 +00:00
parent aaecfccb78
commit 2d057fac9c
78 changed files with 2705 additions and 357 deletions

View File

@ -0,0 +1,53 @@
<?php
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerEarning;
use Dcat\Admin\Grid\BatchAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Throwable;
class DealerChannelSubsidyBatchPay extends BatchAction
{
protected $title = '<i class="feather grid-action-icon icon-stop-circle"></i> 付款';
/**
* @param Model|Authenticatable|HasPermissions|null $user
*
* @return bool
*/
protected function authorize($user): bool
{
return $user->can('dcat.admin.dealer_channel_subsidies.batch_pay');
}
// 确认弹窗信息
public function confirm()
{
return '您确定要支付选中的渠道补贴吗?';
}
// 处理请求
public function handle(Request $request)
{
try {
DB::transaction(function () {
foreach ($this->getKey() as $id) {
$dealerEarning = DealerEarning::lockForUpdate()->channelSubsidy()->withoutPayer()->find($id);
if (! $dealerEarning?->isSettled() || ! $dealerEarning?->isPending()) {
continue;
}
(new DealerEarningService())->pay($dealerEarning);
}
});
} catch (Throwable $e) {
return $this->response()->error('操作失败:'.$e->getMessage())->refresh();
}
return $this->response()->success('操作成功')->refresh();
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerEarning;
use Dcat\Admin\Grid\RowAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class DealerChannelSubsidyPay extends RowAction
{
protected $title = '<i class="feather grid-action-icon icon-stop-circle"></i> 付款';
/**
* @param Model|Authenticatable|HasPermissions|null $user
*
* @return bool
*/
protected function authorize($user): bool
{
return $user->can('dcat.admin.dealer_channel_subsidies.pay');
}
// 确认弹窗信息
public function confirm()
{
return '您确定要支付选中的渠道补贴吗?';
}
// 处理请求
public function handle(Request $request)
{
DB::transaction(function () {
$id = $this->getKey();
$dealerEarning = DealerEarning::lockForUpdate()->channelSubsidy()->withoutPayer()->find($id);
(new DealerEarningService())->pay($dealerEarning);
});
return $this->response()->success('操作成功')->refresh();
}
}

View File

@ -2,8 +2,8 @@
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerManageSubsidy;
use App\Services\Dealer\ManageSubsidyService;
use Dcat\Admin\Grid\BatchAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@ -38,7 +38,9 @@ class DealerManageSubsidyBatchPay extends BatchAction
$dealerManageSubsidy = DealerManageSubsidy::lockForUpdate()->settled()->find($id);
if ($dealerManageSubsidy?->isPending()) {
(new ManageSubsidyService())->pay($dealerManageSubsidy);
(new DealerEarningService())->pay(
$dealerManageSubsidy->earning->setRelation('earningable', $dealerManageSubsidy)
);
}
}
});

View File

@ -2,8 +2,8 @@
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerManageSubsidy;
use App\Services\Dealer\ManageSubsidyService;
use Dcat\Admin\Grid\RowAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@ -34,8 +34,10 @@ class DealerManageSubsidyPay extends RowAction
DB::transaction(function () {
$id = $this->getKey();
(new ManageSubsidyService())->pay(
DealerManageSubsidy::lockForUpdate()->settled()->findOrFail($id)
$dealerManageSubsidy = DealerManageSubsidy::lockForUpdate()->settled()->findOrFail($id);
(new DealerEarningService())->pay(
$dealerManageSubsidy->earning->setRelation('earningable', $dealerManageSubsidy)
);
});

View File

@ -2,8 +2,8 @@
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerManagerSubsidy;
use App\Services\Dealer\ManagerSubsidyService;
use Dcat\Admin\Grid\BatchAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@ -38,7 +38,9 @@ class DealerManagerSubsidyBatchPay extends BatchAction
$dealerManagerSubsidy = DealerManagerSubsidy::lockForUpdate()->settled()->find($id);
if ($dealerManagerSubsidy?->isPending()) {
(new ManagerSubsidyService())->pay($dealerManagerSubsidy);
(new DealerEarningService())->pay(
$dealerManagerSubsidy->earning->setRelation('earningable', $dealerManagerSubsidy)
);
}
}
});

View File

@ -2,8 +2,8 @@
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerManagerSubsidy;
use App\Services\Dealer\ManagerSubsidyService;
use Dcat\Admin\Grid\RowAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@ -34,8 +34,10 @@ class DealerManagerSubsidyPay extends RowAction
DB::transaction(function () {
$id = $this->getKey();
(new ManagerSubsidyService())->pay(
DealerManagerSubsidy::lockForUpdate()->settled()->findOrFail($id)
$dealerManagerSubsidy = DealerManagerSubsidy::lockForUpdate()->settled()->findOrFail($id);
(new DealerEarningService())->pay(
$dealerManagerSubsidy->earning->setRelation('earningable', $dealerManagerSubsidy)
);
});

View File

@ -0,0 +1,53 @@
<?php
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerPurchaseSubsidy;
use Dcat\Admin\Grid\BatchAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Throwable;
class DealerPurchaseSubsidyBatchPay extends BatchAction
{
protected $title = '<i class="feather grid-action-icon icon-stop-circle"></i> 付款';
/**
* @param Model|Authenticatable|HasPermissions|null $user
*
* @return bool
*/
protected function authorize($user): bool
{
return $user->can('dcat.admin.dealer_purchase_subsidies.batch_pay');
}
// 确认弹窗信息
public function confirm()
{
return '您确定要支付选中的进货补贴吗?';
}
// 处理请求
public function handle(Request $request)
{
try {
DB::transaction(function () {
foreach ($this->getKey() as $id) {
$dealerPurchaseSubsidy = DealerPurchaseSubsidy::lockForUpdate()->settleCompleted()->find($id);
if ($dealerPurchaseSubsidy?->isPending()) {
(new DealerEarningService())->pay(
$dealerPurchaseSubsidy->earning->setRelation('earningable', $dealerPurchaseSubsidy)
);
}
}
});
} catch (Throwable $e) {
return $this->response()->error('操作失败:'.$e->getMessage())->refresh();
}
return $this->response()->success('操作成功')->refresh();
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Admin\Actions\Grid;
use App\Admin\Services\DealerEarningService;
use App\Models\DealerPurchaseSubsidy;
use Dcat\Admin\Grid\RowAction;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class DealerPurchaseSubsidyPay extends RowAction
{
protected $title = '<i class="feather grid-action-icon icon-stop-circle"></i> 付款';
/**
* @param Model|Authenticatable|HasPermissions|null $user
*
* @return bool
*/
protected function authorize($user): bool
{
return $user->can('dcat.admin.dealer_purchase_subsidies.pay');
}
// 确认弹窗信息
public function confirm()
{
return '您确定要支付选中的进货补贴吗?';
}
// 处理请求
public function handle(Request $request)
{
DB::transaction(function () {
$id = $this->getKey();
$dealerPurchaseSubsidy = DealerPurchaseSubsidy::lockForUpdate()->settleCompleted()->findOrFail($id);
(new DealerEarningService())->pay(
$dealerPurchaseSubsidy->earning->setRelation('earningable', $dealerPurchaseSubsidy)
);
});
return $this->response()->success('操作成功')->refresh();
}
}

View File

@ -12,6 +12,8 @@ use Illuminate\Http\Request;
class ShippingOrder extends RowAction
{
protected $htmlClasses = ['btn', 'btn-primary'];
public function title()
{
if ($this->title) {

View File

@ -49,6 +49,7 @@ class DealerEarningPay extends AbstractTool
DB::beginTransaction();
$earning = DealerEarning::findOrFail($key);
$earning->update([
'pay_way' => DealerEarning::PAY_WAY_WALLET,
'pay_info' => $earning->getPayInfo(),
'pay_at' => now(),
'status' => DealerEarningStatus::Completed,

View File

@ -0,0 +1,141 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Grid\DealerChannelSubsidyBatchPay;
use App\Admin\Actions\Grid\DealerChannelSubsidyPay;
use App\Admin\Repositories\DealerEarning;
use App\Admin\Widgets\InfoBox;
use App\Enums\DealerEarningStatus;
use App\Models\DealerEarning as DealerEarningModel;
use Dcat\Admin\Admin;
use Dcat\Admin\Grid;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Layout\Row;
class DealerChannelSubsidyController extends AdminController
{
protected $title = '签约渠道补贴';
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
Admin::style(
<<<CSS
.card-header {
margin-top: 1.5rem !important;
margin-bottom: -1rem !important;
}
CSS
);
$builder = DealerEarning::with(['user.userInfo']);
return Grid::make($builder, function (Grid $grid) {
$grid->model()->channelSubsidy()->withoutPayer()->orderBy('id', 'desc');
$grid->column('id')->sortable();
$grid->column('user.phone', '手机号');
$grid->column('user.userInfo.nickname', '昵称');
$grid->column('lvl', '经销商等级')->display(function () {
return $this->lvl->text();
});
$grid->column('total_amount', '补贴金额')->prepend('¥');
$grid->column('fee', '手续费')->prepend('¥')->help('手续费=补贴金额*手续费率');
$grid->column('fee_rate', '手续费率')->append('%');
$grid->column('total_earnings', '总收入')->prepend('¥')->help('总收入=补贴金额-手续费');
$grid->column('remark', '备注')->display('查看')->modal(function ($modal) {
$modal->title('备注');
return '<div>'.nl2br($this->remark).'</div>';
});
$grid->column('settle_at', '结算时间')->display(function () {
return $this->settle_at?->toDateTimeString();
})->sortable();
$grid->column('status', '状态')->display(function ($v) {
if (! $this->isSettled()) {
return "<i class='fa fa-circle' style='font-size: 13px;color: #b9c3cd'></i>&nbsp;&nbsp;待结算";
}
return "<i class='fa fa-circle' style='font-size: 13px;color: {$v->color()}'></i>&nbsp;&nbsp;{$v->text()}";
});
$grid->column('pay_at', '付款时间')->display(function () {
return $this->pay_at?->toDateTimeString();
})->sortable();
$grid->column('created_at', '创建时间')->display(function () {
return $this->created_at?->toDateTimeString();
});
$grid->showRowSelector();
$grid->tools(function ($tools) {
$tools->batch(function ($batch) {
$batch->disableDelete();
if (Admin::user()->can('dcat.admin.dealer_channel_subsidies.batch_pay')) {
$batch->add(new DealerChannelSubsidyBatchPay());
}
});
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (
$actions->row->isSettled() &&
$actions->row->isPending() &&
Admin::user()->can('dcat.admin.dealer_channel_subsidies.pay')
) {
$actions->append(new DealerChannelSubsidyPay());
}
});
$grid->filter(function (Grid\Filter $filter) {
$filter->panel();
$filter->equal('user.phone', '手机号')->width(3);
$filter->where('status', function ($query) {
switch ($this->input) {
case 'pending':
$query->whereNull('settle_at')->where('status', DealerEarningStatus::Pending);
break;
case 'paying':
$query->whereNotNull('settle_at')->where('status', DealerEarningStatus::Pending);
break;
case 'completed':
$query->where('status', DealerEarningStatus::Completed);
break;
}
}, '状态')->select([
'pending' => '待结算',
'paying' => '待付款',
'completed' => '已完成',
])->width(3);
$filter->between('settle_at', '结算时间')->datetime()->width(6);
});
$grid->header(function ($collection) use ($grid) {
return tap(new Row(), function ($row) use ($grid) {
$query = DealerEarningModel::query();
$grid->model()->getQueries()->unique()->each(function ($value) use (&$query) {
if (in_array($value['method'], ['paginate', 'get', 'orderBy', 'orderByDesc'], true)) {
return;
}
$query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
});
$totalAmount = (clone $query)->sum('total_amount');
$totalFee = (clone $query)->sum('fee');
$row->column(3, new InfoBox('补贴金额', $totalAmount, 'fa fa-cny'));
$row->column(3, new InfoBox('手续费', $totalFee, 'fa fa-cny'));
});
});
});
}
}

View File

@ -188,7 +188,8 @@ class DealerController extends AdminController
$productGrid = Grid::make($builder, function (Grid $grid) {
$grid->column('product.name', '商品名称');
$grid->column('product.cover', '商品封面')->image(80, 80);
$grid->column('stock', '剩余库存');
$grid->column('stock', '库存');
$grid->column('deposit_stock', '云库存');
$grid->column('created_at', '创建时间');
$grid->column('logs', '库存记录')->display('查看')->modal(function ($modal) {
$modal->title('商品');

View File

@ -0,0 +1,134 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Repositories\DealerDeliveryBill;
use App\Admin\Repositories\DealerDeliveryProduct;
use App\Enums\DealerDeliveryBillStatus;
use App\Enums\PayWay;
use Dcat\Admin\Admin;
use Dcat\Admin\Grid;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Layout\Row;
use Dcat\Admin\Show;
use Dcat\Admin\Widgets\Box;
class DealerDeliveryBillController extends AdminController
{
protected $title = '云仓提货单';
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
$repository = DealerDeliveryBill::with(['user.userInfo']);
return Grid::make($repository, function (Grid $grid) {
$grid->model()->orderBy('id', 'desc');
$grid->column('id')->sortable();
$grid->column('sn', '提货单号');
$grid->column('user.phone', '手机号');
$grid->column('user.userInfo.nickname', '昵称');
$grid->column('shipping_fee', '运费')->prepend('¥');
$grid->column('pay_way', '支付方式')->display(function ($v) {
return $v?->text();
})->circleDot(PayWay::colors());
$grid->column('pay_at', '付款时间')->display(function ($v) {
return $v?->toDateTimeString();
});
$grid->column('status', '状态')->display(function ($v) {
return $v?->text();
})->circleDot(DealerDeliveryBillStatus::colors());
$grid->column('created_at', '创建时间')->display(function ($v) {
return $v?->toDateTimeString();
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (Admin::user()->can('dcat.admin.dealer_orders.show')) {
$actions->append('<a style="cursor: pointer;" target="_blank" href="'.admin_route('dealer_delivery_bills.show', [$actions->row]).'"><i class="feather icon-eye"></i> 显示 &nbsp;&nbsp;</a>');
}
});
$grid->filter(function (Grid\Filter $filter) {
$filter->panel();
$filter->like('sn', '提货单号')->width(4);
$filter->like('user.phone', '手机号')->width(4);
$filter->equal('status', '状态')->select(DealerDeliveryBillStatus::texts())->width(4);
$filter->between('pay_at', '付款时间')->dateTime()->width(4);
$filter->between('created_at', '创建时间')->dateTime()->width(4);
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return function (Row $row) use ($id) {
$row->column(5, function ($column) use ($id) {
$builder = DealerDeliveryBill::with(['user.userInfo']);
$column->row(Show::make($id, $builder, function (Show $show) {
$show->field('sn', '提货单号');
$show->field('user.phone', '手机号');
$show->field('user.user_info.nickname', '昵称');
$show->field('shipping_fee', '运费');
$show->field('remark', '备注');
$show->field('status', '状态')->as(function () {
return $this->status?->text();
})->circleDot(DealerDeliveryBillStatus::colors());
$show->field('created_at')->as(function ($v) {
return $this->created_at->toDateTimeString();
});
$show->divider();
$show->field('consignee_name', '收货人');
$show->field('consignee_telephone', '联系方式');
$show->field('consignee', '收货地址')->as(function () {
return $this->consignee_zone . ' '. $this->consignee_address;
});
$show->divider();
$show->field('pay_sn', '支付单号');
$show->field('pay_way', '支付方式')->as(function () {
return $this->pay_way?->text();
})->circleDot(PayWay::colors());
$show->field('pay_at', '付款时间')->as(function () {
return $this->pay_at?->toDateTimeString();
});
$show->field('out_trade_no', '外部交易号');
$show->panel()
->tools(function (Show\Tools $tools) use ($show) {
$tools->disableEdit();
$tools->disableDelete();
});
}));
});
$row->column(7, function ($column) use ($id) {
$repository = DealerDeliveryProduct::with(['product']);
$column->row(Box::make('提货单商品', Grid::make($repository, function (Grid $grid) use ($id) {
$grid->model()->where('delivery_bill_id', $id);
$grid->column('product.name', '名称');
$grid->column('product.cover', '封面')->image(50, 50);
$grid->column('qty', '数量');
$grid->disableActions();
$grid->disablePagination();
$grid->disableRefreshButton();
})));
});
};
}
}

View File

@ -62,8 +62,8 @@ class DealerManageSubsidyController extends AdminController
return "<div style='padding:10px'>".nl2br($this->remark).'</div>';
});
$grid->column('status', '状态')->display(function ($v) {
return '<i class="fa fa-circle" style="font-size: 13px; color: '.$v->color().';"></i>&nbsp;&nbsp;'.$v->text();
});
return $v->text();
})->circleDot(DealerManageSubsidyStatus::colors());
$grid->column('earning.pay_at', '付款时间');
$grid->showRowSelector();
@ -123,45 +123,5 @@ class DealerManageSubsidyController extends AdminController
});
});
});
return Grid::make($builder, function (Grid $grid) {
$grid->model()->orderBy('id', 'desc');//默认ID倒叙
$grid->column('id')->sortable();
$grid->column('user.phone', '手机号')->copyable();
$grid->column('lvl', '等级')->display(function () {
return $this->lvl->text();
});
$grid->column('order.sn', '订单编号');
$grid->column('product.name', '商品名称');
$grid->column('sales_volume', '销量');
$grid->column('total_amount', '金额');
$grid->column('order_completed_at', '结算时间')->sortable();
$grid->column('created_at')->sortable();
$grid->disableCreateButton();
$grid->disableActions();
$grid->header(function ($collection) use ($grid) {
$query = DealerManageSubsidyLogModel::query();
// 拿到表格筛选 where 条件数组进行遍历
$grid->model()->getQueries()->unique()->each(function ($value) use (&$query) {
if (in_array($value['method'], ['paginate', 'get', 'orderBy', 'orderByDesc'], true)) {
return;
}
$query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
});
// 查出统计数据
$totalAmount = (clone $query)->sum('total_amount');
// 自定义组件
return "<div style='padding: 10px;'>金额:".$totalAmount.' 元</div>';
});
$grid->filter(function (Grid\Filter $filter) {
$filter->panel(false);
$filter->equal('user.phone', '手机号')->width(3);
$filter->between('order_completed_at', '结算时间')->dateTime()->width(7);
});
});
}
}

View File

@ -63,8 +63,8 @@ class DealerManagerSubsidyController extends AdminController
return "<div style='padding:10px'>".nl2br($this->remark).'</div>';
});
$grid->column('status', '状态')->display(function ($v) {
return '<i class="fa fa-circle" style="font-size: 13px; color: '.$v->color().';"></i>&nbsp;&nbsp;'.$v->text();
});
return $v->text();
})->circleDot(DealerManagerSubsidyStatus::colors());
$grid->column('earning.pay_at', '付款时间');
$grid->showRowSelector();

View File

@ -52,11 +52,12 @@ class DealerOrderController extends AdminController
$statusTexts = DealerOrderStatus::texts();
$grid->column('pay_way')->display(function ($v) {
if ($v) {
return '<i class="fa fa-circle" style="font-size: 13px;color: '.$v->color().'"></i>&nbsp;&nbsp;'.$v->getDealerOrderText();
}
return '';
})->filter(DealerOrderPayWayIn::make(PayWay::dealerOrderTexts()));
return $v?->text();
})->circleDot(PayWay::colors())->filter(DealerOrderPayWayIn::make([
PayWay::Offline->value => '线下',
PayWay::Wallet->value => '钱包',
PayWay::WxpayH5->value => '微信支付',
]));
$grid->column('order_status')->display(function ($v) {
return $this->order_status;
@ -89,7 +90,7 @@ class DealerOrderController extends AdminController
$actions->append(new DealerOrderAllocate());
}
if ((empty($actions->row->consignor) || $actions->row->consignor_id == 1)) {
if ((empty($actions->row->consignor))) {
if ($actions->row->isPay() && Admin::user()->can('dcat.admin.dealer_orders.paid')) {
$actions->append(new DealerOrderPaid());
}
@ -157,27 +158,9 @@ class DealerOrderController extends AdminController
$show->field('consignor.phone')->as(function ($value) {
return $value ?? '系统';
});
$show->html(function () {
$content = '';
if ($this->pay_way) {
$content = '<i class="fa fa-circle" style="font-size: 13px;color: '.$this->pay_way->color().'"></i>&nbsp;&nbsp;'.$this->pay_way->getDealerOrderText();
}
return <<<HTML
<div class="show-field form-group row">
<div class="col-sm-2 control-label">
<span>支付方式</span>
</div>
<div class="col-sm-8">
<div class="box box-solid box-default no-margin box-show">
<div class="box-body">{$content}&nbsp;</div>
</div>
</div>
</div>
HTML;
});
$show->field('pay_way', '支付方式')->as(function () {
return $this->pay_way?->text();
})->circleDot(PayWay::colors());
$show->field('pay_sn', '支付流水号');
$show->field('pay_time', '支付时间');
$show->field('paied_time');
@ -263,6 +246,7 @@ HTML;
$grid->column('price', '标价')->prepend('¥');
$grid->column('sale_price', '实际售价')->prepend('¥');
$grid->column('qty', '数量');
$grid->column('deposit_qty', '云数量');
$grid->disableActions();
$grid->disablePagination();
$grid->disableRefreshButton();

View File

@ -0,0 +1,186 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Grid\DealerPurchaseSubsidyBatchPay;
use App\Admin\Actions\Grid\DealerPurchaseSubsidyPay;
use App\Admin\Repositories\DealerPurchaseSubsidy;
use App\Admin\Repositories\DealerPurchaseSubsidyLog;
use App\Admin\Widgets\InfoBox;
use App\Enums\DealerPurchaseSubsidyStatus;
use App\Models\DealerPurchaseSubsidy as DealerPurchaseSubsidyModel;
use Dcat\Admin\Admin;
use Dcat\Admin\Grid;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Layout\Row;
use Dcat\Admin\Show;
use Dcat\Admin\Widgets\Box;
class DealerPurchaseSubsidyController extends AdminController
{
protected $title = '进货补贴';
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
Admin::style(
<<<CSS
.card-header {
margin-top: 1.5rem !important;
margin-bottom: -1rem !important;
}
CSS
);
$builder = DealerPurchaseSubsidy::with(['user.userInfo']);
return Grid::make($builder, function (Grid $grid) {
$grid->model()->settleCompleted()->orderBy('id', 'desc');//默认ID倒叙
$grid->column('settle_period', '结算周期')->display(function () {
return $this->start_at->rawFormat('Y/m/d') . '-' . $this->end_at->rawFormat('Y/m/d');
})->link(function () {
return admin_route('dealer_purchase_logs.index', [
'user_phone' => $this->user?->phone,
'order_completed_at[start]' => $this->start_at->toDateTimeString(),
'order_completed_at[end]' => $this->end_at->toDateTimeString(),
]);
});
$grid->column('user.phone', '手机号')->copyable();
$grid->column('user.userInfo.nickname', '昵称');
$grid->column('lvl', '等级')->display(function () {
return $this->lvl->text();
});
$grid->column('total_purchase_amount', '进货业绩')->prepend('¥');
$grid->column('subsidy_rate', '补贴比例')->append('%');
$grid->column('total_subsidy', '补贴总额')->prepend('¥')->help('补贴总额=进货业绩*补贴比例');
$grid->column('total_amount', '应得补贴')->prepend('¥');
$grid->column('fee_rate', '手续费率')->append('%');
$grid->column('fee', '手续费')->prepend('¥')->help('手续费=应得补贴*手续费率');
$grid->column('status', '状态')->display(function ($v) {
return $v->text();
})->circleDot(DealerPurchaseSubsidyStatus::colors());
$grid->showRowSelector();
$grid->tools(function ($tools) {
$tools->batch(function ($batch) {
$batch->disableDelete();
if (Admin::user()->can('dcat.admin.dealer_purchase_subsidies.batch_pay')) {
$batch->add(new DealerPurchaseSubsidyBatchPay());
}
});
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (Admin::user()->can('dcat.admin.dealer_purchase_subsidies.show')) {
$actions->append('<a style="cursor: pointer;" target="_blank" href="'.admin_route('dealer_purchase_subsidies.show', ['dealer_purchase_subsidy' => $actions->row]).'"><i class="feather icon-eye"></i> 显示 &nbsp;&nbsp;</a>');
}
if ($actions->row->isPending() && Admin::user()->can('dcat.admin.dealer_purchase_subsidies.pay')) {
$actions->append(new DealerPurchaseSubsidyPay());
}
});
$grid->header(function ($collection) use ($grid) {
return tap(new Row(), function ($row) use ($grid) {
$query = DealerPurchaseSubsidyModel::query();
$grid->model()->getQueries()->unique()->each(function ($value) use (&$query) {
if (in_array($value['method'], ['paginate', 'get', 'orderBy', 'orderByDesc'], true)) {
return;
}
$query = call_user_func_array([$query, $value['method']], $value['arguments'] ?? []);
});
$row->column(3, new InfoBox('进货业绩', (clone $query)->sum('total_purchase_amount'), 'fa fa-cny'));
});
});
$grid->filter(function (Grid\Filter $filter) {
$filter->panel();
$filter->equal('user.phone', '手机号')->width(3);
$filter->equal('status', '状态')->select(DealerPurchaseSubsidyStatus::texts())->width(3);
$filter->whereBetween('settle_period', function ($query) {
$start = $this->input['start'] ?? null;
$end = $this->input['end'] ?? null;
$query->when($start, function ($query, $start) {
$query->where('start_at', '>=', "{$start} 00:00:00");
});
$query->when($end, function ($query, $end) {
$query->where('end_at', '<=', "{$end} 23:59:59");
});
}, '结算周期')->date()->width(6);
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
$row = new Row();
$row->column(5, function ($column) use ($id) {
$builder = DealerPurchaseSubsidy::with(['user.userInfo']);
$column->row(Show::make($id, $builder, function (Show $show) {
$show->field('id');
$show->field('settle_period', '结算周期')->as(function () {
return $this->start_at->rawFormat('Y/m/d') . '-' . $this->end_at->rawFormat('Y/m/d');
});
$show->field('user.phone', '手机号');
$show->field('user.user_info.nickname', '昵称');
$show->field('lvl', '经销商等级')->as(function () {
return $this->lvl->text();
});
$show->field('total_purchase_amount', '进货业绩')->prepend('¥');
$show->field('subsidy_rate', '补贴比例')->append('%');
$show->field('total_subsidy', '补贴总额')->prepend('¥');
$show->field('total_amount', '应得补贴')->prepend('¥');
$show->field('fee_rate', '手续费率')->append('%');
$show->field('fee', '手续费')->prepend('¥');
$show->field('real_amount', '实得补贴')->prepend('¥');
$show->field('status', '状态')->as(function () {
return $this->status?->text();
})->circleDot(DealerPurchaseSubsidyStatus::colors());
$show->panel()
->tools(function (Show\Tools $tools) use ($show) {
$tools->disableEdit();
$tools->disableDelete();
});
}));
});
$row->column(7, function ($column) use ($id) {
$dealerPurchaseSubsidyLogBox = Box::make('补贴明细', Grid::make(DealerPurchaseSubsidyLog::class, function (Grid $grid) use ($id) {
$grid->model()->where('purchase_subsidy_id', $id);
$grid->column('id');
$grid->column('change_amount', '变更金额');
$grid->column('remark', '备注');
$grid->column('created_at', '结算时间');
$grid->disableCreateButton();
$grid->disableActions();
$grid->disableRefreshButton();
}));
$column->row($dealerPurchaseSubsidyLogBox);
});
return $row;
}
}

View File

@ -2,7 +2,6 @@
namespace App\Admin\Controllers;
use App\Admin\Actions\Grid\CreateOrderPackage;
use App\Admin\Actions\Grid\Exports\ShippingOrder as ExportShippingOrder;
use App\Admin\Actions\Grid\KuaidiInfo;
use App\Admin\Actions\Grid\OrderSetTag;
@ -11,9 +10,11 @@ use App\Admin\Actions\Show\OrderCreatePackage;
use App\Admin\Actions\Show\OrderPay;
use App\Admin\Actions\Show\OrderReduce;
use App\Admin\Actions\Show\OrderRemark;
use App\Admin\Extensions\Grid\Tools\Order\ExportProduct;
use App\Admin\Renderable\Grid\Filter\OrderStatusIn;
use App\Admin\Renderable\PackageProductSimpleTable;
use App\Admin\Repositories\Order;
use App\Enums\PayWay;
use App\Models\Order as OrderModel;
use App\Models\OrderLog;
use App\Models\OrderPackage;
@ -25,13 +26,93 @@ use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Grid\Column;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Layout\Row;
use Dcat\Admin\Show;
use Dcat\Admin\Widgets\Box;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
class OrderController extends AdminController
{
/**
* Index interface.
*
* @param Content $content
* @return Content
*/
public function index(Content $content)
{
switch (request()->query('_export')) {
case 'product':
if (! Admin::user()->can('dcat.admin.orders.export_order_products')) {
throw new AuthorizationException('没有操作权限');
}
$query = OrderModel::with(['user.userInfo', 'products']);
$grid = $this->grid();
$grid->processFilter();
foreach ($grid->model()->getQueries() as $condition) {
if (in_array($condition['method'], ['paginate', 'get', 'orderBy', 'orderByDesc'], true)) {
continue;
}
call_user_func_array([$query, $condition['method']], $condition['arguments'] ?? []);
}
return response()->streamDownload(function () use ($query) {
$writer = WriterEntityFactory::createXLSXWriter();
$writer->openToBrowser('订单商品'.time().'.xlsx');
$writer->addRow(WriterEntityFactory::createRowFromArray([
'商品ID',
'商品名称',
'数量',
'价格',
'金额',
'待发货数量',
'所属订单',
'下单手机',
'支付方式',
'付款时间',
'订单状态',
'下单时间',
'是否换货',
]));
$query->lazyById()->each(function ($order) use ($writer) {
foreach ($order->products as $product) {
$writer->addRow(
WriterEntityFactory::createRowFromArray([
$product->sku_id,
$product->name,
$product->quantity,
bcdiv($product->sell_price, '100', 2),
bcdiv($product->total_amount, '100', 2),
$product->remain_quantity,
$order->sn,
$order->user->phone,
$order->pay_way?->text(),
$order->pay_at?->toDateTimeString(),
$order->order_status_text,
$order->created_at?->toDateTimeString(),
$order->is_change ? '是' : '否',
])
);
}
});
$writer->close();
});
break;
}
return parent::index($content);
}
/**
* Make a grid builder.
*
@ -39,106 +120,94 @@ class OrderController extends AdminController
*/
protected function grid()
{
$builder = Order::with(['user', 'tags']);
$grid = new Grid(Order::with(['user', 'tags']));
return Grid::make($builder, function (Grid $grid) {
$grid->column('id')->sortable()->if(function () {
return Admin::user()->can('dcat.admin.orders.show');
})->then(function (Column $column) {
$column->link(function ($value) {
return admin_route('orders.show', ['order' => $value]);
});
});
$grid->setResource('orders');
$grid->model()->orderBy('id', 'desc');
$grid->tools(function (Grid\Tools $tools) {
//设置规格
if (Admin::user()->can('dcat.admin.orders.export_shipping_orders')) {
$tools->append(new ExportShippingOrder());
}
$grid->column('id')->sortable()->if(function () {
return Admin::user()->can('dcat.admin.orders.show');
})->then(function (Column $column) {
$column->link(function ($value) {
return admin_route('orders.show', ['order' => $value]);
});
$grid->column('sn')->copyable();
$grid->column('tags', '标签')->display(function ($tags) {
$array = [];
foreach ($this->tags as $key => $tag) {
$array[] = $tag->name;
}
return $array;
})->label();
$grid->column('user.phone')->copyable();
$grid->column('total_amount')->display(function ($value) {
return bcdiv($value, 100, 2);
})->prepend('¥');
$grid->column('order_status')->display(function ($value) {
return $this->order_status;
})->using([
});
$grid->column('sn')->copyable();
$grid->column('tags', '标签')->display(function ($tags) {
return $tags->implode('name');
})->label();
$grid->column('user.phone')->copyable();
$grid->column('total_amount')->display(function ($value) {
return bcdiv($value, 100, 2);
})->prepend('¥');
$grid->column('order_status')->display(function ($value) {
return $this->order_status;
})->using([
0=>'待付款',
1=>'待发货',
2=>'发货中',
3=>'已发货',
9=>'已完成',
10=>'已取消',
])->dot([
0=>'primary',
1=>'warning',
2=>'danger',
3=>'success',
9=>'success',
10=>'#b3b9bf',
])->filter(
OrderStatusIn::make([
0=>'待付款',
1=>'待发货',
2=>'发货中',
3=>'已发货',
9=>'已完成',
10=>'已取消',
])->dot([
0=>'primary',
1=>'warning',
2=>'danger',
3=>'success',
9=>'success',
10=>'#b3b9bf',
])->filter(
OrderStatusIn::make([
0=>'待付款',
1=>'待发货',
2=>'发货中',
3=>'已发货',
9=>'已完成',
10=>'已取消',
])
);
$grid->column('pay_way')->display(function ($v) {
if ($v) {
return '<i class="fa fa-circle" style="font-size: 13px;color: '.$v->color().'"></i>&nbsp;&nbsp;'.$v->getMallOrderText();
}
])
);
$grid->column('pay_way')->display(function ($v) {
return $v?->mallText();
})->circleDot(PayWay::colors());
$grid->column('pay_at');
$grid->column('created_at')->sortable();
return '';
});
$grid->column('pay_at');
// $grid->column('consignee_name');
// $grid->column('consignee_telephone');
// $grid->column('consignee_zone');
// $grid->column('consignee_address');
$grid->column('created_at')->sortable();
$grid->model()->orderBy('created_at', 'desc');
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (Admin::user()->can('dcat.admin.orders.show')) {
$actions->disableView(false);
}
if (Admin::user()->can('dcat.admin.orders.tags')) {
$actions->append(new OrderSetTag());
}
if (Admin::user()->can('dcat.admin.distribution_pre_incomes.index')) {
$actions->append('<a href="'.admin_route('distribution_pre_incomes.index', ['order[sn]'=>$actions->row->sn]).'" target="_blank"><i class="fa fa-eye"></i> 预收益明细</a>');
}
// $actions->append(new CreateOrderPackage());
});
$grid->setResource('orders');
$grid->filter(function (Grid\Filter $filter) {
$filter->panel();
$filter->like('sn')->width(3);
$filter->like('user.phone')->width(3);
$filter->where('tags', function ($query) {
$query->whereHas('tags', function ($q) {
$q->whereIn('tags.id', $this->input);
});
}, '标签')->multipleSelect(Tag::orderTag()->pluck('name', 'id'))->width(3);
$filter->between('created_at')->dateTime()->width(7);
});
$grid->filter(function (Grid\Filter $filter) {
$filter->panel();
$filter->like('sn')->width(3);
$filter->like('user.phone')->width(3);
$filter->where('tags', function ($query) {
$query->whereHas('tags', function ($q) {
$q->whereIn('tags.id', $this->input);
});
}, '标签')->multipleSelect(Tag::orderTag()->pluck('name', 'id'))->width(3);
$filter->between('created_at')->dateTime()->width(7);
});
$grid->tools(function (Grid\Tools $tools) {
if (Admin::user()->can('dcat.admin.orders.export_shipping_orders')) {
$tools->append(new ExportShippingOrder());
}
if (Admin::user()->can('dcat.admin.orders.export_order_products')) {
$tools->append(new ExportProduct());
}
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (Admin::user()->can('dcat.admin.orders.show')) {
$actions->disableView(false);
}
if (Admin::user()->can('dcat.admin.orders.tags')) {
$actions->append(new OrderSetTag());
}
if (Admin::user()->can('dcat.admin.distribution_pre_incomes.index')) {
$actions->append('<a href="'.admin_route('distribution_pre_incomes.index', ['order[sn]'=>$actions->row->sn]).'" target="_blank"><i class="fa fa-eye"></i> 预收益明细</a>');
}
});
return $grid;
}
/**
@ -180,15 +249,9 @@ class OrderController extends AdminController
return $this->tags->pluck('name');
})->label();
$show->field('pay_at');
$show->field('pay_way')->unescape()->as(function () {
$content = '';
if ($this->pay_way) {
$content = '<i class="fa fa-circle" style="font-size: 13px;color: '.$this->pay_way->color().'"></i>&nbsp;&nbsp;'.$this->pay_way->getMallOrderText();
}
return $content;
});
$show->field('pay_way', '支付方式')->as(function () {
return $this->pay_way?->text();
})->circleDot(PayWay::colors());
});
$show->row(function (Show\Row $show) {
$show->width(6)->field('consignee_name');

View File

@ -4,6 +4,7 @@ namespace App\Admin\Controllers;
use App\Admin\Forms\Settings\Android;
use App\Admin\Forms\Settings\App;
use App\Admin\Forms\Settings\Custom;
use App\Admin\Forms\Settings\Dealer;
use App\Admin\Forms\Settings\Distribution;
use App\Admin\Forms\Settings\Ios;
@ -118,6 +119,7 @@ class SettingController extends AdminController
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'ios']));
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'distribution':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
@ -128,6 +130,7 @@ class SettingController extends AdminController
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'android']));
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'dealer':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
@ -138,6 +141,7 @@ class SettingController extends AdminController
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'android']));
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'withdraw':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
@ -148,6 +152,7 @@ class SettingController extends AdminController
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'android']));
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'ios':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
@ -158,6 +163,7 @@ class SettingController extends AdminController
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'android']));
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'android':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
@ -168,6 +174,7 @@ class SettingController extends AdminController
$tab->add('Android配置', new Android(), true);
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'kuaidi100':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
@ -178,6 +185,7 @@ class SettingController extends AdminController
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'android']));
$tab->add('快递100配置', new Kuaidi100(), true);
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'unipush':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
@ -188,6 +196,18 @@ class SettingController extends AdminController
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'android']));
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->add('Uni-push配置', new Unipush(), true);
$tab->addLink('自定义配置', admin_route('settings.index', ['type'=>'custom']));
break;
case 'custom':
$tab->addLink('系统配置', admin_route('settings.index', ['type'=>'app']));
// $tab->addLink('会员奖励配置', admin_route('settings.index', ['type'=>'distribution']));
$tab->addLink('经销商配置', admin_route('settings.index', ['type'=>'dealer']));
$tab->addLink('提现配置', admin_route('settings.index', ['type'=>'withdraw']));
$tab->addLink('Ios配置', admin_route('settings.index', ['type'=>'ios']));
$tab->addLink('Android配置', admin_route('settings.index', ['type'=>'android']));
$tab->addLink('快递100配置', admin_route('settings.index', ['type'=>'kuaidi100']));
$tab->addLink('Uni-push配置', admin_route('settings.index', ['type'=>'unipush']));
$tab->add('自定义配置', new Custom(), true);
break;
default:
break;

View File

@ -0,0 +1,50 @@
<?php
namespace App\Admin\Extensions\Column;
use Dcat\Admin\Admin;
use Dcat\Admin\Grid\Displayers\AbstractDisplayer;
use Illuminate\Support\Arr;
use UnitEnum;
class CircleDot extends AbstractDisplayer
{
public function display(array $options = [], string $default = 'default')
{
if (is_null($value = $this->value)) {
return;
}
if ($value instanceof UnitEnum) {
$value = $value->value;
}
$background = $this->background($options, $default);
return "<i class='fa fa-circle' style='font-size: 13px;color: {$background}'></i>&nbsp;&nbsp;{$value}";
}
/**
* 获取圆点的背景色
*
* @param array $options
* @param string $default
* @return string
*/
protected function background(array $options, string $default = 'default'): string
{
$original = $this->column->getOriginal();
$style = is_null($original) ? $default : Arr::get(
$options,
$original instanceof UnitEnum
? $original->value
: $original,
$default
);
$style = $style === 'default' ? 'dark70' : $style;
return Admin::color()->get($style, $style);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Admin\Extensions\Grid\Tools\Order;
use Dcat\Admin\Grid\Tools\AbstractTool;
class ExportProduct extends AbstractTool
{
protected function authorize($user): bool
{
return $user->can('dcat.admin.orders.export_order_products');
}
/**
* 按钮样式定义,默认 btn btn-white waves-effect
*
* @var string
*/
protected $style = 'btn btn-primary';
/**
* 按钮文本
*
* @return string|void
*/
public function title()
{
return '<i class="feather grid-action-icon icon-download"></i>导出商品';
}
protected function script()
{
$this->setHtmlAttribute('data-role', 'order-products-exporter');
$url = request()->fullUrlWithQuery(['_export' => 'product']);
return <<<JS
$('button[data-role=order-products-exporter]').click(function () {
window.location.href="$url";
});
JS;
}
}

View File

@ -45,8 +45,9 @@ class DealerEarningPay extends Form implements LazyRenderable
DB::beginTransaction();
$earning = DealerEarning::findOrFail($id);
$earning->update([
'pay_way' => DealerEarning::PAY_WAY_WALLET,
'pay_info' => $earning->getPayInfo(),
'pay_image' => $input['pay_image']??null,
'pay_image' => $input['pay_image'] ?? null,
'pay_at' => now(),
'status' => DealerEarningStatus::Completed,
]);

View File

@ -0,0 +1,48 @@
<?php
namespace App\Admin\Forms\Settings;
use App\Models\Setting;
use App\Services\SettingService;
use Dcat\Admin\Widgets\Form;
class Custom extends Form
{
/**
* Handle the form request.
*
* @param array $input
*
* @return mixed
*/
public function handle(array $input)
{
Setting::where('key', 'custom')->updateOrCreate([
'key' => 'custom',
], ['value' => $input]);
//清配置缓存
app(SettingService::class)->cleanCache('custom');
return $this
->response()
->success('配置更新成功!')
->refresh();
}
/**
* Build a form here.
*/
public function form()
{
$this->keyValue('key_value', '配置')->setKeyLabel('键名')->setValueLabel('键值');
}
public function default()
{
$appSettings = Setting::where('key', 'custom')->value('value');
return [
'key_value' => $appSettings['key_value'] ?? [],
];
}
}

View File

@ -39,6 +39,7 @@ class Dealer extends Form
public function form()
{
$this->switch('wxpay_switch', '微信支付');
$this->currency('delivery_bill_shipping_fee', '云仓运费')->symbol('¥');
$this->currency('fee_rate', '手续费比例(百分比)')->symbol('%');
$this->number('withdraw_threshold_amount', '起提金额(元)');
$this->currency('withdraw_fee_rate', '提现费率')->symbol('%');
@ -139,6 +140,7 @@ class Dealer extends Form
$dealerSettings = (array) Setting::where('key', 'dealer')->value('value');
return [
'wxpay_switch'=> $dealerSettings['wxpay_switch'] ?? 1,
'delivery_bill_shipping_fee'=> $dealerSettings['delivery_bill_shipping_fee'] ?? 0,
'fee_rate'=> $dealerSettings['fee_rate'] ?? '',
'withdraw_threshold_amount'=> $dealerSettings['withdraw_threshold_amount'] ?? 0,
'withdraw_fee_rate'=> $dealerSettings['withdraw_fee_rate'] ?? 0,

View File

@ -18,16 +18,17 @@ class DealerUserProductLogSimpleTable extends LazyRenderable
return Grid::make($builder, function (Grid $grid) {
$grid->column('product.name', '商品名称');
$grid->column('remark', '备注');
$grid->column('is_deposit', '范围')->using([
0=>'本地库存',
1=>'云库存',
]);
$grid->column('qty', '变动数量')->display(function () {
return (in_array($this->type, [
DealerUserProductLog::TYPE_ORDER_IN,
DealerUserProductLog::TYPE_ADMIN_IN,
]) ? '+' : '-').$this->qty.$this->product?->unit;
return $this->qty_format.$this->product?->unit;
});
$grid->column('created_at', '创建时间');
// $grid->withBorder();
$grid->model()->orderBy('created_at', 'desc');
$grid->model()->orderBy('id', 'desc');
$grid->disableRefreshButton();
$grid->disableActions();
});

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\DealerDeliveryBill as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class DealerDeliveryBill extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\DealerDeliveryProduct as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class DealerDeliveryProduct extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\DealerPurchaseSubsidy as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class DealerPurchaseSubsidy extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\DealerPurchaseSubsidyLog as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class DealerPurchaseSubsidyLog extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Admin\Services;
use App\Enums\DealerEarningStatus;
use App\Enums\DealerManagerSubsidyStatus;
use App\Enums\DealerManageSubsidyStatus;
use App\Enums\DealerPurchaseSubsidyStatus;
use App\Enums\DealerWalletAction;
use App\Exceptions\BizException;
use App\Models\DealerChannelSubsidyLog;
use App\Models\DealerEarning;
use App\Models\DealerManagerSubsidy;
use App\Models\DealerManageSubsidy;
use App\Models\DealerPurchaseSubsidy;
use App\Services\Dealer\WalletService;
use Illuminate\Database\Eloquent\Relations\Relation;
class DealerEarningService
{
public function pay(DealerEarning $dealerEarning)
{
if (! $dealerEarning->isSettled()) {
throw new BizException('经销商收益还未结算');
}
if (! $dealerEarning->isPending()) {
throw new BizException('经销商收益状态不是待付款');
}
switch (Relation::getMorphedModel($dealerEarning->earningable_type)) {
// 管理津贴
case DealerManageSubsidy::class:
if (! $dealerEarning->earningable->isPending()) {
throw new BizException('管理津贴状态不是待付款');
}
$dealerEarning->earningable->update([
'status' => DealerManageSubsidyStatus::Completed,
]);
(new WalletService())->changeBalance(
$dealerEarning->user,
$dealerEarning->total_earnings,
DealerWalletAction::ManageSubsidyIn,
'收入-管理津贴',
$dealerEarning
);
break;
// 管理者津贴
case DealerManagerSubsidy::class:
if (! $dealerEarning->earningable->isPending()) {
throw new BizException('管理者津贴状态不是待付款');
}
$dealerEarning->earningable->update([
'status' => DealerManagerSubsidyStatus::Completed,
]);
(new WalletService())->changeBalance(
$dealerEarning->user,
$dealerEarning->total_earnings,
DealerWalletAction::ManagerSubsidyIn,
'收入-管理者津贴',
$dealerEarning
);
break;
// 管理者津贴
case DealerPurchaseSubsidy::class:
if (! $dealerEarning->earningable->isPending()) {
throw new BizException('进货补贴状态不是待付款');
}
$dealerEarning->earningable->update([
'status' => DealerPurchaseSubsidyStatus::Completed,
]);
(new WalletService())->changeBalance(
$dealerEarning->user,
$dealerEarning->total_earnings,
DealerWalletAction::PurchaseSubsidyIn,
'收入-进货补贴',
$dealerEarning
);
break;
case DealerChannelSubsidyLog::class:
(new WalletService())->changeBalance(
$dealerEarning->user,
$dealerEarning->total_earnings,
DealerWalletAction::ChannelSubsidyIn,
'收入-渠道补贴',
$dealerEarning
);
break;
default:
throw new BizException('经销商收入异常');
break;
}
$dealerEarning->update([
'pay_way' => DealerEarning::PAY_WAY_WALLET,
'pay_at' => now(),
'pay_info' => null,
'status' => DealerEarningStatus::Completed,
]);
}
}

View File

@ -1,5 +1,6 @@
<?php
use App\Admin\Extensions\Column\CircleDot;
use App\Admin\Extensions\Column\Modal;
use App\Admin\Extensions\Form\Product\SelectAttr;
use App\Admin\Extensions\Form\Product\SelectSpec;
@ -10,6 +11,7 @@ use Dcat\Admin\Form\Field\Editor;
use Dcat\Admin\Grid;
use Dcat\Admin\Grid\Column;
use Dcat\Admin\Show\Field as ShowField;
use Illuminate\Support\Arr;
/**
* Dcat-admin - admin builder based on Laravel.
@ -33,6 +35,7 @@ use Dcat\Admin\Show\Field as ShowField;
Admin::css('/dist/admin/css/app.css');
Column::extend('modal', Modal::class);
Column::extend('circleDot', CircleDot::class);
Grid::resolving(function (Grid $grid) {
$grid->disableRowSelector();
@ -60,6 +63,27 @@ Editor::resolving(function (Editor $editor) {
});
ShowField::extend('showLabel', Label::class);
ShowField::macro('circleDot', function ($options = [], $default = 'default') {
return $this->unescape()->prepend(function ($_, $original) use ($options, $default) {
if (is_null($original)) {
return;
}
$style = Arr::get(
$options,
$original instanceof UnitEnum
? $original->value
: $original,
$default
);
$style = $style === 'default' ? 'dark70' : $style;
$background = Admin::color()->get($style, $style);
return "<i class='fa fa-circle' style='font-size: 13px;color: {$background}'></i>&nbsp;&nbsp;";
});
});
Admin::style(
<<<'CSS'

View File

@ -186,6 +186,9 @@ Route::group([
$router->get('dealer-earnings-manager', 'DealerEarningController@index')->name('dealer_earnings.manager');
$router->get('dealer-earnings-purchase', 'DealerEarningController@index')->name('dealer_earnings.purchase');
// 签约渠道补贴
$router->get('dealer-channel-subsidies', 'DealerChannelSubsidyController@index')->name('dealer_channel_subsidies.index');
// 管理者津贴
$router->get('dealer-manager-subsidies', 'DealerManagerSubsidyController@index')->name('dealer_manager_subsidies.index');
$router->get('dealer-manager-sales-logs', 'DealerManagerSalesLogController@index')->name('dealer_manager_sales_logs.index');
@ -194,6 +197,8 @@ Route::group([
$router->get('dealer-manage-subsidies', 'DealerManageSubsidyController@index')->name('dealer_manage_subsidies.index');
$router->get('dealer-manage-subsidy-logs', 'DealerManageSubsidyLogController@index')->name('dealer_manage_subsidy_logs.index');
$router->get('dealer-purchase-subsidies', 'DealerPurchaseSubsidyController@index')->name('dealer_purchase_subsidies.index');
$router->get('dealer-purchase-subsidies/{dealer_purchase_subsidy}', 'DealerPurchaseSubsidyController@show')->name('dealer_purchase_subsidies.show');
$router->get('dealer-purchase-logs', 'DealerPurchaseLogController@index')->name('dealer_purchase_logs.index');
//批零余额提现
@ -202,6 +207,10 @@ Route::group([
'index', 'show',
])->names('dealer_wallet_to_bank_logs');
//
$router->get('dealer-delivery-bills', 'DealerDeliveryBillController@index')->name('dealer_delivery_bills.index');
$router->get('dealer-delivery-bills/{dealer_delivery_bill}', 'DealerDeliveryBillController@show')->name('dealer_delivery_bills.show');
/** api接口 **/
$router->get('api/product-categories', 'ProductCategoryController@categories')->name('api.product_categories');
$router->get('api/product-group-details', 'ProductGroupController@details')->name('api.product_group_details');

View File

@ -11,4 +11,14 @@ class OrderStatus
public const SHIPPED = 3; // 已发货/待收货
public const COMPLETED = 9; // 已完成
public const CANCELLED = 10; // 已取消
public static $statusTexts = [
self::UNKNOWN => '其它',
self::PENDING => '待付款',
self::WAIT_SHIPPING => '待发货',
self::SHIPPING => '发货中',
self::SHIPPED => '待收货',
self::COMPLETED => '已完成',
self::CANCELLED => '已取消',
];
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Endpoint\Api\Filters;
use App\Enums\DealerDeliveryBillStatus;
use EloquentFilter\ModelFilter;
class DealerDeliveryBillFilter extends ModelFilter
{
public function status($status)
{
switch ($status) {
case 'pending':
$this->where('status', DealerDeliveryBillStatus::Pending);
break;
case 'paid':
$this->where('status', DealerDeliveryBillStatus::Paid);
break;
case 'cancelled':
$this->where('status', DealerDeliveryBillStatus::Cancelled);
break;
}
}
}

View File

@ -11,6 +11,8 @@ class ConfigurationController extends Controller
{
return response()->json([
'wxpay_switch' => app_settings('dealer.wxpay_switch', 0),
'delivery_bill_shipping_fee' => app_settings('dealer.delivery_bill_shipping_fee', 0),
'withdraw_fee_rate' => app_settings('dealer.withdraw_fee_rate', 0),
]);
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace App\Endpoint\Api\Http\Controllers\Dealer;
use App\Endpoint\Api\Http\Controllers\Controller;
use App\Endpoint\Api\Http\Resources\Dealer\DealerDeliveryBillResource;
use App\Enums\PayWay;
use App\Exceptions\BizException;
use App\Exceptions\PayPasswordIncorrectException;
use App\Helpers\Paginator;
use App\Services\Dealer\DealerDeliveryBillService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Throwable;
class DealerDeliveryBillController extends Controller
{
/**
* 提货单列表
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
$perPage = Paginator::resolvePerPage('per_page', 20, 50);
$deliveryBills = $request->user()->dealerDeliveryBills()
->filter($request->all())
->latest('id')
->simplePaginate($perPage);
return DealerDeliveryBillResource::collection($deliveryBills);
}
/**
* 创建提货单
*
* @param \Illuminate\Http\Request $request
* @param \App\Services\Dealer\DealerDeliveryBillService $dealerDeliveryBillService
* @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request, DealerDeliveryBillService $dealerDeliveryBillService)
{
$input = $request->validate([
'shipping_address_id' => ['bail', 'required'],
'products' => ['bail', 'required', 'array'],
'products.*.id' => ['bail', 'required', 'int'],
'products.*.qty' => ['bail', 'required', 'int', 'min:1'],
'remark' => ['bail', 'nullable', 'string', 'max:255'],
], [], [
'shipping_address_id' => '收货地址',
'products' => '商品',
'remark' => '备注',
]);
$user = $request->user();
$shippingAddress = $user->shippingAddresses()->findOrFail($input['shipping_address_id']);
try {
DB::beginTransaction();
$deliveryBill = $dealerDeliveryBillService->create(
$user,
$input['products'],
[
'name' => $shippingAddress->consignee,
'telephone' => $shippingAddress->telephone,
'zone' => $shippingAddress->zone,
'address' => $shippingAddress->address,
],
$input['remark'] ?? null
);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
throw $e;
}
$deliveryBill->load(['deliveryProducts.product']);
return DealerDeliveryBillResource::make($deliveryBill);
}
/**
* 提货单详情
*
* @param int $id
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function show($id, Request $request)
{
$deliveryBill = $request->user()->dealerDeliveryBills()->findOrFail($id);
$deliveryBill->load(['deliveryProducts.product']);
return DealerDeliveryBillResource::make($deliveryBill);
}
/**
* 提货单支付
*
* @param int $id
* @param \Illuminate\Http\Request $request
* @param \App\Services\Dealer\DealerDeliveryBillService $dealerDeliveryBillService
* @return \Illuminate\Http\JsonResponse
*/
public function pay($id, Request $request, DealerDeliveryBillService $dealerDeliveryBillService)
{
$input = $request->validate([
'pay_way' => ['bail', 'required', 'string'],
'pay_password' => ['bail', 'required_if:pay_way,wallet'],
'products'=> ['array'],
], [
'pay_password.required_if' => '支付密码 不能为空。',
], [
'pay_way' => '支付方式',
'pay_password' => '支付密码',
]);
$payWay = PayWay::tryFrom($input['pay_way']);
if (! in_array($payWay, [PayWay::Wallet, PayWay::WxpayH5, PayWay::WxpayJsApi])) {
throw new BizException('支付方式 非法');
}
$user = $request->user();
$deliveryBill = $user->dealerDeliveryBills()->findOrFail($id);
if ($payWay === PayWay::Wallet && !$user->wallet?->verifyPassword($input['pay_password'])) {
throw new PayPasswordIncorrectException();
}
try {
DB::beginTransaction();
$data = $dealerDeliveryBillService->pay($deliveryBill, $payWay);
DB::commit();
} catch (BizException $e) {
DB::rollBack();
throw $e;
} catch (Throwable $e) {
DB::rollBack();
report($e);
throw new BizException('支付失败,请重试');
}
return response()->json($data);
}
/**
* 取消提货单
*
* @param int $id
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function cancel($id, Request $request)
{
$user = $request->user();
DB::transaction(function () use ($id, $user) {
(new DealerDeliveryBillService())->cancel(
$user->dealerDeliveryBills()->lockForUpdate()->findOrFail($id)
);
});
return response()->noContent();
}
}

View File

@ -5,11 +5,13 @@ namespace App\Endpoint\Api\Http\Controllers\Dealer;
use App\Endpoint\Api\Http\Controllers\Controller;
use App\Endpoint\Api\Http\Resources\Dealer\OrderResource;
use App\Endpoint\Api\Http\Resources\Dealer\OrderSimpleResource;
use App\Enums\DealerLvl;
use App\Enums\PayWay;
use App\Exceptions\BizException;
use App\Exceptions\PayPasswordIncorrectException;
use App\Helpers\Paginator as PaginatorHelper;
use App\Models\DealerOrder;
use App\Models\DealerOrderProduct;
use App\Models\DealerProduct;
use App\Services\Dealer\OrderService;
use Illuminate\Database\QueryException;
@ -217,6 +219,7 @@ class OrderController extends Controller
'pay_image' => ['bail', 'string'],
'pay_way' => ['bail', 'string'],
'pay_password' => ['bail', 'required_if:pay_way,wallet'],
'products'=> ['array'],
], [
'pay_password.required_if' => '支付密码 不能为空。',
], [
@ -224,10 +227,9 @@ class OrderController extends Controller
'pay_way' => '支付方式',
'pay_password' => '支付密码',
]);
$payWay = PayWay::tryFrom($input['pay_way'] ?? 'offline');
if (! in_array($payWay, [PayWay::Offline, PayWay::Wallet, PayWay::WxpayH5])) {
if (! in_array($payWay, [PayWay::Offline, PayWay::Wallet, PayWay::WxpayH5, PayWay::WxpayJsApi])) {
throw new BizException('支付方式 非法');
}
@ -244,6 +246,7 @@ class OrderController extends Controller
break;
case PayWay::WxpayH5:
case PayWay::WxpayJsApi:
if ($order->consignor !== null) {
throw new BizException('订单不是签约订单');
}
@ -254,6 +257,29 @@ class OrderController extends Controller
try {
DB::beginTransaction();
//签约单-处理云库存
if ($order->consignor === null) {
//处理云库存设置如果没有设置则默认云仓库为0。
$products = $input['products'] ?? [];
if ($products) {
$num = 0;
foreach ($order->products as $product) {
//更新云库存
if (isset($products[$product->id]) && ($product->qty + $product->deposit_qty) >= $products[$product->id]) {
DealerOrderProduct::where('id', $product->id)->update([
'qty' => $products[$product->id],
'deposit_qty' =>($product->qty + $product->deposit_qty) - $products[$product->id],
]);
$num += $products[$product->id];
}
}
if ($num < 20) {
throw new BizException('首次发货最少20盒');
}
}
}
$data = $orderService->pay($order, $payWay, $input['pay_image'] ?? null);
DB::commit();
@ -273,7 +299,7 @@ class OrderController extends Controller
}
/**
* 确认收款+发货
* 确认收款
*
* @return void
*/
@ -288,7 +314,8 @@ class OrderController extends Controller
try {
DB::beginTransaction();
$orderService->paidOrder($order);//确认收款
$orderService->shippingOrder($order);//确认发货
//3-14号取消确认收款后自动发货
// $orderService->shippingOrder($order);//确认发货
DB::commit();
} catch (QueryException $e) {
DB::rollBack();
@ -315,15 +342,32 @@ class OrderController extends Controller
public function shippingOrder($id, Request $request, OrderService $orderService)
{
$order = DealerOrder::findOrFail($id);
$userId = $request->user()->id;
$user = $request->user();
//不是发货人
if (!$order->isConsignor($userId)) {
if (!$order->isConsignor($user->id)) {
throw new BizException('订单未找到');
}
$deliveryBill = null;
try {
DB::beginTransaction();
$orderService->shippingOrder($order);//确认发货
//如果发货人是签约经销商, 则可以使用云仓库发货
if ($user->dealer->lvl?->value >= DealerLvl::Contracted->value) {
$deliveryBill = $orderService->shippingOrderByDeposit($order);
if ($deliveryBill) {
$order->refresh();
//扣除本地库存
$orderService->orderOutQty($order);
} else {
$orderService->shippingOrder($order);//确认发货
}
} else {
$orderService->shippingOrder($order);//确认发货
}
DB::commit();
} catch (BizException $e) {
DB::rollBack();
$e = new BizException($e->getMessage());
throw $e;
} catch (QueryException $e) {
DB::rollBack();
if (strpos($e->getMessage(), 'Numeric value out of range') !== false) {
@ -335,7 +379,10 @@ class OrderController extends Controller
report($th);
throw new BizException('操作失败,请刷新后再试');
}
return response()->noContent();
return response()->json([
'delivery_bill_id'=>$deliveryBill?->id,
]);
}
/**
@ -346,9 +393,9 @@ class OrderController extends Controller
public function shippingedOrder($id, Request $request, OrderService $orderService)
{
$order = DealerOrder::findOrFail($id);
$userId = $request->user()->id;
$user = $request->user();
//不是收货人
if (!$order->isUser($userId)) {
if (!$order->isUser($user->id)) {
throw new BizException('订单未找到');
}
try {

View File

@ -37,7 +37,7 @@ class UserProductController extends Controller
{
$list = $request->user()->dealerProductLogs()->filter($request->all())
->with('product')
->orderBy('created_at', 'desc')
->orderBy('id', 'desc')
->simplePaginate(Paginator::resolvePerPage('per_page', 20, 50));
return UserProductLogResource::collection($list);
}

View File

@ -20,4 +20,17 @@ class SettingController extends Controller
'withdraw'=>app_settings('withdraw'),
]);
}
public function custom(Request $request)
{
$keys = (array) $request->input('keys');
$custom = app_settings('custom.key_value');
$keyValue = [];
foreach ($keys as $key) {
$keyValue[$key] = $custom[$key] ?? '';
}
return response()->json([
'values'=>$keyValue,
]);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Endpoint\Api\Http\Resources\Dealer;
use Illuminate\Http\Resources\Json\JsonResource;
class DealerDeliveryBillResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'sn' => $this->sn,
'shipping_fee' => $this->shipping_fee,
'remark' => $this->remark,
'consignee_name' => $this->consignee_name,
'consignee_telephone' => $this->consignee_telephone,
'consignee_zone' => $this->consignee_zone,
'consignee_address' => $this->consignee_address,
'delivery_products' => DealerDeliveryProductResource::collection($this->whenLoaded('deliveryProducts')),
'pay_sn' => $this->pay_sn,
'pay_way' => $this->pay_way,
'pay_at' => $this->pay_at?->toDateTimeString(),
'status' => $this->status,
'created_at' => $this->created_at->toDateTimeString(),
];
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Endpoint\Api\Http\Resources\Dealer;
use Illuminate\Http\Resources\Json\JsonResource;
class DealerDeliveryProductResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->product->id,
'name' => $this->product->name,
'cover' => $this->product->cover,
'qty' => $this->qty,
];
}
}

View File

@ -15,11 +15,13 @@ class OrderProductResource extends JsonResource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'cover'=> $this->cover,
'price'=>$this->price,
'sale_price' =>$this->sale_price,
'qty' =>$this->qty,
'deposit_qty'=> $this->deposit_qty,
];
}
}

View File

@ -2,7 +2,6 @@
namespace App\Endpoint\Api\Http\Resources\Dealer;
use App\Models\DealerUserProductLog;
use Illuminate\Http\Resources\Json\JsonResource;
class UserProductLogResource extends JsonResource
@ -19,11 +18,8 @@ class UserProductLogResource extends JsonResource
'id' => $this->id,
'product_name'=> $this->product?->name,
'remark' => $this->remark,
'qty' => (in_array($this->type, [
DealerUserProductLog::TYPE_ORDER_IN,
DealerUserProductLog::TYPE_ADMIN_IN,
DealerUserProductLog::TYPE_REVOKE_IN,
]) ? '+' : '-').$this->qty.$this->product?->unit,
'is_deposit' => $this->is_deposit,
'qty' => $this->qty_format.$this->product?->unit,
'created_at' => $this->created_at->toDateTimeString(),
'can_revoke' => $this->canRevoke(),
];

View File

@ -20,6 +20,7 @@ class UserProductResource extends JsonResource
'cover' => (string) $this->product?->cover,
'price' => (string) $this->product?->price,
'stock' => (int) $this->stock,
'deposit_stock'=> (int) $this->deposit_stock,
];
}
}

View File

@ -85,6 +85,7 @@ Route::group([
//获取配置
Route::get('configs', [SettingController::class, 'index']);
Route::get('configs-custom', [SettingController::class, 'custom']);
//三方登录聚合
Route::group([
@ -299,5 +300,12 @@ Route::group([
Route::get('purchase-subsidies', [Dealer\PurchaseSubsidyController::class, 'index']);
// 进货补贴流水
Route::get('purchase-subsidies/{purchase_subsidy}/logs', [Dealer\PurchaseSubsidyLogController::class, 'index']);
// 云仓库
Route::get('delivery-bills', [Dealer\DealerDeliveryBillController::class, 'index']);
Route::post('delivery-bills', [Dealer\DealerDeliveryBillController::class, 'store']);
Route::get('delivery-bills/{delivery_bill}', [Dealer\DealerDeliveryBillController::class, 'show']);
Route::post('delivery-bills/{delivery_bill}/pay', [Dealer\DealerDeliveryBillController::class, 'pay']);
Route::post('delivery-bills/{delivery_bill}/cancel', [Dealer\DealerDeliveryBillController::class, 'cancel']);
});
});

View File

@ -0,0 +1,32 @@
<?php
namespace App\Enums;
enum DealerDeliveryBillStatus: int {
case Pending = 0; // 待付款
case Paid = 1; // 已付款
case Cancelled = 10; // 已取消
public function text()
{
return static::texts()[$this->value];
}
public static function colors()
{
return [
static::Pending->value => '#5b69bc',
static::Paid->value => '#21b978',
static::Cancelled->value => '#b3b9bf',
];
}
public static function texts()
{
return [
static::Pending->value => '待付款',
static::Paid->value => '已付款',
static::Cancelled->value => '已取消',
];
}
}

View File

@ -7,15 +7,31 @@ enum DealerEarningStatus: int {
case Paid = 1;//已打款
case Completed = 5;//已完成
/**
* @return string
*/
public function color()
{
return static::colors()[$this->value] ?? '#5b69bc';
}
public function text()
{
return match ($this) {
static::Pending => '待打款',
static::Paid => '待收款',
static::Completed => '已完成',
};
return static::texts()[$this->value];
}
public static function colors()
{
return [
static::Pending->value => '#5b69bc',
static::Paid->value => '#3085d6',
static::Completed->value => '#21b978',
];
}
public static function texts()
{
return [
static::Pending->value => '待打款',
static::Paid->value => '待收款',
static::Completed->value => '已完成',
];
}
}

View File

@ -6,19 +6,19 @@ enum DealerManageSubsidyStatus: int {
case Pending = 0;
case Completed = 5;
public function color(): string
{
return match ($this) {
static::Pending => '#5b69bc',
static::Completed => '#21b978',
};
}
public function text(): string
{
return static::texts()[$this->value] ?? 'Unknown';
}
public static function colors(): array
{
return [
static::Pending->value => '#5b69bc',
static::Completed->value => '#21b978',
];
}
public static function texts(): array
{
return [

View File

@ -19,6 +19,14 @@ enum DealerManagerSubsidyStatus: int {
return static::texts()[$this->value] ?? 'Unknown';
}
public static function colors(): array
{
return [
static::Pending->value => '#5b69bc',
static::Completed->value => '#21b978',
];
}
public static function texts(): array
{
return [

View File

@ -5,4 +5,25 @@ namespace App\Enums;
enum DealerPurchaseSubsidyStatus: int {
case Pending = 0;
case Completed = 5;
public function text(): string
{
return static::texts()[$this->value] ?? 'Unknown';
}
public static function colors(): array
{
return [
static::Pending->value => '#5b69bc',
static::Completed->value => '#21b978',
];
}
public static function texts(): array
{
return [
static::Pending->value => '待付款',
static::Completed->value => '已完成',
];
}
}

View File

@ -15,4 +15,5 @@ enum DealerWalletAction: int {
case TransferOut = 10;
case EarningIn = 11;
case EarningOut = 12;
case DeliveryBillPaid = 13;
}

View File

@ -37,7 +37,7 @@ enum PayWay: string {
};
}
public function getMallOrderText()
public function mallText()
{
return match ($this) {
static::Offline => '线下',
@ -49,25 +49,40 @@ enum PayWay: string {
};
}
public function getDealerOrderText()
/**
* 支付方式文本
*
* @return string
*/
public function text(): string
{
return match ($this) {
static::Offline => '线下打款',
static::Wallet => '余额支付',
static::WxpayH5 => '微信支付',
default => 'Unknown',
static::Offline => '线下',
static::Balance => '余额',
static::Wallet => '钱包',
static::WxpayApp, static::WxpayH5, static::WxpayJsApi, static::WxpayMiniProgram => '微信支付',
static::AlipayApp => '支付宝',
};
}
/**
* @return string
* 支付方式对应的颜色
*
* @return array
*/
public static function dealerOrderTexts(): array
public static function colors(): array
{
return [
static::Offline->value => '线下打款',
static::Wallet->value => '余额支付',
static::WxpayH5->value => '微信支付',
static::Offline->value => '#5b69bc',
static::Balance->value => '#dda451',
static::Wallet->value => '#ff8acc',
// 微信支付
static::WxpayApp->value => '#21b978',
static::WxpayH5->value => '#21b978',
static::WxpayJsApi->value => '#21b978',
static::WxpayMiniProgram->value => '#21b978',
// 支付宝
static::AlipayApp->value => '#3085d6',
];
}
}

View File

@ -355,7 +355,8 @@ class Dealer extends Model
public function canWithdraw()
{
$days = app_settings('dealer.withdraw_days', 7);
return DealerWalletToBankLog::where('user_id', $this->user_id)->where('created_at', '>', now()->subDays($days))->doesntExist();
return true;
// $days = app_settings('dealer.withdraw_days', 7);
// return DealerWalletToBankLog::where('user_id', $this->user_id)->where('created_at', '>', now()->subDays($days))->doesntExist();
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Models;
use App\Enums\DealerDeliveryBillStatus;
use App\Enums\PayWay;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Model;
class DealerDeliveryBill extends Model
{
use Filterable;
protected $attributes = [
'status' => DealerDeliveryBillStatus::Pending,
];
protected $casts = [
'status' => DealerDeliveryBillStatus::class,
'pay_way' => PayWay::class,
'pay_at' => 'datetime',
];
protected $fillable = [
'sn',
'user_id',
'order_id',
'shipping_fee',
'remark',
'consignee_name',
'consignee_telephone',
'consignee_zone',
'consignee_address',
'pay_sn',
'pay_way',
'out_trade_no',
'pay_at',
'status',
];
/**
* {@inheritdoc}
*/
protected static function booted()
{
parent::creating(function ($bill) {
if ($bill->sn === null) {
do {
$bill->sn = serial_number();
} while (static::where('sn', $bill->sn)->exists());
}
});
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function order()
{
return $this->hasOne(DealerOrder::class, 'order_id');
}
public function payLogs()
{
return $this->morphMany(PayLog::class, 'payable');
}
public function deliveryProducts()
{
return $this->hasMany(DealerDeliveryProduct::class, 'delivery_bill_id');
}
public function isPending()
{
return $this->status === DealerDeliveryBillStatus::Pending;
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DealerDeliveryProduct extends Model
{
public function product()
{
return $this->belongsTo(DealerProduct::class, 'product_id');
}
}

View File

@ -68,6 +68,28 @@ class DealerEarning extends Model
return $this->morphTo();
}
/**
* 仅查询渠道补贴收益
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWithoutPayer($query)
{
return $query->whereNull('payer_id');
}
/**
* 仅查询渠道补贴收益
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeChannelSubsidy($query)
{
return $query->where('earningable_type', (new DealerChannelSubsidyLog())->getMorphClass());
}
/**
* 待打款
*
@ -92,6 +114,16 @@ class DealerEarning extends Model
]);
}
/**
* 确认此收益是否结算
*
* @return boolean
*/
public function isSettled(): bool
{
return $this->settle_at !== null;
}
/**
* 待打款状态
*

View File

@ -51,6 +51,8 @@ class DealerOrder extends Model
'remark',
'pay_sn',
'out_trade_no',
'local_status',
'deposit_status',
];
/**
@ -126,6 +128,11 @@ class DealerOrder extends Model
return $query->where('status', DealerOrderStatus::Cancelled);
}
public function dealerDeliveryBill()
{
return $this->belongsTo(DealerDeliveryBill::class, 'order_id');
}
/**
* 此订单所属的用户的信息
*/
@ -277,6 +284,7 @@ class DealerOrder extends Model
{
return in_array($this->pay_way, [
PayWay::WxpayH5,
PayWay::WxpayJsApi,
]);
}

View File

@ -15,6 +15,7 @@ class DealerOrderProduct extends Model
'price',
'sale_price',
'qty',
'deposit_qty',
];
/**

View File

@ -49,6 +49,11 @@ class DealerPurchaseSubsidy extends Model
return $query->where('settle_state', DealerPurchaseSubsidySettleState::Completed);
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function dealer()
{
return $this->belongsTo(Dealer::class, 'user_id', 'user_id');
@ -64,8 +69,20 @@ class DealerPurchaseSubsidy extends Model
return $this->morphOne(DealerEarning::class, 'earningable');
}
/**
* 仅查询待付款的管理津贴
*
* @return bool
*/
public function isPending(): bool
{
return $this->settle_state === DealerPurchaseSubsidySettleState::Completed
&& $this->status === DealerPurchaseSubsidyStatus::Pending;
}
public function isCompleted()
{
return $this->status === DealerPurchaseSubsidyStatus::Completed;
return $this->settle_state === DealerPurchaseSubsidySettleState::Completed
&& $this->status === DealerPurchaseSubsidyStatus::Completed;
}
}

View File

@ -11,10 +11,16 @@ class DealerUserProduct extends Model
use HasFactory;
use HasDateTimeFormatter;
protected $attributes = [
'stock' => 0,
'deposit_stock' => 0,
];
protected $fillable = [
'user_id',
'product_id',
'stock',
'deposit_stock',
];
public function product()

View File

@ -13,12 +13,27 @@ class DealerUserProductLog extends Model
use Filterable;
use HasDateTimeFormatter;
// 本地库存
public const TYPE_ORDER_IN = 1; //采购加库存
public const TYPE_ORDER_OUT = 2; //发货扣库存
public const TYPE_OFFLINE_OUT = 3;//线下去库存
public const TYPE_ADMIN_IN = 4;//后台添加库存
public const TYPE_ADMIN_OUT = 5;//后台扣减库存
public const TYPE_REVOKE_IN = 9;//撤销线下去库存
public const TYPE_OFFLINE_OUT = 3; //线下去库存
public const TYPE_ADMIN_IN = 4; //后台添加库存
public const TYPE_ADMIN_OUT = 5; //后台扣减库存
public const TYPE_REVOKE_IN = 9; //撤销线下去库存
public const TYPE_TRANSFER_IN = 6; // 云库存转本地库存
// 云库存
public const TYPE_DEPOSIT_TRANSFER_OUT = 10; // 云库存转本地库存
public const TYPE_DEPOSIT_TRANSFER_REVOKE = 11; // 撤销云库存转本地库存
public const TYPE_ORDER_OUT_REVOKE = 12;//撤销发货扣库存
protected $attributes = [
'is_deposit' => false,
];
protected $casts = [
'is_deposit' => 'bool',
];
protected $fillable = [
'user_id',
@ -27,6 +42,7 @@ class DealerUserProductLog extends Model
'qty',
'remark',
'revoke_id',
'is_deposit',
];
public function product()
@ -38,4 +54,18 @@ class DealerUserProductLog extends Model
{
return ($this->type == static::TYPE_OFFLINE_OUT) && ($this->revoke_id == 0);
}
public function getQtyFormatAttribute()
{
$symbok = in_array($this->type, [
DealerUserProductLog::TYPE_ORDER_IN,
DealerUserProductLog::TYPE_ADMIN_IN,
DealerUserProductLog::TYPE_REVOKE_IN,
DealerUserProductLog::TYPE_TRANSFER_IN,
DealerUserProductLog::TYPE_DEPOSIT_TRANSFER_REVOKE,
DealerUserProductLog::TYPE_ORDER_OUT_REVOKE,
]) ? '+' : '-';
return $symbok . $this->attributes['qty'];
}
}

View File

@ -417,4 +417,14 @@ class Order extends Model
// 其它
return OrderStatus::UNKNOWN;
}
/**
* 获取订单状态
*
* @return string
*/
public function getOrderStatusTextAttribute(): string
{
return OrderStatus::$statusTexts[$this->order_status] ?? OrderStatus::$statusTexts[OrderStatus::UNKNOWN];
}
}

View File

@ -41,6 +41,20 @@ class PayLog extends Model
'failed_reason',
];
/**
* {@inheritdoc}
*/
protected static function booted()
{
parent::creating(function ($payLog) {
if ($payLog->pay_sn === null) {
do {
$payLog->pay_sn = serial_number();
} while (static::where('pay_sn', $payLog->pay_sn)->exists());
}
});
}
/**
* 获取支付记录所属的模型
*/

View File

@ -197,6 +197,15 @@ class User extends Model implements AuthorizableContract, AuthenticatableContrac
return $this->hasMany(DealerOrder::class, 'user_id');
}
/**
* 经销商提货单
*
*/
public function dealerDeliveryBills()
{
return $this->hasMany(DealerDeliveryBill::class, 'user_id');
}
/**
* 经销商的发货订单
*

View File

@ -66,6 +66,7 @@ class AppServiceProvider extends ServiceProvider
'dealer_wallet_to_bank_log' => \App\Models\DealerWalletToBankLog::class,
'dealer_earnings'=> \App\Models\DealerEarning::class,
'dealer_wallet_log'=> \App\Models\DealerWalletLog::class,
'dealer_delivery_bill'=> \App\Models\DealerDeliveryBill::class,
]);
JsonResource::withoutWrapping();

View File

@ -0,0 +1,259 @@
<?php
namespace App\Services\Dealer;
use App\Enums\DealerDeliveryBillStatus;
use App\Enums\DealerWalletAction;
use App\Enums\PayWay;
use App\Enums\WxpayTradeType;
use App\Exceptions\BizException;
use App\Models\DealerDeliveryBill;
use App\Models\DealerDeliveryProduct;
use App\Models\DealerOrder;
use App\Models\DealerOrderProduct;
use App\Models\DealerProduct;
use App\Models\DealerUserProduct;
use App\Models\DealerUserProductLog;
use App\Models\PayLog;
use App\Models\User;
use App\Services\Payment\WxpayService;
use App\Services\PayService;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
class DealerDeliveryBillService
{
/**
* 创建提货单
*
* @param \App\Models\User $user
* @param array $deliveryProducts
* @param array $consignee
* @param string|null $remark
* @param int|null $DealerOrder
* @return \App\Models\DealerDeliveryBill
*/
public function create(User $user, array $deliveryProducts, array $consignee, ?string $remark = null, ?DealerOrder $dealerOrder = null): DealerDeliveryBill
{
$deliveryProductIds = Arr::pluck($deliveryProducts, 'id');
// 批零商品
$products = DealerProduct::findMany($deliveryProductIds);
if (count($deliveryProducts) !== $products->count()) {
throw new BizException('提货单商品已丢失');
}
// 经销商的商品库存
$dealerProducts = $user->dealerProducts()
->whereIn('product_id', $deliveryProductIds)
->get();
// 创建提货单
$deliveryBill = DealerDeliveryBill::create([
'user_id' => $user->id,
'shipping_fee' => app_settings('dealer.delivery_bill_shipping_fee', 0),
'remark' => $remark,
'consignee_name' => $consignee['name'],
'consignee_telephone' => $consignee['telephone'],
'consignee_zone' => $consignee['zone'],
'consignee_address' => $consignee['address'],
'status' => DealerDeliveryBillStatus::Pending,
'order_id' => $dealerOrder?->id,
]);
$mapProducts = $products->keyBy('id');
$mapDealerProducts = $dealerProducts->keyBy('product_id');
$dealerProductLogs = [];
$deliveryBillProducts = [];
foreach ($deliveryProducts as $deliveryProduct) {
$product = Arr::get($mapProducts, $deliveryProduct['id']);
$dealerProduct = Arr::get($mapDealerProducts, $product->id);
if ($dealerProduct?->deposit_stock < $deliveryProduct['qty']) {
throw new BizException("{$product->name} 库存不足");
}
$dealerProduct->decrement('deposit_stock', $deliveryProduct['qty']);
$dealerProductLogs[] = [
'user_id' => $dealerProduct->user_id,
'product_id' => $dealerProduct->product_id,
'type' => DealerUserProductLog::TYPE_DEPOSIT_TRANSFER_OUT,
'qty' => $deliveryProduct['qty'],
'remark' => "提货单【{$deliveryBill->sn}",
'is_deposit' => true,
'created_at' => $deliveryBill->created_at,
'updated_at' => $deliveryBill->updated_at,
];
$deliveryBillProducts[] = [
'delivery_bill_id' => $deliveryBill->id,
'product_id' => $product->id,
'qty' => $deliveryProduct['qty'],
'created_at' => $deliveryBill->created_at,
'updated_at' => $deliveryBill->updated_at,
];
}
DealerUserProductLog::insert($dealerProductLogs);
DealerDeliveryProduct::insert($deliveryBillProducts);
return $deliveryBill;
}
/**
* 提货单付款
*
* @param \App\Models\DealerDeliveryBill $deliveryBill
* @param \App\Enums\PayWay $payWay
* @return array
*
* @throws \App\Exceptions\BizException
*/
public function pay(DealerDeliveryBill $deliveryBill, PayWay $payWay): array
{
if (! $deliveryBill->isPending()) {
throw new BizException('提货单状态不是待付款');
}
$method = match ($payWay) {
PayWay::Wallet => 'payUsingWallet',
PayWay::WxpayH5, PayWay::WxpayJsApi => 'payUsingWxpay',
default => 'payUsingDefault',
};
if (! method_exists($this, $method)) {
throw new BizException('交易非法');
}
$payLog = $deliveryBill->payLogs()->create([
'pay_way' => $payWay,
]);
return [
'pay_way' => $payLog->pay_way,
'data' => $this->{$method}($deliveryBill, $payLog),
];
}
/**
* 取消提货单
*
* @param DealerDeliveryBill $deliveryBill
* @return void
*/
public function cancel(DealerDeliveryBill $deliveryBill)
{
if (! $deliveryBill->isPending()) {
throw new BizException('提货单状态不是待付款');
}
$deliveryBill->update([
'status' => DealerDeliveryBillStatus::Cancelled,
]);
$dealerProductLogs = [];
foreach ($deliveryBill->deliveryProducts as $deliveryProduct) {
DealerUserProduct::where([
'user_id' => $deliveryBill->user_id,
'product_id' => $deliveryProduct->product_id,
])->update([
'deposit_stock' => DB::raw("deposit_stock + {$deliveryProduct->qty}"),
]);
$dealerProductLogs[] = [
'user_id' => $deliveryBill->user_id,
'product_id' => $deliveryProduct->product_id,
'type' => DealerUserProductLog::TYPE_DEPOSIT_TRANSFER_REVOKE,
'qty' => $deliveryProduct->qty,
'remark' => "撤销提货单【{$deliveryBill->sn}",
'is_deposit' => true,
'created_at' => $deliveryBill->updated_at,
'updated_at' => $deliveryBill->updated_at,
];
}
//撤回已发货的本地库存
if ($deliveryBill->order_id) {
$dealerOrder = DealerOrder::with('products')->find($deliveryBill->order_id);
foreach ($dealerOrder->products as $product) {
if ($product->qty > 0 && $dealerOrder->local_status == 1) {
$userProduct = $dealerOrder->consignor->dealerProducts()->firstOrCreate([
'product_id'=> $product->product_id,
]);
$userProduct->increment('stock', $product->qty);
$dealerProductLogs[] = [
'user_id' => $dealerOrder->consignor_id,
'product_id' => $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_OUT_REVOKE,
'qty' => $product->qty,
'remark' => "撤销订单发货【{$dealerOrder->sn}",
'is_deposit' => false,
'created_at' => $deliveryBill->updated_at,
'updated_at' => $deliveryBill->updated_at,
];
}
//将云仓发货的数量挪回来
DealerOrderProduct::where('id', $product->id)->update([
'qty' => $product->qty + $product->deposit_qty,
'deposit_qty' => 0,
]);
}
$dealerOrder->update([
'local_status'=>0,
]);
}
DealerUserProductLog::insert($dealerProductLogs);
}
/**
* 使用余额支付
*
* @param \App\Models\DealerDeliveryBill $deliveryBill
* @param \App\Models\PayLog $payLog
* @return void
*/
protected function payUsingWallet(DealerDeliveryBill $deliveryBill, PayLog $payLog)
{
(new WalletService())->changeBalance(
$deliveryBill->user,
bcmul($deliveryBill->shipping_fee, '-1', 2),
DealerWalletAction::DeliveryBillPaid,
"提货单号:{$deliveryBill->sn}",
$deliveryBill
);
(new PayService())->handleSuccess($payLog, [
'pay_at' => now(),
]);
}
/**
* 使用微信支付
*
* @param \App\Models\DealerDeliveryBill $deliveryBill
* @param \App\Models\PayLog $payLog
* @return array
*/
protected function payUsingWxpay(DealerDeliveryBill $deliveryBill, PayLog $payLog): array
{
if (is_null($tradeType = WxpayTradeType::tryFromPayWay($payLog->pay_way))) {
throw new BizException('支付方式 非法');
}
$params = [
'body' => app_settings('app.app_name').'-批零订单',
'out_trade_no' => $payLog->pay_sn,
'total_fee' => bcmul($deliveryBill->shipping_fee, '100'),
'trade_type' => $tradeType->value,
];
return (new WxpayService())->pay($params, 'yzk_h5');
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace App\Services\Dealer;
use App\Enums\DealerEarningStatus;
use App\Enums\DealerManageSubsidyStatus;
use App\Enums\DealerWalletAction;
use App\Exceptions\BizException;
use App\Models\DealerEarning;
use App\Models\DealerManageSubsidy;
class ManageSubsidyService
{
/**
* @param \App\Models\DealerManageSubsidy $dealerManageSubsidy
* @return void
*/
public function pay(DealerManageSubsidy $dealerManageSubsidy)
{
if (! $dealerManageSubsidy->isPending()) {
throw new BizException('管理津贴 不是待付款状态');
}
$dealerManageSubsidy->update([
'status' => DealerManageSubsidyStatus::Completed,
]);
$dealerManageSubsidy->earning->update([
'pay_way' => DealerEarning::PAY_WAY_WALLET,
'pay_at' => now(),
'pay_info' => null,
'status' => DealerEarningStatus::Completed,
]);
(new WalletService())->changeBalance(
$dealerManageSubsidy->user,
$dealerManageSubsidy->real_amount,
DealerWalletAction::ManageSubsidyIn,
'收入-管理津贴',
$dealerManageSubsidy
);
}
}

View File

@ -1,43 +0,0 @@
<?php
namespace App\Services\Dealer;
use App\Enums\DealerEarningStatus;
use App\Enums\DealerManagerSubsidyStatus;
use App\Enums\DealerWalletAction;
use App\Exceptions\BizException;
use App\Models\DealerEarning;
use App\Models\DealerManagerSubsidy;
class ManagerSubsidyService
{
/**
* @param \App\Models\DealerManagerSubsidy $dealerManagerSubsidy
* @return void
*/
public function pay(DealerManagerSubsidy $dealerManagerSubsidy)
{
if (! $dealerManagerSubsidy->isPending()) {
throw new BizException('管理者津贴 不是待付款状态');
}
$dealerManagerSubsidy->update([
'status' => DealerManagerSubsidyStatus::Completed,
]);
$dealerManagerSubsidy->earning->update([
'pay_way' => DealerEarning::PAY_WAY_WALLET,
'pay_at' => now(),
'pay_info' => null,
'status' => DealerEarningStatus::Completed,
]);
(new WalletService())->changeBalance(
$dealerManagerSubsidy->user,
$dealerManagerSubsidy->real_amount,
DealerWalletAction::ManagerSubsidyIn,
'收入-管理者津贴',
$dealerManagerSubsidy
);
}
}

View File

@ -2,16 +2,19 @@
namespace App\Services\Dealer;
use App\Enums\DealerDeliveryBillStatus;
use App\Enums\DealerLvl;
use App\Enums\DealerOrderStatus;
use App\Enums\DealerWalletAction;
use App\Enums\PayWay;
use App\Enums\WxpayTradeType;
use App\Exceptions\BizException;
use App\Models\DealerDeliveryBill;
use App\Models\DealerOrder;
use App\Models\DealerOrderAllocateLog;
use App\Models\DealerOrderProduct;
use App\Models\DealerProduct;
use App\Models\DealerUserProduct;
use App\Models\DealerUserProductLog;
use App\Models\ShippingAddress;
use App\Models\User;
@ -283,20 +286,9 @@ class OrderService
throw new BizException('订单状态不是待付款');
}
do {
$payLog = null;
try {
$payLog = $order->payLogs()->create([
'pay_sn' => serial_number(),
'pay_way' => $payWay,
]);
} catch (QueryException $e) {
if (strpos($e->getMessage(), 'Duplicate entry') === false) {
throw $e;
}
}
} while ($payLog === null);
$payLog = $order->payLogs()->create([
'pay_way' => $payWay,
]);
$data = [
'pay_sn' => $payLog->pay_sn,
@ -346,6 +338,7 @@ class OrderService
break;
case PayWay::WxpayH5:
case PayWay::WxpayJsApi:
if (is_null($tradeType = WxpayTradeType::tryFromPayWay($payLog->pay_way))) {
throw new BizException('支付方式 非法');
}
@ -387,6 +380,10 @@ class OrderService
'status' => DealerOrderStatus::Paid,
'paied_time' => now(),
]);
//签约单,云库存直接发货
if ($order->consignor === null) {
$this->orderInDepositstock($order);
}
return $order;
}
@ -394,16 +391,21 @@ class OrderService
* 确认发货
*
* @param DealerOrder $order
* @param string $action
* @return DealerOrder $order
*/
public function shippingOrder(DealerOrder $order)
public function shippingOrder(DealerOrder $order, ?string $action = 'qty')
{
if (!$order->isPaid()) {
throw new BizException('无法发货:订单状态异常,请刷新后再试');
}
//扣减发货人库存
if ($order->consignor) {
$this->orderOutQty($order);
if ($action == 'deposit_qty') {
$this->orderOutDepositQty($order);
} else {
$this->orderOutQty($order);
}
}
$order->update([
@ -413,7 +415,67 @@ class OrderService
return $order;
}
public function shippingedOrder(DealerOrder $order)
/**
* 使用云仓发货
*/
public function shippingOrderByDeposit(DealerOrder $order)
{
$depositProducts = [];
$deliveryBill = null;
//判断这个订单是否已经有待支付的云仓发货单
if ($deliveryBill = DealerDeliveryBill::where([
'user_id' => $order->consignor_id,
'order_id' => $order->id,
'status' => DealerDeliveryBillStatus::Pending,
])->first()) {
return $deliveryBill;
}
//判断本地库存是否足够,不足则生成云仓提货单,并唤起支付。
foreach ($order->products as $product) {
//记录需要生成云仓发货单的商品信息以及数量
if ($_userProduct = DealerUserProduct::where([
'user_id'=>$order->consignor_id,
'product_id'=>$product->product_id,
])->first()) {
if ($product->qty > $_userProduct->stock) {
if ($product->qty > $_userProduct->stock + $_userProduct->deposit_stock) {
throw new BizException('当前可发货库存不足');
}
//记录
$depositProducts[$product->id] = [
'id'=> $product->product_id,
'qty'=> $product->qty - $_userProduct->stock,
];
}
} else {
throw new BizException('当前可发货库存不足');
}
}
if ($depositProducts) {
$dealerDeliveryBillService = new DealerDeliveryBillService();
$deliveryBill = $dealerDeliveryBillService->create($order->consignor, $depositProducts, [
'name'=>$order->consignee_name,
'telephone'=>$order->consignee_telephone,
'zone'=>$order->consignee_zone,
'address'=>$order->consignee_address,
], '订单发货:【'.$order->sn.'】', $order);
//更新订单相关的库存发货情况
if ($order) {
foreach ($order->products as $product) {
if (isset($depositProducts[$product->id])) {
DealerOrderProduct::where('id', $product->id)->update([
'qty' => $product->qty - $depositProducts[$product->id]['qty'],
'deposit_qty' => $depositProducts[$product->id]['qty'],
]);
}
}
}
}
return $deliveryBill;
}
public function shippingedOrder(DealerOrder $order, ?string $action = 'qty')
{
if (!$order->isShipping()) {
throw new BizException('无法收货:订单状态异常,请刷新后再试');
@ -490,42 +552,145 @@ class OrderService
protected function orderInQty(DealerOrder $order)
{
foreach ($order->products as $product) {
//增加本地库存
$userProduct = $order->user->dealerProducts()->firstOrCreate([
'product_id'=> $product->product_id,
]);
$userProduct->increment('stock', $product->qty);
//如果云仓已收货
if ($order->local_status < 2 && $order->deposit_status !== 1) {
$userProduct->increment('stock', $product->qty);
DealerUserProductLog::create([
'user_id'=> $order->user_id,
'product_id'=> $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_IN,
'qty'=>$product->qty,
'remark'=>'订单:'.$order->sn,
]);
} else {
$userProduct->increment('stock', $product->qty + $product->deposit_qty);
DealerUserProductLog::create([
'user_id'=> $order->user_id,
'product_id'=> $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_IN,
'qty'=>$product->qty + $product->deposit_qty,
'remark'=>'订单:'.$order->sn,
]);
}
}
$order->update([
'local_status' => 2,
'deposit_status'=> 2,
]);
}
/**
* 用户通过订单云仓库增加本地库存
*
* @return void
*/
protected function orderInAllQty(DealerOrder $order)
{
foreach ($order->products as $product) {
$userProduct = $order->user->dealerProducts()->firstOrCreate([
'product_id'=> $product->product_id,
]);
$userProduct->increment('stock', $product->qty + $product->deposit_qty);
DealerUserProductLog::create([
'user_id'=> $order->user_id,
'product_id'=> $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_IN,
'qty'=>$product->qty,
'qty'=>$product->qty + $product->deposit_qty,
'remark'=>'订单:'.$order->sn,
]);
}
}
/**
* 用户通过订单扣减库存
* 用户通过订单扣减本地库存发货
*
* @return void
*/
protected function orderOutQty(DealerOrder $order)
public function orderOutQty(DealerOrder $order)
{
foreach ($order->products as $product) {
$userProduct = $order->consignor->dealerProducts()->firstOrCreate([
'product_id'=> $product->product_id,
]);
$userProduct->decrement('stock', $product->qty);
DealerUserProductLog::create([
'user_id'=> $order->consignor_id,
'product_id'=> $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_OUT,
'qty'=>$product->qty,
'remark' =>'订单:'.$order->sn,
]);
if ($product->qty > 0 && $order->local_status == 0) {
$userProduct = $order->consignor->dealerProducts()->firstOrCreate([
'product_id'=> $product->product_id,
]);
$userProduct->decrement('stock', $product->qty);
DealerUserProductLog::create([
'user_id'=> $order->consignor_id,
'product_id'=> $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_OUT,
'qty'=>$product->qty,
'remark' =>'订单:'.$order->sn,
]);
}
}
$order->update([
'local_status'=>1,
]);
}
/**
* 用户通过订单扣减云库存发货
*
* @return void
*/
public function orderOutDepositQty(DealerOrder $order)
{
foreach ($order->products as $product) {
if ($product->deposit_qty > 0 && $order->deposit_status == 0) {
$userProduct = $order->consignor->dealerProducts()->firstOrCreate([
'product_id'=> $product->product_id,
]);
$userProduct->decrement('stock', $product->deposit_qty);
DealerUserProductLog::create([
'user_id'=> $order->consignor_id,
'product_id'=> $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_OUT,
'qty'=>$product->deposit_qty,
'remark' =>'订单:'.$order->sn,
]);
}
}
$order->update([
'deposit_status'=>1,
]);
}
/**
* 用户通过订单获取云库存
*
* @param DealerOrder $order
* @return void
*/
public function orderInDepositstock(DealerOrder $order)
{
foreach ($order->products as $product) {
if ($product->deposit_qty > 0 && $order->deposit_status == 0) {
$userProduct = $order->user->dealerProducts()->firstOrCreate([
'product_id'=> $product->product_id,
]);
$userProduct->increment('deposit_stock', $product->deposit_qty);
DealerUserProductLog::create([
'is_deposit'=>true,
'user_id'=> $order->user_id,
'product_id'=> $product->product_id,
'type' => DealerUserProductLog::TYPE_ORDER_IN,
'qty'=>$product->deposit_qty,
'remark' =>'订单:'.$order->sn,
]);
}
}
$order->update([
'deposit_status'=>2,
]);
}
private function getConsignor(User $user, $totalAmount, ?User $lastConsignor = null)

View File

@ -2,13 +2,19 @@
namespace App\Services;
use App\Enums\DealerDeliveryBillStatus;
use App\Enums\DealerOrderStatus;
use App\Exceptions\BizException;
use App\Exceptions\InvalidPaySerialNumberException;
use App\Models\DealerDeliveryBill;
use App\Models\DealerOrder;
use App\Models\DealerUserProduct;
use App\Models\DealerUserProductLog;
use App\Models\DistributionPreIncomeJob;
use App\Models\Order;
use App\Models\PayLog;
use App\Services\Dealer\OrderService;
use Illuminate\Support\Facades\DB;
class PayService
{
@ -99,9 +105,55 @@ class PayService
} else {
$payable->paied_time = $payLog->pay_at;
$payable->status = DealerOrderStatus::Paid;
//签约单,云库存直接发货
if ($payable->consignor === null) {
(new OrderService())->orderInDepositstock($payable);
}
}
$payable->save();
} elseif ($payable instanceof DealerDeliveryBill) {
if (! $payable->isPending()) {
throw new BizException('提货单状态不是待打款');
}
$payable->pay_sn = $payLog->pay_sn;
$payable->pay_at = $payLog->pay_at;
$payable->out_trade_no = $payLog->out_trade_no;
$payable->pay_way = $payLog->pay_way;
$payable->status = DealerDeliveryBillStatus::Paid;
$payable->save();
// 将云仓库存变更为本地库存
$dealerProductLogs = [];
foreach ($payable->deliveryProducts as $deliveryProduct) {
DealerUserProduct::where([
'user_id' => $payable->user_id,
'product_id' => $deliveryProduct->product_id,
])->update([
'stock' => DB::raw("stock + {$deliveryProduct->qty}"),
]);
$dealerProductLogs[] = [
'user_id' => $payable->user_id,
'product_id' => $deliveryProduct->product_id,
'type' => DealerUserProductLog::TYPE_TRANSFER_IN,
'qty' => $deliveryProduct->qty,
'remark' => "云库存转本地库存,提货单【{$payable->sn}",
'is_deposit' => false,
'created_at' => $payable->updated_at,
'updated_at' => $payable->updated_at,
];
}
DealerUserProductLog::insert($dealerProductLogs);
//如果云仓库提货单存在关联订单,则该订单直接处理云仓部分的发货
if ($payable->order_id) {
$dealerOrder = DealerOrder::find($payable->order_id);
(new OrderService())->shippingOrder($dealerOrder, 'deposit_qty');
}
}
return $payLog;

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDepositStockToDealerUserProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('dealer_user_products', function (Blueprint $table) {
$table->unsignedBigInteger('deposit_stock')->default(0)->comment('托管库存');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('dealer_user_products', function (Blueprint $table) {
$table->dropColumn(['deposit_stock']);
});
}
}

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDepositQtyToDealerOrderProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('dealer_order_products', function (Blueprint $table) {
$table->unsignedBigInteger('deposit_qty')->default(0)->comment('托管数量');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('dealer_order_products', function (Blueprint $table) {
$table->dropColumn(['deposit_qty']);
});
}
}

View File

@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDealerDeliveryBillsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('dealer_delivery_bills', function (Blueprint $table) {
$table->id();
$table->string('sn')->unique();
$table->unsignedBigInteger('user_id');
$table->unsignedBigInteger('order_id')->nullable();
$table->unsignedDecimal('shipping_fee', 18, 2)->default(0)->comment('运费');
$table->string('remark')->nullable()->comment('备注');
// 收货人信息
$table->string('consignee_name')->nullable()->comment('收货人-姓名');
$table->string('consignee_telephone')->nullable()->comment('收货人-联系方式');
$table->string('consignee_zone')->nullable()->comment('收货人-所在地区');
$table->string('consignee_address')->nullable()->comment('收货人-详细地址');
// 支付信息
$table->string('pay_sn')->nullable()->comment('支付单号');
$table->string('pay_way')->nullable()->comment('支付方式');
$table->string('out_trade_no')->nullable()->comment('外部交易号');
$table->timestamp('pay_at')->nullable()->comment('付款时间');
$table->tinyInteger('status')->default(0)->comment('状态');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('dealer_delivery_bills');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateDealerDeliveryProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('dealer_delivery_products', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('delivery_bill_id');
$table->unsignedBigInteger('product_id');
$table->unsignedBigInteger('qty');
$table->timestamps();
$table->unique(['delivery_bill_id', 'product_id']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('dealer_delivery_products');
}
}

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddIsDepositToDealerUserProductLogsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('dealer_user_product_logs', function (Blueprint $table) {
//
$table->boolean('is_deposit')->default(false)->comment('是否云库存');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('dealer_user_product_logs', function (Blueprint $table) {
//
$table->dropColumn(['is_deposit']);
});
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDepositStatusToDealerOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('dealer_orders', function (Blueprint $table) {
//
$table->unsignedTinyInteger('local_status')->nullable()->default(0)->comment('本地仓发货状态');
$table->unsignedTinyInteger('deposit_status')->nullable()->default(0)->comment('云仓发货状态');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('dealer_orders', function (Blueprint $table) {
//
$table->dropColumn(['local_status', 'deposit_status']);
});
}
}

View File

@ -309,6 +309,11 @@ class AdminMenuSeeder extends Seeder
'icon' => '',
'uri' => 'dealer-manager-orders?type=manager&filter-status[]=2&filter-status[]=3',
],
[
'title' => '云仓提货单',
'icon' => '',
'uri' => 'dealer-delivery-bills',
],
// [
// 'title' =>'用户资金',
// 'icon'=>'',
@ -319,10 +324,16 @@ class AdminMenuSeeder extends Seeder
'icon'=>'',
'uri' => 'dealer-earnings-channel?filter-earningable_type[]=dealer_channel_subsidy_log',
],
// [
// 'title' =>'签约渠道补贴',
// 'icon'=>'',
// 'uri' => 'dealer-channel-subsidies',
// ],
[
'title' =>'进货补贴',
'icon' => '',
'uri' => 'dealer-earnings-purchase?filter-earningable_type[]=dealer_purchase_subsidy',
// 'uri' => 'dealer-purchase-subsidies',
],
[
'title' =>'进货补贴明细',
@ -333,6 +344,7 @@ class AdminMenuSeeder extends Seeder
'title'=>'管理津贴',
'icon' => '',
'uri' => 'dealer-earnings-manage?filter-earningable_type[]=dealer_manage_subsidy',
// 'uri' => 'dealer-manage-subsidies',
],
[
'title' =>'管理津贴明细',
@ -343,6 +355,7 @@ class AdminMenuSeeder extends Seeder
'title'=>'管理者津贴',
'icon' => '',
'uri' => 'dealer-earnings-manage?filter-earningable_type[]=dealer_manager_subsidy',
// 'uri' => 'dealer-manager-subsidies',
],
[
'title'=>'管理者津贴明细',

View File

@ -213,6 +213,7 @@ class AdminPermissionSeeder extends Seeder
'curd' => ['index', 'show'],
'children'=>[
'export_shipping_orders'=>['name' =>'导出发货单'],
'export_order_products'=>['name' =>'导出商品'],
'tags'=>['name' =>'标签设置'],
'pay'=>['name' =>'支付订单'],
'reduce'=>['name' =>'订单改价'],
@ -341,6 +342,11 @@ class AdminPermissionSeeder extends Seeder
'cancel'=>['name' =>'取消订单'],
],
],
'dealer_delivery_bills' => [
'name' =>'云仓提货单',
'curd' => ['index', 'show'],
'children' =>[],
],
'dealer_earnings'=>[
'name' =>'资金管理',
'curd' => ['index', 'show'],
@ -352,6 +358,22 @@ class AdminPermissionSeeder extends Seeder
'pay'=>['name' =>'确认打款'],
],
],
'dealer_channel_subsidies' => [
'name' =>'签约渠道补贴',
'curd' => ['index'],
'children' => [
'pay' => ['name' => '付款'],
'batch_pay' => ['name' => '批量付款'],
],
],
'dealer_purchase_subsidies' => [
'name' =>'进货补贴',
'curd' => ['index', 'show'],
'children' => [
'pay' => ['name' => '付款'],
'batch_pay' => ['name' => '批量付款'],
],
],
'dealer_manager_subsidies' => [
'name' =>'管理者津贴',
'curd' => ['index'],