Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions app/HMS/Entities/Members/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ class Project
*/
protected $user;

/**
* @var null|string
*/
protected $tortReason;

/**
* @var null|Carbon
*/
protected $tortDate;

/**
* Gets the value of id.
*
Expand Down Expand Up @@ -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;
}
}
8 changes: 7 additions & 1 deletion app/HMS/Mappings/HMS.Entities.Members.Project.dcm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ 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
joinColumn:
name: user_id
referencedColumnName: id
nullable: false

68 changes: 67 additions & 1 deletion app/Http/Controllers/Members/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,28 +35,37 @@ 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']);
$this->middleware('can:project.create.self')->only(['create', 'store']);
$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']);
}

/**
Expand Down Expand Up @@ -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()]);
}
}
72 changes: 72 additions & 0 deletions app/Notifications/ProjectRemovalRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Notifications;

use HMS\Entities\Members\Project;
use HMS\Entities\Role;
use HMS\Repositories\RoleRepository;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\App;

class ProjectRemovalRequest extends Notification implements ShouldQueue
{
use Queueable;

/**
* @var Project
*/
protected $project;

/**
* Create a new notification instance.
*
* @return void
*/
public function __construct(Project $project)
{
$this->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(),
]
);
}
}
2 changes: 2 additions & 0 deletions config/roles.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
'project.edit.all',
'project.printLabel.self',
'project.printLabel.all',
'project.tort',
'box.buy.self',
'box.issue.all',
'box.view.self',
Expand Down Expand Up @@ -401,6 +402,7 @@
'project.view.all',
'project.edit.all',
'project.printLabel.all',
'project.tort',
'box.issue.all',
'box.view.all',
'box.edit.all',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Database\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20260629214620_add_project_tort_details extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
$this->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');
}
}
10 changes: 10 additions & 0 deletions database/seeds/ContentBlockSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
],
];

/**
Expand Down
17 changes: 17 additions & 0 deletions resources/views/emails/membership/projectRemoval.blade.php
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions resources/views/members/project/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@

@section('content')
<div class="container">

@if ($project->getTortReason())
<div class="alert alert-warning" role="alert">
<h5 class="alert-heading">Removal Requested</h5>
Removal of this project was requested on {{ $project->getTortDate()->toDateString() }}. Any items for this project may be disposed of after the date provided in the email.
<hr>
<strong>Reason:</strong> {{ $project->getTortReason() }}
</div>
@endif

<div class="table-responsive">
<table class="table table-bordered">
<tbody>
Expand Down Expand Up @@ -90,5 +100,25 @@
@endif
@endif
</div>

@if (\Auth::user()->can('project.tort'))
@if ($project->getTortReason())
<div class="card border-light">
<a href="javascript:void(0);" onclick="$(this).find('form').submit();" class="btn btn-sm btn-primary mb-1">
<form action="{{ route('projects.clearTort', $project->getId()) }}" method="POST" style="display: none">
@method('PATCH')
@csrf
</form>
<i class="fas fa-trash fa-lg" aria-hidden="true"></i> Clear Removal Request
</a>
</div>
@else
<div class="card border-light">
<a href="{{ route('projects.tort', $project->getId()) }}" class="btn btn-sm btn-warning mb-1">
<i class="fas fa-trash fa-lg" aria-hidden="true"></i> Request Removal (Tort)
</a>
</div>
@endif
@endif
</div>
@endsection
Loading