diff --git a/app/HMS/Entities/Members/Project.php b/app/HMS/Entities/Members/Project.php index 05b33eb7..522cd57d 100644 --- a/app/HMS/Entities/Members/Project.php +++ b/app/HMS/Entities/Members/Project.php @@ -47,6 +47,16 @@ class Project */ protected $user; + /** + * @var null|string + */ + protected $tortReason; + + /** + * @var null|Carbon + */ + protected $tortDate; + /** * Gets the value of id. * @@ -233,4 +243,44 @@ public function setUser(User $user) return $this; } + + /** + * @return null|string + */ + public function getTortReason() + { + return $this->tortReason; + } + + /** + * @param null|string $tortReason + * + * @return self + */ + public function setTortReason(?string $tortReason) + { + $this->tortReason = $tortReason; + + return $this; + } + + /** + * @return null|Carbon + */ + public function getTortDate() + { + return $this->tortDate; + } + + /** + * @param null|Carbon $tortDate + * + * @return self + */ + public function setTortDate(?Carbon $tortDate) + { + $this->tortDate = $tortDate; + + return $this; + } } diff --git a/app/HMS/Mappings/HMS.Entities.Members.Project.dcm.yml b/app/HMS/Mappings/HMS.Entities.Members.Project.dcm.yml index 887c2b18..2b05c3a0 100644 --- a/app/HMS/Mappings/HMS.Entities.Members.Project.dcm.yml +++ b/app/HMS/Mappings/HMS.Entities.Members.Project.dcm.yml @@ -26,6 +26,12 @@ HMS\Entities\Members\Project: nullable: true state: type: integer + tortReason: + type: text + nullable: true + tortDate: + type: date + nullable: true manyToOne: user: targetEntity: \HMS\Entities\User @@ -33,4 +39,4 @@ HMS\Entities\Members\Project: name: user_id referencedColumnName: id nullable: false - \ No newline at end of file + diff --git a/app/Http/Controllers/Members/ProjectController.php b/app/Http/Controllers/Members/ProjectController.php index 7348580e..5d3efa7d 100644 --- a/app/Http/Controllers/Members/ProjectController.php +++ b/app/Http/Controllers/Members/ProjectController.php @@ -4,11 +4,15 @@ use App\Events\Labels\ProjectPrint; use App\Http\Controllers\Controller; +use App\Notifications\ProjectRemovalRequest; +use Carbon\Carbon; use Doctrine\ORM\EntityNotFoundException; use HMS\Entities\Members\Project; +use HMS\Entities\Role; use HMS\Entities\User; use HMS\Factories\Members\ProjectFactory; use HMS\Repositories\Members\ProjectRepository; +use HMS\Repositories\RoleRepository; use HMS\Repositories\UserRepository; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -31,21 +35,29 @@ class ProjectController extends Controller */ protected $userRepository; + /** + * @var RoleRepository + */ + protected $roleRepository; + /** * Create a new controller instance. * * @param ProjectRepository $projectRepository * @param ProjectFactory $projectFactory * @param UserRepository $userRepository + * @param RoleRepository $roleRepository */ public function __construct( ProjectRepository $projectRepository, ProjectFactory $projectFactory, - UserRepository $userRepository + UserRepository $userRepository, + RoleRepository $roleRepository, ) { $this->projectRepository = $projectRepository; $this->projectFactory = $projectFactory; $this->userRepository = $userRepository; + $this->roleRepository = $roleRepository; $this->middleware('feature:projects'); $this->middleware('can:project.view.self')->only(['index', 'show']); @@ -53,6 +65,7 @@ public function __construct( $this->middleware('can:project.edit.self')->only(['edit', 'update', 'markActive', 'markComplete']); $this->middleware('can:project.edit.all')->only(['markAbandoned']); $this->middleware(['can:project.printLabel.self', 'feature:label_printer'])->only(['printLabel']); + $this->middleware('can:project.tort')->only(['tort', 'clearTort']); } /** @@ -274,4 +287,57 @@ public function markComplete(Project $project) return back(); } + + /** + * Tort a project (request removal). + * + * @param Project $project + * + * @return \Illuminate\Http\Response + */ + public function tort(Project $project) + { + return view('members.project.tort') + ->with(['project' => $project]); + } + + /** + * Perform a tort a project (request removal). + * + * @param Project $project + * + * @return \Illuminate\Http\Response + */ + public function performTort(Request $request, Project $project) + { + $this->validate($request, [ + 'tortReason' => 'required|string', + ]); + + $project->setTortReason($request->tortReason); + $project->setTortDate(Carbon::now()); + $this->projectRepository->save($project); + + $trusteesTeamRole = $this->roleRepository->findOneByName(Role::TEAM_TRUSTEES); + $trusteesTeamRole->notify(new ProjectRemovalRequest($project)); + $project->getUser()->notify(new ProjectRemovalRequest($project)); + + return redirect()->route('projects.show', ['project' => $project->getId()]); + } + + /** + * Clear an earlier tort request. + * + * @param Project $project + * + * @return \Illuminate\Http\Response + */ + public function clearTort(Project $project) + { + $project->setTortReason(null); + $project->setTortDate(null); + $this->projectRepository->save($project); + + return redirect()->route('projects.show', ['project' => $project->getId()]); + } } diff --git a/app/Notifications/ProjectRemovalRequest.php b/app/Notifications/ProjectRemovalRequest.php new file mode 100644 index 00000000..c5d72a9d --- /dev/null +++ b/app/Notifications/ProjectRemovalRequest.php @@ -0,0 +1,72 @@ +project = $project; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * + * @return array + */ + public function via($notifiable) + { + $channels = ['mail']; + + return $channels; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + $roleRepository = App::make(RoleRepository::class); + $trusteesTeamRole = $roleRepository->findOneByName(Role::TEAM_TRUSTEES); + + return (new MailMessage) + ->subject(config('branding.space_name') . ': Torts (Interference with Goods) Act 1977 - Removal requested') + ->from($trusteesTeamRole->getEmail()) + ->markdown( + 'emails.membership.projectRemoval', + [ + 'fullname' => $this->project->getUser()->getFullname(), + 'projectNumber' => $this->project->getId(), + 'projectName' => $this->project->getProjectName(), + 'removalReason' => $this->project->getTortReason(), + ] + ); + } +} diff --git a/config/roles.php b/config/roles.php index f2c23f72..e16e0afe 100644 --- a/config/roles.php +++ b/config/roles.php @@ -46,6 +46,7 @@ 'project.edit.all', 'project.printLabel.self', 'project.printLabel.all', + 'project.tort', 'box.buy.self', 'box.issue.all', 'box.view.self', @@ -401,6 +402,7 @@ 'project.view.all', 'project.edit.all', 'project.printLabel.all', + 'project.tort', 'box.issue.all', 'box.view.all', 'box.edit.all', diff --git a/database/migrations_doctrine/Version20260629214620_add_project_tort_details.php b/database/migrations_doctrine/Version20260629214620_add_project_tort_details.php new file mode 100644 index 00000000..17793e63 --- /dev/null +++ b/database/migrations_doctrine/Version20260629214620_add_project_tort_details.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE member_projects ADD tort_reason LONGTEXT DEFAULT NULL, ADD tort_date DATE DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE member_projects DROP tort_reason, DROP tort_date'); + } +} diff --git a/database/seeds/ContentBlockSeeder.php b/database/seeds/ContentBlockSeeder.php index 3b635c39..61f4f8ba 100644 --- a/database/seeds/ContentBlockSeeder.php +++ b/database/seeds/ContentBlockSeeder.php @@ -265,6 +265,16 @@ class ContentBlockSeeder extends Seeder 'block' => 'main', 'content' => '', ], + [ + 'view' => 'emails.project.tort', + 'block' => 'main', + 'content' => 'In accordance with the Torts (Interference with Goods) Act 1977 we are obligated to inform you that if your goods have not been collected from Nottingham Hackspace at the address below within 14 days of the date of this email, they will be disposed of', + ], + [ + 'view' => 'emails.project.tort', + 'block' => 'additional', + 'content' => 'Should you wish to collect these items at any point, you can do so by getting in touch with a trustee, who can let you into Nottingham hackspace. A friend can also collect your items for you if you wish - please inform us by email that you are happy for someone else to collect the items.', + ], ]; /** diff --git a/resources/views/emails/membership/projectRemoval.blade.php b/resources/views/emails/membership/projectRemoval.blade.php new file mode 100644 index 00000000..386ffdca --- /dev/null +++ b/resources/views/emails/membership/projectRemoval.blade.php @@ -0,0 +1,17 @@ +@component('mail::message') +# Dear {{ $fullname }}, + +@content('emails.project.tort', 'main') + + +The project details are: + +* Project Number: {{ $projectNumber }} +* Project Name: {{ $projectName }} +* Removal Reason: {{ $removalReason }} + +@content('emails.project.tort', 'additional') + + +{{ config('branding.community_name') }} Trustees +@endcomponent diff --git a/resources/views/members/project/show.blade.php b/resources/views/members/project/show.blade.php index ed1d5e48..f025e7e6 100644 --- a/resources/views/members/project/show.blade.php +++ b/resources/views/members/project/show.blade.php @@ -4,6 +4,16 @@ @section('content')