diff --git a/CURRENT_ARCHITECTURE.md b/CURRENT_ARCHITECTURE.md
new file mode 100644
index 00000000..d56620f4
--- /dev/null
+++ b/CURRENT_ARCHITECTURE.md
@@ -0,0 +1,126 @@
+# Architecture Actuelle du Module Lengow
+
+## ⚠️ IMPORTANT : État Actuel
+
+**Le module Lengow fonctionne actuellement avec l'architecture PrestaShop standard** :
+
+- ✅ **Contrôleurs** : `ModuleAdminController` (dans `controllers/admin/`)
+- ✅ **Templates** : Smarty `.tpl` (dans `views/templates/admin/`)
+- ✅ **Routes** : URLs PrestaShop standard avec token admin
+- ✅ **Compatibilité** : PrestaShop 1.7.8+ à 9.x
+
+---
+
+## ❌ Pourquoi la migration Symfony/Twig complète n'est pas active
+
+### Tentative de migration Symfony/Twig
+
+Des fichiers ont été créés pour une migration vers Symfony/Twig :
+- Contrôleurs Symfony dans `src/Controller/`
+- Templates Twig dans `views/templates/twig/`
+- Configuration des routes dans `config/routes.yml`
+
+**MAIS** : Ces fichiers ne sont **PAS actifs** et ne doivent **PAS être activés** car :
+
+1. **Conflits d'architecture** : L'ajout de `getRoutingConfigPath()` cause des boucles infinies
+2. **Crash de la page des plugins** : PrestaShop ne peut pas gérer les deux systèmes simultanément
+3. **Perte du système de tokens** : Les routes Symfony ne gèrent pas automatiquement les tokens de sécurité PrestaShop
+
+---
+
+## 📋 Comment accéder aux pages actuelles (architecture Smarty)
+
+### Pages du module via le back-office
+
+Toutes les pages sont accessibles via le menu Lengow dans le back-office PrestaShop.
+
+**URLs avec token admin** (générées automatiquement par PrestaShop) :
+```
+https://votre-domaine.com/admin-folder/?controller=AdminLengowDashboard&token=xxx...
+https://votre-domaine.com/admin-folder/?controller=AdminLengowHome&token=xxx...
+https://votre-domaine.com/admin-folder/?controller=AdminLengowOrder&token=xxx...
+https://votre-domaine.com/admin-folder/?controller=AdminLengowFeed&token=xxx...
+```
+
+Le token est **obligatoire** pour la sécurité et est généré automatiquement par PrestaShop.
+
+---
+
+## 🎯 Migration Future vers Symfony/Twig
+
+### Pourquoi migrer ?
+
+PrestaShop 8+ et 9 recommandent l'utilisation de Symfony/Twig, mais cette migration est **complexe** :
+
+- **Temps estimé** : 80-120 heures de développement + 20-30 heures de tests
+- **Scope** : 9 contrôleurs + 37 templates + routes + AJAX
+- **Risque** : Interruption de service pendant la migration
+
+### Approche recommandée
+
+1. **Court terme (actuel)** : Conserver l'architecture Smarty qui fonctionne
+2. **Moyen terme** : Planifier la migration comme un projet dédié
+3. **Long terme** : Migration progressive page par page
+
+### Ressources disponibles
+
+Des guides et exemples ont été créés pour faciliter une future migration :
+
+- **`SYMFONY_TWIG_MIGRATION_GUIDE.md`** : Guide complet de migration (38 000+ caractères)
+- **`SYMFONY_MIGRATION_PLAN.md`** : Plan détaillé de migration
+- **`src/Controller/AdminOrdersController.php`** : Exemple de contrôleur Symfony (NON ACTIF)
+- **`views/templates/twig/admin/orders/`** : Exemples de templates Twig (NON ACTIFS)
+- **`config/routes.yml`** : Configuration des routes (NON ACTIVE)
+
+⚠️ **Ces fichiers sont des EXEMPLES uniquement** - ils ne doivent pas être activés sans une migration complète.
+
+---
+
+## 🔧 Corrections PrestaShop 9
+
+Les seules modifications actives pour la compatibilité PrestaShop 9 sont :
+
+### 1. Extension de compatibilité version dans `lengow.php`
+```php
+'ps_versions_compliancy' => ['min' => '1.7.8.0', 'max' => '9.99.99']
+```
+
+### 2. Méthodes `formatPrice()` dans `LengowList.php` et `LengowProduct.php`
+
+Remplacement de `Tools::displayPrice()` (supprimée en PS9) par :
+```php
+private function formatPrice($price, $currency)
+{
+ $locale = Context::getContext()->getCurrentLocale();
+ if ($locale && method_exists($locale, 'formatPrice')) {
+ return $locale->formatPrice($price, $currency->iso_code);
+ }
+
+ // Fallback pour compatibilité
+ $formattedPrice = number_format($price, $currency->decimals, '.', '');
+
+ if ($currency->format == 1) {
+ return $currency->sign . ' ' . $formattedPrice;
+ } else {
+ return $formattedPrice . ' ' . $currency->sign;
+ }
+}
+```
+
+Ces corrections assurent la **compatibilité PrestaShop 9 sans casser l'architecture existante**.
+
+---
+
+## 📚 Résumé
+
+| Élément | État | Emplacement |
+|---------|------|-------------|
+| Contrôleurs Smarty | ✅ **Actifs** | `controllers/admin/AdminLengow*.php` |
+| Templates Smarty | ✅ **Actifs** | `views/templates/admin/*.tpl` |
+| Routes PrestaShop | ✅ **Actives** | URLs avec token admin |
+| Contrôleurs Symfony | ❌ **Inactifs** | `src/Controller/` (exemples) |
+| Templates Twig | ❌ **Inactifs** | `views/templates/twig/` (exemples) |
+| Routes Symfony | ❌ **Inactives** | `config/routes.yml` (exemple) |
+| Compatibilité PS9 | ✅ **Active** | `formatPrice()` dans LengowList/LengowProduct |
+
+**Le module fonctionne parfaitement avec l'architecture Smarty actuelle sur PrestaShop 1.7.8+ à 9.x.**
diff --git a/MIGRATION_STATUS.md b/MIGRATION_STATUS.md
new file mode 100644
index 00000000..a685332d
--- /dev/null
+++ b/MIGRATION_STATUS.md
@@ -0,0 +1,227 @@
+# Migration Symfony/Twig - État des lieux et prochaines étapes
+
+## Ce qui a été fait ✅
+
+### 1. Migration vers Twig dans les contrôleurs legacy
+- **9 contrôleurs admin** modifiés dans `controllers/admin/` pour utiliser Twig au lieu de Smarty
+- Les contrôleurs utilisent `setTemplate('module:lengow/views/templates/admin/...')` pour charger les templates Twig
+- Les données sont préparées directement dans `initContent()` sans appeler les méthodes legacy
+- **Compatibilité PrestaShop 9** : version max mise à jour à 9.99.99
+- **Erreurs corrigées** : Plus d'appels à des méthodes protégées, pas de conflits avec exit()
+
+### 2. Contrôleurs modifiés
+| Page | Contrôleur Admin | Template Twig | État |
+|------|-----------------|---------------|------|
+| Dashboard | AdminLengowDashboardController | dashboard/index.html.twig | ✅ Charge sans erreur |
+| Home/Connexion | AdminLengowHomeController | home/index.html.twig | ✅ Charge sans erreur |
+| Produits/Feed | AdminLengowFeedController | feed/index.html.twig | ✅ Charge sans erreur |
+| Commandes | AdminLengowOrderController | orders/index.html.twig | ✅ Charge sans erreur |
+| Paramètres principaux | AdminLengowMainSettingController | main_setting/index.html.twig | ✅ Charge sans erreur |
+| Paramètres commandes | AdminLengowOrderSettingController | order_setting/index.html.twig | ✅ Charge sans erreur |
+| Toolbox | AdminLengowToolboxController | toolbox/index.html.twig | ✅ Charge sans erreur |
+| Mentions légales | AdminLengowLegalsController | legals/index.html.twig | ✅ Charge sans erreur |
+| Aide | AdminLengowHelpController | help/index.html.twig | ✅ Charge sans erreur |
+
+### 3. Approche de migration corrigée
+**Utilisation des ModuleAdminController avec Twig** :
+- Les contrôleurs restent dans `controllers/admin/` (structure PrestaShop standard)
+- Les URLs legacy fonctionnent : `?controller=AdminLengowHome&token=...`
+- Les contrôleurs préparent les données directement dans `initContent()`
+- Les templates Twig sont chargés via `setTemplate('module:lengow/...')`
+- Pas de redirections - rendu direct avec Twig
+
+### 4. Templates Twig créés
+Structure de base créée :
+- `_partials/base.html.twig` - Layout de base avec assets CSS/JS
+- `_partials/header.html.twig` - Navigation principale (migrée de Smarty)
+- `_partials/footer.html.twig` - Footer
+- Templates individuels pour chaque page (structure minimale)
+
+### 5. Contrôleurs Symfony (optionnels)
+Les contrôleurs Symfony dans `src/Controller/` peuvent être utilisés pour :
+- Routes API personnalisées
+- Actions AJAX spécifiques
+- Endpoints REST
+Ils ne sont pas utilisés pour les pages admin principales.
+
+## Ce qui reste à faire 📋
+
+### 1. Actions AJAX et formulaires à réimplémenter
+
+#### Actions critiques manquantes (à restaurer) :
+**AdminLengowHomeController** :
+- `go_to_credentials` - Affichage du formulaire de connexion
+- `connect_cms` - Connexion au CMS Lengow
+- `go_to_catalog` - Sélection des catalogues
+- `link_catalogs` - Liaison des catalogues
+
+**AdminLengowDashboardController** :
+- `remind_me_later` - Report de la notification de mise à jour
+
+**AdminLengowFeedController, AdminLengowOrderController, etc.** :
+- Diverses actions AJAX pour filtres, exports, imports, etc.
+
+**Solutions possibles** :
+1. Créer des méthodes AJAX séparées dans les contrôleurs admin
+2. Utiliser les contrôleurs Symfony pour gérer les endpoints AJAX
+3. Ajouter des méthodes `processAjax()` dans les contrôleurs admin
+
+### 2. Variables template manquantes
+
+Variables de `prepareDisplay()` non assignées :
+- `showPluginUpgradeModal`
+- `lengowModalAjaxLink`
+- `helpCenterLink`, `updateGuideLink`, `changelogLink`, `supportLink`
+- `multiShop`, `debugMode`
+- `isNewMerchant`
+- Et autres variables spécifiques à chaque page
+
+### 3. Migration complète du contenu des templates
+
+### 1. Migration complète du contenu des templates
+Les templates Twig actuels contiennent des placeholders. Il faut migrer :
+
+#### Dashboard (`views/templates/admin/lengow_dashboard/`)
+- Statistiques et métriques
+- Graphiques de performance
+- Alertes et notifications
+- État du compte marchand
+
+#### Home/Connexion (`views/templates/admin/lengow_home/`)
+- Formulaire de connexion API
+- Sélection des catalogues
+- Gestion des credentials
+- Workflow de configuration initiale
+- Templates AJAX : `connection_*.tpl` → `.html.twig`
+
+#### Feed/Produits (`views/templates/admin/lengow_feed/`)
+- Liste des produits exportables
+- Filtres et sélection
+- Configuration des flux
+- Options d'export
+
+#### Commandes (`views/templates/admin/lengow_order/`)
+- Table des commandes Lengow
+- Filtres et recherche
+- Actions sur commandes (ré-import, renvoi)
+- Détails des erreurs
+
+#### Paramètres (`views/templates/admin/lengow_main_setting/`)
+- Formulaires de configuration
+- Gestion des logs
+- Paramètres globaux
+- Désinstallation
+
+#### Paramètres commandes (`views/templates/admin/lengow_order_setting/`)
+- Mapping marketplace/statuts
+- Configuration transporteurs
+- Règles de gestion des commandes
+
+#### Toolbox (`views/templates/admin/lengow_toolbox/`)
+- Outils de diagnostic
+- Logs système
+- Tests de connectivité
+
+#### Legals & Help
+- Contenu statique à migrer
+
+### 2. Migration de la logique Smarty vers Twig
+Remplacer les constructions Smarty :
+```smarty
+{$variable|escape:'htmlall':'UTF-8'} → {{ variable|escape('html') }}
+{if $condition}...{/if} → {% if condition %}...{% endif %}
+{foreach $items as $item}...{/foreach} → {% for item in items %}...{% endfor %}
+{include file='...'} → {% include '@Modules/lengow/...' %}
+```
+
+### 3. Gestion des assets
+- Vérifier que tous les JS sont chargés correctement
+- S'assurer que les chemins des assets fonctionnent
+- Tester les appels AJAX depuis les nouveaux templates
+
+### 4. Formulaires Symfony
+Pour une intégration complète PrestaShop 9 :
+- Créer des FormTypes Symfony pour les formulaires
+- Remplacer les formulaires HTML legacy
+- Gérer la validation côté serveur avec Symfony
+
+### 5. Services et injection de dépendances
+Améliorer l'architecture :
+- Créer des services Symfony pour la logique métier
+- Injecter les dépendances dans les contrôleurs
+- Utiliser le container de services PrestaShop
+
+### 6. Tests
+- Tester l'installation du module
+- Tester la navigation entre pages
+- Tester les actions AJAX
+- Tester les formulaires
+- Tester sur PrestaShop 8 et 9
+
+## Approche recommandée pour finaliser
+
+### Option 1 : Migration progressive (recommandée)
+1. Commencer par les pages les plus simples (Legals, Help)
+2. Migrer ensuite les pages avec formulaires (Settings)
+3. Finir par les pages complexes avec AJAX (Dashboard, Orders)
+4. Tester page par page
+
+### Option 2 : Migration par composant
+1. Migrer tous les headers/footers
+2. Migrer tous les formulaires
+3. Migrer toutes les tables de données
+4. Migrer les modales et popups
+
+## Structure des fichiers après migration complète
+
+```
+lengow/
+├── config/
+│ └── routes.yml # Routes Symfony ✅
+├── controllers/admin/ # Legacy redirects ✅
+│ └── AdminLengow*.php
+├── src/Controller/ # Contrôleurs Symfony ✅
+│ ├── AdminDashboardController.php
+│ ├── AdminHomeController.php
+│ └── ...
+├── views/
+│ ├── templates/admin/
+│ │ ├── _partials/ # Composants réutilisables ✅
+│ │ ├── dashboard/ # À compléter 📋
+│ │ ├── home/ # À compléter 📋
+│ │ ├── feed/ # À compléter 📋
+│ │ └── ...
+│ ├── css/ # Assets existants ✅
+│ └── js/ # Assets existants ✅
+└── classes/controllers/ # Business logic (conservée) ✅
+ └── Lengow*Controller.php
+```
+
+## Notes importantes
+
+### Compatibilité
+- Le code actuel fonctionne avec PrestaShop 1.7.8 à 9.99.99
+- Les templates Smarty legacy sont toujours présents et peuvent servir de référence
+- L'approche progressive permet de garder le module fonctionnel pendant la migration
+
+### Dépendances
+- PrestaShop 9 utilise Symfony 6.x
+- Twig 3.x est inclus dans PrestaShop 9
+- Les annotations `@AdminSecurity` sont utilisées pour les permissions
+
+### Performance
+- Les contrôleurs Symfony sont plus performants que les legacy
+- Twig est compilé et mis en cache
+- La séparation des responsabilités améliore la maintenabilité
+
+## Conclusion
+
+La fondation Symfony/Twig est en place et fonctionnelle. Le module peut maintenant être étendu progressivement en migrant le contenu des templates. L'architecture actuelle permet :
+
+1. ✅ Routing moderne avec Symfony
+2. ✅ Compatibilité PrestaShop 9
+3. ✅ Navigation entre pages fonctionnelle
+4. ✅ Réutilisation de la logique métier existante
+5. 📋 Templates à enrichir avec le contenu des pages
+
+La migration peut se faire de manière incrémentale, page par page, tout en maintenant la fonctionnalité du module.
diff --git a/README.md b/README.md
index 1b304e53..9b06846d 100755
--- a/README.md
+++ b/README.md
@@ -86,6 +86,29 @@ Once the translations are finished, just run the translation update script in `l
The plugin is translated into English, French, Spanish and Italian.
+## Architecture
+
+### Symfony Controllers (PrestaShop 9 Compatible)
+
+Starting from version 3.9.4, the module uses Symfony controllers and Twig templates for PrestaShop 9 compatibility.
+
+**Available Routes:**
+- `/modules/lengow/dashboard` - Dashboard page
+- `/modules/lengow/home` - Connection/Home page
+- `/modules/lengow/feed` - Product catalog management
+- `/modules/lengow/orders` - Order list
+- `/modules/lengow/settings` - Main settings
+- `/modules/lengow/order-settings` - Order-specific settings
+- `/modules/lengow/toolbox` - Diagnostic tools
+- `/modules/lengow/legals` - Legal information
+- `/modules/lengow/help` - Help and documentation
+
+**Controllers Location:** `src/Controller/Admin*.php`
+**Templates Location:** `views/templates/admin/*/index.html.twig`
+**Routes Definition:** `config/routes.yml`
+
+The legacy ModuleAdminController classes in `controllers/admin/` now redirect to the Symfony routes.
+
## Changelog
The changelog and all available commits are located under [CHANGELOG](CHANGELOG).
diff --git a/SYMFONY_MIGRATION_PLAN.md b/SYMFONY_MIGRATION_PLAN.md
new file mode 100644
index 00000000..2df89151
--- /dev/null
+++ b/SYMFONY_MIGRATION_PLAN.md
@@ -0,0 +1,211 @@
+# Migration complète vers Symfony/Twig - Plan détaillé
+
+> **📖 Guide Complet** : Voir [`SYMFONY_TWIG_MIGRATION_GUIDE.md`](./SYMFONY_TWIG_MIGRATION_GUIDE.md) pour un guide d'implémentation détaillé avec exemples de code complets.
+
+## État actuel (analysé le 2026-01-05)
+
+### ✅ Ce qui existe déjà
+- **10 contrôleurs Symfony** dans `src/Controller/` (squelettes)
+- **Routes Symfony** définies dans `config/routes.yml`
+- **Corrections API PrestaShop 9** : formatPrice(), compatibilité 9.99.99
+- **Infrastructure Twig de base** : Layout, header, footer créés
+- **Guide de migration complet** : `SYMFONY_TWIG_MIGRATION_GUIDE.md`
+
+### ❌ Ce qui manque / à finaliser
+- **37 templates Smarty** (.tpl) à convertir en Twig (.twig)
+- **Contrôleurs Symfony incomplets** : ne retournent pas de Response complète
+- **9 contrôleurs legacy** AdminLengow* toujours actifs
+- **Tests** de compatibilité PS 8+/9
+
+### 📚 Ressources Disponibles
+- **Guide détaillé** : `SYMFONY_TWIG_MIGRATION_GUIDE.md` - Guide complet avec exemples
+- **Exemples de contrôleurs** : `src/Controller/AdminDashboardController.php`
+- **Templates de base** : `views/templates/twig/admin/_partials/`
+- **Routes** : `config/routes.yml`
+
+## Plan de migration par phase
+
+### Phase 1 : Infrastructure Twig ✅ (Prioritaire)
+**Objectif** : Créer l'architecture de base Twig
+
+#### 1.1 Templates de base
+```
+views/templates/twig/admin/
+├── _partials/
+│ ├── base.html.twig # Layout principal
+│ ├── header.html.twig # Header avec navigation
+│ └── footer.html.twig # Footer
+├── dashboard/
+│ └── index.html.twig
+├── home/
+│ └── index.html.twig
+├── feed/
+│ └── index.html.twig
+├── orders/
+│ └── index.html.twig
+├── settings/
+│ └── index.html.twig
+├── order_settings/
+│ └── index.html.twig
+├── toolbox/
+│ └── index.html.twig
+├── legals/
+│ └── index.html.twig
+└── help/
+ └── index.html.twig
+```
+
+#### 1.2 Mapping templates Smarty → Twig
+| Template Smarty actuel | Template Twig cible |
+|------------------------|---------------------|
+| `views/templates/admin/lengow_home/layout.tpl` | `views/templates/twig/admin/home/index.html.twig` |
+| `views/templates/admin/lengow_feed/layout.tpl` | `views/templates/twig/admin/feed/index.html.twig` |
+| `views/templates/admin/lengow_order/layout.tpl` | `views/templates/twig/admin/orders/index.html.twig` |
+| ... | ... |
+
+### Phase 2 : Contrôleurs Symfony complets
+**Objectif** : Finaliser les 10 contrôleurs pour qu'ils retournent des Response Twig
+
+#### 2.1 Pattern à suivre
+```php
+public function indexAction(Request $request): Response
+{
+ // 1. Récupérer les données métier
+ $lengowController = new \LengowHomeController();
+ $data = $lengowController->getData(); // ou équivalent
+
+ // 2. Préparer les variables Twig
+ $templateData = [
+ 'locale' => new \LengowTranslation(),
+ 'localeIsoCode' => \Tools::substr(\Context::getContext()->language->language_code, 0, 2),
+ 'multiShop' => \Shop::isFeatureActive(),
+ 'debugMode' => \LengowConfiguration::debugModeIsActive(),
+ // ... autres variables
+ ];
+
+ // 3. Rendre le template Twig
+ return $this->render('@Modules/lengow/views/templates/twig/admin/home/index.html.twig', $templateData);
+}
+```
+
+#### 2.2 Contrôleurs à finaliser
+- [ ] `AdminHomeController` - Home/Connexion
+- [ ] `AdminDashboardController` - Tableau de bord
+- [ ] `AdminFeedController` - Catalogue produits
+- [ ] `AdminOrdersController` - Liste commandes
+- [ ] `AdminOrderController` - Détail commande (déjà avancé)
+- [ ] `AdminMainSettingController` - Configuration
+- [ ] `AdminOrderSettingController` - Paramètres commandes
+- [ ] `AdminToolboxController` - Outils diagnostic
+- [ ] `AdminLegalsController` - Mentions légales
+- [ ] `AdminHelpController` - Aide
+
+### Phase 3 : Migration des templates
+**Objectif** : Convertir chaque template Smarty en Twig
+
+#### 3.1 Syntaxe Smarty → Twig
+| Smarty | Twig |
+|--------|------|
+| `{$variable}` | `{{ variable }}` |
+| `{if $condition}...{/if}` | `{% if condition %}...{% endif %}` |
+| `{foreach $array as $item}...{/foreach}` | `{% for item in array %}...{% endfor %}` |
+| `{l s='Text'}` | `{{ 'Text'\|trans({}, 'Modules.Lengow.Admin') }}` |
+| `{$variable.property}` | `{{ variable.property }}` |
+| `{include file='...'}` | `{% include '...' %}` |
+
+#### 3.2 Ordre de migration suggéré
+1. **Header/Footer/Layout** (commun à toutes les pages)
+2. **Dashboard** (page principale, référence)
+3. **Home** (connexion, critère MVP)
+4. **Feed, Orders, Settings** (pages principales)
+5. **Toolbox, Legals, Help** (pages secondaires)
+
+### Phase 4 : Actions AJAX
+**Objectif** : Migrer les endpoints AJAX vers Symfony
+
+#### 4.1 Pattern AJAX
+```php
+public function ajaxAction(Request $request): JsonResponse
+{
+ $action = $request->request->get('action') ?? $request->query->get('action');
+
+ switch ($action) {
+ case 'connect_cms':
+ return $this->connectCmsAction($request);
+ case 'link_catalogs':
+ return $this->linkCatalogsAction($request);
+ // ... autres actions
+ default:
+ return new JsonResponse(['error' => 'Unknown action'], 400);
+ }
+}
+```
+
+#### 4.2 Actions à migrer
+- **Home** : `connect_cms`, `link_catalogs`, `go_to_credentials`
+- **Dashboard** : `remind_me_later`, `refresh_status`
+- **Feed** : export actions, filtres
+- **Orders** : actions sur commandes
+- **Settings** : sauvegarde configuration
+
+### Phase 5 : Nettoyage et tests
+**Objectif** : Supprimer le code legacy, tester
+
+#### 5.1 À supprimer
+- [ ] `controllers/admin/AdminLengow*.php` (9 fichiers)
+- [ ] `views/templates/admin/lengow_*/` (37 fichiers .tpl)
+- [ ] Références Smarty dans le code
+
+#### 5.2 Tests
+- [ ] Accès à chaque page via route Symfony
+- [ ] Navigation entre pages
+- [ ] Actions AJAX fonctionnelles
+- [ ] Compatibilité PrestaShop 8.x
+- [ ] Compatibilité PrestaShop 9.x
+- [ ] Pas de régression PS 1.7.8+
+
+## Prochaines étapes immédiates
+
+### Étape 1 : Base Twig (urgent)
+1. Créer `base.html.twig`
+2. Créer `header.html.twig` et `footer.html.twig`
+3. Créer templates squelettes pour les 9 pages
+
+### Étape 2 : Contrôleur Dashboard complet
+1. Finaliser `AdminDashboardController::indexAction()`
+2. Créer template Twig Dashboard complet
+3. Tester l'accès via route Symfony
+
+### Étape 3 : Réplication
+1. Répliquer le pattern Dashboard pour les 8 autres pages
+2. Adapter le contenu spécifique de chaque page
+
+## Notes techniques
+
+### Compatibilité PrestaShop
+- **PS 1.7.6+** : Twig disponible, mais Smarty toujours utilisé pour modules
+- **PS 8.x** : Support complet Twig + Symfony pour modules
+- **PS 9.x** : Twig/Symfony devient le standard
+
+### Limitations connues
+- `ModuleAdminController` ne supporte PAS Twig nativement
+- Il FAUT utiliser `FrameworkBundleAdminController` (Symfony)
+- Routes doivent être déclarées dans `config/routes.yml`
+- Templates Twig doivent utiliser namespace `@Modules/lengow/...`
+
+### Avantages post-migration
+- ✅ Code moderne et maintenable
+- ✅ Compatible PS 8+ et 9 nativement
+- ✅ Meilleure séparation logique/présentation
+- ✅ Syntaxe Twig plus claire que Smarty
+- ✅ Routing Symfony plus flexible
+
+## Estimation
+- **Temps total** : 3-5 jours développement + 2 jours tests
+- **Complexité** : Élevée (refonte complète architecture frontend)
+- **Risque** : Moyen (bien cadré, PrestaShop supporte Twig/Symfony)
+
+---
+
+**Dernière mise à jour** : 2026-01-05
+**Statut** : Migration en cours - Phase 1 infrastructure Twig
diff --git a/SYMFONY_TWIG_MIGRATION_GUIDE.md b/SYMFONY_TWIG_MIGRATION_GUIDE.md
new file mode 100644
index 00000000..e106171e
--- /dev/null
+++ b/SYMFONY_TWIG_MIGRATION_GUIDE.md
@@ -0,0 +1,1198 @@
+# Guide de Migration Symfony/Twig pour Module Lengow PrestaShop
+
+## 📋 Vue d'ensemble
+
+Ce guide fournit une approche complète pour migrer le module Lengow de l'architecture Smarty (`.tpl`) vers Symfony/Twig pour PrestaShop 8+ et 9.
+
+**Compatibilité** :
+- ✅ PrestaShop 1.7.6+ (support Twig partiel avec coexistence Smarty)
+- ✅ PrestaShop 8.x (support Twig/Symfony complet)
+- ✅ PrestaShop 9.x (Twig/Symfony standard)
+
+---
+
+## 🏗️ Architecture Actuelle vs Nouvelle Architecture
+
+### Architecture Actuelle (Smarty)
+```
+controllers/admin/AdminLengow*.php (ModuleAdminController)
+ ↓ délègue à
+classes/controllers/Lengow*Controller.php (logique métier)
+ ↓ rend
+views/templates/admin/**/*.tpl (templates Smarty)
+```
+
+### Nouvelle Architecture (Symfony/Twig)
+```
+src/Controller/Admin*.php (FrameworkBundleAdminController)
+ ↓ utilise directement
+classes/models/* (modèles de données)
+ ↓ rend
+views/templates/twig/admin/**/*.html.twig (templates Twig)
+```
+
+---
+
+## 📂 Structure des Fichiers
+
+### Contrôleurs Symfony
+Emplacement : `src/Controller/`
+
+**Convention de nommage** :
+- `Admin{Page}Controller.php` (ex: `AdminDashboardController.php`)
+- Namespace : `PrestaShop\Module\Lengow\Controller`
+
+### Templates Twig
+Emplacement : `views/templates/twig/admin/`
+
+**Structure recommandée** :
+```
+views/templates/twig/admin/
+├── _partials/
+│ ├── base.html.twig # Layout principal
+│ ├── header.html.twig # En-tête avec navigation
+│ └── footer.html.twig # Pied de page
+├── dashboard/
+│ └── index.html.twig
+├── home/
+│ └── index.html.twig
+├── feed/
+│ ├── index.html.twig
+│ ├── product_list.html.twig
+│ └── ...
+└── ...
+```
+
+### Routes Symfony
+Emplacement : `config/routes.yml`
+
+---
+
+## 🔧 Migration Étape par Étape
+
+### Étape 1 : Créer un Contrôleur Symfony
+
+#### Exemple Complet : Dashboard Controller
+
+**Fichier** : `src/Controller/AdminDashboardController.php`
+
+```php
+version, '>')) {
+ $pluginIsUpToDate = false;
+ }
+
+ // 3. Traiter les actions (si nécessaire)
+ $action = $request->query->get('action');
+ if ($action === 'refresh_status') {
+ \LengowSync::getStatusAccount(true);
+ return $this->redirectToRoute('lengow_admin_dashboard');
+ }
+
+ // 4. Générer les URLs pour les actions
+ $refreshStatusUrl = $this->generateUrl('lengow_admin_dashboard', ['action' => 'refresh_status']);
+
+ // 5. Préparer les variables pour le template
+ $templateVars = [
+ // Services Lengow
+ 'locale' => $locale,
+ 'lengow_link' => $lengowLink,
+
+ // Informations du module
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+
+ // Données métier
+ 'merchantStatus' => $merchantStatus,
+ 'pluginData' => $pluginData,
+ 'pluginIsUpToDate' => $pluginIsUpToDate,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+
+ // Configuration UI
+ 'displayToolbar' => 1,
+ 'current_controller' => 'LengowDashboardController',
+
+ // URLs d'actions
+ 'refresh_status' => $refreshStatusUrl,
+ ];
+
+ // 6. Rendre le template Twig
+ return $this->render('@Modules/lengow/views/templates/twig/admin/dashboard/index.html.twig', $templateVars);
+ }
+
+ /**
+ * Handle AJAX action: remind me later for plugin update
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowDashboard')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function remindMeLaterAction(Request $request): JsonResponse
+ {
+ $timestamp = time() + (7 * 86400); // 7 days
+ \LengowConfiguration::updateGlobalValue(
+ \LengowConfiguration::LAST_UPDATE_PLUGIN_MODAL,
+ $timestamp
+ );
+
+ return new JsonResponse(['success' => true]);
+ }
+
+ /**
+ * Handle AJAX action: get dashboard statistics
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowDashboard')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function getStatisticsAction(Request $request): JsonResponse
+ {
+ $statistics = [
+ 'pending_orders' => \LengowOrder::countOrderToBeSent(),
+ 'errors' => \LengowMain::getLastImportErrors(),
+ 'last_import' => \LengowImport::getLastImport(),
+ ];
+
+ return new JsonResponse($statistics);
+ }
+}
+```
+
+#### Points Clés du Contrôleur Symfony
+
+1. **Hérite de `FrameworkBundleAdminController`** : Donne accès aux services Symfony
+2. **Annotations `@AdminSecurity`** : Gère les permissions PrestaShop
+3. **Type hints stricts** : `Request`, `Response`, `JsonResponse`
+4. **Méthodes d'action** : Suffixe `Action` (ex: `indexAction`, `remindMeLaterAction`)
+5. **Rendu Twig** : `$this->render('@Modules/lengow/...')`
+6. **Génération d'URLs** : `$this->generateUrl('route_name', ['param' => 'value'])`
+7. **Redirections** : `$this->redirectToRoute('route_name')`
+
+---
+
+### Étape 2 : Définir les Routes Symfony
+
+**Fichier** : `config/routes.yml`
+
+```yaml
+# Dashboard routes
+lengow_admin_dashboard:
+ path: /lengow/dashboard
+ methods: [GET, POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::indexAction'
+ _legacy_controller: AdminLengowDashboard
+ _legacy_link: AdminLengowDashboard
+
+lengow_admin_dashboard_remind:
+ path: /lengow/dashboard/remind-me-later
+ methods: [POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::remindMeLaterAction'
+
+lengow_admin_dashboard_stats:
+ path: /lengow/dashboard/statistics
+ methods: [GET]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::getStatisticsAction'
+
+# Home/Connection routes
+lengow_admin_home:
+ path: /lengow/home
+ methods: [GET, POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::indexAction'
+ _legacy_controller: AdminLengowHome
+ _legacy_link: AdminLengowHome
+
+lengow_admin_home_auth:
+ path: /lengow/home/authenticate
+ methods: [POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::authenticateAction'
+
+# Feed/Catalog routes
+lengow_admin_feed:
+ path: /lengow/feed
+ methods: [GET]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::indexAction'
+ _legacy_controller: AdminLengowFeed
+ _legacy_link: AdminLengowFeed
+
+lengow_admin_feed_export:
+ path: /lengow/feed/export
+ methods: [POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::exportAction'
+
+# Orders routes
+lengow_admin_order:
+ path: /lengow/orders
+ methods: [GET]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::indexAction'
+ _legacy_controller: AdminLengowOrder
+ _legacy_link: AdminLengowOrder
+
+lengow_admin_order_import:
+ path: /lengow/orders/import
+ methods: [POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::importAction'
+
+# Settings routes
+lengow_admin_main_setting:
+ path: /lengow/settings
+ methods: [GET, POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminMainSettingController::indexAction'
+ _legacy_controller: AdminLengowMainSetting
+ _legacy_link: AdminLengowMainSetting
+
+lengow_admin_order_setting:
+ path: /lengow/settings/orders
+ methods: [GET, POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderSettingController::indexAction'
+ _legacy_controller: AdminLengowOrderSetting
+ _legacy_link: AdminLengowOrderSetting
+
+# Toolbox route
+lengow_admin_toolbox:
+ path: /lengow/toolbox
+ methods: [GET, POST]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminToolboxController::indexAction'
+ _legacy_controller: AdminLengowToolbox
+ _legacy_link: AdminLengowToolbox
+
+# Legals route
+lengow_admin_legals:
+ path: /lengow/legals
+ methods: [GET]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminLegalsController::indexAction'
+ _legacy_controller: AdminLengowLegals
+ _legacy_link: AdminLengowLegals
+
+# Help route
+lengow_admin_help:
+ path: /lengow/help
+ methods: [GET]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminHelpController::indexAction'
+ _legacy_controller: AdminLengowHelp
+ _legacy_link: AdminLengowHelp
+```
+
+#### Convention de Nommage des Routes
+
+- **Route principale** : `lengow_admin_{page}` (ex: `lengow_admin_dashboard`)
+- **Actions AJAX** : `lengow_admin_{page}_{action}` (ex: `lengow_admin_dashboard_remind`)
+- **Paramètres legacy** : `_legacy_controller` et `_legacy_link` pour compatibilité avec les liens existants
+
+---
+
+### Étape 3 : Créer les Templates Twig
+
+#### Template de Base (Layout Principal)
+
+**Fichier** : `views/templates/twig/admin/_partials/base.html.twig`
+
+```twig
+
+
+
+
+
+ {% block title %}Lengow{% endblock %} - PrestaShop
+
+ {# Styles Lengow #}
+
+
+
+ {% block stylesheets %}{% endblock %}
+
+
+ {# Header avec navigation #}
+ {% include '@Modules/lengow/views/templates/twig/admin/_partials/header.html.twig' %}
+
+ {# Contenu principal #}
+
+ {% block content %}{% endblock %}
+
+
+ {# Footer #}
+ {% include '@Modules/lengow/views/templates/twig/admin/_partials/footer.html.twig' %}
+
+ {# Scripts Lengow #}
+
+
+
+ {% block javascripts %}{% endblock %}
+
+
+```
+
+#### Template Header
+
+**Fichier** : `views/templates/twig/admin/_partials/header.html.twig`
+
+```twig
+
+```
+
+#### Template de Page : Dashboard
+
+**Fichier** : `views/templates/twig/admin/dashboard/index.html.twig`
+
+```twig
+{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %}
+
+{% block title %}{{ locale.t('menu.dashboard') }}{% endblock %}
+
+{% block content %}
+
+
{{ locale.t('menu.dashboard') }}
+
+ {# Account Status Section #}
+
+
{{ locale.t('dashboard.account_status') }}
+
+ {% if merchantStatus %}
+
+
{{ merchantStatus.message }}
+
+ {% if merchantStatus.type == 'success' %}
+
+
{{ locale.t('dashboard.account_id') }}: {{ merchantStatus.account_id }}
+
{{ locale.t('dashboard.catalog_ids') }}: {{ merchantStatus.catalog_ids|join(', ') }}
+
+ {% endif %}
+
+
+
+ {{ locale.t('dashboard.refresh_status') }}
+
+ {% else %}
+
{{ locale.t('dashboard.no_account_data') }}
+ {% endif %}
+
+
+ {# Statistics Section #}
+
+
{{ locale.t('dashboard.statistics') }}
+
+
+
+
{{ locale.t('dashboard.pending_orders') }}
+
{{ total_pending_order }}
+
+
+
+
{{ locale.t('dashboard.plugin_version') }}
+
+ {{ pluginData.version|default('N/A') }}
+ {% if pluginIsUpToDate == false %}
+ {{ locale.t('dashboard.update_available') }}
+ {% endif %}
+
+
+
+
+
+ {# Plugin Update Modal (si nécessaire) #}
+ {% if pluginIsUpToDate == false %}
+
+
+
{{ locale.t('dashboard.update_modal_title') }}
+
{{ locale.t('dashboard.update_modal_message', {'version': pluginData.version}) }}
+
+
+
+
+ {% endif %}
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
+```
+
+---
+
+## 🔄 Conversion Smarty → Twig
+
+### Syntaxe Comparative
+
+| Smarty | Twig | Description |
+|--------|------|-------------|
+| `{$variable}` | `{{ variable }}` | Afficher une variable |
+| `{if $condition}...{/if}` | `{% if condition %}...{% endif %}` | Condition |
+| `{foreach $items as $item}...{/foreach}` | `{% for item in items %}...{% endfor %}` | Boucle |
+| `{include file='header.tpl'}` | `{% include 'header.html.twig' %}` | Inclusion |
+| `{$locale->t('key')}` | `{{ locale.t('key') }}` | Traduction |
+| `{$array\|@count}` | `{{ array\|length }}` | Longueur tableau |
+| `{$price\|number_format:2}` | `{{ price\|number_format(2) }}` | Formatage nombre |
+| `{$text\|escape:'html'}` | `{{ text\|e }}` ou `{{ text\|escape }}` | Échappement HTML |
+
+### Exemples de Conversion
+
+#### Exemple 1 : Liste de Produits
+
+**Smarty (avant)** :
+```smarty
+{if $products && count($products) > 0}
+
+
+
+ {$locale->t('product.name')}
+ {$locale->t('product.price')}
+ {$locale->t('product.stock')}
+
+
+
+ {foreach $products as $product}
+
+ {$product.name|escape:'html'}
+ {$product.price|number_format:2} €
+ {$product.stock}
+
+ {/foreach}
+
+
+{else}
+ {$locale->t('product.no_products')}
+{/if}
+```
+
+**Twig (après)** :
+```twig
+{% if products and products|length > 0 %}
+
+
+
+ {{ locale.t('product.name') }}
+ {{ locale.t('product.price') }}
+ {{ locale.t('product.stock') }}
+
+
+
+ {% for product in products %}
+
+ {{ product.name|e }}
+ {{ product.price|number_format(2) }} €
+ {{ product.stock }}
+
+ {% endfor %}
+
+
+{% else %}
+ {{ locale.t('product.no_products') }}
+{% endif %}
+```
+
+#### Exemple 2 : Formulaire avec Actions
+
+**Smarty (avant)** :
+```smarty
+
+```
+
+**Twig (après)** :
+```twig
+
+```
+
+---
+
+## 🎯 Gestion des Actions AJAX
+
+### Dans le Contrôleur Symfony
+
+```php
+/**
+ * AJAX action example: export products
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowFeed')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+public function exportProductsAction(Request $request): JsonResponse
+{
+ try {
+ // Récupérer les paramètres
+ $format = $request->request->get('format', 'csv');
+ $limit = (int) $request->request->get('limit', 0);
+
+ // Exécuter l'export
+ $result = \LengowExport::exec([
+ 'format' => $format,
+ 'limit' => $limit,
+ ]);
+
+ if ($result) {
+ return new JsonResponse([
+ 'success' => true,
+ 'message' => 'Export completed successfully',
+ 'file_url' => $result['file_url'],
+ ]);
+ }
+
+ return new JsonResponse([
+ 'success' => false,
+ 'error' => 'Export failed',
+ ], 400);
+
+ } catch (\Exception $e) {
+ return new JsonResponse([
+ 'success' => false,
+ 'error' => $e->getMessage(),
+ ], 500);
+ }
+}
+```
+
+### Appel AJAX depuis Twig/JavaScript
+
+```twig
+{% block javascripts %}
+
+{% endblock %}
+```
+
+---
+
+## 📝 Checklist de Migration par Page
+
+### ✅ Dashboard
+- [ ] Créer `src/Controller/AdminDashboardController.php`
+- [ ] Ajouter routes dans `config/routes.yml`
+- [ ] Créer `views/templates/twig/admin/dashboard/index.html.twig`
+- [ ] Tester affichage et actions AJAX
+- [ ] Supprimer `controllers/admin/AdminLengowDashboardController.php` (legacy)
+- [ ] Supprimer `views/templates/admin/dashboard/*.tpl`
+
+### ⏳ Home/Connection
+- [ ] Créer `src/Controller/AdminHomeController.php`
+- [ ] Ajouter routes dans `config/routes.yml`
+- [ ] Créer `views/templates/twig/admin/home/index.html.twig`
+- [ ] Gérer formulaire d'authentification
+- [ ] Tester connexion/déconnexion
+- [ ] Supprimer fichiers legacy
+
+### ⏳ Feed/Products
+- [ ] Créer `src/Controller/AdminFeedController.php`
+- [ ] Ajouter routes (liste, export, etc.)
+- [ ] Créer templates Twig (index, product_list, export_form)
+- [ ] Migrer logique d'export
+- [ ] Tester export CSV/XML
+- [ ] Supprimer fichiers legacy
+
+### ⏳ Orders
+- [ ] Créer `src/Controller/AdminOrderController.php`
+- [ ] Ajouter routes (liste, import, synchronisation)
+- [ ] Créer templates Twig
+- [ ] Migrer logique d'import
+- [ ] Tester import et synchronisation
+- [ ] Supprimer fichiers legacy
+
+### ⏳ Settings (Main)
+- [ ] Créer `src/Controller/AdminMainSettingController.php`
+- [ ] Ajouter routes
+- [ ] Créer templates Twig avec formulaires
+- [ ] Gérer sauvegarde configuration
+- [ ] Tester tous les paramètres
+- [ ] Supprimer fichiers legacy
+
+### ⏳ Settings (Orders)
+- [ ] Créer `src/Controller/AdminOrderSettingController.php`
+- [ ] Ajouter routes
+- [ ] Créer templates Twig
+- [ ] Gérer règles de commandes
+- [ ] Tester configuration
+- [ ] Supprimer fichiers legacy
+
+### ⏳ Toolbox
+- [ ] Créer `src/Controller/AdminToolboxController.php`
+- [ ] Ajouter routes
+- [ ] Créer templates Twig
+- [ ] Migrer outils de diagnostic
+- [ ] Tester fonctionnalités
+- [ ] Supprimer fichiers legacy
+
+### ⏳ Legals
+- [ ] Créer `src/Controller/AdminLegalsController.php`
+- [ ] Ajouter routes
+- [ ] Créer template Twig
+- [ ] Afficher mentions légales
+- [ ] Supprimer fichiers legacy
+
+### ⏳ Help
+- [ ] Créer `src/Controller/AdminHelpController.php`
+- [ ] Ajouter routes
+- [ ] Créer template Twig
+- [ ] Afficher aide/documentation
+- [ ] Supprimer fichiers legacy
+
+---
+
+## 🧪 Tests et Validation
+
+### Tests Manuels par Page
+
+1. **Navigation** : Vérifier que tous les liens de menu fonctionnent
+2. **Affichage** : Vérifier que toutes les données s'affichent correctement
+3. **Formulaires** : Tester la soumission et la validation
+4. **Actions AJAX** : Tester toutes les actions asynchrones
+5. **Traductions** : Vérifier que les traductions s'affichent
+6. **CSS/Layout** : Vérifier l'apparence sur différentes résolutions
+7. **Permissions** : Tester avec différents niveaux d'accès
+
+### Tests de Compatibilité
+
+- [ ] PrestaShop 1.7.8.x
+- [ ] PrestaShop 8.0.x
+- [ ] PrestaShop 8.1.x
+- [ ] PrestaShop 9.0.x
+
+### Tests de Régression
+
+Comparer avec l'ancienne version Smarty :
+- [ ] Toutes les fonctionnalités sont présentes
+- [ ] Les actions produisent les mêmes résultats
+- [ ] Les messages d'erreur sont cohérents
+- [ ] Les performances sont similaires ou meilleures
+
+---
+
+## 🚀 Déploiement et Rollback
+
+### Stratégie de Déploiement Progressif
+
+1. **Phase 1** : Déployer Dashboard et Home (pages principales)
+2. **Phase 2** : Déployer Feed et Orders (fonctionnalités métier)
+3. **Phase 3** : Déployer Settings et utilitaires
+4. **Phase 4** : Nettoyage complet (supprimer tous les fichiers legacy)
+
+### Plan de Rollback
+
+Si un problème survient :
+
+1. **Rollback partiel** : Restaurer uniquement la page problématique
+ ```bash
+ git checkout HEAD~1 -- src/Controller/AdminDashboardController.php
+ git checkout HEAD~1 -- views/templates/twig/admin/dashboard/
+ ```
+
+2. **Rollback complet** : Revenir à la version Smarty
+ ```bash
+ git revert
+ ```
+
+3. **Coexistence temporaire** : Garder les deux versions actives
+ - Routes Symfony pour nouvelles pages
+ - Contrôleurs legacy pour pages non migrées
+
+---
+
+## 📚 Ressources
+
+### Documentation PrestaShop
+- [PrestaShop Devdocs](https://devdocs.prestashop-project.org/)
+- [Symfony Controllers in PrestaShop](https://devdocs.prestashop-project.org/8/modules/concepts/controllers/admin-controllers/override-decorate-controller/)
+
+### Documentation Twig
+- [Twig Documentation](https://twig.symfony.com/doc/3.x/)
+- [Twig Filters](https://twig.symfony.com/doc/3.x/filters/index.html)
+- [Twig Functions](https://twig.symfony.com/doc/3.x/functions/index.html)
+
+### Documentation Symfony
+- [Symfony Routing](https://symfony.com/doc/current/routing.html)
+- [Symfony Controllers](https://symfony.com/doc/current/controller.html)
+- [Symfony Forms](https://symfony.com/doc/current/forms.html)
+
+---
+
+## 💡 Bonnes Pratiques
+
+### Contrôleurs
+
+1. **Une action = une responsabilité** : Séparer les actions complexes
+2. **Validation stricte** : Toujours valider les entrées utilisateur
+3. **Gestion d'erreurs** : Utiliser try/catch pour les opérations risquées
+4. **Logs** : Logger les actions importantes
+5. **Permissions** : Toujours utiliser `@AdminSecurity`
+
+### Templates Twig
+
+1. **Échappement** : Toujours échapper les variables (`|e`)
+2. **Réutilisation** : Créer des includes pour les composants réutilisables
+3. **Lisibilité** : Indenter correctement et commenter le code
+4. **Performance** : Éviter la logique complexe dans les templates
+5. **Assets** : Utiliser `lengowPathUri` pour les chemins relatifs
+
+### Organisation du Code
+
+1. **DRY** : Ne pas dupliquer le code
+2. **SOLID** : Respecter les principes SOLID
+3. **Naming** : Noms explicites et cohérents
+4. **Documentation** : Commenter les méthodes complexes
+5. **Git** : Commits atomiques et descriptifs
+
+---
+
+## ⚠️ Pièges Courants
+
+### Erreur 1 : Routes non trouvées
+**Symptôme** : 404 sur les nouvelles URLs
+**Solution** : Vérifier que `config/routes.yml` est bien chargé et que le cache est vidé
+
+### Erreur 2 : Templates non trouvés
+**Symptôme** : `Unable to load template`
+**Solution** : Vérifier le chemin complet : `@Modules/lengow/views/templates/twig/...`
+
+### Erreur 3 : Variables undefined dans Twig
+**Symptôme** : Erreur sur variable manquante
+**Solution** : S'assurer que toutes les variables sont passées au template depuis le contrôleur
+
+### Erreur 4 : Permissions refusées
+**Symptôme** : 403 Forbidden
+**Solution** : Vérifier l'annotation `@AdminSecurity` et les permissions utilisateur
+
+### Erreur 5 : AJAX ne fonctionne pas
+**Symptôme** : Requête AJAX échoue
+**Solution** : Vérifier la route, la méthode HTTP et le format de réponse (JsonResponse)
+
+---
+
+## 🎓 Exemple Complet : Page Feed/Products
+
+Voici un exemple complet d'une page migrée de bout en bout.
+
+### Contrôleur Symfony
+
+**Fichier** : `src/Controller/AdminFeedController.php`
+
+```php
+query->get('limit', 100)
+ );
+
+ // Get export formats
+ $formats = \LengowFeed::getAvailableFormats();
+
+ return $this->render('@Modules/lengow/views/templates/twig/admin/feed/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'products' => $products,
+ 'formats' => $formats,
+ 'current_controller' => 'LengowFeedController',
+ 'export_url' => $this->generateUrl('lengow_admin_feed_export'),
+ ]);
+ }
+
+ /**
+ * Export products
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowFeed')")
+ */
+ public function exportAction(Request $request): JsonResponse
+ {
+ try {
+ $format = $request->request->get('format', 'csv');
+ $limit = (int) $request->request->get('limit', 0);
+
+ $result = \LengowExport::exec([
+ 'format' => $format,
+ 'limit' => $limit,
+ ]);
+
+ if ($result) {
+ return new JsonResponse([
+ 'success' => true,
+ 'file_url' => $result['file_url'],
+ 'exported_count' => $result['count'],
+ ]);
+ }
+
+ return new JsonResponse([
+ 'success' => false,
+ 'error' => 'Export failed',
+ ], 400);
+
+ } catch (\Exception $e) {
+ return new JsonResponse([
+ 'success' => false,
+ 'error' => $e->getMessage(),
+ ], 500);
+ }
+ }
+}
+```
+
+### Template Twig
+
+**Fichier** : `views/templates/twig/admin/feed/index.html.twig`
+
+```twig
+{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %}
+
+{% block title %}{{ locale.t('menu.products') }}{% endblock %}
+
+{% block content %}
+
+
{{ locale.t('menu.products') }}
+
+ {# Export Form #}
+
+
{{ locale.t('feed.export_products') }}
+
+
+
+
+
+
+ {# Product List #}
+
+
{{ locale.t('feed.exportable_products') }}
+
+ {% if products|length > 0 %}
+
+
+
+ {{ locale.t('product.id') }}
+ {{ locale.t('product.name') }}
+ {{ locale.t('product.price') }}
+ {{ locale.t('product.stock') }}
+
+
+
+ {% for product in products %}
+
+ {{ product.id }}
+ {{ product.name|e }}
+ {{ product.price|number_format(2) }} €
+ {{ product.stock }}
+
+ {% endfor %}
+
+
+ {% else %}
+
{{ locale.t('feed.no_products') }}
+ {% endif %}
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
+```
+
+---
+
+## ✅ Conclusion
+
+Ce guide fournit une base solide pour migrer le module Lengow vers Symfony/Twig. L'approche recommandée est :
+
+1. **Commencer par une page simple** (Dashboard ou Legals)
+2. **Valider le fonctionnement complet** avant de passer à la suivante
+3. **Migrer progressivement** page par page
+4. **Tester à chaque étape** sur différentes versions de PrestaShop
+5. **Documenter les problèmes** rencontrés et solutions trouvées
+
+**Estimation de temps par page** :
+- Page simple (Dashboard, Legals, Help) : 4-6 heures
+- Page moyenne (Home, Orders) : 8-12 heures
+- Page complexe (Feed, Settings) : 12-20 heures
+
+**Total estimé** : 80-120 heures de développement + 20-30 heures de tests
+
+---
+
+**Dernière mise à jour** : 2026-01-05
+**Version du guide** : 1.0
+**Auteur** : GitHub Copilot pour Lengow
diff --git a/classes/models/LengowList.php b/classes/models/LengowList.php
index a7e617ae..6e206174 100755
--- a/classes/models/LengowList.php
+++ b/classes/models/LengowList.php
@@ -313,9 +313,10 @@ public function displayRow($item)
break;
case 'price':
if (isset($item['currency'])) {
- $value = Tools::displayPrice($item[$key], $this->getCurrencyByCode($item['currency']));
+ $currency = $this->getCurrencyByCode($item['currency']);
+ $value = $this->formatPrice($item[$key], $currency);
} else {
- $value = Tools::displayPrice($item[$key]);
+ $value = $this->formatPrice($item[$key]);
}
break;
case 'switch_product':
@@ -718,4 +719,41 @@ public function getTotal()
{
return $this->total;
}
+
+ /**
+ * Format price for display (PrestaShop 9 compatibility)
+ *
+ * @param float $price Price to format
+ * @param Currency|null $currency Currency object (optional)
+ *
+ * @return string Formatted price
+ */
+ protected function formatPrice($price, $currency = null)
+ {
+ if ($currency === null) {
+ $currency = $this->context->currency;
+ }
+
+ // Use PrestaShop's locale system if available (PS 1.7.6+)
+ if (method_exists($this->context, 'getCurrentLocale')) {
+ $locale = $this->context->getCurrentLocale();
+ if ($locale !== null) {
+ return $locale->formatPrice(
+ $price,
+ $currency->iso_code
+ );
+ }
+ }
+
+ // Fallback: manual formatting using currency properties
+ $blank = ($currency->format % 2 != 0) ? ' ' : '';
+ $decimals = (int)$currency->decimals;
+ $priceFormatted = number_format($price, $decimals, '.', '');
+
+ if ($currency->format % 2 == 0) {
+ return $currency->sign . $blank . $priceFormatted;
+ } else {
+ return $priceFormatted . $blank . $currency->sign;
+ }
+ }
}
diff --git a/classes/models/LengowProduct.php b/classes/models/LengowProduct.php
index cbb32649..5ed8ce4e 100755
--- a/classes/models/LengowProduct.php
+++ b/classes/models/LengowProduct.php
@@ -321,7 +321,7 @@ public function makeAttributes()
foreach ($combinations as $c) {
$attributeId = $c['id_product_attribute'];
$priceToConvert = Tools::convertPrice($c['price'], $this->context->currency);
- $price = Tools::displayPrice($priceToConvert, $this->context->currency);
+ $price = $this->formatPrice($priceToConvert, $this->context->currency);
if (array_key_exists($attributeId, $combArray)) {
$combArray[$attributeId]['attributes'][$c['group_name']] = [
$c['group_name'],
@@ -1077,4 +1077,41 @@ public static function getMaxImageType()
}
throw new LengowException(LengowMain::setLogMessage('log.export.error_cant_find_image_size'));
}
+
+ /**
+ * Format price for display (PrestaShop 9 compatibility)
+ *
+ * @param float $price Price to format
+ * @param Currency|null $currency Currency object (optional)
+ *
+ * @return string Formatted price
+ */
+ protected function formatPrice($price, $currency = null)
+ {
+ if ($currency === null) {
+ $currency = $this->context->currency;
+ }
+
+ // Use PrestaShop's locale system if available (PS 1.7.6+)
+ if (method_exists($this->context, 'getCurrentLocale')) {
+ $locale = $this->context->getCurrentLocale();
+ if ($locale !== null) {
+ return $locale->formatPrice(
+ $price,
+ $currency->iso_code
+ );
+ }
+ }
+
+ // Fallback: manual formatting using currency properties
+ $blank = ($currency->format % 2 != 0) ? ' ' : '';
+ $decimals = (int)$currency->decimals;
+ $priceFormatted = number_format($price, $decimals, '.', '');
+
+ if ($currency->format % 2 == 0) {
+ return $currency->sign . $blank . $priceFormatted;
+ } else {
+ return $priceFormatted . $blank . $currency->sign;
+ }
+ }
}
diff --git a/config/routes.yml b/config/routes.yml
deleted file mode 100644
index 2b39e009..00000000
--- a/config/routes.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-admin_orders_view:
- path: /sell/orders/{orderId}/view
- methods: [ GET, POST ]
- defaults:
- _disable_module_prefix: true
- _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::viewAction'
- _legacy_controller: AdminOrders
- _legacy_link: AdminOrders:vieworder
- _legacy_parameters:
- id_order: orderId
- options:
- expose: true
- requirements:
- orderId: \d+
-
-admin_orders_update_shipping:
- path: /sell/orders/{orderId}/shipping
- methods: [ POST ]
- defaults:
- _disable_module_prefix: true
- _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::updateShippingAction'
- _legacy_controller: AdminOrders
- requirements:
- orderId: \d+
-
-lengow_save_refund_reason:
- path: /admin/lengow/refund-reason/save
- methods: [ POST ]
- defaults:
- _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundReason'
-
-lengow_save_refund_mode:
- path: /admin/lengow/refund-mode/save
- methods: [ POST ]
- defaults:
- _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundMode'
diff --git a/config/routes.yml.example b/config/routes.yml.example
new file mode 100644
index 00000000..06870e9f
--- /dev/null
+++ b/config/routes.yml.example
@@ -0,0 +1,180 @@
+admin_orders_view:
+ path: /sell/orders/{orderId}/view
+ methods: [ GET, POST ]
+ defaults:
+ _disable_module_prefix: true
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::viewAction'
+ _legacy_controller: AdminOrders
+ _legacy_link: AdminOrders:vieworder
+ _legacy_parameters:
+ id_order: orderId
+ options:
+ expose: true
+ requirements:
+ orderId: \d+
+
+admin_orders_update_shipping:
+ path: /sell/orders/{orderId}/shipping
+ methods: [ POST ]
+ defaults:
+ _disable_module_prefix: true
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::updateShippingAction'
+ _legacy_controller: AdminOrders
+ requirements:
+ orderId: \d+
+
+lengow_save_refund_reason:
+ path: /admin/lengow/refund-reason/save
+ methods: [ POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundReason'
+
+lengow_save_refund_mode:
+ path: /admin/lengow/refund-mode/save
+ methods: [ POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderController::saveRefundMode'
+
+# Lengow Dashboard
+lengow_admin_dashboard:
+ path: /modules/lengow/dashboard
+ methods: [ GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::indexAction'
+ _legacy_controller: AdminLengowDashboard
+
+lengow_admin_dashboard_remind:
+ path: /modules/lengow/dashboard/remind-me-later
+ methods: [ POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminDashboardController::remindMeLaterAction'
+
+# Lengow Home/Connection
+lengow_admin_home:
+ path: /modules/lengow/home
+ methods: [ GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::indexAction'
+ _legacy_controller: AdminLengowHome
+
+lengow_admin_home_ajax:
+ path: /modules/lengow/home/ajax
+ methods: [ POST, GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminHomeController::ajaxAction'
+
+# Lengow Feed/Product
+lengow_admin_feed:
+ path: /modules/lengow/feed
+ methods: [ GET, POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::indexAction'
+ _legacy_controller: AdminLengowFeed
+
+lengow_admin_feed_ajax:
+ path: /modules/lengow/feed/ajax
+ methods: [ POST, GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminFeedController::ajaxAction'
+
+# Lengow Orders
+lengow_admin_order_index:
+ path: /modules/lengow/orders
+ methods: [ GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::indexAction'
+ _legacy_controller: AdminLengowOrder
+
+lengow_admin_order_load_table:
+ path: /modules/lengow/orders/load-table
+ methods: [ POST, GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::loadTableAction'
+
+lengow_admin_order_reimport:
+ path: /modules/lengow/orders/re-import
+ methods: [ POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::reImportAction'
+
+lengow_admin_order_resend:
+ path: /modules/lengow/orders/re-send
+ methods: [ POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::reSendAction'
+
+lengow_admin_order_import_all:
+ path: /modules/lengow/orders/import-all
+ methods: [ POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::importAllAction'
+
+lengow_admin_order_synchronize:
+ path: /modules/lengow/orders/synchronize
+ methods: [ POST, GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::synchronizeAction'
+
+lengow_admin_order_cancel_reimport:
+ path: /modules/lengow/orders/cancel-re-import
+ methods: [ POST, GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::cancelReImportAction'
+
+lengow_admin_order_save_shipping_method:
+ path: /modules/lengow/orders/save-shipping-method
+ methods: [ POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::saveShippingMethodAction'
+
+lengow_admin_order_force_resend:
+ path: /modules/lengow/orders/force-resend
+ methods: [ POST, GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrdersController::forceResendAction'
+
+# Lengow Main Settings
+lengow_admin_main_setting:
+ path: /modules/lengow/settings
+ methods: [ GET, POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminMainSettingController::indexAction'
+ _legacy_controller: AdminLengowMainSetting
+
+# Lengow Order Settings
+lengow_admin_order_setting:
+ path: /modules/lengow/order-settings
+ methods: [ GET, POST ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderSettingController::indexAction'
+ _legacy_controller: AdminLengowOrderSetting
+
+lengow_admin_order_setting_ajax:
+ path: /modules/lengow/order-settings/ajax
+ methods: [ POST, GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminOrderSettingController::ajaxAction'
+
+# Lengow Toolbox
+lengow_admin_toolbox:
+ path: /modules/lengow/toolbox
+ methods: [ GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminToolboxController::indexAction'
+ _legacy_controller: AdminLengowToolbox
+
+# Lengow Legals
+lengow_admin_legals:
+ path: /modules/lengow/legals
+ methods: [ GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminLegalsController::indexAction'
+ _legacy_controller: AdminLengowLegals
+
+# Lengow Help
+lengow_admin_help:
+ path: /modules/lengow/help
+ methods: [ GET ]
+ defaults:
+ _controller: 'PrestaShop\Module\Lengow\Controller\AdminHelpController::indexAction'
+ _legacy_controller: AdminLengowHelp
diff --git a/lengow.php b/lengow.php
index 29e63326..07f60e3a 100755
--- a/lengow.php
+++ b/lengow.php
@@ -50,7 +50,7 @@ public function __construct()
$this->module_key = '__LENGOW_PRESTASHOP_PRODUCT_KEY__';
$this->ps_versions_compliancy = [
'min' => '1.7.8',
- 'max' => '8.99.99',
+ 'max' => '9.99.99',
];
$this->bootstrap = true;
diff --git a/src/Controller/AdminDashboardController.php b/src/Controller/AdminDashboardController.php
new file mode 100644
index 00000000..dcd0460c
--- /dev/null
+++ b/src/Controller/AdminDashboardController.php
@@ -0,0 +1,98 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Dashboard Controller for PrestaShop 9
+ */
+class AdminDashboardController extends FrameworkBundleAdminController
+{
+ /**
+ * Dashboard page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowDashboard')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+
+ // Get merchant status and plugin information
+ $merchantStatus = \LengowSync::getStatusAccount();
+ $pluginData = \LengowSync::getPluginData();
+ $pluginIsUpToDate = \LengowSync::isPluginUpToDate();
+ $currentController = 'LengowDashboardController';
+
+ // Handle refresh status action
+ $action = $request->query->get('action');
+ if ($action === 'refresh_status') {
+ \LengowSync::getStatusAccount(true);
+ return $this->redirectToRoute('lengow_admin_dashboard');
+ }
+
+ $refreshStatusUrl = $this->generateUrl('lengow_admin_dashboard', ['action' => 'refresh_status']);
+
+ return $this->render('@Modules/lengow/views/templates/admin/dashboard/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+ 'merchantStatus' => $merchantStatus,
+ 'pluginData' => $pluginData,
+ 'pluginIsUpToDate' => $pluginIsUpToDate,
+ 'displayToolbar' => 1,
+ 'current_controller' => $currentController,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ 'refresh_status' => $refreshStatusUrl,
+ ]);
+ }
+
+ /**
+ * Handle remind me later action for plugin update modal
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowDashboard')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function remindMeLaterAction(Request $request): JsonResponse
+ {
+ $timestamp = time() + (7 * 86400);
+ \LengowConfiguration::updateGlobalValue(\LengowConfiguration::LAST_UPDATE_PLUGIN_MODAL, $timestamp);
+
+ return new JsonResponse(['success' => true]);
+ }
+}
diff --git a/src/Controller/AdminFeedController.php b/src/Controller/AdminFeedController.php
new file mode 100644
index 00000000..5723d00b
--- /dev/null
+++ b/src/Controller/AdminFeedController.php
@@ -0,0 +1,99 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Feed/Product Controller for PrestaShop 9
+ */
+class AdminFeedController extends FrameworkBundleAdminController
+{
+ /**
+ * Feed/Product page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowFeed')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+ $currentController = 'LengowFeedController';
+
+ // Use legacy controller for complex display logic
+ $lengowController = new \LengowFeedController();
+ $lengowController->prepareDisplay();
+
+ return $this->render('@Modules/lengow/views/templates/admin/feed/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+ 'displayToolbar' => 1,
+ 'current_controller' => $currentController,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ 'merchantStatus' => \LengowSync::getStatusAccount(),
+ 'pluginData' => \LengowSync::getPluginData(),
+ 'pluginIsUpToDate' => \LengowSync::isPluginUpToDate(),
+ ]);
+ }
+
+ /**
+ * Handle AJAX actions for feed page
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowFeed')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function ajaxAction(Request $request): JsonResponse
+ {
+ // Delegate to legacy controller for AJAX handling
+ $lengowController = new \LengowFeedController();
+
+ ob_start();
+ $lengowController->postProcess();
+ $output = ob_get_clean();
+
+ if (!empty($output)) {
+ $data = json_decode($output, true);
+ if (json_last_error() === JSON_ERROR_NONE) {
+ return new JsonResponse($data);
+ }
+ return new JsonResponse(['output' => $output]);
+ }
+
+ return new JsonResponse(['success' => true]);
+ }
+}
diff --git a/src/Controller/AdminHelpController.php b/src/Controller/AdminHelpController.php
new file mode 100644
index 00000000..7fe385ed
--- /dev/null
+++ b/src/Controller/AdminHelpController.php
@@ -0,0 +1,63 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Help Controller for PrestaShop 9
+ */
+class AdminHelpController extends FrameworkBundleAdminController
+{
+ /**
+ * Help page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowHelp')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+ $currentController = 'LengowHelpController';
+
+ return $this->render('@Modules/lengow/views/templates/admin/help/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+ 'displayToolbar' => 1,
+ 'current_controller' => $currentController,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ ]);
+ }
+}
diff --git a/src/Controller/AdminHomeController.php b/src/Controller/AdminHomeController.php
new file mode 100644
index 00000000..cc65fc26
--- /dev/null
+++ b/src/Controller/AdminHomeController.php
@@ -0,0 +1,283 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Home/Connection Controller for PrestaShop 9
+ */
+class AdminHomeController extends FrameworkBundleAdminController
+{
+ /**
+ * Home/Connection page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowHome')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+ $isNewMerchant = \LengowConfiguration::isNewMerchant();
+
+ // If not a new merchant, redirect to dashboard
+ if (!$isNewMerchant) {
+ return $this->redirectToRoute('lengow_admin_dashboard');
+ }
+
+ $lengowAjaxLink = $this->generateUrl('lengow_admin_home_ajax');
+
+ return $this->render('@Modules/lengow/views/templates/admin/home/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengow_ajax_link' => $lengowAjaxLink,
+ 'displayToolbar' => 0,
+ ]);
+ }
+
+ /**
+ * Handle AJAX actions for home/connection page
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowHome')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function ajaxAction(Request $request): JsonResponse
+ {
+ $action = $request->request->get('action', $request->query->get('action'));
+ $module = \Module::getInstanceByName('lengow');
+
+ switch ($action) {
+ case 'go_to_credentials':
+ $displayContent = $module->display(
+ _PS_MODULE_LENGOW_DIR_,
+ 'views/templates/admin/home/connection_cms.html.twig'
+ );
+ return new JsonResponse([
+ 'content' => preg_replace('/\r|\n/', '', $displayContent)
+ ]);
+
+ case 'connect_cms':
+ $accessToken = $request->request->get('accessToken', '');
+ $secret = $request->request->get('secret', '');
+
+ $credentialsValid = $this->checkApiCredentials($accessToken, $secret);
+ $cmsConnected = false;
+ $hasCatalogToLink = false;
+
+ if ($credentialsValid) {
+ $cmsConnected = $this->connectCms();
+ if ($cmsConnected) {
+ $hasCatalogToLink = $this->hasCatalogToLink();
+ }
+ }
+
+ $content = $this->renderView('@Modules/lengow/views/templates/admin/home/connection_cms_result.html.twig', [
+ 'credentialsValid' => $credentialsValid,
+ 'cmsConnected' => $cmsConnected,
+ 'hasCatalogToLink' => $hasCatalogToLink,
+ ]);
+
+ return new JsonResponse([
+ 'success' => $cmsConnected,
+ 'content' => preg_replace('/\r|\n/', '', $content),
+ ]);
+
+ case 'go_to_catalog':
+ $retry = $request->request->get('retry', 'false') !== 'false';
+
+ if ($retry) {
+ \LengowConfiguration::resetCatalogIds();
+ }
+
+ $content = $this->renderView('@Modules/lengow/views/templates/admin/home/connection_catalog.html.twig', [
+ 'shopCollection' => \LengowShop::getActiveShops(),
+ 'catalogList' => $this->getCatalogList(),
+ ]);
+
+ return new JsonResponse([
+ 'content' => preg_replace('/\r|\n/', '', $content)
+ ]);
+
+ case 'link_catalogs':
+ $catalogSelected = $request->request->get('catalogSelected', []);
+ $catalogsLinked = true;
+
+ if (!empty($catalogSelected)) {
+ $catalogsLinked = $this->saveCatalogsLinked($catalogSelected);
+ }
+
+ $displayConnectionResult = $module->display(
+ _PS_MODULE_LENGOW_DIR_,
+ 'views/templates/admin/home/connection_catalog_failed.html.twig'
+ );
+
+ return new JsonResponse([
+ 'success' => $catalogsLinked,
+ 'content' => preg_replace('/\r|\n/', '', $displayConnectionResult),
+ ]);
+
+ default:
+ return new JsonResponse(['error' => 'Unknown action'], 400);
+ }
+ }
+
+ /**
+ * Check API credentials and save them in Database
+ *
+ * @param string $accessToken access token for api
+ * @param string $secret secret for api
+ *
+ * @return bool
+ */
+ private function checkApiCredentials(string $accessToken, string $secret): bool
+ {
+ $accountId = \LengowConnector::getAccountIdByCredentials($accessToken, $secret);
+ if ($accountId) {
+ return \LengowConfiguration::setAccessIds([
+ \LengowConfiguration::ACCOUNT_ID => $accountId,
+ \LengowConfiguration::ACCESS_TOKEN => $accessToken,
+ \LengowConfiguration::SECRET => $secret,
+ ]);
+ }
+
+ return false;
+ }
+
+ /**
+ * Connect cms with Lengow
+ *
+ * @return bool
+ */
+ private function connectCms(): bool
+ {
+ $cmsToken = \LengowMain::getToken();
+ $cmsConnected = \LengowSync::syncCatalog(true);
+
+ if (!$cmsConnected) {
+ $syncData = json_encode(\LengowSync::getSyncData());
+ $result = \LengowConnector::queryApi(\LengowConnector::POST, \LengowConnector::API_CMS, [], $syncData);
+
+ if (isset($result->common_account)) {
+ $cmsConnected = true;
+ $messageKey = 'log.connection.cms_creation_success';
+ } else {
+ $messageKey = 'log.connection.cms_creation_failed';
+ }
+ } else {
+ $messageKey = 'log.connection.cms_already_exist';
+ }
+
+ \LengowMain::log(
+ \LengowLog::CODE_CONNECTION,
+ \LengowMain::setLogMessage($messageKey, ['cms_token' => $cmsToken])
+ );
+
+ if (!$cmsConnected) {
+ \LengowConfiguration::resetAccessIds();
+ \LengowConfiguration::resetAuthorizationToken();
+ }
+
+ return $cmsConnected;
+ }
+
+ /**
+ * Check if account has catalog to link
+ *
+ * @return bool
+ */
+ private function hasCatalogToLink(): bool
+ {
+ $activeShops = \LengowShop::getActiveShops(true);
+ if (empty($activeShops)) {
+ return \LengowCatalog::hasCatalogNotLinked();
+ }
+
+ return false;
+ }
+
+ /**
+ * Get all catalogs available in Lengow
+ *
+ * @return array
+ */
+ private function getCatalogList(): array
+ {
+ $activeShops = \LengowShop::getActiveShops(true);
+ if (empty($activeShops)) {
+ return \LengowCatalog::getCatalogList();
+ }
+
+ return [];
+ }
+
+ /**
+ * Save catalogs linked in database and send data to Lengow with call API
+ *
+ * @param array $catalogSelected
+ *
+ * @return bool
+ */
+ private function saveCatalogsLinked(array $catalogSelected): bool
+ {
+ $catalogsByShops = [];
+ foreach ($catalogSelected as $catalog) {
+ $catalogsByShops[$catalog['shopId']] = $catalog['catalogId'];
+ }
+
+ if (!empty($catalogsByShops)) {
+ foreach ($catalogsByShops as $idShop => $catalogIds) {
+ \LengowConfiguration::setCatalogIds($catalogIds, $idShop);
+ \LengowConfiguration::setActiveShop($idShop);
+ }
+
+ \LengowConfiguration::updateGlobalValue(\LengowConfiguration::LAST_UPDATE_SETTING, time());
+
+ $catalogsLinked = \LengowCatalog::linkCatalogs($catalogsByShops);
+ $messageKey = $catalogsLinked
+ ? 'log.connection.link_catalog_success'
+ : 'log.connection.link_catalog_failed';
+
+ \LengowMain::log(
+ \LengowLog::CODE_CONNECTION,
+ \LengowMain::setLogMessage($messageKey)
+ );
+
+ return $catalogsLinked;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Controller/AdminLegalsController.php b/src/Controller/AdminLegalsController.php
new file mode 100644
index 00000000..1f909f7f
--- /dev/null
+++ b/src/Controller/AdminLegalsController.php
@@ -0,0 +1,63 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Legals Controller for PrestaShop 9
+ */
+class AdminLegalsController extends FrameworkBundleAdminController
+{
+ /**
+ * Legals page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowLegals')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+ $currentController = 'LengowLegalsController';
+
+ return $this->render('@Modules/lengow/views/templates/admin/legals/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+ 'displayToolbar' => 0,
+ 'current_controller' => $currentController,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ ]);
+ }
+}
diff --git a/src/Controller/AdminMainSettingController.php b/src/Controller/AdminMainSettingController.php
new file mode 100644
index 00000000..196abd4c
--- /dev/null
+++ b/src/Controller/AdminMainSettingController.php
@@ -0,0 +1,77 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Main Setting Controller for PrestaShop 9
+ */
+class AdminMainSettingController extends FrameworkBundleAdminController
+{
+ /**
+ * Main Settings page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowMainSetting')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+ $currentController = 'LengowMainSettingController';
+
+ // Process form submission
+ if ($request->isMethod('POST')) {
+ $lengowController = new \LengowMainSettingController();
+ $lengowController->postProcess();
+ }
+
+ // Prepare display data using legacy controller
+ $lengowController = new \LengowMainSettingController();
+ $lengowController->prepareDisplay();
+
+ return $this->render('@Modules/lengow/views/templates/admin/main_setting/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+ 'displayToolbar' => 1,
+ 'current_controller' => $currentController,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ 'merchantStatus' => \LengowSync::getStatusAccount(),
+ 'pluginData' => \LengowSync::getPluginData(),
+ 'pluginIsUpToDate' => \LengowSync::isPluginUpToDate(),
+ ]);
+ }
+}
diff --git a/src/Controller/AdminOrderController.php b/src/Controller/AdminOrderController.php
deleted file mode 100644
index f5759a19..00000000
--- a/src/Controller/AdminOrderController.php
+++ /dev/null
@@ -1,632 +0,0 @@
-
- * @copyright 2017 Lengow SAS
- * @license http://www.apache.org/licenses/LICENSE-2.0
- */
-
-namespace PrestaShop\Module\Lengow\Controller;
-
-use PrestaShop\PrestaShop\Core\Domain\CartRule\Exception\InvalidCartRuleDiscountValueException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Command\UpdateOrderShippingDetailsCommand;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\CannotEditDeliveredOrderProductException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\CannotFindProductInOrderException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\DuplicateProductInOrderException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\DuplicateProductInOrderInvoiceException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidAmountException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidCancelProductException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidOrderStateException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\InvalidProductQuantityException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\NegativePaymentAmountException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderConstraintException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderEmailSendException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\OrderNotFoundException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Exception\TransistEmailSendingException;
-use PrestaShop\PrestaShop\Core\Domain\Order\Query\GetOrderForViewing;
-use PrestaShop\PrestaShop\Core\Domain\Order\QueryResult\OrderForViewing;
-use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductOutOfStockException;
-use PrestaShop\PrestaShop\Core\Domain\ValueObject\QuerySorting;
-use PrestaShop\PrestaShop\Core\Order\OrderSiblingProviderInterface;
-use PrestaShopBundle\Controller\Admin\Sell\Order\ActionsBarButtonsCollection;
-use PrestaShopBundle\Controller\Admin\Sell\Order\OrderController;
-use PrestaShopBundle\Exception\InvalidModuleException;
-use PrestaShopBundle\Form\Admin\Sell\Customer\PrivateNoteType;
-use PrestaShopBundle\Form\Admin\Sell\Order\AddOrderCartRuleType;
-use PrestaShopBundle\Form\Admin\Sell\Order\AddProductRowType;
-use PrestaShopBundle\Form\Admin\Sell\Order\ChangeOrderAddressType;
-use PrestaShopBundle\Form\Admin\Sell\Order\ChangeOrderCurrencyType;
-use PrestaShopBundle\Form\Admin\Sell\Order\EditProductRowType;
-use PrestaShopBundle\Form\Admin\Sell\Order\InternalNoteType;
-use PrestaShopBundle\Form\Admin\Sell\Order\OrderMessageType;
-use PrestaShopBundle\Form\Admin\Sell\Order\OrderPaymentType;
-use PrestaShopBundle\Form\Admin\Sell\Order\UpdateOrderShippingType;
-use PrestaShopBundle\Form\Admin\Sell\Order\UpdateOrderStatusType;
-use PrestaShopBundle\Security\Annotation\AdminSecurity;
-use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
-use Symfony\Component\Form\Extension\Core\Type\TextType;
-use Symfony\Component\HttpFoundation\JsonResponse;
-use Symfony\Component\HttpFoundation\RedirectResponse;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpFoundation\Response;
-
-if (!defined('_PS_VERSION_')) {
- exit;
-}
-
-class AdminOrderController extends OrderController
-{
- /**
- * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))")
- *
- * @param int $orderId
- * @param Request $request
- *
- * @return Response
- */
- public function viewAction(int $orderId, Request $request): Response
- {
- try {
- if (!$this->isFromLengow($orderId)) {
- return parent::viewAction($orderId, $request);
- }
- /** @var OrderForViewing $orderForViewing */
- $orderForViewing = $this->getQueryBus()->handle(new GetOrderForViewing($orderId, QuerySorting::DESC));
- } catch (OrderException $e) {
- $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e)));
-
- return $this->redirectToRoute('admin_orders_index');
- }
- $locale = new \LengowTranslation();
- $formFactory = $this->get('form.factory');
- $updateOrderStatusForm = $formFactory->createNamed(
- 'update_order_status',
- UpdateOrderStatusType::class,
- [
- 'new_order_status_id' => $orderForViewing->getHistory()->getCurrentOrderStatusId(),
- ]
- );
- $updateOrderStatusActionBarForm = $formFactory->createNamed(
- 'update_order_status_action_bar',
- UpdateOrderStatusType::class,
- [
- 'new_order_status_id' => $orderForViewing->getHistory()->getCurrentOrderStatusId(),
- ]
- );
-
- $addOrderCartRuleForm = $this->createForm(AddOrderCartRuleType::class, [], [
- 'order_id' => $orderId,
- ]);
- $addOrderPaymentForm = $this->createForm(OrderPaymentType::class, [
- 'id_currency' => $orderForViewing->getCurrencyId(),
- ], [
- 'id_order' => $orderId,
- ]);
-
- $orderMessageForm = $this->createForm(OrderMessageType::class, [
- 'lang_id' => $orderForViewing->getCustomer()->getLanguageId(),
- ], [
- 'action' => $this->generateUrl('admin_orders_send_message', ['orderId' => $orderId]),
- ]);
- $orderMessageForm->handleRequest($request);
-
- $changeOrderCurrencyForm = $this->createForm(ChangeOrderCurrencyType::class, [], [
- 'current_currency_id' => $orderForViewing->getCurrencyId(),
- ]);
-
- $changeOrderAddressForm = null;
- $privateNoteForm = null;
-
- if (null !== $orderForViewing->getCustomer() && $orderForViewing->getCustomer()->getId() !== 0) {
- $changeOrderAddressForm = $this->createForm(ChangeOrderAddressType::class, [], [
- 'customer_id' => $orderForViewing->getCustomer()->getId(),
- ]);
-
- $privateNoteForm = $this->createForm(PrivateNoteType::class, [
- 'note' => $orderForViewing->getCustomer()->getPrivateNote(),
- ]);
- }
-
- $updateOrderShippingForm = $this->createForm(UpdateOrderShippingType::class, [
- 'new_carrier_id' => $orderForViewing->getCarrierId(),
- ], [
- 'order_id' => $orderId,
- ]);
- $isActiveReturnCarrier = false;
- $isActiveReturnTrackingNumber = false;
- if ($this->isFromLengow($orderId)) {
- $isActiveReturnTrackingNumber = $this->isActiveReturnTrackingNumber($orderId);
- $isActiveReturnCarrier = $this->isActiveReturnTrackingCarrier($orderId);
- }
-
- if ($isActiveReturnTrackingNumber) {
- $returnTrackingNumber = $this->getReturnTrackingNumber($orderId);
- $updateOrderShippingForm->add(\LengowAction::ARG_RETURN_TRACKING_NUMBER, TextType::class, [
- 'required' => false,
- 'data' => $returnTrackingNumber,
- ]);
- }
-
- if ($isActiveReturnCarrier) {
- $returnCarrier = $this->getReturnCarrier($orderId);
- $updateOrderShippingForm->add(\LengowAction::ARG_RETURN_CARRIER, ChoiceType::class, [
- 'required' => false,
- 'data' => $returnCarrier,
- 'choices' => \LengowCarrier::getCarriersChoices(
- $orderForViewing->getCustomer()->getLanguageId()
- ),
- ]);
- }
- $currencyDataProvider = $this->container->get('prestashop.adapter.data_provider.currency');
- $orderCurrency = $currencyDataProvider->getCurrencyById($orderForViewing->getCurrencyId());
-
- $addProductRowForm = $this->createForm(AddProductRowType::class, [], [
- 'order_id' => $orderId,
- 'currency_id' => $orderForViewing->getCurrencyId(),
- 'symbol' => $orderCurrency->symbol,
- ]);
- $editProductRowForm = $this->createForm(EditProductRowType::class, [], [
- 'order_id' => $orderId,
- 'symbol' => $orderCurrency->symbol,
- ]);
-
- $internalNoteForm = $this->createForm(InternalNoteType::class, [
- 'note' => $orderForViewing->getNote(),
- ]);
-
- $formBuilder = $this->get('prestashop.core.form.identifiable_object.builder.cancel_product_form_builder');
- $backOfficeOrderButtons = new ActionsBarButtonsCollection();
-
- try {
- $this->dispatchHook(
- 'actionGetAdminOrderButtons',
- [
- 'controller' => $this,
- 'id_order' => $orderId,
- 'actions_bar_buttons_collection' => $backOfficeOrderButtons,
- ]
- );
-
- $cancelProductForm = $formBuilder->getFormFor($orderId);
- if ($this->isFromLengow($orderId)) {
- $lengowOrder = new \LengowOrder($orderId);
- $marketplace = $lengowOrder->getMarketplace();
- $refundReasons = $marketplace->getRefundReasons();
- $refundMode = $marketplace->getRefundModes();
- $refundSelectedDatas = $lengowOrder->getRefundDataFromLengowOrder($orderId, $marketplace->name);
- }
- } catch (\Exception $e) {
- $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e)));
-
- return $this->redirectToRoute('admin_orders_index');
- }
-
- $this->handleOutOfStockProduct($orderForViewing);
-
- $merchandiseReturnEnabled = (bool) $this->configuration->get('PS_ORDER_RETURN');
-
- /** @var OrderSiblingProviderInterface $orderSiblingProvider */
- $orderSiblingProvider = $this->get('prestashop.adapter.order.order_sibling_provider');
-
- $paginationNum = (int) $this->configuration->get('PS_ORDER_PRODUCTS_NB_PER_PAGE', self::DEFAULT_PRODUCTS_NUMBER);
- $paginationNumOptions = self::PRODUCTS_PAGINATION_OPTIONS;
- if (!in_array($paginationNum, $paginationNumOptions)) {
- $paginationNumOptions[] = $paginationNum;
- }
- sort($paginationNumOptions);
- $metatitle = sprintf(
- '%s %s %s',
- $this->trans('Orders', 'Admin.Orderscustomers.Feature'),
- $this->configuration->get('PS_NAVIGATION_PIPE', '>'),
- $this->trans(
- 'Order %reference% from %firstname% %lastname%',
- 'Admin.Orderscustomers.Feature',
- [
- '%reference%' => $orderForViewing->getReference(),
- '%firstname%' => $orderForViewing->getCustomer()->getFirstName(),
- '%lastname%' => $orderForViewing->getCustomer()->getLastName(),
- ]
- )
- );
-
- return $this->render('@PrestaShop/Admin/Sell/Order/Order/view.html.twig', [
- 'showContentHeader' => true,
- 'enableSidebar' => true,
- 'orderCurrency' => $orderCurrency,
- 'meta_title' => $metatitle,
- 'help_link' => $this->generateSidebarLink($request->attributes->get('_legacy_controller')),
- 'orderForViewing' => $orderForViewing,
- 'addOrderCartRuleForm' => $addOrderCartRuleForm->createView(),
- 'updateOrderStatusForm' => $updateOrderStatusForm->createView(),
- 'updateOrderStatusActionBarForm' => $updateOrderStatusActionBarForm->createView(),
- 'addOrderPaymentForm' => $addOrderPaymentForm->createView(),
- 'changeOrderCurrencyForm' => $changeOrderCurrencyForm->createView(),
- 'privateNoteForm' => $privateNoteForm ? $privateNoteForm->createView() : null,
- 'updateOrderShippingForm' => $updateOrderShippingForm->createView(),
- 'cancelProductForm' => $cancelProductForm->createView(),
- 'invoiceManagementIsEnabled' => $orderForViewing->isInvoiceManagementIsEnabled(),
- 'changeOrderAddressForm' => $changeOrderAddressForm ? $changeOrderAddressForm->createView() : null,
- 'orderMessageForm' => $orderMessageForm->createView(),
- 'addProductRowForm' => $addProductRowForm->createView(),
- 'editProductRowForm' => $editProductRowForm->createView(),
- 'backOfficeOrderButtons' => $backOfficeOrderButtons,
- 'merchandiseReturnEnabled' => $merchandiseReturnEnabled,
- 'priceSpecification' => $this->getContextLocale()->getPriceSpecification($orderCurrency->iso_code)->toArray(),
- 'previousOrderId' => $orderSiblingProvider->getPreviousOrderId($orderId),
- 'nextOrderId' => $orderSiblingProvider->getNextOrderId($orderId),
- 'paginationNum' => $paginationNum,
- 'paginationNumOptions' => $paginationNumOptions,
- 'isAvailableQuantityDisplayed' => $this->configuration->getBoolean('PS_STOCK_MANAGEMENT'),
- 'internalNoteForm' => $internalNoteForm->createView(),
- 'returnTrackingNumber' => $this->getReturnTrackingNumber($orderId),
- 'returnCarrier' => $this->getReturnCarrier($orderId),
- 'isActiveReturnTrackingNumber' => $isActiveReturnTrackingNumber,
- 'isActiveReturnCarrier' => $isActiveReturnCarrier,
- 'returnTrackingNumberLabel' => $locale->t('order.screen.return_tracking_number_label'),
- 'returnCarrierLabel' => $locale->t('order.screen.return_carrier_label'),
- 'returnCarrierName' => $this->getReturnCarrierName($orderId),
- 'refundReasons' => $refundReasons ?? [],
- 'refundModes' => $refundMode ?? [],
- 'refundReasonSelected' => $refundSelectedDatas['refund_reason'] ?? '',
- 'refundModeSelected' => $refundSelectedDatas['refund_mode'] ?? '',
- ]);
- }
-
- /**
- * @AdminSecurity(
- * "is_granted('update', request.get('_legacy_controller'))",
- * redirectRoute="admin_orders_view",
- * redirectQueryParamsToKeep={"orderId"},
- * message="You do not have permission to edit this."
- * )
- *
- * @param int $orderId
- * @param Request $request
- *
- * @return RedirectResponse
- */
- public function updateShippingAction(int $orderId, Request $request): RedirectResponse
- {
- $form = $this->createForm(UpdateOrderShippingType::class, [], [
- 'order_id' => $orderId,
- ]);
-
- if ($this->isFromLengow($orderId)) {
- if ($this->isActiveReturnTrackingNumber($orderId)) {
- $form->add(\LengowAction::ARG_RETURN_TRACKING_NUMBER, TextType::class, [
- 'required' => false,
- ]);
- }
- if ($this->isActiveReturnTrackingCarrier($orderId)) {
- $order = new \LengowOrder($orderId);
- $form->add(\LengowAction::ARG_RETURN_CARRIER, ChoiceType::class, [
- 'required' => false,
- 'choices' => \LengowCarrier::getCarriersChoices(
- $order->id_lang
- ),
- ]);
- }
- }
-
- $form->handleRequest($request);
-
- if ($form->isSubmitted() && $form->isValid()) {
- $data = $form->getData();
-
- try {
- if (!empty($data[\LengowAction::ARG_RETURN_TRACKING_NUMBER])) {
- \LengowOrderDetail::updateOrderReturnTrackingNumber(
- $data[\LengowAction::ARG_RETURN_TRACKING_NUMBER],
- $orderId
- );
- }
- if (!empty($data[\LengowAction::ARG_RETURN_CARRIER])) {
- \LengowOrderDetail::updateOrderReturnCarrier(
- (int) $data[\LengowAction::ARG_RETURN_CARRIER],
- $orderId
- );
- }
- $this->getCommandBus()->handle(
- new UpdateOrderShippingDetailsCommand(
- $orderId,
- (int) $data['current_order_carrier_id'],
- (int) $data['new_carrier_id'],
- $data['tracking_number']
- )
- );
-
- $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
- } catch (TransistEmailSendingException $e) {
- $this->addFlash(
- 'error',
- $this->trans(
- 'An error occurred while sending an email to the customer.',
- 'Admin.Orderscustomers.Notification'
- )
- );
- } catch (\Exception $e) {
- $this->addFlash('error', $this->getErrorMessageForException($e, $this->getErrorMessages($e)));
- }
- } else {
- // exit ('form not valid');
- }
-
- return $this->redirectToRoute('admin_orders_view', [
- 'orderId' => $orderId,
- ]);
- }
-
- /**
- * @param OrderForViewing $orderForViewing
- */
- private function handleOutOfStockProduct(OrderForViewing $orderForViewing)
- {
- $isStockManagementEnabled = $this->configuration->getBoolean('PS_STOCK_MANAGEMENT');
- if (!$isStockManagementEnabled || $orderForViewing->isDelivered() || $orderForViewing->isShipped()) {
- return;
- }
-
- foreach ($orderForViewing->getProducts()->getProducts() as $product) {
- if ($product->getAvailableQuantity() <= 0) {
- $this->addFlash(
- 'warning',
- $this->trans('This product is out of stock:', 'Admin.Orderscustomers.Notification') . ' ' . $product->getName()
- );
- }
- }
- }
-
- /**
- * @param \Exception $e
- *
- * @return array
- */
- private function getErrorMessages(\Exception $e)
- {
- $refundableQuantity = 0;
- if ($e instanceof InvalidCancelProductException) {
- $refundableQuantity = $e->getRefundableQuantity();
- }
- $orderInvoiceNumber = '#unknown';
- if ($e instanceof DuplicateProductInOrderInvoiceException) {
- $orderInvoiceNumber = $e->getOrderInvoiceNumber();
- }
-
- return [
- CannotEditDeliveredOrderProductException::class => $this->trans('You cannot edit the cart once the order delivered.', 'Admin.Orderscustomers.Notification'),
- OrderNotFoundException::class => $e instanceof OrderNotFoundException ?
- $this->trans(
- 'Order #%d cannot be loaded.',
- 'Admin.Orderscustomers.Notification',
- ['#%d' => $e->getOrderId()->getValue()]
- ) : '',
- OrderEmailSendException::class => $this->trans(
- 'An error occurred while sending the e-mail to the customer.',
- 'Admin.Orderscustomers.Notification'
- ),
- OrderException::class => $this->trans(
- $e->getMessage(),
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidAmountException::class => $this->trans(
- 'Only numbers and decimal points (".") are allowed in the amount fields, e.g. 10.50 or 1050.',
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidCartRuleDiscountValueException::class => [
- InvalidCartRuleDiscountValueException::INVALID_MIN_PERCENT => $this->trans(
- 'Percent value must be greater than 0.',
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidCartRuleDiscountValueException::INVALID_MAX_PERCENT => $this->trans(
- 'Percent value cannot exceed 100.',
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidCartRuleDiscountValueException::INVALID_MIN_AMOUNT => $this->trans(
- 'Amount value must be greater than 0.',
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidCartRuleDiscountValueException::INVALID_MAX_AMOUNT => $this->trans(
- 'Discount value cannot exceed the total price of this order.',
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidCartRuleDiscountValueException::INVALID_FREE_SHIPPING => $this->trans(
- 'Shipping discount value cannot exceed the total price of this order.',
- 'Admin.Orderscustomers.Notification'
- ),
- ],
- InvalidCancelProductException::class => [
- InvalidCancelProductException::INVALID_QUANTITY => $this->trans(
- 'Positive product quantity is required.',
- 'Admin.Notifications.Error'
- ),
- InvalidCancelProductException::QUANTITY_TOO_HIGH => $this->trans(
- 'Please enter a maximum quantity of [1].',
- 'Admin.Orderscustomers.Notification',
- ['[1]' => $refundableQuantity]
- ),
- InvalidCancelProductException::NO_REFUNDS => $this->trans(
- 'Please select at least one product.',
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidCancelProductException::INVALID_AMOUNT => $this->trans(
- 'Please enter a positive amount.',
- 'Admin.Orderscustomers.Notification'
- ),
- InvalidCancelProductException::NO_GENERATION => $this->trans(
- 'Please generate at least one credit slip or voucher.',
- 'Admin.Orderscustomers.Notification'
- ),
- ],
- InvalidModuleException::class => $this->trans(
- 'You must choose a payment module to create the order.',
- 'Admin.Orderscustomers.Notification'
- ),
- ProductOutOfStockException::class => $this->trans(
- 'There are not enough products in stock.',
- 'Admin.Catalog.Notification'
- ),
- NegativePaymentAmountException::class => $this->trans(
- 'Invalid value: the payment must be a positive amount.',
- 'Admin.Notifications.Error'
- ),
- InvalidOrderStateException::class => [
- InvalidOrderStateException::ALREADY_PAID => $this->trans(
- 'Invalid action: this order has already been paid.',
- 'Admin.Notifications.Error'
- ),
- InvalidOrderStateException::DELIVERY_NOT_FOUND => $this->trans(
- 'Invalid action: this order has not been delivered.',
- 'Admin.Notifications.Error'
- ),
- InvalidOrderStateException::UNEXPECTED_DELIVERY => $this->trans(
- 'Invalid action: this order has already been delivered.',
- 'Admin.Notifications.Error'
- ),
- InvalidOrderStateException::NOT_PAID => $this->trans(
- 'Invalid action: this order has not been paid.',
- 'Admin.Notifications.Error'
- ),
- InvalidOrderStateException::INVALID_ID => $this->trans(
- 'You must choose an order status to create the order.',
- 'Admin.Orderscustomers.Notification'
- ),
- ],
-
- OrderConstraintException::class => [
- OrderConstraintException::INVALID_CUSTOMER_MESSAGE => $this->trans(
- 'The order message given is invalid.',
- 'Admin.Orderscustomers.Notification'
- ),
- ],
- InvalidProductQuantityException::class => $this->trans(
- 'Positive product quantity is required.',
- 'Admin.Notifications.Error'
- ),
- DuplicateProductInOrderException::class => $this->trans(
- 'This product is already in your order, please edit the quantity instead.',
- 'Admin.Notifications.Error'
- ),
- DuplicateProductInOrderInvoiceException::class => $this->trans(
- 'This product is already in the invoice [1], please edit the quantity instead.',
- 'Admin.Notifications.Error',
- ['[1]' => $orderInvoiceNumber]
- ),
- CannotFindProductInOrderException::class => $this->trans(
- 'You cannot edit the price of a product that no longer exists in your catalog.',
- 'Admin.Notifications.Error'
- ),
- ];
- }
-
- /**
- * @return bool
- */
- private function isActiveReturnTrackingNumber(int $orderId): bool
- {
- $lengowOrder = new \LengowOrder($orderId);
- if ($lengowOrder->getMarketplace()) {
- return $lengowOrder->getMarketplace()->hasReturnTrackingNumber();
- }
-
- return false;
- }
-
- /**
- * @return bool
- */
- private function isActiveReturnTrackingCarrier(int $orderId): bool
- {
- $lengowOrder = new \LengowOrder($orderId);
- if ($lengowOrder->getMarketplace()) {
- return $lengowOrder->getMarketplace()->hasReturnTrackingCarrier();
- }
-
- return false;
- }
-
- /**
- * @param int $orderId
- *
- * @return string
- */
- private function getReturnTrackingNumber(int $orderId): string
- {
- return \LengowOrderDetail::getOrderReturnTrackingNumber($orderId);
- }
-
- /**
- * @param int $orderId
- *
- * @return string
- */
- private function getReturnCarrier(int $orderId): string
- {
- return \LengowOrderDetail::getOrderReturnCarrier($orderId);
- }
-
- /**
- * @param int $orderId
- *
- * @return string
- */
- private function getReturnCarrierName(int $orderId): string
- {
- return \LengowOrderDetail::getOrderReturnCarrierName($orderId);
- }
-
- /**
- * @param int $orderId
- */
- private function isFromLengow(int $orderId): bool
- {
- return \LengowOrder::isFromLengow($orderId);
- }
-
- public function saveRefundReason(Request $request)
- {
- $data = json_decode($request->getContent(), true);
- $orderId = (int) $data['orderId'];
- $reason = $data['reason'] ?? '';
-
- if (empty($orderId) || empty($reason)) {
- return new JsonResponse(['success' => false, 'message' => 'Données manquantes']);
- }
-
- \Db::getInstance()->update('lengow_orders', [
- 'refund_reason' => pSQL($reason),
- ], 'id_order = ' . (int) $orderId);
-
- return new JsonResponse(['success' => true]);
- }
-
- public function saveRefundMode(Request $request)
- {
- $data = json_decode($request->getContent(), true);
- $orderId = (int) $data['orderId'];
- $reason = $data['mode'] ?? '';
-
- if (empty($orderId) || empty($reason)) {
- return new JsonResponse(['success' => false, 'message' => 'Données manquantes']);
- }
-
- \Db::getInstance()->update('lengow_orders', [
- 'refund_mode' => pSQL($reason),
- ], 'id_order = ' . (int) $orderId);
-
- return new JsonResponse(['success' => true]);
- }
-}
diff --git a/src/Controller/AdminOrderSettingController.php b/src/Controller/AdminOrderSettingController.php
new file mode 100644
index 00000000..af38cb9e
--- /dev/null
+++ b/src/Controller/AdminOrderSettingController.php
@@ -0,0 +1,105 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Order Settings Controller for PrestaShop 9
+ */
+class AdminOrderSettingController extends FrameworkBundleAdminController
+{
+ /**
+ * Order Settings page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowOrderSetting')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+ $currentController = 'LengowOrderSettingController';
+
+ // Process form submission
+ if ($request->isMethod('POST')) {
+ $lengowController = new \LengowOrderSettingController();
+ $lengowController->postProcess();
+ }
+
+ // Prepare display data using legacy controller
+ $lengowController = new \LengowOrderSettingController();
+ $lengowController->prepareDisplay();
+
+ return $this->render('@Modules/lengow/views/templates/admin/order_setting/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+ 'displayToolbar' => 1,
+ 'current_controller' => $currentController,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ 'merchantStatus' => \LengowSync::getStatusAccount(),
+ 'pluginData' => \LengowSync::getPluginData(),
+ 'pluginIsUpToDate' => \LengowSync::isPluginUpToDate(),
+ ]);
+ }
+
+ /**
+ * Handle AJAX actions for order settings page
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrderSetting')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function ajaxAction(Request $request): JsonResponse
+ {
+ // Delegate to legacy controller for AJAX handling
+ $lengowController = new \LengowOrderSettingController();
+
+ ob_start();
+ $lengowController->postProcess();
+ $output = ob_get_clean();
+
+ if (!empty($output)) {
+ $data = json_decode($output, true);
+ if (json_last_error() === JSON_ERROR_NONE) {
+ return new JsonResponse($data);
+ }
+ return new JsonResponse(['output' => $output]);
+ }
+
+ return new JsonResponse(['success' => true]);
+ }
+}
diff --git a/src/Controller/AdminOrdersController.php b/src/Controller/AdminOrdersController.php
new file mode 100644
index 00000000..d1b9e8fe
--- /dev/null
+++ b/src/Controller/AdminOrdersController.php
@@ -0,0 +1,485 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+use Shop;
+use Tools;
+use Module;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Orders List Controller for PrestaShop 8+/9
+ *
+ * This controller handles the Lengow orders list page with complete
+ * migration from Smarty to Twig templates for PrestaShop 8+/9 compatibility.
+ */
+class AdminOrdersController extends FrameworkBundleAdminController
+{
+ /**
+ * Display orders list page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = Module::getInstanceByName('lengow');
+
+ // Get order controller instance
+ $lengowOrderController = new \LengowOrderController();
+
+ // Get last import information
+ $lastImport = \LengowMain::getLastImport();
+ $orderCollection = [
+ 'last_import_date' => $lastImport['timestamp'] !== 'none'
+ ? \LengowMain::getDateInCorrectFormat($lastImport['timestamp'])
+ : '',
+ 'last_import_type' => $lastImport['type'],
+ 'link' => \LengowMain::getCronUrl(),
+ ];
+
+ // Get report mail settings
+ $reportMailEnabled = (bool) \LengowConfiguration::getGlobalValue(\LengowConfiguration::REPORT_MAIL_ENABLED);
+ $reportMailAddress = \LengowConfiguration::getReportEmailAddress();
+
+ // Get number of imported orders
+ $sql = 'SELECT COUNT(*) as `total` FROM `' . _DB_PREFIX_ . 'lengow_orders`';
+ try {
+ $totalOrders = \Db::getInstance()->executeS($sql);
+ $nbOrderImported = (int) $totalOrders[0]['total'];
+ } catch (\PrestaShopDatabaseException $e) {
+ $nbOrderImported = 0;
+ }
+
+ // Get warning messages
+ $warningMessages = [];
+ if (\LengowConfiguration::debugModeIsActive()) {
+ $warningMessages[] = $locale->t(
+ 'order.screen.debug_warning_message',
+ ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowMainSetting')]
+ );
+ }
+ if (\LengowCarrier::hasDefaultCarrierNotMatched()) {
+ $warningMessages[] = $locale->t(
+ 'order.screen.no_carrier_warning_message',
+ ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowOrderSetting')]
+ );
+ }
+ $warningMessage = !empty($warningMessages) ? join(' ', $warningMessages) : false;
+
+ // Build orders table
+ $lengowTable = $lengowOrderController->buildTable();
+
+ // Get plugin information
+ $pluginData = \LengowSync::getPluginData();
+ $pluginIsUpToDate = true;
+ if ($pluginData && version_compare($pluginData['version'], $module->version, '>')) {
+ $pluginIsUpToDate = false;
+ }
+
+ // Get system information
+ $multiShop = Shop::isFeatureActive();
+ $debugMode = \LengowConfiguration::debugModeIsActive();
+
+ return $this->render('@Modules/lengow/views/templates/twig/admin/orders/index.html.twig', [
+ // Translation and links
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+
+ // Order data
+ 'orderCollection' => $orderCollection,
+ 'reportMailEnabled' => $reportMailEnabled,
+ 'report_mail_address' => $reportMailAddress,
+ 'nb_order_imported' => $nbOrderImported,
+ 'warning_message' => $warningMessage,
+ 'lengow_table' => $lengowTable,
+
+ // Plugin information
+ 'pluginData' => $pluginData,
+ 'pluginIsUpToDate' => $pluginIsUpToDate,
+
+ // System information
+ 'multiShop' => $multiShop,
+ 'debugMode' => $debugMode,
+ 'showCarrierNotification' => \LengowCarrier::hasDefaultCarrierNotMatched(),
+
+ // Toolbar
+ 'displayToolbar' => 1,
+ 'current_controller' => 'LengowOrderController',
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ 'merchantStatus' => \LengowSync::getStatusAccount(),
+ ]);
+ }
+
+ /**
+ * Load orders table via AJAX
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function loadTableAction(Request $request): JsonResponse
+ {
+ $lengowOrderController = new \LengowOrderController();
+ $orderTable = $lengowOrderController->buildTable();
+
+ $data = [];
+ $data['order_table'] = preg_replace('/\r|\n/', '', $orderTable);
+
+ return new JsonResponse($data);
+ }
+
+ /**
+ * Re-import order via AJAX
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function reImportAction(Request $request): JsonResponse
+ {
+ $idOrderLengow = (int) $request->get('id', 0);
+
+ if (!$idOrderLengow) {
+ return new JsonResponse(['error' => 'Invalid order ID'], 400);
+ }
+
+ \LengowOrder::reImportOrder($idOrderLengow);
+
+ $lengowOrderController = new \LengowOrderController();
+ $list = $lengowOrderController->loadTable();
+ $row = $list->getRow(' id = ' . $idOrderLengow);
+ $html = $list->displayRow($row);
+ $html = preg_replace('/\r|\n/', '', $html);
+
+ $data = [
+ 'id_order_lengow' => $idOrderLengow,
+ 'html' => $html,
+ ];
+
+ return new JsonResponse($data);
+ }
+
+ /**
+ * Re-send order via AJAX
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function reSendAction(Request $request): JsonResponse
+ {
+ $idOrderLengow = (int) $request->get('id', 0);
+
+ if (!$idOrderLengow) {
+ return new JsonResponse(['error' => 'Invalid order ID'], 400);
+ }
+
+ \LengowOrder::reSendOrder($idOrderLengow);
+
+ $lengowOrderController = new \LengowOrderController();
+ $list = $lengowOrderController->loadTable();
+ $row = $list->getRow(' id = ' . $idOrderLengow);
+ $html = $list->displayRow($row);
+ $html = preg_replace('/\r|\n/', '', $html);
+
+ $data = [
+ 'id_order_lengow' => $idOrderLengow,
+ 'html' => $html,
+ ];
+
+ return new JsonResponse($data);
+ }
+
+ /**
+ * Import all orders via AJAX
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function importAllAction(Request $request): JsonResponse
+ {
+ $locale = new \LengowTranslation();
+ $module = Module::getInstanceByName('lengow');
+
+ // Run import
+ if (Shop::getContextShopID()) {
+ $import = new \LengowImport([
+ \LengowImport::PARAM_SHOP_ID => Shop::getContextShopID(),
+ \LengowImport::PARAM_LOG_OUTPUT => false,
+ ]);
+ } else {
+ $import = new \LengowImport([\LengowImport::PARAM_LOG_OUTPUT => false]);
+ }
+ $return = $import->exec();
+
+ // Get import messages
+ $lengowOrderController = new \LengowOrderController();
+ $message = $lengowOrderController->loadMessage($return);
+
+ // Get updated data
+ $lastImport = \LengowMain::getLastImport();
+ $orderCollection = [
+ 'last_import_date' => $lastImport['timestamp'] !== 'none'
+ ? \LengowMain::getDateInCorrectFormat($lastImport['timestamp'])
+ : '',
+ 'last_import_type' => $lastImport['type'],
+ 'link' => \LengowMain::getCronUrl(),
+ ];
+
+ // Get warning messages
+ $warningMessages = [];
+ if (\LengowConfiguration::debugModeIsActive()) {
+ $lengowLink = new \LengowLink();
+ $warningMessages[] = $locale->t(
+ 'order.screen.debug_warning_message',
+ ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowMainSetting')]
+ );
+ }
+ if (\LengowCarrier::hasDefaultCarrierNotMatched()) {
+ $lengowLink = new \LengowLink();
+ $warningMessages[] = $locale->t(
+ 'order.screen.no_carrier_warning_message',
+ ['url' => $lengowLink->getAbsoluteAdminLink('AdminLengowOrderSetting')]
+ );
+ }
+ $warningMessage = !empty($warningMessages) ? join(' ', $warningMessages) : false;
+
+ // Render partials
+ $this->get('twig')->addGlobal('orderCollection', $orderCollection);
+ $this->get('twig')->addGlobal('locale', $locale);
+ $this->get('twig')->addGlobal('reportMailEnabled', (bool) \LengowConfiguration::getGlobalValue(\LengowConfiguration::REPORT_MAIL_ENABLED));
+ $this->get('twig')->addGlobal('report_mail_address', \LengowConfiguration::getReportEmailAddress());
+ $this->get('twig')->addGlobal('warning_message', $warningMessage);
+ $this->get('twig')->addGlobal('lengow_link', new \LengowLink());
+
+ $displayWarningMessage = $this->get('twig')->render('@Modules/lengow/views/templates/twig/admin/orders/_partials/warning_message.html.twig');
+ $displayLastImportation = $this->get('twig')->render('@Modules/lengow/views/templates/twig/admin/orders/_partials/last_importation.html.twig');
+
+ // Get order table
+ $orderTable = $lengowOrderController->buildTable();
+ $list = $lengowOrderController->loadTable();
+
+ if ($list->getTotal() > 0) {
+ $displayListOrder = $orderTable;
+ } else {
+ $this->get('twig')->addGlobal('nb_order_imported', 0);
+ $displayListOrder = $this->get('twig')->render('@Modules/lengow/views/templates/twig/admin/orders/_partials/no_order.html.twig');
+ }
+
+ $data = [
+ 'message' => '' . join(' ', $message) . '
',
+ 'warning_message' => preg_replace('/\r|\n/', '', $displayWarningMessage),
+ 'last_importation' => preg_replace('/\r|\n/', '', $displayLastImportation),
+ 'import_orders' => $locale->t('order.screen.button_update_orders'),
+ 'list_order' => preg_replace('/\r|\n/', '', $displayListOrder),
+ 'show_carrier_notification' => \LengowCarrier::hasDefaultCarrierNotMatched(),
+ ];
+
+ return new JsonResponse($data);
+ }
+
+ /**
+ * Synchronize order with Lengow
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function synchronizeAction(Request $request): Response
+ {
+ $idOrder = (int) $request->get('id_order', 0);
+
+ if (!$idOrder) {
+ $this->addFlash('error', 'Invalid order ID');
+ return $this->redirectToRoute('lengow_admin_order_index');
+ }
+
+ $lengowOrder = new \LengowOrder($idOrder);
+ $synchro = $lengowOrder->synchronizeOrder();
+
+ if ($synchro) {
+ $synchroMessage = \LengowMain::setLogMessage(
+ 'log.import.order_synchronized_with_lengow',
+ ['order_id' => $idOrder]
+ );
+ } else {
+ $synchroMessage = \LengowMain::setLogMessage(
+ 'log.import.order_not_synchronized_with_lengow',
+ ['order_id' => $idOrder]
+ );
+ }
+
+ \LengowMain::log(\LengowLog::CODE_IMPORT, $synchroMessage, false, $lengowOrder->lengowMarketplaceSku);
+
+ // Redirect to PrestaShop order view
+ return $this->redirect($this->getOrderAdminLink($idOrder));
+ }
+
+ /**
+ * Cancel and re-import order
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function cancelReImportAction(Request $request): Response
+ {
+ $idOrder = (int) $request->get('id_order', 0);
+
+ if (!$idOrder) {
+ $this->addFlash('error', 'Invalid order ID');
+ return $this->redirectToRoute('lengow_admin_order_index');
+ }
+
+ $lengowOrder = new \LengowOrder($idOrder);
+ $newIdOrder = $lengowOrder->cancelAndreImportOrder();
+
+ if (!$newIdOrder) {
+ $newIdOrder = $idOrder;
+ }
+
+ return $this->redirect($this->getOrderAdminLink($newIdOrder));
+ }
+
+ /**
+ * Save shipping method via AJAX
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return JsonResponse
+ */
+ public function saveShippingMethodAction(Request $request): JsonResponse
+ {
+ $idOrder = (int) $request->get('id_order', 0);
+ $shippingMethod = $request->get('method', '');
+
+ $response = ['success' => false, 'message' => ''];
+
+ if (!$idOrder || !$shippingMethod) {
+ $response['message'] = 'Missing parameters';
+ return new JsonResponse($response, 400);
+ }
+
+ try {
+ $result = \Db::getInstance()->update(
+ 'lengow_orders',
+ ['method' => pSQL($shippingMethod)],
+ 'id_order = ' . $idOrder
+ );
+
+ if ($result) {
+ $response = [
+ 'success' => true,
+ 'message' => 'Delivery method successfully updated',
+ ];
+ \LengowMain::log(
+ \LengowLog::CODE_IMPORT,
+ 'Updated shipping method : ' . $shippingMethod,
+ false,
+ $idOrder
+ );
+ } else {
+ $response['message'] = 'No changes or errors';
+ }
+ } catch (\Exception $e) {
+ $response['message'] = 'Error: ' . $e->getMessage();
+ return new JsonResponse($response, 500);
+ }
+
+ return new JsonResponse($response);
+ }
+
+ /**
+ * Force resend action
+ *
+ * @AdminSecurity("is_granted('update', 'AdminLengowOrder')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function forceResendAction(Request $request): Response
+ {
+ $idOrder = (int) $request->get('id_order', 0);
+ $actionType = $request->get('action_type', \LengowAction::TYPE_SHIP);
+
+ if (!$idOrder) {
+ $this->addFlash('error', 'Invalid order ID');
+ return $this->redirectToRoute('lengow_admin_order_index');
+ }
+
+ $lengowOrder = new \LengowOrder($idOrder);
+ $lengowOrder->callAction($actionType);
+
+ return $this->redirect($this->getOrderAdminLink($idOrder));
+ }
+
+ /**
+ * Get order admin link (PrestaShop order view)
+ *
+ * @param int $idOrder
+ * @return string
+ */
+ private function getOrderAdminLink(int $idOrder): string
+ {
+ $link = new \LengowLink();
+ try {
+ if (version_compare(_PS_VERSION_, '1.7.7', '<')) {
+ $href = $link->getAbsoluteAdminLink('AdminOrders')
+ . '&vieworder&id_order=' . $idOrder;
+ } else {
+ $sfParams = ['orderId' => $idOrder];
+ $href = \Link::getUrlSmarty([
+ 'entity' => 'sf',
+ 'route' => 'admin_orders_view',
+ 'sf-params' => $sfParams,
+ ]);
+ }
+ } catch (\PrestaShopException $e) {
+ $href = '#';
+ }
+
+ return $href;
+ }
+}
diff --git a/src/Controller/AdminToolboxController.php b/src/Controller/AdminToolboxController.php
new file mode 100644
index 00000000..b86c6364
--- /dev/null
+++ b/src/Controller/AdminToolboxController.php
@@ -0,0 +1,63 @@
+
+ * @copyright 2017 Lengow SAS
+ * @license http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+namespace PrestaShop\Module\Lengow\Controller;
+
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+/**
+ * Lengow Toolbox Controller for PrestaShop 9
+ */
+class AdminToolboxController extends FrameworkBundleAdminController
+{
+ /**
+ * Toolbox page
+ *
+ * @AdminSecurity("is_granted('read', 'AdminLengowToolbox')")
+ *
+ * @param Request $request
+ * @return Response
+ */
+ public function indexAction(Request $request): Response
+ {
+ $locale = new \LengowTranslation();
+ $lengowLink = new \LengowLink();
+ $module = \Module::getInstanceByName('lengow');
+ $currentController = 'LengowToolboxController';
+
+ return $this->render('@Modules/lengow/views/templates/admin/toolbox/index.html.twig', [
+ 'locale' => $locale,
+ 'lengowPathUri' => $module->getPathUri(),
+ 'lengowUrl' => \LengowConfiguration::getLengowUrl(),
+ 'lengow_link' => $lengowLink,
+ 'displayToolbar' => 0,
+ 'current_controller' => $currentController,
+ 'total_pending_order' => \LengowOrder::countOrderToBeSent(),
+ ]);
+ }
+}
diff --git a/views/templates/admin/_partials/base.html.twig b/views/templates/admin/_partials/base.html.twig
new file mode 100644
index 00000000..0796d6a3
--- /dev/null
+++ b/views/templates/admin/_partials/base.html.twig
@@ -0,0 +1,68 @@
+{#
+ # Copyright 2017 Lengow SAS.
+ #
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
+ # not use this file except in compliance with the License. You may obtain
+ # a copy of the License at
+ #
+ # http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ # License for the specific language governing permissions and limitations
+ # under the License.
+ #
+ # @author Team Connector
+ # @copyright 2017 Lengow SAS
+ # @license http://www.apache.org/licenses/LICENSE-2.0
+ #}
+
+
+
+
+
+
+ {% block title %}Lengow{% endblock %}
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block stylesheets %}{% endblock %}
+
+
+
+ {% if displayToolbar is defined and displayToolbar == 1 %}
+ {% include '@Modules/lengow/views/templates/admin/_partials/header.html.twig' %}
+ {% endif %}
+
+
+ {% block content %}{% endblock %}
+
+
+ {% if displayToolbar is defined and displayToolbar == 1 %}
+ {% include '@Modules/lengow/views/templates/admin/_partials/footer.html.twig' %}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+ {% block javascripts %}{% endblock %}
+
+
diff --git a/views/templates/admin/_partials/footer.html.twig b/views/templates/admin/_partials/footer.html.twig
new file mode 100644
index 00000000..b331ddfa
--- /dev/null
+++ b/views/templates/admin/_partials/footer.html.twig
@@ -0,0 +1,6 @@
+{#
+ # Copyright 2017 Lengow SAS.
+ # Footer for Lengow pages
+ #}
+
+{# Footer content if needed in the future #}
diff --git a/views/templates/admin/_partials/header.html.twig b/views/templates/admin/_partials/header.html.twig
new file mode 100644
index 00000000..6356751e
--- /dev/null
+++ b/views/templates/admin/_partials/header.html.twig
@@ -0,0 +1,68 @@
+{#
+ # Copyright 2017 Lengow SAS.
+ # Navigation header for Lengow pages
+ #}
+
+
diff --git a/views/templates/admin/dashboard/index.html.twig b/views/templates/admin/dashboard/index.html.twig
new file mode 100644
index 00000000..b9bd6388
--- /dev/null
+++ b/views/templates/admin/dashboard/index.html.twig
@@ -0,0 +1,19 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Dashboard{% endblock %}
+
+{% block content %}
+
+
+ {# Dashboard content will be loaded here - For now using a placeholder #}
+ {# The full dashboard content from the legacy templates will be migrated progressively #}
+
+
{{ locale.t('dashboard.title')|default('Dashboard') }}
+
Dashboard content - To be migrated from legacy template
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/admin/feed/index.html.twig b/views/templates/admin/feed/index.html.twig
new file mode 100644
index 00000000..c5ee88ae
--- /dev/null
+++ b/views/templates/admin/feed/index.html.twig
@@ -0,0 +1,18 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Products{% endblock %}
+
+{% block content %}
+
+
+
+
{{ locale.t('feed.title')|default('Products') }}
+ {# Feed/Product content will be migrated from legacy templates #}
+
Feed content - To be migrated from legacy template
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/admin/help/index.html.twig b/views/templates/admin/help/index.html.twig
new file mode 100644
index 00000000..a6eba059
--- /dev/null
+++ b/views/templates/admin/help/index.html.twig
@@ -0,0 +1,18 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Help{% endblock %}
+
+{% block content %}
+
+
+
+
{{ locale.t('help.title')|default('Help') }}
+ {# Help content will be migrated from legacy templates #}
+
Help content - To be migrated from legacy template
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/admin/home/index.html.twig b/views/templates/admin/home/index.html.twig
new file mode 100644
index 00000000..07f53aa8
--- /dev/null
+++ b/views/templates/admin/home/index.html.twig
@@ -0,0 +1,25 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Home{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+ {# Connection content will be loaded via AJAX #}
+
Connection page - Content loaded via AJAX
+
+
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/admin/legals/index.html.twig b/views/templates/admin/legals/index.html.twig
new file mode 100644
index 00000000..f35c72bd
--- /dev/null
+++ b/views/templates/admin/legals/index.html.twig
@@ -0,0 +1,14 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Legals{% endblock %}
+
+{% block content %}
+
+
+
+
{{ locale.t('legals.title')|default('Legal Information') }}
+ {# Legals content will be migrated from legacy templates #}
+
Legals content - To be migrated from legacy template
+
+
+{% endblock %}
diff --git a/views/templates/admin/main_setting/index.html.twig b/views/templates/admin/main_setting/index.html.twig
new file mode 100644
index 00000000..d5237d0f
--- /dev/null
+++ b/views/templates/admin/main_setting/index.html.twig
@@ -0,0 +1,18 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Settings{% endblock %}
+
+{% block content %}
+
+
+
+
{{ locale.t('settings.title')|default('Settings') }}
+ {# Settings content will be migrated from legacy templates #}
+
Settings content - To be migrated from legacy template
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/admin/order_setting/index.html.twig b/views/templates/admin/order_setting/index.html.twig
new file mode 100644
index 00000000..d3a434ac
--- /dev/null
+++ b/views/templates/admin/order_setting/index.html.twig
@@ -0,0 +1,18 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Order Settings{% endblock %}
+
+{% block content %}
+
+
+
+
{{ locale.t('order_setting.title')|default('Order Settings') }}
+ {# Order Settings content will be migrated from legacy templates #}
+
Order Settings content - To be migrated from legacy template
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/admin/orders/index.html.twig b/views/templates/admin/orders/index.html.twig
new file mode 100644
index 00000000..b62319f2
--- /dev/null
+++ b/views/templates/admin/orders/index.html.twig
@@ -0,0 +1,18 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Orders{% endblock %}
+
+{% block content %}
+
+
+
+
{{ locale.t('order.title')|default('Orders') }}
+ {# Orders content will be migrated from legacy templates #}
+
Orders content - To be migrated from legacy template
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/admin/toolbox/index.html.twig b/views/templates/admin/toolbox/index.html.twig
new file mode 100644
index 00000000..30b61335
--- /dev/null
+++ b/views/templates/admin/toolbox/index.html.twig
@@ -0,0 +1,18 @@
+{% extends '@Modules/lengow/views/templates/admin/_partials/base.html.twig' %}
+
+{% block title %}Lengow - Toolbox{% endblock %}
+
+{% block content %}
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+{% endblock %}
diff --git a/views/templates/twig/admin/_partials/base.html.twig b/views/templates/twig/admin/_partials/base.html.twig
new file mode 100644
index 00000000..14e894d2
--- /dev/null
+++ b/views/templates/twig/admin/_partials/base.html.twig
@@ -0,0 +1,41 @@
+
+
+
+
+
+ {% block title %}Lengow - PrestaShop{% endblock %}
+
+ {# PrestaShop admin styles #}
+
+
+ {# Lengow module styles #}
+
+
+
+ {% block stylesheets %}{% endblock %}
+
+
+ {% block header %}
+ {% include '@Modules/lengow/views/templates/twig/admin/_partials/header.html.twig' %}
+ {% endblock %}
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+ {% block footer %}
+ {% include '@Modules/lengow/views/templates/twig/admin/_partials/footer.html.twig' %}
+ {% endblock %}
+
+ {# PrestaShop admin scripts #}
+
+
+
+ {# Lengow module scripts #}
+
+
+ {% block javascripts %}{% endblock %}
+
+
diff --git a/views/templates/twig/admin/_partials/footer.html.twig b/views/templates/twig/admin/_partials/footer.html.twig
new file mode 100644
index 00000000..85e1d429
--- /dev/null
+++ b/views/templates/twig/admin/_partials/footer.html.twig
@@ -0,0 +1,16 @@
+{# Lengow Footer #}
+
diff --git a/views/templates/twig/admin/_partials/header.html.twig b/views/templates/twig/admin/_partials/header.html.twig
new file mode 100644
index 00000000..70c91fcc
--- /dev/null
+++ b/views/templates/twig/admin/_partials/header.html.twig
@@ -0,0 +1,60 @@
+{# Lengow Header with Navigation #}
+
diff --git a/views/templates/twig/admin/dashboard/index.html.twig b/views/templates/twig/admin/dashboard/index.html.twig
new file mode 100644
index 00000000..ca71eee3
--- /dev/null
+++ b/views/templates/twig/admin/dashboard/index.html.twig
@@ -0,0 +1,61 @@
+{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %}
+
+{% block title %}{{ 'Dashboard'|trans({}, 'Modules.Lengow.Admin') }} - Lengow{% endblock %}
+
+{% block content %}
+
+
+
+ {% if not pluginIsUpToDate %}
+
+ {% endif %}
+
+
+ {# Merchant status #}
+ {% if merchantStatus %}
+
+
{{ 'Account status'|trans({}, 'Modules.Lengow.Admin') }}
+
+
{{ 'Status'|trans({}, 'Modules.Lengow.Admin') }}:
+ {% if merchantStatus.type == 'success' %}
+ {{ merchantStatus.message }}
+ {% else %}
+ {{ merchantStatus.message }}
+ {% endif %}
+
+
+
+ {% endif %}
+
+ {# Statistics placeholder #}
+
+
{{ 'Statistics'|trans({}, 'Modules.Lengow.Admin') }}
+
+
{{ 'Dashboard statistics'|trans({}, 'Modules.Lengow.Admin') }}
+
+
+
+ {# Quick actions #}
+
+
{{ 'Quick actions'|trans({}, 'Modules.Lengow.Admin') }}
+
+
+
+
+{% endblock %}
diff --git a/views/templates/twig/admin/home/index.html.twig b/views/templates/twig/admin/home/index.html.twig
new file mode 100644
index 00000000..0b60a835
--- /dev/null
+++ b/views/templates/twig/admin/home/index.html.twig
@@ -0,0 +1,25 @@
+{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %}
+
+{% block title %}{{ 'Connection'|trans({}, 'Modules.Lengow.Admin') }} - Lengow{% endblock %}
+
+{% block content %}
+
+
+
+
+ {% if isNewMerchant %}
+
+
{{ 'Welcome to Lengow'|trans({}, 'Modules.Lengow.Admin') }}
+
{{ 'Please connect your Lengow account to start using the module'|trans({}, 'Modules.Lengow.Admin') }}.
+
+ {% else %}
+
+
{{ 'Account connected'|trans({}, 'Modules.Lengow.Admin') }}
+
{{ 'Your Lengow account is successfully connected'|trans({}, 'Modules.Lengow.Admin') }}.
+
+ {% endif %}
+
+
+{% endblock %}
diff --git a/views/templates/twig/admin/orders/_partials/last_importation.html.twig b/views/templates/twig/admin/orders/_partials/last_importation.html.twig
new file mode 100644
index 00000000..c78193bc
--- /dev/null
+++ b/views/templates/twig/admin/orders/_partials/last_importation.html.twig
@@ -0,0 +1,36 @@
+{#
+ # Copyright 2017 Lengow SAS.
+ #
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
+ # not use this file except in compliance with the License. You may obtain
+ # a copy of the License at
+ #
+ # http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ # License for the specific language governing permissions and limitations
+ # under the License.
+ #
+ # @author Team Connector
+ # @copyright 2017 Lengow SAS
+ # @license http://www.apache.org/licenses/LICENSE-2.0
+ #}
+
+{% if orderCollection.last_import_type != 'none' %}
+
+ {{ locale.t('order.screen.last_order_importation') }} : {{ orderCollection.last_import_date }}
+
+{% else %}
+
+ {{ locale.t('order.screen.no_order_importation') }}
+
+{% endif %}
+
+{% if reportMailEnabled %}
+
+ {{ locale.t('order.screen.all_order_will_be_sent_to') }} {{ report_mail_address|join(', ') }}
+ ({{ locale.t('order.screen.change_this') }} )
+
+{% endif %}
diff --git a/views/templates/twig/admin/orders/_partials/no_order.html.twig b/views/templates/twig/admin/orders/_partials/no_order.html.twig
new file mode 100644
index 00000000..ae732ca8
--- /dev/null
+++ b/views/templates/twig/admin/orders/_partials/no_order.html.twig
@@ -0,0 +1,29 @@
+{#
+ # Copyright 2017 Lengow SAS.
+ #
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
+ # not use this file except in compliance with the License. You may obtain
+ # a copy of the License at
+ #
+ # http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ # License for the specific language governing permissions and limitations
+ # under the License.
+ #
+ # @author Team Connector
+ # @copyright 2017 Lengow SAS
+ # @license http://www.apache.org/licenses/LICENSE-2.0
+ #}
+
+
+
+
+
{{ locale.t('order.screen.no_order_title') }}
+
{{ locale.t('order.screen.no_order_message') }}
+
+
diff --git a/views/templates/twig/admin/orders/_partials/warning_message.html.twig b/views/templates/twig/admin/orders/_partials/warning_message.html.twig
new file mode 100644
index 00000000..6ce4af4a
--- /dev/null
+++ b/views/templates/twig/admin/orders/_partials/warning_message.html.twig
@@ -0,0 +1,25 @@
+{#
+ # Copyright 2017 Lengow SAS.
+ #
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
+ # not use this file except in compliance with the License. You may obtain
+ # a copy of the License at
+ #
+ # http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ # License for the specific language governing permissions and limitations
+ # under the License.
+ #
+ # @author Team Connector
+ # @copyright 2017 Lengow SAS
+ # @license http://www.apache.org/licenses/LICENSE-2.0
+ #}
+
+{% if warning_message %}
+
+ {{ warning_message|raw }}
+
+{% endif %}
diff --git a/views/templates/twig/admin/orders/index.html.twig b/views/templates/twig/admin/orders/index.html.twig
new file mode 100644
index 00000000..ac83ed47
--- /dev/null
+++ b/views/templates/twig/admin/orders/index.html.twig
@@ -0,0 +1,89 @@
+{#
+ # Copyright 2017 Lengow SAS.
+ #
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
+ # not use this file except in compliance with the License. You may obtain
+ # a copy of the License at
+ #
+ # http://www.apache.org/licenses/LICENSE-2.0
+ #
+ # Unless required by applicable law or agreed to in writing, software
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ # License for the specific language governing permissions and limitations
+ # under the License.
+ #
+ # @author Team Connector
+ # @copyright 2017 Lengow SAS
+ # @license http://www.apache.org/licenses/LICENSE-2.0
+ #}
+
+{% extends '@Modules/lengow/views/templates/twig/admin/_partials/base.html.twig' %}
+
+{% block lengow_content %}
+
+
+ {% if debugMode %}
+
+ {{ locale.t('menu.debug_active') }}
+
+ {% endif %}
+
+
+ {# Warning Messages #}
+
+ {% include '@Modules/lengow/views/templates/twig/admin/orders/_partials/warning_message.html.twig' %}
+
+
+
+ {# Last Importation Info #}
+
+ {% include '@Modules/lengow/views/templates/twig/admin/orders/_partials/last_importation.html.twig' %}
+
+
+ {# Messages Container #}
+
+
+
+
+
+ {# Loading Indicator #}
+
+
+
{{ locale.t('order.screen.import_charge_first') }}
+
{{ locale.t('order.screen.import_charge_second') }}
+
+
+ {# Orders Table #}
+
+
+ {% if nb_order_imported == 0 %}
+ {% include '@Modules/lengow/views/templates/twig/admin/orders/_partials/no_order.html.twig' %}
+ {% else %}
+ {{ lengow_table|raw }}
+ {% endif %}
+
+
+
+
+
+
+
+{% endblock %}