投诉意见

main
Jing Li 2024-04-02 22:40:16 +08:00
parent 5af0202f13
commit 90358ef4e7
18 changed files with 678 additions and 3 deletions

View File

@ -0,0 +1,164 @@
<?php
namespace App\Admin\Controllers\Complaint;
use App\Admin\Controllers\AdminController;
use App\Admin\Services\Complaint\ComplaintService;
use App\Enums\ComplaintStatus;
use App\Models\Complaint;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Slowlyo\OwlAdmin\Admin;
use Slowlyo\OwlAdmin\Renderers\AjaxAction;
use Slowlyo\OwlAdmin\Renderers\DrawerAction;
use Slowlyo\OwlAdmin\Renderers\Page;
/**
* @property ComplaintService $service
*/
class ComplaintController extends AdminController
{
protected string $serviceName = ComplaintService::class;
public function list(): Page
{
$crud = $this->baseCRUD()
->headerToolbar([
...$this->baseHeaderToolBar(),
])
->bulkActions([])
->filter($this->baseFilter()->body([
amis()->GroupControl()->mode('horizontal')->body([
amis()->TextControl()
->name('employee_name')
->label(__('complaint.complaint.employee'))
->placeholder(__('complaint.complaint.employee')),
amis()->InputDatetimeRange()
->name('created_at')
->label(__('complaint.complaint.created_at'))
->format('YYYY-MM-DD HH:mm:ss'),
amis()->SelectControl('complaint_status', __('complaint.complaint.complaint_status'))
->multiple()
->options(ComplaintStatus::options()),
]),
]))
->columns([
amis()->TableColumn()->name('id')->label(__('complaint.complaint.id')),
amis()->TableColumn()
->name('_employee')
->label(__('complaint.complaint.employee'))
->value('${anonymous ? "匿名" : employee.name}'),
amis()->TableColumn()
->name('content')
->label(__('complaint.complaint.content'))
->popOver('${content}')
->type('tpl')
->set('tpl', '${content|truncate:50}'),
amis()->TableColumn()
->name('result')
->label(__('complaint.complaint.result'))
->popOver('${result}')
->type('tpl')
->set('tpl', '${result|truncate:50}'),
amis()->TableColumn()
->name('complaint_status')
->label(__('complaint.complaint.complaint_status'))
->type('mapping')
->map(ComplaintStatus::labelMap()),
amis()->TableColumn()->name('created_at')->label(__('complaint.complaint.created_at')),
$this->rowActions([
$this->rowProcessStartButton()
->visible(Admin::user()->can('complaint.complaints.start'))
->visibleOn('${complaint_status === '.ComplaintStatus::Pending->value.'}'),
$this->rowProcessCompleteButton()
->visible(Admin::user()->can('complaint.complaints.complete'))
->visibleOn('${complaint_status === '.ComplaintStatus::Processing->value.'}'),
]),
]);
return $this->baseList($crud);
}
/**
* 处理开始
*/
public function start($id)
{
/** @var Complaint */
$complaint = Complaint::findOrFail($id);
if (! $complaint->isPending()) {
admin_abort('举报投诉记录的状态不是待审核');
}
$complaint->update([
'complaint_status' => ComplaintStatus::Processing->value,
]);
return $this->response()->successMessage('操作成功');
}
/**
* 处理结束
*/
public function complete($id, Request $request)
{
$validator = Validator::make(
data: $request->input(),
rules: [
'result' => ['bail', 'required', 'string'],
],
attributes: [
'result' => __('complaint.complaint.result'),
],
);
if ($validator->fails()) {
admin_abort($validator->errors()->first());
}
/** @var Complaint */
$complaint = Complaint::findOrFail($id);
if (! $complaint->isProcessing()) {
admin_abort('举报投诉记录的状态不是处理中');
}
$complaint->update([
'result' => $request->input('result'),
'complaint_status' => ComplaintStatus::Processed->value,
]);
return $this->response()->successMessage('操作成功');
}
/**
* 处理开始按钮
*/
protected function rowProcessStartButton(): AjaxAction
{
return amis()->AjaxAction()
->icon('fa fa-play-circle-o')
->label(__('complaint.complaint.start'))
->level('link')
->api('post:'.admin_url('/complaint/complaints/$id/start'))
->confirmText('是否开始处理选中的举报投诉记录')
->confirmTitle('系统消息');
}
/**
* 处理结束按钮
*/
protected function rowProcessCompleteButton(): DrawerAction
{
return amis()->DrawerAction()->icon('fa fa-stop-circle-o')->label(__('complaint.complaint.complete'))->level('link')->drawer(
amis()->Drawer()->title(__('complaint.complaint.result'))->body([
amis()->Form()->title('')
->api('post:'.admin_url('/complaint/complaints/$id/complete'))
->body([
amis()->TextareaControl('result', __('complaint.complaint.result'))->required()->minRows(15),
]),
])->size('lg')
);
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Admin\Controllers\Complaint;
use App\Admin\Controllers\AdminController;
use App\Admin\Services\Complaint\FeedbackService;
use Slowlyo\OwlAdmin\Admin;
use Slowlyo\OwlAdmin\Renderers\Page;
/**
* @property FeedbackService $service
*/
class FeedbackController extends AdminController
{
protected string $serviceName = FeedbackService::class;
public function list(): Page
{
$crud = $this->baseCRUD()
->headerToolbar([
...$this->baseHeaderToolBar(),
])
->bulkActions([])
->filter($this->baseFilter()->body([
amis()->GroupControl()->mode('horizontal')->body([
amis()->TextControl()
->name('employee_name')
->label(__('complaint.feedback.employee'))
->placeholder(__('complaint.feedback.employee'))
->columnRatio(4),
amis()->InputDatetimeRange()
->name('created_at')
->label(__('complaint.feedback.created_at'))
->format('YYYY-MM-DD HH:mm:ss')
->columnRatio(4),
]),
]))
->columns([
amis()->TableColumn()->name('id')->label(__('complaint.feedback.id')),
amis()->TableColumn()->name('employee.name')->label(__('complaint.feedback.employee')),
amis()->TableColumn()->name('content')->label(__('complaint.feedback.content')),
amis()->TableColumn()->name('created_at')->label(__('complaint.feedback.created_at')),
$this->rowActions([
$this->rowDeleteButton()->visible(Admin::user()->can('admin.complaint.feedback.delete')),
]),
]);
return $this->baseList($crud);
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Admin\Filters;
use EloquentFilter\ModelFilter;
class ComplaintFilter extends ModelFilter
{
public function employeeName($name)
{
$this->where('anonymous', false)
->whereRelation('employee', 'name', 'like', "%{$name}%");
}
public function complaintStatus($status)
{
$this->whereIn('complaint_status', explode(',', $status));
}
public function createdAt($createdAt)
{
$this->whereBetween('created_at', explode(',', $createdAt));
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Admin\Filters;
use EloquentFilter\ModelFilter;
class FeedbackFilter extends ModelFilter
{
public function employeeName($name)
{
$this->related('employee', 'name', 'like', "%{$name}%");
}
public function createdAt($createdAt)
{
$this->whereBetween('created_at', explode(',', $createdAt));
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Admin\Services\Complaint;
use App\Admin\Filters\ComplaintFilter;
use App\Admin\Services\BaseService;
use App\Models\Complaint;
class ComplaintService extends BaseService
{
protected string $modelName = Complaint::class;
protected string $modelFilterName = ComplaintFilter::class;
protected array $withRelationships = ['employee'];
public function sortColumn()
{
return 'id';
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Admin\Services\Complaint;
use App\Admin\Filters\FeedbackFilter;
use App\Admin\Services\BaseService;
use App\Models\Feedback;
class FeedbackService extends BaseService
{
protected string $modelName = Feedback::class;
protected string $modelFilterName = FeedbackFilter::class;
protected array $withRelationships = ['employee'];
public function sortColumn()
{
return 'id';
}
}

View File

@ -1,6 +1,8 @@
<?php
use App\Admin\Controllers\BaseKeywordController;
use App\Admin\Controllers\Complaint\ComplaintController;
use App\Admin\Controllers\Complaint\FeedbackController;
use App\Admin\Controllers\Finance\CommissionIncomeController;
use App\Admin\Controllers\Finance\LedgerController;
use App\Admin\Controllers\Finance\ReimbursementController;
@ -8,13 +10,13 @@ use App\Admin\Controllers\Finance\SalesStatisticController;
use App\Admin\Controllers\Finance\StoreMasterCommissionController;
use App\Admin\Controllers\Finance\StoreStatisticController;
use App\Admin\Controllers\Hr\EmployeeController;
use App\Admin\Controllers\Hr\HolidayController;
use App\Admin\Controllers\Hr\OfficalBusinessController;
use App\Admin\Controllers\Hr\OvertimeController;
use App\Admin\Controllers\Hr\RestController;
use App\Admin\Controllers\Hr\SignController;
use App\Admin\Controllers\Hr\SignLogController;
use App\Admin\Controllers\Hr\SignRepairController;
use App\Admin\Controllers\Hr\HolidayController;
use App\Admin\Controllers\Hr\OvertimeController;
use App\Admin\Controllers\Hr\OfficalBusinessController;
use App\Admin\Controllers\Store\DeviceController;
use App\Admin\Controllers\Store\EmployeeController as StoreEmployeeController;
use App\Admin\Controllers\Store\StoreController;
@ -93,6 +95,23 @@ Route::group([
$router->resource('business', OfficalBusinessController::class);
});
/*
|--------------------------------------------------------------------------
| 投诉意见
|--------------------------------------------------------------------------
*/
$router->group([
'prefix' => 'complaint',
'as' => 'complaint.',
], function (Router $router) {
// 举报投诉
$router->resource('complaints', ComplaintController::class)->only(['index']);
$router->post('complaints/{complaint}/start', [ComplaintController::class, 'start'])->name('complaints.start');
$router->post('complaints/{complaint}/complete', [ComplaintController::class, 'complete'])->name('complaints.complete');
// 意见箱
$router->resource('feedback', FeedbackController::class)->only(['index', 'destroy']);
});
/*
|--------------------------------------------------------------------------
| 财务管理

View File

@ -0,0 +1,33 @@
<?php
namespace App\Enums;
enum ComplaintStatus: int
{
case Pending = 1;
case Processing = 2;
case Processed = 3;
public function text(): string
{
return self::options()[$this->value];
}
public static function options(): array
{
return [
self::Pending->value => '待处理',
self::Processing->value => '处理中',
self::Processed->value => '已处理',
];
}
public static function labelMap(): array
{
return [
self::Pending->value => '<span class="label label-primary">'.self::Pending->text().'</span>',
self::Processing->value => '<span class="label label-warning">'.self::Processing->text().'</span>',
self::Processed->value => '<span class="label label-success">'.self::Processed->text().'</span>',
];
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Models;
use App\Enums\ComplaintStatus;
use App\Traits\HasDateTimeFormatter;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Complaint extends Model
{
use Filterable, HasFactory, HasDateTimeFormatter;
protected $attributes = [
'anonymous' => false,
'complaint_status' => ComplaintStatus::Pending,
];
protected $casts = [
'anonymous' => 'bool',
'complaint_status' => ComplaintStatus::class,
];
protected $fillable = [
'employee_id', 'content', 'result', 'anonymous', 'complaint_status',
];
public function employee(): BelongsTo
{
return $this->belongsTo(Employee::class);
}
public function isPending(): bool
{
return $this->complaint_status === ComplaintStatus::Pending;
}
public function isProcessing(): bool
{
return $this->complaint_status === ComplaintStatus::Processing;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use App\Traits\HasDateTimeFormatter;
use EloquentFilter\Filterable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Feedback extends Model
{
use Filterable, HasFactory, HasDateTimeFormatter;
protected $fillable = [
'employee_id', 'content',
];
public function employee(): BelongsTo
{
return $this->belongsTo(Employee::class);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Database\Factories;
use App\Enums\ComplaintStatus;
use App\Models\Employee;
use App\Models\Complaint;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Arr;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Complaint>
*/
class ComplaintFactory extends Factory
{
protected $model = Complaint::class;
protected static array $employees = [];
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'content' => fake()->paragraph(),
'result' => null,
'anonymous' => false,
'complaint_status' => ComplaintStatus::Pending,
];
}
/**
* 已处理的投诉
*/
public function processed(): static
{
return $this->state(fn (array $attributes) => [
'result' => fake()->paragraph(),
'complaint_status' => ComplaintStatus::Processed,
]);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Database\Factories;
use App\Models\Employee;
use App\Models\Feedback;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Arr;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Feedback>
*/
class FeedbackFactory extends Factory
{
protected $model = Feedback::class;
protected static array $employees = [];
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'content' => fake()->paragraph(),
];
}
}

View File

@ -0,0 +1,32 @@
<?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('complaints', function (Blueprint $table) {
$table->id();
$table->foreignId('employee_id');
$table->text('content')->nullable()->comment('投诉内容');
$table->text('result')->nullable()->comment('处理结果');
$table->boolean('anonymous')->default(false)->comment('是否匿名');
$table->tinyInteger('complaint_status')->default(1)->comment('1: 未处理, 2 处理中, 3 已处理');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('complaints');
}
};

View File

@ -0,0 +1,29 @@
<?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('feedback', function (Blueprint $table) {
$table->id();
$table->foreignId('employee_id');
$table->text('content')->nullable()->comment('反馈内容');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('feedback');
}
};

View File

@ -149,6 +149,36 @@ class AdminPermissionSeeder extends Seeder
],
],
/*
|--------------------------------------------------------------------------
| 投诉意见
|--------------------------------------------------------------------------
*/
'complaint' => [
'name' => '投诉意见',
'icon' => 'mdi:star-four-points-box-outline',
'uri' => '/complaint',
'children' => [
'complaints' => [
'name' => '举报投诉',
'icon' => 'pixelarticons:list-box',
'uri' => '/complaint/complaints',
'resource' => ['list'],
'children' => [
'start' => '开始',
'complete' => '完成',
],
],
'feedback' => [
'name' => '意见箱',
'icon' => 'tabler:box',
'uri' => '/complaint/feedback',
'resource' => ['list', 'delete'],
'children' => [],
],
],
],
/*
|--------------------------------------------------------------------------
| 财务报表

View File

@ -0,0 +1,38 @@
<?php
namespace Database\Seeders;
use App\Enums\ComplaintStatus;
use App\Models\Complaint;
use App\Models\Employee;
use Illuminate\Database\Seeder;
class ComplaintSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$timestamp = now();
/** @var \Illuminate\Database\Eloquent\Collection */
$employees = Employee::pluck('id');
Complaint::insert(
collect([])
->merge(Complaint::factory()->count(5)->make())
->merge(Complaint::factory()->count(5)->state(['anonymous' => true])->make())
->merge(Complaint::factory()->count(5)->state(['complaint_status' => ComplaintStatus::Processing])->make())
->merge(Complaint::factory()->count(5)->processed()->make())
->map(function (Complaint $instance) use ($timestamp, $employees) {
return array_merge($instance->toArray(), [
'employee_id' => $employees->random(),
'created_at' => $timestamp,
'updated_at' => $timestamp,
]);
})
->all()
);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Database\Seeders;
use App\Models\Employee;
use App\Models\Feedback;
use Illuminate\Database\Seeder;
class FeedbackSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$timestamp = now();
/** @var \Illuminate\Database\Eloquent\Collection */
$employees = Employee::pluck('id');
Feedback::insert(
Feedback::factory()->count(10)->make()
->map(function (Feedback $instance) use ($timestamp, $employees) {
return array_merge($instance->toArray(), [
'employee_id' => $employees->random(),
'created_at' => $timestamp,
'updated_at' => $timestamp,
]);
})
->all()
);
}
}

View File

@ -0,0 +1,21 @@
<?php
return [
'complaint' => [
'id' => 'ID',
'employee' => '投诉人',
'content' => '投诉内容',
'result' => '处理结果',
'complaint_status' => '状态',
'created_at' => '投诉时间',
'start' => '开始',
'complete' => '完成',
],
'feedback' => [
'id' => 'ID',
'employee' => '反馈人',
'content' => '反馈内容',
'created_at' => '反馈时间',
],
];