diff --git a/app/Events/Applications/ApplicationStatusChanged.php b/app/Events/Applications/ApplicationStatusChanged.php new file mode 100644 index 0000000..ebc34c6 --- /dev/null +++ b/app/Events/Applications/ApplicationStatusChanged.php @@ -0,0 +1,25 @@ +application = $application; + $this->status = $status ?? $application->status; + } + + public function broadcastOn(): PrivateChannel { + return new PrivateChannel('channel-name'); + } +} diff --git a/app/Http/Controllers/Api/Applications/ApplicationsController.php b/app/Http/Controllers/Api/Applications/ApplicationsController.php new file mode 100644 index 0000000..66ebba3 --- /dev/null +++ b/app/Http/Controllers/Api/Applications/ApplicationsController.php @@ -0,0 +1,48 @@ +model = $model; + } + + public function index(Request $request): JsonResponse { + $filters = collect($request->has('filters') ? json_decode($request->get('filters'), true) : [])->filter(function($val) {return $val;}); + $query = $this->model->query(); + $service = FiltersService::getService('applications'); + $service->applyFilters($query, $filters); + $query->orderBy('id', 'desc'); + $paginator = $query->paginate(config('app.pagination_limit')); + return fractal($paginator, new ApplicationTransformer())->respond(); + } + + public function show(Request $request, $id): JsonResponse { + $query = $this->model->query(); + $model = $query->byUuid($id)->firstOrFail(); + return fractal($model, new ApplicationTransformer())->respond(); + } + + public function store(Request $request) { + } + + public function update(Request $request, $uuid) { + } + + public function destroy(Request $request, $uuid): JsonResponse { + $model = $this->model->byUuid($uuid)->firstOrFail(); + $model->delete(); + return response()->json(null, 204); + } + + +} diff --git a/app/Http/Controllers/Api/Companies/MembersController.php b/app/Http/Controllers/Api/Companies/MembersController.php index a84afa4..819587e 100644 --- a/app/Http/Controllers/Api/Companies/MembersController.php +++ b/app/Http/Controllers/Api/Companies/MembersController.php @@ -24,7 +24,7 @@ class MembersController extends Controller { $query = $this->model->query(); $service = FiltersService::getService('companyMembers'); $service->applyFilters($query, $filters); - $paginator = $query->paginate(2); + $paginator = $query->paginate(config('app.pagination_limit')); return fractal($paginator, new CompanyMemberTransformer())->respond(); } diff --git a/app/Http/Controllers/Api/Forms/FormsController.php b/app/Http/Controllers/Api/Forms/FormsController.php index 5806ef5..1865dfe 100644 --- a/app/Http/Controllers/Api/Forms/FormsController.php +++ b/app/Http/Controllers/Api/Forms/FormsController.php @@ -10,12 +10,16 @@ use App\Services\Forms\FormsService; use App\Transformers\Objects\ObjectTransformer; use App\Transformers\Objects\ObjectTypeTransformer; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class FormsController extends Controller { public function get(Request $request, $target, $type = null, $id = null) { if ($target === 'model') { - return FormsService::getService($type)->form($id, json_decode($request->get('extra_props'), true) ?? []); + $service = FormsService::getService($type); + if ($request->user() || $service->unprotected) return $service->form($id, json_decode($request->get('extra_props'), true) ?? []); + else return response()->json(['error' => 'Unauthenticated.'], 401); + //return FormsService::getService($type)->form($id, json_decode($request->get('extra_props'), true) ?? []); } elseif ($object = NirObject::byUuid($id)->first()) { return fractal($object, new ObjectTransformer())->respond(); } elseif ($objectType = ObjectType::byUuidOrName($type)->first()) { @@ -43,6 +47,14 @@ class FormsController extends Controller { } } + public function getFeedbackFormSupport(Request $request): ?JsonResponse { + return ($objectType = ObjectType::byName('feedback-form-support')->first()) ? fractal($objectType, new ObjectTypeTransformer())->respond() : null; + } + public function saveFeedbackFormSupport(Request $request): ?JsonResponse { + return FormsService::getService('feedback-form-support')->save($request->all()); + } + + public function filters(Request $request, $type) { $filters = collect(json_decode($request->get('filters', []), true)); diff --git a/app/Http/Controllers/Api/Objects/ObjectsController.php b/app/Http/Controllers/Api/Objects/ObjectsController.php index 14672f0..255e126 100644 --- a/app/Http/Controllers/Api/Objects/ObjectsController.php +++ b/app/Http/Controllers/Api/Objects/ObjectsController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Objects\Field; use App\Models\Objects\NirObject; use App\Models\Objects\ObjectType; +use App\Services\Filters\FiltersService; use App\Transformers\Objects\ObjectTransformer; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Http\JsonResponse; @@ -19,7 +20,11 @@ class ObjectsController extends Controller { } public function index(Request $request): JsonResponse { + $filters = collect($request->has('filters') ? json_decode($request->get('filters'), true) : [])->filter(function($val) {return $val;}); $query = $this->model->query(); + $service = FiltersService::getService('objects'); + $service->applyFilters($query, $filters); + /* if (($val = $request->get('type')) && ($type = ObjectType::query()->where(['name' => $val])->first())) { $query->whereHas('type', function ($query) use ($val) { $query->where('name', $val)->orWhereHas('parent', function ($query) use ($val) { @@ -36,6 +41,7 @@ class ObjectsController extends Controller { elseif ($field = Field::byUuidOrName($prop)->first()) $field->applyFilter($query, $value); }); $query->orderBy('created_at', 'desc'); + */ $paginator = $query->paginate(config('app.pagination_limit')); return fractal($paginator, new ObjectTransformer())->respond(); } diff --git a/app/Http/Controllers/Api/Pages/PagesController.php b/app/Http/Controllers/Api/Pages/PagesController.php index 533998b..b21b466 100644 --- a/app/Http/Controllers/Api/Pages/PagesController.php +++ b/app/Http/Controllers/Api/Pages/PagesController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api\Pages; use App\Http\Controllers\Controller; use App\Models\Pages\Page; use App\Models\Publications\Publication; +use App\Services\Filters\FiltersService; use App\Transformers\Pages\PageTransformer; use App\Transformers\Publications\PublicationTransformer; use Illuminate\Http\JsonResponse; @@ -31,7 +32,10 @@ class PagesController extends Controller { } public function index(Request $request): JsonResponse { + $filters = collect($request->has('filters') ? json_decode($request->get('filters'), true) : [])->filter(function($val) {return $val;}); $query = $this->model->query(); + $service = FiltersService::getService('pages'); + $service->applyFilters($query, $filters); $paginator = $query->paginate(config('app.pagination_limit')); return fractal($paginator, new PageTransformer())->respond(); } diff --git a/app/Http/Controllers/Api/Publications/PublicationsController.php b/app/Http/Controllers/Api/Publications/PublicationsController.php index 510f906..27afe7a 100644 --- a/app/Http/Controllers/Api/Publications/PublicationsController.php +++ b/app/Http/Controllers/Api/Publications/PublicationsController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Pages\Page; use App\Models\Publications\Publication; use App\Models\Publications\PublicationType; +use App\Services\Filters\FiltersService; use App\Transformers\Publications\PublicationTransformer; use App\Transformers\Registries\RegistryTransformer; use Illuminate\Http\JsonResponse; @@ -26,18 +27,10 @@ class PublicationsController extends Controller { } public function index(Request $request): JsonResponse { + $filters = collect($request->has('filters') ? json_decode($request->get('filters'), true) : [])->filter(function($val) {return $val;}); $query = $this->model->query()->orderBy('published_at', 'desc'); - $user = Auth::user(); - if (!($user->isAdmin ?? null)) { - $query->where(['is_published' => true]); - $query->where('published_at', '<=', now()); - } - - if ($page = Page::byUuid($request->get('page_id'))->first()) { - $query->where(['page_id' => $page->id]); - } elseif ($page = Page::query()->where(['sub_type' => $request->get('sub_type')])->first()) { - $query->where('page_id', $page->id); - } + $service = FiltersService::getService('publications'); + $service->applyFilters($query, $filters); $paginator = $query->paginate(config('app.pagination_limit')); return fractal($paginator, new PublicationTransformer())->respond(); } diff --git a/app/Http/Controllers/Api/Users/UsersController.php b/app/Http/Controllers/Api/Users/UsersController.php index e15c821..95cd827 100644 --- a/app/Http/Controllers/Api/Users/UsersController.php +++ b/app/Http/Controllers/Api/Users/UsersController.php @@ -5,82 +5,71 @@ namespace App\Http\Controllers\Api\Users; use App\Http\Controllers\Controller; use App\Models\User; use App\Transformers\Users\UserTransformer; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -class UsersController extends Controller -{ - protected $model; +class UsersController extends Controller { + protected User $model; - public function __construct(User $model) - { + public function __construct(User $model) { $this->model = $model; + /* $this->middleware('permission:List users')->only('index'); $this->middleware('permission:List users')->only('show'); $this->middleware('permission:Create users')->only('store'); $this->middleware('permission:Update users')->only('update'); $this->middleware('permission:Delete users')->only('destroy'); + */ } - public function index(Request $request) - { - $paginator = $this->model->with('roles.permissions')->paginate($request->get('limit', config('app.pagination_limit', 20))); - if ($request->has('limit')) { - $paginator->appends('limit', $request->get('limit')); - } + public function checkEmail(Request $request): JsonResponse { + $this->validate($request, ['email' => 'required|email|unique:users,email']); + return response()->json(null); + } + + public function index(Request $request): JsonResponse { + $paginator = $this->model->with('roles.permissions')->paginate($request->get('limit', config('app.pagination_limit', 20))); + //if ($request->has('limit')) $paginator->appends('limit', $request->get('limit')); return fractal($paginator, new UserTransformer())->respond(); } - public function show($id) - { + public function show($id): JsonResponse { $user = $this->model->with('roles.permissions')->byUuid($id)->firstOrFail(); - return fractal($user, new UserTransformer())->respond(); } - public function store(Request $request) - { + public function store(Request $request): JsonResponse { $this->validate($request, [ 'name' => 'required', 'email' => 'required|email|unique:users,email', 'password' => 'required|min:8|confirmed', ]); $user = $this->model->create($request->all()); - if ($request->has('roles')) { - $user->syncRoles($request['roles']); - } - + $user->assignRole('User'); + //if ($request->has('roles')) $user->syncRoles($request['roles']); return fractal($user, new UserTransformer())->respond(201); } - public function update(Request $request, $uuid) - { + public function update(Request $request, $uuid): JsonResponse { $user = $this->model->byUuid($uuid)->firstOrFail(); $rules = [ 'name' => 'required', 'email' => 'required|email|unique:users,email,'.$user->id, ]; - if ($request->method() == 'PATCH') { - $rules = [ - 'name' => 'sometimes|required', - 'email' => 'sometimes|required|email|unique:users,email,'.$user->id, - ]; - } + if ($request->method() == 'PATCH') $rules = [ + 'name' => 'sometimes|required', + 'email' => 'sometimes|required|email|unique:users,email,'.$user->id, + ]; $this->validate($request, $rules); - // Except password as we don't want to let the users change a password from this endpoint $user->update($request->except('_token', 'password')); - if ($request->has('roles')) { - $user->syncRoles($request['roles']); - } - + //if ($request->has('roles')) $user->syncRoles($request['roles']); return fractal($user->fresh(), new UserTransformer())->respond(); } - public function destroy(Request $request, $uuid) - { + public function destroy(Request $request, $uuid): JsonResponse { $user = $this->model->byUuid($uuid)->firstOrFail(); $user->delete(); - return response()->json(null, 204); } } diff --git a/app/Listeners/Applications/SendApplicationStatusChangedNotifications.php b/app/Listeners/Applications/SendApplicationStatusChangedNotifications.php new file mode 100644 index 0000000..17af9b4 --- /dev/null +++ b/app/Listeners/Applications/SendApplicationStatusChangedNotifications.php @@ -0,0 +1,44 @@ + [ApplicationStatus::COMPLETED], + 'addresses' => [ApplicationStatus::PROCESSING] + ]; + + public function __construct() { + } + + public function handle(ApplicationStatusChanged $event) { + if (in_array($event->status, $this->notifiableStatuses['addresses'])) $this->notifyAddressees($event->application, $event->status); + if (in_array($event->status, $this->notifiableStatuses['applicant'])) $this->notifyApplicant($event->application, $event->status); + } + + public function notifyAddressees(Application $application, $status) { + $application->addressees->each(function(CompanyMember $member) use($application, $status) { + $this->notify($member->user, $application, $status); + }); + } + + public function notifyApplicant(Application $application, $status) { + $this->notify($application->submitter, $application, $status); + } + + public function notify(User $recipient, Application $application, $status) { + try { + Mail::to($recipient->email)->send(new NotifyApplicationStatusChanged($application, $recipient, $status)); + } catch (\Exception $exception) {} + } + + +} diff --git a/app/Mail/Applications/NotifyApplicationStatusChanged.php b/app/Mail/Applications/NotifyApplicationStatusChanged.php new file mode 100644 index 0000000..35e2537 --- /dev/null +++ b/app/Mail/Applications/NotifyApplicationStatusChanged.php @@ -0,0 +1,29 @@ +application = $application; + $this->recipient = $recipient; + $this->status = $status; + } + + public function build(): NotifyApplicationStatusChanged { + $subject = $this->application->title; + return $this->subject($subject)->view("mail.applications.status-changed"); + } +} diff --git a/app/Models/Applications/Application.php b/app/Models/Applications/Application.php new file mode 100644 index 0000000..da20988 --- /dev/null +++ b/app/Models/Applications/Application.php @@ -0,0 +1,96 @@ +belongsTo(User::class); + } + + public function product(): BelongsTo { + return $this->belongsTo(Product::class); + } + + public function conclusions(): HasMany { + return $this->hasMany(Conclusion::class); + } + + + + public function getTitleAttribute(): string { + return "Заявка №{$this->number} от " . $this->created_at->format('d.m.Y'); + } + + public function getParsedStatusAttribute(): array { + return ['name' => $this->status, 'title' => ApplicationStatus::TITLES[$this->status] ?? null]; + } + + public function getPropertiesAttribute(): ?Model { + return $this->getObject('application-properties', 'properties'); + } + + public function getAddresseesAttribute() { + return CompanyMember::query()->mainCompany()->whereHas('objects', function($query) { + Field::applyFilters($query, collect(['types' => 'company-member-properties', 'moderate-permissions' => 'applications'])); + })->get(); + } + + + public function submit(): bool { + $res = $this->update(['status' => ApplicationStatus::PROCESSING]); + event(new ApplicationStatusChanged($this)); + return $res; + } + public function complete(): bool { + $res = $this->update(['status' => ApplicationStatus::COMPLETED]); + event(new ApplicationStatusChanged($this)); + return $res; + } + + + public function setNumber() { + if (!$this->number) $this->update(['number' => self::generateNumber()]); + } + + public static function generateNumber($start = 100, $digits = 5): string { + $res = intval(static::query()->max('number') ?? $start) + 1; + while (strlen($res) < $digits) { + $res = "0{$res}"; + } + return trim($res); + } + + +} diff --git a/app/Models/Applications/ApplicationStatus.php b/app/Models/Applications/ApplicationStatus.php new file mode 100644 index 0000000..189a82f --- /dev/null +++ b/app/Models/Applications/ApplicationStatus.php @@ -0,0 +1,17 @@ + 'Направлено', + self::PROCESSING => 'В работе', + self::COMPLETED => 'Выполнено', + self::CANCELED =>'Отозвана заявителем' + ]; +} \ No newline at end of file diff --git a/app/Models/Applications/Conclusion.php b/app/Models/Applications/Conclusion.php new file mode 100644 index 0000000..b99dadd --- /dev/null +++ b/app/Models/Applications/Conclusion.php @@ -0,0 +1,39 @@ +belongsTo(Application::class); + } + + public function author(): BelongsTo { + return $this->belongsTo(User::class); + } + + +} diff --git a/app/Models/Companies/CompanyMember.php b/app/Models/Companies/CompanyMember.php index cd76a5f..b78d956 100644 --- a/app/Models/Companies/CompanyMember.php +++ b/app/Models/Companies/CompanyMember.php @@ -4,6 +4,7 @@ namespace App\Models\Companies; use App\Models\Advisories\AdvisoryMember; use App\Models\User; +use App\Support\HasObjectsTrait; use App\Support\RelationValuesTrait; use App\Support\UuidScopeTrait; use Illuminate\Database\Eloquent\Model; @@ -13,7 +14,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Auth; class CompanyMember extends Model { - use UuidScopeTrait, SoftDeletes, RelationValuesTrait; + use UuidScopeTrait, SoftDeletes, HasObjectsTrait, RelationValuesTrait; protected $dates = [ 'deleted_at', @@ -55,6 +56,17 @@ class CompanyMember extends Model { } + public function scopeMainCompany($query) { + return $query->whereHas('company', function($query) { + $query->where(['is_main' => 1]); + }); + } + + + + public function getPropertiesAttribute(): ?Model { + return $this->getObject('company-member-properties'); + } public function getIsMeAttribute(): ?bool { return ($user = Auth::user()) ? ($user->id === $this->user_id) : null; diff --git a/app/Models/Objects/Field.php b/app/Models/Objects/Field.php index 56f1186..848c946 100644 --- a/app/Models/Objects/Field.php +++ b/app/Models/Objects/Field.php @@ -144,13 +144,14 @@ class Field extends Model { public static function applyFilters(Builder $query, Collection $filters, ?FiltersService $service = null) { if ($types = $filters->get('types')) { + $types = is_array($types) ? $types : [$types]; $filters->forget('types'); $query->whereHas('type', function($query) use($types) { - $query->whereIn('uuid', is_array($types) ? $types : [$types]); + $query->whereIn('uuid', $types)->orWhereIn('name', $types); }); $filters->filter(function($value) {return $value;})->each(function($value, $prop) use($query, $types, $service) { $field = Field::query()->where(['name' => $prop])->whereHas('groups.objectType', function($query) use($types) { - $query->whereIn('uuid', is_array($types) ? $types : [$types]); + $query->whereIn('uuid', $types)->orWhereIn('name', $types); })->first(); if ($field) $field->applyFilter($query, $value); }); diff --git a/app/Models/Objects/NirObject.php b/app/Models/Objects/NirObject.php index 9c443fd..288d8be 100644 --- a/app/Models/Objects/NirObject.php +++ b/app/Models/Objects/NirObject.php @@ -6,11 +6,13 @@ use App\Models\Objects\Values\BooleanValue; use App\Models\Objects\Values\DateValue; use App\Models\Objects\Values\DocumentValue; use App\Models\Objects\Values\FloatValue; +use App\Models\Objects\Values\HtmlValue; use App\Models\Objects\Values\ImageValue; use App\Models\Objects\Values\IntegerValue; use App\Models\Objects\Values\RelationValue; use App\Models\Objects\Values\StringValue; use App\Models\Objects\Values\TextValue; +use App\Models\Pages\Page; use App\Models\Registries\Entry; use App\Models\User; use App\Support\UuidScopeTrait; @@ -46,6 +48,10 @@ class NirObject extends Model { return $this->morphedByMany(Entry::class, 'objectable'); } + public function pages(): MorphToMany { + return $this->morphedByMany(Page::class, 'objectable'); + } + public function objects(): MorphToMany { return $this->morphToMany(NirObject::class, 'objectable'); } @@ -76,6 +82,9 @@ class NirObject extends Model { public function textValues(): HasMany { return $this->hasMany(TextValue::class, 'object_id'); } + public function htmlValues(): HasMany { + return $this->hasMany(HtmlValue::class, 'object_id'); + } public function integerValues(): HasMany { return $this->hasMany(IntegerValue::class, 'object_id'); } diff --git a/app/Models/Pages/Page.php b/app/Models/Pages/Page.php index e037bd9..c3c86e5 100644 --- a/app/Models/Pages/Page.php +++ b/app/Models/Pages/Page.php @@ -2,8 +2,10 @@ namespace App\Models\Pages; +use App\Models\Objects\Field; use App\Models\Publications\Publication; use App\Models\Registries\Registry; +use App\Models\User; use App\Support\HasObjectsTrait; use App\Support\RelationValuesTrait; use App\Support\UuidScopeTrait; @@ -94,6 +96,12 @@ class Page extends Model { + public function isEditable(User $user): bool { + return $user->isModerator && $user->membership()->whereHas('objects', function($query) { + Field::applyFilters($query, collect(['types' => 'company-member-properties', 'moderate-pages' => $this->parents->add($this)->pluck('uuid')->all()])); + })->exists(); + } + public function addSection($typeName, $ord = null): ?Model { return $this->createObject($typeName, $ord, 'sections'); } diff --git a/app/Models/Products/Product.php b/app/Models/Products/Product.php new file mode 100644 index 0000000..82da331 --- /dev/null +++ b/app/Models/Products/Product.php @@ -0,0 +1,39 @@ +hasMany(Application::class); + } + + + public function getApplicationAttribute() { + return $this->applications()->first(); + } + +} diff --git a/app/Models/User.php b/app/Models/User.php index 22470d5..42207a8 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,13 +4,23 @@ namespace App\Models; use App\Events\UserRegistered; use App\Mail\PasswordResetRequested; +use App\Models\Advisories\Advisory; +use App\Models\Advisories\AdvisoryMember; +use App\Models\Advisories\AdvisoryMemberRank; +use App\Models\Applications\Conclusion; +use App\Models\Companies\Company; +use App\Models\Companies\CompanyMember; +use App\Models\Companies\CompanyMemberRole; +use App\Models\Objects\Field; use App\Support\HasRolesUuid; use App\Support\HasSocialLogin; use App\Support\RelationValuesTrait; use App\Support\UuidScopeTrait; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -55,6 +65,22 @@ class User extends Authenticatable { return $this->belongsTo(Asset::class, 'asset_id'); } + public function companies(): BelongsToMany { + return $this->belongsToMany(Company::class, 'company_members'); + } + + public function membership(): HasMany { + return $this->hasMany(CompanyMember::class); + } + public function advisoryMembership(): HasManyThrough { + return $this->hasManyThrough(AdvisoryMember::class, CompanyMember::class); + } + + + public function applicationConclusions(): HasMany { + return $this->hasMany(Conclusion::class, 'author_id'); + } + public function getInitialsAttribute(): string { return collect(explode(' ', $this->name))->slice(0, 2)->map(function($item) { @@ -67,14 +93,34 @@ class User extends Authenticatable { } public function getIsAdminAttribute(): bool { - return $this->hasRole('Administrator'); + return $this->hasRole('Administrator') || $this->isMainCompanyAdmin; + } + public function getIsModeratorAttribute(): bool { + return $this->membership()->where(['role' => CompanyMemberRole::MODERATOR])->mainCompany()->exists(); + } + public function getIsMainCompanyAdminAttribute(): bool { + return $this->membership()->where(['role' => CompanyMemberRole::ADMINISTRATOR])->mainCompany()->exists(); + } + public function getIsMainCompanyMemberAttribute(): bool { + return $this->companies()->where(['is_main' => 1])->exists(); + } + public function getIsExpertAttribute(): bool { + return $this->membership()->mainCompany()->whereHas('objects', function($query) { + Field::applyFilters($query, collect(['types' => 'company-member-properties', 'moderate-permissions' => 'applications'])); + })->exists(); } - public function getIsPrivilegedAttribute() { - return $this->isAdmin; + public function getPrivilegesAttribute(): array { + return [ + 'admin' => $this->isAdmin, + 'expert' => $this->isExpert, + 'main_company_member' => $this->isMainCompanyMember + ]; } + + public static function getByData($data, $triggerEvent = false) { $result = false; if ($email = trim($data['email'] ?? null)) { @@ -108,6 +154,14 @@ class User extends Authenticatable { } + public function setPassword(string $password): bool { + return $this->update(['password' => Hash::make($password)]); + } + + public function checkPassword(string $password): bool { + return Hash::check($password, $this->password); + } + public function sendPasswordResetNotification($token) { Mail::to($this->email)->send(new PasswordResetRequested($this, $token)); } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 30aeedb..3dd2dc4 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,8 @@ namespace App\Providers; use App\Models\Advisories\Advisory; use App\Models\Advisories\AdvisoryCompany; use App\Models\Advisories\AdvisoryMember; +use App\Models\Applications\Application; +use App\Models\Applications\Conclusion; use App\Models\Asset; use App\Models\Companies\Address; use App\Models\Companies\BankDetails; @@ -20,6 +22,7 @@ use App\Models\Objects\NirObject; use App\Models\Objects\ObjectType; use App\Models\Pages\Page; use App\Models\Permission; +use App\Models\Products\Product; use App\Models\Publications\Publication; use App\Models\Registries\Category; use App\Models\Registries\Entry; @@ -51,7 +54,7 @@ class AppServiceProvider extends ServiceProvider public function boot() { Carbon::setLocale(config('app.locale')); - // if ($this->app->environment('local')) Mail::alwaysTo('panabonic@yandex.ru'); + if ($this->app->environment('local')) Mail::alwaysTo('test@nirgroup.ru'); Relation::enforceMorphMap([ 'asset' => Asset::class, @@ -84,7 +87,11 @@ class AppServiceProvider extends ServiceProvider 'advisory' => Advisory::class, 'advisory-member' => AdvisoryMember::class, - 'advisory-company' => AdvisoryCompany::class + 'advisory-company' => AdvisoryCompany::class, + + 'application' => Application::class, + 'application-conclusion' => Conclusion::class, + 'product' => Product::class ]); } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 0c4f8b8..cf65602 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,9 +2,11 @@ namespace App\Providers; +use App\Events\Applications\ApplicationStatusChanged; use App\Events\FeedbackSender; use App\Events\PasswordRecovered; use App\Events\UserRegistered; +use App\Listeners\Applications\SendApplicationStatusChangedNotifications; use App\Listeners\SendFeedbackMessage; use App\Listeners\SendPasswordRecoveredNotification; use App\Listeners\SendRegistrationNotification; @@ -25,6 +27,10 @@ class EventServiceProvider extends ServiceProvider { ], FeedbackSender::class => [ SendFeedbackMessage::class + ], + + ApplicationStatusChanged::class => [ + SendApplicationStatusChangedNotifications::class ] ]; diff --git a/app/Services/Filters/Applications/ApplicationFilters.php b/app/Services/Filters/Applications/ApplicationFilters.php new file mode 100644 index 0000000..69f8990 --- /dev/null +++ b/app/Services/Filters/Applications/ApplicationFilters.php @@ -0,0 +1,104 @@ + 'common', + 'title' => 'Общие параметры', + 'fields' => $this->nativeFields($filters) + ] + ]; + $query = Application::query(); + $this->applyFilters($query, $filters); + return ['groups' => ['data' => $groups], 'total' => $query->count()]; + } + + public function nativeFields(Collection $filters): array { + return [ + [ + 'name' => 'created_at', + 'title' => 'Дата формирования заявки', + 'type' => FieldType::DATE, + 'range' => $this->getDatesRange('created_at', $filters), + 'value' => $filters->get('created_at') + ], + [ + 'name' => 'status', + 'title' => 'Статус', + 'type' => FieldType::RELATION, + 'represented' => $this->getStatuses($filters), + 'value' => $this->getRelationValue($filters->get('status'), ApplicationStatus::TITLES) + ], + [ + 'name' => 'expert', + 'title' => 'Эксперт', + 'type' => FieldType::RELATION, + 'represented' => $this->getExperts($filters), + 'value' => ($val = $filters->get('expert')) ? fractal(User::byUuids($val)->get(), new UserTransformer()) : null + ] + ]; + } + + public function getDatesRange($prop, Collection $filters): array { + $query = Application::query(); + $this->applyFilters($query, $filters->except($prop)); + return ['min' => ($v = $query->min($prop)) ? Date::create($v)->format('Y-m-d') : null, 'max' => ($v = $query->max($prop)) ? Date::create($v)->format('Y-m-d') : null]; + } + + + public function getStatuses(Collection $filters): array { + $query = Application::query()->whereNotNull('status')->distinct('status'); + $this->applyFilters($query, $filters->except('status')); + $res = $query->get('status')->map(function($item) {return $item->status;})->all(); + return $this->getRelationItems(collect(ApplicationStatus::TITLES)->only($res)->all()); + } + + + public function getExperts(Collection $filters): Fractal { + return fractal(User::query()->whereHas('applicationConclusions.application', function($query) use($filters) { + $this->applyFilters($query, $filters->except('expert')); + })->orderBy('name')->get(), new UserTransformer()); + } + + + public function applyFilters(Builder $query, Collection $filters) { + $this->applyNativeFilters($query, $filters); + $this->applyPermissionsFilters($query); + } + + public function applyNativeFilters(Builder $query, Collection $filters) { + $filters->each(function($value, $prop) use($query) { + $this->applyNativeFilter($query, $prop, $value); + }); + } + + public function applyNativeFilter(Builder $query, $prop, $value) { + if ($value) { + if ($prop === 'search') $this->applySearchFilter($query, $value, ['number', 'applicant', ['submitter' => ['name']], ['product' => ['name', 'purpose', 'normative', 'producer']]]); + elseif ($prop === 'created_at') $this->applyDateFilter($query, 'created_at', $value); + elseif ($prop === 'status') $query->whereIn('status', is_array($value) ? $value : [$value]); + elseif ($prop === 'expert') $this->applyRelationFilter($query, 'conclusions.author', $value); + } + } + + public function applyPermissionsFilters(Builder $query) { + $user = Auth::user(); + if (!$user->isExpert && !$user->isAdmin) $query->where(['submitter_id' => $user->id ?? null]); + } + +} diff --git a/app/Services/Filters/Applications/ApplicationFiltersServices.php b/app/Services/Filters/Applications/ApplicationFiltersServices.php new file mode 100644 index 0000000..b651333 --- /dev/null +++ b/app/Services/Filters/Applications/ApplicationFiltersServices.php @@ -0,0 +1,9 @@ + ApplicationFilters::class + ]; +} diff --git a/app/Services/Filters/FiltersService.php b/app/Services/Filters/FiltersService.php index 3a11212..cc7f8e5 100644 --- a/app/Services/Filters/FiltersService.php +++ b/app/Services/Filters/FiltersService.php @@ -2,7 +2,11 @@ namespace App\Services\Filters; +use App\Services\Filters\Applications\ApplicationFiltersServices; use App\Services\Filters\Companies\CompanyFiltersServices; +use App\Services\Filters\Objects\ObjectFiltersServices; +use App\Services\Filters\Pages\PageFiltersServices; +use App\Services\Filters\Publications\PublicationFiltersServices; use App\Services\Filters\Registries\RegistryFiltersServices; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; @@ -10,8 +14,12 @@ use Illuminate\Support\Facades\Date; class FiltersService { public static array $services = [ + PageFiltersServices::class, + PublicationFiltersServices::class, RegistryFiltersServices::class, - CompanyFiltersServices::class + CompanyFiltersServices::class, + ApplicationFiltersServices::class, + ObjectFiltersServices::class ]; diff --git a/app/Services/Filters/Objects/ObjectFilters.php b/app/Services/Filters/Objects/ObjectFilters.php new file mode 100644 index 0000000..44e588a --- /dev/null +++ b/app/Services/Filters/Objects/ObjectFilters.php @@ -0,0 +1,53 @@ +applyFilters($query, $filters); + return ['groups' => ['data' => $groups], 'total' => $query->count()]; + } + + + public function applyFilters(Builder $query, Collection $filters) { + $this->applyNativeFilters($query, $filters); + $this->applyObjectFilters($query, $filters); + $this->applyPermissionsFilters($query); + } + + public function applyNativeFilters(Builder $query, Collection $filters) { + $filters->each(function($value, $prop) use($query) { + $this->applyNativeFilter($query, $prop, $value); + }); + } + + public function applyNativeFilter(Builder $query, $prop, $value) { + if ($value) { + if ($prop === 'search') $this->applySearchFilter($query, $value, ['name', ['pages' => ['name']], ['stringValues' => ['value']], ['textValues' => ['value']], ['htmlValues' => ['value']]]); + } + } + + public function applyObjectFilters(Builder $query, Collection $filters) { + Field::applyFilters($query, $filters); + } + + public function applyCompanyFilter(Builder $query, $value) { + $query->whereHas('company', function($query) use($value) { + ($value === 'main') ? $query->where(['is_main' => 1]) : $query->whereIn('uuid', is_array($value) ? $value : [$value]); + }); + } + + + public function applyPermissionsFilters(Builder $query) { + } + +} diff --git a/app/Services/Filters/Objects/ObjectFiltersServices.php b/app/Services/Filters/Objects/ObjectFiltersServices.php new file mode 100644 index 0000000..b1de4e5 --- /dev/null +++ b/app/Services/Filters/Objects/ObjectFiltersServices.php @@ -0,0 +1,9 @@ + ObjectFilters::class + ]; +} diff --git a/app/Services/Filters/Pages/PageFilters.php b/app/Services/Filters/Pages/PageFilters.php new file mode 100644 index 0000000..3fcdefc --- /dev/null +++ b/app/Services/Filters/Pages/PageFilters.php @@ -0,0 +1,50 @@ + 'common', + 'title' => 'Общие параметры', + 'fields' => $this->nativeFields($filters) + ] + ]; + $query = CompanyMember::query(); + $this->applyFilters($query, $filters); + return ['groups' => ['data' => $groups], 'total' => $query->count()]; + } + + public function nativeFields(Collection $filters): array { + return [ + ]; + } + + + public function applyFilters(Builder $query, Collection $filters) { + $this->applyNativeFilters($query, $filters); + $this->applyPermissionsFilters($query); + } + + public function applyNativeFilters(Builder $query, Collection $filters) { + $filters->each(function($value, $prop) use($query) { + $this->applyNativeFilter($query, $prop, $value); + }); + } + + public function applyNativeFilter(Builder $query, $prop, $value) { + if ($value) { + if ($prop === 'search') $this->applySearchFilter($query, $value, ['name']); + } + } + + public function applyPermissionsFilters(Builder $query) { + } + +} diff --git a/app/Services/Filters/Pages/PageFiltersServices.php b/app/Services/Filters/Pages/PageFiltersServices.php new file mode 100644 index 0000000..fd930ff --- /dev/null +++ b/app/Services/Filters/Pages/PageFiltersServices.php @@ -0,0 +1,9 @@ + PageFilters::class + ]; +} diff --git a/app/Services/Filters/Publications/PublicationFilters.php b/app/Services/Filters/Publications/PublicationFilters.php new file mode 100644 index 0000000..b7d3708 --- /dev/null +++ b/app/Services/Filters/Publications/PublicationFilters.php @@ -0,0 +1,54 @@ + 'common', + 'title' => 'Общие параметры', + 'fields' => $this->nativeFields($filters) + ] + ]; + $query = CompanyMember::query(); + $this->applyFilters($query, $filters); + return ['groups' => ['data' => $groups], 'total' => $query->count()]; + } + + public function nativeFields(Collection $filters): array { + return [ + ]; + } + + + public function applyFilters(Builder $query, Collection $filters) { + $this->applyNativeFilters($query, $filters); + $this->applyPermissionsFilters($query); + } + + public function applyNativeFilters(Builder $query, Collection $filters) { + $filters->each(function($value, $prop) use($query) { + $this->applyNativeFilter($query, $prop, $value); + }); + } + + public function applyNativeFilter(Builder $query, $prop, $value) { + if ($value) { + if ($prop === 'search') $this->applySearchFilter($query, $value, ['name', 'excerpt']); + elseif ($prop === 'page') $this->applyRelationFilter($query, 'page', $value); + } + } + + public function applyPermissionsFilters(Builder $query) { + $user = Auth::user(); + if (!($user->isAdmin ?? null)) $query->where(['is_published' => true])->where('published_at', '<=', now()); + } + +} diff --git a/app/Services/Filters/Publications/PublicationFiltersServices.php b/app/Services/Filters/Publications/PublicationFiltersServices.php new file mode 100644 index 0000000..7637913 --- /dev/null +++ b/app/Services/Filters/Publications/PublicationFiltersServices.php @@ -0,0 +1,9 @@ + PublicationFilters::class + ]; +} diff --git a/app/Services/Forms/Applications/ApplicationForms.php b/app/Services/Forms/Applications/ApplicationForms.php new file mode 100644 index 0000000..80a7b0d --- /dev/null +++ b/app/Services/Forms/Applications/ApplicationForms.php @@ -0,0 +1,126 @@ + 'Формирование заявки', 'update' => 'Редактирование заявки']; + + public function form(?string $id = null, array $data = []): array { + $model = Application::byUuid($id)->first(); + return [ + 'title' => $this->formTitle($model), + 'frames' => [ + ['title' => 'Общие сведения', 'groups' => $this->form1Groups($model)], + ['title' => 'Документация', 'groups' => $this->form2Groups($model)] + ] + ]; + } + + public function form1Groups(?Application $model): array { + $groups = [ + ['name' => 'common', 'title' => '', 'fields' => $this->commonGroupFields($model)] + ]; + return ['data' => $groups]; + } + + public function form2Groups(?Application $model): array { + $groups = [ + ['name' => 'documents', 'title' => '', 'fields' => $this->documentsGroupFields($model)] + ]; + return ['data' => $groups]; + } + + public function commonGroupFields(?Application $model): array { + $fields = [ + [ + 'name' => 'applicant', + 'title' => 'Организация-заявитель', + 'type' => FieldType::STRING, + 'required' => true, + 'value' => $model->applicant ?? null + ], + [ + 'name' => 'product_name', + 'title' => 'Hаименование продукции', + 'type' => FieldType::TEXT, + 'required' => true, + 'value' => $model->product->name ?? null + ], + [ + 'name' => 'product_purpose', + 'title' => 'Hазначение продукции', + 'type' => FieldType::TEXT, + 'required' => true, + 'value' => $model->product->purpose ?? null + ], + [ + 'name' => 'product_usage', + 'title' => 'Область применения продукции', + 'type' => FieldType::TEXT, + 'required' => true, + 'value' => $model->product->usage ?? null + ], + [ + 'name' => 'normative', + 'title' => 'Нормативно-технический документ', + 'type' => FieldType::STRING, + 'value' => $model->product->normative ?? null + ], + [ + 'name' => 'producer', + 'title' => 'Изготовитель/разработчик', + 'type' => FieldType::TEXT, + 'required' => true, + 'value' => $model->product->producer ?? null + ] + ]; + return ['data' => $fields]; + } + + public function documentsGroupFields(?Application $model): array { + $fields = [ + [ + 'name' => 'documents', + 'title' => 'Документация', + 'type' => FieldType::DOCUMENT, + 'multiple' => true, + 'value' => $model ? fractal($model->properties->getValue('documents'), new AssetTransformer()) : null + ] + ]; + return ['data' => $fields]; + } + + + + public function store(array $data): ?JsonResponse { + $product = Product::create(); + $model = Application::create(['submitter_id' => Auth::user()->id, 'product_id' => $product->id, 'number' => Application::generateNumber(), 'status' => ApplicationStatus::PROCESSING]); + $this->updateData($model, $data); + $model->submit(); + return fractal($model, new ApplicationTransformer())->respond(); + } + + public function update(string $id, array $data): ?JsonResponse { + $model = Application::byUuid($id)->firstOrFail(); + $this->updateData($model, $data); + return fractal($model, new ApplicationTransformer())->respond(); + } + + public function updateData(Application $model, array $data) { + $model->update(['applicant' => $data['applicant'] ?? null]); + $model->product->update(['name' => $data['product_name'] ?? null, 'purpose' => $data['product_purpose'] ?? null, 'usage' => $data['product_usage'] ?? null, + 'normative' => $data['normative'] ?? null, 'producer' => $data['producer'] ?? null]); + $model->properties->setValue('documents', $data['documents'] ?? null); + } + +} diff --git a/app/Services/Forms/Applications/ApplicationFormsServices.php b/app/Services/Forms/Applications/ApplicationFormsServices.php new file mode 100644 index 0000000..ea9e1be --- /dev/null +++ b/app/Services/Forms/Applications/ApplicationFormsServices.php @@ -0,0 +1,10 @@ + ApplicationForms::class, + 'applicationConclusion' => ConclusionForms::class + ]; +} diff --git a/app/Services/Forms/Applications/ConclusionForms.php b/app/Services/Forms/Applications/ConclusionForms.php new file mode 100644 index 0000000..0e85c96 --- /dev/null +++ b/app/Services/Forms/Applications/ConclusionForms.php @@ -0,0 +1,61 @@ + 'Ответ на заявку', 'update' => 'Редактирование ответа']; + + public function form(?string $id = null, array $data = []): array { + $model = Conclusion::byUuid($id)->first(); + return [ + 'title' => $this->formTitle($model), + 'frames' => [ + ['title' => 'Общие сведения', 'groups' => $this->form1Groups($model)], + ] + ]; + } + + public function form1Groups(?Conclusion $model): array { + $groups = [ + ['name' => 'common', 'title' => '', 'fields' => $this->commonGroupFields($model)] + ]; + return ['data' => $groups]; + } + + public function commonGroupFields(?Conclusion $model): array { + $fields = [ + [ + 'name' => 'message', + 'title' => 'Мнение эксперта', + 'type' => FieldType::HTML, + 'required' => true, + 'value' => $model->message ?? null + ] + ]; + return ['data' => $fields]; + } + + + + public function store(array $data): ?JsonResponse { + $application = Application::byUuid($data['application'] ?? null)->firstOrFail(); + $model = $application->conclusions()->create(['author_id' => Auth::user()->id, 'message' => $data['message'] ?? null]); + $application->complete(); + return fractal($model, new ConclusionTransformer())->respond(); + } + + public function update(string $id, array $data): ?JsonResponse { + $model = Conclusion::byUuid($id)->firstOrFail(); + $model->update(['message' => $data['message'] ?? null]); + return fractal($model, new ConclusionTransformer())->respond(); + } + +} diff --git a/app/Services/Forms/Companies/CompanyMemberForms.php b/app/Services/Forms/Companies/CompanyMemberForms.php index fdb00f2..b04e755 100644 --- a/app/Services/Forms/Companies/CompanyMemberForms.php +++ b/app/Services/Forms/Companies/CompanyMemberForms.php @@ -6,12 +6,14 @@ use App\Models\Companies\CompanyMember; use App\Models\Companies\CompanyMemberRank; use App\Models\Companies\CompanyMemberRole; use App\Models\Companies\Department; +use App\Models\Dictionaries\Dictionary; use App\Models\Objects\FieldType; use App\Models\Pages\Page; use App\Models\User; use App\Services\Forms\FormsService; use App\Transformers\Companies\CompanyMemberTransformer; use App\Transformers\Companies\DepartmentTransformer; +use App\Transformers\Dictionaries\DictionaryItemTransformer; use App\Transformers\Pages\PageTransformer; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Auth; @@ -20,20 +22,35 @@ class CompanyMemberForms extends FormsService { public array $formTitles = ['create' => 'Добавление сотрудника организации', 'update' => 'Редактирование сотрудника']; public function form(?string $id = null, array $data = []): array { + $department = Department::byUuid($data['department'] ?? null)->first(); $model = CompanyMember::byUuid($id)->first(); + $company = $model->company ?? $department->company; + $user = Auth::user(); + $frames = [['title' => 'Общие сведения', 'groups' => $this->form1Groups($model)]]; + if (($company->is_main ?? null) && $user->isAdmin) $frames[] = ['title' => 'Полномочия', 'groups' => $this->form2Groups($model)]; + return ['title' => $this->formTitle($model), 'frames' => $frames]; + } + + public function form1Groups(?CompanyMember $model): array { $groups = [ - [ - 'name' => 'common', - 'fields' => $this->commonGroupFields($model), + ['name' => 'common', 'title' => '', 'fields' => $this->commonGroupFields($model)] + ]; + return ['data' => $groups]; + } + + public function form2Groups(?CompanyMember $model): array { + $groups = [ + ['name' => 'permissions', 'title' => '', 'fields' => $this->permissionsGroupFields($model), 'dynamic' => [ - ['field' => 'role', 'hide' => ['pages']], - ['field' => 'role', 'value' => CompanyMemberRole::MODERATOR, 'show' => ['pages']] + ['field' => 'role', 'hide' => ['moderate-pages']], + ['field' => 'role', 'value' => CompanyMemberRole::MODERATOR, 'show' => ['moderate-pages']] ] ] ]; - return ['title' => $this->formTitle($model), 'data' => $groups]; + return ['data' => $groups]; } + public function commonGroupFields(?CompanyMember $model): array { $fields = [ [ @@ -47,7 +64,7 @@ class CompanyMemberForms extends FormsService { [ 'name' => 'name', 'title' => 'ФИО', - 'type' => FieldType::TEXT, + 'type' => FieldType::STRING, 'required' => true, 'value' => $model->user->name ?? null ], @@ -75,23 +92,6 @@ class CompanyMemberForms extends FormsService { 'title' => 'Внутренний телефон', 'type' => FieldType::STRING, 'value' => $model->intercom ?? null - ], - [ - 'name' => 'role', - 'title' => 'Роль', - 'type' => FieldType::RELATION, - 'required' => true, - 'appearance' => 'radio', - 'options' => $this->getRelationItems(CompanyMemberRole::TITLES), - 'value' => $this->getRelationValue(CompanyMemberRole::TITLES, $model->role ?? null) - ], - [ - 'name' => 'pages', - 'title' => 'Разделы сайта', - 'type' => FieldType::RELATION, - 'multiple' => true, - 'options' => fractal(Page::all(), new PageTransformer()), - 'value' => null ] /* [ @@ -124,12 +124,46 @@ class CompanyMemberForms extends FormsService { return ['data' => $fields]; } + public function permissionsGroupFields(?CompanyMember $model): array { + $fields = [ + [ + 'name' => 'role', + 'title' => 'Роль', + 'type' => FieldType::RELATION, + 'required' => true, + 'appearance' => 'radio', + 'options' => $this->getRelationItems(CompanyMemberRole::TITLES), + 'value' => $this->getRelationValue(CompanyMemberRole::TITLES, $model->role ?? null) + ], + [ + 'name' => 'moderate-pages', + 'title' => 'Разделы сайта', + 'type' => FieldType::RELATION, + 'multiple' => true, + 'options' => fractal(Page::all(), new PageTransformer()), + 'value' => $model ? fractal($model->properties->getValue('moderate-pages'), new PageTransformer()) : null + ], + [ + 'name' => 'moderate-permissions', + 'title' => 'Права', + 'type' => FieldType::RELATION, + 'multiple' => true, + 'appearance' => 'checkbox', + 'options' => fractal(Dictionary::byName('moderate-permissions')->first()->items, new DictionaryItemTransformer()), + 'value' => $model ? fractal($model->properties->getValue('moderate-permissions'), new DictionaryItemTransformer()) : null + ], + ]; + + return ['data' => $fields]; + } + public function store(array $data): ?JsonResponse { if (($department = Department::byUuid($data['department'] ?? null)->first()) && ($user = User::getByData(collect($data)->except('role')->all(), true))) { $model = $department->addMember($user, $data['position'] ?? null); $model->update(['position' => $data['position'] ?? null, 'role' => $data['role'] ?? null, 'room' => $data['room'] ?? null, 'intercom' => $data['intercom'] ?? null]); + $model->properties->setValues($data); return fractal($model, new CompanyMemberTransformer())->respond(); } } @@ -140,6 +174,7 @@ class CompanyMemberForms extends FormsService { if ($department = Department::byUuid($data['department'] ?? null)->first()) $model->update(['department_id' => $department->id]); $model->user->update(['name' => $data['name'] ?? null, 'phone' => $data['phone'] ?? null]); if (($email = $data['email'] ?? null) && !User::query()->where(['email' => $email])->exists()) $model->user->update(['email' => $data['email']]); + $model->properties->setValues($data); return fractal($model->fresh(), new CompanyMemberTransformer())->respond(); } diff --git a/app/Services/Forms/FormsService.php b/app/Services/Forms/FormsService.php index d14ece8..bf4ac9f 100644 --- a/app/Services/Forms/FormsService.php +++ b/app/Services/Forms/FormsService.php @@ -3,6 +3,7 @@ namespace App\Services\Forms; use App\Services\Forms\Advisories\AdvisoryFormsServices; +use App\Services\Forms\Applications\ApplicationFormsServices; use App\Services\Forms\Companies\CompanyFormsServices; use App\Services\Forms\Feedback\FeedbackFormsServices; use App\Services\Forms\Pages\PageFormsServices; @@ -12,6 +13,7 @@ use App\Services\Forms\Users\UserFormsServices; class FormsService { public array $formTitles = ['create' => '', 'update' => '']; + public array $errors = []; public static array $services = [ PageFormsServices::class, @@ -20,7 +22,8 @@ class FormsService { UserFormsServices::class, FeedbackFormsServices::class, AdvisoryFormsServices::class, - CompanyFormsServices::class + CompanyFormsServices::class, + ApplicationFormsServices::class ]; public function __construct() { diff --git a/app/Services/Forms/Users/UserFormsServices.php b/app/Services/Forms/Users/UserFormsServices.php index 94200b0..c754d12 100644 --- a/app/Services/Forms/Users/UserFormsServices.php +++ b/app/Services/Forms/Users/UserFormsServices.php @@ -4,6 +4,8 @@ namespace App\Services\Forms\Users; class UserFormsServices { public static array $services = [ - 'user' => UserForms::class + 'user' => UserForms::class, + 'userProfile' => UserProfileForms::class, + 'userPassword' => UserPasswordForms::class ]; } diff --git a/app/Services/Forms/Users/UserPasswordForms.php b/app/Services/Forms/Users/UserPasswordForms.php new file mode 100644 index 0000000..d478b43 --- /dev/null +++ b/app/Services/Forms/Users/UserPasswordForms.php @@ -0,0 +1,70 @@ + '', 'update' => 'Изменение пароля']; + + public function form(?string $id = null, array $data = []): array { + $model = User::byUuid($id)->first(); + $groups = [ + ['name' => 'common', 'fields' => $this->commonGroupFields($model)] + ]; + return ['title' => $this->formTitle($model), 'data' => $groups]; + } + + public function commonGroupFields(?User $model): array { + $fields = [ + [ + 'name' => 'current_password', + 'title' => 'Текущий пароль', + 'type' => FieldType::STRING, + 'appearance' => 'password', + 'required' => true + ], + [ + 'name' => 'password', + 'title' => 'Новый пароль', + 'type' => FieldType::STRING, + 'appearance' => 'password', + 'required' => true + ], + [ + 'name' => 'confirmation', + 'title' => 'Подтверждение пароля', + 'type' => FieldType::STRING, + 'appearance' => 'password', + 'required' => true + ] + ]; + return ['data' => $fields]; + } + + + + public function store(array $data): void { + } + + public function update(string $id, array $data): ?JsonResponse { + $model = User::byUuid($id)->firstOrFail(); + //Validator::make($data, []); + if (!$model->checkPassword($data['current_password'])) $this->error('current_password', 'Текущий пароль указан неверно'); + if (($data['password']) !== ($data['confirmation'])) $this->error('confirmation', 'Пароль и подтверждение пароля не совпадают'); + if (!$this->errors) { + $model->setPassword($data['password']); + return fractal($model, new UserTransformer())->respond(); + } else return response()->json($this->errors, 422); + } + + public function error($field, $message) { + $this->errors[$field][] = $message; + } + + +} diff --git a/app/Services/Forms/Users/UserProfileForms.php b/app/Services/Forms/Users/UserProfileForms.php new file mode 100644 index 0000000..a4aeece --- /dev/null +++ b/app/Services/Forms/Users/UserProfileForms.php @@ -0,0 +1,61 @@ + '', 'update' => 'Редактирование профиля']; + + public function form(?string $id = null, array $data = []): array { + $model = User::byUuid($id)->first(); + $groups = [ + ['name' => 'common', 'fields' => $this->commonGroupFields($model)] + ]; + return ['title' => $this->formTitle($model), 'data' => $groups]; + } + + public function commonGroupFields(?User $model): array { + $fields = [ + [ + 'name' => 'name', + 'title' => 'Фамилия, имя, отчество', + 'type' => FieldType::STRING, + 'required' => true, + 'value' => $model->name ?? null + ], + [ + 'name' => 'phone', + 'title' => 'Телефон', + 'type' => FieldType::STRING, + 'value' => $model->phone ?? null + ], + [ + 'name' => 'avatar', + 'title' => 'Фотография', + 'type' => FieldType::IMAGE, + 'value' => ($model->avatar ?? null) ? fractal($model->avatar, new AssetTransformer()) : null + ] + ]; + return ['data' => $fields]; + } + + + + public function store(array $data): void { + } + + public function update(string $id, array $data): ?JsonResponse { + $model = User::byUuid($id)->firstOrFail(); + $model->update($data); + $model->setAvatar($data['avatar'] ?? null); + return fractal($model->fresh(), new UserTransformer())->respond(); + } + + +} diff --git a/app/Services/PermissionsService.php b/app/Services/PermissionsService.php index c2e5de4..d25fe43 100644 --- a/app/Services/PermissionsService.php +++ b/app/Services/PermissionsService.php @@ -2,7 +2,9 @@ namespace App\Services; +use App\Models\Applications\Application; use App\Models\Objects\NirObject; +use App\Models\Pages\Page; use App\Models\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Facades\Auth; @@ -12,7 +14,9 @@ class PermissionsService { private ?User $user; private array $rules = [ - NirObject::class => 'nirObject' + NirObject::class => 'nirObject', + Application::class => 'application', + Page::class => 'page' ]; public function __construct(Model $model, ?User $user = null) { @@ -21,11 +25,14 @@ class PermissionsService { } public function get(): array { - $rule = $this->rules[get_class($this->model)] ?? null; - $func = "{$rule}Permissions"; - if ($this->user->isPrivileged) return ['anything' => true]; - elseif (method_exists($this, $func)) return $this->$func(); - else return []; + $result = []; + if ($this->user) { + $rule = $this->rules[get_class($this->model)] ?? null; + $func = "{$rule}Permissions"; + $result = method_exists($this, $func) ? $this->$func() : []; + if ($this->user->isAdmin) $result['anything'] = true; + } + return $result; } @@ -33,4 +40,12 @@ class PermissionsService { return ['edit' => $this->model->owner_id === $this->user->id]; } + public function applicationPermissions(): array { + return ['edit' => $this->model->submitter_id === $this->user->id, 'reply' => $this->user->isExpert]; + } + + public function pagePermissions(): array { + return ['edit' => $this->model->isEditable($this->user)]; + } + } \ No newline at end of file diff --git a/app/Transformers/Applications/ApplicationTransformer.php b/app/Transformers/Applications/ApplicationTransformer.php new file mode 100644 index 0000000..bc56de5 --- /dev/null +++ b/app/Transformers/Applications/ApplicationTransformer.php @@ -0,0 +1,54 @@ + $model->uuid, + 'title' => $model->title, + 'status' => $model->parsedStatus, + 'number' => $model->number, + 'applicant' => $model->applicant, + 'created_at' => $model->created_at ? $model->created_at->toIso8601String() : null, + 'updated_at' => $model->updated_at ? $model->updated_at->toIso8601String() : null + ]; + } + + public function includeSubmitter(Application $model): ?Item { + return $model->submitter ? $this->item($model->submitter, new UserTransformer()) : null; + } + + public function includeProduct(Application $model): ?Item { + return $model->product ? $this->item($model->product, new ProductTransformer()) : null; + } + + public function includeProperties(Application $model): ?Item { + return $model->properties ? $this->item($model->properties, new ObjectTransformer()) : null; + } + + public function includeConclusions(Application $model): Collection { + return $this->collection($model->conclusions, new ConclusionTransformer()); + } + + public function includePermissions(Application $model): Primitive { + return $this->primitive((new PermissionsService($model))->get()); + } + +} \ No newline at end of file diff --git a/app/Transformers/Applications/ConclusionTransformer.php b/app/Transformers/Applications/ConclusionTransformer.php new file mode 100644 index 0000000..7ebf6c6 --- /dev/null +++ b/app/Transformers/Applications/ConclusionTransformer.php @@ -0,0 +1,34 @@ + $model->uuid, + 'message' => $model->message, + 'created_at' => $model->created_at ? $model->created_at->toIso8601String() : null, + 'updated_at' => $model->updated_at ? $model->updated_at->toIso8601String() : null + ]; + } + + public function includeAuthor(Conclusion $model): ?Item { + return $model->author ? $this->item($model->author, new UserTransformer()) : null; + } + + public function includeApplication(Conclusion $model): ?Item { + return $model->application ? $this->item($model->application, new ApplicationTransformer()) : null; + } + +} \ No newline at end of file diff --git a/app/Transformers/Objects/ObjectTransformer.php b/app/Transformers/Objects/ObjectTransformer.php index d9212fc..77fe061 100644 --- a/app/Transformers/Objects/ObjectTransformer.php +++ b/app/Transformers/Objects/ObjectTransformer.php @@ -4,6 +4,7 @@ namespace App\Transformers\Objects; use App\Models\Objects\NirObject; use App\Services\PermissionsService; +use App\Transformers\Pages\PageTransformer; use App\Transformers\Users\UserTransformer; use League\Fractal\Resource\Collection; use League\Fractal\Resource\Item; @@ -16,7 +17,7 @@ class ObjectTransformer extends TransformerAbstract { ]; protected array $availableIncludes = [ - 'groups', 'type', 'owner', 'permissions' + 'groups', 'type', 'owner', 'pages', 'permissions' ]; public function transform(NirObject $model): array { @@ -42,6 +43,10 @@ class ObjectTransformer extends TransformerAbstract { return $model->owner ? $this->item($model->owner, new UserTransformer()) : null; } + public function includePages(NirObject $model): Collection { + return $this->collection($model->pages, new PageTransformer()); + } + public function includePermissions(NirObject $model): Primitive { return $this->primitive((new PermissionsService($model))->get()); } diff --git a/app/Transformers/Products/ProductTransformer.php b/app/Transformers/Products/ProductTransformer.php new file mode 100644 index 0000000..325da45 --- /dev/null +++ b/app/Transformers/Products/ProductTransformer.php @@ -0,0 +1,32 @@ + $model->uuid, + 'name' => $model->name, + 'purpose' => $model->purpose, + 'usage' => $model->usage, + 'normative' => $model->normative, + 'producer' => $model->producer + ]; + } + + public function includeApplications(Product $model): Collection { + return $this->collection($model->applications, new ApplicationTransformer()); + } + +} \ No newline at end of file diff --git a/app/Transformers/Users/UserTransformer.php b/app/Transformers/Users/UserTransformer.php index 562ef3d..57dd25b 100644 --- a/app/Transformers/Users/UserTransformer.php +++ b/app/Transformers/Users/UserTransformer.php @@ -4,13 +4,15 @@ namespace App\Transformers\Users; use App\Models\User; use App\Transformers\Assets\AssetTransformer; +use App\Transformers\Companies\CompanyMemberTransformer; use League\Fractal\Resource\Collection; use League\Fractal\Resource\Item; +use League\Fractal\Resource\Primitive; use League\Fractal\TransformerAbstract; class UserTransformer extends TransformerAbstract { protected array $defaultIncludes = []; - protected array $availableIncludes = ['avatar', 'roles']; + protected array $availableIncludes = ['avatar', 'roles', 'membership', 'privileges']; public function transform(User $model): array { return [ @@ -33,4 +35,12 @@ class UserTransformer extends TransformerAbstract { return $this->collection($model->roles, new RoleTransformer()); } + public function includeMembership(User $model): Collection { + return $this->collection($model->membership, new CompanyMemberTransformer()); + } + + public function includePrivileges(User $model): Primitive { + return $this->primitive($model->privileges); + } + } diff --git a/database/migrations/2023_07_31_152001_create_applications_table.php b/database/migrations/2023_07_31_152001_create_applications_table.php new file mode 100644 index 0000000..2830775 --- /dev/null +++ b/database/migrations/2023_07_31_152001_create_applications_table.php @@ -0,0 +1,38 @@ +id(); + $table->char('uuid', 36)->index()->unique(); + $table->integer('submitter_id')->index()->nullable(); + $table->integer('product_id')->index()->nullable(); + $table->string('status')->index()->nullable(); + $table->string('number')->index()->nullable(); + $table->string('applicant', 750)->index()->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('applications'); + } +} diff --git a/database/migrations/2023_07_31_153559_create_products_table.php b/database/migrations/2023_07_31_153559_create_products_table.php new file mode 100644 index 0000000..b19d8db --- /dev/null +++ b/database/migrations/2023_07_31_153559_create_products_table.php @@ -0,0 +1,38 @@ +id(); + $table->char('uuid', 36)->index()->unique(); + $table->text('name')->nullable(); + $table->text('purpose')->nullable(); + $table->text('usage')->nullable(); + $table->text('normative')->nullable(); + $table->text('producer')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('products'); + } +} diff --git a/database/migrations/2023_07_31_155054_create_application_conclusions_table.php b/database/migrations/2023_07_31_155054_create_application_conclusions_table.php new file mode 100644 index 0000000..dcb7a65 --- /dev/null +++ b/database/migrations/2023_07_31_155054_create_application_conclusions_table.php @@ -0,0 +1,36 @@ +id(); + $table->char('uuid', 36)->index()->unique(); + $table->integer('application_id')->index()->nullable(); + $table->integer('author_id')->index()->nullable(); + $table->text('message')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('application_conclusions'); + } +} diff --git a/database/seeders/Dictionaries/DictionariesTableSeeder.php b/database/seeders/Dictionaries/DictionariesTableSeeder.php index 202b509..d85ac44 100644 --- a/database/seeders/Dictionaries/DictionariesTableSeeder.php +++ b/database/seeders/Dictionaries/DictionariesTableSeeder.php @@ -44,6 +44,10 @@ class DictionariesTableSeeder extends Seeder { 'research-types' => [ 'title' => 'Виды исследовательских работ', 'items' => ['nir' => 'НИР', 'niokr' => 'НИОКР'] + ], + 'moderate-permissions' => [ + 'title' => 'Права', + 'items' => ['applications' => 'Рассмотрение предварительных заявок'] ] ]; diff --git a/database/seeders/Objects/FieldsTableSeeder.php b/database/seeders/Objects/FieldsTableSeeder.php index 006a4e3..eda6507 100644 --- a/database/seeders/Objects/FieldsTableSeeder.php +++ b/database/seeders/Objects/FieldsTableSeeder.php @@ -6,8 +6,10 @@ use App\Models\Dictionaries\DictionaryItem; use App\Models\Objects\Field; use App\Models\Objects\FieldType; use App\Models\Objects\ObjectType; +use App\Models\Pages\Page; use App\Transformers\Dictionaries\DictionaryItemTransformer; use App\Transformers\Objects\ObjectTypeTransformer; +use App\Transformers\Pages\PageTransformer; use Illuminate\Database\Seeder; class FieldsTableSeeder extends Seeder { @@ -331,6 +333,26 @@ class FieldsTableSeeder extends Seeder { 'type' => FieldType::STRING, 'required' => true, ], + + 'moderate-pages' => [ + 'title' => 'Модерируемые разделы сайта', + 'type' => FieldType::RELATION, + 'multiple' => true, + 'params' => [ + 'related' => Page::class, 'transformer' => PageTransformer::class, + 'options' => ['show' => true] + ] + ], + 'moderate-permissions' => [ + 'title' => 'Права', + 'type' => FieldType::RELATION, + 'multiple' => true, + 'params' => [ + 'appearance' => 'checkbox', + 'related' => DictionaryItem::class, 'transformer' => DictionaryItemTransformer::class, + 'options' => ['show' => true, 'whereHas' => ['dictionary' => ['name' => 'moderate-permissions']]] + ] + ] ]; public function run() { diff --git a/database/seeders/Objects/ObjectTypeFieldsTableSeeder.php b/database/seeders/Objects/ObjectTypeFieldsTableSeeder.php index afe1c37..36b8a54 100644 --- a/database/seeders/Objects/ObjectTypeFieldsTableSeeder.php +++ b/database/seeders/Objects/ObjectTypeFieldsTableSeeder.php @@ -103,6 +103,18 @@ class ObjectTypeFieldsTableSeeder extends Seeder { 'common' => [ 'fields' => ['operation-type', 'order-name', 'order-date', 'order-document', 'listings', 'active-since', 'active-till', 'developer'] ] + ], + + 'company-member-properties' => [ + 'common' => [ + 'fields' => ['moderate-pages', 'moderate-permissions'] + ] + ], + + 'application-properties' => [ + 'common' => [ + 'fields' => ['documents'] + ] ] ]; diff --git a/database/seeders/Objects/ObjectTypesTableSeeder.php b/database/seeders/Objects/ObjectTypesTableSeeder.php index f8ecb44..70eb758 100644 --- a/database/seeders/Objects/ObjectTypesTableSeeder.php +++ b/database/seeders/Objects/ObjectTypesTableSeeder.php @@ -104,6 +104,14 @@ class ObjectTypesTableSeeder extends Seeder { 'title' => 'Действие со сводом правил' ] ] + ], + + 'company-member-properties' => [ + 'title' => 'Параметры сотрудника организации' + ], + + 'application-properties' => [ + 'title' => 'Параметры заявки' ] ]; diff --git a/resources/views/mail/applications/status-changed.blade.php b/resources/views/mail/applications/status-changed.blade.php new file mode 100644 index 0000000..3863e25 --- /dev/null +++ b/resources/views/mail/applications/status-changed.blade.php @@ -0,0 +1,12 @@ +@extends('mail.layouts.layout') + +@section('content') + @if($status === \App\Models\Applications\ApplicationStatus::PROCESSING) +

{{$application->title}} поступила на рассмотрение

+

Заявитель: {{$application->applicant ?? 'не указано'}}

+

Наименование продукции: {{$application->product->name ?? 'не указано'}}

+

Назначение продукции: {{$application->product->purpose ?? 'не указано'}}

+ @elseif($status === \App\Models\Applications\ApplicationStatus::COMPLETED) +

{{$application->title}} выполнена.

+ @endif +@endsection \ No newline at end of file diff --git a/resources/views/mail/user/password-reset-requested.blade.php b/resources/views/mail/user/password-reset-requested.blade.php index 559d32c..59b2830 100644 --- a/resources/views/mail/user/password-reset-requested.blade.php +++ b/resources/views/mail/user/password-reset-requested.blade.php @@ -1,7 +1,7 @@ @extends('mail.layouts.layout') @section('content') -

Вы запросили сброс пароля на интернет-потрале {{\App\Models\Advisories\Advisory::main()->caption}}

+

Вы запросили сброс пароля на сайте ФАУ ФЦС

Сбросить пароль

Ссылка на сброс пароля действительна в течение 60 минут.

Если вы не запрашивали сброс пароля, никаких дальнейших действий не требуется.

diff --git a/routes/api.php b/routes/api.php index 6e6d6e9..c5338d6 100644 --- a/routes/api.php +++ b/routes/api.php @@ -8,12 +8,11 @@ Route::get('assets/{uuid}', 'Api\Assets\RenderFileController@open'); Route::get('assets/{uuid}/render', 'Api\Assets\RenderFileController@show'); Route::get('assets/{uuid}/download', 'Api\Assets\RenderFileController@download'); -Route::post('register', 'Api\Auth\RegisterController@store'); +Route::post('signup', 'Api\Users\UsersController@store'); +Route::get('users/check-email', 'Api\Users\UsersController@checkEmail'); Route::post('passwords/reset', 'Api\Auth\PasswordsController@store'); Route::put('passwords/reset', 'Api\Auth\PasswordsController@update'); -Route::get('/check-email', 'Api\Auth\RegisterController@checkEmail'); - Route::get('pages', 'Api\Pages\PagesController@index'); Route::get('pages/root', 'Api\Pages\PagesController@root'); Route::get('pages/find', 'Api\Pages\PagesController@find'); @@ -23,13 +22,10 @@ Route::get('publications', 'Api\Publications\PublicationsController@index'); Route::get('publications/find', 'Api\Publications\PublicationsController@find'); Route::get('publications/{id}', 'Api\Publications\PublicationsController@show'); -Route::apiResource('object-types', 'Api\Objects\ObjectTypesController'); - -Route::group(['prefix' => 'forms'], function() { - Route::get('/{target}/{type?}/{id?}', 'Api\Forms\FormsController@get'); - Route::post('/{target}/{type?}/{id?}', 'Api\Forms\FormsController@save'); -}); +Route::apiResource('object-types', 'Api\Objects\ObjectTypesController', []); +Route::put('objects/move/{id}', 'Api\Objects\ObjectsController@move'); +Route::apiResource('objects', 'Api\Objects\ObjectsController'); Route::group(['prefix' => 'registries'], function() { Route::get('/categories', 'Api\Registries\CategoriesController@index'); @@ -47,6 +43,10 @@ Route::group(['prefix' => 'registries'], function() { }); }); + +Route::get('forms/object/feedback-form-support', 'Api\Forms\FormsController@getFeedbackFormSupport'); +Route::post('forms/model/feedback-form-support', 'Api\Forms\FormsController@saveFeedbackFormSupport'); + Route::get('filters/{type}', 'Api\Forms\FormsController@filters'); Route::group(['middleware' => ['auth:api']], function() { @@ -71,8 +71,11 @@ Route::group(['middleware' => ['auth:api']], function() { Route::post('/', 'Api\Assets\UploadFileController@store'); }); - Route::put('objects/move/{id}', 'Api\Objects\ObjectsController@move'); - Route::apiResource('objects', 'Api\Objects\ObjectsController'); + + Route::group(['prefix' => 'forms'], function() { + Route::get('/{target}/{type?}/{id?}', 'Api\Forms\FormsController@get'); + Route::post('/{target}/{type?}/{id?}', 'Api\Forms\FormsController@save'); + }); Route::get('dadata/{inn}', 'Api\Companies\CompaniesController@getDataByInn'); @@ -88,4 +91,5 @@ Route::group(['middleware' => ['auth:api']], function() { Route::apiResource('advisory-companies', 'Api\Advisories\AdvisoryCompaniesController'); Route::apiResource('advisory-members', 'Api\Advisories\AdvisoryMembersController'); + Route::apiResource('applications', 'Api\Applications\ApplicationsController'); }); \ No newline at end of file