generated from liutk/owl-admin-base
Merge branch 'main' of https://gitea.hmily.club/pdkj/store-manage into main
commit
4f49fefbb2
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Controllers\Finance;
|
||||
|
||||
use App\Admin\Controllers\AdminController;
|
||||
use App\Admin\Services\Finance\ReimbursementService;
|
||||
use App\Enums\ReimbursementStatus;
|
||||
use App\Models\Reimbursement;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Slowlyo\OwlAdmin\Admin;
|
||||
use Slowlyo\OwlAdmin\Renderers\DialogAction;
|
||||
use Slowlyo\OwlAdmin\Renderers\Form;
|
||||
use Slowlyo\OwlAdmin\Renderers\Page;
|
||||
|
||||
/**
|
||||
* @property ReimbursementService $service
|
||||
*/
|
||||
class ReimbursementController extends AdminController
|
||||
{
|
||||
protected string $serviceName = ReimbursementService::class;
|
||||
|
||||
/**
|
||||
* 审核
|
||||
*/
|
||||
public function approval($id, Request $request)
|
||||
{
|
||||
$validator = Validator::make(
|
||||
data: $request->all(),
|
||||
rules: [
|
||||
'approval_result' => ['bail', 'required', Rule::in([1, 2])],
|
||||
'failed_reason' => ['bail', 'required_if:approval_result,2'],
|
||||
],
|
||||
attributes: [
|
||||
'approval_result' => __('finance.reimbursement.approval_result'),
|
||||
'failed_reason' => __('finance.reimbursement.failed_reason'),
|
||||
],
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return $this->response()->fail($validator->errors()->first());
|
||||
}
|
||||
|
||||
/** @var \App\Models\Reimbursement */
|
||||
$reimbursement = Reimbursement::findOrFail($id);
|
||||
|
||||
if (! $reimbursement->isPending()) {
|
||||
return $this->response()->fail('收支报销不是待审核状态');
|
||||
}
|
||||
|
||||
$reimbursement->update([
|
||||
'reimbursement_status' => $request->input('approval_result') == 1
|
||||
? ReimbursementStatus::Passed
|
||||
: ReimbursementStatus::Rejected,
|
||||
]);
|
||||
|
||||
return $this->response()->success(null, '审核成功');
|
||||
}
|
||||
|
||||
public function list(): Page
|
||||
{
|
||||
$crud = $this->baseCRUD()
|
||||
->headerToolbar([
|
||||
...$this->baseHeaderToolBar(),
|
||||
])
|
||||
->bulkActions([])
|
||||
->filter($this->baseFilter()->body([
|
||||
amis()->GroupControl()->mode('horizontal')->body([
|
||||
amis()->TextControl('employee_name', __('finance.reimbursement.employee'))
|
||||
->placeholder(__('finance.reimbursement.employee')),
|
||||
amis()->SelectControl('reimbursement_type_id', __('finance.reimbursement.type'))
|
||||
->multiple()
|
||||
->source(admin_url('api/keywords/tree-list?parent_key=reimbursement_type'))
|
||||
->labelField('name')
|
||||
->valueField('key'),
|
||||
amis()->SelectControl('status', __('finance.reimbursement.status'))
|
||||
->multiple()
|
||||
->options(ReimbursementStatus::options()),
|
||||
]),
|
||||
amis()->GroupControl()->mode('horizontal')->body([
|
||||
amis()->InputDatetimeRange()
|
||||
->name('datetime_range')
|
||||
->label(__('finance.reimbursement.created_at'))
|
||||
->format('YYYY-MM-DD HH:mm:ss')
|
||||
->columnRatio(4),
|
||||
]),
|
||||
]))
|
||||
->columns([
|
||||
// amis()->TableColumn()->name('id')->label(__('finance.reimbursement.id')),
|
||||
amis()->TableColumn()->name('employee.name')->label(__('finance.reimbursement.employee')),
|
||||
amis()->TableColumn()->name('expense')->label(__('finance.reimbursement.expense')),
|
||||
amis()->TableColumn()->name('reason')->label(__('finance.reimbursement.reason')),
|
||||
amis()->TableColumn()->name('type.name')->label(__('finance.reimbursement.type')),
|
||||
amis()->TableColumn()
|
||||
->name('reimbursement_status')
|
||||
->label(__('finance.reimbursement.status'))
|
||||
->type('mapping')
|
||||
->map(ReimbursementStatus::labelMap()),
|
||||
amis()->TableColumn()->name('created_at')->label(__('finance.reimbursement.created_at')),
|
||||
$this->rowActions([
|
||||
$this->rowApprovalButton()
|
||||
->visibleOn('${reimbursement_status == '.ReimbursementStatus::Pending->value.'}'),
|
||||
$this->rowShowButton()->visible(Admin::user()->can('admin.finance.reimbursements.view')),
|
||||
]),
|
||||
]);
|
||||
|
||||
return $this->baseList($crud);
|
||||
}
|
||||
|
||||
public function detail(): Form
|
||||
{
|
||||
return $this->baseDetail()->title()->body([
|
||||
amis()->Property()->items([
|
||||
['label' => __('finance.reimbursement.employee'), 'content' => '${employee.name}'],
|
||||
['label' => __('finance.reimbursement.expense'), 'content' => '${expense}'],
|
||||
['label' => __('finance.reimbursement.reason'), 'content' => '${reason}'],
|
||||
['label' => __('finance.reimbursement.type'), 'content' => '${type.name}'],
|
||||
['label' => __('finance.reimbursement.status'), 'content' => amis()->Mapping()->map(ReimbursementStatus::labelMap())->value('${reimbursement_status}')],
|
||||
['label' => __('finance.reimbursement.created_at'), 'content' => '${created_at}'],
|
||||
['label' => __('finance.reimbursement.photos'), 'content' => amis()->Images()->enlargeAble()->source('${photos}')->enlargeWithGallary(), 'span' => 3],
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核操作按钮
|
||||
*/
|
||||
protected function rowApprovalButton(): DialogAction
|
||||
{
|
||||
return amis()->DialogAction()->icon('fa-regular fa-check-circle')->label(__('finance.reimbursement.approval'))->level('link')->dialog(
|
||||
amis()->Dialog()->title(__('finance.reimbursement.approval'))->body([
|
||||
amis()->Form()->title('')
|
||||
->api('post:'.admin_url('finance/reimbursements/${id}/approval'))
|
||||
->body([
|
||||
amis()->RadiosControl('approval_result', __('finance.reimbursement.approval_result'))
|
||||
->options([
|
||||
['label' => '通过', 'value' => 1],
|
||||
['label' => '驳回', 'value' => 2],
|
||||
])
|
||||
->selectFirst()
|
||||
->required(),
|
||||
amis()->TextareaControl('failed_reason', __('finance.reimbursement.failed_reason'))
|
||||
->visibleOn('${approval_result == 2}')
|
||||
->required(),
|
||||
]),
|
||||
])->size('md')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Filters;
|
||||
|
||||
use EloquentFilter\ModelFilter;
|
||||
|
||||
class ReimbursementFilter extends ModelFilter
|
||||
{
|
||||
public function employeeName($name)
|
||||
{
|
||||
$this->related('employee', 'name', 'like', "%{$name}%");
|
||||
}
|
||||
|
||||
public function reimbursementType($id)
|
||||
{
|
||||
$this->where('reimbursement_type_id', explode(',', $id));
|
||||
}
|
||||
|
||||
public function datetimeRange($datetimeRange)
|
||||
{
|
||||
$this->whereBetween('created_at', explode(',', $datetimeRange));
|
||||
}
|
||||
|
||||
public function status($status)
|
||||
{
|
||||
$this->whereIn('reimbursement_status', explode(',', $status));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Admin\Services\Finance;
|
||||
|
||||
use App\Admin\Filters\ReimbursementFilter;
|
||||
use App\Admin\Services\BaseService;
|
||||
use App\Models\Reimbursement;
|
||||
|
||||
class ReimbursementService extends BaseService
|
||||
{
|
||||
protected string $modelName = Reimbursement::class;
|
||||
|
||||
protected string $modelFilterName = ReimbursementFilter::class;
|
||||
|
||||
protected array $withRelationships = ['employee', 'type'];
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
use App\Admin\Controllers\BaseKeywordController;
|
||||
use App\Admin\Controllers\Finance\CommissionIncomeController;
|
||||
use App\Admin\Controllers\Finance\LedgerController;
|
||||
use App\Admin\Controllers\Finance\ReimbursementController;
|
||||
use App\Admin\Controllers\Finance\SalesStatisticController;
|
||||
use App\Admin\Controllers\Finance\StoreMasterCommissionController;
|
||||
use App\Admin\Controllers\Finance\StoreStatisticController;
|
||||
|
|
@ -104,6 +105,9 @@ Route::group([
|
|||
$router->post('ledgers/{ledger}/ledger-amount', [LedgerController::class, 'updateLedgerAmount'])->name('ledgers.update_ledger_amount');
|
||||
// 佣金收入
|
||||
$router->get('commission-incomes', [CommissionIncomeController::class, 'index'])->name('commission_incomes.index');
|
||||
// 收支报销
|
||||
$router->resource('reimbursements', ReimbursementController::class);
|
||||
$router->post('reimbursements/{reimbursement}/approval', [ReimbursementController::class, 'approval'])->name('reimbursements.approval');
|
||||
// 销售统计
|
||||
$router->get('sales-statistics', [SalesStatisticController::class, 'index'])->name('sales_statistics.index');
|
||||
// 门店统计
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ReimbursementStatus: int
|
||||
{
|
||||
case Pending = 1;
|
||||
case Passed = 2;
|
||||
case Rejected = 3;
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Pending => '待审核',
|
||||
self::Passed => '已通过',
|
||||
self::Rejected => '未通过',
|
||||
};
|
||||
}
|
||||
|
||||
public static function options(): array
|
||||
{
|
||||
return collect(self::cases())
|
||||
->map(fn (ReimbursementStatus $case) => [
|
||||
'label' => $case->label(),
|
||||
'value' => $case->value,
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
public static function labelMap(): array
|
||||
{
|
||||
return [
|
||||
self::Pending->value => '<span class="label label-primary">'.self::Pending->label().'</span>',
|
||||
self::Passed->value => '<span class="label label-success">'.self::Passed->label().'</span>',
|
||||
self::Rejected->value => '<span class="label label-danger">'.self::Rejected->label().'</span>',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ReimbursementStatus;
|
||||
use App\Traits\HasDateTimeFormatter;
|
||||
use EloquentFilter\Filterable;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Reimbursement extends Model
|
||||
{
|
||||
use Filterable, HasDateTimeFormatter, HasFactory;
|
||||
|
||||
protected $attributes = [
|
||||
'reimbursement_status' => ReimbursementStatus::Pending,
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'reimbursement_status' => ReimbursementStatus::class,
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'employee_id',
|
||||
'reimbursement_type_id',
|
||||
'expense',
|
||||
'reason',
|
||||
'photos',
|
||||
'reimbursement_status',
|
||||
];
|
||||
|
||||
public function employee(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Employee::class);
|
||||
}
|
||||
|
||||
public function type(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Keyword::class, 'reimbursement_type_id', 'key');
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是待审核
|
||||
*/
|
||||
public function isPending(): bool
|
||||
{
|
||||
return $this->reimbursement_status === ReimbursementStatus::Pending;
|
||||
}
|
||||
|
||||
protected function photos(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function (mixed $value) {
|
||||
if (! is_array($photos = json_decode($value ?? '', true))) {
|
||||
$photos = [];
|
||||
}
|
||||
|
||||
return $photos;
|
||||
},
|
||||
set: fn (mixed $value) => json_encode(is_array($value) ? $value : []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('reimbursements', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('employee_id')->comment('报销人');
|
||||
$table->string('reimbursement_type_id')->comment('报销类型, keywords.reimbursement_type');
|
||||
$table->decimal('expense', 10, 2)->comment('报销费用');
|
||||
$table->string('reason')->nullable()->comment('报销原因');
|
||||
$table->text('photos')->nullable()->comment('照片');
|
||||
$table->tinyInteger('reimbursement_status')->default(1)->comment('状态: 1 待审核, 2 已通过, 3 已拒绝');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('reimbursement_type_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('reimbursements');
|
||||
}
|
||||
};
|
||||
|
|
@ -171,6 +171,13 @@ class AdminPermissionSeeder extends Seeder
|
|||
'index' => '佣金收入',
|
||||
],
|
||||
],
|
||||
'reimbursements' => [
|
||||
'name' => '收支报销',
|
||||
'icon' => 'ri:money-cny-circle-fill',
|
||||
'uri' => '/finance/reimbursements',
|
||||
'resource' => ['list', 'view'],
|
||||
'children' => [],
|
||||
],
|
||||
'sales_statistics' => [
|
||||
'name' => '销售统计',
|
||||
'icon' => 'ri:bar-chart-2-line',
|
||||
|
|
|
|||
|
|
@ -88,7 +88,12 @@ class KeywordSeeder extends Seeder
|
|||
'key' => 'holiday_type',
|
||||
'name' => '请假类型',
|
||||
'children' => ['病假', '事假'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'reimbursement_type',
|
||||
'name' => '报销类型',
|
||||
'children' => ['门店日常支出', '门店活动支出', '日常费用', '器材费', '差旅费', '车辆类报销', '会议费'],
|
||||
],
|
||||
];
|
||||
|
||||
$this->insertKeywors($keywords);
|
||||
|
|
|
|||
|
|
@ -40,4 +40,19 @@ return [
|
|||
'created_at' => '创建时间',
|
||||
'updated_at' => '更新时间',
|
||||
],
|
||||
|
||||
'reimbursement' => [
|
||||
'id' => 'ID',
|
||||
'employee' => '报销人',
|
||||
'type' => '类型',
|
||||
'expense' => '报销金额',
|
||||
'reason' => '报销原因',
|
||||
'photos' => '报销图片',
|
||||
'status' => '状态',
|
||||
'approval' => '审核',
|
||||
'approval_status' => '状态',
|
||||
'approval_result' => '审核结果',
|
||||
'failed_reason' => '驳回原因',
|
||||
'created_at' => '报销时间',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
Loading…
Reference in New Issue