taskori-project est le module de gestion clients / projets / tâches de la suite Taskori. Il tourne à l'adresse https://project.taskori.app et constitue le point d'entrée principal pour le suivi opérationnel : création de clients, organisation en projets, attribution et suivi de tâches avec colonnes personnalisées, upload de fichiers, rapports automatiques par e-mail et intégration bidirectionnelle avec taskori-ts (timesheet) et taskori-share (stockage).
Repository GitLab : gitlab.lorva.dev/taskori/taskori-project (runner dédié tag taskori-project)
| Couche | Technologie |
|---|---|
| Langage serveur | PHP 8.1 (Apache mod_rewrite) |
| Base de données principale | MariaDB — base taskori_projet |
| Base de données auth (lecture) | MariaDB — base taskori_auth (partagée) |
| Sessions | Redis 7 (php-ext-redis via PECL) |
| JWT | firebase/php-jwt v6 (HS256) |
| Image GD | Redimensionnement / compression photos côté serveur |
| Frontend | Bootstrap 5.3, Bootstrap Icons, polices Syne + DM Sans |
| Container | Docker (php:8.1-apache + Supervisor) |
| Reverse proxy | Traefik (réseau externe web) |
| CI/CD | GitLab CI — branches dev et main |
taskori-project-web (PHP 8.1 Apache) → 172.32.0.184 (réseau web) / 172.34.0.184 (réseau db)
taskori-project-redis (Redis 7-alpine) → réseau interne taskori-project-network
Le container web est géré par Supervisord qui démarre Apache au premier plan. Les sessions PHP sont stockées dans Redis via session.save_handler = redis.
Traefik — middlewares actifs :
X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-PolicyPort interne : 80 (Apache), aucun port exposé directement.
Toutes les variables sont injectées par entrypoint.sh via envsubst dans config/conf.php.tpl → config/conf.php.
| Variable | Rôle |
|---|---|
DB_HOST / DB_NAME / DB_USER / DB_PASS |
Connexion base principale taskori_projet |
AUTH_DB_NAME / AUTH_DB_USER / AUTH_DB_PASS |
Accès en lecture à taskori_auth (membres, entreprises) |
JWT_SECRET |
Clé de validation des cookies JWT |
APP_URL |
URL publique (défaut : https://project.taskori.app) |
TASKORI_AUTH_URL |
URL de taskori-auth pour redirections login (défaut : https://taskori.app) |
TASKORI_SHARE_INTERNAL_URL |
URL interne de taskori-share (http://taskori-share-web) |
TASKORI_SHARE_TOKEN |
Token X-Taskori-Share-Token pour les appels internes vers share |
TASKORI_TS_INTERNAL_URL |
URL interne de taskori-ts (http://taskori-ts-web) |
TASKORI_TS_SYNC_TOKEN |
Token X-Taskori-Sync-Token pour les appels TS ↔ project |
TASKORI_MANAGE_INTERNAL_URL |
URL interne de taskori-manage |
TASKORI_MANAGE_INTERNAL_TOKEN |
Token X-Taskori-Project-Token vers manage |
SMTP_* |
Envoi d'e-mails (rapports automatiques) |
SENTRY_DSN |
Monitoring Sentry (optionnel) |
REDIS_HOST / REDIS_PORT |
Serveur Redis sessions |
Toute requête entrante est vérifiée par lib/auth_middleware.php → require_auth() :
taskori_token (HS256) est décodé avec firebase/php-jwt.TASKORI_AUTH_URL/login?redirect_uri=....user_uid, company_uid, role, role_uid, plan, firstname, lastname, email.Deux tokens distincts contournent la vérification JWT pour les appels internes :
X-Taskori-Sync-Token (TASKORI_TS_SYNC_TOKEN) : utilisé par taskori-ts pour pousser ts_update_logged_time directement sur une tâche.X-Taskori-Share-Token (TASKORI_SHARE_TOKEN) : utilisé pour les appels vers l'API interne de taskori-share.lib/permissions.php gère un système de permissions par rôle (read / write / none) structuré en trois modules : project, ts, manage. Les permissions sont récupérées depuis taskori-auth et mises en cache Redis 300 secondes (clé perms:{role_uid}). Un rôle admin reçoit automatiquement toutes les permissions en write.
Base : taskori_projet — toutes les tables sont en InnoDB utf8mb4_unicode_ci.
clientsClients des sociétés. Clé fonctionnelle : (company_uid, uid).
| Colonne | Type | Description |
|---|---|---|
uid |
VARCHAR(36) UNIQUE | UUID v4 |
company_uid |
VARCHAR(36) | Référence société (taskori-auth) |
name |
VARCHAR(255) | Nom du client |
email, phone |
VARCHAR | Contact direct (legacy, voir migration manage) |
address, city, zip, country |
VARCHAR | Adresse |
notes |
TEXT | Notes libres |
deleted_at |
DATETIME | Soft delete |
projectsProjets liés à un client.
| Colonne | Type | Description |
|---|---|---|
uid |
VARCHAR(36) UNIQUE | UUID v4 |
company_uid |
VARCHAR(36) | Référence société |
client_id |
INT FK → clients.id |
Client propriétaire |
name |
VARCHAR(255) | Nom du projet |
status |
ENUM(active,completed,archived) |
Statut, défaut active |
deadline |
DATE | Échéance (ajouté en migration idempotente) |
contact_uid |
VARCHAR(36) | Référence client_contacts.uid dans taskori_manage |
manager_uid |
VARCHAR(36) | UID du chef de projet interne (taskori-auth) |
deleted_at |
DATETIME | Soft delete |
tasksTâches liées à un projet.
| Colonne | Type | Description |
|---|---|---|
uid |
VARCHAR(36) UNIQUE | UUID v4 |
project_id |
INT FK → projects.id |
Projet parent |
company_uid |
VARCHAR(36) | Dénormalisation pour requêtes rapides |
title |
VARCHAR(255) | Titre |
status |
ENUM(todo,in_progress,done) |
Statut |
assignee_uid |
VARCHAR(36) | UID utilisateur assigné |
due_date |
DATE | Échéance |
address |
VARCHAR(255) | Adresse d'intervention |
ts_logged_minutes |
INT | Minutes loguées par taskori-ts (poussé via sync) |
deleted_at |
DATETIME | Soft delete |
task_columnsColonnes personnalisées d'un projet. Les colonnes systèmes (is_system=1) sont créées automatiquement à la création du projet : Titre (type title, position -200) et Temps (type duration, position -100).
| Colonne | Type | Description |
|---|---|---|
uid |
VARCHAR(36) UNIQUE | UUID v4 |
project_id |
INT FK → projects.id |
Projet propriétaire |
name |
VARCHAR(100) | Libellé affiché |
type |
ENUM | Voir types de colonnes ci-dessous |
options |
JSON | Options pour select/multiselect |
position |
INT | Ordre d'affichage |
required |
TINYINT(1) | Champ obligatoire |
closes_task |
TINYINT(1) | Passage automatique en done à la saisie |
is_system |
TINYINT(1) | Colonne non supprimable |
is_private |
TINYINT(1) | Visible managers seulement |
Types de colonnes supportés : text, number, date, time, photo, file, checkbox, select, multiselect, url, email, phone, duration
task_valuesValeurs des cellules (une ligne par tâche × colonne). Contrainte UNIQUE (task_id, column_id) — upsert via ON DUPLICATE KEY UPDATE.
| Colonne | Type | Description |
|---|---|---|
task_id |
INT FK → tasks.id CASCADE |
Tâche |
column_id |
INT FK → task_columns.id CASCADE |
Colonne |
value |
TEXT | Valeur texte/nombre/date... |
file_path |
VARCHAR(500) | share:{file_uid} pour les fichiers dans taskori-share, ou chemin local legacy |
task_templates / task_template_columnsModèles de colonnes réutilisables par société. Lors de la création d'un projet, un template peut être sélectionné pour pré-remplir les colonnes. Les colonnes de template supportent is_private.
project_filesIndex des fichiers attachés à un projet (stockage réel dans taskori-share).
| Colonne | Type | Description |
|---|---|---|
uid |
VARCHAR(36) UNIQUE | UUID local |
share_file_uid |
VARCHAR(36) | UID dans taskori-share (clé de liaison) |
file_path |
VARCHAR(500) | Vide si share, chemin local si fallback |
folder_name |
VARCHAR(100) | Photos, Fichiers, devis, factures... |
compressed |
TINYINT(1) | 0 = en attente de compression par le cron nightly |
mime_type |
VARCHAR(100) | Type MIME |
project_reportsConfiguration des rapports automatiques par e-mail.
| Colonne | Type | Description |
|---|---|---|
project_id |
INT FK → projects.id |
Projet |
client_email |
VARCHAR(255) NULL | Destinataire principal (nullable depuis migration_005) |
contact_uid |
— | Résolu à l'envoi par le cron via contact_uid du projet |
frequency |
ENUM(daily,weekly,monthly) |
Fréquence |
send_day_of_week |
TINYINT | Jour ISO (1=lundi…7=dimanche) pour hebdo |
send_day_of_month |
TINYINT | Jour 1-28 pour mensuel |
skip_if_no_update |
TINYINT(1) | Ne pas envoyer si aucun changement |
last_hash |
VARCHAR(64) | Hash pour détecter les changements |
extra_recipients |
JSON | Tableau d'adresses e-mail supplémentaires |
Les migrations sont appliquées manuellement ou via scripts :
| Fichier | Contenu |
|---|---|
database/migration.sql |
Schéma initial complet (clients, projects, tasks, task_columns, task_values, task_templates, project_files, project_reports) |
database/migration_004.sql |
Ajout share_file_uid sur project_files |
database/migration_005.sql |
Ajout contact_uid et manager_uid sur projects ; client_email nullable + extra_recipients sur project_reports |
Certaines migrations sont aussi exécutées de façon idempotente au chargement des pages (ALTER TABLE ... ADD COLUMN IF NOT EXISTS), notamment pour deadline, ts_logged_minutes, folder_name, compressed.
Le fichier index.php est le point d'entrée unique. La navigation utilise un mécanisme POST → session → redirect pour éviter les paramètres sensibles (UIDs) dans l'URL :
_nav=1, _view=<vue> et les paramètres contextuels (client_uid, project_uid) est soumis.$_SESSION['nav'][<vue>].APP_URL/<vue>.$_SESSION['nav'][$view].Vues disponibles : dashboard, clients, projects, tasks, templates, export
La fonction JS tsNav(view, params) est injectée dans toutes les pages pour déclencher cette navigation côté client.
api.php)Toutes les requêtes sont en POST. L'action est passée dans $_POST['action'].
Vérification du cookie JWT sauf pour ts_update_logged_time (token sync TS).
| Action | Rôle requis | Description |
|---|---|---|
task_create |
manager | Crée une tâche dans un projet |
task_update |
manager ou assigné | Met à jour un champ (title, status, assignee_uid, due_date, address) |
task_delete |
manager | Soft delete — bloqué si task_values existent ou si des interventions TS référencent la tâche |
| Action | Rôle requis | Description |
|---|---|---|
column_create |
manager | Crée une colonne custom |
column_update |
manager | Modifie nom / closes_task / required |
column_delete |
manager | Supprime (interdit si is_system=1) |
column_reorder |
manager | Réordonne les colonnes par tableau d'UIDs |
column_toggle_private |
manager | Bascule visibilité manager-only |
| Action | Rôle requis | Description |
|---|---|---|
value_update |
manager ou assigné | Upsert d'une valeur ; ferme la tâche si closes_task=1 |
file_upload |
manager ou assigné | Upload vers taskori-share (fallback local si share KO) ; max 10 Mo pour photos/fichiers tâche |
task_file_delete |
manager ou assigné | Supprime le fichier de share et la référence task_values |
task_value_override |
manager | Écrase une valeur sans accumulation (ex: durée) |
ts_values_bulk_save |
tout membre | Sauvegarde groupée depuis TS — durées accumulées (ON DUPLICATE KEY UPDATE value+=value) |
| Action | Description |
|---|---|
templates_list |
Liste les templates de la société |
template_create / template_delete |
CRUD templates |
template_columns_list / template_column_add / template_column_update / template_column_delete |
CRUD colonnes de template |
template_column_toggle_private |
Visibilité manager-only |
| Action | Description |
|---|---|
report_config_get |
Lit la config rapport d'un projet |
report_config_save |
Crée ou met à jour la config (fréquence, jour, skip, extra_recipients) |
report_config_delete |
Supprime la config |
| Action | Description |
|---|---|
ts_tasks_list |
Liste tâches + colonnes d'un projet pour TS (techs : colonnes required/closes_task seulement) |
ts_file_stream |
Stream d'un fichier share vers TS |
ts_client_info |
Infos client pour TS |
ts_project_info |
Infos projet + client + fichiers pour TS |
ts_open_projects |
Projets avec tâches ouvertes sans valeurs |
ts_update_logged_time |
Push minutes loguées depuis TS (bypass JWT, token sync) |
project_interventions |
Historique des interventions TS pour un projet (proxy vers TS internal API) |
| Action | Description |
|---|---|
client_list |
Liste des clients de la société |
project_list |
Projets d'un client |
members |
Membres de la société (depuis taskori_auth) |
project_file_upload |
Upload fichier projet (max 50 Mo) |
project_files_list |
Liste fichiers + sync depuis share |
project_file_delete |
Suppression fichier share + nettoyage task_values |
Tous les fichiers uploadés sont stockés dans taskori-share via l'API interne. Le module project maintient uniquement un index local (project_files) avec la colonne share_file_uid.
$_FILES).image/jpeg, image/png, image/gif, image/webp), max 10 Mo.get_or_create_share_folder() → POST /api/internal/folder_get_or_create.php avec X-Taskori-Share-Token.upload_to_share() → POST /api/internal/upload.php (contenu en base64 dans le JSON).uploads/{company_uid}/projects/{project_uid}/.project_files avec share_file_uid et compressed=0.delete_from_share() appelle POST /api/internal/delete.php. La référence project_files et les task_values associées sont ensuite nettoyées localement.
project_files_list enrichit la liste locale avec les fichiers connus de share via GET /api/internal/list_project_files.php.
Les photos uploadées sans compression immédiate ont compressed=0. Un cron nightly (géré par taskori-cron ou le service dédié) se charge de la compression ultérieure via GD (max 1920px, qualité 85).
TS appelle ts_tasks_list, ts_client_info, ts_project_info, ts_open_projects pour récupérer les données de travail.
ts_values_bulk_save : une intervention TS sauvegarde ses valeurs de champs. Les colonnes duration sont accumulées (plusieurs interventions = somme des durées). Les autres types : upsert standard.ts_update_logged_time : bypass JWT, utilisé par TS pour mettre à jour tasks.ts_logged_minutes en temps réel.Avant de soft-delete une tâche ou un projet, l'API interroge POST /api/internal/interventions_by_project.php sur TS (X-Taskori-Sync-Token) pour vérifier qu'aucune intervention ne référence la ressource.
lib/plan.php + lib/plan_guard.php implémentent les limites par plan.
| Plan | Utilisateurs max | Projets actifs max | Stockage |
|---|---|---|---|
solo |
1 | 2 | 10 GB |
pro |
10 | illimité | 25 GB |
business |
illimité | illimité | 50 GB |
Plan effectif (effective_plan()) :
active / lifetime → plan souscrittrialing non expiré → solo → pro (trial), pro/business inchangétrialing expiré → solopast_due → plan souscrit pendant 7 jours, puis solocanceled → plan jusqu'à subscription_ends_at, puis sololimited / inconnu → soloLa fonction project_create_blocker() est appelée avant toute création de projet et retourne 'project_limit_reached' si le quota est atteint.
/dashboard)Statistiques (clients, projets actifs, tâches). Affiche les projets avec échéance dans les 14 prochains jours et les 5 derniers projets créés.
/clients)Liste des clients avec soft delete. Navigation vers les projets d'un client.
/projects)Liste active + archive (pliable). Actions : créer, éditer, archiver, supprimer (bloqué si données).
/tasks)Vue tableau des tâches d'un projet avec colonnes dynamiques. Affiche la durée totale loguée par TS (ts_logged_minutes). Chargement des valeurs optimisé en une seule requête (IN sur les task_ids).
/templates)Gestion des modèles de colonnes réutilisables par société.
/export)Export CSV d'un projet : toutes les tâches avec leurs valeurs custom + liste des fichiers.
Pipeline GitLab (.gitlab-ci.yml) avec runner tagué taskori-project.
stages: test → build → deploy
*.php (hors vendor/) via php -l.devregistry.lorva.dev/taskori/taskori-project:{slug}docker compose -f docker-compose.dev.yml up -dmain (production){slug} ET latest$DEPLOY_HOST :
.env à partir des variables CIscp du .env et docker-compose.yml vers /docker/taskori-projet/docker pull latest + docker compose up -dVariables CI nécessaires : DB_*, AUTH_*, JWT_*, APP_URL, SMTP_*, TASKORI_*, SENTRY_DSN, DEPLOY_HOST, DEPLOY_USER + clé SSH id_ed25519.
scripts/migrate_clients_to_manage.php est un script one-shot pour migrer les clients existants de taskori_projet vers le CRM taskori_manage :
taskori_manage.clients.projects.contact_uid pour les projets existants.devis et factures pour chaque client.--dry-run.finfo).X-Taskori-Share-Token, X-Taskori-Sync-Token, X-Taskori-Project-Token).upload_max_filesize=20M./[^a-zA-Z0-9\-]/) avant usage en base.taskori-project/
├── index.php # Point d'entrée, routing, auth
├── api.php # API POST actions
├── download.php # Téléchargement fichiers
├── config/
│ ├── conf.php.tpl # Template config (envsubst)
│ └── config.php # Config PDO globale
├── lib/
│ ├── auth_db.php # Singleton PDO taskori_auth
│ ├── auth_middleware.php # require_auth() + redirect
│ ├── db.php # Singleton PDO taskori_projet
│ ├── i18n.php # Internationalisation (FR/EN/ES)
│ ├── jwt.php # Validation cookie JWT HS256
│ ├── layout.php # nav_link(), render_nav_js()
│ ├── permissions.php # get_permissions() + can() + cache Redis
│ ├── plan.php # plan_features(), effective_plan()
│ ├── plan_guard.php # project_create_blocker()
│ ├── share.php # upload/download/delete vers taskori-share + compress_image_to_tmp()
│ └── storage.php # get_bucket_info() depuis share
├── pages/
│ ├── dashboard.php
│ ├── clients.php
│ ├── projects.php
│ ├── tasks.php
│ ├── templates.php
│ └── export.php
├── lang/
│ ├── fr.php / en.php / es.php
├── database/
│ ├── migration.sql # Schéma initial
│ ├── migration_004.sql # share_file_uid
│ └── migration_005.sql # contact_uid, manager_uid, extra_recipients
├── scripts/
│ └── migrate_clients_to_manage.php
├── docker/
│ └── supervisord.conf
├── Dockerfile
├── docker-compose.yml
├── docker-compose.dev.yml
├── entrypoint.sh
└── .gitlab-ci.yml