sergeybodin 2023-08-09 09:10:35 +03:00
commit eaa9d73e80
54 changed files with 1539 additions and 104 deletions

View File

@ -0,0 +1,25 @@
<?php
namespace App\Events\Applications;
use App\Models\Applications\Application;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ApplicationStatusChanged {
use Dispatchable, InteractsWithSockets, SerializesModels;
public Application $application;
public string $status;
public function __construct(Application $application, $status = null) {
$this->application = $application;
$this->status = $status ?? $application->status;
}
public function broadcastOn(): PrivateChannel {
return new PrivateChannel('channel-name');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers\Api\Applications;
use App\Http\Controllers\Controller;
use App\Models\Applications\Application;
use App\Services\Filters\FiltersService;
use App\Transformers\Applications\ApplicationTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ApplicationsController extends Controller {
protected Application $model;
public function __construct(Application $model) {
$this->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);
}
}

View File

@ -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();
}

View File

@ -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));

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Listeners\Applications;
use App\Events\Applications\ApplicationStatusChanged;
use App\Mail\Applications\NotifyApplicationStatusChanged;
use App\Models\Applications\Application;
use App\Models\Applications\ApplicationStatus;
use App\Models\Companies\CompanyMember;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
class SendApplicationStatusChangedNotifications {
public array $notifiableStatuses = [
'applicant' => [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) {}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Mail\Applications;
use App\Models\Applications\Application;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class NotifyApplicationStatusChanged extends Mailable implements ShouldQueue {
use Queueable, SerializesModels;
public Application $application;
public User $recipient;
public string $status;
public function __construct(Application $application, User $recipient, $status) {
$this->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");
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Models\Applications;
use App\Events\Applications\ApplicationStatusChanged;
use App\Models\Companies\CompanyMember;
use App\Models\Objects\Field;
use App\Models\Products\Product;
use App\Models\User;
use App\Support\HasObjectsTrait;
use App\Support\RelationValuesTrait;
use App\Support\UuidScopeTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Application extends Model {
use UuidScopeTrait, SoftDeletes, HasObjectsTrait, RelationValuesTrait;
protected $dates = [
];
protected $fillable = [
'submitter_id',
'product_id',
'status',
'number',
'applicant'
];
protected $hidden = [
'id'
];
public function submitter(): BelongsTo {
return $this->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);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Models\Applications;
class ApplicationStatus {
public const SUBMITTED = 'submitted';
public const PROCESSING = 'processing';
public const COMPLETED = 'completed';
public const CANCELED = 'canceled';
public const TITLES = [
self::SUBMITTED => 'Направлено',
self::PROCESSING => 'В работе',
self::COMPLETED => 'Выполнено',
self::CANCELED =>'Отозвана заявителем'
];
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Models\Applications;
use App\Models\User;
use App\Support\UuidScopeTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Conclusion extends Model {
use UuidScopeTrait, SoftDeletes;
public $table = 'application_conclusions';
protected $dates = [
];
protected $fillable = [
'application_id',
'author_id',
'message'
];
protected $hidden = [
'id'
];
public function application(): BelongsTo {
return $this->belongsTo(Application::class);
}
public function author(): BelongsTo {
return $this->belongsTo(User::class);
}
}

View File

@ -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;

View File

@ -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);
});

View File

@ -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');
}

View File

@ -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');
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Models\Products;
use App\Models\Applications\Application;
use App\Support\UuidScopeTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model {
use UuidScopeTrait, SoftDeletes;
protected $dates = [
];
protected $fillable = [
'name',
'purpose',
'usage',
'normative',
'producer'
];
protected $hidden = [
'id'
];
public function applications(): HasMany {
return $this->hasMany(Application::class);
}
public function getApplicationAttribute() {
return $this->applications()->first();
}
}

View File

@ -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));
}

View File

@ -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
]);
}
}

View File

@ -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
]
];

View File

