generated from liutk/owl-admin-base
Merge branch 'main' of https://gitea.hmily.club/pdkj/store-manage into main
commit
d76f8e57f3
|
|
@ -8,7 +8,9 @@ use App\Admin\Services\BaseService;
|
|||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\{Validator, Storage};
|
||||
use App\Enums\ExamStatus;
|
||||
use App\Enums\MessageType;
|
||||
use App\Models\Employee;
|
||||
use App\Services\MessageService;
|
||||
|
||||
class ExaminationService extends BaseService
|
||||
{
|
||||
|
|
@ -71,6 +73,19 @@ class ExaminationService extends BaseService
|
|||
|
||||
$examination->update(['exam_status' => ExamStatus::Published, 'published_at' => now()]);
|
||||
|
||||
(new MessageService())->create(
|
||||
MessageType::Exam,
|
||||
'考试通知',
|
||||
'您有一张待完成试卷,请尽快完成考试。',
|
||||
$employees->all(),
|
||||
[
|
||||
'examination' => [
|
||||
'id' => $examination->id,
|
||||
'name' => $examination->name,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ Route::group([
|
|||
$router->get('stores', [StoreController::class, 'shareList']);
|
||||
$router->get('employees', [EmployeeController::class, 'shareList']);
|
||||
$router->get('employee-sign-logs', [SignLogController::class, 'shareList']);
|
||||
$router->get('keywords/tree-list', [KeywordController::class, 'getTreeList'])->name('api.keywords.tree-list');
|
||||
$router->get('keywords/tree-list', [KeywordController::class, 'getTreeList']);
|
||||
|
||||
$router->get('workflow/value-options', [WorkflowController::class, 'getValueOptions']);
|
||||
$router->post('workflow/apply', [WorkflowController::class, 'apply']);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum MessageType: string
|
||||
{
|
||||
case System = 'system'; // 系统消息
|
||||
case Exam = 'exam'; // 考试通知
|
||||
case Approval = 'approval'; // 审批通知
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Enums\MessageType;
|
||||
use EloquentFilter\ModelFilter;
|
||||
|
||||
class MessageFilter extends ModelFilter
|
||||
{
|
||||
public function filter($filter)
|
||||
{
|
||||
switch ($filter) {
|
||||
case 'system':
|
||||
$this->whereIn('type', [MessageType::System, MessageType::Exam]);
|
||||
break;
|
||||
|
||||
case 'work':
|
||||
$this->whereNotIn('type', [MessageType::System, MessageType::Exam]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,18 @@
|
|||
|
||||
namespace App\Http\Controllers\Api\Auth;
|
||||
|
||||
use App\Admin\Services\EmployeeService;
|
||||
use App\Enums\{UserRole, BusinessStatus};
|
||||
use App\Exceptions\RuntimeException;
|
||||
use App\Http\Controllers\Api\Controller;
|
||||
use App\Http\Resources\KeywordResource;
|
||||
use App\Http\Resources\StoreResource;
|
||||
use App\Models\Message;
|
||||
use App\Models\{Employee, Store, AdminUser};
|
||||
use Illuminate\Http\{Request, Response};
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Enums\{UserRole, BusinessStatus};
|
||||
use App\Http\Resources\KeywordResource;
|
||||
use App\Admin\Services\EmployeeService;
|
||||
use App\Http\Resources\StoreResource;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* 个人中心
|
||||
|
|
@ -23,7 +24,11 @@ class UserController extends Controller
|
|||
public function profile()
|
||||
{
|
||||
$user = $this->guard()->user();
|
||||
$admin = $user->adminUser;
|
||||
|
||||
$unreadMessageCount = Message::ofEmployee($user)
|
||||
->whereDoesntHave('readingLogs', fn ($query) => $query->where('employee_id', $user->id))
|
||||
->count();
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
|
|
@ -31,7 +36,7 @@ class UserController extends Controller
|
|||
'avatar' => $user->avatar,
|
||||
'jobs' => KeywordResource::collection($user->jobs),
|
||||
'store' => $user->store ? StoreResource::make($user->store) : null,
|
||||
'unread_notifications' => 0,
|
||||
'unread_notifications' => $unreadMessageCount,
|
||||
// 身份: user-普通员工, store-店长, admin-管理员
|
||||
'role' => $user->userRole(),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Filters\MessageFilter;
|
||||
use App\Http\Controllers\Api\Controller;
|
||||
use App\Http\Resources\MessageResource;
|
||||
use App\Models\Message;
|
||||
use App\Models\MessageReadingLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Throwable;
|
||||
|
||||
class MessageController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
/** @var \App\Models\Employee */
|
||||
$user = $request->user();
|
||||
|
||||
/** @var \Illuminate\Contracts\Pagination\Paginator */
|
||||
$paginator = Message::filter($request->input(), MessageFilter::class)
|
||||
->ofEmployee($user)
|
||||
->withCount([
|
||||
'readingLogs' => fn ($query) => $query->where('employee_id', $user->id),
|
||||
])
|
||||
->latest('id')
|
||||
->simplePaginate($request->query('per_page', 20));
|
||||
|
||||
return collect($paginator->items())->map(function (Message $message) {
|
||||
return array_merge(
|
||||
MessageResource::make($message)->resolve(),
|
||||
['is_read' => $message->reading_logs_count],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function show($id, Request $request): MessageResource
|
||||
{
|
||||
/** @var \App\Models\Employee */
|
||||
$user = $request->user();
|
||||
|
||||
$message = Message::ofEmployee($user)->findOrFail($id);
|
||||
|
||||
$message->readingLogs()->firstOrCreate([
|
||||
'employee_id' => $user->id,
|
||||
]);
|
||||
|
||||
return MessageResource::make($message);
|
||||
}
|
||||
|
||||
public function readAll(Request $request)
|
||||
{
|
||||
/** @var \App\Models\Employee */
|
||||
$user = $request->user();
|
||||
|
||||
$messages = Message::ofEmployee($user)
|
||||
->whereDoesntHave('readingLogs', fn ($query) => $query->where('employee_id', $user->id))
|
||||
->get();
|
||||
|
||||
try {
|
||||
$datetime = date('Y-m-d H:i:s');
|
||||
|
||||
MessageReadingLog::insert(
|
||||
$messages->map(fn (Message $message) => [
|
||||
'message_id' => $message->id,
|
||||
'employee_id' => $user->id,
|
||||
'created_at' => $datetime,
|
||||
'updated_at' => $datetime,
|
||||
])->all()
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class MessageResource extends JsonResource
|
||||
{
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->resource->id,
|
||||
'type' => $this->resource->type,
|
||||
'title' => $this->resource->title,
|
||||
'content' => $this->resource->content,
|
||||
'additional' => $this->resource->additional,
|
||||
'created_at' => $this->resource->created_at->timestamp,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\MessageType;
|
||||
use App\Traits\HasDateTimeFormatter;
|
||||
use EloquentFilter\Filterable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Message extends Model
|
||||
{
|
||||
use Filterable, HasDateTimeFormatter, HasFactory;
|
||||
|
||||
protected $attributes = [
|
||||
'employee_ids' => [],
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'type' => MessageType::class,
|
||||
'additional' => 'json',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'title',
|
||||
'content',
|
||||
'additional',
|
||||
'employee_ids',
|
||||
];
|
||||
|
||||
public function scopeOfEmployee(Builder $query, Employee $employee)
|
||||
{
|
||||
$query->whereJsonLength('employee_ids', 0)->orWhereJsonContains('employee_ids', $employee->id);
|
||||
}
|
||||
|
||||
public function readingLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(MessageReadingLog::class);
|
||||
}
|
||||
|
||||
protected function employeeIds(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: function (mixed $value) {
|
||||
if (! is_array($ids = json_decode($value ?? '', true))) {
|
||||
$ids = [];
|
||||
}
|
||||
|
||||
return $ids;
|
||||
},
|
||||
set: fn (mixed $value) => json_encode(is_array($value) ? $value : []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MessageReadingLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'message_id',
|
||||
'employee_id',
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\MessageType;
|
||||
use App\Models\Employee;
|
||||
use App\Models\Message;
|
||||
|
||||
class MessageService
|
||||
{
|
||||
/**
|
||||
* 创建消息通知
|
||||
*
|
||||
* @param array<int, \App\Models\Employee|int> $employees
|
||||
*/
|
||||
public function create(MessageType $type, ?string $title, ?string $content, array $employees = [], array $additional = [])
|
||||
{
|
||||
$employeeIds = collect($employees)->map(function ($employee) {
|
||||
if ($employee instanceof Employee) {
|
||||
return $employee->id;
|
||||
}
|
||||
return $employee;
|
||||
})->all();
|
||||
|
||||
$message = Message::create([
|
||||
'type' => $type,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'additional' => $additional,
|
||||
'employee_ids' => $employeeIds,
|
||||
]);
|
||||
|
||||
switch ($message->type) {
|
||||
// @todo 根据消息类型发送通知
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ return [
|
|||
],
|
||||
],
|
||||
'except' => [
|
||||
|
||||
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<?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('messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('type')->comment('类型');
|
||||
$table->string('title')->nullable()->comment('标题');
|
||||
$table->text('content')->nullable()->comment('内容');
|
||||
$table->text('additional')->nullable()->comment('附加数据');
|
||||
$table->json('employee_ids')->nullable()->comment('员工IDs');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('type');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('notifications');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?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('message_reading_logs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('message_id');
|
||||
$table->foreignId('employee_id');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['message_id', 'employee_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('message_reading_logs');
|
||||
}
|
||||
};
|
||||
|
|
@ -15,8 +15,8 @@ class AdminPermissionSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
AdminMenu::truncate();
|
||||
AdminPermission::truncate();
|
||||
// AdminMenu::truncate();
|
||||
// AdminPermission::truncate();
|
||||
$data = [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use App\Http\Controllers\Api\FeedbackController;
|
|||
use App\Http\Controllers\Api\FileUploadController;
|
||||
use App\Http\Controllers\Api\KeywordController;
|
||||
use App\Http\Controllers\Api\LedgerController;
|
||||
use App\Http\Controllers\Api\MessageController;
|
||||
use App\Http\Controllers\Api\RegionController;
|
||||
use App\Http\Controllers\Api\ReimbursementController;
|
||||
use App\Http\Controllers\Api\StatisticsController;
|
||||
|
|
@ -46,6 +47,11 @@ Route::group([
|
|||
// 个人账户 - 门店业绩指标任务
|
||||
Route::get('/account/store-performance-tasks', [TaskPerformanceController::class, 'index']);
|
||||
|
||||
// 消息通知
|
||||
Route::get('/message/messages', [MessageController::class, 'index']);
|
||||
Route::get('/message/messages/{message}', [MessageController::class, 'show']);
|
||||
Route::post('/message/read-all', [MessageController::class, 'readAll']);
|
||||
|
||||
// 统计数据 - 首页统计
|
||||
Route::get('/statistics/dashboard', [StatisticsController::class, 'dashboard']);
|
||||
// 统计数据 - 门店统计
|
||||
|
|
|
|||
Loading…
Reference in New Issue