Compare commits
10 Commits
202b516dc3
...
v0.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb0e6c1ea4 | ||
|
|
6d3ecc1322 | ||
|
|
f5986090c0 | ||
|
|
d6399c20e1 | ||
|
|
a972d243f5 | ||
|
|
56bf88f293 | ||
| 9d80e017c2 | |||
| 4e91507158 | |||
| 318f14ea88 | |||
|
|
4216f1b5a1 |
@@ -16,7 +16,7 @@ src/Entity/ # Entités Doctrine (User, Client, Project, Task, TaskStatu
|
||||
src/ApiResource/ # Ressources API Platform (si découplées des entités)
|
||||
src/State/ # Providers et Processors API Platform (MeProvider, AppVersionProvider, ActiveTimeEntryProvider, UserPasswordHasherProcessor, TaskNumberProcessor, ClientTicket*Provider/Processor, NotificationProvider, Gitea*Provider, Gitea*Processor)
|
||||
src/Service/ # Services métier (NotificationService)
|
||||
src/Controller/ # Controllers custom Symfony (NotificationUnreadCountController, MarkAllReadController)
|
||||
src/Controller/ # Controllers custom Symfony (NotificationUnreadCountController, MarkAllReadController, UserAvatarController, TaskDocumentDownloadController)
|
||||
src/Mcp/Tool/ # MCP tools organisés par domaine (Project/, Task/, TaskMeta/, TimeEntry/, Reference/)
|
||||
src/Security/ # Authenticators custom (ApiTokenAuthenticator pour MCP HTTP)
|
||||
src/Command/ # Commandes console (GenerateApiTokenCommand)
|
||||
@@ -28,10 +28,10 @@ migrations/ # Migrations Doctrine
|
||||
docs/plans/ # Plans d'implémentation
|
||||
docs/superpowers/ # Plans et specs superpowers
|
||||
frontend/ # App Nuxt 4
|
||||
frontend/pages/ # Pages (index, login, my-tasks, projects, projects/[id], projects/[id]/groups, projects/[id]/archives, time-tracking, admin, portal/, portal/projects/[id], portal/projects/[id]/new-ticket)
|
||||
frontend/pages/ # Pages (index, login, my-tasks, profile, projects, projects/[id], projects/[id]/groups, projects/[id]/archives, time-tracking, admin, portal/, portal/projects/[id], portal/projects/[id]/new-ticket)
|
||||
frontend/layouts/ # Layouts (default, portal)
|
||||
frontend/components/ # Composants Vue organisés en sous-dossiers (ui/, client/, project/, task/, user/, admin/, time-tracking/, client-ticket/, notification/)
|
||||
frontend/composables/# Composables (useApi, useAppVersion, useNotifications, useClientTicketHelpers)
|
||||
frontend/composables/# Composables (useApi, useAppVersion, useNotifications, useClientTicketHelpers, useAvatarService)
|
||||
frontend/stores/ # Stores Pinia (auth, ui, timer)
|
||||
frontend/services/ # Services API (auth, clients, gitea, projects, tasks, task-statuses, task-efforts, task-groups, task-priorities, task-tags, users, time-entries, client-tickets, notifications, task-documents)
|
||||
frontend/services/dto/ # Types TypeScript
|
||||
@@ -80,6 +80,8 @@ Exemples : `feat : add login page`, `fix(auth) : prevent null token crash`
|
||||
- PostgreSQL : `LIKE` sur colonne JSON ne marche pas → utiliser `roles::text LIKE` via native SQL
|
||||
- Controllers custom sous `/api/` : ajouter `priority: 1` sur `#[Route]` pour éviter le conflit avec API Platform `{id}`
|
||||
- Serialization : pour embarquer une relation (pas IRI), ajouter le groupe du parent aux propriétés de l'entité cible
|
||||
- Upload fichiers : utiliser `$file->getMimeType()` (pas `getClientMimeType()`) pour valider côté serveur — nécessite `symfony/mime`
|
||||
- Auth endpoints mixtes (ROLE_USER + ROLE_CLIENT) : utiliser `#[IsGranted('IS_AUTHENTICATED_FULLY')]` au lieu d'un rôle spécifique
|
||||
|
||||
### Frontend
|
||||
|
||||
|
||||
190
README.md
190
README.md
@@ -1,10 +1,173 @@
|
||||
# Lesstime
|
||||
|
||||
Application de gestion de projet. Symfony 8 + API Platform 4 + Nuxt 4.
|
||||
Application de gestion de projet avec suivi du temps et portail client.
|
||||
|
||||
## MCP Server
|
||||
## Stack
|
||||
|
||||
Lesstime expose un serveur MCP (Model Context Protocol) permettant aux assistants IA (Claude Code, ChatGPT, Codex) d'interagir avec les projets, tâches et le suivi du temps.
|
||||
| Couche | Technologies |
|
||||
|--------|-------------|
|
||||
| **Backend** | PHP 8.4, Symfony 8, API Platform 4, Doctrine ORM |
|
||||
| **Frontend** | Nuxt 4 (SPA), Vue 3, Pinia, Tailwind CSS |
|
||||
| **Base de données** | PostgreSQL 16 |
|
||||
| **Auth** | JWT HTTP-only cookie (lexik/jwt-authentication-bundle) |
|
||||
| **Infrastructure** | Docker (PHP-FPM, Nginx, PostgreSQL) |
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
- Gestion de projets et tâches (kanban, groupes, priorités, tags, efforts)
|
||||
- Suivi du temps (timer, calendrier, vue liste)
|
||||
- Portail client avec tickets (bug, amélioration, autre)
|
||||
- Gestion de documents (upload, prévisualisation, téléchargement)
|
||||
- Profil utilisateur avec avatar (crop circulaire)
|
||||
- Notifications temps réel
|
||||
- Intégration Gitea (issues, repos)
|
||||
- Serveur MCP pour assistants IA
|
||||
- Multi-langue (i18n)
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Docker & Docker Compose
|
||||
- Git
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# 1. Cloner le repo
|
||||
git clone <url> && cd lesstime
|
||||
|
||||
# 2. Démarrer les containers
|
||||
make start
|
||||
|
||||
# 3. Installation complète (composer, migrations, fixtures, build Nuxt)
|
||||
make install
|
||||
```
|
||||
|
||||
L'application est accessible sur **http://localhost:8082**.
|
||||
|
||||
### Comptes de test (fixtures)
|
||||
|
||||
| Utilisateur | Mot de passe | Rôle | Détails |
|
||||
|-------------|-------------|------|---------|
|
||||
| `admin` | `admin` | ROLE_ADMIN | Administrateur |
|
||||
| `alice` | `alice` | ROLE_USER | Utilisateur interne |
|
||||
| `bob` | `bob` | ROLE_USER | Utilisateur interne |
|
||||
| `charlie` | `charlie` | ROLE_USER | Utilisateur interne |
|
||||
| `client-liot` | `client` | ROLE_CLIENT | Client LIOT (projet SIRH) |
|
||||
| `client-acme` | `client` | ROLE_CLIENT | Client ACME (projet CRM) |
|
||||
|
||||
## Commandes
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
make start # Démarrer les containers
|
||||
make stop # Arrêter les containers
|
||||
make restart # Redémarrer les containers
|
||||
make shell # Shell dans le container PHP
|
||||
make shell-root # Shell root dans le container PHP
|
||||
```
|
||||
|
||||
### Développement
|
||||
|
||||
```bash
|
||||
make dev-nuxt # Dev server Nuxt (hot reload, port 3002)
|
||||
make cache-clear # Vider le cache Symfony
|
||||
make logs-dev # Tail logs Symfony
|
||||
```
|
||||
|
||||
### Base de données
|
||||
|
||||
```bash
|
||||
make migration-migrate # Lancer les migrations
|
||||
make fixtures # Charger les fixtures
|
||||
make db-reset # Reset BDD + migrations + fixtures (⚠️ supprime les données)
|
||||
```
|
||||
|
||||
### Tests & Qualité
|
||||
|
||||
```bash
|
||||
make test # PHPUnit
|
||||
make php-cs-fixer-allow-risky # Fix code style PHP (Symfony + PSR-12)
|
||||
```
|
||||
|
||||
### Installation complète
|
||||
|
||||
```bash
|
||||
make install # Composer + migrations + fixtures + build Nuxt
|
||||
make reset # Tout supprimer et réinstaller (⚠️ supprime la BDD)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── Entity/ # Entités Doctrine
|
||||
├── ApiResource/ # Ressources API Platform (découplées)
|
||||
├── State/ # Providers et Processors API Platform
|
||||
├── Controller/ # Controllers custom Symfony
|
||||
├── Service/ # Services métier
|
||||
├── EventListener/ # Listeners Doctrine
|
||||
├── Exception/ # Exceptions custom
|
||||
├── Security/ # Authenticators custom
|
||||
├── Repository/ # Repositories Doctrine
|
||||
├── Command/ # Commandes console
|
||||
├── DataFixtures/ # Fixtures
|
||||
└── Mcp/Tool/ # MCP tools par domaine
|
||||
├── Project/
|
||||
├── Task/
|
||||
├── TaskMeta/
|
||||
├── TimeEntry/
|
||||
└── Reference/
|
||||
|
||||
frontend/
|
||||
├── pages/ # Pages Nuxt (routing auto)
|
||||
│ ├── portal/ # Pages portail client
|
||||
│ └── projects/ # Pages projets
|
||||
├── layouts/ # Layouts (default, portal)
|
||||
├── components/ # Composants Vue
|
||||
│ ├── ui/ # Composants génériques
|
||||
│ ├── task/ # Tâches
|
||||
│ ├── user/ # Utilisateur (avatar, etc.)
|
||||
│ ├── project/ # Projets
|
||||
│ ├── client/ # Clients
|
||||
│ ├── client-ticket/ # Tickets client
|
||||
│ ├── admin/ # Administration
|
||||
│ ├── notification/ # Notifications
|
||||
│ └── time-tracking/ # Suivi du temps
|
||||
├── composables/ # Composables (useApi, useNotifications, etc.)
|
||||
├── stores/ # Stores Pinia (auth, ui, timer)
|
||||
├── services/ # Services API
|
||||
│ └── dto/ # Types TypeScript
|
||||
├── plugins/ # Plugins Nuxt
|
||||
├── utils/ # Utilitaires
|
||||
├── i18n/locales/ # Traductions
|
||||
└── middleware/ # Middleware auth
|
||||
|
||||
config/ # Config Symfony
|
||||
migrations/ # Migrations Doctrine
|
||||
docker/ # Dockerfiles et config Nginx
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
| Container | Port | Description |
|
||||
|-----------|------|-------------|
|
||||
| `php-lesstime-fpm` | 3002 (dev Nuxt) | PHP-FPM + Node 24 |
|
||||
| `nginx-lesstime` | 8082 | Nginx reverse proxy |
|
||||
| PostgreSQL | 5435 | Base de données |
|
||||
|
||||
Configuration : `docker/.env.docker` (override local : `docker/.env.docker.local`)
|
||||
|
||||
## API
|
||||
|
||||
Toutes les routes API sont préfixées `/api` (API Platform).
|
||||
|
||||
- Documentation auto-générée : **http://localhost:8082/api**
|
||||
- Auth : `POST /login_check` avec `{ username, password }` → cookie JWT `BEARER`
|
||||
|
||||
## Serveur MCP
|
||||
|
||||
Lesstime expose un serveur MCP (Model Context Protocol) permettant aux assistants IA d'interagir avec les données.
|
||||
|
||||
### Tools disponibles (22)
|
||||
|
||||
@@ -16,13 +179,6 @@ Lesstime expose un serveur MCP (Model Context Protocol) permettant aux assistant
|
||||
| TaskMeta | `list-statuses`, `list-priorities`, `list-efforts`, `list-tags`, `list-groups`, `create-group`, `update-group` |
|
||||
| TimeEntry | `list-time-entries`, `create-time-entry`, `update-time-entry`, `delete-time-entry` |
|
||||
|
||||
### Transports
|
||||
|
||||
| Transport | Usage | Auth |
|
||||
|-----------|-------|------|
|
||||
| **STDIO** | Claude Code sur la machine locale | Aucune |
|
||||
| **HTTP** (`/_mcp`) | Clients MCP sur le réseau local | API token (`Authorization: Bearer <token>`) |
|
||||
|
||||
### Configuration locale (STDIO)
|
||||
|
||||
```json
|
||||
@@ -55,17 +211,19 @@ Lesstime expose un serveur MCP (Model Context Protocol) permettant aux assistant
|
||||
### Gestion des tokens API
|
||||
|
||||
```bash
|
||||
# Générer un token pour un utilisateur
|
||||
docker exec -u www-data php-lesstime-fpm php bin/console app:generate-api-token <username>
|
||||
```
|
||||
|
||||
### Mise en production (réseau local)
|
||||
## Déploiement
|
||||
|
||||
1. Déployer le code sur le serveur
|
||||
2. `composer install --no-dev --optimize-autoloader`
|
||||
3. `php bin/console doctrine:migrations:migrate --no-interaction`
|
||||
4. `php bin/console cache:clear --env=prod`
|
||||
5. `docker restart nginx-lesstime`
|
||||
6. `php bin/console app:generate-api-token admin` — noter le token
|
||||
7. Ouvrir le port 8082 sur le firewall du serveur (LAN uniquement)
|
||||
8. Configurer les clients MCP avec l'URL `http://<ip-serveur>:8082/_mcp` + le token
|
||||
5. `cd frontend && npm install && npm run build:dist`
|
||||
6. `docker restart nginx-lesstime`
|
||||
7. Ouvrir le port 8082 sur le firewall (LAN uniquement)
|
||||
|
||||
## Licence
|
||||
|
||||
Propriétaire — Tous droits réservés.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
parameters:
|
||||
app.version: '0.1.0'
|
||||
app.version: '0.1.2'
|
||||
|
||||
50
deploy/nginx/lesstime.conf
Normal file
50
deploy/nginx/lesstime.conf
Normal file
@@ -0,0 +1,50 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name project.malio-dev.fr;
|
||||
|
||||
root /var/www/lesstime/frontend/.output/public;
|
||||
index index.html;
|
||||
|
||||
client_max_body_size 55m;
|
||||
|
||||
location ^~ /api/ {
|
||||
root /var/www/lesstime/public;
|
||||
try_files $uri /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ^~ /bundles/ {
|
||||
root /var/www/lesstime/public;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location = /api/login_check {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/lesstime/public/index.php;
|
||||
fastcgi_param DOCUMENT_ROOT /var/www/lesstime/public;
|
||||
fastcgi_param SCRIPT_NAME /index.php;
|
||||
fastcgi_param PATH_INFO /login_check;
|
||||
fastcgi_param REQUEST_URI /login_check;
|
||||
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
|
||||
}
|
||||
|
||||
location ^~ /_mcp {
|
||||
root /var/www/lesstime/public;
|
||||
try_files $uri /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ ^/index\.php(/|$) {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME /var/www/lesstime/public/index.php;
|
||||
fastcgi_param DOCUMENT_ROOT /var/www/lesstime/public;
|
||||
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
|
||||
internal;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@
|
||||
<!-- Delete button -->
|
||||
<button
|
||||
v-if="isAdmin"
|
||||
type="button"
|
||||
class="absolute right-1 top-1 hidden rounded p-0.5 text-neutral-400 transition-colors hover:bg-red-50 hover:text-red-500 group-hover:block"
|
||||
@click.stop="$emit('delete', doc)"
|
||||
>
|
||||
|
||||
@@ -289,6 +289,7 @@ const isOpen = computed({
|
||||
})
|
||||
|
||||
function close() {
|
||||
if (confirmDeleteDocOpen.value || confirmDeleteOpen.value) return
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
@@ -390,6 +391,8 @@ function populateForm(task: Task | null) {
|
||||
|
||||
watch(() => props.modelValue, async (open) => {
|
||||
if (open) {
|
||||
confirmDeleteDocOpen.value = false
|
||||
documentToDelete.value = null
|
||||
populateForm(props.task)
|
||||
try {
|
||||
clientTickets.value = await clientTicketService.getAll({ project: props.projectId })
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Teleport v-if="modelValue" to="body">
|
||||
<Transition name="modal" appear>
|
||||
<div class="fixed inset-0 z-[70] flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/30" @click="cancel" />
|
||||
<div class="absolute inset-0 bg-black/30" @click.stop="cancel" />
|
||||
<div class="relative z-10 w-full max-w-md rounded-lg bg-white p-6 shadow-xl">
|
||||
<h3 class="text-lg font-bold text-neutral-900">{{ $t('taskDocuments.confirmDeleteTitle') }}</h3>
|
||||
<p class="mt-3 text-sm text-neutral-600">
|
||||
|
||||
@@ -236,20 +236,20 @@ onMounted(() => {
|
||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('myTasks.title') }}</h1>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
class="rounded-lg p-2 transition-colors"
|
||||
class="flex items-center justify-center rounded-md p-1.5 transition-colors"
|
||||
:class="viewMode === 'kanban' ? 'bg-primary-500 text-white' : 'text-neutral-400 hover:text-primary-500'"
|
||||
:title="$t('myTasks.viewKanban')"
|
||||
@click="viewMode = 'kanban'"
|
||||
>
|
||||
<Icon name="mdi:view-column-outline" size="20" />
|
||||
<Icon name="mdi:view-column-outline" size="18" />
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg p-2 transition-colors"
|
||||
class="flex items-center justify-center rounded-md p-1.5 transition-colors"
|
||||
:class="viewMode === 'list' ? 'bg-primary-500 text-white' : 'text-neutral-400 hover:text-primary-500'"
|
||||
:title="$t('myTasks.viewList')"
|
||||
@click="viewMode = 'list'"
|
||||
>
|
||||
<Icon name="mdi:view-list-outline" size="20" />
|
||||
<Icon name="mdi:view-list-outline" size="18" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
91
script/deploy-release.sh
Executable file
91
script/deploy-release.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Usage: ./script/deploy-release.sh v0.1.0
|
||||
# Requires: curl, tar, (optional) rsync
|
||||
#
|
||||
# Auth token: set RELEASE_TOKEN env var or create /etc/lesstime-release-token
|
||||
umask 002
|
||||
|
||||
TAG="${1:-}"
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "Usage: $0 v0.1.0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_OWNER="MALIO-DEV"
|
||||
REPO_NAME="Lesstime"
|
||||
GITEA_API="https://gitea.malio.fr/api/v1"
|
||||
DEPLOY_DIR="/var/www/lesstime"
|
||||
|
||||
if [ -f /etc/lesstime-release-token ] && [ -z "${RELEASE_TOKEN:-}" ]; then
|
||||
RELEASE_TOKEN="$(cat /etc/lesstime-release-token)"
|
||||
fi
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
cleanup() {
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
release_json="$tmp_dir/release.json"
|
||||
curl_opts=(-sS)
|
||||
if [ -n "${RELEASE_TOKEN:-}" ]; then
|
||||
curl_opts+=(-H "Authorization: token ${RELEASE_TOKEN}")
|
||||
fi
|
||||
curl "${curl_opts[@]}" \
|
||||
"${GITEA_API}/repos/${REPO_OWNER}/${REPO_NAME}/releases/tags/${TAG}" \
|
||||
-o "$release_json"
|
||||
|
||||
asset_url="$(python3 - "$release_json" <<'PY'
|
||||
import json, sys
|
||||
data = json.load(open(sys.argv[1], 'r'))
|
||||
assets = data.get("assets", [])
|
||||
for a in assets:
|
||||
name = a.get("name", "")
|
||||
if name.startswith("lesstime-") and name.endswith(".tar.gz"):
|
||||
print(a.get("browser_download_url", ""))
|
||||
break
|
||||
PY
|
||||
)"
|
||||
|
||||
if [ -z "$asset_url" ]; then
|
||||
echo "Release asset not found for tag ${TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
archive="$tmp_dir/artefact.tar.gz"
|
||||
curl "${curl_opts[@]}" -L "$asset_url" -o "$archive"
|
||||
|
||||
tar -xzf "$archive" -C "$tmp_dir"
|
||||
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -a --delete --no-perms --no-owner --no-group \
|
||||
--exclude ".env" \
|
||||
--exclude ".env.local" \
|
||||
--exclude "config/jwt" \
|
||||
--exclude "var" \
|
||||
"$tmp_dir"/ "$DEPLOY_DIR"/
|
||||
else
|
||||
cp -a "$tmp_dir"/. "$DEPLOY_DIR"/
|
||||
fi
|
||||
|
||||
# Ensure Nginx can traverse the deploy path.
|
||||
chmod o+rx "$(dirname "$DEPLOY_DIR")" "$DEPLOY_DIR" 2>/dev/null || true
|
||||
|
||||
# Create frontend/dist symlink if needed (nginx serves from frontend/dist)
|
||||
if [ -d "${DEPLOY_DIR}/frontend/.output/public" ] && [ ! -L "${DEPLOY_DIR}/frontend/dist" ]; then
|
||||
ln -sfn "${DEPLOY_DIR}/frontend/.output/public" "${DEPLOY_DIR}/frontend/dist"
|
||||
fi
|
||||
|
||||
echo "Release ${TAG} deployed to ${DEPLOY_DIR}"
|
||||
|
||||
if [ -f "${DEPLOY_DIR}/.env.local" ]; then
|
||||
echo "Clearing cache..."
|
||||
php "${DEPLOY_DIR}/bin/console" cache:clear --env=prod --no-debug
|
||||
|
||||
echo "Running migrations (if any)..."
|
||||
php "${DEPLOY_DIR}/bin/console" doctrine:migrations:migrate --no-interaction --env=prod
|
||||
else
|
||||
echo "Skip post-deploy: ${DEPLOY_DIR}/.env.local not found" >&2
|
||||
fi
|
||||
@@ -54,7 +54,7 @@ class CreateTaskTool
|
||||
$task = new Task();
|
||||
$task->setProject($project);
|
||||
$task->setTitle($title);
|
||||
$task->setNumber($this->taskRepository->findMaxNumberByProject($project) + 1);
|
||||
$task->setNumber($this->taskRepository->findMaxNumberByProjectForUpdate($project) + 1);
|
||||
|
||||
if (null !== $description) {
|
||||
$task->setDescription($description);
|
||||
|
||||
@@ -28,7 +28,7 @@ class TaskRepository extends ServiceEntityRepository
|
||||
$conn = $this->getEntityManager()->getConnection();
|
||||
|
||||
$result = $conn->fetchOne(
|
||||
'SELECT COALESCE(MAX(number), 0) FROM task WHERE project_id = :project FOR UPDATE',
|
||||
'SELECT COALESCE(MAX(number), 0) FROM task WHERE project_id = :project',
|
||||
['project' => $project->getId()],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user