@ -0,0 +1,104 @@
<?php
namespace App\Services\Filters\Applications;
use App\Models\Applications\Application;
use App\Models\Applications\ApplicationStatus;
use App\Models\Objects\FieldType;
use App\Models\User;
use App\Services\Filters\FiltersService;
use App\Transformers\Users\UserTransformer;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
use Spatie\Fractal\Fractal;
class ApplicationFilters extends FiltersService {
public function get(Collection $filters): array {
$groups = [
[
'name' => '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]);
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Services\Filters\Applications;
class ApplicationFiltersServices {
public static array $services = [
'applications' => ApplicationFilters::class
];
}

View File

@ -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
];

View File

@ -0,0 +1,53 @@
<?php
namespace App\Services\Filters\Objects;
use App\Models\Objects\Field;
use App\Models\Objects\NirObject;
use App\Services\Filters\FiltersService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class ObjectFilters extends FiltersService {
public function get(Collection $filters): array {
$groups = [
];
$query = NirObject::query();
$this->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) {
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Services\Filters\Objects;
class ObjectFiltersServices {
public static array $services = [
'objects' => ObjectFilters::class
];
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Services\Filters\Pages;
use App\Models\Companies\CompanyMember;
use App\Services\Filters\FiltersService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class PageFilters extends FiltersService {
public function get(Collection $filters): array {
$groups = [
[
'name' => '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) {
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Services\Filters\Pages;
class PageFiltersServices {
public static array $services = [
'pages' => PageFilters::class
];
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Services\Filters\Publications;
use App\Models\Companies\CompanyMember;
use App\Services\Filters\FiltersService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
class PublicationFilters extends FiltersService {
public function get(Collection $filters): array {
$groups = [
[
'name' => '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());
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Services\Filters\Publications;
class PublicationFiltersServices {
public static array $services = [
'publications' => PublicationFilters::class
];
}

View File

@ -0,0 +1,126 @@
<?php
namespace App\Services\Forms\Applications;
use App\Models\Applications\Application;
use App\Models\Applications\ApplicationStatus;
use App\Models\Objects\FieldType;
use App\Models\Products\Product;
use App\Services\Forms\FormsService;
use App\Transformers\Applications\ApplicationTransformer;
use App\Transformers\Assets\AssetTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
class ApplicationForms extends FormsService {
public array $formTitles = ['create' => 'Формирование заявки', '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);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Services\Forms\Applications;
class ApplicationFormsServices {
public static array $services = [
'application' => ApplicationForms::class,
'applicationConclusion' => ConclusionForms::class
];
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Services\Forms\Applications;
use App\Models\Applications\Application;
use App\Models\Applications\Conclusion;
use App\Models\Objects\FieldType;
use App\Services\Forms\FormsService;
use App\Transformers\Applications\ConclusionTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
class ConclusionForms extends FormsService {
public array $formTitles = ['create' => 'Ответ на заявку', '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();
}
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -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
];
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Services\Forms\Users;
use App\Models\Objects\FieldType;
use App\Models\User;
use App\Services\Forms\FormsService;
use App\Transformers\Users\UserTransformer;
use Illuminate\Http\JsonResponse;
class UserPasswordForms extends FormsService {
public array $formTitles = ['create' => '', '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;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Services\Forms\Users;
use App\Models\Objects\FieldType;
use App\Models\User;
use App\Services\Forms\FormsService;
use App\Transformers\Assets\AssetTransformer;
use App\Transformers\Users\UserTransformer;
use Illuminate\Http\JsonResponse;
class UserProfileForms extends FormsService {
public array $formTitles = ['create' => '', '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();
}
}

View File

@ -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)];
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Transformers\Applications;
use App\Models\Applications\Application;
use App\Services\PermissionsService;
use App\Transformers\Objects\ObjectTransformer;
use App\Transformers\Products\ProductTransformer;
use App\Transformers\Users\UserTransformer;
use League\Fractal\Resource\Collection;
use League\Fractal\Resource\Item;
use League\Fractal\Resource\Primitive;
use League\Fractal\TransformerAbstract;
class ApplicationTransformer extends TransformerAbstract {
protected array $defaultIncludes = [];
protected array $availableIncludes = [
'submitter', 'product', 'properties', 'conclusions', 'permissions'
];
public function transform(Application $model): array {
return [
'id' => $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());
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Transformers\Applications;
use App\Models\Applications\Conclusion;
use App\Transformers\Users\UserTransformer;
use League\Fractal\Resource\Item;
use League\Fractal\TransformerAbstract;
class ConclusionTransformer extends TransformerAbstract {
protected array $defaultIncludes = [];
protected array $availableIncludes = [
'author', 'application'
];
public function transform(Conclusion $model): array {
return [
'id' => $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;
}
}

View File

@ -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());
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Transformers\Products;
use App\Models\Products\Product;
use App\Transformers\Applications\ApplicationTransformer;
use League\Fractal\Resource\Collection;
use League\Fractal\TransformerAbstract;
class ProductTransformer extends TransformerAbstract {
protected array $defaultIncludes = [];
protected array $availableIncludes = [
'applications'
];
public function transform(Product $model): array {
return [
'id' => $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());
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateApplicationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('applications', function (Blueprint $table) {
$table->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');
}
}

View File

@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->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');
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateApplicationConclusionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('application_conclusions', function (Blueprint $table) {
$table->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');
}
}

View File

@ -44,6 +44,10 @@ class DictionariesTableSeeder extends Seeder {
'research-types' => [
'title' => 'Виды исследовательских работ',
'items' => ['nir' => 'НИР', 'niokr' => 'НИОКР']
],
'moderate-permissions' => [
'title' => 'Права',
'items' => ['applications' => 'Рассмотрение предварительных заявок']
]
];

View File

@ -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() {

View File

@ -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']
]
]
];

View File

@ -104,6 +104,14 @@ class ObjectTypesTableSeeder extends Seeder {
'title' => 'Действие со сводом правил'
]
]
],
'company-member-properties' => [
'title' => 'Параметры сотрудника организации'
],
'application-properties' => [
'title' => 'Параметры заявки'
]
];

View File

@ -0,0 +1,12 @@
@extends('mail.layouts.layout')
@section('content')
@if($status === \App\Models\Applications\ApplicationStatus::PROCESSING)
<p><b>{{$application->title}} поступила на рассмотрение</b></p>
<p><b>Заявитель</b>: {{$application->applicant ?? 'не указано'}}</p>
<p><b>Наименование продукции</b>: {{$application->product->name ?? 'не указано'}}</p>
<p><b>Назначение продукции</b>: {{$application->product->purpose ?? 'не указано'}}</p>
@elseif($status === \App\Models\Applications\ApplicationStatus::COMPLETED)
<p><b>{{$application->title}} выполнена.</b></p>
@endif
@endsection

View File

@ -1,7 +1,7 @@
@extends('mail.layouts.layout')
@section('content')
<p>Вы запросили сброс пароля на интернет-потрале {{\App\Models\Advisories\Advisory::main()->caption}}</p>
<p>Вы запросили сброс пароля на сайте ФАУ ФЦС</p>
<p><a class="btn" href="{{url('password/reset', [$token, $recipient->email])}}">Сбросить пароль</a></p>
<p>Ссылка на сброс пароля действительна в течение 60 минут.</p>
<p>Если вы не запрашивали сброс пароля, никаких дальнейших действий не требуется.</p>

View File

@ -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');
});