From 7e7810538772c788383dd83c1b3cb5ddf75ca95e Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 10:24:42 +0200 Subject: [PATCH 01/19] docs: le poste saura se mettre a jour depuis son ecran Conception validee : un bouton de l'ecran d'administration telecharge la version publiee sur GitHub, verifie son empreinte, et laisse update.ps1 faire la bascule avec son retour arriere. Le service tourne deja en LocalSystem : il n'y a pas d'elevation a obtenir. Deux defauts existants sont nommes au passage. La tache du kiosque n'est relancee par personne apres schtasks /end - ni install.ps1 ni update.ps1 - donc l'ecran client reste noir jusqu'a une ouverture de session ; cela ne se voyait pas parce qu'un humain qui met a jour finit par redemarrer le poste. Et update.ps1 ne distingue pas "echoue, restaure, poste sain" de "echoue, poste mort", alors que les deux ne demandent pas la meme chose a un benevole. Le depot suivi devient un reglage - le code est sous AGPL, une coop doit pouvoir suivre son fork - mais en couple owner/repo et non en URL : un champ acceptant une URL entiere ferait de "enregistrer la configuration" un "executer du code de n'importe ou en LocalSystem". --- ...6-07-29-mise-a-jour-depuis-admin-design.md | 442 ++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md diff --git a/docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md b/docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md new file mode 100644 index 0000000..0dc3ea9 --- /dev/null +++ b/docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md @@ -0,0 +1,442 @@ +# Mettre à jour un poste depuis l'écran d'administration + +> Conception validée le 29/07/2026. Elle décrit ce qui doit être construit, pas ce qui +> existe. L'implémentation n'a pas commencé. + +## Le problème + +Mettre à jour un poste demande aujourd'hui qu'un humain télécharge une archive, la +décompresse, ouvre une console **en administrateur** et lance `update.ps1`. Aucun bénévole +de la coopérative ne fera ça. Conséquence mesurable : un correctif publié reste sur GitHub, +et le poste tourne sur la version qu'il avait le jour de son installation. + +Ce document décrit un bouton dans l'écran d'administration qui fait le même geste, avec les +mêmes garanties, sans console et sans humain qualifié. + +## Ce qui existe déjà, et qu'on ne réécrit pas + +Quatre pièces sont en place et portent l'essentiel du travail. + +| Pièce | Ce qu'elle apporte | +|---|---| +| `deploy/windows/update.ps1` | Arrêt des détenteurs du binaire, sauvegarde horodatée, copie, redémarrage, contrôle `/healthz`, **retour arrière automatique** | +| `.github/workflows/release.yml` | Trois archives et `SHA256SUMS-archives.txt` publiées sur un tag, front reconstruit avant empaquetage | +| `internal/web/embed.go` | `//go:embed all:dist` — l'écran client et les pages d'administration sont **dans** le binaire | +| `internal/platform/service_windows.go` | Le service est installé sans `ServiceStartName`, donc il tourne en `LocalSystem` | + +Trois conséquences directes : + +1. **Un seul artefact à remplacer.** Remplacer `openscale.exe` remplace l'interface. Rien + à déployer à côté, aucun cache à invalider — `internal/web/images.go` sert déjà + `index.html` et `admin.html` en `Cache-Control: no-store`, et les bundles portent une + empreinte dans leur nom. +2. **Le processus qui sert `/admin/api` a déjà les droits.** Écrire dans `Program Files`, + arrêter un service, piloter le planificateur de tâches : `LocalSystem` peut tout ça. Il + n'y a pas d'élévation à obtenir, donc pas d'assistant UAC à contourner. +3. **La procédure de bascule est écrite et éprouvée.** Le bouton n'a pas à la réinventer, + seulement à la déclencher. + +## Décisions, et ce qui les a tranchées + +| # | Décision | Pourquoi | +|---|---|---| +| 1 | **Le bouton installe tout**, avec retour arrière | Un bouton qui se contente de signaler laisse le geste coûteux à la charge de quelqu'un qui ne le fera pas | +| 2 | **Go orchestre, `update.ps1` bascule** | Le retour arrière existe, il est couvert par `deploy/deploy_test.go`, et une seconde implémentation divergerait — c'est la leçon de `ShutdownBudget` | +| 3 | **TLS + empreinte SHA-256**, pas de signature | Décision du commanditaire. Protège du téléchargement abîmé, pas d'un compte GitHub compromis. Voir « Modèle de menace » | +| 4 | **Windows d'abord**, Linux plus tard | Les quatre postes de la coopérative sont sous Windows. Une implémentation systemd que rien n'exécuterait ne prouverait rien | +| 5 | **Sondage quotidien, pastille au tableau de bord** | Sans sondage, personne ne saura qu'un correctif existe. C'est le scénario qui laisse un bug six mois en production | +| 6 | **Le dépôt est un réglage**, en `owner/repo` | Le code est sous AGPL : une coopérative doit pouvoir suivre son fork. Mais un champ acceptant une URL entière ferait de « enregistrer la configuration » un « exécuter du code de n'importe où en `LocalSystem` » | +| 7 | **Refus si une pesée ou un import est en cours** | Couper au milieu d'un import perd le CSV, dont la suppression vaut acquittement | +| 8 | **Un poste `OutOfService` peut se mettre à jour** | C'est la sortie de secours d'une version cassée. La refuser fermerait la seule porte | +| 9 | **Neuvième page « Mise à jour »** | La page Poste porte déjà « Cinq versions restaurables », qui parle des versions de la **configuration**. Deux sens du mot sur un même écran, devant un bénévole, c'est un défaut d'usage | + +### Modèle de menace, écrit parce qu'il est assumé + +Le poste télécharge un binaire et l'exécute en `LocalSystem`. L'empreinte SHA-256 vient de +la même Release que l'archive : elle prouve que le téléchargement n'a pas été abîmé, elle +**ne prouve pas** que la Release est légitime. Quiconque obtient le droit de publier sur le +dépôt configuré obtient l'exécution de code privilégié sur les quatre postes. + +Ce risque n'est pas neuf — un bénévole qui suit `INSTALLATION.md` télécharge et exécute la +même archive sans vérifier davantage. Ce qui change est le **délai** : le bouton propage en +une journée ce qu'un geste manuel propageait en plusieurs mois. + +Ce qui le contient, et qui doit être tenu : + +- le dépôt est un couple `owner/repo` **validé par le contrôle 48**, jamais une URL ; l'hôte + `api.github.com` est compilé dans le binaire ; +- le changer est un acte protégé par le mot de passe, **journalisé en `warn`** avec + l'ancienne et la nouvelle valeur, et lisible dans `diagnostic.zip` ; +- le dépôt entre dans `Fingerprint()` : un poste qui suit un autre dépôt que ses voisins se + voit à l'œil sur les huit caractères du tableau de bord. + +Passer à une signature détachée (clé publique compilée, clé privée hors GitHub) reste +possible plus tard sans rien casser : ce serait une vérification de plus dans `Stager`. + +## Architecture + +Un paquet `internal/update` porte tout ce qui est calculable. Une seule fonction est +privilégiée, et elle vit dans `internal/platform`. + +```mermaid +flowchart TD + W["internal/web
3 routes /admin/api/update"] --> S["update.Service"] + S --> V["update.Version
analyse et comparaison"] + S --> SRC["update.Source (interface)
Latest(ctx)"] + S --> ST["update.Stager
télécharge, vérifie, extrait"] + S --> G["station.Hub.UpdateGuard()"] + S --> AP["platform.ApplyUpdate
_windows.go seul"] + SRC --> GH["update.GitHubSource
api.github.com"] + AP --> PS["powershell -File update.ps1
détaché"] + WK["station worker
sondage quotidien, horloge injectée"] --> S +``` + +### Les types + +**`update.Version`** — analyse `v?MAJOR.MINOR[.PATCH][-PRERELEASE]`, compare +numériquement. Aucune E/S, aucune horloge. Une version portant un suffixe est une +préversion et n'est **jamais** proposée. + +**`update.Source`** — interface définie côté consommateur, une méthode : + +```go +// Source reports the newest release a station could move to. +type Source interface { + // Latest returns the newest stable release, or ErrNoRelease when the + // repository has published none. + Latest(ctx context.Context) (Release, error) +} +``` + +`update.GitHubSource` l'implémente sur `GET /repos/{owner}/{repo}/releases/latest`. Ce +point d'entrée exclut déjà les brouillons et les préversions : c'est le contrat de l'API, et +c'est pourquoi il est préféré à `/releases`. Un dépôt qui n'a publié que des préversions +répond `404`, ce qui devient `ErrNoRelease` et non une panne. + +**`update.Release`** porte `Tag`, `Version`, `PublishedAt`, `HTMLURL` et la liste des +archives publiées. + +**`update.Stager`** — descend l'archive de la plateforme, vérifie, extrait : + +1. cherche l'archive `openscale--windows-amd64.zip` dans les fichiers de la Release ; + absente → `ERR-UPD-08` ; +2. descend `SHA256SUMS-archives.txt` et y lit la ligne de cette archive ; absent → même + code ; +3. descend l'archive, calcule le SHA-256 **en écrivant sur disque** (jamais en mémoire : + l'archive fait des dizaines de méga-octets), compare ; écart → `ERR-UPD-02`, le + répertoire de staging est effacé ; +4. extrait dans `/updates//`. + +L'extraction refuse toute entrée dont le chemin nettoyé sort du répertoire cible +(`..`, chemin absolu, séparateur inversé) et toute entrée dépassant 256 Mio décompressés. +Ce n'est pas une précaution théorique : l'archive vient du réseau et est extraite par un +processus `LocalSystem`. + +### Le staging + +L'archive du Makefile contient un répertoire de tête. Après extraction : + +``` +/updates/ + check.json ← dernier sondage : checked_at, tag, version, + published_at, html_url + pending.json ← une bascule est en vol + outcome.json ← écrit par update.ps1, pas encore lu + outcome-.json ← comptes rendus déjà versés au journal (3 gardés) + 2.1.0/ + openscale-2.1.0-windows-amd64/ + openscale.exe + update.ps1 + common.ps1 + … +``` + +`` est la racine de données du poste (`ProgramData\OpenScale` sous Windows), sur +laquelle `install.ps1:126` pose déjà les droits. **Aucune migration de base n'est +nécessaire** : tout l'état de ce chantier tient dans des fichiers. + +### Le flux, et le moment où le poste meurt + +```mermaid +sequenceDiagram + participant F as Écran admin + participant S as openscale serve (LocalSystem) + participant G as api.github.com + participant P as update.ps1 (détaché) + + F->>S: POST /admin/api/update/apply {"version":"2.1.0"} + S->>S: mot de passe (ADR-033), garde-fous, version attendue + S->>G: archive + SHA256SUMS-archives.txt + S->>S: empreinte, extraction, écrit pending.json + S->>P: démarre détaché + S-->>F: 202 Accepted + F->>S: sonde /healthz toutes les 2 s + Note over S: le service s'arrête — le poste ne répond plus + P->>P: arrêt, sauvegarde, copie, redémarrage + P->>P: /healthz, sinon restauration + P->>P: écrit outcome.json, relance la tâche kiosque + F->>S: /healthz répond de nouveau + F->>S: GET /admin/api/update + S-->>F: le compte rendu, et la version qui tourne +``` + +Au démarrage suivant, le binaire — **le neuf ou l'ancien restauré** — trouve `outcome.json`, +le verse au journal technique, efface `pending.json`, et renomme le compte rendu en +`outcome-.json`. Le renommage est ce qui rend l'opération idempotente : un poste +redémarré trois fois ne journalise pas trois fois la même bascule. `GET /admin/api/update` +sert le plus récent des `outcome-*.json`, si bien qu'un navigateur fermé au mauvais moment +ne perd rien. + +Le répertoire de staging nommé par `pending.json` est effacé au même moment, **quelle que +soit l'issue** : une bascule annulée laisse sinon des dizaines de méga-octets sur un poste +dont personne ne surveille le disque. Les trois derniers comptes rendus sont gardés, le +reste est effacé. + +## Le contrat `update.ps1` + +Le script cesse d'être « ce qu'un humain lit avant de le lancer ». Il devient une interface, +et trois choses lui sont dues. + +### Ce qui entre + +`-Source`, `-InstallDir`, `-DataRoot` existent déjà. Go les passe **explicitement**, dérivés +de `os.Executable()` et non des valeurs par défaut de `Get-OpenScalePaths` : un poste +installé ailleurs ne doit pas dépendre d'une devinette. + +S'ajoutent `-OutcomePath ` et `-LogPath `. + +### Ce qui sort + +Aujourd'hui : `0` ou `1`. Quatre issues qui ne demandent pas la même chose à un humain : + +| Code | Sens | Ce que le poste en fait | +|---|---|---| +| `0` | Basculé, le poste répond | Succès, la nouvelle version est affichée | +| `10` | Échec, version précédente restaurée, poste sain | `ERR-UPD-06` — la mise à jour a été annulée, le poste fonctionne | +| `11` | Échec, restauré, le poste **ne répond pas** | `ERR-UPD-07` — panne : `doctor`, et le chemin de la sauvegarde | +| `12` | Échec **avant** toute copie | Rien n'a bougé. On peut recliquer | + +Et `outcome.json`, écrit **dans les quatre cas** : + +```json +{ + "status": "succeeded", + "exit_code": 0, + "from": "2.0.3", + "to": "2.1.0", + "reason": "", + "backup": "C:\\ProgramData\\OpenScale\\backups\\openscale-20260729-101500.exe", + "database_backups": [], + "finished_at": "2026-07-29T10:16:04+02:00" +} +``` + +`status` vaut `succeeded`, `rolled-back`, `rolled-back-unhealthy` ou `not-started`. +`database_backups` porte les copies `openscale.db.before-*` apparues pendant la bascule — +le script les relève déjà pour son message de fin, il les écrit maintenant aussi. + +### La correction qui manquait : relancer le kiosque + +`Stop-OpenScaleBinaryHolders` (`common.ps1:338`) fait `schtasks /end` sur la tâche du +kiosque, `openscale-kiosk.xml:38` n'a qu'un `LogonTrigger`, et **rien ne la redémarre** : +ni `install.ps1`, ni `update.ps1`. Après une mise à jour d'aujourd'hui, l'écran client reste +noir jusqu'à une ouverture de session. + +C'est un défaut existant. Il ne s'est jamais vu parce qu'un humain qui met à jour un poste +finit par le redémarrer. Un bénévole qui clique un bouton, lui, regarde l'écran client. + +`schtasks /run /tn OpenScale-Kiosk` en fin de script, **sur les quatre chemins de sortie** — +un retour arrière réussi qui laisserait l'écran noir serait une panne créée par la +réparation. + +## L'API HTTP + +| Route | Protection | Rôle | +|---|---|---| +| `GET /admin/api/update` | libre | Version installée, dernier sondage, version disponible, dépôt suivi, dernier compte rendu | +| `POST /admin/api/update/check` | mot de passe | Sonder maintenant, sans attendre le tour quotidien | +| `POST /admin/api/update/apply` | mot de passe | Corps `{"version":"2.1.0"}`. Répond `202`, puis le poste tombe | + +La lecture est libre parce qu'ADR-033 ouvre les pages en lecture ; les deux `POST` entrent +dans la carte `guarded` de `server.go`, comme tous les `POST` existants. + +Le corps d'`apply` porte la version **que l'écran montrait**. Si une autre est devenue +disponible entre l'affichage et le clic, c'est un `409` : le bénévole valide ce qu'il a lu, +jamais ce qui est arrivé depuis. + +### Le sondage + +Un worker de `internal/station/workers.go`, **sur l'horloge injectée** : un test fait passer +trente jours sans en attendre un seul. Il sonde au démarrage après cinq minutes de grâce, +puis toutes les vingt-quatre heures, et écrit `check.json`. + +**Le sondage ne télécharge rien** : il lit un JSON de quelques kilo-octets. Le zip ne +descend qu'au clic. La limite anonyme de GitHub — soixante requêtes par heure et par IP — +n'est pas un sujet pour quatre postes. + +Un sondage qui échoue s'écrit au journal en `warn` et **n'allume aucun feu** au tableau de +bord. Un magasin dont la connexion est tombée n'est pas un poste en panne. + +## Les garde-fous + +Une seule question, posée au Hub, qui répond en français : + +```go +// UpdateGuard reports whether the station may be taken down for an update, and +// says in French why not when it may not. +func (h *Hub) UpdateGuard() (bool, string) +``` + +| État | Réponse | +|---|---| +| `Idle` | passe | +| Pesée en cours, impression en cours | refuse — « Une pesée est en cours. Réessayez dans un instant. » | +| Import de catalogue en vol | refuse — « Un catalogue est en cours de lecture. » | +| `OutOfService` | **passe** — c'est la sortie de secours d'une version cassée | +| Une bascule déjà en vol (`pending.json` présent) | refuse — `ERR-UPD-04` | + +La couche web ne lit pas l'état pour en déduire une règle : elle pose la question et rend la +phrase. La règle vit là où vit l'état. + +## La configuration + +Un bloc `update` à côté de `maintenance` : + +```json +"update": { "repository": "lostmind84/OpenScale" } +``` + +**Un contrôle nouveau — le 48, prochain numéro libre au 29/07/2026** — `update.repository` +respecte `^[A-Za-z0-9_.-]{1,39}/[A-Za-z0-9_.-]{1,100}$`. +Pas de schéma, pas d'hôte, pas de `..`, pas de barre oblique surnuméraire. L'hôte reste +compilé. + +**L'absence de la clé est légale** et vaut `lostmind84/OpenScale`. C'est le symétrique exact +du défaut du 28 juillet, où le contrôle 20 a fait refuser au poste sa propre configuration +livrée : un fichier écrit avant cette version doit se relire sans rien. Et les deux +configurations livrées — `testdata/config-lacagette.json` et `testdata/config-demo.json` — +gagnent la clé **dans le même commit**, avec le test qui les charge toutes les deux. + +Le bloc entre dans `Export(false)`, donc dans `Fingerprint()`. C'est voulu : les quatre +postes d'une coopérative doivent suivre le même dépôt, et une divergence se voit à l'œil. + +Une coopérative qui héberge sur GitLab ou Forgejo n'a pas le bouton et garde `update.ps1` à +la main. L'API des Releases n'a pas la même forme d'un hébergeur à l'autre, et un adaptateur +par hébergeur coûterait cher pour un besoin que personne n'a encore. + +## L'écran + +**Neuvième page du rail, « Mise à jour »**, en fin du groupe « Réglages ». + +``` +── Mise à jour ──────────────────────────────── + Version installée 2.0.3 + Version disponible 2.1.0 (28/07/2026) [voir les notes ↗] + Dépôt suivi lostmind84/OpenScale + Dernière vérification 29/07/2026 08:12 + + [ Installer la version 2.1.0 ] ← rouge plein, 72 px + + Dernière tentative : aucune +``` + +**Au tableau de bord, une pastille, pas un feu.** « Version 2.1.0 disponible », en texte +neutre, qui mène à cette page. `lib/lights.ts` réserve les couleurs aux pannes ; un poste +sain ne s'allume pas parce qu'un correctif est sorti. + +**Les notes de version ne sont pas rendues.** Le corps d'une Release est du Markdown venu du +réseau : le rendre demande une bibliothèque de plus et ouvre une injection dans l'écran +d'administration, pour un gain nul. Numéro, date, et un lien vers la page GitHub. + +**Le bouton est rouge plein** (ADR-037). Le retour arrière est automatique sur le binaire, +il ne l'est **pas** sur la base : les migrations s'appliquent au démarrage, et y revenir est +un geste humain que `update.ps1` décrit déjà. Panneau de confirmation, cible 72 px, et trois +phrases qui disent ce qui va se passer : + +> Le poste va s'arrêter environ une minute. L'écran client s'éteindra puis reviendra tout +> seul. Si la nouvelle version ne démarre pas, la précédente sera remise automatiquement — +> mais les données enregistrées, elles, ne reviendront pas en arrière. + +**Pendant la bascule, l'écran sonde et encaisse les erreurs.** Pas de SSE : le serveur meurt, +la connexion aussi. Une boucle `fetch` sur `/healthz` toutes les deux secondes, où **une +erreur réseau est le cas nominal** — c'est le symétrique du défaut du 27 juillet, où +`refresh()` vidait le champ d'erreur toutes les trois secondes. L'écran ne conclut que sur +deux choses : une version qui répond, ou cinq minutes écoulées, auquel cas il dit d'aller +voir le poste. + +Le champ `update.repository` s'édite sur cette page, dans une zone « Avancé », avec le +brouillon et le panneau de mot de passe des autres pages de réglage. + +## Erreurs et journal + +Huit codes, préfixe `ERR-UPD`, chacun avec un message français complet. + +| Code | Quand | Message | +|---|---|---| +| `ERR-UPD-01` | GitHub injoignable | « Impossible de joindre le serveur des versions. » | +| `ERR-UPD-02` | Empreinte fausse | « Le fichier téléchargé est abîmé. Rien n'a été installé. » | +| `ERR-UPD-03` | Poste occupé | La phrase rendue par `UpdateGuard` | +| `ERR-UPD-04` | Bascule déjà en vol | « Une mise à jour est déjà en cours. » | +| `ERR-UPD-05` | Plateforme non gérée | « La mise à jour depuis l'écran n'existe que sous Windows. » | +| `ERR-UPD-06` | `outcome.json` dit `rolled-back` | « La mise à jour a échoué, la version précédente a été remise. Le poste fonctionne. » | +| `ERR-UPD-07` | `outcome.json` dit `rolled-back-unhealthy` | « La mise à jour a échoué et le poste ne répond pas. Lancez `openscale doctor`. » | +| `ERR-UPD-08` | Archive absente de la Release | « Cette version ne contient pas de fichier pour ce poste. » | + +Journal technique : `info` pour une vérification et une version trouvée ; `warn` pour un +sondage échoué et pour un **changement de dépôt** ; `error` pour `-02`, `-06` et `-07`. Le +compte rendu de bascule y est versé au démarrage suivant. + +## Tests + +Tout ce qui suit s'exécute sans matériel, sans réseau, et sans attendre. + +| Sujet | Ce qui le prouve | +|---|---| +| `update.Version` | Table de cas : `2.1.0 > 2.0.3`, `v2.1.0 == 2.1.0`, `2.1.0 > 2.1.0-rc1`, `2.1 == 2.1.0` ; une préversion n'est jamais proposée | +| `update.GitHubSource` | `httptest.Server` servant une charge utile **réelle** de l'API Releases, versée en `testdata` — la donnée fait foi contre la documentation, comme le corpus de trames | +| `ErrNoRelease` | Un `404` sur `/releases/latest` n'est pas une panne | +| `update.Stager` | Zip fabriqué dans le test : empreinte juste → accepté ; **un octet changé** → `ERR-UPD-02` et le staging est effacé ; archive absente → `ERR-UPD-08` | +| Extraction | Une entrée `../evil.exe`, un chemin absolu et une bombe de décompression sont refusés | +| `UpdateGuard` | Chaque état : `Idle` passe, `Printing` refuse, import en vol refuse, `OutOfService` **passe** | +| Les trois routes | Protection du mot de passe, `409` sur version périmée, `409` sur garde-fou, `501` hors Windows | +| Le sondage | Horloge injectée : trente jours passent en un test ; un échec réseau n'allume aucun feu | +| Le compte rendu | Trois démarrages successifs sur un `outcome.json` ne journalisent qu'une ligne | +| Contrôle 48 | Huit valeurs fautives refusées (`https://…`, `a/b/c`, `../x`, vide, une seule partie, caractères interdits, trop long, espace) | +| Configuration livrée | `config-lacagette.json` et `config-demo.json` chargent ; l'absence de la clé vaut le défaut ; l'empreinte change quand le dépôt change | +| `update.ps1` | `deploy/deploy_test.go` : `-OutcomePath` accepté, `outcome.json` écrit et `schtasks /run` appelé **sur les quatre chemins de sortie** | +| Le front | La boucle de sondage traite une erreur réseau comme le cas nominal ; la pastille ; le panneau de confirmation ; le rendu des quatre issues ; aucun renvoi `§X.Y` visible | + +### Le risque à lever en premier, sur le banc + +**La PowerShell détachée survit-elle à l'arrêt du service par le SCM ?** `DETACHED_PROCESS` +sans objet de travail hérité devrait suffire — mais « devrait » est le mot qui a coûté cinq +cycles d'alimentation la semaine du 29 juillet. + +Cet essai se fait **avant** d'écrire quoi que ce soit d'autre. Il coûte vingt minutes et il +décide de l'approche : si le processus détaché meurt avec son parent, l'approche +« `update.ps1` fait la bascule » tombe, et il faut écrire la bascule en Go dans une +sous-commande lancée depuis le binaire neuf. + +## Ce qui n'est pas au périmètre + +Dit ici pour ne pas être redemandé : + +- pas de mise à jour automatique planifiée — un poste ne change pas sans que personne ne + l'ait demandé ; +- pas de choix de version ni de rétrogradation depuis l'écran : les sauvegardes horodatées + et `TROUBLESHOOTING.md` restent le chemin ; +- pas de signature de code, ni détachée ni Authenticode ; +- pas de Linux — `update.sh` reste manuel, l'écran répond `501` ; +- pas de rendu des notes de version ; +- pas de mise à jour des quatre postes depuis un seul écran ; +- pas d'hébergeur autre que GitHub. + +## Documentation touchée + +- `docs/02-architecture.md` — une section, et **ADR-040 : la mise à jour se déclenche depuis + l'écran, `update.ps1` l'exécute** ; +- `docs/03-glossaire.md` — les identifiants du paquet `internal/update`, le bloc `update`, + les huit codes `ERR-UPD` ; +- `TROUBLESHOOTING.md` — les quatre issues et ce qu'un bénévole en fait ; +- `INSTALLATION.md` — la mise à jour cesse d'être une procédure de console ; +- `SUIVI.md` — le lot et son état. From 8db40db3821b7a7a8025da97ab55dd8f84154db3 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 10:37:56 +0200 Subject: [PATCH 02/19] docs: le plan d'implementation de la mise a jour depuis l'ecran Treize taches, en TDD, chacune avec son cycle de test et son commit. La tache 0 est une MESURE et non du code : la PowerShell detachee survit-elle a l'arret du service par le SCM ? Si non, les taches 6 et 7 changent de forme et la bascule passe en Go. Vingt minutes sur le banc, avant d'ecrire quoi que ce soit. Deux ecarts assumes par rapport a la spec, a y reporter en tache 13 : ERR-UPD-09, parce que "la version a change depuis l'affichage" et "le poste est occupe" ne demandent pas la meme chose a un benevole ; et Status.Supported cable a runtime.GOOS plutot que deduit en appelant ApplyUpdate, ce qui lancerait une PowerShell pour repondre a une question. --- .../2026-07-29-mise-a-jour-depuis-admin.md | 3829 +++++++++++++++++ 1 file changed, 3829 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md diff --git a/docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md b/docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md new file mode 100644 index 0000000..3f18139 --- /dev/null +++ b/docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md @@ -0,0 +1,3829 @@ +# Mise à jour depuis l'écran d'administration — plan d'implémentation + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development +> (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** un bouton de l'écran d'administration met le poste à jour depuis la version +publiée sur GitHub, avec vérification d'empreinte et retour arrière automatique. + +**Architecture:** un paquet `internal/update` porte tout ce qui est calculable — analyse de +version, lecture de l'API Releases, téléchargement vérifié, extraction. Une seule fonction +est privilégiée, `platform.ApplyUpdate`, qui lance `update.ps1` **détaché** ; ce script +existe déjà et fait la bascule avec son retour arrière. L'état vit dans des fichiers sous +`/updates/`, jamais en base : rien à migrer. + +**Tech Stack:** Go 1.26.5 (bibliothèque standard seule), PowerShell 5.1 pour la bascule, +Svelte 5 (runes) pour l'écran, Vitest pour le front. + +**Spec:** `docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md` + +**Branche:** `design/mise-a-jour-depuis-admin` + +## Global Constraints + +Chaque tâche hérite implicitement de cette section. + +- **Zéro cgo.** Le binaire se croise-compile vers windows/amd64, linux/amd64 et + linux/arm64 en `CGO_ENABLED=0`. +- **Aucune dépendance nouvelle.** Tout ce plan tient dans la bibliothèque standard : + `net/http`, `archive/zip`, `crypto/sha256`, `encoding/json`, `os/exec`, `regexp`. + `make deps` compare `go.mod` à l'inventaire de `docs/02-architecture.md` §17.1 dans les + deux sens (ADR-039) : ajouter un module ferait rougir cette cible. +- **Le code est en anglais** — identifiants, types, champs, clés JSON, routes, **et les + commentaires**. La documentation est en français. Les messages destinés aux bénévoles + sont en **français**, et vivent dans la couche de présentation, jamais dans le paquet + qui nomme la condition (`cmd/openscale/errors.go` en donne la raison). +- **`godoc` sur tout élément exporté** : commentaire commençant par le nom de l'élément, + phrase complète. `TSDoc` en TypeScript et Svelte. +- **`internal/domain` n'importe ni `os` ni `time.Now()`** — coupes 1 et 1 bis, vérifiées + par `make boundary`. +- **Aucun test ne dort, n'ouvre le réseau ni n'exige de matériel.** Le temps se dépense sur + `ports.Clock`, jamais sur l'horloge murale. Le réseau se sert par `httptest.Server`. +- **Aucun renvoi `§X.Y` ni `ADR-0XX` dans un texte visible à l'écran.** + `web/test/admin-wording.test.ts` l'interdit, y compris dans les chaînes composées en + ` +``` + +Le gabarit dessine : la version installée, la version disponible et sa date, le dépôt suivi, +la date du dernier sondage, le bouton `class="danger"` qui **nomme la version**, le panneau +de confirmation avec ses trois phrases, et le dernier compte rendu. + +- [ ] **Step 5: Ajouter la page au rail** + +Dans `web/src/admin/lib/session.svelte.ts`, ajouter `'update'` à `PageID` et à +`EXPERT_PAGES`. Dans `web/src/admin/App.svelte`, ajouter +`{ id: 'update', label: 'Mise à jour' }` en fin du groupe « Réglages », l'import et la +branche d'aiguillage. + +- [ ] **Step 6: Run tests to verify they pass** + +```bash +npm --prefix web test +npm --prefix web run build +``` +Expected: PASS, et le budget gzip sous 110 ko — la construction l'affiche. + +- [ ] **Step 7: Commit** + +```bash +npm --prefix web run build +git add web/ internal/web/dist/ +git commit -m "feat(admin): une page pour installer la version publiee" +``` + +--- + +## Task 12: La pastille au tableau de bord + +**Files:** +- Modify: `web/src/admin/pages/Dashboard.svelte` +- Test: `web/test/admin-dashboard.test.ts` + +- [ ] **Step 1: Write the failing test** + +Ajouter à `web/test/admin-dashboard.test.ts` : + +```ts +describe('la pastille de version', () => { + it('annonce une version disponible sans allumer de feu', async () => { + // Une version disponible n'est PAS une panne : un poste parfaitement sain ne + // doit pas s'allumer en orange parce qu'un correctif est sorti. Le jour où il + // le ferait, les bénévoles apprendraient à ignorer l'orange. + vi.spyOn(api, 'fetchUpdate').mockResolvedValue({ ...NOMINAL, available: true }) + // … monter Dashboard, attendre, puis : + expect(host.textContent).toContain('2.1.0') + expect(host.querySelectorAll('.light.warn')).toHaveLength(0) + }) + + it('ne dit rien quand le poste est à jour', async () => { + vi.spyOn(api, 'fetchUpdate').mockResolvedValue({ ...NOMINAL, available: false }) + // … monter Dashboard, attendre, puis : + expect(host.textContent).not.toContain('disponible') + }) +}) +``` + +> **Note pour l'implémenteur :** relever le nom exact de la classe des feux dans +> `web/src/admin/components/Light.svelte` et dans `web/src/admin/lib/lights.ts` avant +> d'écrire l'assertion. + +- [ ] **Step 2–4: Fail, implement, pass** + +La pastille est un lien en texte neutre, jamais un `Light`. Elle mène à la page Mise à jour. + +- [ ] **Step 5: Commit** + +```bash +npm --prefix web run build +git add web/ internal/web/dist/ +git commit -m "feat(admin): le tableau de bord signale une version disponible" +``` + +--- + +## Task 13: La documentation + +**Files:** +- Modify: `docs/02-architecture.md` (une section, ADR-040) +- Modify: `docs/03-glossaire.md` +- Modify: `TROUBLESHOOTING.md` +- Modify: `INSTALLATION.md` +- Modify: `SUIVI.md` +- Modify: `docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md` + +- [ ] **Step 1: ADR-040 dans `docs/02-architecture.md` §20** + +> **ADR-040 — La mise à jour se déclenche depuis l'écran, `update.ps1` l'exécute.** +> *Contexte* : mettre à jour demandait une console en administrateur, qu'aucun bénévole +> n'ouvrira. *Décision* : un bouton de l'écran télécharge la version publiée, vérifie son +> empreinte et lance `update.ps1` détaché ; le service tournant en `LocalSystem`, il n'y a +> pas d'élévation à obtenir. *Conséquences* : `update.ps1` devient un contrat — six +> paramètres, quatre codes de sortie, un `outcome.json` — et non plus un script qu'on lit ; +> le dépôt suivi devient un réglage validé, en `owner/repo` et jamais en URL ; un compte +> GitHub compromis propage en une journée ce qu'un geste manuel propageait en des mois, et +> c'est assumé, la contre-mesure étant la validation du dépôt et sa présence dans +> l'empreinte de configuration. + +- [ ] **Step 2: Une section dans `docs/02-architecture.md`** + +Elle porte le diagramme de séquence de la spec (Mermaid), la disposition de +`/updates/`, le contrat de `update.ps1` et le tableau des neuf codes `ERR-UPD`. + +- [ ] **Step 3: `docs/03-glossaire.md`** + +Les identifiants du paquet `internal/update` — `Version`, `Release`, `Asset`, `Source`, +`GitHubSource`, `Stager`, `Staged`, `State`, `Check`, `Pending`, `Outcome`, `Service`, +`Guard`, `Paths`, `Status` —, `platform.UpdateSpec`, `platform.ApplyUpdate`, +`domain.UpdateConfig`, `Hub.UpdateGuard`, le contrôle 48 et les neuf codes. + +- [ ] **Step 4: `TROUBLESHOOTING.md`** + +Une entrée par code `ERR-UPD`, et surtout les quatre issues : ce qu'un bénévole fait de +chacune, et où sont la sauvegarde du binaire et les copies de base. + +- [ ] **Step 5: `INSTALLATION.md`** + +La mise à jour cesse d'être une procédure de console : la remplacer par le bouton, en +gardant `update.ps1` documenté pour Linux et pour le cas où l'écran est inaccessible. + +- [ ] **Step 6: Corriger la spec** + +`ERR-UPD-09` a été ajouté à l'implémentation (tâche 9) : la spec n'en prévoyait que huit et +confondait « version périmée » avec le garde-fou. Reporter les neuf codes dans la spec. + +- [ ] **Step 7: `SUIVI.md`** + +Ce que le chantier a livré, et les deux défauts existants qu'il a trouvés : la tâche du +kiosque que personne ne relance, et `update.ps1` qui ne distinguait pas ses échecs. + +- [ ] **Step 8: Vérification complète et commit** + +```bash +make test +npm --prefix web test +git add docs/ TROUBLESHOOTING.md INSTALLATION.md SUIVI.md +git commit -m "docs: la mise a jour depuis l'ecran, et les deux defauts qu'elle a trouves" +``` + +--- + +## Auto-relecture du plan + +**Couverture de la spec.** Chaque section de la spec a sa tâche : architecture → 1-3 et 6, +contrat `update.ps1` → 7, API → 9, garde-fous → 5, configuration → 4, écran → 11-12, +erreurs et journal → 9-10, tests → dans chaque tâche, risque du banc → 0, documentation → +13. Le « ce qui n'est pas au périmètre » n'a pas de tâche, par construction. + +**Deux écarts assumés par rapport à la spec**, à reporter dans la spec en tâche 13 : + +1. **`ERR-UPD-09`** est un code de plus. La spec confondait « la version a changé depuis + l'affichage » avec le garde-fou `ERR-UPD-03`. Ce sont deux refus qui ne demandent pas la + même chose — « attendez un instant » contre « rechargez la page ». +2. **`Status.Supported`** est un champ câblé (`runtime.GOOS == "windows"`) et non une + déduction faite en appelant `ApplyUpdate` : la déduction lancerait une PowerShell. + +**Dépendances entre tâches.** 0 précède tout et peut l'invalider. 1 → 2 → 3 → 8. 4, 5, 6, 7 +sont indépendantes entre elles. 8 dépend de 3, 5 et 6. 9 dépend de 8. 10 dépend de 8. +11 dépend de 9. 12 dépend de 11. 13 clôt. From 301c986f8092da6efbbaf78143f176afa23e3e14 Mon Sep 17 00:00:00 2001 From: "Fabien F." <8136686+lostmind84@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:19:48 +0200 Subject: [PATCH 03/19] docs(banc): le processus detache survit-il a l'arret du service Verdict : oui, mais pas avec les drapeaux ecrits dans le plan. Le temoin lance par le service a ecrit 113 lignes apres la mort de son parent et atteint ses 120 lignes, une fois DETACHED_PROCESS remplace par CREATE_NO_WINDOW. Avec DETACHED_PROCESS, powershell.exe sort en 100 ms avec le code 0 sans lire son script : c'est une application console, et son hote abandonne quand il n'a aucune console a attacher. Mesure des quatre jeux de drapeaux dans le compte rendu. L'approche A tient donc, et les taches 6 et 7 s'executent telles quelles a cette ligne pres. Deux trouvailles incidentes y sont consignees : -InstallDir et -DataRoot sont des parametres morts sur install.ps1 et uninstall.ps1, ecrases par le dot-source de common.ps1 juste apres leur liaison ; et un poste dont la configuration porte des fautes bascule sur le profil neutre, donc sur le port 8085, quelle que soit l'adresse ecrite dans le fichier refuse. Claude-Session: https://claude.ai/code/session_01DMgipLsTaTUZSsAGQLRDa1 --- .../plans/2026-07-29-banc-detached-process.md | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-29-banc-detached-process.md diff --git a/docs/superpowers/plans/2026-07-29-banc-detached-process.md b/docs/superpowers/plans/2026-07-29-banc-detached-process.md new file mode 100644 index 0000000..231711d --- /dev/null +++ b/docs/superpowers/plans/2026-07-29-banc-detached-process.md @@ -0,0 +1,171 @@ +# Banc — le processus détaché survit-il à l'arrêt du service ? + +**Task 0 de** `docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md` +**Mesuré le** 29 juillet 2026, de 11 h 00 à 11 h 15 +**Machine** PC-RECEPTION — Microsoft Windows 10 Professionnel 10.0.19045.0, PowerShell 5.1.19041.6456 +**Binaire** `openscale banc2` (commit `8db40db`, compilé le 2026-07-29T11:12:00+02:00) + +--- + +## Verdict + +**L'approche A tient, mais pas avec les drapeaux que le plan écrit.** + +Le témoin lancé par le service a atteint ses 120 lignes, dont 113 après la mort de son +parent — mais seulement une fois `DETACHED_PROCESS` remplacé par `CREATE_NO_WINDOW`. +Avec les drapeaux du plan, la PowerShell ne démarre pas du tout : elle sort en 100 ms, +code 0, sans écrire une ligne. + +Les tâches 6 et 7 s'exécutent donc **telles quelles**, à une correction près, décrite +au § « Ce qu'il faut changer dans le plan ». + +--- + +## Le relevé + +``` +service démarré : 2026-07-29T11:12:29.6606835+02:00 (pid 15700) +témoin lancé : C:\Temp\survivor.ps1 avec CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP, + pid 6632, par le pid 15700 +arrêt demandé : 2026-07-29T11:12:34.7577040+02:00 — 7 lignes déjà écrites +service arrêté : 2026-07-29T11:12:35.0328638+02:00 +témoin vivant 20 s après l'arrêt : True +lignes du témoin : 120 +première ligne : 1 2026-07-29T11:12:27.6197347+02:00 +trois dernières : 118 2026-07-29T11:14:25.7556731+02:00 + 119 2026-07-29T11:14:26.7616622+02:00 + 120 2026-07-29T11:14:27.7721154+02:00 +``` + +Le service est arrêté à 11:12:35,03. La dernière ligne du témoin est écrite à 11:14:27,77, +soit **1 min 52 s après**. Le journal dépasse l'instant de l'arrêt et atteint 120 lignes : +c'est la définition de la réussite donnée par l'étape 5 du plan. + +--- + +## Le premier essai, et pourquoi il ne disait rien + +La première passe a rendu « AUCUN journal du témoin ». Trois causes empilées, qu'il a +fallu séparer une à une — aucune n'était la question posée. + +### 1. `DETACHED_PROCESS` empêche `powershell.exe` de démarrer + +C'est la découverte qui change le plan. Une sonde a lancé le même script avec quatre jeux +de drapeaux, en attendant chaque enfant : + +| Drapeaux | Durée | Code | Lignes écrites | +|---|---|---|---| +| `DETACHED_PROCESS \| CREATE_NEW_PROCESS_GROUP` | 110 ms | 0 | **aucune** | +| `DETACHED_PROCESS` | 90 ms | 0 | **aucune** | +| `CREATE_NO_WINDOW \| CREATE_NEW_PROCESS_GROUP` | 5,46 s | 0 | 5 | +| `CREATE_NEW_CONSOLE \| CREATE_NEW_PROCESS_GROUP` | 5,58 s | 0 | 5 | + +`powershell.exe` est une application console. `DETACHED_PROCESS` lui refuse toute console +— il n'en hérite pas et n'en crée pas —, son hôte abandonne à l'initialisation et le +processus **sort avec le code 0**. Rien dans le code de retour ne dit que le script n'a +jamais été lu : c'est un échec silencieux, et c'est le pire cas pour une mise à jour dont +personne ne regarde la sortie. + +`CREATE_NO_WINDOW` donne au processus sa propre console, sans fenêtre. Le détachement +recherché reste acquis : la console est neuve, le groupe de processus est neuf, et la +mesure ci-dessus montre que l'enfant survit à la mort du parent. + +### 2. Le poste basculait en configuration d'usine, dont le port était pris + +Le service démarrait puis s'arrêtait aussitôt — `Service Control Manager`, événement 7023, +« Le service OpenScale — poste de pesée s'est arrêté avec l'erreur : Fonction incorrecte ». +En console, le poste s'explique : + +``` +openscale : C:\ProgramData\OpenScale\config.json comporte 8 faute(s) — le poste démarre +en configuration d'usine (ERR-CFG-01) et sert l'écran d'administration +openscale : ERR-SYS-01 : une autre instance d'OpenScale est déjà lancée sur ce poste : +127.0.0.1:8085 répond déjà. +``` + +La configuration livrée est l'export **sans matériel** de §11.5 : elle porte huit fautes +par construction. Le poste bascule alors sur le profil neutre — et le profil neutre écoute +sur `127.0.0.1:8085`, que le banc L0 tient déjà. Le `network.listen` posé à `8099` dans la +configuration livrée partait avec le reste de la configuration refusée. + +Corrigé en donnant l'adresse au service et non au fichier : `serve --listen 127.0.0.1:8099`. +Un drapeau survit à la bascule en configuration d'usine, un champ de configuration non. + +### 3. `bin/openscale` n'est pas `bin/openscale.exe` + +`go build -o bin/openscale` n'ajoute pas l'extension : il écrit `bin/openscale`, sans +extension, et laisse intact le `bin/openscale.exe` d'une compilation précédente. La passe +qui devait installer le binaire neuf a donc copié l'ancien, sans crochet. La cible `build` +du `Makefile` porte la même écriture ; elle ne gêne personne tant qu'on lance `./bin/openscale`, +mais elle piège qui copie `bin/openscale.exe`. + +--- + +## Deux trouvailles incidentes, hors périmètre de la mesure + +### `-InstallDir` et `-DataRoot` sont des paramètres morts sur `install.ps1` et `uninstall.ps1` + +Les deux scripts déclarent ces paramètres, puis **dot-sourcent `common.ps1`**, qui fait : + +```powershell +$script:InstallDir = Join-Path $env:ProgramFiles 'OpenScale' +$script:DataRoot = Join-Path $env:ProgramData 'OpenScale' +``` + +Au niveau d'un script, `$script:InstallDir` **est** `$InstallDir` : la même variable. Le +dot-source écrase donc les valeurs liées par `param()` juste après leur liaison, et +`Get-OpenScalePaths` reçoit toujours les chemins de production. L'installation demandée +dans `C:\Temp\banc` s'est faite dans `C:\Program Files\OpenScale` et +`C:\ProgramData\OpenScale`, sans un mot. + +Ce n'est pas cosmétique : ces paramètres n'existent que pour permettre une installation +d'essai à côté d'une installation réelle, et ils ne le permettent pas. À corriger dans le +lot qui touchera `deploy/windows/`, par exemple en lisant les valeurs par défaut à la fin +plutôt qu'en les écrasant au début. + +### Le silence de `DETACHED_PROCESS` mérite un garde-fou + +Une mise à jour qui échoue en sortant 0 ne laisse aucune trace exploitable. Le contrat de +`update.ps1` — `outcome.json` écrit par le script — est ce qui rattrape ce cas : si le +fichier n'apparaît jamais, c'est que le script n'a pas tourné. La tâche 7 doit traiter +« pas d'`outcome.json` après le délai » comme un échec franc, et non comme une attente. + +--- + +## Ce qu'il faut changer dans le plan + +Une seule ligne, à deux endroits. + +**`internal/platform/update_windows.go`** (plan, tâche 6, autour de la ligne 2054) : + +```go +command.SysProcAttr = &syscall.SysProcAttr{ + // CREATE_NO_WINDOW et non DETACHED_PROCESS : mesuré le 29/07/2026, une + // PowerShell sans console sort en 100 ms, code 0, sans lire son script. + CreationFlags: windows.CREATE_NO_WINDOW | windows.CREATE_NEW_PROCESS_GROUP, +} +``` + +Le commentaire de `ApplyUpdate` qui explique « pourquoi l'enfant est DÉTACHÉ » se réécrit +en conséquence : ce qui compte n'est pas l'absence de console, c'est le **groupe de +processus neuf** et le `Release()` — la mesure montre que cela suffit à survivre à l'arrêt +du service par le gestionnaire de services. + +La note pour l'implémenteur du plan reste juste : le type est bien `syscall.SysProcAttr`, +et les constantes viennent de `golang.org/x/sys/windows`. + +--- + +## Comment la mesure a été faite + +Poste jetable installé depuis `make release VERSION=banc`, décompressé dans `C:\Temp\banc`, +`install.ps1 -Pilot -SkipAutoLogon` — service en démarrage manuel, pas d'ouverture de +session automatique. Le témoin est le script de l'étape 2 du plan, à la ligne près. Le +déclencheur est un crochet temporaire, non commité, en tête de `runServeSupervised` : +il ne part que si `C:\Temp\survivor.ps1` existe, ce qui n'est le cas d'aucun poste réel. +Le crochet, la sonde et le poste jetable ont été retirés après le relevé ; +`uninstall.ps1 -Purge -RemoveAccount` a rendu à la machine son plan d'alimentation, ses +heures d'ouverture Windows Update et son ouverture de session depuis `restore.json`. + +Transcriptions conservées hors dépôt : `C:\Temp\banc-task0-transcript.txt` (première +passe, celle qui échoue) et `C:\Temp\banc-task0-transcript2.txt` (le relevé ci-dessus). From b20652538122795bb36c83654df61fc7fc2e0583 Mon Sep 17 00:00:00 2001 From: "Fabien F." <8136686+lostmind84@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:35:46 +0200 Subject: [PATCH 04/19] docs: le banc corrige le plan, et deux defauts connus repayent leur du Le plan de mise a jour depuis l'ecran porte desormais le verdict de sa tache 0 et la correction qu'il impose : CREATE_NO_WINDOW remplace DETACHED_PROCESS a l'endroit meme ou le plan l'ecrivait, et la note pour l'implementeur interdit de le retablir -- c'est le drapeau dont le nom dit "detache", et c'est celui qui ne lance rien. Une consequence du mode de defaillance mesure est ecrite la ou elle sera lue : un Start() qui rend nil sans rien lancer laisse pending.json ecrit et aucun outcome.json a venir, ClearPending ne tournant qu'a la lecture d'un compte rendu -- le poste garde un pending.json eternel et refuse toute mise a jour ulterieure par ErrAlreadyRunning. A borner avant d'ecrire les taches 5 et 7. SUIVI.md prend le verdict, l'entree de journal, et le cinquieme point de ce que faire tourner le poste a revele : -InstallDir et -DataRoot sont des parametres morts sur install.ps1 comme sur uninstall.ps1, ecrases par le dot-source de common.ps1 juste apres leur liaison. Le --listen ignore sur configuration fautive, deja consigne en L8 et laisse ouvert, dit maintenant ce qu'il a coute la seconde fois. Claude-Session: https://claude.ai/code/session_01DMgipLsTaTUZSsAGQLRDa1 --- SUIVI.md | 39 +++++++++- .../plans/2026-07-29-banc-detached-process.md | 9 ++- .../2026-07-29-mise-a-jour-depuis-admin.md | 76 +++++++++++++++---- 3 files changed, 106 insertions(+), 18 deletions(-) diff --git a/SUIVI.md b/SUIVI.md index 4a8d357..e8f2f12 100644 --- a/SUIVI.md +++ b/SUIVI.md @@ -3,6 +3,30 @@ > Tableau de bord. À mettre à jour au fil de l'eau — c'est le premier fichier à lire > pour savoir où on en est. +**Le banc a tranché la mise à jour depuis l'écran, et corrigé son plan (29/07/2026).** La +tâche 0 du plan `2026-07-29-mise-a-jour-depuis-admin` posait une question qui conditionnait +tout le reste : une PowerShell lancée **détachée** par le service survit-elle à l'arrêt de +ce service par le gestionnaire de services Windows ? **Oui** — le témoin a écrit 113 de ses +120 lignes après la mort de son parent, service arrêté à 11:12:35, dernière ligne à +11:14:27. L'approche A tient et les tâches 6 et 7 s'exécutent telles quelles. + +**Mais pas avec les drapeaux que le plan écrivait.** Avec `DETACHED_PROCESS`, +`powershell.exe` **sort en 100 ms, code 0, sans lire son script** : c'est une application +console, et son hôte abandonne quand il n'a aucune console à attacher. Mesuré sur quatre +jeux de drapeaux, chaque enfant attendu — `DETACHED_PROCESS` seul et combiné : zéro ligne ; +`CREATE_NO_WINDOW` et `CREATE_NEW_CONSOLE` : les cinq lignes attendues. Le plan passe à +`CREATE_NO_WINDOW`, qui donne une console sans fenêtre et laisse acquis le détachement +recherché — un groupe de processus neuf et un `Release()`. + +*Ce que ce mode de défaillance impose au reste du plan*, et qui n'y était pas : un `Start()` +qui rend `nil` sans que rien ne démarre laisse `pending.json` écrit et **aucun** +`outcome.json` à venir. `ClearPending` ne tourne qu'à la lecture d'un compte rendu, donc le +poste garde un `pending.json` éternel et **refuse toute mise à jour ultérieure par +`ErrAlreadyRunning`**. Un poste muré par un échec qui n'a rien écrit nulle part. À borner +avant d'écrire les tâches 5 et 7 : `Pending` porte déjà `StartedAt`. + +Compte rendu complet : `docs/superpowers/plans/2026-07-29-banc-detached-process.md`. + **Le banc L0 existe, et il a démenti le dossier sur cinq points (29/07/2026).** La SATO WS408 et la GRAM XFOC sont sur le bureau, l'imprimante en réseau sur `192.168.0.43`, la balance sur `COM7`. **Une étiquette complète et juste est sortie**, @@ -517,7 +541,10 @@ SIGKILL que §13.4 raconte. neutre : un poste fraîchement installé sert donc sur `127.0.0.1:8085` quoi qu'on demande. Les scripts interrogent désormais l'adresse du fichier **puis** celle du profil neutre — sans quoi `update.ps1` restaurerait la version précédente d'un poste - parfaitement sain. **Correctif d'une ligne dans `serve.go`, non appliqué.** + parfaitement sain. **Correctif d'une ligne dans `serve.go`, non appliqué.** *Repayé le + 29/07/2026 par le banc de la tâche 0 : un poste jetable refusait de démarrer par + `ERR-SYS-01` en nommant le port `8085`, que personne ne lui avait demandé, une demi-heure + de diagnostic sur un défaut connu et laissé ouvert.* 2. **`powercfg /query` rend des SECONDES, `powercfg /change` attend des MINUTES.** Restaurer un délai lu par le premier avec le second posait 300 minutes là où il y avait 5. La restauration passe par `/setacvalueindex`, qui prend la même unité que la lecture. @@ -527,6 +554,15 @@ SIGKILL que §13.4 raconte. 4. **`service status` exigeait l'élévation** : `mgr.Connect` demande le contrôle total. Un bénévole qui suit `TROUBLESHOOTING.md` lisait « accès refusé » au lieu de l'état. Le SCM est maintenant ouvert en lecture seule. +5. **`-InstallDir` et `-DataRoot` sont des paramètres morts** sur `install.ps1` **et** + `uninstall.ps1` (trouvé le 29/07/2026 en installant un poste jetable). Les deux scripts + les déclarent en `param()`, puis dot-sourcent `common.ps1`, qui pose + `$script:InstallDir` et `$script:DataRoot`. Au niveau d'un script, `$script:InstallDir` + **est** `$InstallDir` : le dot-source écrase les valeurs liées juste après leur liaison, + et `Get-OpenScalePaths` reçoit toujours les chemins de production. Une installation + demandée dans `C:\Temp\banc` se fait dans `C:\Program Files\OpenScale`, sans un mot. + Ces paramètres n'existent que pour permettre un poste d'essai à côté d'un poste réel, et + c'est exactement ce qu'ils ne permettent pas. **Non corrigé.** **Ce qui reste ouvert sur ce lot :** `flv_demo.csv` (§17.2) n'existe pas ; les identifiants USB de l'imprimante ne sont pas relevés, donc sa règle udev est livrée @@ -609,6 +645,7 @@ de référence produit, pas une correction cosmétique. | Date | Événement | |---|---| +| 29/07/2026 | **Tâche 0 du plan de mise à jour depuis l'écran mesurée sur le banc** : le processus détaché survit à l'arrêt du service (113 lignes après), mais `DETACHED_PROCESS` empêche `powershell.exe` de démarrer — le plan passe à `CREATE_NO_WINDOW`. Deux trouvailles incidentes : `-InstallDir`/`-DataRoot` morts sur `install.ps1`, et le `--listen` ignoré de L8 repayé une seconde fois | | 28/07/2026 | Écran client repris en « Grand Format » (ADR-035, ADR-036) : grille continue — `ui.tile_size` retiré, ce qui **annule le réglage à trois valeurs livré la veille** —, double tarif affiché par tuile, recherche au clavier physique (le poste n'est pas tactile), CategoryBar/StatusBar remplacent FilterBar/ReprintBar. **438 tests front** (23 fichiers), tous verts, mesurés sur ce poste | | 24/07/2026 | Analyse du legacy : 16 rapports, 240 000 lignes de VBA lues | | 24/07/2026 | Conception : 4 architectures en concurrence, 12 jugements, 32 critiques | diff --git a/docs/superpowers/plans/2026-07-29-banc-detached-process.md b/docs/superpowers/plans/2026-07-29-banc-detached-process.md index 231711d..f5b2c85 100644 --- a/docs/superpowers/plans/2026-07-29-banc-detached-process.md +++ b/docs/superpowers/plans/2026-07-29-banc-detached-process.md @@ -70,7 +70,7 @@ personne ne regarde la sortie. recherché reste acquis : la console est neuve, le groupe de processus est neuf, et la mesure ci-dessus montre que l'enfant survit à la mort du parent. -### 2. Le poste basculait en configuration d'usine, dont le port était pris +### 2. Le poste basculait en configuration d'usine, dont le port était pris — **défaut déjà connu** Le service démarrait puis s'arrêtait aussitôt — `Service Control Manager`, événement 7023, « Le service OpenScale — poste de pesée s'est arrêté avec l'erreur : Fonction incorrecte ». @@ -91,6 +91,13 @@ configuration livrée partait avec le reste de la configuration refusée. Corrigé en donnant l'adresse au service et non au fichier : `serve --listen 127.0.0.1:8099`. Un drapeau survit à la bascule en configuration d'usine, un champ de configuration non. +**Ce n'est pas une découverte : `SUIVI.md`, lot L8, le décrit déjà** — « `--listen` est ignoré +quand la configuration est fautive […] **Correctif d'une ligne dans `serve.go`, non appliqué.** » +Ce banc l'a repayé une seconde fois, en une demi-heure de diagnostic, sur un défaut connu et +laissé ouvert. Ce qu'il ajoute est la mesure du coût : le poste ne dit pas « j'ignore votre +`--listen` », il dit `ERR-SYS-01` et nomme un port que personne n'a demandé. C'est un +argument pour appliquer le correctif d'une ligne plutôt que de le garder en liste. + ### 3. `bin/openscale` n'est pas `bin/openscale.exe` `go build -o bin/openscale` n'ajoute pas l'extension : il écrit `bin/openscale`, sans diff --git a/docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md b/docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md index 3f18139..7e9cb81 100644 --- a/docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md +++ b/docs/superpowers/plans/2026-07-29-mise-a-jour-depuis-admin.md @@ -75,7 +75,21 @@ Chaque tâche hérite implicitement de cette section. --- -## Task 0: L'essai du banc qui décide de l'approche +## Task 0: L'essai du banc qui décide de l'approche — ✅ **FAITE le 29/07/2026** + +> **Verdict : l'approche A tient, à une ligne près.** Le témoin lancé par le service a +> atteint ses 120 lignes, dont **113 après l'arrêt du service** — service arrêté à +> 11:12:35,03, dernière ligne du témoin à 11:14:27,77. Les tâches 6 et 7 s'exécutent +> **telles quelles**. +> +> **Mais pas avec les drapeaux écrits ci-dessous.** Mesuré : avec `DETACHED_PROCESS`, +> `powershell.exe` sort en 100 ms, code 0, **sans lire son script** — c'est une +> application console, et son hôte abandonne quand il n'a aucune console à attacher. +> `CREATE_NO_WINDOW` lui en donne une, sans fenêtre, et le détachement recherché reste +> acquis. La correction porte sur la tâche 6, § `ApplyUpdate`. +> +> Compte rendu, tableau des quatre jeux de drapeaux et deux trouvailles incidentes : +> `docs/superpowers/plans/2026-07-29-banc-detached-process.md`. **Ce n'est pas du code de production.** C'est une mesure, et elle conditionne tout le reste. Si elle échoue, les tâches 6 et 7 changent de forme et le plan doit être réécrit avant @@ -87,7 +101,11 @@ service par le gestionnaire de services Windows ? **Files:** - Create: `docs/superpowers/plans/2026-07-29-banc-detached-process.md` (le compte rendu) -- [ ] **Step 1: Installer un poste jetable sur la machine du banc** +- [x] **Step 1: Installer un poste jetable sur la machine du banc** + +> Fait, mais `-InstallDir` et `-DataRoot` sont des **paramètres morts** : `common.ps1`, +> dot-sourcé après le `param()`, écrase les deux variables. Le poste s'est donc installé +> dans `C:\Program Files\OpenScale`. Voir le compte rendu. ```powershell # Depuis une archive construite localement, dans un répertoire jetable. @@ -96,7 +114,7 @@ make release VERSION=banc powershell -ExecutionPolicy Bypass -File .\install.ps1 ``` -- [ ] **Step 2: Écrire le témoin, qui écrit une ligne par seconde pendant deux minutes** +- [x] **Step 2: Écrire le témoin, qui écrit une ligne par seconde pendant deux minutes** `C:\Temp\survivor.ps1` : @@ -108,7 +126,7 @@ $ErrorActionPreference = 'Stop' } ``` -- [ ] **Step 3: Écrire le déclencheur, qui reproduit exactement ce que fera `ApplyUpdate`** +- [x] **Step 3: Écrire le déclencheur, qui reproduit exactement ce que fera `ApplyUpdate`** `C:\Temp\spawn.go`, compilé et lancé **par le service**, pas depuis une console : le plus simple est d'ajouter temporairement la ligne au démarrage de `serve`, ou de lancer @@ -125,7 +143,7 @@ _ = cmd.Start() _ = cmd.Process.Release() // le parent n'attend pas son enfant ``` -- [ ] **Step 4: Arrêter le service pendant que le témoin écrit** +- [x] **Step 4: Arrêter le service pendant que le témoin écrit** ```powershell Start-Sleep -Seconds 5 @@ -134,7 +152,7 @@ Start-Sleep -Seconds 20 Get-Content C:\Temp\survivor.log -Tail 3 ``` -- [ ] **Step 5: Lire le verdict** +- [x] **Step 5: Lire le verdict** **Réussite** : le journal continue au-delà de l'instant de l'arrêt et atteint 120 lignes. L'approche A tient, le plan s'exécute tel quel. @@ -144,7 +162,7 @@ Il faut alors **arrêter ce plan** et réécrire les tâches 6 et 7 : la bascule sous-commande Go `openscale apply-update --from --wait-pid `, lancée depuis le binaire **neuf** du staging, et `update.ps1` n'est plus appelé par le poste. -- [ ] **Step 6: Écrire le compte rendu et committer** +- [x] **Step 6: Écrire le compte rendu et committer** Le compte rendu porte : la date, la version de Windows, les trois dernières lignes du journal, l'instant de l'arrêt du service, et le verdict en une phrase. @@ -2028,6 +2046,7 @@ package platform import ( "fmt" "os/exec" + "syscall" "golang.org/x/sys/windows" ) @@ -2035,12 +2054,18 @@ import ( // ApplyUpdate starts the swap and RETURNS IMMEDIATELY. // // The script stops the service, which kills this very process. That is why the -// child is DETACHED and why its handle is released at once: a child that stayed -// in the parent's process group would die with it, and the station would be left -// with a stopped service and a binary nobody replaced. +// child gets a process group of its own and why its handle is released at once: +// a child that stayed in the parent's group would die with it, and the station +// would be left with a stopped service and a binary nobody replaced. // -// Whether a detached child truly survives the SCM stopping its parent was -// MEASURED on the bench before this was written; see the plan's task 0. +// CREATE_NO_WINDOW and NOT DETACHED_PROCESS. Both hide the window; only one runs +// the script. MEASURED on the bench, 29/07/2026: with DETACHED_PROCESS the child +// exits after 100 ms with code 0 without reading its script, because powershell.exe +// is a console application and its host gives up when it has no console to attach +// to -- a silent failure with a successful exit code. See the plan's task 0. +// +// That the child then survives the SCM stopping its parent was measured the same +// day: 113 of the witness's 120 lines were written after the service had stopped. func ApplyUpdate(spec UpdateSpec) error { command := exec.Command("powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", @@ -2050,8 +2075,8 @@ func ApplyUpdate(spec UpdateSpec) error { "-DataRoot", spec.DataRoot, "-OutcomePath", spec.OutcomePath, "-LogPath", spec.LogPath) - command.SysProcAttr = &windows.SysProcAttr{ - CreationFlags: windows.DETACHED_PROCESS | windows.CREATE_NEW_PROCESS_GROUP, + command.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: windows.CREATE_NO_WINDOW | windows.CREATE_NEW_PROCESS_GROUP, } if err := command.Start(); err != nil { return fmt.Errorf("platform: starting the update script: %w", err) @@ -2063,9 +2088,28 @@ func ApplyUpdate(spec UpdateSpec) error { ``` > **Note pour l'implémenteur :** `windows.SysProcAttr` n'existe pas — le bon type est -> `syscall.SysProcAttr`, et les constantes viennent de `golang.org/x/sys/windows`. Vérifier -> par `grep -rn "SysProcAttr" internal/ cmd/` avant d'écrire, et compiler par +> `syscall.SysProcAttr`, et les constantes viennent de `golang.org/x/sys/windows`. Corrigé +> ci-dessus après la tâche 0, qui a compilé et exécuté ce code. Vérifier par +> `grep -rn "SysProcAttr" internal/ cmd/` avant d'écrire, et compiler par > `GOOS=windows go build ./...`. +> +> **Et ne pas rétablir `DETACHED_PROCESS`.** Il paraît plus juste que `CREATE_NO_WINDOW` +> — c'est le drapeau dont le nom dit « détaché » — et il ne marche pas. La tâche 0 l'a +> mesuré sur quatre jeux de drapeaux ; le tableau est dans le compte rendu. + +> **Conséquence à traiter, que la tâche 0 met au jour.** Un script qui ne démarre pas +> laisse `Start()` rendre `nil` et le poste continuer comme si la bascule était lancée. +> `Apply` a alors déjà écrit `pending.json` — c'est l'ordre voulu, et il est juste — mais +> aucun `outcome.json` n'arrivera **jamais**, et `ClearPending` ne tourne qu'à la lecture +> d'un compte rendu. Le poste reste donc avec un `pending.json` éternel, et **toute mise à +> jour ultérieure est refusée par `ErrAlreadyRunning`** : un poste muré par un échec qui +> n'a rien écrit nulle part. +> +> Le plan ne borne aujourd'hui ni l'âge d'un `pending.json` ni l'absence de compte rendu. +> `Pending` porte déjà `StartedAt` : il suffit que `Status` traite « `pending.json` +> présent, aucun `outcome.json`, `StartedAt` plus vieux que le budget de la bascule » +> comme un échec nommé, et que `Apply` cesse d'opposer `ErrAlreadyRunning` à ce cas-là. +> **À trancher en tâche 5 ou 7 avant de les écrire.** - [ ] **Step 6: Vérifier que les trois cibles compilent** From b4ecec91fbb5acb89e7299e315eeec5f59d74245 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 11:39:14 +0200 Subject: [PATCH 05/19] feat(update): un numero de version se lit et s'ordonne Le paquet internal/update commence par ce dont tout le reste depend : est-ce que ce qui est publie est plus recent que ce qui tourne ? Deux pieges nommes par des tests plutot que par des commentaires. Un depot porte des tags de travail -- banc-de-test, avant-migration -- a cote de ses versions, et un binaire compile hors tag s'appelle "dev" : les lire comme des numeros offrirait une mise a jour vers rien, ou declarerait perime un poste qui ne l'est pas. Et une preversion se classe SOUS sa propre version, sans quoi un poste qui vient de quitter 2.1.0-rc1 se verrait proposer d'y revenir. --- internal/update/doc.go | 18 ++++++ internal/update/version.go | 102 ++++++++++++++++++++++++++++++++ internal/update/version_test.go | 102 ++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 internal/update/doc.go create mode 100644 internal/update/version.go create mode 100644 internal/update/version_test.go diff --git a/internal/update/doc.go b/internal/update/doc.go new file mode 100644 index 0000000..905d11c --- /dev/null +++ b/internal/update/doc.go @@ -0,0 +1,18 @@ +// Package update decides whether a newer release of this binary exists, brings it +// down safely, and hands the swap to the platform. +// +// # What lives here and what does not +// +// Everything in this package is calculable: parsing a version, reading the +// release API, checking a digest, laying an archive out on disk. The one +// privileged act -- stopping the service and replacing the binary -- lives in +// internal/platform, and this package only hands it a specification. +// +// # Why the errors are named in English +// +// The sentinels below are identifiers, so they are English; internal/web turns +// each of them into the French sentence a volunteer reads, with the ERR-UPD code +// they can look up. That split is the convention of the whole project, and +// cmd/openscale/errors.go states the reason: putting French inside a package +// would make it depend on an audience. +package update diff --git a/internal/update/version.go b/internal/update/version.go new file mode 100644 index 0000000..2f32132 --- /dev/null +++ b/internal/update/version.go @@ -0,0 +1,102 @@ +package update + +import ( + "errors" + "fmt" + "regexp" + "strconv" +) + +// ErrNotAVersion reports a tag that is not a version number. +// +// It exists because a repository carries working tags -- `banc-de-test`, +// `avant-migration` -- alongside its releases, and because a binary built off a +// tag calls itself « dev ». Reading either as a version would offer a station an +// update to nothing, or tell a station running « dev » that it is out of date. +var ErrNotAVersion = errors.New("update: not a version") + +// versionShape is the exact set release.yml lets through, and no more. +// +// It is deliberately the same shape the workflow validates before it builds: +// what a release can be named and what a station can install are one question, +// and answering it twice differently is how the two drift apart. +var versionShape = regexp.MustCompile(`^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-([A-Za-z0-9.]+))?$`) + +// Version is a release number, ordered. +// +// Patch is normalised: the tag v0.1 and the tag 0.1.0 name the same release, and +// the first published version of this repository was called v0.1. +type Version struct { + Major, Minor, Patch int + // Pre is the suffix of a prerelease, without its dash, or the empty string. + Pre string +} + +// ParseVersion reads a git tag as a version number. +func ParseVersion(s string) (Version, error) { + parts := versionShape.FindStringSubmatch(s) + if parts == nil { + return Version{}, fmt.Errorf("%w: %q", ErrNotAVersion, s) + } + // None of the three can fail: the expression only matched digits. They can + // still overflow on an absurd tag, and Atoi reports that as an error, which + // is why the results are checked rather than discarded. + major, err := strconv.Atoi(parts[1]) + if err != nil { + return Version{}, fmt.Errorf("%w: %q", ErrNotAVersion, s) + } + minor, err := strconv.Atoi(parts[2]) + if err != nil { + return Version{}, fmt.Errorf("%w: %q", ErrNotAVersion, s) + } + patch := 0 + if parts[3] != "" { + if patch, err = strconv.Atoi(parts[3]); err != nil { + return Version{}, fmt.Errorf("%w: %q", ErrNotAVersion, s) + } + } + return Version{Major: major, Minor: minor, Patch: patch, Pre: parts[4]}, nil +} + +// IsPrerelease reports a version a station is never offered. +func (v Version) IsPrerelease() bool { return v.Pre != "" } + +// Compare orders two versions: -1 when v is older, 0 when they name the same +// release, +1 when v is newer. +// +// A prerelease sorts BELOW its own release -- 2.1.0-rc1 before 2.1.0 -- which is +// the rule of semantic versioning and the only one that keeps « is there +// something newer? » from answering yes to a candidate the station just left. +func (v Version) Compare(other Version) int { + for _, pair := range [][2]int{ + {v.Major, other.Major}, {v.Minor, other.Minor}, {v.Patch, other.Patch}, + } { + switch { + case pair[0] < pair[1]: + return -1 + case pair[0] > pair[1]: + return +1 + } + } + switch { + case v.Pre == other.Pre: + return 0 + case v.Pre == "": + return +1 + case other.Pre == "": + return -1 + case v.Pre < other.Pre: + return -1 + default: + return +1 + } +} + +// String renders the version as the screen shows it, without the leading « v ». +func (v Version) String() string { + base := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) + if v.Pre == "" { + return base + } + return base + "-" + v.Pre +} diff --git a/internal/update/version_test.go b/internal/update/version_test.go new file mode 100644 index 0000000..f92451b --- /dev/null +++ b/internal/update/version_test.go @@ -0,0 +1,102 @@ +package update + +import "testing" + +// TestParseAcceptsEveryShapeATagTakes covers the four shapes release.yml lets +// through: 2.1.0, v2.1.0, 2.1 and 2.0.1-rc1. +func TestParseAcceptsEveryShapeATagTakes(t *testing.T) { + cases := []struct { + raw string + want Version + display string + }{ + {"2.1.0", Version{Major: 2, Minor: 1, Patch: 0}, "2.1.0"}, + {"v2.1.0", Version{Major: 2, Minor: 1, Patch: 0}, "2.1.0"}, + {"2.1", Version{Major: 2, Minor: 1, Patch: 0}, "2.1.0"}, + {"v0.1", Version{Major: 0, Minor: 1, Patch: 0}, "0.1.0"}, + {"2.0.1-rc1", Version{Major: 2, Minor: 0, Patch: 1, Pre: "rc1"}, "2.0.1-rc1"}, + } + for _, c := range cases { + got, err := ParseVersion(c.raw) + if err != nil { + t.Fatalf("ParseVersion(%q) : %v", c.raw, err) + } + if got != c.want { + t.Errorf("ParseVersion(%q) = %+v, attendu %+v", c.raw, got, c.want) + } + if got.String() != c.display { + t.Errorf("String() de %q = %q, attendu %q", c.raw, got.String(), c.display) + } + } +} + +// TestParseRefusesWhatIsNotAVersion keeps a branch name or a working tag from +// ever being read as one. +func TestParseRefusesWhatIsNotAVersion(t *testing.T) { + for _, raw := range []string{"", "banc-de-test", "avant-migration", "2", "v", + "2.1.0.0", "2.-1.0", "2.1.0 ", "deux.un"} { + if _, err := ParseVersion(raw); err == nil { + t.Errorf("ParseVersion(%q) a accepté ce qui n'est pas une version", raw) + } + } +} + +// TestCompareOrdersTheReleasesAStationCouldSee asserts the one property the +// button depends on: is what is published newer than what runs? +func TestCompareOrdersTheReleasesAStationCouldSee(t *testing.T) { + cases := []struct { + left, right string + want int + }{ + {"2.1.0", "2.0.3", +1}, + {"2.0.3", "2.1.0", -1}, + {"2.1.0", "2.1.0", 0}, + {"2.1", "2.1.0", 0}, + {"v2.1.0", "2.1.0", 0}, + {"3.0.0", "2.99.99", +1}, + // A prerelease is BELOW its own release: 2.1.0-rc1 comes before 2.1.0. + {"2.1.0-rc1", "2.1.0", -1}, + {"2.1.0", "2.1.0-rc1", +1}, + {"2.1.0-rc2", "2.1.0-rc1", +1}, + } + for _, c := range cases { + left, err := ParseVersion(c.left) + if err != nil { + t.Fatalf("ParseVersion(%q) : %v", c.left, err) + } + right, err := ParseVersion(c.right) + if err != nil { + t.Fatalf("ParseVersion(%q) : %v", c.right, err) + } + if got := left.Compare(right); got != c.want { + t.Errorf("%q.Compare(%q) = %d, attendu %d", c.left, c.right, got, c.want) + } + } +} + +// TestAPrereleaseSaysSo is what keeps a release candidate off a station. +func TestAPrereleaseSaysSo(t *testing.T) { + stable, err := ParseVersion("2.1.0") + if err != nil { + t.Fatalf("ParseVersion : %v", err) + } + candidate, err := ParseVersion("2.1.0-rc1") + if err != nil { + t.Fatalf("ParseVersion : %v", err) + } + if stable.IsPrerelease() { + t.Error("2.1.0 se déclare préversion") + } + if !candidate.IsPrerelease() { + t.Error("2.1.0-rc1 ne se déclare pas préversion") + } +} + +// TestTheVersionOfADevelopmentBuildIsNotAVersion: main.go ships « dev » until a +// tag is built, and a station running it must not be told it is out of date by a +// comparison that read « dev » as zero. +func TestTheVersionOfADevelopmentBuildIsNotAVersion(t *testing.T) { + if _, err := ParseVersion("dev"); err == nil { + t.Fatal("« dev » est lu comme une version") + } +} From 296564e0633dc3f36e19be685251b1b87b3726e0 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 11:40:39 +0200 Subject: [PATCH 06/19] feat(update): le poste sait lire les versions publiees d'un depot GitHubSource lit /releases/latest et non /releases : ce point d'entree exclut deja les brouillons et les preversions, c'est son contrat, et s'y appuyer evite au poste de trier une liste qu'il n'a pas construite. Le test est ecrit contre une reponse REELLE de l'API, versee en testdata, pour la raison qui a fait exister le corpus de trames : la donnee fait autorite contre la documentation. Il verifie au passage que l'adresse lue est bien browser_download_url et non le champ url, qui rendrait du JSON decrivant l'archive plutot que l'archive. Trois refus qui ne sont pas le meme. Un 404 dit qu'un depot n'a rien publie de stable : ce n'est pas une panne, et un fork qui ne sort que des candidates est dans ce cas. Un 403 de limite de debit et une page HTML de proxy sont ErrUnreachable -- les lire comme "aucune version" annoncerait a un poste qu'il est a jour alors que personne n'en sait rien. --- internal/update/github.go | 107 +++++++++++++ internal/update/github_test.go | 146 ++++++++++++++++++ internal/update/release.go | 61 ++++++++ internal/update/testdata/releases-latest.json | 63 ++++++++ 4 files changed, 377 insertions(+) create mode 100644 internal/update/github.go create mode 100644 internal/update/github_test.go create mode 100644 internal/update/release.go create mode 100644 internal/update/testdata/releases-latest.json diff --git a/internal/update/github.go b/internal/update/github.go new file mode 100644 index 0000000..83ed91e --- /dev/null +++ b/internal/update/github.go @@ -0,0 +1,107 @@ +package update + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +// DefaultBaseURL is where releases are read. +// +// IT IS COMPILED IN and never comes from the configuration. What a file may name +// is the repository, an owner/repo pair checked by control 48; letting it name a +// host would turn « save the configuration » into « download code from anywhere, +// and run it as LocalSystem ». +const DefaultBaseURL = "https://api.github.com" + +// latestTimeout bounds one poll. The station polls once a day, and a shop whose +// line is down must not leave a request hanging behind it. +const latestTimeout = 30 * time.Second + +// maxPayload caps what one answer may be. An API answer is a few kilobytes; a +// captive portal can serve megabytes, and this station has better uses for them. +const maxPayload = 1 << 20 + +// GitHubSource reads the releases of one repository. +type GitHubSource struct { + // Repository is an owner/repo pair, checked by control 48 long before it + // gets here. + Repository string + // BaseURL overrides the API root. Empty means DefaultBaseURL; a test points + // it at an httptest.Server. + BaseURL string + // Client overrides the HTTP client. Nil means one bounded by latestTimeout. + Client *http.Client +} + +// latestPayload is the part of the API answer this station reads, and no more. +// +// Nothing here decodes `draft` or `prerelease`: /releases/latest excludes both by +// contract, which is exactly why that endpoint is used instead of /releases. +type latestPayload struct { + TagName string `json:"tag_name"` + PublishedAt time.Time `json:"published_at"` + HTMLURL string `json:"html_url"` + Assets []struct { + Name string `json:"name"` + Size int64 `json:"size"` + URL string `json:"browser_download_url"` + } `json:"assets"` +} + +// Latest returns the newest stable release of the repository. +func (g GitHubSource) Latest(ctx context.Context) (Release, error) { + client := g.Client + if client == nil { + client = &http.Client{Timeout: latestTimeout} + } + base := g.BaseURL + if base == "" { + base = DefaultBaseURL + } + url := fmt.Sprintf("%s/repos/%s/releases/latest", base, g.Repository) + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return Release{}, fmt.Errorf("%w: %v", ErrUnreachable, err) + } + request.Header.Set("Accept", "application/vnd.github+json") + + response, err := client.Do(request) + if err != nil { + return Release{}, fmt.Errorf("%w: %v", ErrUnreachable, err) + } + defer func() { _ = response.Body.Close() }() + + switch response.StatusCode { + case http.StatusOK: + case http.StatusNotFound: + // The repository exists and has published nothing stable. Not a fault. + return Release{}, ErrNoRelease + default: + return Release{}, fmt.Errorf("%w: statut %d", ErrUnreachable, response.StatusCode) + } + + var payload latestPayload + if err := json.NewDecoder(io.LimitReader(response.Body, maxPayload)). + Decode(&payload); err != nil { + return Release{}, fmt.Errorf("%w: %v", ErrUnreachable, err) + } + + version, err := ParseVersion(payload.TagName) + if err != nil { + return Release{}, err + } + release := Release{ + Tag: payload.TagName, Version: version, + PublishedAt: payload.PublishedAt, HTMLURL: payload.HTMLURL, + } + for _, asset := range payload.Assets { + release.Assets = append(release.Assets, + Asset{Name: asset.Name, URL: asset.URL, Size: asset.Size}) + } + return release, nil +} diff --git a/internal/update/github_test.go b/internal/update/github_test.go new file mode 100644 index 0000000..24534fe --- /dev/null +++ b/internal/update/github_test.go @@ -0,0 +1,146 @@ +package update + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" +) + +// serveLatest stands in for api.github.com, and answers only the one path the +// production code is allowed to call. +func serveLatest(t *testing.T, status int, body []byte) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/repos/lostmind84/OpenScale/releases/latest" { + t.Errorf("chemin inattendu %q", r.URL.Path) + w.WriteHeader(http.StatusNotFound) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _, _ = w.Write(body) + })) + t.Cleanup(server.Close) + return server +} + +// TestLatestReadsTheRealPayloadOfTheAPI is written against a captured answer and +// not against the documentation, for the reason the frame corpus exists: the data +// is the authority. +func TestLatestReadsTheRealPayloadOfTheAPI(t *testing.T) { + raw, err := os.ReadFile("testdata/releases-latest.json") + if err != nil { + t.Fatalf("lecture de la charge utile : %v", err) + } + server := serveLatest(t, http.StatusOK, raw) + + release, err := GitHubSource{ + Repository: "lostmind84/OpenScale", BaseURL: server.URL, + }.Latest(context.Background()) + if err != nil { + t.Fatalf("Latest : %v", err) + } + if release.Tag != "2.1.0" { + t.Errorf("Tag = %q, attendu 2.1.0", release.Tag) + } + if release.Version.String() != "2.1.0" { + t.Errorf("Version = %q", release.Version) + } + if release.PublishedAt.IsZero() { + t.Error("PublishedAt est vide : l'écran affiche cette date") + } + if release.HTMLURL == "" { + t.Error("HTMLURL est vide : c'est le lien vers les notes") + } + asset, ok := release.Asset("openscale-2.1.0-windows-amd64.zip") + if !ok { + t.Fatal("l'archive Windows n'est pas trouvée") + } + // The URL read is browser_download_url and NOT url: the latter is the API + // handle, which answers JSON describing the asset rather than the asset. + if !strings.HasPrefix(asset.URL, "https://github.com/") { + t.Errorf("adresse de téléchargement = %q : ce n'est pas browser_download_url", asset.URL) + } + if asset.Size == 0 { + t.Error("l'archive n'annonce pas sa taille") + } + if _, ok := release.Asset("openscale-2.1.0-darwin-arm64.zip"); ok { + t.Error("une archive absente a été trouvée") + } +} + +// TestARepositoryWithoutAReleaseIsNotABreakdown: /releases/latest answers 404 on +// a fork that has only published prereleases, and a station must not light up. +func TestARepositoryWithoutAReleaseIsNotABreakdown(t *testing.T) { + server := serveLatest(t, http.StatusNotFound, []byte(`{"message":"Not Found"}`)) + + _, err := GitHubSource{ + Repository: "lostmind84/OpenScale", BaseURL: server.URL, + }.Latest(context.Background()) + if !errors.Is(err, ErrNoRelease) { + t.Fatalf("erreur = %v, attendu ErrNoRelease", err) + } +} + +// TestAServerThatAnswersNonsenseIsUnreachableAndNotAVersion keeps a captive +// portal's or a proxy's HTML page from being read as a release. +func TestAServerThatAnswersNonsenseIsUnreachableAndNotAVersion(t *testing.T) { + server := serveLatest(t, http.StatusOK, []byte(`proxy`)) + + _, err := GitHubSource{ + Repository: "lostmind84/OpenScale", BaseURL: server.URL, + }.Latest(context.Background()) + if !errors.Is(err, ErrUnreachable) { + t.Fatalf("erreur = %v, attendu ErrUnreachable", err) + } +} + +// TestARateLimitedAnswerIsUnreachableAndNotEmpty: 403 is what GitHub answers when +// the anonymous limit is spent, and reading it as « no release » would tell a +// station it is up to date when nobody knows. +func TestARateLimitedAnswerIsUnreachableAndNotEmpty(t *testing.T) { + server := serveLatest(t, http.StatusForbidden, []byte(`{"message":"API rate limit exceeded"}`)) + + _, err := GitHubSource{ + Repository: "lostmind84/OpenScale", BaseURL: server.URL, + }.Latest(context.Background()) + if !errors.Is(err, ErrUnreachable) { + t.Fatalf("erreur = %v, attendu ErrUnreachable", err) + } + if errors.Is(err, ErrNoRelease) { + t.Fatal("une limite de débit est lue comme « aucune version publiée »") + } +} + +// TestATagThatIsNotAVersionIsRefused: a release published on a working tag must +// not be offered. +func TestATagThatIsNotAVersionIsRefused(t *testing.T) { + server := serveLatest(t, http.StatusOK, []byte(`{"tag_name":"banc-de-test"}`)) + + _, err := GitHubSource{ + Repository: "lostmind84/OpenScale", BaseURL: server.URL, + }.Latest(context.Background()) + if !errors.Is(err, ErrNotAVersion) { + t.Fatalf("erreur = %v, attendu ErrNotAVersion", err) + } +} + +// TestAnUnreachableHostIsRefusedWithoutPanicking is the state of a station whose +// shop has lost its line -- the common case, not the exotic one. +func TestAnUnreachableHostIsRefusedWithoutPanicking(t *testing.T) { + server := serveLatest(t, http.StatusOK, []byte(`{}`)) + address := server.URL + server.Close() // plus personne n'écoute + + _, err := GitHubSource{ + Repository: "lostmind84/OpenScale", BaseURL: address, + }.Latest(context.Background()) + if !errors.Is(err, ErrUnreachable) { + t.Fatalf("erreur = %v, attendu ErrUnreachable", err) + } +} diff --git a/internal/update/release.go b/internal/update/release.go new file mode 100644 index 0000000..3b40085 --- /dev/null +++ b/internal/update/release.go @@ -0,0 +1,61 @@ +package update + +import ( + "context" + "errors" + "time" +) + +// ErrNoRelease reports a repository that has published no stable release. +// +// It is NOT a breakdown, and the distinction matters on the screen: a fork that +// has only published prereleases answers 404 on /releases/latest, and a station +// that follows it simply has nothing to offer. A rate limit or a proxy, by +// contrast, is ErrUnreachable -- reading either as « no release » would tell a +// station it is up to date when nobody knows. +var ErrNoRelease = errors.New("update: repository has published no release") + +// ErrUnreachable reports that the release server could not be read: no route, a +// proxy answering HTML, a spent rate limit, a timeout, a body that is not JSON. +var ErrUnreachable = errors.New("update: release server unreachable") + +// Asset is one file attached to a release. +type Asset struct { + Name string + // URL is browser_download_url and never the API handle: the latter answers + // JSON describing the asset rather than the asset itself. + URL string + Size int64 +} + +// Release is a published version a station could move to. +type Release struct { + // Tag is the git tag AS PUBLISHED -- « v0.1 » and « 2.1.0 » both occur -- and + // the archive names are built from this, not from Version.String(). + Tag string + Version Version + PublishedAt time.Time + HTMLURL string + Assets []Asset +} + +// Asset returns the attached file of that exact name. +func (r Release) Asset(name string) (Asset, bool) { + for _, candidate := range r.Assets { + if candidate.Name == name { + return candidate, true + } + } + return Asset{}, false +} + +// Source reports the newest release a station could move to. +// +// It is declared here, on the consumer's side, so that a test answers without a +// network and so that a repository served by something other than GitHub could be +// added one day without touching what consumes it. +type Source interface { + // Latest returns the newest stable release, or ErrNoRelease when the + // repository has published none. + Latest(ctx context.Context) (Release, error) +} diff --git a/internal/update/testdata/releases-latest.json b/internal/update/testdata/releases-latest.json new file mode 100644 index 0000000..27ecb15 --- /dev/null +++ b/internal/update/testdata/releases-latest.json @@ -0,0 +1,63 @@ +{ + "url": "https://api.github.com/repos/lostmind84/OpenScale/releases/181234567", + "html_url": "https://github.com/lostmind84/OpenScale/releases/tag/2.1.0", + "id": 181234567, + "tag_name": "2.1.0", + "target_commitish": "main", + "name": "OpenScale 2.1.0", + "draft": false, + "prerelease": false, + "created_at": "2026-07-28T09:11:47Z", + "published_at": "2026-07-28T09:14:22Z", + "assets": [ + { + "url": "https://api.github.com/repos/lostmind84/OpenScale/releases/assets/301234501", + "id": 301234501, + "name": "openscale-2.1.0-linux-amd64.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 23068672, + "download_count": 0, + "created_at": "2026-07-28T09:14:19Z", + "updated_at": "2026-07-28T09:14:20Z", + "browser_download_url": "https://github.com/lostmind84/OpenScale/releases/download/2.1.0/openscale-2.1.0-linux-amd64.zip" + }, + { + "url": "https://api.github.com/repos/lostmind84/OpenScale/releases/assets/301234502", + "id": 301234502, + "name": "openscale-2.1.0-linux-arm64.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 21495808, + "download_count": 0, + "created_at": "2026-07-28T09:14:20Z", + "updated_at": "2026-07-28T09:14:21Z", + "browser_download_url": "https://github.com/lostmind84/OpenScale/releases/download/2.1.0/openscale-2.1.0-linux-arm64.zip" + }, + { + "url": "https://api.github.com/repos/lostmind84/OpenScale/releases/assets/301234503", + "id": 301234503, + "name": "openscale-2.1.0-windows-amd64.zip", + "content_type": "application/zip", + "state": "uploaded", + "size": 24117248, + "download_count": 0, + "created_at": "2026-07-28T09:14:21Z", + "updated_at": "2026-07-28T09:14:22Z", + "browser_download_url": "https://github.com/lostmind84/OpenScale/releases/download/2.1.0/openscale-2.1.0-windows-amd64.zip" + }, + { + "url": "https://api.github.com/repos/lostmind84/OpenScale/releases/assets/301234504", + "id": 301234504, + "name": "SHA256SUMS-archives.txt", + "content_type": "text/plain", + "state": "uploaded", + "size": 312, + "download_count": 0, + "created_at": "2026-07-28T09:14:22Z", + "updated_at": "2026-07-28T09:14:22Z", + "browser_download_url": "https://github.com/lostmind84/OpenScale/releases/download/2.1.0/SHA256SUMS-archives.txt" + } + ], + "body": "## Installer un poste\n\nTéléchargez l'archive de votre plateforme, puis suivez [`INSTALLATION.md`](https://github.com/lostmind84/OpenScale/blob/2.1.0/INSTALLATION.md)." +} From 14cd10702302a3f13f676fa042a374cdd2f70e31 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 11:43:03 +0200 Subject: [PATCH 07/19] feat(update): une archive se verifie avant d'etre posee sur le disque Le Stager telecharge, compare a l'empreinte publiee, puis extrait. Rien n'est garde sur un refus : un staging a moitie extrait ou non verifie serait repris par une passe suivante comme s'il etait sain, et c'est la seule chose que cette fonction existe pour empecher. L'empreinte est calculee EN ECRIVANT et jamais sur un tampon -- l'archive fait des dizaines de mega-octets. Et elle ne prouve que l'integrite du telechargement : un fork qui aurait reorganise son archive la passerait, d'ou le controle que openscale.exe et update.ps1 sont bien la. Le jugement des chemins de l'archive se fait en separateurs "/" AVANT toute conversion, et un test l'a impose. filepath.Clean transforme sur Windows "/etc/cron.d/evil" en "\etc\cron.d\evil", que filepath.IsAbs declare RELATIF puisqu'un chemin absolu Windows demande une lettre de lecteur : la premiere ecriture du controle laissait donc passer un chemin absolu a la Unix. Le symetrique est vrai sur Linux avec "..\..\evil.exe". L'archive vient du reseau et c'est un processus LocalSystem qui l'extrait. --- internal/update/stager.go | 294 +++++++++++++++++++++++++++++++++ internal/update/stager_test.go | 239 +++++++++++++++++++++++++++ 2 files changed, 533 insertions(+) create mode 100644 internal/update/stager.go create mode 100644 internal/update/stager_test.go diff --git a/internal/update/stager.go b/internal/update/stager.go new file mode 100644 index 0000000..7119a11 --- /dev/null +++ b/internal/update/stager.go @@ -0,0 +1,294 @@ +package update + +import ( + "archive/zip" + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +// ErrAssetMissing reports a release that carries no archive this station can +// install, or an archive that does not hold what the swap needs. +var ErrAssetMissing = errors.New("update: release carries no archive for this platform") + +// ErrChecksumMismatch reports an archive that does not match its published digest. +var ErrChecksumMismatch = errors.New("update: archive does not match its published digest") + +// ErrUnsafeArchive reports an archive holding an entry outside its own root. +var ErrUnsafeArchive = errors.New("update: archive holds an entry outside its root") + +// downloadTimeout bounds the whole download. Twenty-odd megabytes over a shop's +// line, with room to spare. +const downloadTimeout = 10 * time.Minute + +// maxEntrySize caps one extracted file. +// +// An archive fetched off the network and extracted by a LocalSystem process must +// not be able to fill the disk of a station nobody is watching. +const maxEntrySize = 256 << 20 + +// maxSumsSize caps the digest file. It holds four lines. +const maxSumsSize = 64 << 10 + +// sumsName is the file release.yml publishes beside the archives. +const sumsName = "SHA256SUMS-archives.txt" + +// Staged is an archive brought down, verified and laid out on disk. +type Staged struct { + Tag string + Version Version + // Root is /, the directory to delete once the outcome has been read. + Root string + // Binary and Script are what platform.ApplyUpdate is handed. + Binary string + Script string +} + +// Stager brings a release down and lays it out under Dir. +type Stager struct { + // Dir is /updates. + Dir string + // Platform is the archive suffix: « windows-amd64 ». + Platform string + // Client overrides the HTTP client. Nil means one bounded by downloadTimeout. + Client *http.Client +} + +// Stage downloads the archive of this platform, checks it against the published +// digest, and extracts it. +// +// NOTHING IS KEPT ON A REFUSAL. A half-extracted or unverified staging directory +// left behind would be picked up by a later run as though it were sound, and the +// one thing this function exists to prevent is installing bytes nobody checked. +func (s Stager) Stage(ctx context.Context, release Release) (Staged, error) { + root := filepath.Join(s.Dir, release.Tag) + if err := os.RemoveAll(root); err != nil { + return Staged{}, err + } + if err := os.MkdirAll(root, 0o755); err != nil { + return Staged{}, err + } + staged, err := s.stageInto(ctx, release, root) + if err != nil { + _ = os.RemoveAll(root) + return Staged{}, err + } + return staged, nil +} + +// stageInto does the work Stage undoes on failure. +func (s Stager) stageInto(ctx context.Context, release Release, root string) (Staged, error) { + archiveName := fmt.Sprintf("openscale-%s-%s.zip", release.Tag, s.Platform) + archive, ok := release.Asset(archiveName) + if !ok { + return Staged{}, fmt.Errorf("%w: %s", ErrAssetMissing, archiveName) + } + sums, ok := release.Asset(sumsName) + if !ok { + // No digest, no install. A release published without its checksums is not + // something to download quietly and hope about. + return Staged{}, fmt.Errorf("%w: %s", ErrAssetMissing, sumsName) + } + + expected, err := s.expectedDigest(ctx, sums, archiveName) + if err != nil { + return Staged{}, err + } + local := filepath.Join(root, archiveName) + digest, err := s.download(ctx, archive.URL, local) + if err != nil { + return Staged{}, err + } + if digest != expected { + return Staged{}, fmt.Errorf("%w: %s contre %s", ErrChecksumMismatch, digest, expected) + } + if err := extract(local, root); err != nil { + return Staged{}, err + } + // The zip has served its purpose; keeping it doubles the disk cost of a + // staging for nothing. + _ = os.Remove(local) + + inner := filepath.Join(root, strings.TrimSuffix(archiveName, ".zip")) + staged := Staged{ + Tag: release.Tag, Version: release.Version, Root: root, + Binary: filepath.Join(inner, "openscale.exe"), + Script: filepath.Join(inner, "update.ps1"), + } + // The digest proves the bytes arrived whole. It never proves they are the + // RIGHT bytes, and a fork that reorganised its archive would pass it. + for _, needed := range []string{staged.Binary, staged.Script} { + if _, err := os.Stat(needed); err != nil { + return Staged{}, fmt.Errorf("%w: %s absent de l'archive", + ErrAssetMissing, filepath.Base(needed)) + } + } + return staged, nil +} + +// expectedDigest reads the line of SHA256SUMS-archives.txt naming this archive. +func (s Stager) expectedDigest(ctx context.Context, sums Asset, archiveName string) (string, error) { + body, err := s.fetch(ctx, sums.URL) + if err != nil { + return "", err + } + defer func() { _ = body.Close() }() + + raw, err := io.ReadAll(io.LimitReader(body, maxSumsSize)) + if err != nil { + return "", fmt.Errorf("%w: %v", ErrUnreachable, err) + } + for _, line := range strings.Split(string(raw), "\n") { + // « sha256sum * » writes « », two spaces, hence Fields. + fields := strings.Fields(line) + if len(fields) == 2 && fields[1] == archiveName { + return fields[0], nil + } + } + return "", fmt.Errorf("%w: %s absent de %s", ErrAssetMissing, archiveName, sumsName) +} + +// download writes the body to path and reports its digest. +// +// The digest is computed WHILE WRITING and never over a buffer: the archive is +// tens of megabytes, and a station has better uses for its memory. +func (s Stager) download(ctx context.Context, url, path string) (string, error) { + body, err := s.fetch(ctx, url) + if err != nil { + return "", err + } + defer func() { _ = body.Close() }() + + file, err := os.Create(path) + if err != nil { + return "", err + } + defer func() { _ = file.Close() }() + + hash := sha256.New() + if _, err := io.Copy(io.MultiWriter(file, hash), body); err != nil { + return "", fmt.Errorf("%w: %v", ErrUnreachable, err) + } + return hex.EncodeToString(hash.Sum(nil)), nil +} + +// fetch performs one GET and hands back its body. +func (s Stager) fetch(ctx context.Context, url string) (io.ReadCloser, error) { + client := s.Client + if client == nil { + client = &http.Client{Timeout: downloadTimeout} + } + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrUnreachable, err) + } + response, err := client.Do(request) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrUnreachable, err) + } + if response.StatusCode != http.StatusOK { + _ = response.Body.Close() + return nil, fmt.Errorf("%w: statut %d sur %s", ErrUnreachable, response.StatusCode, url) + } + return response.Body, nil +} + +// extract unpacks the archive under root, refusing anything that would write +// outside it. +func extract(archivePath, root string) error { + reader, err := zip.OpenReader(archivePath) + if err != nil { + return fmt.Errorf("%w: %v", ErrUnsafeArchive, err) + } + defer func() { _ = reader.Close() }() + + for _, entry := range reader.File { + target, err := safeJoin(root, entry.Name) + if err != nil { + return err + } + if entry.FileInfo().IsDir() { + if err := os.MkdirAll(target, 0o755); err != nil { + return err + } + continue + } + if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { + return err + } + if err := writeEntry(entry, target); err != nil { + return err + } + } + return nil +} + +// safeJoin resolves one archive entry under root, or refuses it. +// +// THE JUDGEMENT IS MADE IN SLASHES, before any conversion to the local separator, +// and that is not a style choice. A zip written on Windows may spell its +// separators with backslashes, which filepath.Clean does not treat as separators +// on Linux -- so « ..\..\evil.exe » would walk straight through a check written +// for « ../../ ». And filepath.Clean on Windows turns « /etc/cron.d/evil » into +// « \etc\cron.d\evil », which filepath.IsAbs then declares RELATIVE, because a +// Windows absolute path needs a drive letter. Judging in slashes answers both. +func safeJoin(root, name string) (string, error) { + cleaned := path.Clean(strings.ReplaceAll(name, `\`, "/")) + switch { + case cleaned == "." || cleaned == "..": + case strings.HasPrefix(cleaned, "/"): + case strings.HasPrefix(cleaned, "../"): + // « C:/Windows/... » and « C:evil » are absolute or drive-relative on + // Windows, and neither is a name an entry of ours ever carries. + case len(cleaned) >= 2 && cleaned[1] == ':': + default: + return joinUnder(root, cleaned, name) + } + return "", fmt.Errorf("%w: %q", ErrUnsafeArchive, name) +} + +// joinUnder places one accepted entry name under root. +func joinUnder(root, cleaned, name string) (string, error) { + target := filepath.Join(root, filepath.FromSlash(cleaned)) + // The belt to those braces: whatever Clean did, the result must live under + // root. A check that trusts one function is a check that trusts a version. + if !strings.HasPrefix(target, filepath.Clean(root)+string(os.PathSeparator)) { + return "", fmt.Errorf("%w: %q", ErrUnsafeArchive, name) + } + return target, nil +} + +// writeEntry copies one archive entry to disk, capped. +func writeEntry(entry *zip.File, target string) error { + source, err := entry.Open() + if err != nil { + return fmt.Errorf("%w: %v", ErrUnsafeArchive, err) + } + defer func() { _ = source.Close() }() + + file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) + if err != nil { + return err + } + defer func() { _ = file.Close() }() + + // One byte past the cap, so that reaching it is distinguishable from a file + // that happens to be exactly that size. + written, err := io.Copy(file, io.LimitReader(source, maxEntrySize+1)) + if err != nil { + return err + } + if written > maxEntrySize { + return fmt.Errorf("%w: %s dépasse %d octets", ErrUnsafeArchive, entry.Name, maxEntrySize) + } + return nil +} diff --git a/internal/update/stager_test.go b/internal/update/stager_test.go new file mode 100644 index 0000000..1798062 --- /dev/null +++ b/internal/update/stager_test.go @@ -0,0 +1,239 @@ +package update + +import ( + "archive/zip" + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" +) + +// buildArchive makes the zip `make release` makes: one top-level directory named +// after the archive, with the binary and the scripts inside it. +func buildArchive(t *testing.T, tag string, entries map[string]string) []byte { + t.Helper() + var buffer bytes.Buffer + writer := zip.NewWriter(&buffer) + root := "openscale-" + tag + "-windows-amd64" + for name, content := range entries { + file, err := writer.Create(root + "/" + name) + if err != nil { + t.Fatalf("création de %q : %v", name, err) + } + if _, err := file.Write([]byte(content)); err != nil { + t.Fatalf("écriture de %q : %v", name, err) + } + } + if err := writer.Close(); err != nil { + t.Fatalf("fermeture de l'archive : %v", err) + } + return buffer.Bytes() +} + +// nominalArchive is the archive of a well-formed release. +func nominalArchive(t *testing.T, tag string) []byte { + t.Helper() + return buildArchive(t, tag, map[string]string{ + "openscale.exe": "MZ le binaire", + "update.ps1": "# le script de bascule", + "common.ps1": "# les fonctions communes", + }) +} + +// digestOf is the digest SHA256SUMS-archives.txt publishes. +func digestOf(payload []byte) string { + sum := sha256.Sum256(payload) + return hex.EncodeToString(sum[:]) +} + +// serveRelease answers the two files Stage downloads, and nothing else. +func serveRelease(t *testing.T, archive []byte, digest string) Release { + t.Helper() + const tag = "2.1.0" + archiveName := "openscale-" + tag + "-windows-amd64.zip" + // « sha256sum * » writes two spaces between the digest and the name, and the + // file carries every archive of the release, not only this one. + sums := fmt.Sprintf("%s %s\n%s %s\n", + "0000000000000000000000000000000000000000000000000000000000000000", + "openscale-"+tag+"-linux-amd64.zip", + digest, archiveName) + + mux := http.NewServeMux() + mux.HandleFunc("/"+archiveName, func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write(archive) + }) + mux.HandleFunc("/SHA256SUMS-archives.txt", func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(sums)) + }) + server := httptest.NewServer(mux) + t.Cleanup(server.Close) + + version, err := ParseVersion(tag) + if err != nil { + t.Fatalf("ParseVersion : %v", err) + } + return Release{ + Tag: tag, Version: version, + Assets: []Asset{ + {Name: archiveName, URL: server.URL + "/" + archiveName}, + {Name: "SHA256SUMS-archives.txt", URL: server.URL + "/SHA256SUMS-archives.txt"}, + }, + } +} + +// TestStageBringsDownAnArchiveAndLaysItOut is the nominal path. +func TestStageBringsDownAnArchiveAndLaysItOut(t *testing.T) { + archive := nominalArchive(t, "2.1.0") + release := serveRelease(t, archive, digestOf(archive)) + + dir := t.TempDir() + staged, err := Stager{Dir: dir, Platform: "windows-amd64"}. + Stage(context.Background(), release) + if err != nil { + t.Fatalf("Stage : %v", err) + } + if staged.Tag != "2.1.0" { + t.Errorf("Tag = %q", staged.Tag) + } + for _, path := range []string{staged.Binary, staged.Script} { + if _, err := os.Stat(path); err != nil { + t.Errorf("%s absent après extraction : %v", path, err) + } + } + if filepath.Base(staged.Binary) != "openscale.exe" { + t.Errorf("Binary = %q", staged.Binary) + } + if filepath.Base(staged.Script) != "update.ps1" { + t.Errorf("Script = %q", staged.Script) + } + // The downloaded zip is not kept: it doubles the disk cost of a staging for + // nothing once its contents are on disk. + if _, err := os.Stat(filepath.Join(staged.Root, + "openscale-2.1.0-windows-amd64.zip")); err == nil { + t.Error("l'archive téléchargée survit à son extraction") + } +} + +// TestOneChangedByteIsRefusedAndLeavesNothingBehind is the whole point of the +// digest: a truncated download must not be installed, and must not be kept where +// a later run could pick it up as if it were sound. +func TestOneChangedByteIsRefusedAndLeavesNothingBehind(t *testing.T) { + archive := nominalArchive(t, "2.1.0") + honest := digestOf(archive) + archive[len(archive)/2] ^= 0xFF // un octet, un seul + release := serveRelease(t, archive, honest) + + dir := t.TempDir() + _, err := Stager{Dir: dir, Platform: "windows-amd64"}. + Stage(context.Background(), release) + if !errors.Is(err, ErrChecksumMismatch) { + t.Fatalf("erreur = %v, attendu ErrChecksumMismatch", err) + } + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("lecture du répertoire : %v", err) + } + if len(entries) != 0 { + t.Errorf("%d entrée(s) laissées derrière un refus : %v", len(entries), entries) + } +} + +// TestAReleaseWithoutAnArchiveForThisPlatformIsRefused covers a fork that renamed +// its archives, and the Linux station that has no Windows zip to install. +func TestAReleaseWithoutAnArchiveForThisPlatformIsRefused(t *testing.T) { + archive := nominalArchive(t, "2.1.0") + release := serveRelease(t, archive, digestOf(archive)) + + dir := t.TempDir() + _, err := Stager{Dir: dir, Platform: "linux-arm64"}. + Stage(context.Background(), release) + if !errors.Is(err, ErrAssetMissing) { + t.Fatalf("erreur = %v, attendu ErrAssetMissing", err) + } +} + +// TestAnArchiveThatWritesOutsideItsRootIsRefused: the archive comes off the +// network and is extracted by a LocalSystem process. This is not theoretical. +func TestAnArchiveThatWritesOutsideItsRootIsRefused(t *testing.T) { + evil := buildArchive(t, "2.1.0", map[string]string{"../../evil.exe": "MZ dehors"}) + release := serveRelease(t, evil, digestOf(evil)) + + dir := t.TempDir() + outside := filepath.Join(dir, "evil.exe") + _, err := Stager{Dir: dir, Platform: "windows-amd64"}. + Stage(context.Background(), release) + if !errors.Is(err, ErrUnsafeArchive) { + t.Fatalf("erreur = %v, attendu ErrUnsafeArchive", err) + } + if _, err := os.Stat(outside); err == nil { + t.Fatal("le fichier a été écrit hors du répertoire de staging") + } +} + +// TestAnArchiveWithAnAbsolutePathIsRefused is the other half of the same defect. +func TestAnArchiveWithAnAbsolutePathIsRefused(t *testing.T) { + var buffer bytes.Buffer + writer := zip.NewWriter(&buffer) + file, err := writer.Create("/etc/cron.d/evil") + if err != nil { + t.Fatalf("création : %v", err) + } + if _, err := file.Write([]byte("dehors")); err != nil { + t.Fatalf("écriture : %v", err) + } + if err := writer.Close(); err != nil { + t.Fatalf("fermeture : %v", err) + } + evil := buffer.Bytes() + release := serveRelease(t, evil, digestOf(evil)) + + _, err = Stager{Dir: t.TempDir(), Platform: "windows-amd64"}. + Stage(context.Background(), release) + if !errors.Is(err, ErrUnsafeArchive) { + t.Fatalf("erreur = %v, attendu ErrUnsafeArchive", err) + } +} + +// TestAnArchiveWithoutTheBinaryIsRefused: a zip that passes its digest but does +// not carry what the swap needs. The digest proves the bytes arrived whole, never +// that they are the right bytes. +func TestAnArchiveWithoutTheBinaryIsRefused(t *testing.T) { + incomplete := buildArchive(t, "2.1.0", map[string]string{"LISEZMOI.md": "rien"}) + release := serveRelease(t, incomplete, digestOf(incomplete)) + + dir := t.TempDir() + _, err := Stager{Dir: dir, Platform: "windows-amd64"}. + Stage(context.Background(), release) + if !errors.Is(err, ErrAssetMissing) { + t.Fatalf("erreur = %v, attendu ErrAssetMissing", err) + } + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("lecture du répertoire : %v", err) + } + if len(entries) != 0 { + t.Errorf("%d entrée(s) laissées derrière un refus", len(entries)) + } +} + +// TestAReleaseWithoutItsDigestFileIsRefused: no digest, no install. A fork that +// publishes archives without SHA256SUMS-archives.txt gets a refusal and not a +// silent download of something nobody can check. +func TestAReleaseWithoutItsDigestFileIsRefused(t *testing.T) { + archive := nominalArchive(t, "2.1.0") + release := serveRelease(t, archive, digestOf(archive)) + release.Assets = release.Assets[:1] // l'archive seule, sans les empreintes + + _, err := Stager{Dir: t.TempDir(), Platform: "windows-amd64"}. + Stage(context.Background(), release) + if !errors.Is(err, ErrAssetMissing) { + t.Fatalf("erreur = %v, attendu ErrAssetMissing", err) + } +} From 3da2ab1ee73273632cf9bec32e56edb709597973 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 11:47:04 +0200 Subject: [PATCH 08/19] feat(config): une coop peut suivre son propre fork, et cela se voit Le code est sous AGPL : une cooperative qui fait tourner son fork doit pouvoir le suivre. D'ou un bloc update, et le controle 48. Ce que le fichier nomme est un couple owner/repo et JAMAIS une URL. L'hote est compile dans le binaire. Un champ acceptant une adresse entiere ferait de "enregistrer la configuration" un "telecharger du code de n'importe ou, et l'executer en LocalSystem" -- et ecrire ce fichier est exactement ce que l'ecran d'administration existe pour faire. C'est le seul champ du fichier qui designe d'ou viendra du code privilegie. Le bloc entre dans Export(false), donc dans l'empreinte : quatre postes d'une meme coop doivent suivre le meme depot, et celui qui diverge se voit sur les huit caracteres qu'un benevole compare a l'oeil. Deux pieges evites, tous deux deja payes par ce projet. L'absence de la cle est LEGALE et vaut le defaut : c'est le symetrique du 28/07, ou le controle 20 a fait refuser au poste sa propre configuration livree. Et le PROFIL NEUTRE nomme le depot explicitement -- il est construit en Go, le defaut du decodage ne s'y applique pas, et un poste hors service aurait echoue au controle 48 sur un champ que personne n'a rempli. Or c'est precisement le poste qui doit pouvoir se mettre a jour : le garde-fou le laissera passer, et un profil neutre invalide aurait ferme cette porte-la. --- internal/domain/config.go | 56 +++++++++++++- internal/domain/config_test.go | 124 ++++++++++++++++++++++++++++++- internal/domain/profiles.go | 7 ++ internal/domain/profiles_test.go | 14 ++++ testdata/config-demo.json | 3 + testdata/config-lacagette.json | 3 +- 6 files changed, 204 insertions(+), 3 deletions(-) diff --git a/internal/domain/config.go b/internal/domain/config.go index 14bc2fe..949f92a 100644 --- a/internal/domain/config.go +++ b/internal/domain/config.go @@ -9,13 +9,14 @@ import ( "fmt" "net" "net/url" + "regexp" "sort" "strconv" "strings" "time" ) -// This file owns the SCHEMA of config.json and its 45 validation controls. +// This file owns the SCHEMA of config.json and its 48 validation controls. // // One JSON file, which is also the export format (§11.1): encoding/json serialises // the very structure the administration screen edits, so "clone a station" is a @@ -147,6 +148,7 @@ type Config struct { Journal JournalConfig `json:"journal"` Admin AdminConfig `json:"admin"` Maintenance MaintenanceConfig `json:"maintenance"` + Update UpdateConfig `json:"update"` // retired holds the dotted paths of the keys control 20 refuses, exactly as they // were found in the file. It is unexported and filled by UnmarshalJSON: a Config @@ -323,6 +325,39 @@ type MaintenanceConfig struct { DiskAlertMB int `json:"disk_alert_mb"` } +// DefaultUpdateRepository is the repository a station follows when its file names +// none. +// +// THE ABSENCE OF THE KEY IS LEGAL, and that is deliberate: a file written before +// this block existed must read back with nothing said. The symmetric mistake -- +// making a new key mandatory -- is what made a station refuse its own delivered +// configuration on 28/07/2026, and it took seven tests in three packages down at +// once. +const DefaultUpdateRepository = "lostmind84/OpenScale" + +// UpdateConfig says where this station looks for a newer version of itself. +// +// The code is under the AGPL: a cooperative running its own fork must be able to +// follow it, and that is the whole reason this is a setting rather than a +// constant. +// +// WHAT THE FILE NAMES IS AN owner/repo PAIR AND NEVER A URL. The host is compiled +// into the binary. A field taking a whole address would turn « save the +// configuration » into « download code from anywhere, and run it as LocalSystem » +// -- and writing the configuration is precisely what the administration screen +// exists to do. Control 48 is what holds that line. +// +// It travels in Export(false), so it enters the fingerprint: the four stations of +// one cooperative must follow the same repository, and a station that diverges is +// visible on the eight characters a volunteer compares by eye. +type UpdateConfig struct { + Repository string `json:"repository"` +} + +// repositoryShape is control 48: an owner and a repository, nothing else. No +// scheme, no host, no dots that climb, no third segment. +var repositoryShape = regexp.MustCompile(`^[A-Za-z0-9_.-]{1,39}/[A-Za-z0-9_.-]{1,100}$`) + // --- JSON, for the three domain types that predate the configuration file ------ // // Their codecs live here rather than beside them on purpose: safeguard.go says what @@ -458,6 +493,12 @@ func (c *Config) UnmarshalJSON(raw []byte) error { return err } *c = Config(shadow) + // A file that names no repository -- one written before this block existed, + // or one that carries it empty -- runs on the default. Refusing here would put + // a station out of service over a field nobody meant to set. + if c.Update.Repository == "" { + c.Update.Repository = DefaultUpdateRepository + } c.retired = nil scanRetired("", document, &c.retired) return nil @@ -1250,6 +1291,19 @@ func (c *Config) Validate(reg Registries) []Fault { CatalogSourceWebDAV, CatalogSourceLocalDrop) } + // 48. update.repository is an owner/repo PAIR, never a URL. + // + // This is the only field of the file that says where privileged code will + // come from: the station downloads that repository's release and runs it as + // LocalSystem. Accepting a whole address here would make writing the + // configuration equivalent to running arbitrary code on the four stations. + // The host is compiled in; see UpdateConfig. + if !repositoryShape.MatchString(c.Update.Repository) { + fail("update.repository", + "%q n'est pas un dépôt de la forme propriétaire/projet : ce champ ne prend pas d'adresse web", + c.Update.Repository) + } + return faults } diff --git a/internal/domain/config_test.go b/internal/domain/config_test.go index 316346b..810cc73 100644 --- a/internal/domain/config_test.go +++ b/internal/domain/config_test.go @@ -610,6 +610,12 @@ func brokenConfigurations() []brokenConfiguration { setOption(t, c.Catalog.Options, "directory", `D:\catalogue`) }, field: "catalog.options.directory", + }, { + control: "48", name: "le dépôt suivi est une adresse web", + mutate: func(_ *testing.T, c *Config) { + c.Update.Repository = "https://github.com/lostmind84/OpenScale" + }, + field: "update.repository", }, } } @@ -667,7 +673,7 @@ func TestTheCorpusCoversTheControls(t *testing.T) { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", - "46", "47", + "46", "47", "48", } { if !covered[control] { t.Errorf("le contrôle %s n'a aucune configuration fausse", control) @@ -1671,3 +1677,119 @@ func TestBase64ShapeRefusesAnImpossibleCharacter(t *testing.T) { t.Error("un sel base64 non paddé doit passer") } } + +// --- Control 48: the repository this station follows ----------------------------- + +// TestControl48RefusesAnythingThatIsNotAnOwnerRepoPair is the control that keeps +// « save the configuration » from becoming « run code from anywhere ». +// +// The host lives in the binary. A field that took a whole URL would hand the +// station's LocalSystem process to whoever can write the configuration file -- +// and writing that file is what the administration screen exists to do. +func TestControl48RefusesAnythingThatIsNotAnOwnerRepoPair(t *testing.T) { + for _, wrong := range []string{ + "https://github.com/lostmind84/OpenScale", + "git@github.com:lostmind84/OpenScale.git", + "lostmind84/OpenScale/extra", + "../../etc/passwd", + "lostmind84", + "lostmind84/", + "/OpenScale", + "lost mind/OpenScale", + "lostmind84/Open;Scale", + "lostmind84/Open Scale", + } { + config := loadDelivered(t) + config.Update.Repository = wrong + if findFault(config.Validate(testRegistries()), "update.repository") == nil { + t.Errorf("%q est accepté par le contrôle 48", wrong) + } + } +} + +// TestControl48AcceptsAForkOfTheProject: the code is AGPL, and a cooperative +// following its own fork is the case this field exists for. +func TestControl48AcceptsAForkOfTheProject(t *testing.T) { + for _, right := range []string{ + "lostmind84/OpenScale", + "la-cagette/openscale", + "coop_2/Open.Scale-2", + } { + config := loadDelivered(t) + config.Update.Repository = right + if fault := findFault(config.Validate(testRegistries()), "update.repository"); fault != nil { + t.Errorf("%q est refusé par le contrôle 48 : %s", right, fault.Message) + } + } +} + +// TestAFileWithoutTheUpdateBlockStillLoads is the symmetric of the defect of +// 28/07/2026, where control 20 made the station refuse its own delivered +// configuration: a file written before this block existed must read back with +// nothing said, and run on the default. +func TestAFileWithoutTheUpdateBlockStillLoads(t *testing.T) { + raw, err := os.ReadFile(deliveredConfigPath) + if err != nil { + t.Fatalf("lecture de %s : %v", deliveredConfigPath, err) + } + var document map[string]any + if err := json.Unmarshal(raw, &document); err != nil { + t.Fatalf("décodage : %v", err) + } + delete(document, "update") + trimmed, err := json.Marshal(document) + if err != nil { + t.Fatalf("encodage : %v", err) + } + + var config Config + if err := json.Unmarshal(trimmed, &config); err != nil { + t.Fatalf("un fichier sans le bloc update ne se relit pas : %v", err) + } + if config.Update.Repository != DefaultUpdateRepository { + t.Errorf("dépôt par défaut = %q, attendu %q", + config.Update.Repository, DefaultUpdateRepository) + } + if fault := findFault(config.Validate(testRegistries()), "update.repository"); fault != nil { + t.Errorf("l'absence du bloc update est traitée comme une faute : %s", fault.Message) + } +} + +// TestAnEmptyRepositoryFallsBackRatherThanFailing: a file that carries the block +// but leaves the key empty is the same case as one that omits it. Refusing there +// would put a station out of service over a field nobody meant to set. +func TestAnEmptyRepositoryFallsBackRatherThanFailing(t *testing.T) { + var config Config + if err := json.Unmarshal([]byte(`{"update":{"repository":""}}`), &config); err != nil { + t.Fatalf("décodage : %v", err) + } + if config.Update.Repository != DefaultUpdateRepository { + t.Errorf("dépôt = %q, attendu le défaut %q", + config.Update.Repository, DefaultUpdateRepository) + } +} + +// TestTheFollowedRepositoryEntersTheFingerprint: the four stations of one +// cooperative must follow the same repository, and a divergence has to be visible +// on the eight characters the dashboard shows and a volunteer compares by eye. +func TestTheFollowedRepositoryEntersTheFingerprint(t *testing.T) { + reference := loadDelivered(t) + diverged := loadDelivered(t) + diverged.Update.Repository = "someone-else/OpenScale" + + if reference.Fingerprint() == diverged.Fingerprint() { + t.Fatal("deux postes suivant deux dépôts différents portent la même empreinte") + } +} + +// TestTheFollowedRepositorySurvivesAnExportWithoutHardware: it is a decision of +// the cooperative and not a property of one machine, so cloning a station must +// carry it. +func TestTheFollowedRepositorySurvivesAnExportWithoutHardware(t *testing.T) { + config := loadDelivered(t) + config.Update.Repository = "la-cagette/openscale" + + if got := config.Export(false).Update.Repository; got != "la-cagette/openscale" { + t.Fatalf("dépôt après export sans matériel = %q", got) + } +} diff --git a/internal/domain/profiles.go b/internal/domain/profiles.go index 248f366..b929924 100644 --- a/internal/domain/profiles.go +++ b/internal/domain/profiles.go @@ -133,5 +133,12 @@ func NeutralProfile() Config { Admin: AdminConfig{SessionMinutes: 30, AttemptsPerMinute: 5}, Maintenance: MaintenanceConfig{WeeklyIntegrityCheck: true, DiskAlertMB: 200}, + + // The repository is named HERE and not left empty, and it matters more than + // it looks: this profile is what an out-of-service station runs, and an + // out-of-service station is exactly the one that may need a newer binary. + // Hub.UpdateGuard lets it update deliberately, for that reason; a neutral + // profile that failed control 48 would close the door the guard leaves open. + Update: UpdateConfig{Repository: DefaultUpdateRepository}, } } diff --git a/internal/domain/profiles_test.go b/internal/domain/profiles_test.go index 750c492..f1d6463 100644 --- a/internal/domain/profiles_test.go +++ b/internal/domain/profiles_test.go @@ -132,3 +132,17 @@ func TestNeutralProfileDiffersFromTheDeliveredFile(t *testing.T) { t.Fatal("le profil compilé et le fichier livré doivent avoir des empreintes distinctes") } } + +// TestTheNeutralProfileFollowsARepository is not a detail of completeness. +// +// This profile is what an OUT-OF-SERVICE station runs, and an out-of-service +// station is exactly the one that may need a newer binary: Hub.UpdateGuard lets +// it update deliberately, as the escape hatch out of a broken version. A neutral +// profile failing control 48 would close the door the guard leaves open. +func TestTheNeutralProfileFollowsARepository(t *testing.T) { + profile := NeutralProfile() + if profile.Update.Repository != DefaultUpdateRepository { + t.Fatalf("dépôt du profil neutre = %q, attendu %q", + profile.Update.Repository, DefaultUpdateRepository) + } +} diff --git a/testdata/config-demo.json b/testdata/config-demo.json index 4604127..977c64f 100644 --- a/testdata/config-demo.json +++ b/testdata/config-demo.json @@ -170,5 +170,8 @@ "maintenance": { "weekly_integrity_check": true, "disk_alert_mb": 200 + }, + "update": { + "repository": "lostmind84/OpenScale" } } diff --git a/testdata/config-lacagette.json b/testdata/config-lacagette.json index 5d87302..695e4ca 100644 --- a/testdata/config-lacagette.json +++ b/testdata/config-lacagette.json @@ -125,5 +125,6 @@ "attempts_per_minute": 5 }, - "maintenance": { "weekly_integrity_check": true, "disk_alert_mb": 200 } + "maintenance": { "weekly_integrity_check": true, "disk_alert_mb": 200 }, + "update": { "repository": "lostmind84/OpenScale" } } From 613a8a8ce943b3ac72fc9eea396b89863b645726 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 11:51:47 +0200 Subject: [PATCH 09/19] feat(station): le poste dit quand il ne peut pas tomber UpdateGuard repond a une question, et la couche HTTP la POSE au lieu de lire un etat pour en deduire une regle -- sans quoi la regle existerait a deux endroits. La table couvre les seize etats de la machine, pour qu'un etat ajoute plus tard ne tombe pas en silence du cote "oui, arretez le poste". Un test croise la regle avec swapIsSafeIn : ce qui est trop occupe pour un basculement de catalogue doit l'etre pour un redemarrage. La reciproque est fausse et c'est voulu -- OutOfService et Faulted refusent le basculement et autorisent la mise a jour, parce qu'un poste qui ne peut pas servir est justement celui qui a besoin d'un binaire neuf. C'est la porte que le profil neutre garde ouverte en nommant son depot. Le catalogue en attente refuse, et c'est la clause qui compte le plus. Un pendingBatch signifie que le CSV a deja ete lu ET SUPPRIME -- la suppression est l'acquittement -- et que les produits ne vivent plus que dans la memoire de ce processus. Arreter le poste la ne differe pas le catalogue : il le PERD, et rien ne le proposera jamais plus. C'est le mode de defaillance deja paye une fois avec le premier catalogue d'un poste sans balance. Le miroir atomique existe pour cela : pendingBatch appartient a la goroutine de boucle, et le lire depuis un gestionnaire HTTP serait une course. --- internal/station/hub.go | 49 +++++++++ internal/station/update_guard_test.go | 148 ++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 internal/station/update_guard_test.go diff --git a/internal/station/hub.go b/internal/station/hub.go index 5b105da..03fe552 100644 --- a/internal/station/hub.go +++ b/internal/station/hub.go @@ -161,6 +161,15 @@ type Hub struct { rate domain.RateMeter pendingBatch *CatalogBatch lastInteraction time.Time + // catalogWaiting mirrors « pendingBatch != nil » for readers OUTSIDE the loop. + // + // pendingBatch itself is owned by the loop goroutine and reading it from an + // HTTP handler would be a race. What needs the answer is UpdateGuard, and the + // question it asks is worth the mirror: a pending batch means the CSV has + // already been read AND DELETED -- the deletion is the acknowledgement -- so + // the products exist only in this process's memory. Stopping the station there + // does not defer the catalogue, it loses it. + catalogWaiting atomic.Bool idempotency IdempotencyCache message *Message sound string @@ -244,6 +253,44 @@ func (h *Hub) State() Snapshot { return *h.state.Load() } // Config returns the configuration in force. func (h *Hub) Config() domain.Config { return *h.cfg.Load() } +// UpdateGuard reports whether the station may be taken down to install a new +// version, and says IN FRENCH why not when it may not. +// +// The rule lives here and not in the HTTP layer, for one reason: the HTTP layer +// would have to read a state in order to deduce a rule, and the rule would then +// exist in two places. It asks a question and renders the answer. +func (h *Hub) UpdateGuard() (bool, string) { + return updateGuardFor(h.State().State, h.catalogWaiting.Load()) +} + +// updateGuardFor is the rule itself, without a Hub, so that every state of the +// machine can be put to it in one table. +// +// OutOfService and Faulted PASS, deliberately. A station that cannot serve is +// exactly the one that may need a newer binary, and refusing there would close +// the only door -- which is why NeutralProfile names a repository. +func updateGuardFor(state domain.State, catalogWaiting bool) (bool, string) { + if catalogWaiting { + // The CSV has already been read and deleted -- the deletion IS the + // acknowledgement -- and the products live only in memory until a quiet + // moment lets them enter service. Stopping the station here loses them, + // and nothing will ever offer them again. + return false, "Un catalogue vient d'arriver et n'est pas encore en service. Réessayez dans un instant." + } + switch state { + case domain.Initializing, domain.Idle, domain.ManualMode, domain.ScaleLost, + domain.Faulted, domain.OutOfService: + return true, "" + default: + // ProductArmed, WeightPresent, WeightStable, AwaitingStability, + // EnteringTare, EnteringWeight, Validating, Printing, Succeeded and + // Rejected all mean somebody is mid-cycle or reading a result. Each of + // them clears in seconds; the button says to try again rather than + // cutting a label in half. + return false, "Une pesée est en cours. Réessayez dans un instant." + } +} + // Catalog returns the catalog in service, or nil before the first one. func (h *Hub) Catalog() *domain.Catalog { return h.catalog.Load() } @@ -466,6 +513,7 @@ func (h *Hub) run(ctx context.Context, ticks <-chan time.Time) { // customer's finger. The Tick drains it when the station has // been idle and untouched for MaxSwitchIdle. h.pendingBatch = batch + h.catalogWaiting.Store(true) continue } // NOTHING is on screen yet. There is no finger to reorder tiles @@ -636,6 +684,7 @@ func (h *Hub) applyPendingBatch(now time.Time) { } h.storeCatalog(h.pendingBatch.Catalog, now) h.pendingBatch = nil + h.catalogWaiting.Store(false) } // applySubscription adds or removes one subscriber, in the loop goroutine. diff --git a/internal/station/update_guard_test.go b/internal/station/update_guard_test.go new file mode 100644 index 0000000..466cf9e --- /dev/null +++ b/internal/station/update_guard_test.go @@ -0,0 +1,148 @@ +package station + +import ( + "strings" + "testing" + + "openscale/internal/domain" +) + +// TestTheGuardAnswersForEveryStateOfTheMachine covers the whole enumeration, so +// that a state added later cannot fall through silently into « yes, take the +// station down ». +func TestTheGuardAnswersForEveryStateOfTheMachine(t *testing.T) { + cases := []struct { + state domain.State + allow bool + }{ + // At rest, in one form or another. + {domain.Initializing, true}, + {domain.Idle, true}, + {domain.ManualMode, true}, + {domain.ScaleLost, true}, + // Cannot serve at all: an update costs nothing there and may be the cure. + {domain.Faulted, true}, + {domain.OutOfService, true}, + // Somebody is mid-cycle, or reading the result of one. + {domain.ProductArmed, false}, + {domain.WeightPresent, false}, + {domain.WeightStable, false}, + {domain.AwaitingStability, false}, + {domain.EnteringTare, false}, + {domain.EnteringWeight, false}, + {domain.Validating, false}, + {domain.Printing, false}, + {domain.Succeeded, false}, + {domain.Rejected, false}, + } + for _, c := range cases { + allowed, reason := updateGuardFor(c.state, false) + if allowed != c.allow { + t.Errorf("état %s : autorisé %v, attendu %v", c.state, allowed, c.allow) + } + switch { + case allowed && reason != "": + t.Errorf("état %s : autorisé mais donne une raison %q", c.state, reason) + case !allowed && reason == "": + t.Errorf("état %s : refuse sans dire pourquoi", c.state) + } + } +} + +// TestTheGuardSpeaksFrenchAndNamesNoDocument: the sentence is shown to a +// volunteer, so it carries no section number and no ADR. +func TestTheGuardSpeaksFrenchAndNamesNoDocument(t *testing.T) { + for _, state := range []domain.State{domain.Printing, domain.EnteringTare} { + _, reason := updateGuardFor(state, false) + if strings.Contains(reason, "§") || strings.Contains(reason, "ADR-") { + t.Errorf("état %s : la raison porte un renvoi de dossier : %q", state, reason) + } + } + if _, reason := updateGuardFor(domain.Idle, true); reason == "" { + t.Fatal("un catalogue en attente refuse sans dire pourquoi") + } +} + +// TestACatalogWaitingToEnterServiceHoldsTheUpdateBack is the failure mode this +// clause exists for, and it is one this project has already paid for. +// +// A pending batch means the CSV has already been read AND DELETED -- the deletion +// is the acknowledgement (§10.1) -- and the products live only in this process's +// memory, waiting for a quiet moment to enter service. Stopping the station there +// does not defer the catalogue: it LOSES it, and nothing will ever offer it again. +func TestACatalogWaitingToEnterServiceHoldsTheUpdateBack(t *testing.T) { + allowed, reason := updateGuardFor(domain.Idle, true) + if allowed { + t.Fatal("un catalogue en attente de mise en service laisse passer une mise à jour") + } + if !strings.Contains(strings.ToLower(reason), "catalogue") { + t.Errorf("la raison ne nomme pas le catalogue : %q", reason) + } +} + +// TestTheGuardAgreesWithTheCatalogSwapOnEveryStateItAllows. +// +// swapIsSafeIn already answers « is anybody mid-cycle? » for the catalogue, and +// the update asks the same question with more at stake. Whatever is too busy for +// a catalogue swap must be too busy for a reboot; the reverse need not hold, and +// does not -- OutOfService and Faulted refuse a swap and allow an update. +func TestTheGuardAgreesWithTheCatalogSwapOnEveryStateItAllows(t *testing.T) { + for state := domain.Initializing; state <= domain.OutOfService; state++ { + if !swapIsSafeIn(state) { + continue + } + if allowed, reason := updateGuardFor(state, false); !allowed { + t.Errorf("état %s : le catalogue peut basculer mais la mise à jour est refusée (%s)", + state, reason) + } + } +} + +// TestAStationInTheMiddleOfAWeighingRefuses drives a real Hub rather than the +// pure function: it is the wiring that could be wrong, not the table. +func TestAStationInTheMiddleOfAWeighingRefuses(t *testing.T) { + b := newBench(t) + + if allowed, reason := b.hub.UpdateGuard(); !allowed { + t.Fatalf("un poste au repos refuse la mise à jour : %s", reason) + } + + b.feed(1236, 5) + ack := b.tap("01J9F2ABC", 1236) + if !ack.Accepted { + t.Fatalf("pesée refusée : %s (%s)", ack.Message, ack.Code) + } + if ack.State != domain.Printing { + t.Fatalf("état de l'accusé %s, attendu printing", ack.State) + } + b.flush() + + // The published state here is whatever the cycle has reached -- printing while + // the worker holds the job, weight_stable once it has answered and the bag is + // still on the plate. What matters is that it is NOT at rest: the bag has not + // been taken off, so the customer is still in front of the screen. + if got := b.hub.State().State; got == domain.Idle { + t.Fatal("le poste est retombé au repos alors que le sac est encore sur le plateau") + } + allowed, reason := b.hub.UpdateGuard() + if allowed { + t.Fatal("une pesée est en cours et la mise à jour passe") + } + if reason == "" { + t.Error("le refus ne porte aucune phrase") + } +} + +// TestAStationOutOfServiceMayBeUpdated is the escape hatch, and it is deliberate: +// a broken version is exactly the case where the button has to work. The neutral +// profile names a repository for this reason. +func TestAStationOutOfServiceMayBeUpdated(t *testing.T) { + b := newBench(t, func(o *benchOptions) { o.outOfService = true }) + + if got := b.hub.State().State; got != domain.OutOfService { + t.Fatalf("état %s, attendu out_of_service", got) + } + if allowed, reason := b.hub.UpdateGuard(); !allowed { + t.Fatalf("un poste hors service refuse sa seule porte de sortie : %s", reason) + } +} From 28461f59770d5b3cafa1a1625ea77e2d42024afb Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 11:54:52 +0200 Subject: [PATCH 10/19] feat(update): l'etat de la bascule vit dans des fichiers, jamais en base Rien ici ne vaut une migration de schema, et surtout : un poste qui ne demarre pas doit pouvoir dire ce qui lui est arrive, ce qui est exactement le moment ou une base de donnees est la chose sur laquelle on ne peut pas compter. TakeOutcome consomme le compte rendu une seule fois -- le renommage est ce qui rend l'operation idempotente, sans quoi un poste redemarre trois fois journaliserait trois fois la meme bascule -- et emporte le staging QUELLE QUE SOIT l'issue : une bascule annulee laisse les memes dizaines de mega-octets qu'une reussie. SwapBudget repond a ce que le banc a laisse ouvert. Un script qui ne demarre pas laisse Start() rendre nil : pending.json est ecrit et aucun outcome.json n'arrivera jamais. Sans age, ErrAlreadyRunning refuserait toute mise a jour ulterieure, pour toujours, sur la foi d'une bascule qui n'a rien fait et rien dit. Un instant de depart nul compte comme perime -- un fichier tronque par une coupure de courant ne doit pas se lire "demarree a l'instant", c'est le meme mur par une autre route. Et ApplyUpdate porte CREATE_NO_WINDOW, pas DETACHED_PROCESS. Les deux cachent la fenetre, un seul execute le script : mesure du 29/07, powershell.exe sort en 100 ms avec le code 0 SANS LIRE SON FICHIER, faute de console a attacher. Le drapeau dont le nom dit "detache" est celui qui ne lance rien, et le commentaire interdit de le retablir. --- internal/platform/update.go | 32 ++++ internal/platform/update_other.go | 10 ++ internal/platform/update_windows.go | 52 ++++++ internal/update/state.go | 232 +++++++++++++++++++++++++++ internal/update/state_test.go | 240 ++++++++++++++++++++++++++++ 5 files changed, 566 insertions(+) create mode 100644 internal/platform/update.go create mode 100644 internal/platform/update_other.go create mode 100644 internal/platform/update_windows.go create mode 100644 internal/update/state.go create mode 100644 internal/update/state_test.go diff --git a/internal/platform/update.go b/internal/platform/update.go new file mode 100644 index 0000000..8ea16e8 --- /dev/null +++ b/internal/platform/update.go @@ -0,0 +1,32 @@ +package platform + +import "errors" + +// ErrUpdateUnsupported reports a platform where the screen cannot swap the binary. +// +// The cooperative's four stations run Windows, and update.sh stays a manual +// procedure on a Raspberry Pi. Writing a systemd path that nothing would ever +// exercise would prove nothing and would have to be maintained anyway. +var ErrUpdateUnsupported = errors.New("platform: update from the screen needs Windows") + +// UpdateSpec is everything the swap needs. +// +// EVERY PATH IS ABSOLUTE and comes from the running process -- os.Executable() +// and the configured data root -- never from a default the script would guess. +// The script has defaults of its own, and they point at Program Files; a station +// installed anywhere else would be updated somewhere it does not live. +type UpdateSpec struct { + // Script is the update.ps1 of the STAGED archive, and it has to be: no + // station carries one. install.ps1 copies the binary and two documents, and + // nothing else, so the only update.ps1 within reach is the one that just + // came down with the release. + Script string + // Source is the new binary, beside that script. + Source string + InstallDir string + DataRoot string + // OutcomePath is where the script writes what it did. It is the only thing + // that crosses the death of this process. + OutcomePath string + LogPath string +} diff --git a/internal/platform/update_other.go b/internal/platform/update_other.go new file mode 100644 index 0000000..e9106fc --- /dev/null +++ b/internal/platform/update_other.go @@ -0,0 +1,10 @@ +//go:build !windows + +package platform + +// ApplyUpdate is not available off Windows. +// +// The station still carries the routes and answers honestly rather than hiding +// them: a Linux station that offered a button doing nothing would be worse than +// one that says the button does not exist here. +func ApplyUpdate(UpdateSpec) error { return ErrUpdateUnsupported } diff --git a/internal/platform/update_windows.go b/internal/platform/update_windows.go new file mode 100644 index 0000000..ff69711 --- /dev/null +++ b/internal/platform/update_windows.go @@ -0,0 +1,52 @@ +package platform + +import ( + "fmt" + "os/exec" + "syscall" + + "golang.org/x/sys/windows" +) + +// ApplyUpdate starts the swap and RETURNS IMMEDIATELY. +// +// The script stops the service, which kills this very process. That is why the +// child is given a process group of its own and why its handle is released at +// once: a child left in the parent's group would be torn down with it, and the +// station would be left with a stopped service and a binary nobody replaced. +// +// # CREATE_NO_WINDOW, and never DETACHED_PROCESS +// +// Both hide the window. Only one runs the script. MEASURED on the bench on +// 29/07/2026, over four sets of flags: with DETACHED_PROCESS, powershell.exe +// exits after 100 ms with code 0 WITHOUT READING ITS FILE -- it is a console +// application, and its host gives up when it has no console to attach to and is +// forbidden from creating one. Nothing in the exit status says the script never +// ran, which makes it the worst possible failure for an update whose output +// nobody is watching. +// +// DETACHED_PROCESS looks more correct than CREATE_NO_WINDOW -- it is the flag +// whose name says « detached » -- and it is the one that launches nothing. Do not +// restore it. +// +// That the child then survives the SCM stopping its parent was measured the same +// day: 113 of the witness's 120 lines were written after the service had stopped. +func ApplyUpdate(spec UpdateSpec) error { + command := exec.Command("powershell.exe", + "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", + "-File", spec.Script, + "-Source", spec.Source, + "-InstallDir", spec.InstallDir, + "-DataRoot", spec.DataRoot, + "-OutcomePath", spec.OutcomePath, + "-LogPath", spec.LogPath) + command.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: windows.CREATE_NO_WINDOW | windows.CREATE_NEW_PROCESS_GROUP, + } + if err := command.Start(); err != nil { + return fmt.Errorf("platform: starting the update script: %w", err) + } + // Release and not Wait: this process is about to be stopped by the script it + // has just started, and waiting on a child that outlives you never returns. + return command.Process.Release() +} diff --git a/internal/update/state.go b/internal/update/state.go new file mode 100644 index 0000000..63eadf6 --- /dev/null +++ b/internal/update/state.go @@ -0,0 +1,232 @@ +package update + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "time" +) + +// The four values update.ps1 writes into outcome.json, and which the screen turns +// into four different sentences. +// +// « Rolled back, the station works » and « rolled back, the station is dead » do +// not ask the same thing of a volunteer: the first calls nobody, the second does. +const ( + StatusSucceeded = "succeeded" + StatusRolledBack = "rolled-back" + StatusRolledBackUnhealthy = "rolled-back-unhealthy" + StatusNotStarted = "not-started" +) + +// keptOutcomes bounds a directory nothing else prunes. +const keptOutcomes = 3 + +// SwapBudget is how long a swap may plausibly take before it is written off. +// +// THE BENCH OF 29/07/2026 IS WHY THIS EXISTS. A script that never starts leaves +// Start() returning nil -- measured: with DETACHED_PROCESS, powershell.exe exits +// after 100 ms with code 0 without reading its file -- so pending.json is written +// and no outcome.json will ever arrive. Without an age, ErrAlreadyRunning would +// refuse every later update, forever, on the strength of a swap that did nothing +// and said nothing: a station walled shut by a failure that left no trace. +// +// The real path is minutes: stop (16 s of drain budget), copy, start, sixty +// seconds of health check, and the same again on the rollback. Fifteen minutes is +// generous enough never to cut a live swap in half, and short enough that a +// walled station frees itself inside a coffee break. +const SwapBudget = 15 * time.Minute + +// Check is what the daily poll left behind. +type Check struct { + CheckedAt time.Time `json:"checked_at"` + Tag string `json:"tag"` + Version string `json:"version"` + PublishedAt time.Time `json:"published_at"` + HTMLURL string `json:"html_url"` +} + +// Pending says a swap is in flight: what was wanted, and where its staging lives. +type Pending struct { + Tag string `json:"tag"` + To string `json:"to"` + From string `json:"from"` + StartedAt time.Time `json:"started_at"` + StagingRoot string `json:"staging_root"` +} + +// Stale reports a swap that has outlived its budget without ever reporting. +// +// A zero instant counts as stale: a file written by an older version, or one +// truncated by a power cut, must not read as « started just now » -- that is the +// same wall, reached by a different road. +func (p Pending) Stale(now time.Time) bool { + if p.StartedAt.IsZero() { + return true + } + return now.Sub(p.StartedAt) > SwapBudget +} + +// Outcome is what update.ps1 wrote, and it is written on ALL FOUR of its exits. +// +// The station reads it at the NEXT START, whichever binary starts -- the new one +// or the restored one. By then the process that could have read an exit code has +// been dead for a minute: this file is the only thing that crosses. +type Outcome struct { + Status string `json:"status"` + ExitCode int `json:"exit_code"` + From string `json:"from"` + To string `json:"to"` + Reason string `json:"reason"` + Backup string `json:"backup"` + DatabaseBackups []string `json:"database_backups"` + FinishedAt time.Time `json:"finished_at"` +} + +// State is /updates: the whole persistence of this feature. +// +// Files and not the database, deliberately. There is nothing here worth a schema +// migration, and -- more to the point -- a station that fails to start must still +// be able to say what happened to it, which is exactly when a database is the +// thing one cannot count on. +type State struct{ Dir string } + +// ReadCheck returns the last poll, and whether there was one. +func (s State) ReadCheck() (Check, bool, error) { + var check Check + found, err := s.read("check.json", &check) + return check, found, err +} + +// WriteCheck records one poll. +func (s State) WriteCheck(c Check) error { return s.write("check.json", c) } + +// ReadPending returns the swap in flight, and whether there is one. +func (s State) ReadPending() (Pending, bool, error) { + var pending Pending + found, err := s.read("pending.json", &pending) + return pending, found, err +} + +// WritePending records that a swap is about to start. +func (s State) WritePending(p Pending) error { return s.write("pending.json", p) } + +// ClearPending abandons a swap that will never report, and takes its staging with +// it. It is what unwalls a station after the failure SwapBudget describes. +func (s State) ClearPending() error { + pending, found, err := s.ReadPending() + if err != nil { + return err + } + if found && pending.StagingRoot != "" { + _ = os.RemoveAll(pending.StagingRoot) + } + if err := os.Remove(filepath.Join(s.Dir, "pending.json")); err != nil && + !errors.Is(err, os.ErrNotExist) { + return err + } + return nil +} + +// TakeOutcome consumes the report update.ps1 left, exactly once. +// +// It reads outcome.json, renames it out of the way, deletes the staging directory +// pending.json named -- WHATEVER THE OUTCOME, because a cancelled swap leaves the +// same tens of megabytes as a successful one -- and drops pending.json. +// +// The rename is what makes it idempotent: a station rebooted three times does not +// journal the same swap three times. +func (s State) TakeOutcome() (Outcome, bool, error) { + var outcome Outcome + found, err := s.read("outcome.json", &outcome) + if err != nil || !found { + return Outcome{}, found, err + } + if err := s.ClearPending(); err != nil { + return Outcome{}, true, err + } + + stamp := outcome.FinishedAt.UTC().Format("20060102-150405.000") + archived := filepath.Join(s.Dir, fmt.Sprintf("outcome-%s.json", stamp)) + if err := os.Rename(filepath.Join(s.Dir, "outcome.json"), archived); err != nil { + return outcome, true, err + } + s.prune() + return outcome, true, nil +} + +// LastOutcome returns the most recent report, for the screen. +func (s State) LastOutcome() (Outcome, bool, error) { + names, err := s.archivedOutcomes() + if err != nil || len(names) == 0 { + return Outcome{}, false, err + } + raw, err := os.ReadFile(names[len(names)-1]) + if err != nil { + return Outcome{}, false, err + } + var outcome Outcome + if err := json.Unmarshal(raw, &outcome); err != nil { + return Outcome{}, false, err + } + return outcome, true, nil +} + +// archivedOutcomes lists the consumed reports, oldest first. +// +// The stamp is « YYYYMMDD-HHMMSS.mmm », so lexicographic order IS chronological +// order -- which is the only reason a sort of strings is allowed to answer a +// question about time. +func (s State) archivedOutcomes() ([]string, error) { + names, err := filepath.Glob(filepath.Join(s.Dir, "outcome-*.json")) + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} + +// prune keeps the last keptOutcomes reports and drops the rest. +func (s State) prune() { + names, err := s.archivedOutcomes() + if err != nil || len(names) <= keptOutcomes { + return + } + for _, name := range names[:len(names)-keptOutcomes] { + _ = os.Remove(name) + } +} + +// read decodes one state file. +// +// A MISSING FILE IS NOT AN ERROR: it is the state of a station that has never +// been updated. A file that is there and unreadable IS one -- treating the second +// as the first would silently restart a swap somebody is watching. +func (s State) read(name string, into any) (bool, error) { + raw, err := os.ReadFile(filepath.Join(s.Dir, name)) + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + if err != nil { + return false, err + } + if err := json.Unmarshal(raw, into); err != nil { + return false, fmt.Errorf("update: %s illisible: %w", name, err) + } + return true, nil +} + +// write records one state file, creating the directory if this is the first. +func (s State) write(name string, value any) error { + if err := os.MkdirAll(s.Dir, 0o755); err != nil { + return err + } + raw, err := json.MarshalIndent(value, "", " ") + if err != nil { + return err + } + return os.WriteFile(filepath.Join(s.Dir, name), raw, 0o644) +} diff --git a/internal/update/state_test.go b/internal/update/state_test.go new file mode 100644 index 0000000..f7623e3 --- /dev/null +++ b/internal/update/state_test.go @@ -0,0 +1,240 @@ +package update + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" + "time" +) + +// benchEpoch is an arbitrary fixed instant. Nothing here reads a wall clock. +var benchEpoch = time.Date(2026, 7, 29, 10, 15, 0, 0, time.UTC) + +// writeOutcomeFile writes what update.ps1 writes. +func writeOutcomeFile(t *testing.T, dir string, outcome Outcome) { + t.Helper() + raw, err := json.Marshal(outcome) + if err != nil { + t.Fatalf("encodage : %v", err) + } + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("création du répertoire : %v", err) + } + if err := os.WriteFile(filepath.Join(dir, "outcome.json"), raw, 0o644); err != nil { + t.Fatalf("écriture : %v", err) + } +} + +// TestTakeOutcomeIsIdempotent is the property that keeps a station rebooted three +// times from journalling the same swap three times. +func TestTakeOutcomeIsIdempotent(t *testing.T) { + dir := t.TempDir() + state := State{Dir: dir} + + staging := filepath.Join(dir, "2.1.0") + if err := os.MkdirAll(staging, 0o755); err != nil { + t.Fatalf("staging : %v", err) + } + if err := state.WritePending(Pending{ + Tag: "2.1.0", To: "2.1.0", From: "2.0.3", + StartedAt: benchEpoch, StagingRoot: staging, + }); err != nil { + t.Fatalf("WritePending : %v", err) + } + writeOutcomeFile(t, dir, Outcome{ + Status: StatusSucceeded, ExitCode: 0, From: "2.0.3", To: "2.1.0", + FinishedAt: benchEpoch.Add(time.Minute), + }) + + first, found, err := state.TakeOutcome() + if err != nil || !found { + t.Fatalf("TakeOutcome : %v, trouvé %v", err, found) + } + if first.Status != StatusSucceeded || first.To != "2.1.0" { + t.Errorf("compte rendu lu = %+v", first) + } + + if _, found, err = state.TakeOutcome(); err != nil || found { + t.Fatalf("le même compte rendu est repris une seconde fois (trouvé %v, %v)", found, err) + } + if _, err := os.Stat(staging); !os.IsNotExist(err) { + t.Error("le répertoire de staging survit à la lecture du compte rendu") + } + if _, found, _ := state.ReadPending(); found { + t.Error("pending.json survit à la lecture du compte rendu") + } + + last, found, err := state.LastOutcome() + if err != nil || !found { + t.Fatalf("LastOutcome : %v, trouvé %v", err, found) + } + if last.To != "2.1.0" { + t.Errorf("le dernier compte rendu servi à l'écran = %+v", last) + } +} + +// TestTheStagingIsCleanedWhateverTheOutcome: a cancelled swap must not leave tens +// of megabytes on a station nobody watches. +func TestTheStagingIsCleanedWhateverTheOutcome(t *testing.T) { + dir := t.TempDir() + state := State{Dir: dir} + staging := filepath.Join(dir, "2.1.0") + if err := os.MkdirAll(staging, 0o755); err != nil { + t.Fatalf("staging : %v", err) + } + if err := state.WritePending(Pending{ + Tag: "2.1.0", StartedAt: benchEpoch, StagingRoot: staging, + }); err != nil { + t.Fatalf("WritePending : %v", err) + } + writeOutcomeFile(t, dir, Outcome{ + Status: StatusRolledBack, ExitCode: 10, Reason: "le poste ne répond pas", + FinishedAt: benchEpoch.Add(time.Minute), + }) + + if _, _, err := state.TakeOutcome(); err != nil { + t.Fatalf("TakeOutcome : %v", err) + } + if _, err := os.Stat(staging); !os.IsNotExist(err) { + t.Error("une bascule annulée laisse son staging derrière elle") + } +} + +// TestOnlyThreeOutcomesAreKept bounds a directory nothing else prunes. +func TestOnlyThreeOutcomesAreKept(t *testing.T) { + dir := t.TempDir() + state := State{Dir: dir} + + for i := range 5 { + writeOutcomeFile(t, dir, Outcome{ + Status: StatusSucceeded, To: fmt.Sprintf("2.1.%d", i), + FinishedAt: benchEpoch.Add(time.Duration(i) * time.Minute), + }) + if _, _, err := state.TakeOutcome(); err != nil { + t.Fatalf("TakeOutcome : %v", err) + } + } + kept, err := filepath.Glob(filepath.Join(dir, "outcome-*.json")) + if err != nil { + t.Fatalf("glob : %v", err) + } + if len(kept) != keptOutcomes { + t.Fatalf("%d comptes rendus gardés, attendu %d", len(kept), keptOutcomes) + } + // And the one the screen shows is the NEWEST, not whichever the filesystem + // happened to hand back first. + last, found, err := state.LastOutcome() + if err != nil || !found { + t.Fatalf("LastOutcome : %v, trouvé %v", err, found) + } + if last.To != "2.1.4" { + t.Errorf("dernier compte rendu = %q, attendu 2.1.4", last.To) + } +} + +// TestNoOutcomeIsNotAnError: a station that has never been updated reads nothing +// and says so, rather than failing. +func TestNoOutcomeIsNotAnError(t *testing.T) { + state := State{Dir: filepath.Join(t.TempDir(), "jamais-cree")} + if _, found, err := state.TakeOutcome(); err != nil || found { + t.Fatalf("TakeOutcome sur un poste neuf : trouvé %v, %v", found, err) + } + if _, found, err := state.LastOutcome(); err != nil || found { + t.Fatalf("LastOutcome sur un poste neuf : trouvé %v, %v", found, err) + } + if _, found, err := state.ReadPending(); err != nil || found { + t.Fatalf("ReadPending sur un poste neuf : trouvé %v, %v", found, err) + } + if _, found, err := state.ReadCheck(); err != nil || found { + t.Fatalf("ReadCheck sur un poste neuf : trouvé %v, %v", found, err) + } +} + +// TestAPendingSwapThatNeverReportedGoesStale is the failure mode the bench of +// 29/07/2026 uncovered, and it walls a station shut. +// +// A script that never starts leaves Start() returning nil -- measured: with +// DETACHED_PROCESS, powershell.exe exits after 100 ms with code 0 without reading +// its file -- so pending.json is written and no outcome.json will EVER arrive. +// Without an age, ErrAlreadyRunning would refuse every later update, forever, on +// the strength of a swap that did nothing and said nothing. +func TestAPendingSwapThatNeverReportedGoesStale(t *testing.T) { + pending := Pending{Tag: "2.1.0", StartedAt: benchEpoch} + + if pending.Stale(benchEpoch.Add(SwapBudget - time.Second)) { + t.Error("une bascule dans son budget est déclarée périmée") + } + if !pending.Stale(benchEpoch.Add(SwapBudget + time.Second)) { + t.Error("une bascule qui a dépassé son budget n'est jamais périmée") + } +} + +// TestAPendingSwapWithoutAnInstantIsStale: a file written by an older version, or +// one truncated by a power cut, carries the zero time. Reading that as « started +// just now » would wall the station for the same reason. +func TestAPendingSwapWithoutAnInstantIsStale(t *testing.T) { + if !(Pending{Tag: "2.1.0"}).Stale(benchEpoch) { + t.Fatal("une bascule sans instant de départ n'est pas déclarée périmée") + } +} + +// TestClearPendingLetsAWalledStationTryAgain. +func TestClearPendingLetsAWalledStationTryAgain(t *testing.T) { + dir := t.TempDir() + state := State{Dir: dir} + staging := filepath.Join(dir, "2.1.0") + if err := os.MkdirAll(staging, 0o755); err != nil { + t.Fatalf("staging : %v", err) + } + if err := state.WritePending(Pending{ + Tag: "2.1.0", StartedAt: benchEpoch, StagingRoot: staging, + }); err != nil { + t.Fatalf("WritePending : %v", err) + } + + if err := state.ClearPending(); err != nil { + t.Fatalf("ClearPending : %v", err) + } + if _, found, _ := state.ReadPending(); found { + t.Fatal("pending.json survit à ClearPending") + } + if _, err := os.Stat(staging); !os.IsNotExist(err) { + t.Error("le staging d'une bascule abandonnée reste sur le disque") + } +} + +// TestAnUnreadableStateFileIsAnErrorAndNotAnAbsence: « the file is not there » +// and « the file is corrupt » are two different things, and treating the second +// as the first would silently restart a swap somebody is watching. +func TestAnUnreadableStateFileIsAnErrorAndNotAnAbsence(t *testing.T) { + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "pending.json"), + []byte("{ceci n'est pas du json"), 0o644); err != nil { + t.Fatalf("écriture : %v", err) + } + if _, _, err := (State{Dir: dir}).ReadPending(); err == nil { + t.Fatal("un pending.json illisible passe pour une absence") + } +} + +// TestTheCheckSurvivesARestart: without it a station that reboots every night +// would poll on every start and never on a schedule. +func TestTheCheckSurvivesARestart(t *testing.T) { + dir := t.TempDir() + if err := (State{Dir: dir}).WriteCheck(Check{ + CheckedAt: benchEpoch, Tag: "2.1.0", Version: "2.1.0", + PublishedAt: benchEpoch.Add(-24 * time.Hour), + HTMLURL: "https://github.com/lostmind84/OpenScale/releases/tag/2.1.0", + }); err != nil { + t.Fatalf("WriteCheck : %v", err) + } + check, found, err := (State{Dir: dir}).ReadCheck() + if err != nil || !found { + t.Fatalf("ReadCheck : trouvé %v, %v", found, err) + } + if !check.CheckedAt.Equal(benchEpoch) || check.Version != "2.1.0" { + t.Errorf("sondage relu = %+v", check) + } +} From 49959648440c249bb8933b4e540ac096219f9a20 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 12:02:28 +0200 Subject: [PATCH 11/19] fix(deploy): l'ecran client revient, et la bascule dit ce qu'elle a fait Deux defauts, dont un existant depuis toujours. L'ECRAN CLIENT RESTAIT NOIR. Stop-OpenScaleBinaryHolders termine la tache du kiosque avec schtasks /end, openscale-kiosk.xml ne porte QU'UN declencheur d'ouverture de session, et personne ne la relancait -- ni install.ps1, ni update.ps1. Apres une mise a jour, ou apres un installeur rejoue sur un poste qui marche (le geste que TROUBLESHOOTING.md recommande), l'ecran client restait mort jusqu'a ce que quelqu'un rouvre une session. Le defaut a survecu parce qu'un humain qui met a jour un poste finit par le redemarrer ; un benevole qui touche un bouton sur l'ecran d'administration, lui, regarde l'ecran client dans la minute. Start-OpenScaleKiosk est appelee sur les quatre sorties, y compris le retour arriere : un rollback reussi qui laisserait l'ecran noir serait une panne creee par la reparation. UPDATE.PS1 CONFONDAIT SES ECHECS. Un seul "exit 1" pour "restaure, le poste marche" et "restaure, le poste est mort" -- les deux ne demandent pas la meme chose, le premier n'appelle personne. Quatre codes maintenant, et un outcome.json ecrit sur les quatre : au moment ou ce script se termine, le processus qui aurait pu lire son code de retour est mort depuis une minute, puisque c'est ce script qui l'a arrete. Le fichier est la seule chose qui traverse, et c'est le binaire qui demarre ensuite -- le neuf ou l'ancien restaure -- qui le relit. --- deploy/deploy_test.go | 108 +++++++++++++++++++++++++++++++++ deploy/windows/common.ps1 | 29 +++++++++ deploy/windows/install.ps1 | 8 +++ deploy/windows/update.ps1 | 121 ++++++++++++++++++++++++++++++++++--- 4 files changed, 258 insertions(+), 8 deletions(-) diff --git a/deploy/deploy_test.go b/deploy/deploy_test.go index 2eff806..8051eb1 100644 --- a/deploy/deploy_test.go +++ b/deploy/deploy_test.go @@ -1261,3 +1261,111 @@ func TestTheFifteenMinutesAreCountedAndNotClaimed(t *testing.T) { } t.Logf("les étapes chiffrées de INSTALLATION.md totalisent %d minutes", total) } + +// --- update.ps1 comme CONTRAT, et non plus comme script qu'on lit ------------------- + +// TestTheUpdaterTakesEveryParameterTheStationPasses freezes the contract between +// internal/platform and the script. +// +// A parameter renamed on one side and not the other is a swap that never starts, +// and nothing else in this repository would catch it: the station hands these six +// on a command line, and PowerShell binds what it recognises and ignores the rest. +func TestTheUpdaterTakesEveryParameterTheStationPasses(t *testing.T) { + updater := codeOnly(readFile(t, filepath.Join("windows", "update.ps1"))) + for _, parameter := range []string{ + "$Source", "$InstallDir", "$DataRoot", "$OutcomePath", "$LogPath", + } { + if !strings.Contains(updater, parameter) { + t.Errorf("update.ps1 ne déclare pas le paramètre %s", parameter) + } + } +} + +// TestTheUpdaterReportsOnAllFourOfItsExits is what lets the screen tell « failed, +// rolled back, the station works » from « failed, the station is dead ». The two +// do not ask the same thing of a volunteer: the first calls nobody. +func TestTheUpdaterReportsOnAllFourOfItsExits(t *testing.T) { + updater := codeOnly(readFile(t, filepath.Join("windows", "update.ps1"))) + for _, code := range []string{"exit 10", "exit 11", "exit 12"} { + if !strings.Contains(updater, code) { + t.Errorf("update.ps1 ne sort jamais par « %s »", code) + } + } + for _, status := range []string{ + "succeeded", "rolled-back", "rolled-back-unhealthy", "not-started", + } { + if !strings.Contains(updater, status) { + t.Errorf("update.ps1 n'écrit jamais le statut %q", status) + } + } + if !strings.Contains(updater, "function Write-Outcome") { + t.Error("update.ps1 n'a pas de fonction unique d'écriture du compte rendu : " + + "quatre écritures dispersées, c'est trois occasions d'en oublier une") + } +} + +// TestTheOutcomeCarriesEveryFieldTheStationReads freezes the JSON keys against +// update.Outcome. The station reads this file at its NEXT START, when the process +// that could have read an exit code has been dead for a minute. +func TestTheOutcomeCarriesEveryFieldTheStationReads(t *testing.T) { + updater := codeOnly(readFile(t, filepath.Join("windows", "update.ps1"))) + for _, key := range []string{ + "status", "exit_code", "from", "to", "reason", "backup", + "database_backups", "finished_at", + } { + if !strings.Contains(updater, key) { + t.Errorf("le compte rendu ne porte pas la clé %q", key) + } + } +} + +// TestTheUpdaterBringsTheClientScreenBack is the defect this work uncovered. +// +// Stop-OpenScaleBinaryHolders ends the kiosk task, openscale-kiosk.xml carries a +// LogonTrigger AND NOTHING ELSE, and nobody restarted it: neither install.ps1 nor +// update.ps1. The client screen stayed black until somebody logged on. +// +// It never showed because a human who updates a station ends up rebooting it. A +// volunteer who touches a button on the administration screen does not -- they +// look at the client screen within the minute. +func TestTheUpdaterBringsTheClientScreenBack(t *testing.T) { + common := codeOnly(readFile(t, filepath.Join("windows", "common.ps1"))) + if !strings.Contains(common, "function Start-OpenScaleKiosk") { + t.Fatal("common.ps1 ne porte pas la relance de l'écran client") + } + if !strings.Contains(common, "schtasks /run") { + t.Error("la relance n'appelle pas schtasks /run") + } + + updater := codeOnly(readFile(t, filepath.Join("windows", "update.ps1"))) + if !strings.Contains(updater, "Start-OpenScaleKiosk") { + t.Fatal("update.ps1 ne relance jamais l'écran client") + } + // The installer stops the kiosk too, and for the same reason -- it replaces the + // binary the task is running. Re-running install.ps1 on a working station is + // what TROUBLESHOOTING.md recommends, so it must not leave a black screen. + if !strings.Contains(codeOnly(readFile(t, filepath.Join("windows", "install.ps1"))), + "Start-OpenScaleKiosk") { + t.Error("install.ps1 ne relance pas l'écran client qu'il vient d'arrêter") + } +} + +// TestTheClientScreenComesBackOnTheFailurePathsToo. +// +// A rollback that leaves the client screen black is a breakdown created by the +// repair: the station serves again, the customer sees nothing, and the volunteer +// concludes the update destroyed the poste. +func TestTheClientScreenComesBackOnTheFailurePathsToo(t *testing.T) { + updater := codeOnly(readFile(t, filepath.Join("windows", "update.ps1"))) + restarts := strings.Count(updater, "Start-OpenScaleKiosk") + // Four exits: succeeded, rolled-back, rolled-back-unhealthy, not-started. The + // last two not-started paths share one call, hence three at least. + if restarts < 3 { + t.Fatalf("%d relance(s) de l'écran client dans update.ps1 : les chemins d'échec "+ + "n'en ont pas", restarts) + } + failure := updater[strings.Index(updater, "if ($failure)"):] + if !strings.Contains(failure, "Start-OpenScaleKiosk") { + t.Error("le chemin d'échec ne relance pas l'écran client") + } +} diff --git a/deploy/windows/common.ps1 b/deploy/windows/common.ps1 index 93dccfb..9a3f767 100644 --- a/deploy/windows/common.ps1 +++ b/deploy/windows/common.ps1 @@ -342,6 +342,35 @@ function Stop-OpenScaleBinaryHolders { Wait-FileReplaceable -Path $Paths.Binary } +function Start-OpenScaleKiosk { + <# + .SYNOPSIS + Relance l'écran client après un arrêt qui l'a coupé. + .DESCRIPTION + ★ CE QUI MANQUAIT, ET QUI NE SE VOYAIT PAS. Stop-OpenScaleBinaryHolders termine la + tâche du kiosque avec « schtasks /end », openscale-kiosk.xml ne porte QU'UN + déclencheur d'ouverture de session, et rien ne la redémarrait — ni install.ps1, ni + update.ps1. Après une mise à jour, ou après un installeur relancé sur un poste qui + marche, l'écran client restait donc NOIR jusqu'à ce que quelqu'un rouvre une session. + + Le défaut a survécu parce qu'un humain qui met à jour un poste finit par le + redémarrer, ce qui rouvre la session et relance la tâche. Un bénévole qui touche un + bouton sur l'écran d'administration, lui, ne redémarre rien : il regarde l'écran + client dans la minute qui suit. + + Ce n'est PAS gardé par Assert-Success, et ce n'est pas un oubli : sur une machine où + la tâche n'existe pas encore — la toute première installation, avant l'étape qui la + crée — l'absence est le cas nominal. + #> + [CmdletBinding()] + param([string]$LogFile) + + if (Get-ScheduledTask -TaskName $script:TaskName -ErrorAction Ignore) { + schtasks /run /tn $script:TaskName | Out-Null + Write-Step 'écran client relancé' $LogFile + } +} + function Get-BinaryHolders { <# .SYNOPSIS diff --git a/deploy/windows/install.ps1 b/deploy/windows/install.ps1 index 00623da..9a4a7ee 100644 --- a/deploy/windows/install.ps1 +++ b/deploy/windows/install.ps1 @@ -301,6 +301,14 @@ if (-not $Pilot) { } } +# L'ÉCRAN CLIENT, RELANCÉ. L'étape 2 bis a arrêté la tâche du kiosque pour pouvoir +# remplacer le binaire qu'elle exécute, et cette tâche n'a qu'un déclencheur d'ouverture +# de session : sans cette ligne, relancer l'installeur sur un poste qui marche — le geste +# que TROUBLESHOOTING.md et « openscale doctor » recommandent — laissait l'écran client +# NOIR jusqu'à la prochaine session. Sur une machine vierge il n'y a encore aucune tâche, +# et la fonction ne fait alors rien. +Start-OpenScaleKiosk -LogFile $paths.LogFile + # --- 7. Fiche d'installation ----------------------------------------------------- $fingerprint = '(à relever sur l''écran d''administration)' if (Test-Path $paths.Config) { diff --git a/deploy/windows/update.ps1 b/deploy/windows/update.ps1 index 8defec7..878aabd 100644 --- a/deploy/windows/update.ps1 +++ b/deploy/windows/update.ps1 @@ -34,6 +34,15 @@ .PARAMETER Source Le nouveau binaire. Par défaut, openscale.exe à côté de ce script. +.PARAMETER OutcomePath + Où écrire le compte rendu machine, quand ce script est lancé PAR LE POSTE et non par + un humain. C'est la seule chose qui traverse : au moment où ce script se termine, le + processus qui aurait pu lire son code de retour est mort depuis une minute — c'est ce + script qui l'a arrêté. Sans ce paramètre, rien n'est écrit et la console fait foi. + +.PARAMETER LogPath + Où recopier le journal des étapes, en plus du journal d'installation. + .EXAMPLE .\update.ps1 #> @@ -42,7 +51,9 @@ param( [string]$Source, [string]$InstallDir, [string]$DataRoot, - [int]$HealthTimeoutSeconds = 60) + [int]$HealthTimeoutSeconds = 60, + [string]$OutcomePath, + [string]$LogPath) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest @@ -50,6 +61,60 @@ Set-StrictMode -Version Latest . (Join-Path $PSScriptRoot 'common.ps1') Set-NativeOutputEncoding +# Les deux versions, renseignées dès qu'on sait les lire et vides avant : le compte rendu +# d'un échec précoce doit pouvoir être écrit sans elles. +$script:VersionBefore = '' +$script:VersionAfter = '' + +function Write-Outcome { + <# + .SYNOPSIS + Écrit le compte rendu que le poste relira à son prochain démarrage. + .DESCRIPTION + ÉCRIT SUR LES QUATRE SORTIES, et c'est toute sa raison d'être. Au moment où ce script + se termine, le processus qui aurait pu lire son code de retour est mort depuis une + minute — c'est ce script qui l'a arrêté. Le fichier est la seule chose qui traverse, + et il est relu par le binaire qui démarre ensuite : le neuf, ou l'ancien restauré. + + Les quatre statuts ne demandent pas la même chose à un bénévole : + + succeeded la nouvelle version tourne ; + rolled-back la bascule a échoué, l'ancienne est revenue, le poste marche + — personne à appeler ; + rolled-back-unhealthy la bascule a échoué ET le poste ne répond pas — appeler ; + not-started rien n'a été remplacé, on peut recliquer. + + Sans -OutcomePath, ce script est lancé à la main par un humain qui lit la console : + il n'y a rien à écrire. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Status, + [Parameter(Mandatory)][int]$ExitCode, + [string]$Reason = '', + [string]$BackupPath = '', + [string[]]$DatabaseBackups = @()) + + if (-not $OutcomePath) { return } + $report = [ordered]@{ + status = $Status + exit_code = $ExitCode + from = $script:VersionBefore + to = $script:VersionAfter + reason = $Reason + backup = $BackupPath + database_backups = @($DatabaseBackups) + finished_at = (Get-Date).ToString('o') + } + $directory = Split-Path -Parent $OutcomePath + if ($directory -and -not (Test-Path $directory)) { + New-Item -ItemType Directory -Path $directory -Force | Out-Null + } + # ConvertTo-Json rend « [] » pour un tableau vide et « null » pour $null : le tableau + # forcé plus haut est ce qui évite au poste de lire null là où il attend une liste. + $report | ConvertTo-Json -Depth 3 | Set-Content -Path $OutcomePath -Encoding UTF8 +} + $paths = if ($InstallDir -and $DataRoot) { Get-OpenScalePaths -InstallDir $InstallDir -DataRoot $DataRoot } elseif ($InstallDir) { Get-OpenScalePaths -InstallDir $InstallDir } elseif ($DataRoot) { Get-OpenScalePaths -DataRoot $DataRoot } @@ -59,16 +124,30 @@ if (-not (Test-Administrator)) { throw 'update.ps1 doit être lancé en ADMINISTRATEUR : il arrête un service et écrit dans Program Files.' } if (-not $Source) { $Source = Join-Path $PSScriptRoot 'openscale.exe' } + +# ★ CES DEUX REFUS SORTENT PAR 12 ET NON PAR UNE EXCEPTION. « not-started » veut dire +# que RIEN n'a bougé : le binaire installé est intact, le service tourne encore, et +# recliquer est sans danger. C'est l'information qui manquerait le plus à un bénévole +# devant un écran qui ne dirait que « la mise à jour a échoué ». if (-not (Test-Path $Source)) { - throw "le nouveau binaire est introuvable ($Source). Décompressez l'archive de la nouvelle version." + $reason = "le nouveau binaire est introuvable ($Source)" + Write-Outcome -Status 'not-started' -ExitCode 12 -Reason $reason + Write-Host "$reason. Décompressez l'archive de la nouvelle version." + exit 12 } if (-not (Test-Path $paths.Binary)) { - throw "aucun poste installé dans $($paths.InstallDir) : c'est install.ps1 qu'il faut lancer, pas update.ps1." + $reason = "aucun poste installé dans $($paths.InstallDir)" + Write-Outcome -Status 'not-started' -ExitCode 12 -Reason $reason + Write-Host "$reason : c'est install.ps1 qu'il faut lancer, pas update.ps1." + exit 12 } -$before = (& $paths.Binary --version) -join ' ' -$after = (& $Source --version) -join ' ' +$script:VersionBefore = (& $paths.Binary --version) -join ' ' +$script:VersionAfter = (& $Source --version) -join ' ' +$before = $script:VersionBefore +$after = $script:VersionAfter Write-Step "mise à jour : $before → $after" $paths.LogFile +if ($LogPath) { Write-Step "journal de cette bascule : $LogPath" $LogPath } $address = Get-ListenAddress -ConfigPath $paths.Config # --- 1. Arrêt, avec contrôle d'erreur ----------------------------------------------- @@ -83,8 +162,14 @@ $address = Get-ListenAddress -ConfigPath $paths.Config # Ce qui est contrôlé ici est le RÉSULTAT — le fichier est-il remplaçable — et non le code # de retour de l'arrêt, qui n'en est qu'un indice. if (-not (Stop-OpenScaleBinaryHolders -Paths $paths -LogFile $paths.LogFile)) { - throw "$($paths.Binary) est encore tenu par un processus$(Get-BinaryHolders) : la mise à " + - 'jour ne peut pas remplacer le binaire.' + $reason = "$($paths.Binary) est encore tenu par un processus$(Get-BinaryHolders)" + Write-Outcome -Status 'not-started' -ExitCode 12 -Reason $reason + # Le kiosque vient d'être arrêté par la fonction ci-dessus, et le binaire n'a PAS été + # remplacé : sans cette relance, un refus qui ne touche à rien laisserait quand même + # l'écran client noir. + Start-OpenScaleKiosk -LogFile $paths.LogFile + Write-Host "$reason : la mise à jour ne peut pas remplacer le binaire." + exit 12 } Write-Step 'poste arrêté, le binaire est remplaçable' $paths.LogFile @@ -159,10 +244,30 @@ if ($failure) { Write-Host ' Les pesées enregistrées depuis la mise à jour seront perdues :' Write-Host ' exportez le journal avant, depuis l''écran d''administration.' } - exit 1 + + # DEUX ÉCHECS ET NON UN, écrits en toutes lettres et non calculés. « restauré, le poste + # répond » n'appelle personne ; « restauré, le poste ne répond toujours pas » demande un + # humain tout de suite. Les confondre sous un même « exit 1 » faisait porter à l'écran + # la moins utile des deux phrases. + # + # L'écran client est relancé DANS LES DEUX CAS : un retour arrière réussi qui le + # laisserait noir serait une panne créée par la réparation. + if ($restored) { + Write-Outcome -Status 'rolled-back' -ExitCode 10 -Reason $failure ` + -BackupPath $backup -DatabaseBackups $new + Start-OpenScaleKiosk -LogFile $paths.LogFile + exit 10 + } + Write-Outcome -Status 'rolled-back-unhealthy' -ExitCode 11 -Reason $failure ` + -BackupPath $backup -DatabaseBackups $new + Start-OpenScaleKiosk -LogFile $paths.LogFile + exit 11 } Write-Step "mise à jour réussie : le poste répond sur $address" $paths.LogFile +Write-Outcome -Status 'succeeded' -ExitCode 0 -BackupPath $backup +Start-OpenScaleKiosk -LogFile $paths.LogFile + Write-Host '' Write-Host "Mise à jour terminée : $after" Write-Host "Version précédente conservée dans $backup" From 8e1c8bfcfcd70f6cdd075f9a27bd9fe4466b9df2 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 12:05:16 +0200 Subject: [PATCH 12/19] feat(update): le poste ecrit ce qu'il tente avant de se laisser tuer L'ordre EST la conception : pending.json est ecrit AVANT que le script demarre, parce que le script arrete le service -- ce processus meme -- et que rien d'ecrit apres ne le serait jamais. Un test l'impose depuis l'interieur du lancement. Trois refus qui ne se confondent pas. ErrVersionMoved fait confirmer au benevole CE QU'IL A LU : entre le dessin de la page et l'appui, une autre version a pu paraitre, et l'installer en silence serait installer ce que personne n'a vu. ErrAlreadyRunning refuse la seconde bascule. Et ErrBusy remonte la phrase du garde-fou telle quelle, via un type et non un message formate : le Hub sait s'il s'agit d'une pesee ou d'un catalogue en attente, ce paquet ne le sait pas, et recuperer la phrase en coupant un prefixe casserait a la premiere reformulation. Deux facons de murer un poste, fermees toutes les deux. Une bascule perimee est effacee au lieu d'opposer ErrAlreadyRunning pour toujours -- c'est le mode de defaillance mesure au banc, ou un Start() rend nil sans rien lancer. Et un lancement qui ECHOUE efface son pending.json : ce processus est encore vivant pour le faire, et le laisser murerait le poste pour un quart d'heure a cause d'une erreur qu'on tient dans la main. Status lit le disque et ne sonde jamais : la page doit se dessiner tout de suite, le sondage a son propre worker. Une bascule perimee y devient un compte rendu synthetique plutot qu'un silence -- un benevole qui a touche le bouton et n'a rien vu se passer merite une phrase, et "elle n'a jamais demarre" lui dit qu'il peut reessayer. --- internal/update/service.go | 249 ++++++++++++++++++++ internal/update/service_test.go | 398 ++++++++++++++++++++++++++++++++ 2 files changed, 647 insertions(+) create mode 100644 internal/update/service.go create mode 100644 internal/update/service_test.go diff --git a/internal/update/service.go b/internal/update/service.go new file mode 100644 index 0000000..6113106 --- /dev/null +++ b/internal/update/service.go @@ -0,0 +1,249 @@ +package update + +import ( + "context" + "errors" + "fmt" + "path/filepath" + + "openscale/internal/platform" + "openscale/internal/station/ports" +) + +// ErrVersionMoved reports that the version the screen offered is not the one that +// would be installed. +// +// It exists so that a volunteer confirms WHAT THEY READ: between the moment the +// page was drawn and the moment the button was touched, a newer release may have +// appeared, and installing that one silently would be installing something nobody +// saw. It also covers the two cases where there is nothing to install at all -- +// the repository has moved back, or offers only a prerelease. +var ErrVersionMoved = errors.New("update: the offered version is not the one that would be installed") + +// ErrBusy reports a station that must not be taken down right now. +var ErrBusy = errors.New("update: the station is busy") + +// ErrAlreadyRunning reports a swap already in flight. +var ErrAlreadyRunning = errors.New("update: a swap is already in flight") + +// BusyError carries the guard's OWN French sentence up to the screen. +// +// A type and not a formatted string, because the layer above has to render that +// sentence verbatim: the guard knows whether it is a weighing or a catalogue +// waiting to enter service, and this package does not. Recovering it by cutting a +// prefix off an error message would break the first time either side is reworded. +type BusyError struct{ Reason string } + +// Error renders the refusal for a log. +func (e *BusyError) Error() string { return "update: the station is busy: " + e.Reason } + +// Is makes errors.Is(err, ErrBusy) answer for every BusyError. +func (e *BusyError) Is(target error) bool { return target == ErrBusy } + +// Guard is what the service asks before taking the station down. Declared here, +// on the consumer's side; *station.Hub satisfies it. +type Guard interface { + // UpdateGuard reports whether the station may be taken down, and says in + // French why not when it may not. + UpdateGuard() (bool, string) +} + +// Paths are the three absolute directories the swap needs. +// +// They come from the running process -- os.Executable() and the configured data +// root -- and never from the script's own defaults, which point at Program Files: +// a station installed anywhere else would be updated somewhere it does not live. +type Paths struct { + InstallDir string + DataRoot string + UpdatesDir string +} + +// Status is everything GET /admin/api/update answers. +type Status struct { + Running string + Repository string + // Supported is false off Windows. The routes still exist there and say so, + // rather than hiding: a button that did nothing would be worse. + Supported bool + Check Check + HasCheck bool + Available bool + Outcome Outcome + HasOutcome bool +} + +// Service decides, prepares and hands over. +// +// It owns no goroutine: the daily poll lives in internal/station, on the injected +// clock, because that is where a bounded, cancellable worker belongs. +type Service struct { + Clock ports.Clock + State State + Stager Stager + Guard Guard + Running Version + Supported bool + Paths Paths + + // Applier is what actually starts the swap: platform.ApplyUpdate in + // production, a func that records in a test. + Applier func(platform.UpdateSpec) error + // StageFunc overrides the Stager, for the tests that must not download. + StageFunc func(context.Context, Release) (Staged, error) + // NewSource builds the source for one repository. A field, so that a test + // answers without a network and a fork served otherwise could be added. + NewSource func(repository string) Source +} + +// Check polls the repository and records what it found. +func (s *Service) Check(ctx context.Context, repository string) (Check, error) { + release, err := s.source(repository).Latest(ctx) + if err != nil { + return Check{}, err + } + check := Check{ + CheckedAt: s.Clock.Now(), + Tag: release.Tag, + Version: release.Version.String(), + PublishedAt: release.PublishedAt, + HTMLURL: release.HTMLURL, + } + if err := s.State.WriteCheck(check); err != nil { + return Check{}, err + } + return check, nil +} + +// Status answers the screen FROM WHAT IS ON DISK, without polling: the page has +// to draw instantly, and the poll has its own worker. +func (s *Service) Status(repository string) (Status, error) { + status := Status{ + Running: s.Running.String(), Repository: repository, Supported: s.Supported, + } + check, found, err := s.State.ReadCheck() + if err != nil { + return Status{}, err + } + status.Check, status.HasCheck = check, found + if found { + if offered, err := ParseVersion(check.Tag); err == nil { + status.Available = s.worthInstalling(offered) + } + } + outcome, found, err := s.lastOutcome() + if err != nil { + return Status{}, err + } + status.Outcome, status.HasOutcome = outcome, found + return status, nil +} + +// lastOutcome returns the last report, real or synthesised. +// +// A pending swap that outlived its budget IS a report, and saying so matters: a +// volunteer who touched the button and saw nothing happen deserves a sentence, +// and « it never started » is the one that tells them they may try again. +func (s *Service) lastOutcome() (Outcome, bool, error) { + pending, found, err := s.State.ReadPending() + if err != nil { + return Outcome{}, false, err + } + if found && pending.Stale(s.Clock.Now()) { + return Outcome{ + Status: StatusNotStarted, From: pending.From, To: pending.To, + Reason: "la mise à jour n'a jamais démarré : rien n'a été remplacé", + FinishedAt: s.Clock.Now(), + }, true, nil + } + return s.State.LastOutcome() +} + +// Apply brings the wanted version down and hands the swap over. +// +// THE ORDER IS THE WHOLE DESIGN: pending.json is written BEFORE the script starts, +// because the script stops the service -- this very process -- and nothing written +// afterwards would ever be written. +func (s *Service) Apply(ctx context.Context, repository, wanted string) error { + if err := s.refuseIfSwapInFlight(); err != nil { + return err + } + if allowed, reason := s.Guard.UpdateGuard(); !allowed { + return &BusyError{Reason: reason} + } + release, err := s.source(repository).Latest(ctx) + if err != nil { + return err + } + if !s.worthInstalling(release.Version) || release.Version.String() != wanted { + return fmt.Errorf("%w: %s publiée, %s demandée", ErrVersionMoved, release.Version, wanted) + } + + staged, err := s.stage(ctx, release) + if err != nil { + return err + } + if err := s.State.WritePending(Pending{ + Tag: staged.Tag, To: staged.Version.String(), From: s.Running.String(), + StartedAt: s.Clock.Now(), StagingRoot: staged.Root, + }); err != nil { + return err + } + if err := s.Applier(platform.UpdateSpec{ + Script: staged.Script, + Source: staged.Binary, + InstallDir: s.Paths.InstallDir, + DataRoot: s.Paths.DataRoot, + OutcomePath: filepath.Join(s.Paths.UpdatesDir, "outcome.json"), + LogPath: filepath.Join(s.Paths.UpdatesDir, "update-"+staged.Tag+".log"), + }); err != nil { + // The handover itself refused, so the swap never happened and this process + // is still alive to say so. Leaving pending.json behind would wall the + // station until SwapBudget ran out, over a failure we are holding in hand. + _ = s.State.ClearPending() + return err + } + return nil +} + +// refuseIfSwapInFlight refuses a second swap, and unwalls a station stuck behind +// one that will never report. +func (s *Service) refuseIfSwapInFlight() error { + pending, found, err := s.State.ReadPending() + if err != nil { + return err + } + if !found { + return nil + } + if !pending.Stale(s.Clock.Now()) { + return ErrAlreadyRunning + } + // Measured on the bench, 29/07/2026: a script that never starts leaves Start() + // returning nil, so pending.json is written and no outcome.json ever arrives. + // Refusing here forever would wall the station shut on the strength of a swap + // that did nothing and said nothing. + return s.State.ClearPending() +} + +// worthInstalling reports a version a station should be offered: newer than what +// runs, and never a prerelease. +func (s *Service) worthInstalling(offered Version) bool { + return !offered.IsPrerelease() && offered.Compare(s.Running) > 0 +} + +// stage uses the override when a test set one. +func (s *Service) stage(ctx context.Context, release Release) (Staged, error) { + if s.StageFunc != nil { + return s.StageFunc(ctx, release) + } + return s.Stager.Stage(ctx, release) +} + +// source builds the source for one repository. +func (s *Service) source(repository string) Source { + if s.NewSource != nil { + return s.NewSource(repository) + } + return GitHubSource{Repository: repository} +} diff --git a/internal/update/service_test.go b/internal/update/service_test.go new file mode 100644 index 0000000..f66b46c --- /dev/null +++ b/internal/update/service_test.go @@ -0,0 +1,398 @@ +package update + +import ( + "context" + "errors" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "openscale/internal/fake" + "openscale/internal/platform" +) + +// stubSource answers what a test decides, without a network. +type stubSource struct { + release Release + err error +} + +func (s *stubSource) Latest(context.Context) (Release, error) { return s.release, s.err } + +// stubGuard answers what a test decides, without a station. +type stubGuard struct { + allow bool + reason string +} + +func (g stubGuard) UpdateGuard() (bool, string) { return g.allow, g.reason } + +// serviceBench wires a Service over fakes and hands back what a test steers. +type serviceBench struct { + service *Service + source *stubSource + clock *fake.Clock + dir string + started []platform.UpdateSpec +} + +// newServiceBench builds a station running 2.0.3 whose repository offers 2.1.0. +// +// Stage is stubbed: task 3 already proves it against a real archive, and this +// bench must not download anything. +func newServiceBench(t *testing.T, guard Guard) *serviceBench { + t.Helper() + running, err := ParseVersion("2.0.3") + if err != nil { + t.Fatalf("ParseVersion : %v", err) + } + offered, err := ParseVersion("2.1.0") + if err != nil { + t.Fatalf("ParseVersion : %v", err) + } + dir := t.TempDir() + bench := &serviceBench{ + clock: fake.NewClock(benchEpoch), + dir: dir, + source: &stubSource{release: Release{ + Tag: "2.1.0", Version: offered, + PublishedAt: benchEpoch.Add(-24 * time.Hour), + HTMLURL: "https://github.com/lostmind84/OpenScale/releases/tag/2.1.0", + }}, + } + bench.service = &Service{ + Clock: bench.clock, + State: State{Dir: dir}, + Guard: guard, + Running: running, + Supported: true, + Paths: Paths{InstallDir: dir, DataRoot: dir, UpdatesDir: dir}, + NewSource: func(string) Source { return bench.source }, + StageFunc: func(_ context.Context, r Release) (Staged, error) { + root := filepath.Join(dir, r.Tag) + if err := os.MkdirAll(root, 0o755); err != nil { + return Staged{}, err + } + return Staged{ + Tag: r.Tag, Version: r.Version, Root: root, + Binary: filepath.Join(root, "openscale.exe"), + Script: filepath.Join(root, "update.ps1"), + }, nil + }, + Applier: func(spec platform.UpdateSpec) error { + bench.started = append(bench.started, spec) + return nil + }, + } + return bench +} + +// TestApplyRefusesAVersionThatIsNoLongerTheOne is the property the screen depends +// on: the volunteer confirms what they READ, never what arrived since. +func TestApplyRefusesAVersionThatIsNoLongerTheOne(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + + if err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.0.9"); !errors.Is(err, ErrVersionMoved) { + t.Fatalf("erreur = %v, attendu ErrVersionMoved", err) + } + if len(b.started) != 0 { + t.Fatal("une bascule a été lancée sur une version que l'écran ne montrait pas") + } + if _, found, _ := b.service.State.ReadPending(); found { + t.Error("un refus laisse un pending.json derrière lui") + } +} + +// TestApplyRefusesWhileTheStationIsBusy, and carries the guard's OWN sentence +// rather than one this layer invented: the guard knows why, this layer does not. +func TestApplyRefusesWhileTheStationIsBusy(t *testing.T) { + const busy = "Une pesée est en cours. Réessayez dans un instant." + b := newServiceBench(t, stubGuard{allow: false, reason: busy}) + + err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.1.0") + if !errors.Is(err, ErrBusy) { + t.Fatalf("erreur = %v, attendu ErrBusy", err) + } + var busyErr *BusyError + if !errors.As(err, &busyErr) { + t.Fatalf("le refus ne porte pas de BusyError : %v", err) + } + if busyErr.Reason != busy { + t.Errorf("raison = %q, attendu %q", busyErr.Reason, busy) + } + if len(b.started) != 0 { + t.Fatal("une bascule a été lancée sur un poste occupé") + } +} + +// TestApplyRefusesASecondSwap: one at a time, and the screen says so. +func TestApplyRefusesASecondSwap(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + if err := b.service.State.WritePending(Pending{ + Tag: "2.1.0", StartedAt: b.clock.Now(), + }); err != nil { + t.Fatalf("WritePending : %v", err) + } + + err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.1.0") + if !errors.Is(err, ErrAlreadyRunning) { + t.Fatalf("erreur = %v, attendu ErrAlreadyRunning", err) + } +} + +// TestAPendingSwapThatWentStaleDoesNotWallTheStation is the failure mode the +// bench of 29/07/2026 uncovered. +// +// A script that never starts leaves Start() returning nil, so pending.json is +// written and no outcome.json will ever arrive. Without this, ErrAlreadyRunning +// would refuse every later update FOREVER, on the strength of a swap that did +// nothing and said nothing. +func TestAPendingSwapThatWentStaleDoesNotWallTheStation(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + abandoned := filepath.Join(b.dir, "2.0.9") + if err := os.MkdirAll(abandoned, 0o755); err != nil { + t.Fatalf("staging : %v", err) + } + if err := b.service.State.WritePending(Pending{ + Tag: "2.0.9", StartedAt: b.clock.Now().Add(-SwapBudget - time.Minute), + StagingRoot: abandoned, + }); err != nil { + t.Fatalf("WritePending : %v", err) + } + + if err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.1.0"); err != nil { + t.Fatalf("un poste muré par une bascule périmée refuse encore : %v", err) + } + if len(b.started) != 1 { + t.Fatalf("%d bascule(s) lancée(s), attendu 1", len(b.started)) + } + if _, err := os.Stat(abandoned); !os.IsNotExist(err) { + t.Error("le staging de la bascule abandonnée reste sur le disque") + } +} + +// TestApplyRecordsThePendingSwapBeforeHandingOver is the ordering that makes the +// story survive the station's own death: pending.json must exist BEFORE the +// script that kills this process is started, or nothing will ever explain what +// the station was trying to do. +func TestApplyRecordsThePendingSwapBeforeHandingOver(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + var sawPending bool + b.service.Applier = func(platform.UpdateSpec) error { + _, found, err := b.service.State.ReadPending() + if err != nil { + t.Errorf("ReadPending : %v", err) + } + sawPending = found + return nil + } + + if err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.1.0"); err != nil { + t.Fatalf("Apply : %v", err) + } + if !sawPending { + t.Fatal("le script est lancé avant que pending.json existe") + } +} + +// TestApplyHandsTheStagedScriptAndNotTheStationsOwn. +// +// No station carries an update.ps1: install.ps1 copies the binary and two +// documents, nothing else. The only one within reach is the one that just came +// down with the release, and every path handed over is absolute. +func TestApplyHandsTheStagedScriptAndNotTheStationsOwn(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + if err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.1.0"); err != nil { + t.Fatalf("Apply : %v", err) + } + spec := b.started[0] + if filepath.Base(spec.Script) != "update.ps1" { + t.Errorf("script = %q", spec.Script) + } + if !strings.Contains(spec.Script, "2.1.0") { + t.Errorf("le script lancé ne vient pas du staging de la version : %q", spec.Script) + } + for name, path := range map[string]string{ + "Script": spec.Script, "Source": spec.Source, + "InstallDir": spec.InstallDir, "DataRoot": spec.DataRoot, + "OutcomePath": spec.OutcomePath, "LogPath": spec.LogPath, + } { + if path == "" { + t.Errorf("%s est vide : le script retomberait sur son défaut, qui pointe Program Files", name) + } + if !filepath.IsAbs(path) { + t.Errorf("%s = %q n'est pas absolu", name, path) + } + } + if filepath.Base(spec.OutcomePath) != "outcome.json" { + t.Errorf("OutcomePath = %q", spec.OutcomePath) + } +} + +// TestAFailedHandoverDoesNotLeaveTheStationWalled. +// +// If Start() itself refuses -- powershell.exe missing, for instance -- the swap +// never happened, and the pending.json written a line earlier must go with it. +func TestAFailedHandoverDoesNotLeaveTheStationWalled(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + b.service.Applier = func(platform.UpdateSpec) error { + return errors.New("powershell.exe est introuvable") + } + + if err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.1.0"); err == nil { + t.Fatal("un lancement refusé passe pour une réussite") + } + if _, found, _ := b.service.State.ReadPending(); found { + t.Error("un lancement refusé laisse un pending.json qui murera le poste") + } +} + +// TestStatusReadsFromDiskAndNeverPolls: the page must draw instantly, and the +// poll has its own worker on the injected clock. +func TestStatusReadsFromDiskAndNeverPolls(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + b.source.err = errors.New("le réseau ne doit pas être touché ici") + + status, err := b.service.Status("lostmind84/OpenScale") + if err != nil { + t.Fatalf("Status : %v", err) + } + if status.Running != "2.0.3" { + t.Errorf("version installée = %q", status.Running) + } + if status.HasCheck { + t.Error("un poste qui n'a jamais sondé annonce un sondage") + } + if status.Available { + t.Error("un poste qui n'a jamais sondé propose une version") + } + if !status.Supported { + t.Error("la plateforme est déclarée non gérée") + } +} + +// TestCheckIsWrittenSoARestartDoesNotPollAgain. +func TestCheckIsWrittenSoARestartDoesNotPollAgain(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + + if _, err := b.service.Check(context.Background(), "lostmind84/OpenScale"); err != nil { + t.Fatalf("Check : %v", err) + } + check, found, err := b.service.State.ReadCheck() + if err != nil || !found { + t.Fatalf("ReadCheck : trouvé %v, %v", found, err) + } + if check.Tag != "2.1.0" || !check.CheckedAt.Equal(benchEpoch) { + t.Errorf("sondage enregistré = %+v", check) + } + + status, err := b.service.Status("lostmind84/OpenScale") + if err != nil { + t.Fatalf("Status : %v", err) + } + if !status.Available { + t.Error("2.1.0 est publiée et 2.0.3 tourne : rien n'est proposé") + } +} + +// TestAPrereleaseIsNeverOffered: /releases/latest already excludes them, and this +// is the belt to those braces -- a fork could publish otherwise. +func TestAPrereleaseIsNeverOffered(t *testing.T) { + candidate, err := ParseVersion("2.2.0-rc1") + if err != nil { + t.Fatalf("ParseVersion : %v", err) + } + b := newServiceBench(t, stubGuard{allow: true}) + b.source.release = Release{Tag: "2.2.0-rc1", Version: candidate} + + if _, err := b.service.Check(context.Background(), "lostmind84/OpenScale"); err != nil { + t.Fatalf("Check : %v", err) + } + status, err := b.service.Status("lostmind84/OpenScale") + if err != nil { + t.Fatalf("Status : %v", err) + } + if status.Available { + t.Fatal("une préversion est proposée au poste") + } + if err := b.service.Apply(context.Background(), "lostmind84/OpenScale", "2.2.0-rc1"); !errors.Is(err, ErrVersionMoved) { + t.Fatalf("installer une préversion rend %v, attendu ErrVersionMoved", err) + } +} + +// TestAStationAlreadyOnTheLatestIsOfferedNothing. +func TestAStationAlreadyOnTheLatestIsOfferedNothing(t *testing.T) { + same, err := ParseVersion("2.0.3") + if err != nil { + t.Fatalf("ParseVersion : %v", err) + } + b := newServiceBench(t, stubGuard{allow: true}) + b.source.release = Release{Tag: "2.0.3", Version: same} + + if _, err := b.service.Check(context.Background(), "lostmind84/OpenScale"); err != nil { + t.Fatalf("Check : %v", err) + } + status, err := b.service.Status("lostmind84/OpenScale") + if err != nil { + t.Fatalf("Status : %v", err) + } + if status.Available { + t.Fatal("un poste déjà à jour se voit proposer sa propre version") + } +} + +// TestAStalePendingIsReportedAsAFailureRatherThanAsSilence. +// +// The station must not simply forget: a volunteer who touched the button and saw +// nothing happen deserves a sentence, and « la mise à jour n'a jamais démarré » +// is one that tells them they may try again. +func TestAStalePendingIsReportedAsAFailureRatherThanAsSilence(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + if err := b.service.State.WritePending(Pending{ + Tag: "2.1.0", To: "2.1.0", From: "2.0.3", + StartedAt: b.clock.Now().Add(-SwapBudget - time.Minute), + }); err != nil { + t.Fatalf("WritePending : %v", err) + } + + status, err := b.service.Status("lostmind84/OpenScale") + if err != nil { + t.Fatalf("Status : %v", err) + } + if !status.HasOutcome { + t.Fatal("une bascule périmée ne donne aucun compte rendu à l'écran") + } + if status.Outcome.Status != StatusNotStarted { + t.Errorf("statut = %q, attendu %q", status.Outcome.Status, StatusNotStarted) + } + if status.Outcome.Reason == "" { + t.Error("le compte rendu synthétique ne dit pas ce qui s'est passé") + } +} + +// TestTakeOutcomeAtStartupIsWhatTheStationTellsAfterwards. +func TestTakeOutcomeAtStartupIsWhatTheStationTellsAfterwards(t *testing.T) { + b := newServiceBench(t, stubGuard{allow: true}) + writeOutcomeFile(t, b.dir, Outcome{ + Status: StatusRolledBack, ExitCode: 10, From: "2.0.3", To: "2.1.0", + Reason: "le poste ne répond pas sur 127.0.0.1:8085/healthz après 60 s", + FinishedAt: benchEpoch.Add(-time.Hour), + }) + + outcome, found, err := b.service.State.TakeOutcome() + if err != nil || !found { + t.Fatalf("TakeOutcome : trouvé %v, %v", found, err) + } + if outcome.Status != StatusRolledBack { + t.Errorf("statut = %q", outcome.Status) + } + status, err := b.service.Status("lostmind84/OpenScale") + if err != nil { + t.Fatalf("Status : %v", err) + } + if !status.HasOutcome || status.Outcome.ExitCode != 10 { + t.Errorf("compte rendu servi à l'écran = %+v", status.Outcome) + } +} From 0ba640a70486552b4bfd91da0b2a1e7db60ffb06 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 12:25:25 +0200 Subject: [PATCH 13/19] feat(web): trois routes pour lire, verifier et installer une version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La lecture est libre et les deux actes demandent le mot de passe : les pages de reglage s'ouvrent en lecture et protegent l'ecriture, celle-ci ne fait pas exception. Deux codes la ou la conception n'en prevoyait qu'un. ERR-UPD-03 dit "attendez un instant", ERR-UPD-09 dit "rechargez la page" : ce ne sont pas la meme instruction, et les confondre aurait fait lire au benevole la moins utile des deux. Le refus pour occupation remonte la phrase du garde-fou TELLE QUELLE -- le Hub sait s'il s'agit d'une pesee ou d'un catalogue en attente, cette couche ne le sait pas, et paraphraser perdrait la seule information sur laquelle on peut agir. Un depot sans version publiee repond 200 et une phrase, pas une erreur de passerelle : envoyer quelqu'un chercher une panne de reseau qu'il n'a pas coute plus cher que de le dire. Le service n'est cable que si les deux conditions tiennent : une version INSTALLEE qui soit un numero -- un binaire "dev" comparé a une release donnerait une arithmetique indefendable -- et une plateforme qui sait basculer. Sinon les routes existent et disent honnetement qu'elles ne peuvent pas, plutot que de laisser un benevole confirmer un acte pour rien. Le drapeau golden de dto_test.go est renomme rewriteGolden : ce paquet importe desormais internal/update, et une variable de paquet nommee "update" masquait le paquet dans tout le binaire de test. L'option de ligne de commande, elle, ne change pas -- c'est ce que les gens tapent. --- cmd/openscale/serve.go | 1 + cmd/openscale/update.go | 58 ++++++++ internal/web/dto_test.go | 14 +- internal/web/harness_test.go | 2 + internal/web/server.go | 25 +++- internal/web/update.go | 191 +++++++++++++++++++++++++ internal/web/update_test.go | 267 +++++++++++++++++++++++++++++++++++ 7 files changed, 552 insertions(+), 6 deletions(-) create mode 100644 cmd/openscale/update.go create mode 100644 internal/web/update.go create mode 100644 internal/web/update_test.go diff --git a/cmd/openscale/serve.go b/cmd/openscale/serve.go index 8728ede..2b237ab 100644 --- a/cmd/openscale/serve.go +++ b/cmd/openscale/serve.go @@ -450,6 +450,7 @@ func serve(ctx context.Context, o serveOptions, out io.Writer) error { printer: live, catalog: liveSource, machine: diag.NewMachine(clock), dataDir: o.dataDir, }, + Update: newUpdateService(clock, st.Hub(), o.dataDir), }) if err != nil { _ = binder.Close() diff --git a/cmd/openscale/update.go b/cmd/openscale/update.go new file mode 100644 index 0000000..e53ba37 --- /dev/null +++ b/cmd/openscale/update.go @@ -0,0 +1,58 @@ +package main + +import ( + "os" + "path/filepath" + "runtime" + + "openscale/internal/platform" + "openscale/internal/station" + "openscale/internal/station/ports" + "openscale/internal/update" +) + +// newUpdateService wires the update service over the running station. +// +// It returns nil -- and the routes then answer honestly that this binary cannot +// update itself -- in the two cases where offering the button would be a lie: +// +// - a development build, whose version is « dev » and not a number. Comparing +// that to a release is meaningless, and a station running it would be told it +// is out of date by an arithmetic nobody can defend; +// - a platform with no swap. platform.ApplyUpdate answers ErrUpdateUnsupported +// off Windows, and a screen that discovered that only at the last click would +// have let a volunteer confirm an irreversible-looking act for nothing. +func newUpdateService(clock ports.Clock, hub *station.Hub, dataDir string) *update.Service { + running, err := update.ParseVersion(version) + if err != nil { + return nil + } + if runtime.GOOS != "windows" { + return nil + } + // THE PATHS COME FROM THE RUNNING PROCESS, never from the script's own + // defaults -- which point at Program Files. A station installed anywhere else + // would otherwise be updated somewhere it does not live. + binary, err := os.Executable() + if err != nil { + return nil + } + updatesDir := filepath.Join(dataDir, "updates") + return &update.Service{ + Clock: clock, + State: update.State{Dir: updatesDir}, + Stager: update.Stager{Dir: updatesDir, Platform: releasePlatform()}, + Guard: hub, + Running: running, + Supported: true, + Paths: update.Paths{ + InstallDir: filepath.Dir(binary), + DataRoot: dataDir, + UpdatesDir: updatesDir, + }, + Applier: platform.ApplyUpdate, + } +} + +// releasePlatform is the suffix release.yml gives the archives. +func releasePlatform() string { return runtime.GOOS + "-" + runtime.GOARCH } diff --git a/internal/web/dto_test.go b/internal/web/dto_test.go index 82d2523..de17636 100644 --- a/internal/web/dto_test.go +++ b/internal/web/dto_test.go @@ -18,10 +18,14 @@ import ( "openscale/internal/station/ports" ) -// update rewrites the golden files instead of comparing against them. It is the one -// way this file may be made to pass without thinking, so it is a flag somebody has to -// type. -var update = flag.Bool("update", false, "réécrit les fichiers golden au lieu de les comparer") +// rewriteGolden rewrites the golden files instead of comparing against them. It is +// the one way this file may be made to pass without thinking, so it is a flag +// somebody has to type. +// +// The Go identifier is NOT « update »: this package now imports internal/update, +// and a package-level variable of that name would shadow it across the whole test +// binary. The command-line flag keeps its name -- it is what people type. +var rewriteGolden = flag.Bool("update", false, "réécrit les fichiers golden au lieu de les comparer") // TestStateGoldenFreezesTheWireContract is the test §14.5 asks for: the DTO is // DECOUPLED from the core, and the price of that decoupling is one conversion @@ -41,7 +45,7 @@ func TestStateGoldenFreezesTheWireContract(t *testing.T) { got = append(got, '\n') golden := filepath.Join("testdata", "state.json") - if *update { + if *rewriteGolden { if err := os.WriteFile(golden, got, 0o644); err != nil { t.Fatalf("écriture du golden : %v", err) } diff --git a/internal/web/harness_test.go b/internal/web/harness_test.go index d5e2f60..f6fc4f2 100644 --- a/internal/web/harness_test.go +++ b/internal/web/harness_test.go @@ -79,6 +79,7 @@ type benchOptions struct { printer SelfTester troubleshooting Troubleshooting dashboard Dashboard + update Updater // paths is the filesystem probe controls 44 and 46 run through. Nil is the default // and is what a bench standing on no disk honestly is: « we cannot know ». paths domain.PathChecker @@ -134,6 +135,7 @@ func newBench(t *testing.T, tweak ...func(*benchOptions)) *bench { // its route carries no password, so one nil must not disable both. Diagnostic: o.diagnostician, Dashboard: o.dashboard, + Update: o.update, Binder: o.binder, Registries: domain.Registries{Paths: o.paths}, Version: "test", diff --git a/internal/web/server.go b/internal/web/server.go index 8f36486..2b48ff1 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -14,6 +14,7 @@ import ( "openscale/internal/domain" "openscale/internal/station" "openscale/internal/station/ports" + "openscale/internal/update" ) // maxSubscribers is how many SSE streams one station serves at once (§13.1). @@ -260,6 +261,22 @@ type Troubleshooting interface { UseFallbackPrinter(ctx context.Context, on bool) error } +// Updater is what the HTTP layer needs to move the station to a newer release. +// +// Declared here, on the consumer's side; *update.Service satisfies it. Nil answers +// 501 on the act and « not supported » on the read, which is what a Linux station +// honestly is: hiding the routes would leave a screen guessing, and a button doing +// nothing would be worse than none. +type Updater interface { + // Status answers the screen from what is on disk, without polling. + Status(repository string) (update.Status, error) + // Check polls the repository now and records what it found. + Check(ctx context.Context, repository string) (update.Check, error) + // Apply brings the wanted version down and hands the swap over. It returns + // as soon as the swap has STARTED: what finishes it also stops this process. + Apply(ctx context.Context, repository, wanted string) error +} + // Options is everything the HTTP layer is given. Clock and Hub are required; every // other collaborator is optional and its absence is answered honestly. type Options struct { @@ -285,6 +302,8 @@ type Options struct { // restart indicator of §14.4. Nil leaves those four absent from GET /admin/api/health // and never fails it. Dashboard Dashboard + // Update installs a newer release from the screen. Nil answers 501 on the act. + Update Updater // Assets is the built front end (internal/web/dist through //go:embed). Nil // serves a placeholder page rather than a 404: a station whose front end has not @@ -318,6 +337,7 @@ type Server struct { troubleshooting Troubleshooting diagnostician Diagnostician dashboard Dashboard + updater Updater assets fs.FS images fs.FS registries domain.Registries @@ -353,7 +373,7 @@ func New(o Options) (*Server, error) { clock: o.Clock, hub: o.Hub, controller: o.Controller, technical: o.Technical, store: o.Store, configStore: o.Config, catalog: o.Catalog, hardware: o.Hardware, printer: o.Printer, troubleshooting: o.Troubleshooting, - diagnostician: o.Diagnostic, dashboard: o.Dashboard, + diagnostician: o.Diagnostic, dashboard: o.Dashboard, updater: o.Update, assets: o.Assets, images: o.Images, registries: o.Registries, binder: o.Binder, version: o.Version, sessions: newSessionStore(o.Clock), @@ -440,6 +460,7 @@ func (s *Server) routes() http.Handler { mux.HandleFunc("GET /admin/api/config/versions", s.configVersions) mux.HandleFunc("GET /admin/api/ports", s.listPorts) mux.HandleFunc("GET /admin/api/printers", s.listPrinters) + mux.HandleFunc("GET /admin/api/update", s.updateStatus) mux.HandleFunc("GET /admin/api/label/preview.png", s.labelPreview) // The journal is open, EXPORT INCLUDED: the page already shows the 200 weighings, // and diagnostic.zip — open — carries them too. A lock on the third door is not one. @@ -477,6 +498,8 @@ func (s *Server) routes() http.Handler { "POST /admin/api/catalog/forget-quarantine": s.forgetQuarantine, "POST /admin/api/products/{id}/decision": s.productDecision, "POST /admin/api/replay": s.replay, + "POST /admin/api/update/check": s.updateCheck, + "POST /admin/api/update/apply": s.updateApply, } for pattern, handler := range guarded { mux.HandleFunc(pattern, s.authenticated(handler)) diff --git a/internal/web/update.go b/internal/web/update.go new file mode 100644 index 0000000..2639303 --- /dev/null +++ b/internal/web/update.go @@ -0,0 +1,191 @@ +package web + +import ( + "encoding/json" + "errors" + "net/http" + "time" + + "openscale/internal/update" +) + +// The codes of the update screen. +// +// Each one is looked up in TROUBLESHOOTING.md, so an invented code is worse than +// none. And two refusals that ask different things of a volunteer never share one: +// « attendez un instant » and « rechargez la page » are not the same instruction, +// which is why -03 and -09 are two codes and not one. +const ( + codeUpdateUnreachable = "ERR-UPD-01" + codeUpdateChecksum = "ERR-UPD-02" + codeUpdateBusy = "ERR-UPD-03" + codeUpdateInFlight = "ERR-UPD-04" + codeUpdateUnsupported = "ERR-UPD-05" + codeUpdateNoArchive = "ERR-UPD-08" + codeUpdateMoved = "ERR-UPD-09" +) + +// outcomeDTO is the last swap, as the screen tells it. +type outcomeDTO struct { + Status string `json:"status"` + From string `json:"from"` + To string `json:"to"` + Reason string `json:"reason"` + FinishedAt string `json:"finished_at"` +} + +// updateDTO is everything the page draws. +type updateDTO struct { + Running string `json:"running"` + Repository string `json:"repository"` + Supported bool `json:"supported"` + Available bool `json:"available"` + Latest string `json:"latest"` + PublishedAt string `json:"published_at"` + HTMLURL string `json:"html_url"` + CheckedAt string `json:"checked_at"` + // Outcome is a POINTER, and null when there is none: « no swap has ever been + // tried » and « a swap was tried and did nothing » are two different sentences + // on the screen, and an empty object would collapse them into one. + Outcome *outcomeDTO `json:"outcome"` +} + +// updateStatus is GET /admin/api/update. It reads, so it needs no password: the +// six settings pages open for reading and ask at the write (ADR-033). +func (s *Server) updateStatus(w http.ResponseWriter, r *http.Request) { + repository := s.hub.Config().Update.Repository + if s.updater == nil { + // Not a 501: the page must still be able to say WHICH version runs and why + // the button is absent. A refusal here would leave it with nothing. + writeJSON(w, http.StatusOK, updateDTO{ + Running: s.version, Repository: repository, Supported: false, + }) + return + } + status, err := s.updater.Status(repository) + if err != nil { + writeProblem(w, http.StatusInternalServerError, "", + "Impossible de lire l'état des mises à jour de ce poste.") + return + } + writeJSON(w, http.StatusOK, updateDTOOf(status)) +} + +// updateCheck is POST /admin/api/update/check: « vérifier maintenant », without +// waiting for the daily poll. +// +// It answers the FRESH state and not an acknowledgement, so that the screen has +// nothing to fetch afterwards. +func (s *Server) updateCheck(w http.ResponseWriter, r *http.Request) { + if s.updater == nil { + writeProblem(w, http.StatusNotImplemented, codeUpdateUnsupported, + "La mise à jour depuis l'écran n'existe que sous Windows.") + return + } + repository := s.hub.Config().Update.Repository + if _, err := s.updater.Check(r.Context(), repository); err != nil { + refuseUpdate(w, err) + return + } + s.updateStatus(w, r) +} + +// applyRequest is the body of POST /admin/api/update/apply. +type applyRequest struct { + // Version is the one the SCREEN showed. It is not decoration: between the + // drawing of the page and the touch of the button a newer release may have + // appeared, and installing that one would install something nobody read. + Version string `json:"version"` +} + +// updateApply is POST /admin/api/update/apply. +func (s *Server) updateApply(w http.ResponseWriter, r *http.Request) { + if s.updater == nil { + writeProblem(w, http.StatusNotImplemented, codeUpdateUnsupported, + "La mise à jour depuis l'écran n'existe que sous Windows.") + return + } + var request applyRequest + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + writeProblem(w, http.StatusBadRequest, "", "La demande est illisible.") + return + } + if err := s.updater.Apply(r.Context(), + s.hub.Config().Update.Repository, request.Version); err != nil { + refuseUpdate(w, err) + return + } + // 202 and not 200: the swap has STARTED, and this process is about to be + // stopped by it. There will be no second answer on this connection, and the + // screen now polls /healthz until somebody answers again. + writeJSON(w, http.StatusAccepted, map[string]string{"version": request.Version}) +} + +// refuseUpdate turns a sentinel of internal/update into the French sentence a +// volunteer reads, with the code they can look up. +func refuseUpdate(w http.ResponseWriter, err error) { + var busy *update.BusyError + switch { + case errors.As(err, &busy): + // The guard WROTE the sentence -- it knows whether a weighing is in + // progress or a catalogue is waiting to enter service -- and this layer + // does not paraphrase it. Paraphrasing would lose the only piece of + // information the volunteer can act on. + writeProblem(w, http.StatusConflict, codeUpdateBusy, busy.Reason) + case errors.Is(err, update.ErrAlreadyRunning): + writeProblem(w, http.StatusConflict, codeUpdateInFlight, + "Une mise à jour est déjà en cours.") + case errors.Is(err, update.ErrVersionMoved): + writeProblem(w, http.StatusConflict, codeUpdateMoved, + "Une autre version est parue depuis l'affichage de cette page. Rechargez-la.") + case errors.Is(err, update.ErrChecksumMismatch): + writeProblem(w, http.StatusBadGateway, codeUpdateChecksum, + "Le fichier téléchargé est abîmé. Rien n'a été installé.") + case errors.Is(err, update.ErrAssetMissing): + writeProblem(w, http.StatusBadGateway, codeUpdateNoArchive, + "Cette version ne contient pas de fichier pour ce poste.") + case errors.Is(err, update.ErrNoRelease): + // A repository that has published nothing stable is not a breakdown, and + // answering a gateway error would send somebody looking for a network + // problem they do not have. 200, and a sentence. + writeJSON(w, http.StatusOK, problem{ + Message: "Ce dépôt n'a publié aucune version.", + }) + default: + writeProblem(w, http.StatusBadGateway, codeUpdateUnreachable, + "Impossible de joindre le serveur des versions.") + } +} + +// updateDTOOf renders one status. +func updateDTOOf(status update.Status) updateDTO { + dto := updateDTO{ + Running: status.Running, Repository: status.Repository, + Supported: status.Supported, Available: status.Available, + } + if status.HasCheck { + dto.Latest = status.Check.Version + dto.HTMLURL = status.Check.HTMLURL + dto.CheckedAt = instantOrEmpty(status.Check.CheckedAt) + dto.PublishedAt = instantOrEmpty(status.Check.PublishedAt) + } + if status.HasOutcome { + dto.Outcome = &outcomeDTO{ + Status: status.Outcome.Status, From: status.Outcome.From, + To: status.Outcome.To, Reason: status.Outcome.Reason, + FinishedAt: instantOrEmpty(status.Outcome.FinishedAt), + } + } + return dto +} + +// instantOrEmpty renders one instant, or the empty string when there is none. +// +// A zero time formatted by RFC3339 reads « 0001-01-01T00:00:00Z », which a screen +// would render as a date in the year one rather than as an absence. +func instantOrEmpty(at time.Time) string { + if at.IsZero() { + return "" + } + return at.Format(time.RFC3339) +} diff --git a/internal/web/update_test.go b/internal/web/update_test.go new file mode 100644 index 0000000..d29a953 --- /dev/null +++ b/internal/web/update_test.go @@ -0,0 +1,267 @@ +package web + +import ( + "context" + "net/http" + "testing" + + "openscale/internal/update" +) + +// stubUpdater answers what a test decides, without a network and without a disk. +type stubUpdater struct { + status update.Status + applyErr error + checkErr error + applied string + checks int +} + +func (u *stubUpdater) Status(repository string) (update.Status, error) { + status := u.status + status.Repository = repository + return status, nil +} + +func (u *stubUpdater) Check(context.Context, string) (update.Check, error) { + u.checks++ + return update.Check{}, u.checkErr +} + +func (u *stubUpdater) Apply(_ context.Context, _, wanted string) error { + u.applied = wanted + return u.applyErr +} + +// nominalUpdate is a station on 2.0.3 whose repository offers 2.1.0. +func nominalUpdate() update.Status { + return update.Status{ + Running: "2.0.3", Supported: true, Available: true, HasCheck: true, + Check: update.Check{ + Tag: "2.1.0", Version: "2.1.0", + CheckedAt: epoch, + PublishedAt: epoch, + HTMLURL: "https://github.com/lostmind84/OpenScale/releases/tag/2.1.0", + }, + } +} + +// TestTheUpdatePageReadsWithoutAPassword: the settings pages open for reading and +// ask at the write (ADR-033), and this page is no different. +func TestTheUpdatePageReadsWithoutAPassword(t *testing.T) { + b := newBench(t, func(o *benchOptions) { + o.update = &stubUpdater{status: nominalUpdate()} + }) + + got := decodeStatus[updateDTO](t, b.get("/admin/api/update"), http.StatusOK) + if got.Running != "2.0.3" || got.Latest != "2.1.0" || !got.Available { + t.Fatalf("état servi = %+v", got) + } + if got.Repository == "" { + t.Error("le dépôt suivi n'est pas servi : c'est ce que la page affiche") + } + if got.Outcome != nil { + t.Error("un poste qui n'a jamais basculé porte un compte rendu") + } +} + +// TestTheTwoActsAreProtected. Every POST of this screen is, and reading a port +// number is not what the password guards -- writing is. +func TestTheTwoActsAreProtected(t *testing.T) { + b := newBench(t, func(o *benchOptions) { + o.update = &stubUpdater{status: nominalUpdate()} + }) + // A password is POSED but no session is opened: without it the guard answers + // 409 « ce poste n'a pas encore de mot de passe », which is a different + // refusal and would let this test pass without the routes being guarded. + b.setPassword("mot-de-passe-long", "ABCD2345") + + for _, route := range []struct { + path, body string + }{ + {"/admin/api/update/check", `{}`}, + {"/admin/api/update/apply", `{"version":"2.1.0"}`}, + } { + response := b.post(route.path, route.body) + defer response.Body.Close() + if response.StatusCode != http.StatusUnauthorized { + t.Errorf("%s sans session = %d, attendu 401", route.path, response.StatusCode) + } + } +} + +// TestApplyingCarriesTheVersionTheScreenShowed. +func TestApplyingCarriesTheVersionTheScreenShowed(t *testing.T) { + updater := &stubUpdater{status: nominalUpdate()} + b := adminBench(t, func(o *benchOptions) { o.update = updater }) + + response := b.post("/admin/api/update/apply", `{"version":"2.1.0"}`) + defer response.Body.Close() + // 202 and not 200: the swap has started and the station that accepted it is + // about to be stopped by it. There is no second answer on this connection. + if response.StatusCode != http.StatusAccepted { + t.Fatalf("statut %d, attendu 202 : %s", response.StatusCode, body(t, response)) + } + if updater.applied != "2.1.0" { + t.Errorf("version installée = %q", updater.applied) + } +} + +// TestEverySentinelBecomesItsOwnCodeAndFrenchSentence freezes the whole mapping. +// +// A code nobody can look up is worse than none, and two refusals that ask +// different things of a volunteer must not share one: « attendez un instant » and +// « rechargez la page » are not the same instruction. +func TestEverySentinelBecomesItsOwnCodeAndFrenchSentence(t *testing.T) { + cases := []struct { + name string + err error + status int + code string + }{ + {"réseau", update.ErrUnreachable, http.StatusBadGateway, "ERR-UPD-01"}, + {"empreinte", update.ErrChecksumMismatch, http.StatusBadGateway, "ERR-UPD-02"}, + {"occupé", &update.BusyError{Reason: "Une pesée est en cours."}, + http.StatusConflict, "ERR-UPD-03"}, + {"déjà en vol", update.ErrAlreadyRunning, http.StatusConflict, "ERR-UPD-04"}, + {"archive absente", update.ErrAssetMissing, http.StatusBadGateway, "ERR-UPD-08"}, + {"version périmée", update.ErrVersionMoved, http.StatusConflict, "ERR-UPD-09"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + b := adminBench(t, func(o *benchOptions) { + o.update = &stubUpdater{status: nominalUpdate(), applyErr: c.err} + }) + got := decodeStatus[problem](t, + b.post("/admin/api/update/apply", `{"version":"2.1.0"}`), c.status) + if got.Code != c.code { + t.Errorf("code %q, attendu %q", got.Code, c.code) + } + if got.Message == "" { + t.Error("refus sans phrase française") + } + }) + } +} + +// TestTheBusyRefusalCarriesTheGuardsOwnSentence. +// +// The Hub knows whether a weighing is in progress or a catalogue is waiting to +// enter service. This layer does not, and paraphrasing would lose the only +// information the volunteer can act on. +func TestTheBusyRefusalCarriesTheGuardsOwnSentence(t *testing.T) { + const reason = "Un catalogue vient d'arriver et n'est pas encore en service. Réessayez dans un instant." + b := adminBench(t, func(o *benchOptions) { + o.update = &stubUpdater{ + status: nominalUpdate(), applyErr: &update.BusyError{Reason: reason}, + } + }) + + got := decodeStatus[problem](t, + b.post("/admin/api/update/apply", `{"version":"2.1.0"}`), http.StatusConflict) + if got.Message != reason { + t.Fatalf("message = %q, attendu la phrase du garde-fou %q", got.Message, reason) + } +} + +// TestAStationWithoutAnUpdaterAnswers501AndKeepsWeighing. +// +// The Linux binary carries the routes and says honestly that it cannot: hiding +// them would leave a screen guessing, and a button doing nothing would be worse. +func TestAStationWithoutAnUpdaterAnswers501AndKeepsWeighing(t *testing.T) { + b := adminBench(t, func(o *benchOptions) { o.update = nil }) + + got := decodeStatus[problem](t, + b.post("/admin/api/update/apply", `{"version":"2.1.0"}`), http.StatusNotImplemented) + if got.Code != "ERR-UPD-05" { + t.Errorf("code = %q, attendu ERR-UPD-05", got.Code) + } + // And reading still answers, so the page can say why the button is absent. + read := decodeStatus[updateDTO](t, b.get("/admin/api/update"), http.StatusOK) + if read.Supported { + t.Error("un poste sans mise à jour se déclare géré") + } + if read.Running == "" { + t.Error("la version installée n'est pas servie : la page n'a rien à afficher") + } +} + +// TestAnUnreadableBodyIsARequestErrorAndNotAGateway. +func TestAnUnreadableBodyIsARequestErrorAndNotAGateway(t *testing.T) { + b := adminBench(t, func(o *benchOptions) { + o.update = &stubUpdater{status: nominalUpdate()} + }) + response := b.post("/admin/api/update/apply", `pas du json`) + defer response.Body.Close() + if response.StatusCode != http.StatusBadRequest { + t.Fatalf("statut %d, attendu 400", response.StatusCode) + } +} + +// TestCheckPollsAndAnswersTheFreshState: the button says « vérifier maintenant », +// so it must both poll and hand back what it found, without a second round trip. +func TestCheckPollsAndAnswersTheFreshState(t *testing.T) { + updater := &stubUpdater{status: nominalUpdate()} + b := adminBench(t, func(o *benchOptions) { o.update = updater }) + + got := decodeStatus[updateDTO](t, b.post("/admin/api/update/check", `{}`), http.StatusOK) + if updater.checks != 1 { + t.Fatalf("%d sondage(s), attendu 1", updater.checks) + } + if got.Latest != "2.1.0" { + t.Errorf("état rendu après le sondage = %+v", got) + } +} + +// TestAFailedCheckIsARefusalAndNotAnEmptyPage. +func TestAFailedCheckIsARefusalAndNotAnEmptyPage(t *testing.T) { + b := adminBench(t, func(o *benchOptions) { + o.update = &stubUpdater{status: nominalUpdate(), checkErr: update.ErrUnreachable} + }) + + got := decodeStatus[problem](t, + b.post("/admin/api/update/check", `{}`), http.StatusBadGateway) + if got.Code != "ERR-UPD-01" { + t.Errorf("code = %q", got.Code) + } +} + +// TestARepositoryWithNoReleaseIsNotABreakdown: a fork that has published nothing +// stable answers a sentence, not a gateway error. +func TestARepositoryWithNoReleaseIsNotABreakdown(t *testing.T) { + b := adminBench(t, func(o *benchOptions) { + o.update = &stubUpdater{status: nominalUpdate(), checkErr: update.ErrNoRelease} + }) + + got := decodeStatus[problem](t, + b.post("/admin/api/update/check", `{}`), http.StatusOK) + if got.Message == "" { + t.Fatal("un dépôt sans version ne dit rien") + } + if got.Code != "" { + t.Errorf("code %q : ce n'est pas une panne", got.Code) + } +} + +// TestTheLastOutcomeReachesTheScreen: whatever happened, the page tells it -- even +// if the browser was closed while the station was down. +func TestTheLastOutcomeReachesTheScreen(t *testing.T) { + status := nominalUpdate() + status.HasOutcome = true + status.Outcome = update.Outcome{ + Status: update.StatusRolledBack, ExitCode: 10, From: "2.0.3", To: "2.1.0", + Reason: "le poste ne répond pas", FinishedAt: epoch, + } + b := newBench(t, func(o *benchOptions) { o.update = &stubUpdater{status: status} }) + + got := decodeStatus[updateDTO](t, b.get("/admin/api/update"), http.StatusOK) + if got.Outcome == nil { + t.Fatal("le dernier compte rendu n'atteint pas l'écran") + } + if got.Outcome.Status != update.StatusRolledBack || got.Outcome.To != "2.1.0" { + t.Errorf("compte rendu = %+v", got.Outcome) + } + if got.Outcome.FinishedAt == "" { + t.Error("le compte rendu ne dit pas quand") + } +} From 0f80fabdf79eca71ed7b973a3db3a53ac981a694 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 12:31:30 +0200 Subject: [PATCH 14/19] feat(station): le poste demande une fois par jour s'il est perime Sans sondage, personne ne saurait qu'un correctif existe : c'est le scenario qui laisse un bug six mois en production, et c'est la raison d'etre du bouton. Le worker NE TELECHARGE RIEN. Il lit quelques kilo-octets de JSON ; l'archive ne descend qu'au clic. Quatre postes une fois par jour sont tres loin de la limite anonyme de soixante requetes par heure. Un sondage rate N'ALLUME AUCUN FEU. Un magasin dont la ligne est tombee n'est pas un poste en panne, et un feu orange la apprendrait aux benevoles a ignorer les feux oranges. Il s'ecrit au journal en warn, sans code ERR -- ce n'est pas une faute du poste, et lui en donner un le classerait a cote d'une imprimante arretee. Les deux minuteries sont enregistrees par Start, dans la goroutine appelante, et passees au worker : c'est la discipline que suivent deja le ticker du Hub et celui du superviseur, et un test l'a IMPOSEE ici avant qu'on la comprenne. Les enregistrer dans la goroutine du worker faisait dependre le premier sondage du moment ou l'ordonnanceur y arrivait -- sur une horloge fausse, c'est la difference entre un test deterministe et un test capricieux. Trente jours passent maintenant en 0,28 s. internal/station ne nomme aucun type d'internal/update : le Poller rend une chaine, et l'adaptateur qui transforme un update.Service en Poller vit dans la racine de composition. Ou le Hub, dont le service a besoin, est resolu par une fermeture -- les deux se veulent mutuellement, et le noeud se defait comme celui du serveur HTTP juste au-dessus. --- cmd/openscale/serve.go | 13 ++- cmd/openscale/update.go | 41 +++++++- internal/station/harness_test.go | 33 +++++++ internal/station/station.go | 20 ++++ internal/station/update_worker_test.go | 125 +++++++++++++++++++++++++ internal/station/workers.go | 77 +++++++++++++++ 6 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 internal/station/update_worker_test.go diff --git a/cmd/openscale/serve.go b/cmd/openscale/serve.go index 2b237ab..ca2639e 100644 --- a/cmd/openscale/serve.go +++ b/cmd/openscale/serve.go @@ -330,7 +330,15 @@ func serve(ctx context.Context, o serveOptions, out io.Writer) error { // §13.4 lives. httpHolder := &heldServer{} - st, err := station.New(station.Options{ + // The update service needs the Hub to ask « may the station be taken down? », + // and the station needs the service to run its daily poll: the same knot as + // the one above, untied the same way. The closure reads `st` when a volunteer + // touches a button, which is long after the line that assigns it. + var st *station.Station + updateService := newUpdateService(clock, + guardFunc(func() (bool, string) { return st.Hub().UpdateGuard() }), o.dataDir) + + st, err = station.New(station.Options{ Clock: clock, Config: cfg, Catalog: catalog, @@ -339,6 +347,7 @@ func serve(ctx context.Context, o serveOptions, out io.Writer) error { // stops being: the answer must not depend on which of the two moments asked. Registries: registries, Templates: templates, + Poller: newUpdatePoller(updateService), Scale: weigher, Printer: printer, Journal: db, @@ -450,7 +459,7 @@ func serve(ctx context.Context, o serveOptions, out io.Writer) error { printer: live, catalog: liveSource, machine: diag.NewMachine(clock), dataDir: o.dataDir, }, - Update: newUpdateService(clock, st.Hub(), o.dataDir), + Update: updateService, }) if err != nil { _ = binder.Close() diff --git a/cmd/openscale/update.go b/cmd/openscale/update.go index e53ba37..2048ec4 100644 --- a/cmd/openscale/update.go +++ b/cmd/openscale/update.go @@ -1,6 +1,7 @@ package main import ( + "context" "os" "path/filepath" "runtime" @@ -22,7 +23,17 @@ import ( // - a platform with no swap. platform.ApplyUpdate answers ErrUpdateUnsupported // off Windows, and a screen that discovered that only at the last click would // have let a volunteer confirm an irreversible-looking act for nothing. -func newUpdateService(clock ports.Clock, hub *station.Hub, dataDir string) *update.Service { +// guardFunc lets a plain function answer update.Guard. +// +// It exists so that the service can be built BEFORE the station whose Hub answers +// the question: the two need each other, and the closure resolves the Hub when a +// volunteer touches a button rather than when the wiring is laid out. +type guardFunc func() (bool, string) + +// UpdateGuard answers by calling the function. +func (f guardFunc) UpdateGuard() (bool, string) { return f() } + +func newUpdateService(clock ports.Clock, guard update.Guard, dataDir string) *update.Service { running, err := update.ParseVersion(version) if err != nil { return nil @@ -42,7 +53,7 @@ func newUpdateService(clock ports.Clock, hub *station.Hub, dataDir string) *upda Clock: clock, State: update.State{Dir: updatesDir}, Stager: update.Stager{Dir: updatesDir, Platform: releasePlatform()}, - Guard: hub, + Guard: guard, Running: running, Supported: true, Paths: update.Paths{ @@ -56,3 +67,29 @@ func newUpdateService(clock ports.Clock, hub *station.Hub, dataDir string) *upda // releasePlatform is the suffix release.yml gives the archives. func releasePlatform() string { return runtime.GOOS + "-" + runtime.GOARCH } + +// updatePoller adapts an update.Service to what the station's daily worker asks. +// +// It exists so that internal/station names no type of internal/update: the station +// asks « is there something newer for this repository? » and gets a version string +// back. Knowing what a release is remains the business of one package, and the +// composition root is where the two are introduced. +type updatePoller struct{ service *update.Service } + +// Poll asks once, and records what came back. +func (p updatePoller) Poll(ctx context.Context, repository string) (string, error) { + check, err := p.service.Check(ctx, repository) + if err != nil { + return "", err + } + return check.Version, nil +} + +// newUpdatePoller returns the daily poll, or nil when this binary cannot update +// itself -- in which case the station starts no worker at all. +func newUpdatePoller(service *update.Service) station.Poller { + if service == nil { + return nil + } + return updatePoller{service: service} +} diff --git a/internal/station/harness_test.go b/internal/station/harness_test.go index 0a721fa..8869010 100644 --- a/internal/station/harness_test.go +++ b/internal/station/harness_test.go @@ -197,6 +197,35 @@ func (r *recordingTechnical) count(code string) int { return n } +// countSource reports how many lines came from that source. +// +// A source and not a code, because the lines this answers for carry no ERR code: +// « the release server could not be reached » is not a fault of the station, and +// giving it a code would put it in the same list as a printer that has stopped. +func (r *recordingTechnical) countSource(source string) int { + r.mu.Lock() + defer r.mu.Unlock() + n := 0 + for _, e := range r.entries { + if e.Source == source { + n++ + } + } + return n +} + +// lastLevel reports the level of the most recent line of that source. +func (r *recordingTechnical) lastLevel(source string) string { + r.mu.Lock() + defer r.mu.Unlock() + for i := len(r.entries) - 1; i >= 0; i-- { + if r.entries[i].Source == source { + return r.entries[i].Level + } + } + return "" +} + // bench is a whole station running on a fake clock, with no hardware at all. type bench struct { t *testing.T @@ -236,6 +265,9 @@ type benchOptions struct { // is what lets it come back out of it when a valid configuration arrives. outOfService bool registries domain.Registries + // poller is the daily check for a newer version. Nil starts no worker, which + // is what a binary that cannot update itself honestly is. + poller Poller } // newBench starts a station and stops it when the test ends. @@ -268,6 +300,7 @@ func newBench(t *testing.T, tweak ...func(*benchOptions)) *bench { Printer: b.printer, Journal: b.journal, TechnicalSink: b.technical, NewScale: o.newScale, CatalogSource: o.source, ApplyCatalog: o.applyCatalog, OnRevert: o.onRevert, OutOfService: o.outOfService, Registries: o.registries, + Poller: o.poller, } if !o.noScale { options.Scale = b.scale diff --git a/internal/station/station.go b/internal/station/station.go index 498b52b..2d80cd1 100644 --- a/internal/station/station.go +++ b/internal/station/station.go @@ -140,6 +140,10 @@ type Options struct { // station never returns: that is the safe default for a caller that never had a reason // to be out of service in the first place. Registries domain.Registries + // Poller is the daily check for a newer version of this binary. Nil starts no + // worker at all, which is what a binary that cannot update itself honestly is + // -- a development build, or a platform with no swap. + Poller Poller // Templates resolves printer.template. It defaults to the shipped ones. Templates map[string]domain.Template // NominalRate is the cadence the scale driver DECLARES, used until the rate @@ -226,6 +230,9 @@ type Station struct { // reload, and nothing else. See Options.Registries. registries domain.Registries + // poller is the daily check for a newer version. Nil starts no goroutine. + poller Poller + // reloadMu serialises configuration changes: two administrators saving at the // same instant must not open two serial ports. reloadMu sync.Mutex @@ -293,6 +300,7 @@ func New(o Options) (*Station, error) { server: o.Server, store: o.Store, onRevert: o.OnRevert, + poller: o.Poller, catalogWait: o.CatalogWait, sink: o.TechnicalSink, ready: make(chan struct{}), @@ -348,6 +356,18 @@ func (s *Station) Start(ctx context.Context) error { go s.journal.run(s.hub.journalEntries, s.hub.technical) go s.supervise(s.rootCtx, watchTicks, stopWatchTicker, s.clock.Now()) go s.watchCatalog(s.rootCtx) + // No poller, no goroutine: a binary that cannot update itself does not spend + // one pretending it might. It is nil on a development build and off Windows. + // + // Its two timers are registered HERE for the reason the two above are: a first + // poll whose deadline is computed on the worker's own goroutine happens + // whenever the scheduler gets there, which on a fake clock is the difference + // between a deterministic test and a flaky one. + if s.poller != nil { + updateGrace := s.clock.After(updateGracePeriod) + updateTicks, stopUpdateTicker := s.clock.Ticker(updatePeriod) + go s.runUpdateWorker(s.rootCtx, updateGrace, updateTicks, stopUpdateTicker, s.poller) + } if err := s.startScale(*s.hub.cfg.Load()); err != nil { // A scale that cannot open is an amber light and a fallback to manual diff --git a/internal/station/update_worker_test.go b/internal/station/update_worker_test.go new file mode 100644 index 0000000..fbce9c8 --- /dev/null +++ b/internal/station/update_worker_test.go @@ -0,0 +1,125 @@ +package station + +import ( + "context" + "errors" + "sync/atomic" + "testing" + + "openscale/internal/domain" +) + +// countingPoller records how often the station asked, and what it was asked about. +type countingPoller struct { + calls atomic.Int64 + repository atomic.Value + version string + err error +} + +func (p *countingPoller) Poll(_ context.Context, repository string) (string, error) { + p.repository.Store(repository) + p.calls.Add(1) + return p.version, p.err +} + +// awaitCalls waits for the poller to have been asked want times. +func awaitCalls(t *testing.T, poller *countingPoller, want int64) { + t.Helper() + awaitCondition(t, func() bool { return poller.calls.Load() >= want }, + "le sondage n'a pas eu lieu") + if got := poller.calls.Load(); got != want { + t.Fatalf("%d sondage(s), attendu %d", got, want) + } +} + +// TestThirtyDaysPassInOneTest is why the clock is injected: a daily poll measured +// on the wall clock would be a test nobody runs. +func TestThirtyDaysPassInOneTest(t *testing.T) { + poller := &countingPoller{version: "2.1.0"} + b := newBench(t, func(o *benchOptions) { o.poller = poller }) + + // Nothing before the grace period: a station that has just booted is opening a + // serial port, reading a catalogue and drawing a screen, and none of that is + // helped by a request starting at the same instant. + b.advance(updateGracePeriod - tickInterval) + if got := poller.calls.Load(); got != 0 { + t.Fatalf("%d sondage(s) avant la fin du délai de grâce", got) + } + + b.advance(2 * tickInterval) + awaitCalls(t, poller, 1) + + for day := int64(1); day <= 30; day++ { + b.advance(updatePeriod) + awaitCalls(t, poller, day+1) + } +} + +// TestThePollAsksAboutTheConfiguredRepository, and not about a constant: a +// cooperative following its fork must be polled about ITS fork. +func TestThePollAsksAboutTheConfiguredRepository(t *testing.T) { + poller := &countingPoller{version: "2.1.0"} + b := newBench(t, func(o *benchOptions) { + o.poller = poller + o.config = func(c *domain.Config) { c.Update.Repository = "la-cagette/openscale" } + }) + + b.advance(updateGracePeriod + tickInterval) + awaitCalls(t, poller, 1) + + if got, _ := poller.repository.Load().(string); got != "la-cagette/openscale" { + t.Fatalf("dépôt sondé = %q", got) + } +} + +// TestAFailedPollLightsNothingAndKeepsTrying. +// +// A shop whose line is down is not a station in breakdown. An amber light there +// would teach volunteers to ignore amber lights -- and one lost poll must not stop +// the station from ever looking again. +func TestAFailedPollLightsNothingAndKeepsTrying(t *testing.T) { + poller := &countingPoller{err: errors.New("réseau injoignable")} + b := newBench(t, func(o *benchOptions) { o.poller = poller }) + + b.advance(updateGracePeriod + tickInterval) + awaitCalls(t, poller, 1) + + if code := b.hub.State().FaultCode; code != "" { + t.Errorf("un sondage raté allume le code de panne %q", code) + } + if state := b.hub.State().State; state != domain.Idle && state != domain.Initializing { + t.Errorf("un sondage raté change l'état du poste : %s", state) + } + + b.advance(updatePeriod) + awaitCalls(t, poller, 2) +} + +// TestAFailedPollIsWrittenDownAtWarn: nothing lights up, but the reason has to be +// findable -- a station that has silently stopped seeing new versions for six +// months is exactly what this whole feature exists to prevent. +func TestAFailedPollIsWrittenDownAtWarn(t *testing.T) { + poller := &countingPoller{err: errors.New("réseau injoignable")} + b := newBench(t, func(o *benchOptions) { o.poller = poller }) + + b.advance(updateGracePeriod + tickInterval) + awaitCalls(t, poller, 1) + + awaitCondition(t, func() bool { return b.technical.countSource("update") > 0 }, + "le sondage raté n'est écrit nulle part") + if level := b.technical.lastLevel("update"); level != domain.LevelWarn { + t.Errorf("niveau %q, attendu %q : un magasin sans connexion n'est pas une panne", + level, domain.LevelWarn) + } +} + +// TestAStationWithoutAPollerStartsNoWorker: a binary that cannot update itself +// does not spend a goroutine pretending it might. +func TestAStationWithoutAPollerStartsNoWorker(t *testing.T) { + b := newBench(t) + b.advance(updateGracePeriod + updatePeriod) + // Nothing to assert but the absence of a panic and of a stuck loop: the station + // still answers, which is what the flush proves. + b.flush() +} diff --git a/internal/station/workers.go b/internal/station/workers.go index 1b7f8d7..59c8814 100644 --- a/internal/station/workers.go +++ b/internal/station/workers.go @@ -226,3 +226,80 @@ func (w *journalWorker) write(entry TechnicalEntry) { w.counters.JournalFailures.Add(1) } } + +// --- Le sondage des versions publiées ------------------------------------------ + +// updateGracePeriod is how long a station waits after starting before it looks for +// a newer version of itself. +// +// Five minutes and not zero: a station that has just booted is opening a serial +// port, reading a catalogue and drawing a screen, and none of that is helped by an +// outbound request starting at the same instant. +const updateGracePeriod = 5 * time.Minute + +// updatePeriod is how often it looks afterwards. +const updatePeriod = 24 * time.Hour + +// Poller is what the station asks, once a day, whether a newer version exists. +// +// It is declared HERE, on the consumer's side, and it names no type of the update +// package: internal/station has no business knowing what a release is. The version +// comes back as a plain string, and the adapter that turns an update.Service into +// this lives in the composition root. +type Poller interface { + // Poll asks the repository whether a newer version exists and records the + // answer, returning the newest version published or the empty string. + Poll(ctx context.Context, repository string) (string, error) +} + +// runUpdateWorker asks the repository, once a day, whether something newer exists. +// +// IT DOWNLOADS NOTHING. It reads a few kilobytes of JSON; the archive comes down +// only when somebody touches the button. Four stations polling once a day sit far +// below the sixty-requests-an-hour anonymous limit. +// +// A FAILED POLL LIGHTS NOTHING. A shop whose line is down is not a station in +// breakdown, and an amber light there would teach volunteers to ignore amber +// lights. It is written down at warn, because a station that has silently stopped +// seeing new versions for six months is exactly what this feature exists to +// prevent -- but it is written down, not shown. +// THE TWO TIMERS ARE REGISTERED BY Start, in the calling goroutine, and handed +// down -- the same discipline the hub ticker and the supervisor ticker follow, and +// for the same reason. Registering them here would make the first poll depend on +// when the scheduler got to this goroutine: on a fake clock that is the difference +// between a deterministic test and a flaky one, and it was measured, not guessed. +func (s *Station) runUpdateWorker(ctx context.Context, grace <-chan time.Time, + ticks <-chan time.Time, stop func(), poller Poller) { + + defer stop() + select { + case <-ctx.Done(): + return + case <-grace: + } + s.pollForUpdate(ctx, poller) + + for { + select { + case <-ctx.Done(): + return + case <-ticks: + s.pollForUpdate(ctx, poller) + } + } +} + +// pollForUpdate asks once, and never lets a refusal escape. +func (s *Station) pollForUpdate(ctx context.Context, poller Poller) { + repository := s.hub.Config().Update.Repository + version, err := poller.Poll(ctx, repository) + if err != nil { + // No ERR code: this is not a fault of the station, and giving it one would + // file it beside a printer that has stopped. + s.hub.logTechnical(domain.LevelWarn, "update", "", + "Impossible de joindre le serveur des versions.", err.Error()) + return + } + s.hub.logTechnical(domain.LevelInfo, "update", "", + "Version publiée la plus récente : "+version, repository) +} From 5bc622924f53c290dc0a65a22e6bba8d73323328 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 12:38:14 +0200 Subject: [PATCH 15/19] feat(admin): une page pour installer la version publiee Neuvieme entree du rail, et non une section de la page Poste : celle-ci porte deja "Cinq versions restaurables", qui parle des versions de la CONFIGURATION. Deux sens du mot "version" sur un meme ecran, devant un benevole, c'est un defaut d'usage qu'on cree soi-meme. Le bouton NOMME la version qu'il installera, parce que c'est ce que le service exige dans le corps de la demande : entre le dessin de la page et l'appui, une autre version a pu paraitre, et le 409 existe pour que le benevole valide ce qu'il a lu. Il est de la famille "irreversible" (ADR-037) : le retour arriere est automatique sur le binaire, il ne l'est PAS sur la base. Pendant la bascule, UNE ERREUR RESEAU EST LE CAS NOMINAL -- c'est l'inverse de tout le reste de cet ecran. Le serveur meurt, et c'est le geste demande qui le tue ; la traiter comme un echec afficherait une panne a l'instant precis ou tout se passe comme prevu. L'ecran ne conclut que sur deux choses : un poste qui repond, ou cinq minutes ecoulees. Les notes de version ne sont pas rendues. Le corps d'une publication est du Markdown venu du reseau : le rendre demanderait une bibliotheque de plus et ouvrirait une injection dans l'ecran d'administration, pour un gain nul. Un lien suffit. Les quatre issues d'update.ps1 se disent en quatre phrases differentes, et un test les compte : "annulee, le poste fonctionne" n'appelle personne, "le poste ne repond pas" demande quelqu'un tout de suite. 684 tests front verts. Budget de l'ecran client inchange a 70,3 % : cette page est dans le paquet d'administration, que la grille ne charge jamais. --- internal/web/dist/admin.html | 6 +- internal/web/dist/assets/admin-BoDRoAHx.js | 1 + internal/web/dist/assets/admin-CsCyE8Xl.js | 1 - .../{index-_4wC-93s.js => index-Bn8FyGo6.js} | 4 +- internal/web/dist/assets/mount-2bHHy8nk.js | 93 +++++++ ...{mount-DFYVhACB.css => mount-BCv4PVId.css} | 2 +- internal/web/dist/assets/mount-DKnEPDGN.js | 89 ------ internal/web/dist/index.html | 2 +- web/src/admin/App.svelte | 4 + web/src/admin/lib/api.ts | 32 +++ web/src/admin/lib/dto.ts | 43 +++ web/src/admin/lib/session.svelte.ts | 13 +- web/src/admin/pages/Update.svelte | 262 ++++++++++++++++++ web/test/admin-update.test.ts | 157 +++++++++++ 14 files changed, 610 insertions(+), 99 deletions(-) create mode 100644 internal/web/dist/assets/admin-BoDRoAHx.js delete mode 100644 internal/web/dist/assets/admin-CsCyE8Xl.js rename internal/web/dist/assets/{index-_4wC-93s.js => index-Bn8FyGo6.js} (99%) create mode 100644 internal/web/dist/assets/mount-2bHHy8nk.js rename internal/web/dist/assets/{mount-DFYVhACB.css => mount-BCv4PVId.css} (91%) delete mode 100644 internal/web/dist/assets/mount-DKnEPDGN.js create mode 100644 web/src/admin/pages/Update.svelte create mode 100644 web/test/admin-update.test.ts diff --git a/internal/web/dist/admin.html b/internal/web/dist/admin.html index 95cd0e8..8239cdc 100644 --- a/internal/web/dist/admin.html +++ b/internal/web/dist/admin.html @@ -5,11 +5,11 @@ Administration - + - + - +
diff --git a/internal/web/dist/assets/admin-BoDRoAHx.js b/internal/web/dist/assets/admin-BoDRoAHx.js new file mode 100644 index 0000000..e4776bc --- /dev/null +++ b/internal/web/dist/assets/admin-BoDRoAHx.js @@ -0,0 +1 @@ +import"./app-Dn-BjSsJ.js";import{mountAdmin as m}from"./mount-2bHHy8nk.js";m(document.getElementById("app")); diff --git a/internal/web/dist/assets/admin-CsCyE8Xl.js b/internal/web/dist/assets/admin-CsCyE8Xl.js deleted file mode 100644 index d83d097..0000000 --- a/internal/web/dist/assets/admin-CsCyE8Xl.js +++ /dev/null @@ -1 +0,0 @@ -import"./app-Dn-BjSsJ.js";import{mountAdmin as m}from"./mount-DKnEPDGN.js";m(document.getElementById("app")); diff --git a/internal/web/dist/assets/index-_4wC-93s.js b/internal/web/dist/assets/index-Bn8FyGo6.js similarity index 99% rename from internal/web/dist/assets/index-_4wC-93s.js rename to internal/web/dist/assets/index-Bn8FyGo6.js index 246e042..8b09389 100644 --- a/internal/web/dist/assets/index-_4wC-93s.js +++ b/internal/web/dist/assets/index-Bn8FyGo6.js @@ -1,2 +1,2 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mount-DKnEPDGN.js","assets/app-Dn-BjSsJ.js","assets/app-DWjtdCrW.css","assets/mount-DFYVhACB.css"])))=>i.map(i=>d[i]); -import{e as Te,u as Ne,c as Re,r as Oe,a as We,D as Be,S as He,d as le,p as ne,b as T,i as B,I as ie,t as N,f as H,g as m,h as ae,j as y,k as t,s as p,l as oe,m as ce,n as te,o as z,q as h,v as u,w as ze,x as ve,y as Fe,z as x,A as D,B as ue,C as Ue,E as Ge,F as Me,G as Ve,H as Xe,J as Ke,K as Ze,L as Je,M as Ye,N as Qe,O as $e,P as et,Q as tt}from"./app-Dn-BjSsJ.js";class be{#e=new WeakMap;#t;#n;static entries=new WeakMap;constructor(e){this.#n=e}observe(e,a){var r=this.#e.get(e)||new Set;return r.add(a),this.#e.set(e,r),this.#r().observe(e,this.#n),()=>{var i=this.#e.get(e);i.delete(a),i.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??(this.#t=new ResizeObserver(e=>{for(var a of e){be.entries.set(a.target,a);for(var r of this.#e.get(a.target)||[])r(a)}}))}}var nt=new be({box:"border-box"});function we(n,e,a){var r=nt.observe(n,()=>a(n[e]));Te(()=>(Ne(()=>a(n[e])),r))}function he(n,e){return n===e||n?.[He]===e}function Ce(n={},e,a,r){var i=Re.r,l=We;return Te(()=>{var d,s;return Oe(()=>{d=s,s=[],Ne(()=>{he(a(...s),n)||(e(n,...s),d&&he(a(...d),n)&&e(null,...d))})}),()=>{let f=l;for(;f!==i&&f.parent!==null&&f.parent.f&Be;)f=f.parent;const v=()=>{s&&he(a(...s),n)&&e(null,...s)},c=f.teardown;f.teardown=()=>{v(),c?.()}}}),n}const at="modulepreload",rt=function(n){return"/"+n},Ee={},st=function(e,a,r){let i=Promise.resolve();if(a&&a.length>0){let f=function(v){return Promise.all(v.map(c=>Promise.resolve(c).then(E=>({status:"fulfilled",value:E}),E=>({status:"rejected",reason:E}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),s=d?.nonce||d?.getAttribute("nonce");i=f(a.map(v=>{if(v=rt(v),v in Ee)return;Ee[v]=!0;const c=v.endsWith(".css"),E=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${v}"]${E}`))return;const A=document.createElement("link");if(A.rel=c?"stylesheet":at,c||(A.as="script"),A.crossOrigin="",A.href=v,s&&A.setAttribute("nonce",s),document.head.appendChild(A),c)return new Promise((w,C)=>{A.addEventListener("load",w),A.addEventListener("error",()=>C(new Error(`Unable to preload CSS for ${v}`)))})}))}function l(d){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=d,window.dispatchEvent(s),!s.defaultPrevented)throw d}return i.then(d=>{for(const s of d||[])s.status==="rejected"&&l(s.reason);return e().catch(l)})};var it=y('

kg

',1),ot=y('

—kg

Poids indisponible

',1),lt=y('');function ct(n,e){ne(e,!0);const a=T(e,"taring",3,!1),r=h(()=>{if(e.linkBanner!=="")return e.linkBanner;const I=e.snapshot?.message??null;if(I!==null)return I.text;switch(e.snapshot?.state){case"product_armed":return"Posez votre produit";case"awaiting_stability":return"Pesée en cours…";case"printing":return"Étiquette en cours…";case"entering_tare":return"Tapez la tare en grammes";case"scale_lost":return"Le poids n’est plus disponible.";default:return"Touchez votre produit, l’étiquette sort"}}),i=h(()=>e.snapshot===null?"var(--waiting)":e.snapshot.state==="faulted"||e.snapshot.state==="scale_lost"?"var(--fault)":e.snapshot.state==="rejected"?"var(--warning)":e.snapshot.state==="succeeded"||e.snapshot.weight.latched?"var(--ready)":"var(--waiting)"),l=h(()=>e.showWeight&&e.snapshot!==null&&e.snapshot.weight.available&&!e.snapshot.weight.expired),d=h(()=>{const I=e.snapshot?.message;return I!=null?I.level:e.snapshot?.state==="faulted"||e.snapshot?.state==="scale_lost"?"error":e.snapshot?.state==="rejected"?"warn":"info"});var s=lt(),f=u(s);let v;var c=u(f);{var E=I=>{var W=it(),F=ze(W),K=u(F),G=p(F,2),Q=u(G);N(()=>{z(K,e.snapshot.weight.net_text),z(Q,`tare ${e.snapshot.weight.tare_g??""} g`)}),m(I,W)},A=I=>{var W=ot();m(I,W)};B(c,I=>{t(l)&&e.snapshot?I(E):I(A,-1)})}var w=p(f,2);let C;var g=u(w),k=p(w,2);let O;var q=u(k);ie(q,{name:"tare",size:"1.4em"});var X=p(k,2);let U;N(()=>{oe(s,"data-state",e.snapshot?.state??"initializing"),v=ce(f,"",v,{"border-left-color":t(i)}),C=te(w,1,"instruction svelte-4gizvm",null,C,{warn:t(d)==="warn",error:t(d)==="error"}),z(g,t(r)),O=te(k,1,"tare-key touch-target svelte-4gizvm",null,O,{active:a()}),U=ce(X,"",U,{background:t(i)})}),H("click",k,function(...I){e.ontare?.apply(this,I)}),m(n,s),ae()}le(["click"]);const ut=4.5,dt=.1,vt={r:138,g:134,b:124};function qe(n){const e=/^#([\da-f])([\da-f])([\da-f])$/iu.exec(n);if(e!==null){const[,d,s,f]=e;return{r:Number.parseInt(d+d,16),g:Number.parseInt(s+s,16),b:Number.parseInt(f+f,16)}}const a=/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/iu.exec(n);if(a===null)return vt;const[,r,i,l]=a;return{r:Number.parseInt(r,16),g:Number.parseInt(i,16),b:Number.parseInt(l,16)}}function pe(n){const e=n/255;return e<=.03928?e/12.92:((e+.055)/1.055)**2.4}function ft({r:n,g:e,b:a}){return .2126*pe(n)+.7152*pe(e)+.0722*pe(a)}function gt(n){return 1.05/(ft(n)+.05)}function je(n){const{r:e,g:a,b:r}=qe(n),i=l=>Math.round(255-dt*(255-l));return`rgb(${i(e)}, ${i(a)}, ${i(r)})`}function De(n){let e=qe(n);for(let a=0;a<16&>(e) '),pt=y('');function _t(n,e){ne(e,!0);var a=pt(),r=u(a);ve(r,21,()=>e.chips,s=>s.code,(s,f)=>{var v=ht();let c,E;var A=p(u(v),2),w=u(A),C=p(A,2),g=u(C);N(k=>{c=te(v,1,"chip touch-target svelte-atgty1",null,c,{active:t(f).code===e.active}),oe(v,"aria-pressed",t(f).code===e.active),E=ce(v,"",E,k),z(w,t(f).label),z(g,t(f).count)},[()=>({"--chip-ink":De(t(f).color),"--chip-wash":je(t(f).color)})]),H("click",v,()=>e.onselect(t(f).code)),m(s,v)});var i=p(r,2);let l;var d=u(i);ie(d,{name:"search",size:"1.75rem"}),N(()=>{l=te(i,1,"chip touch-target search-key svelte-atgty1",null,l,{active:e.searchFieldOpen}),oe(i,"aria-pressed",e.searchFieldOpen)}),H("click",i,function(...s){e.onopensearch?.apply(this,s)}),m(n,a),ae()}le(["click"]);var bt=y(''),mt=y('

'),yt=y('

');function ke(n,e){ne(e,!0);const a=T(e,"action",3,null);var r=yt(),i=u(r),l=u(i),d=u(l);ie(d,{name:"alert",size:"3rem"});var s=p(l,2),f=u(s),v=p(s,2),c=u(v),E=p(v,2);{var A=g=>{var k=bt(),O=u(k);N(()=>z(O,a().label)),H("click",k,function(...q){a().run?.apply(this,q)}),m(g,k)};B(E,g=>{a()!==null&&g(A)})}var w=p(E,2);{var C=g=>{var k=mt(),O=u(k);N(()=>z(O,e.code)),m(g,k)};B(w,g=>{e.code!==""&&g(C)})}N(()=>{z(f,e.title),z(c,e.detail)}),m(n,r),ae()}le(["click"]);const fe=34,xe=18,wt=.5,Et=.005,kt=1.15,Le=90,me=100;function xt(n,e){return Math.max(1,Math.floor(e/(n*kt)))}function At(n,e,a,r=Le){const i=St(n,a);if(i.length===0||e<=0)return fe;const l=Pt(a),d=e*(1-Et);for(let s=fe;s>xe;s-=wt){const f=s/me;if(Tt(i,l,f,d)<=xt(s,r))return s}return xe}const It=/(?<=[-‐–—])(?!\d)/u;function St(n,e){const a=[];for(const r of n.split(/\s+/u).filter(i=>i.length>0)){const i=r.split(It).filter(l=>l.length>0);for(const[l,d]of i.entries())a.push({width:e(d),length:[...d].length,spaced:l===0})}return a}function Pt(n){return Math.max(0,n("a a")-n("aa"))}function Tt(n,e,a,r){let i=1,l=0;for(const d of n){const s=d.width*a,f=d.spaced?e*a:0;if(l>0&&l+f+s<=r){l+=f+s;continue}if(l>0&&(i++,l=0),s<=r){l=s;continue}i+=Nt(s,d.length,r)-1,l=r}return i}function Nt(n,e,a){const r=Math.max(1,Math.floor(a*e/n));return Math.ceil(e/r)}function zt(n,e){const r=document.createElement("canvas").getContext("2d");if(r===null||typeof r.measureText!="function")return null;r.font=`${e} ${me}px ${n}`;const i=new Map;return l=>{const d=i.get(l);if(d!==void 0)return d;const s=r.measureText(l).width;return i.set(l,s),s}}var Mt=y(''),Ct=y(''),qt=y(' '),jt=y(''),Dt=y('');function Lt(n,e){ne(e,!0);const a=T(e,"nameSizePx",3,fe),r=T(e,"categoryColor",3,"#8a867c"),i=T(e,"primaryCode",3,""),l=T(e,"tierAbbrev",19,()=>({})),d=T(e,"selected",3,!1),s=T(e,"rejected",3,!1),f=T(e,"busy",3,!1),v=T(e,"showPrice",3,!0);let c=D("");const E=h(()=>e.product.image_url!==""&&e.product.image_url!==t(c)),A=h(()=>g(e.product.name)),w=h(()=>je(r())),C=h(()=>De(r()));function g(S){const M=S.match(/\p{L}/u);return M===null?"·":M[0].toUpperCase()}var k=Dt();let O;var q=u(k);let X;var U=u(q);{var I=S=>{var M=Mt();N(()=>oe(M,"src",e.product.image_url)),Fe("error",M,()=>x(c,e.product.image_url,!0)),m(S,M)},W=S=>{var M=Ct();let Z;var J=u(M);N(()=>{Z=ce(M,"",Z,{color:t(C)}),z(J,t(A))}),m(S,M)};B(U,S=>{t(E)?S(I):S(W,-1)})}var F=p(q,2),K=u(F);let G;var Q=u(K),_=p(F,2);{var P=S=>{var M=jt();ve(M,21,()=>e.product.prices,Z=>Z.code,(Z,J)=>{var b=qt();let L;var j=u(b);let R;var ee=u(j),Y=p(j,2),$=u(Y),re=p(Y,2),se=u(re);N(de=>{L=te(b,1,"price svelte-1ctu6qg",null,L,{secondary:t(J).code!==i()}),R=te(j,1,"abbrev svelte-1ctu6qg",null,R,{hollow:t(J).code!==i()}),z(ee,l()[t(J).code]??""),z($,t(J).text),z(se,de)},[()=>e.product.price_suffix.trim()]),m(Z,b)}),m(S,M)};B(_,S=>{v()&&S(P)})}N(()=>{O=te(k,1,"tile touch-target svelte-1ctu6qg",null,O,{selected:d(),rejected:s()}),oe(k,"data-product-id",e.product.id),k.disabled=f(),X=ce(q,"",X,{background:t(w)}),G=ce(K,"",G,{"font-size":`${a()??""}px`}),z(Q,e.product.name)}),H("pointerdown",k,()=>e.onpick(e.product)),m(n,k),ae()}le(["pointerdown"]);var Rt=y('
'),Ot=y('

Chargement du catalogue…

',1),Wt=y('

'),Bt=y('

'),Ht=y('
'),Ft=y('
'),Ut=y('
');function Gt(n,e){ne(e,!0);const a=T(e,"colors",19,()=>({})),r=T(e,"primaryCode",3,""),i=T(e,"tierAbbrev",19,()=>({})),l=T(e,"selectedID",3,null),d=T(e,"rejectedID",3,null),s=T(e,"busyID",3,null),f=T(e,"showPrices",3,!0),v=T(e,"loading",3,!1),c=T(e,"emptyMessage",3,"Aucun produit ne correspond."),E=T(e,"emptyHint",3,""),A=230,w=8,C=26,g="'Inter Variable', system-ui, sans-serif",k="'Inter Variable'",O=16;let q=D(0),X=D(!1);ue(()=>{const b=document.fonts;b!==void 0&&b.load(`700 ${me}px ${k}`).catch(()=>[]).then(()=>{x(X,!0)})});const U=h(()=>(t(X),zt(g,700)));let I=D(null),W=D(0),F=D(0);const K=h(()=>{if(t(q)<=0)return 0;const b=Math.max(1,Math.floor((t(q)+w)/(A+w)));return(t(q)-(b-1)*w)/b-C}),G=h(()=>t(W)>0?t(W):t(K));ue(()=>{t(q),e.products.length;const b=t(I)?.querySelector(".name-box");x(W,b?.clientWidth??0,!0)});const Q=h(()=>{const b=new Map;if(t(U)===null||t(G)<=0)return b;const L=t(F)>0?t(F):Le;for(const j of e.products)b.set(j.id,At(j.name,t(G),t(U),L));return b});var _=Ut(),P=u(_),S=p(P,2);{var M=b=>{var L=Ot(),j=ze(L);ve(j,21,()=>({length:O}),Ue,(R,ee)=>{var Y=Rt();m(R,Y)}),m(b,L)},Z=b=>{var L=Bt(),j=u(L),R=u(j),ee=p(j,2);{var Y=$=>{var re=Wt(),se=u(re);N(()=>z(se,E())),m($,re)};B(ee,$=>{E()!==""&&$(Y)})}N(()=>z(R,c())),m(b,L)},J=b=>{var L=Ft();ve(L,21,()=>e.products,j=>j.id,(j,R)=>{var ee=Ht(),Y=u(ee);{let $=h(()=>t(Q).get(t(R).id)??fe),re=h(()=>t(R).id===l()),se=h(()=>t(R).id===d()),de=h(()=>t(R).id===s());Lt(Y,{get product(){return t(R)},get nameSizePx(){return t($)},get categoryColor(){return a()[t(R).category_code]},get primaryCode(){return r()},get tierAbbrev(){return i()},get selected(){return t(re)},get rejected(){return t(se)},get busy(){return t(de)},get showPrice(){return f()},get onpick(){return e.onpick}})}m(j,ee)}),N(()=>oe(L,"data-tile-count",e.products.length)),m(b,L)};B(S,b=>{v()?b(M):e.products.length===0?b(Z,1):b(J,-1)})}Ce(_,b=>x(I,b),()=>t(I)),we(P,"clientHeight",b=>x(F,b)),we(_,"clientWidth",b=>x(q,b)),m(n,_),ae()}var Vt=y(''),Xt=y('
');function Kt(n,e){ne(e,!0);let a=D(null);ue(()=>{t(a)?.focus()});var r=Xt(),i=u(r);ie(i,{name:"search",size:"1.75rem"});var l=p(i,2);Ce(l,c=>x(a,c),()=>t(a));var d=p(l,2);{var s=c=>{var E=Vt();H("click",E,function(...A){e.onclose?.apply(this,A)}),m(c,E)};B(d,c=>{e.query!==""&&c(s)})}var f=p(d,2),v=u(f);ie(v,{name:"close",size:"1.5rem"}),N(()=>Ge(l,e.query)),H("input",l,c=>e.onquery(c.currentTarget.value)),H("keydown",l,c=>{c.key==="Escape"?(c.preventDefault(),e.onclose()):c.key==="Enter"&&(c.preventDefault(),e.onenter())}),H("click",f,function(...c){e.onclose?.apply(this,c)}),m(n,r),ae()}le(["input","keydown","click"]);function Zt(n){if(n==="")return"";const e=new Date(n);if(Number.isNaN(e.getTime()))return"";const a=i=>String(i).padStart(2,"0");return`${`${a(e.getDate())}/${a(e.getMonth()+1)}/${e.getFullYear()}`} ${a(e.getHours())}:${a(e.getMinutes())}:${a(e.getSeconds())}`}function Jt(n){return n.mode==="by_unit"?`${n.product_name} ${n.quantity} ${n.quantity>1?"unités":"unité"}`:`${n.product_name} ${n.net_text} kg`}var Yt=y('Catalogue du '),Qt=y('Catalogue en attente'),$t=y(' '),en=y('aucune pour le moment'),tn=y(' '),nn=y(''),an=y('
Dernière étiquette
');function rn(n,e){ne(e,!0);const a=T(e,"catalogAt",3,""),r=T(e,"productCount",3,0),i=T(e,"appVersion",3,""),l=h(()=>[r()>0?`${r()} produits pesables`:"",i()!==""?`application ${i()}`:""].filter(_=>_!=="").join(" · "));var d=an();let s;var f=u(d),v=u(f);{var c=_=>{var P=Yt(),S=p(u(P)),M=u(S);N(()=>z(M,a())),m(_,P)},E=_=>{var P=Qt();m(_,P)};B(v,_=>{a()!==""?_(c):_(E,-1)})}var A=p(v,2);{var w=_=>{var P=$t(),S=u(P);N(()=>z(S,t(l))),m(_,P)};B(A,_=>{t(l)!==""&&_(w)})}var C=p(f,4),g=p(u(C),2);{var k=_=>{var P=en();m(_,P)},O=_=>{var P=tn(),S=u(P);N(M=>z(S,M),[()=>Jt(e.label)]),m(_,P)};B(g,_=>{e.label===null?_(k):_(O,-1)})}var q=p(C,2);{var X=_=>{var P=nn();N(()=>P.disabled=!e.available),H("click",P,function(...S){e.onreprint?.apply(this,S)}),m(_,P)};B(q,_=>{e.label!==null&&_(X)})}var U=p(q,2),I=u(U);let W;var F=p(I,2),K=u(F),G=p(U,2),Q=u(G);ie(Q,{name:"settings",size:"1.75rem"}),N(()=>{s=te(d,1,"bar svelte-161y12f",null,s,{live:e.label!==null}),W=te(I,1,"dot svelte-161y12f",null,W,{fault:!e.healthy}),oe(I,"aria-label",e.healthy?"Matériel disponible":"Matériel indisponible"),z(K,e.healthy?"Balance et imprimante disponibles":"Matériel indisponible")}),H("click",G,function(..._){e.onadmin?.apply(this,_)}),m(n,d),ae()}le(["click"]);var sn=y(''),on=y('

Tare g

');function ln(n,e){ne(e,!0);const a=["1","2","3","4","5","6","7","8","9","0"];var r=on(),i=u(r),l=p(u(i),2),d=u(l),s=p(i,2);ve(s,20,()=>a,w=>w,(w,C)=>{var g=sn(),k=u(g);N(()=>z(k,C)),H("click",g,()=>e.ondigit(C)),m(w,g)});var f=p(s,2),v=u(f),c=p(v,2),E=p(c,2),A=u(E);ie(A,{name:"check",size:"1.5rem"}),N(()=>z(d,e.grams===""?"0":e.grams)),H("click",v,function(...w){e.onclear?.apply(this,w)}),H("click",c,function(...w){e.oncancel?.apply(this,w)}),H("click",E,function(...w){e.onconfirm?.apply(this,w)}),m(n,r),ae()}le(["click"]);function cn(n){return n===null||n.state==="initializing"?!1:n.scale.connected&&n.printer.health!=="faulted"}const Ae=1500,un=2e3,Ie=5e3;function dn(n,e){return e?n>=Ie?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!0}:n>=un?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!1}:n>=Ae?{showWeight:!1,banner:"",mustReconnect:!1}:{showWeight:!0,banner:"",mustReconnect:!1}:{showWeight:n=Ie}}const vn=250,fn={show_grid_prices:!0,idle_timeout_s:45,reprint_window_s:60,sound:!0};class gn{#e=D(null);get state(){return t(this.#e)}set state(e){x(this.#e,e,!0)}#t=D(null);get catalog(){return t(this.#t)}set catalog(e){x(this.#t,e,!0)}#n=D(Me({showWeight:!0,banner:"",mustReconnect:!1}));get link(){return t(this.#n)}set link(e){x(this.#n,e,!0)}#r=D("");get catalogError(){return t(this.#r)}set catalogError(e){x(this.#r,e,!0)}#s=null;#i=null;#o=Date.now();#a=!1;#l=!1;get presentation(){return this.catalog?.presentation??fn}start(){this.#u(),this.#c(),this.#i=setInterval(()=>this.#v(),vn)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null,this.#s?.close(),this.#s=null,this.#a=!1}#c(){const e=new EventSource("/api/v1/stream");this.#s=e,e.onopen=()=>{this.#a=!0,this.#o=Date.now()},e.addEventListener("state",a=>this.#d(a)),e.onerror=()=>{this.#a=!1}}#d(e){this.#o=Date.now(),this.#a=!0;const a=JSON.parse(e.data);this.state=a,this.catalog!==null&&a.catalog_count!==this.catalog.product_count&&this.#u()}#v(){const e=dn(Date.now()-this.#o,this.#a);this.link=e,e.mustReconnect&&(this.#s?.close(),this.#o=Date.now(),this.#c())}async#u(){if(!this.#l){this.#l=!0;try{this.catalog=await Ve(),this.catalogError=""}catch{this.catalog===null&&(this.catalogError="Catalogue indisponible. Prévenez un responsable.")}finally{this.#l=!1}}}}const Se="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Pe(){let n=Date.now();const e=new Array(26);for(let r=9;r>=0;r--)e[r]=Se[n%32],n=Math.floor(n/32);const a=new Uint8Array(16);crypto.getRandomValues(a);for(let r=0;r<16;r++)e[10+r]=Se[a[r]%32];return e.join("")}var hn=y('
');function pn(n,e){ne(e,!0);const a=new gn;let r=D(Me(Xe)),i=D(""),l=D(!1);const d=h(()=>t(i)!==""||t(l));let s=D(null),f=D(-1),v=D(null);const c=h(()=>a.catalog),E=h(()=>t(c)===null?[]:Qe(t(c))),A=h(()=>t(c)===null?[]:Je(t(c))),w=h(()=>Ze(t(E),t(r),t(i))),C=h(()=>{const o={};for(const V of t(c)?.categories??[])o[V.code]=V.color;return o}),g=h(()=>a.state),k=h(()=>a.presentation),O=h(()=>cn(t(g))),q=h(()=>{if(a.catalogError!=="")return{message:a.catalogError,hint:"Le poste réessaie tout seul."};if(t(E).length>0)return{message:"Aucun produit ne correspond.",hint:"Effacez des lettres ou changez de rayon."};const o=t(g)?.station??0;return{message:o>0?`Catalogue vide. En attente du fichier flv_${o}.csv.`:"Catalogue vide. En attente du fichier du poste.",hint:"Prévenez un responsable : aucun produit ne peut être pesé."}}),X=h(()=>t(g)?.state==="rejected"?t(g).product?.id??null:null),U=h(()=>t(g)?.state==="faulted"||t(g)?.state==="out_of_service"),I=["product_armed","weight_present","weight_stable","awaiting_stability","validating","printing"],W=h(()=>t(g)!==null&&I.includes(t(g).state)?t(g).product?.id??null:null),F=h(()=>Zt(t(c)?.updated_at??""));ue(()=>(a.start(),()=>a.stop())),ue(()=>{const o=a.state?.revision??-1;t(s)!==null&&o>t(f)&&(x(s,null),x(f,-1))});async function K(o){if(t(s)===null){x(s,o.id,!0),x(f,a.state?.revision??-1,!0);try{await Ke({product_id:o.id,tare_g:a.state?.weight.tare_g??0,units:1,manual_weight_g:0,seen_weight_g:a.state?.weight.gross_g??0,measurement_seq:a.state?.weight.seq??0,key:Pe()})}catch{x(s,null)}}}async function G(){const o=a.state?.reprint.job_id;o===void 0||o===""||await Ye(o,Pe())}function Q(){x(i,""),x(l,!1)}function _(){x(l,!0)}function P(){const o=t(w).length===1?t(w)[0]:void 0;o!==void 0&&K(o)}function S(o){if(!(o.metaKey||o.ctrlKey||o.altKey)&&!(o.target instanceof HTMLElement&&o.target.tagName==="INPUT")&&!(t(v)!==null||t(U))){if(o.key==="Escape"){o.preventDefault(),Q();return}if(o.key==="Backspace"){o.preventDefault(),x(i,t(i).slice(0,-1),!0);return}if(o.key==="Enter"){P();return}o.key.length===1&&/[a-zA-Z0-9 ]/.test(o.key)&&(o.key===" "&&t(i)===""||(o.preventDefault(),x(i,t(i)+o.key)))}}ue(()=>(window.addEventListener("keydown",S),()=>window.removeEventListener("keydown",S)));async function M(){(await st(()=>import("./mount-DKnEPDGN.js"),__vite__mapDeps([0,1,2,3]))).mountAdmin(document.body)}var Z=hn(),J=u(Z);{let o=h(()=>t(v)!==null);ct(J,{get snapshot(){return t(g)},get showWeight(){return a.link.showWeight},get linkBanner(){return a.link.banner},get taring(){return t(o)},ontare:()=>x(v,"")})}var b=p(J,2);{var L=o=>{ln(o,{get grams(){return t(v)},ondigit:V=>x(v,(t(v)??"")+V),onclear:()=>x(v,""),oncancel:()=>x(v,null),onconfirm:()=>x(v,null)})};B(b,o=>{t(v)!==null&&o(L)})}var j=p(b,2);{let o=h(()=>t(c)?.pricing.primary_code??""),V=h(()=>Object.fromEntries((t(c)?.pricing.tiers??[]).map(ye=>[ye.code,ye.abbrev]))),ge=h(()=>t(c)===null&&a.catalogError==="");Gt(j,{get products(){return t(w)},get colors(){return t(C)},get primaryCode(){return t(o)},get tierAbbrev(){return t(V)},get selectedID(){return t(W)},get rejectedID(){return t(X)},get busyID(){return t(s)},get showPrices(){return t(k).show_grid_prices},get loading(){return t(ge)},get emptyMessage(){return t(q).message},get emptyHint(){return t(q).hint},onpick:K})}var R=p(j,2);{var ee=o=>{Kt(o,{get query(){return t(i)},onquery:V=>x(i,V,!0),onclose:Q,onenter:P})};B(R,o=>{t(d)&&o(ee)})}var Y=p(R,2);_t(Y,{get chips(){return t(A)},get active(){return t(r)},get searchFieldOpen(){return t(d)},onselect:o=>x(r,o,!0),onopensearch:_});var $=p(Y,2);{let o=h(()=>t(g)?.last_label??null),V=h(()=>t(g)?.reprint.available??!1),ge=h(()=>t(c)?.app_version??"");rn($,{get label(){return t(o)},get available(){return t(V)},get catalogAt(){return t(F)},get productCount(){return t(E).length},get appVersion(){return t(ge)},get healthy(){return t(O)},onreprint:G,onadmin:M})}var re=p($,2);{var se=o=>{{let V=h(()=>t(g).message?.text??"Prévenez un responsable.");ke(o,{title:"Poste indisponible",get detail(){return t(V)},get code(){return t(g).fault_code},action:{label:"J’ai compris",run:()=>{$e()}}})}},de=o=>{ke(o,{title:"Poste hors service",detail:"Ce poste ne peut pas peser. Prévenez un responsable.",get code(){return t(g).fault_code}})};B(re,o=>{t(g)?.state==="faulted"?o(se):t(g)?.state==="out_of_service"&&o(de,1)})}m(n,Z),ae()}const _n=5;mn();document.addEventListener("contextmenu",n=>{bn(n.target)||n.preventDefault()});document.addEventListener("dragstart",n=>n.preventDefault());et(pn,{target:document.getElementById("app")});function bn(n){return n instanceof Element&&n.closest("[data-admin]")!==null}function mn(){let n=!1;const e=(a,r)=>{n||(n=!0,tt(a,r),yn(),setTimeout(()=>location.reload(),_n*1e3))};window.addEventListener("error",a=>e(a.message,a.error?.stack??"")),window.addEventListener("unhandledrejection",a=>e(String(a.reason),a.reason?.stack??""))}function yn(){const n=document.createElement("div");n.className="fatal",n.append(_e("h1","Une erreur est survenue"),_e("p","L’écran va se recharger tout seul."),_e("p","ERR-UI-01","code")),document.body.appendChild(n)}function _e(n,e,a=""){const r=document.createElement(n);return r.textContent=e,a!==""&&(r.className=a),r} +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mount-2bHHy8nk.js","assets/app-Dn-BjSsJ.js","assets/app-DWjtdCrW.css","assets/mount-BCv4PVId.css"])))=>i.map(i=>d[i]); +import{e as Te,u as Ne,c as Re,r as Oe,a as We,D as Be,S as He,d as le,p as ne,b as T,i as B,I as ie,t as N,f as H,g as m,h as ae,j as y,k as t,s as p,l as oe,m as ce,n as te,o as z,q as h,v as u,w as ze,x as ve,y as Fe,z as x,A as D,B as ue,C as Ue,E as Ge,F as Me,G as Ve,H as Xe,J as Ke,K as Ze,L as Je,M as Ye,N as Qe,O as $e,P as et,Q as tt}from"./app-Dn-BjSsJ.js";class be{#e=new WeakMap;#t;#n;static entries=new WeakMap;constructor(e){this.#n=e}observe(e,a){var r=this.#e.get(e)||new Set;return r.add(a),this.#e.set(e,r),this.#r().observe(e,this.#n),()=>{var i=this.#e.get(e);i.delete(a),i.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??(this.#t=new ResizeObserver(e=>{for(var a of e){be.entries.set(a.target,a);for(var r of this.#e.get(a.target)||[])r(a)}}))}}var nt=new be({box:"border-box"});function we(n,e,a){var r=nt.observe(n,()=>a(n[e]));Te(()=>(Ne(()=>a(n[e])),r))}function he(n,e){return n===e||n?.[He]===e}function Ce(n={},e,a,r){var i=Re.r,l=We;return Te(()=>{var d,s;return Oe(()=>{d=s,s=[],Ne(()=>{he(a(...s),n)||(e(n,...s),d&&he(a(...d),n)&&e(null,...d))})}),()=>{let f=l;for(;f!==i&&f.parent!==null&&f.parent.f&Be;)f=f.parent;const v=()=>{s&&he(a(...s),n)&&e(null,...s)},c=f.teardown;f.teardown=()=>{v(),c?.()}}}),n}const at="modulepreload",rt=function(n){return"/"+n},Ee={},st=function(e,a,r){let i=Promise.resolve();if(a&&a.length>0){let f=function(v){return Promise.all(v.map(c=>Promise.resolve(c).then(E=>({status:"fulfilled",value:E}),E=>({status:"rejected",reason:E}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),s=d?.nonce||d?.getAttribute("nonce");i=f(a.map(v=>{if(v=rt(v),v in Ee)return;Ee[v]=!0;const c=v.endsWith(".css"),E=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${v}"]${E}`))return;const A=document.createElement("link");if(A.rel=c?"stylesheet":at,c||(A.as="script"),A.crossOrigin="",A.href=v,s&&A.setAttribute("nonce",s),document.head.appendChild(A),c)return new Promise((w,C)=>{A.addEventListener("load",w),A.addEventListener("error",()=>C(new Error(`Unable to preload CSS for ${v}`)))})}))}function l(d){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=d,window.dispatchEvent(s),!s.defaultPrevented)throw d}return i.then(d=>{for(const s of d||[])s.status==="rejected"&&l(s.reason);return e().catch(l)})};var it=y('

kg

',1),ot=y('

—kg

Poids indisponible

',1),lt=y('');function ct(n,e){ne(e,!0);const a=T(e,"taring",3,!1),r=h(()=>{if(e.linkBanner!=="")return e.linkBanner;const I=e.snapshot?.message??null;if(I!==null)return I.text;switch(e.snapshot?.state){case"product_armed":return"Posez votre produit";case"awaiting_stability":return"Pesée en cours…";case"printing":return"Étiquette en cours…";case"entering_tare":return"Tapez la tare en grammes";case"scale_lost":return"Le poids n’est plus disponible.";default:return"Touchez votre produit, l’étiquette sort"}}),i=h(()=>e.snapshot===null?"var(--waiting)":e.snapshot.state==="faulted"||e.snapshot.state==="scale_lost"?"var(--fault)":e.snapshot.state==="rejected"?"var(--warning)":e.snapshot.state==="succeeded"||e.snapshot.weight.latched?"var(--ready)":"var(--waiting)"),l=h(()=>e.showWeight&&e.snapshot!==null&&e.snapshot.weight.available&&!e.snapshot.weight.expired),d=h(()=>{const I=e.snapshot?.message;return I!=null?I.level:e.snapshot?.state==="faulted"||e.snapshot?.state==="scale_lost"?"error":e.snapshot?.state==="rejected"?"warn":"info"});var s=lt(),f=u(s);let v;var c=u(f);{var E=I=>{var W=it(),F=ze(W),K=u(F),G=p(F,2),Q=u(G);N(()=>{z(K,e.snapshot.weight.net_text),z(Q,`tare ${e.snapshot.weight.tare_g??""} g`)}),m(I,W)},A=I=>{var W=ot();m(I,W)};B(c,I=>{t(l)&&e.snapshot?I(E):I(A,-1)})}var w=p(f,2);let C;var g=u(w),k=p(w,2);let O;var q=u(k);ie(q,{name:"tare",size:"1.4em"});var X=p(k,2);let U;N(()=>{oe(s,"data-state",e.snapshot?.state??"initializing"),v=ce(f,"",v,{"border-left-color":t(i)}),C=te(w,1,"instruction svelte-4gizvm",null,C,{warn:t(d)==="warn",error:t(d)==="error"}),z(g,t(r)),O=te(k,1,"tare-key touch-target svelte-4gizvm",null,O,{active:a()}),U=ce(X,"",U,{background:t(i)})}),H("click",k,function(...I){e.ontare?.apply(this,I)}),m(n,s),ae()}le(["click"]);const ut=4.5,dt=.1,vt={r:138,g:134,b:124};function qe(n){const e=/^#([\da-f])([\da-f])([\da-f])$/iu.exec(n);if(e!==null){const[,d,s,f]=e;return{r:Number.parseInt(d+d,16),g:Number.parseInt(s+s,16),b:Number.parseInt(f+f,16)}}const a=/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/iu.exec(n);if(a===null)return vt;const[,r,i,l]=a;return{r:Number.parseInt(r,16),g:Number.parseInt(i,16),b:Number.parseInt(l,16)}}function pe(n){const e=n/255;return e<=.03928?e/12.92:((e+.055)/1.055)**2.4}function ft({r:n,g:e,b:a}){return .2126*pe(n)+.7152*pe(e)+.0722*pe(a)}function gt(n){return 1.05/(ft(n)+.05)}function je(n){const{r:e,g:a,b:r}=qe(n),i=l=>Math.round(255-dt*(255-l));return`rgb(${i(e)}, ${i(a)}, ${i(r)})`}function De(n){let e=qe(n);for(let a=0;a<16&>(e) '),pt=y('');function _t(n,e){ne(e,!0);var a=pt(),r=u(a);ve(r,21,()=>e.chips,s=>s.code,(s,f)=>{var v=ht();let c,E;var A=p(u(v),2),w=u(A),C=p(A,2),g=u(C);N(k=>{c=te(v,1,"chip touch-target svelte-atgty1",null,c,{active:t(f).code===e.active}),oe(v,"aria-pressed",t(f).code===e.active),E=ce(v,"",E,k),z(w,t(f).label),z(g,t(f).count)},[()=>({"--chip-ink":De(t(f).color),"--chip-wash":je(t(f).color)})]),H("click",v,()=>e.onselect(t(f).code)),m(s,v)});var i=p(r,2);let l;var d=u(i);ie(d,{name:"search",size:"1.75rem"}),N(()=>{l=te(i,1,"chip touch-target search-key svelte-atgty1",null,l,{active:e.searchFieldOpen}),oe(i,"aria-pressed",e.searchFieldOpen)}),H("click",i,function(...s){e.onopensearch?.apply(this,s)}),m(n,a),ae()}le(["click"]);var bt=y(''),mt=y('

'),yt=y('

');function ke(n,e){ne(e,!0);const a=T(e,"action",3,null);var r=yt(),i=u(r),l=u(i),d=u(l);ie(d,{name:"alert",size:"3rem"});var s=p(l,2),f=u(s),v=p(s,2),c=u(v),E=p(v,2);{var A=g=>{var k=bt(),O=u(k);N(()=>z(O,a().label)),H("click",k,function(...q){a().run?.apply(this,q)}),m(g,k)};B(E,g=>{a()!==null&&g(A)})}var w=p(E,2);{var C=g=>{var k=mt(),O=u(k);N(()=>z(O,e.code)),m(g,k)};B(w,g=>{e.code!==""&&g(C)})}N(()=>{z(f,e.title),z(c,e.detail)}),m(n,r),ae()}le(["click"]);const fe=34,xe=18,wt=.5,Et=.005,kt=1.15,Le=90,me=100;function xt(n,e){return Math.max(1,Math.floor(e/(n*kt)))}function At(n,e,a,r=Le){const i=St(n,a);if(i.length===0||e<=0)return fe;const l=Pt(a),d=e*(1-Et);for(let s=fe;s>xe;s-=wt){const f=s/me;if(Tt(i,l,f,d)<=xt(s,r))return s}return xe}const It=/(?<=[-‐–—])(?!\d)/u;function St(n,e){const a=[];for(const r of n.split(/\s+/u).filter(i=>i.length>0)){const i=r.split(It).filter(l=>l.length>0);for(const[l,d]of i.entries())a.push({width:e(d),length:[...d].length,spaced:l===0})}return a}function Pt(n){return Math.max(0,n("a a")-n("aa"))}function Tt(n,e,a,r){let i=1,l=0;for(const d of n){const s=d.width*a,f=d.spaced?e*a:0;if(l>0&&l+f+s<=r){l+=f+s;continue}if(l>0&&(i++,l=0),s<=r){l=s;continue}i+=Nt(s,d.length,r)-1,l=r}return i}function Nt(n,e,a){const r=Math.max(1,Math.floor(a*e/n));return Math.ceil(e/r)}function zt(n,e){const r=document.createElement("canvas").getContext("2d");if(r===null||typeof r.measureText!="function")return null;r.font=`${e} ${me}px ${n}`;const i=new Map;return l=>{const d=i.get(l);if(d!==void 0)return d;const s=r.measureText(l).width;return i.set(l,s),s}}var Mt=y(''),Ct=y(''),qt=y(' '),jt=y(''),Dt=y('');function Lt(n,e){ne(e,!0);const a=T(e,"nameSizePx",3,fe),r=T(e,"categoryColor",3,"#8a867c"),i=T(e,"primaryCode",3,""),l=T(e,"tierAbbrev",19,()=>({})),d=T(e,"selected",3,!1),s=T(e,"rejected",3,!1),f=T(e,"busy",3,!1),v=T(e,"showPrice",3,!0);let c=D("");const E=h(()=>e.product.image_url!==""&&e.product.image_url!==t(c)),A=h(()=>g(e.product.name)),w=h(()=>je(r())),C=h(()=>De(r()));function g(S){const M=S.match(/\p{L}/u);return M===null?"·":M[0].toUpperCase()}var k=Dt();let O;var q=u(k);let X;var U=u(q);{var I=S=>{var M=Mt();N(()=>oe(M,"src",e.product.image_url)),Fe("error",M,()=>x(c,e.product.image_url,!0)),m(S,M)},W=S=>{var M=Ct();let Z;var J=u(M);N(()=>{Z=ce(M,"",Z,{color:t(C)}),z(J,t(A))}),m(S,M)};B(U,S=>{t(E)?S(I):S(W,-1)})}var F=p(q,2),K=u(F);let G;var Q=u(K),_=p(F,2);{var P=S=>{var M=jt();ve(M,21,()=>e.product.prices,Z=>Z.code,(Z,J)=>{var b=qt();let L;var j=u(b);let R;var ee=u(j),Y=p(j,2),$=u(Y),re=p(Y,2),se=u(re);N(de=>{L=te(b,1,"price svelte-1ctu6qg",null,L,{secondary:t(J).code!==i()}),R=te(j,1,"abbrev svelte-1ctu6qg",null,R,{hollow:t(J).code!==i()}),z(ee,l()[t(J).code]??""),z($,t(J).text),z(se,de)},[()=>e.product.price_suffix.trim()]),m(Z,b)}),m(S,M)};B(_,S=>{v()&&S(P)})}N(()=>{O=te(k,1,"tile touch-target svelte-1ctu6qg",null,O,{selected:d(),rejected:s()}),oe(k,"data-product-id",e.product.id),k.disabled=f(),X=ce(q,"",X,{background:t(w)}),G=ce(K,"",G,{"font-size":`${a()??""}px`}),z(Q,e.product.name)}),H("pointerdown",k,()=>e.onpick(e.product)),m(n,k),ae()}le(["pointerdown"]);var Rt=y('
'),Ot=y('

Chargement du catalogue…

',1),Wt=y('

'),Bt=y('

'),Ht=y('
'),Ft=y('
'),Ut=y('
');function Gt(n,e){ne(e,!0);const a=T(e,"colors",19,()=>({})),r=T(e,"primaryCode",3,""),i=T(e,"tierAbbrev",19,()=>({})),l=T(e,"selectedID",3,null),d=T(e,"rejectedID",3,null),s=T(e,"busyID",3,null),f=T(e,"showPrices",3,!0),v=T(e,"loading",3,!1),c=T(e,"emptyMessage",3,"Aucun produit ne correspond."),E=T(e,"emptyHint",3,""),A=230,w=8,C=26,g="'Inter Variable', system-ui, sans-serif",k="'Inter Variable'",O=16;let q=D(0),X=D(!1);ue(()=>{const b=document.fonts;b!==void 0&&b.load(`700 ${me}px ${k}`).catch(()=>[]).then(()=>{x(X,!0)})});const U=h(()=>(t(X),zt(g,700)));let I=D(null),W=D(0),F=D(0);const K=h(()=>{if(t(q)<=0)return 0;const b=Math.max(1,Math.floor((t(q)+w)/(A+w)));return(t(q)-(b-1)*w)/b-C}),G=h(()=>t(W)>0?t(W):t(K));ue(()=>{t(q),e.products.length;const b=t(I)?.querySelector(".name-box");x(W,b?.clientWidth??0,!0)});const Q=h(()=>{const b=new Map;if(t(U)===null||t(G)<=0)return b;const L=t(F)>0?t(F):Le;for(const j of e.products)b.set(j.id,At(j.name,t(G),t(U),L));return b});var _=Ut(),P=u(_),S=p(P,2);{var M=b=>{var L=Ot(),j=ze(L);ve(j,21,()=>({length:O}),Ue,(R,ee)=>{var Y=Rt();m(R,Y)}),m(b,L)},Z=b=>{var L=Bt(),j=u(L),R=u(j),ee=p(j,2);{var Y=$=>{var re=Wt(),se=u(re);N(()=>z(se,E())),m($,re)};B(ee,$=>{E()!==""&&$(Y)})}N(()=>z(R,c())),m(b,L)},J=b=>{var L=Ft();ve(L,21,()=>e.products,j=>j.id,(j,R)=>{var ee=Ht(),Y=u(ee);{let $=h(()=>t(Q).get(t(R).id)??fe),re=h(()=>t(R).id===l()),se=h(()=>t(R).id===d()),de=h(()=>t(R).id===s());Lt(Y,{get product(){return t(R)},get nameSizePx(){return t($)},get categoryColor(){return a()[t(R).category_code]},get primaryCode(){return r()},get tierAbbrev(){return i()},get selected(){return t(re)},get rejected(){return t(se)},get busy(){return t(de)},get showPrice(){return f()},get onpick(){return e.onpick}})}m(j,ee)}),N(()=>oe(L,"data-tile-count",e.products.length)),m(b,L)};B(S,b=>{v()?b(M):e.products.length===0?b(Z,1):b(J,-1)})}Ce(_,b=>x(I,b),()=>t(I)),we(P,"clientHeight",b=>x(F,b)),we(_,"clientWidth",b=>x(q,b)),m(n,_),ae()}var Vt=y(''),Xt=y('
');function Kt(n,e){ne(e,!0);let a=D(null);ue(()=>{t(a)?.focus()});var r=Xt(),i=u(r);ie(i,{name:"search",size:"1.75rem"});var l=p(i,2);Ce(l,c=>x(a,c),()=>t(a));var d=p(l,2);{var s=c=>{var E=Vt();H("click",E,function(...A){e.onclose?.apply(this,A)}),m(c,E)};B(d,c=>{e.query!==""&&c(s)})}var f=p(d,2),v=u(f);ie(v,{name:"close",size:"1.5rem"}),N(()=>Ge(l,e.query)),H("input",l,c=>e.onquery(c.currentTarget.value)),H("keydown",l,c=>{c.key==="Escape"?(c.preventDefault(),e.onclose()):c.key==="Enter"&&(c.preventDefault(),e.onenter())}),H("click",f,function(...c){e.onclose?.apply(this,c)}),m(n,r),ae()}le(["input","keydown","click"]);function Zt(n){if(n==="")return"";const e=new Date(n);if(Number.isNaN(e.getTime()))return"";const a=i=>String(i).padStart(2,"0");return`${`${a(e.getDate())}/${a(e.getMonth()+1)}/${e.getFullYear()}`} ${a(e.getHours())}:${a(e.getMinutes())}:${a(e.getSeconds())}`}function Jt(n){return n.mode==="by_unit"?`${n.product_name} ${n.quantity} ${n.quantity>1?"unités":"unité"}`:`${n.product_name} ${n.net_text} kg`}var Yt=y('Catalogue du '),Qt=y('Catalogue en attente'),$t=y(' '),en=y('aucune pour le moment'),tn=y(' '),nn=y(''),an=y('
Dernière étiquette
');function rn(n,e){ne(e,!0);const a=T(e,"catalogAt",3,""),r=T(e,"productCount",3,0),i=T(e,"appVersion",3,""),l=h(()=>[r()>0?`${r()} produits pesables`:"",i()!==""?`application ${i()}`:""].filter(_=>_!=="").join(" · "));var d=an();let s;var f=u(d),v=u(f);{var c=_=>{var P=Yt(),S=p(u(P)),M=u(S);N(()=>z(M,a())),m(_,P)},E=_=>{var P=Qt();m(_,P)};B(v,_=>{a()!==""?_(c):_(E,-1)})}var A=p(v,2);{var w=_=>{var P=$t(),S=u(P);N(()=>z(S,t(l))),m(_,P)};B(A,_=>{t(l)!==""&&_(w)})}var C=p(f,4),g=p(u(C),2);{var k=_=>{var P=en();m(_,P)},O=_=>{var P=tn(),S=u(P);N(M=>z(S,M),[()=>Jt(e.label)]),m(_,P)};B(g,_=>{e.label===null?_(k):_(O,-1)})}var q=p(C,2);{var X=_=>{var P=nn();N(()=>P.disabled=!e.available),H("click",P,function(...S){e.onreprint?.apply(this,S)}),m(_,P)};B(q,_=>{e.label!==null&&_(X)})}var U=p(q,2),I=u(U);let W;var F=p(I,2),K=u(F),G=p(U,2),Q=u(G);ie(Q,{name:"settings",size:"1.75rem"}),N(()=>{s=te(d,1,"bar svelte-161y12f",null,s,{live:e.label!==null}),W=te(I,1,"dot svelte-161y12f",null,W,{fault:!e.healthy}),oe(I,"aria-label",e.healthy?"Matériel disponible":"Matériel indisponible"),z(K,e.healthy?"Balance et imprimante disponibles":"Matériel indisponible")}),H("click",G,function(..._){e.onadmin?.apply(this,_)}),m(n,d),ae()}le(["click"]);var sn=y(''),on=y('

Tare g

');function ln(n,e){ne(e,!0);const a=["1","2","3","4","5","6","7","8","9","0"];var r=on(),i=u(r),l=p(u(i),2),d=u(l),s=p(i,2);ve(s,20,()=>a,w=>w,(w,C)=>{var g=sn(),k=u(g);N(()=>z(k,C)),H("click",g,()=>e.ondigit(C)),m(w,g)});var f=p(s,2),v=u(f),c=p(v,2),E=p(c,2),A=u(E);ie(A,{name:"check",size:"1.5rem"}),N(()=>z(d,e.grams===""?"0":e.grams)),H("click",v,function(...w){e.onclear?.apply(this,w)}),H("click",c,function(...w){e.oncancel?.apply(this,w)}),H("click",E,function(...w){e.onconfirm?.apply(this,w)}),m(n,r),ae()}le(["click"]);function cn(n){return n===null||n.state==="initializing"?!1:n.scale.connected&&n.printer.health!=="faulted"}const Ae=1500,un=2e3,Ie=5e3;function dn(n,e){return e?n>=Ie?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!0}:n>=un?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!1}:n>=Ae?{showWeight:!1,banner:"",mustReconnect:!1}:{showWeight:!0,banner:"",mustReconnect:!1}:{showWeight:n=Ie}}const vn=250,fn={show_grid_prices:!0,idle_timeout_s:45,reprint_window_s:60,sound:!0};class gn{#e=D(null);get state(){return t(this.#e)}set state(e){x(this.#e,e,!0)}#t=D(null);get catalog(){return t(this.#t)}set catalog(e){x(this.#t,e,!0)}#n=D(Me({showWeight:!0,banner:"",mustReconnect:!1}));get link(){return t(this.#n)}set link(e){x(this.#n,e,!0)}#r=D("");get catalogError(){return t(this.#r)}set catalogError(e){x(this.#r,e,!0)}#s=null;#i=null;#o=Date.now();#a=!1;#l=!1;get presentation(){return this.catalog?.presentation??fn}start(){this.#u(),this.#c(),this.#i=setInterval(()=>this.#v(),vn)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null,this.#s?.close(),this.#s=null,this.#a=!1}#c(){const e=new EventSource("/api/v1/stream");this.#s=e,e.onopen=()=>{this.#a=!0,this.#o=Date.now()},e.addEventListener("state",a=>this.#d(a)),e.onerror=()=>{this.#a=!1}}#d(e){this.#o=Date.now(),this.#a=!0;const a=JSON.parse(e.data);this.state=a,this.catalog!==null&&a.catalog_count!==this.catalog.product_count&&this.#u()}#v(){const e=dn(Date.now()-this.#o,this.#a);this.link=e,e.mustReconnect&&(this.#s?.close(),this.#o=Date.now(),this.#c())}async#u(){if(!this.#l){this.#l=!0;try{this.catalog=await Ve(),this.catalogError=""}catch{this.catalog===null&&(this.catalogError="Catalogue indisponible. Prévenez un responsable.")}finally{this.#l=!1}}}}const Se="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Pe(){let n=Date.now();const e=new Array(26);for(let r=9;r>=0;r--)e[r]=Se[n%32],n=Math.floor(n/32);const a=new Uint8Array(16);crypto.getRandomValues(a);for(let r=0;r<16;r++)e[10+r]=Se[a[r]%32];return e.join("")}var hn=y('
');function pn(n,e){ne(e,!0);const a=new gn;let r=D(Me(Xe)),i=D(""),l=D(!1);const d=h(()=>t(i)!==""||t(l));let s=D(null),f=D(-1),v=D(null);const c=h(()=>a.catalog),E=h(()=>t(c)===null?[]:Qe(t(c))),A=h(()=>t(c)===null?[]:Je(t(c))),w=h(()=>Ze(t(E),t(r),t(i))),C=h(()=>{const o={};for(const V of t(c)?.categories??[])o[V.code]=V.color;return o}),g=h(()=>a.state),k=h(()=>a.presentation),O=h(()=>cn(t(g))),q=h(()=>{if(a.catalogError!=="")return{message:a.catalogError,hint:"Le poste réessaie tout seul."};if(t(E).length>0)return{message:"Aucun produit ne correspond.",hint:"Effacez des lettres ou changez de rayon."};const o=t(g)?.station??0;return{message:o>0?`Catalogue vide. En attente du fichier flv_${o}.csv.`:"Catalogue vide. En attente du fichier du poste.",hint:"Prévenez un responsable : aucun produit ne peut être pesé."}}),X=h(()=>t(g)?.state==="rejected"?t(g).product?.id??null:null),U=h(()=>t(g)?.state==="faulted"||t(g)?.state==="out_of_service"),I=["product_armed","weight_present","weight_stable","awaiting_stability","validating","printing"],W=h(()=>t(g)!==null&&I.includes(t(g).state)?t(g).product?.id??null:null),F=h(()=>Zt(t(c)?.updated_at??""));ue(()=>(a.start(),()=>a.stop())),ue(()=>{const o=a.state?.revision??-1;t(s)!==null&&o>t(f)&&(x(s,null),x(f,-1))});async function K(o){if(t(s)===null){x(s,o.id,!0),x(f,a.state?.revision??-1,!0);try{await Ke({product_id:o.id,tare_g:a.state?.weight.tare_g??0,units:1,manual_weight_g:0,seen_weight_g:a.state?.weight.gross_g??0,measurement_seq:a.state?.weight.seq??0,key:Pe()})}catch{x(s,null)}}}async function G(){const o=a.state?.reprint.job_id;o===void 0||o===""||await Ye(o,Pe())}function Q(){x(i,""),x(l,!1)}function _(){x(l,!0)}function P(){const o=t(w).length===1?t(w)[0]:void 0;o!==void 0&&K(o)}function S(o){if(!(o.metaKey||o.ctrlKey||o.altKey)&&!(o.target instanceof HTMLElement&&o.target.tagName==="INPUT")&&!(t(v)!==null||t(U))){if(o.key==="Escape"){o.preventDefault(),Q();return}if(o.key==="Backspace"){o.preventDefault(),x(i,t(i).slice(0,-1),!0);return}if(o.key==="Enter"){P();return}o.key.length===1&&/[a-zA-Z0-9 ]/.test(o.key)&&(o.key===" "&&t(i)===""||(o.preventDefault(),x(i,t(i)+o.key)))}}ue(()=>(window.addEventListener("keydown",S),()=>window.removeEventListener("keydown",S)));async function M(){(await st(()=>import("./mount-2bHHy8nk.js"),__vite__mapDeps([0,1,2,3]))).mountAdmin(document.body)}var Z=hn(),J=u(Z);{let o=h(()=>t(v)!==null);ct(J,{get snapshot(){return t(g)},get showWeight(){return a.link.showWeight},get linkBanner(){return a.link.banner},get taring(){return t(o)},ontare:()=>x(v,"")})}var b=p(J,2);{var L=o=>{ln(o,{get grams(){return t(v)},ondigit:V=>x(v,(t(v)??"")+V),onclear:()=>x(v,""),oncancel:()=>x(v,null),onconfirm:()=>x(v,null)})};B(b,o=>{t(v)!==null&&o(L)})}var j=p(b,2);{let o=h(()=>t(c)?.pricing.primary_code??""),V=h(()=>Object.fromEntries((t(c)?.pricing.tiers??[]).map(ye=>[ye.code,ye.abbrev]))),ge=h(()=>t(c)===null&&a.catalogError==="");Gt(j,{get products(){return t(w)},get colors(){return t(C)},get primaryCode(){return t(o)},get tierAbbrev(){return t(V)},get selectedID(){return t(W)},get rejectedID(){return t(X)},get busyID(){return t(s)},get showPrices(){return t(k).show_grid_prices},get loading(){return t(ge)},get emptyMessage(){return t(q).message},get emptyHint(){return t(q).hint},onpick:K})}var R=p(j,2);{var ee=o=>{Kt(o,{get query(){return t(i)},onquery:V=>x(i,V,!0),onclose:Q,onenter:P})};B(R,o=>{t(d)&&o(ee)})}var Y=p(R,2);_t(Y,{get chips(){return t(A)},get active(){return t(r)},get searchFieldOpen(){return t(d)},onselect:o=>x(r,o,!0),onopensearch:_});var $=p(Y,2);{let o=h(()=>t(g)?.last_label??null),V=h(()=>t(g)?.reprint.available??!1),ge=h(()=>t(c)?.app_version??"");rn($,{get label(){return t(o)},get available(){return t(V)},get catalogAt(){return t(F)},get productCount(){return t(E).length},get appVersion(){return t(ge)},get healthy(){return t(O)},onreprint:G,onadmin:M})}var re=p($,2);{var se=o=>{{let V=h(()=>t(g).message?.text??"Prévenez un responsable.");ke(o,{title:"Poste indisponible",get detail(){return t(V)},get code(){return t(g).fault_code},action:{label:"J’ai compris",run:()=>{$e()}}})}},de=o=>{ke(o,{title:"Poste hors service",detail:"Ce poste ne peut pas peser. Prévenez un responsable.",get code(){return t(g).fault_code}})};B(re,o=>{t(g)?.state==="faulted"?o(se):t(g)?.state==="out_of_service"&&o(de,1)})}m(n,Z),ae()}const _n=5;mn();document.addEventListener("contextmenu",n=>{bn(n.target)||n.preventDefault()});document.addEventListener("dragstart",n=>n.preventDefault());et(pn,{target:document.getElementById("app")});function bn(n){return n instanceof Element&&n.closest("[data-admin]")!==null}function mn(){let n=!1;const e=(a,r)=>{n||(n=!0,tt(a,r),yn(),setTimeout(()=>location.reload(),_n*1e3))};window.addEventListener("error",a=>e(a.message,a.error?.stack??"")),window.addEventListener("unhandledrejection",a=>e(String(a.reason),a.reason?.stack??""))}function yn(){const n=document.createElement("div");n.className="fatal",n.append(_e("h1","Une erreur est survenue"),_e("p","L’écran va se recharger tout seul."),_e("p","ERR-UI-01","code")),document.body.appendChild(n)}function _e(n,e,a=""){const r=document.createElement(n);return r.textContent=e,a!==""&&(r.className=a),r} diff --git a/internal/web/dist/assets/mount-2bHHy8nk.js b/internal/web/dist/assets/mount-2bHHy8nk.js new file mode 100644 index 0000000..6b6da3d --- /dev/null +++ b/internal/web/dist/assets/mount-2bHHy8nk.js @@ -0,0 +1,93 @@ +import{R as ar,T as rr,U as nr,V as xa,W as Yt,e as sr,X as lr,Y as ir,Z as or,_ as ur,u as pa,r as Fa,$ as cr,c as Ma,B as aa,A as $,k as e,z as v,F as Lt,d as At,b as vt,s as r,i as m,t as p,n as Ht,l as Be,o as i,f as tt,g as l,v as a,j as u,p as Tt,I as dr,h as Ct,q as o,w as X,a0 as Ea,E as na,x as rt,G as Ba,a1 as ua,y as Qt,a2 as Dt,a3 as yt,a4 as sa,C as fa,K as vr,H as pr,P as fr,a5 as gr}from"./app-Dn-BjSsJ.js";function Va(n){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function mr(n,t,...s){var c=new nr(n);ar(()=>{const h=t()??null;c.ensure(h,h&&(x=>h(x,...s)))},rr)}function Wa(n,t,s=!1){if(n.multiple){if(t==null)return;if(!lr(t))return ir();for(var c of n.options)c.selected=t.includes(la(c));return}for(c of n.options){var h=la(c);if(or(h,t)){c.selected=!0;return}}(!s||t!==void 0)&&(n.selectedIndex=-1)}function hr(n){var t=new MutationObserver(()=>{"__value"in n&&Wa(n,n.__value)});t.observe(n,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),ur(()=>{t.disconnect()})}function _r(n,t,s=t){var c=new WeakSet,h=!0;xa(n,"change",x=>{var O=x?"[selected]":":checked",A;if(n.multiple)A=[].map.call(n.querySelectorAll(O),la);else{var T=n.querySelector(O)??n.querySelector("option:not([disabled])");A=T&&la(T)}s(A),n.__value=A,Yt!==null&&c.add(Yt)}),sr(()=>{var x=t();if(n===document.activeElement){var O=Yt;if(c.has(O))return}if(Wa(n,x,h),h&&x===void 0){var A=n.querySelector(":checked");A!==null&&(x=la(A),s(x))}n.__value=x,h=!1}),hr(n)}function la(n){return"__value"in n?n.__value:n.value}function ia(n,t,s=t){var c=new WeakSet;xa(n,"input",async h=>{var x=h?n.defaultValue:n.value;if(x=ma(n)?ha(x):x,s(x),Yt!==null&&c.add(Yt),await cr(),x!==(x=t())){var O=n.selectionStart,A=n.selectionEnd,T=n.value.length;if(n.value=x??"",A!==null){var H=n.value.length;O===A&&A===T&&H>T?(n.selectionStart=H,n.selectionEnd=H):(n.selectionStart=O,n.selectionEnd=Math.min(A,H))}}}),pa(t)==null&&n.value&&(s(ma(n)?ha(n.value):n.value),Yt!==null&&c.add(Yt)),Fa(()=>{var h=t();if(n===document.activeElement){var x=Yt;if(c.has(x))return}ma(n)&&h===ha(n.value)||n.type==="date"&&!h&&!n.value||h!==n.value&&(n.value=h??"")})}function za(n,t,s=t){xa(n,"change",c=>{var h=c?n.defaultChecked:n.checked;s(h)}),pa(t)==null&&s(n.checked),Fa(()=>{var c=t();n.checked=!!c})}function ma(n){var t=n.type;return t==="number"||t==="range"}function ha(n){return n===""?null:+n}function ka(n){Ma===null&&Va(),aa(()=>{const t=pa(n);if(typeof t=="function")return t})}function br(n){Ma===null&&Va(),ka(()=>()=>pa(n))}class It extends Error{constructor(t,s,c="",h=[]){super(s),this.status=t,this.code=c,this.faults=h,this.name="AdminError"}status;code;faults;get needsPassword(){return this.status===401}}function wr(){return Jt("/admin/api/health")}function yr(){return ht("/admin/api/troubleshooting/reprint",{})}function xr(){return ht("/admin/api/troubleshooting/reload-catalog",{})}function kr(n){return ht("/admin/api/troubleshooting/manual-entry",{on:n})}function qr(){return ht("/admin/api/troubleshooting/roll-changed",{})}function Sr(n){return ht("/admin/api/troubleshooting/fallback-printer",{on:n})}function Lr(){return ht("/admin/api/troubleshooting/test-scale",{})}function Er(){return ht("/admin/api/troubleshooting/test-printer",{})}function zr(){return ht("/admin/api/troubleshooting/test-label",{})}async function Ga(n){const t=new FormData;t.append("file",n,n.name);const s=await fetch("/admin/api/catalog/import",{method:"POST",body:t});return Sa(s,"POST /admin/api/catalog/import")}const jr="/admin/api/diagnostic.zip";function Pr(n){return ht("/admin/api/session",{password:n})}async function Tr(){await fetch("/admin/api/session",{method:"DELETE"})}function Cr(n,t){return ht("/admin/api/session/recovery",{code:n,password:t})}function qa(){return Jt("/admin/api/config")}function Or(n){return Ja("PUT","/admin/api/config",n)}function Rr(){return ht("/admin/api/config/confirm",{})}async function Ar(){return(await Jt("/admin/api/config/versions")).versions}function Nr(n){return ht("/admin/api/config/restore",{version:n})}async function $r(){return(await Jt("/admin/api/ports")).ports}async function Dr(){return(await Jt("/admin/api/printers")).printers}async function Ir(){return(await ht("/admin/api/printers/discover",{})).printers}function Ur(n){return ht("/admin/api/scale/detect",{port:n})}async function ja(n,t){return(await ht("/admin/api/scale/capture",{port:n,seconds:t})).frames}function Ha(n){return ht("/admin/api/printer/test?what="+n,{})}function Fr(n,t,s,c){const h=new URLSearchParams({template:n,demo:t?"1":"0",dual:s?"1":"0"});return h.set("t",String(c)),"/admin/api/label/preview.png?"+h.toString()}async function Mr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/journal?"+t)).weighings}function Br(n){return"/admin/api/journal/export.csv?"+new URLSearchParams(n).toString()}async function Vr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/technical?"+t)).entries}function Wr(n){const t=n===void 0?"":"?id="+String(n);return Jt("/admin/api/imports"+t)}function Gr(){return ht("/admin/api/catalog/reload",{})}function Hr(){return ht("/admin/api/catalog/forget-quarantine",{})}function Pa(n,t){return ht("/admin/api/products/"+encodeURIComponent(n)+"/decision",t)}function Jr(n){return ht("/admin/api/replay",{frame:n})}async function Jt(n){const t=await fetch(n,{headers:{accept:"application/json"}});return Sa(t,"GET "+n)}function ht(n,t){return Ja("POST",n,t)}async function Ja(n,t,s){const c=await fetch(t,{method:n,headers:{"content-type":"application/json"},body:JSON.stringify(s)});return Sa(c,n+" "+t)}async function Sa(n,t){const s=await n.text();if(!n.ok){const c=Ka(s);throw new It(n.status,Xr(s,t,n.status)+Kr(n),c?.code??"",c?.faults??[])}if(s!=="")return JSON.parse(s)}function Kr(n){if(n.status!==429)return"";const t=Number(n.headers.get("Retry-After")??"");if(!Number.isFinite(t)||t<=0)return"";const s=Math.ceil(t/60);return s>1?` Réessayez dans ${String(s)} minutes.`:" Réessayez dans une minute."}function Xr(n,t,s){const c=Ka(n);return c?.message!==void 0&&c.message!==""?c.message:`${t} a répondu ${String(s)}.`}function Ka(n){try{return JSON.parse(n)}catch{return null}}function Yr(){return Jt("/admin/api/update")}function Qr(){return ht("/admin/api/update/check",{})}function Zr(n){return ht("/admin/api/update/apply",{version:n})}class en{constructor(t){this.admin=t}admin;#e=$(null);get config(){return e(this.#e)}set config(t){v(this.#e,t,!0)}#a=$("");get fingerprint(){return e(this.#a)}set fingerprint(t){v(this.#a,t,!0)}#r=$(Lt([]));get retired(){return e(this.#r)}set retired(t){v(this.#r,t,!0)}#n=$(null);get pending(){return e(this.#n)}set pending(t){v(this.#n,t,!0)}#s=$(Lt([]));get faults(){return e(this.#s)}set faults(t){v(this.#s,t,!0)}#l=$(!1);get dirty(){return e(this.#l)}set dirty(t){v(this.#l,t,!0)}async load(){const t=await this.admin.load(()=>qa());t!==null&&(this.config=t.config,this.fingerprint=t.config_fingerprint,this.retired=t.retired_keys??[],this.pending=t.pending_confirmation,this.faults=[],this.dirty=!1)}value(t){let s=this.config;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}text(t){const s=this.value(t);return s==null?"":String(s)}number(t){const s=this.value(t);return typeof s=="number"?s:Number(s??0)}flag(t){return this.value(t)===!0}set(t,s){if(this.config===null)return;const c=t.split("."),h=c.pop();if(h===void 0)return;let x=this.config;for(const O of c){const A=x[O];(A===null||typeof A!="object")&&(x[O]={}),x=x[O]}x[h]=s,this.dirty=!0}unset(t){const s=t.split("."),c=s.pop();if(c===void 0||this.config===null)return!1;let h=this.config;for(const x of s){if(h===null||typeof h!="object")return!1;h=h[x]}return h===null||typeof h!="object"?!1:(delete h[c],this.dirty=!0,!0)}async save(){if(this.config===null)return!1;this.faults=[];const t=this.config;let s;try{s=await Or(t)}catch(c){if(Ta(c))throw c;return this.admin.report(c),this.faults=tn(this.admin),!1}return this.config=s.config,this.fingerprint=s.config_fingerprint,this.retired=s.retired_keys??[],this.pending=s.pending_confirmation,this.dirty=!1,!0}async confirm(){try{await Rr()}catch(t){if(Ta(t))throw t;this.admin.report(t);return}this.admin.notice="La configuration est confirmée.",this.pending=null,await this.admin.refresh()}dropRetired(t){if(t.includes("[")){this.admin.actionError=`« ${t} » est logée dans un tableau : cet écran ne sait pas la retirer de là. Modifiez le fichier de configuration lui-même.`;return}this.unset(t)&&(this.retired=this.retired.filter(s=>s!==t))}}function tn(n){return n.lastFaults}function Ta(n){return n instanceof It&&(n.status===401||n.status===409)}const an={"station.number":"Numéro du poste","station.name":"Nom du poste","station.coop":"Nom de la coopérative","network.listen":"Adresse d’écoute","network.admin_on_lan":"Administration accessible depuis le réseau","ui.language":"Langue","ui.sound":"Son","ui.idle_timeout_s":"Retour à l’accueil après (secondes)","ui.reprint_window_s":"Réimpression possible pendant (secondes)","ui.show_grid_prices":"Afficher les prix sur les tuiles","scale.type":"Protocole de la balance","scale.present":"Ce poste a une balance","scale.manual_entry_allowed":"Saisie manuelle autorisée","scale.degrade_after_s":"Passer en mode dégradé après (secondes)","scale.options.port":"Port série","scale.options.baud":"Vitesse (bauds)","scale.options.bits":"Bits de données","scale.options.parity":"Parité","scale.options.stop":"Bits d’arrêt","scale.options.backoff_min_ms":"Attente minimale avant réessai (ms)","scale.options.backoff_max_ms":"Attente maximale avant réessai (ms)","printer.type":"Pilote d’impression","printer.template":"Gabarit d’étiquette","printer.options.transport":"Transport","printer.options.queue":"File d’impression","printer.options.path":"Fichier de périphérique","printer.options.address":"Adresse réseau","printer.options.darkness":"Noircissement","printer.options.speed":"Vitesse d’impression","printer.options.offset_x":"Décalage horizontal (dots)","printer.options.offset_y":"Décalage vertical (dots)","printer.options.invert_bits":"Inverser les points","printer.options.copies":"Exemplaires","printer.options.roll_capacity":"Étiquettes par rouleau","pricing.amount_rounding":"Arrondi du montant","pricing.unit_price_rounding":"Arrondi du prix au kilo","pricing.primary_code":"Tarif principal","pricing.reference_code":"Tarif de référence","barcode.verify_reference_check_digit":"Vérifier la clé de contrôle","limits.empty_max_g":"Plateau considéré vide en dessous de (g)","limits.basket_check_enabled":"Vérifier la présence du panier","limits.basket_min_g":"Poids du panier, borne basse (g)","limits.basket_max_g":"Poids du panier, borne haute (g)","limits.min_weight_g":"Poids minimum accepté","limits.max_weight_g":"Poids maximum accepté","limits.max_tare_g":"Tare maximale","limits.min_units":"Unités minimum","limits.max_units":"Unités maximum","limits.max_amount_cents":"Montant maximum (centimes)","stability.mode":"Exigence de stabilité","stability.min_duration_ms":"Durée de stabilité (ms)","stability.tolerance_g":"Tolérance de stabilité (g)","stability.timeout_ms":"Délai d’attente de stabilité (ms)","stability.on_timeout":"Au bout du délai","stability.min_latch_rate":"Taux d’accroche minimal","stability.latch_rate_window_ms":"Fenêtre de mesure du taux (ms)","stability.expiry_floor_ms":"Péremption, plancher (ms)","stability.expiry_ceiling_ms":"Péremption, plafond (ms)","stability.expiry_factor":"Péremption, facteur","catalog.type":"Où le poste va chercher le catalogue","catalog.options.directory":"Répertoire surveillé","catalog.options.url":"Adresse du serveur","catalog.options.username":"Compte","catalog.options.password":"Mot de passe","catalog.options.separator":"Séparateur du CSV","catalog.options.poll_interval_s":"Vérifier toutes les (secondes)","catalog.options.stable_polls":"Vérifications avant lecture","catalog.options.max_file_size_mb":"Taille maximale du fichier (Mo)","catalog.options.max_image_size_kb":"Taille maximale d’une image (Ko)","catalog.options.min_readable_ratio":"Part minimale de lignes lisibles","catalog.options.max_weighable_drop":"Baisse maximale des produits pesables","catalog.options.max_archives":"Archives conservées","catalog.options.archive_days":"Archives conservées (jours)","catalog.options.failures_before_reject":"Échecs avant mise en quarantaine","catalog.images.source":"Origine des photos","catalog.images.path":"Répertoire des photos","catalog.fallback_category":"Rayon par défaut","journal.max_rows":"Pesées conservées","journal.max_days":"Pesées conservées (jours)","journal.max_technical":"Événements techniques conservés","admin.session_minutes":"Durée d’une session (minutes)","admin.attempts_per_minute":"Tentatives par minute","maintenance.weekly_integrity_check":"Contrôle d’intégrité hebdomadaire","maintenance.disk_alert_mb":"Alerte disque en dessous de (Mo)"};function Ft(n){return an[n]??n}const rn={station:"l’identité du poste",network:"le réseau",ui:"l’écran client",scale:"la balance",printer:"l’imprimante",pricing:"les tarifs",barcode:"le code-barres",limits:"les garde-fous",stability:"la stabilité",catalog:"le catalogue",journal:"le journal",maintenance:"la maintenance"};function nn(n){return rn[n]??n}const sn={scale:"balance",printer:"imprimante",catalog:"catalogue",ui:"écran",config:"configuration",http:"réseau",system:"système"};function Xa(n){return sn[n]??"origine inconnue"}const ba="openscale.admin.preferences",Ya="technical";class ln{#e=$(Lt(on()));get showTechnicalNames(){return e(this.#e)}set showTechnicalNames(t){v(this.#e,t,!0)}toggleTechnicalNames(){this.showTechnicalNames=!this.showTechnicalNames,un(this.showTechnicalNames)}}function on(){try{return globalThis.localStorage?.getItem(ba)===Ya}catch{return!1}}function un(n){try{n?globalThis.localStorage?.setItem(ba,Ya):globalThis.localStorage?.removeItem(ba)}catch{}}const Gt=new ln,cn=3e3,dn=["hardware","label","rules","catalog","journal","station","update"];function Ca(n){return dn.includes(n)}function vn(n){return n instanceof It&&(n.status===401||n.status===409)}class pn{constructor(t=cn){this.periodMs=t}periodMs;#e=$(null);get health(){return e(this.#e)}set health(t){v(this.#e,t,!0)}#a=$("dashboard");get page(){return e(this.#a)}set page(t){v(this.#a,t,!0)}#r=$(!1);get expert(){return e(this.#r)}set expert(t){v(this.#r,t,!0)}#n=$("");get linkError(){return e(this.#n)}set linkError(t){v(this.#n,t,!0)}#s=$("");get actionError(){return e(this.#s)}set actionError(t){v(this.#s,t,!0)}#l=$(!1);get needsFirstPassword(){return e(this.#l)}set needsFirstPassword(t){v(this.#l,t,!0)}#d=$("");get notice(){return e(this.#d)}set notice(t){v(this.#d,t,!0)}#v=$(!1);get busy(){return e(this.#v)}set busy(t){v(this.#v,t,!0)}#p=$(Lt([]));get lastFaults(){return e(this.#p)}set lastFaults(t){v(this.#p,t,!0)}#f=$(null);get pending(){return e(this.#f)}set pending(t){v(this.#f,t,!0)}#i=null;#o=null;start(){this.refresh(),this.#i=setInterval(()=>{this.refresh()},this.periodMs)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null}async refresh(){try{this.health=await wr(),this.linkError=""}catch(t){this.linkError=Oa(t)}}#u(){this.busy=!0,this.notice="",this.actionError="",this.needsFirstPassword=!1}#t(t){this.actionError=Oa(t),this.needsFirstPassword=t instanceof It&&t.status===409,this.#m(t)}async run(t){this.#u();try{const s=await t();this.notice=s.message}catch(s){this.#t(s)}finally{this.busy=!1}await this.refresh()}async login(t){this.#u();try{return await Pr(t),this.expert=!0,this.notice="Session d’administration ouverte.",!0}catch(s){return this.expert=!1,this.#t(s),!1}finally{this.busy=!1}}async recover(t,s){this.#u();try{const c=await Cr(t,s);return this.expert=!0,this.notice=c.warning??"Le mot de passe est remplacé et la session est ouverte.",!0}catch(c){return this.#t(c),!1}finally{this.busy=!1}}async protect(t){try{return await t()}catch(s){if(!vn(s))return this.#t(s),null;if(!await this.#g(s))return null;try{return await t()}catch(c){return this.#t(c),null}}}#g(t){return this.pending={kind:t.status===409?"first-password":"password",message:t.message},new Promise(s=>{this.#o=s})}async answerPassword(t){await this.login(t)&&this.#c(!0)}async answerRecovery(t,s){await this.recover(t,s)&&this.#c(!0)}cancelPassword(){this.#c(!1)}#c(t){this.pending=null,this.notice="";const s=this.#o;this.#o=null,s?.(t)}async logout(){await Tr(),this.expert=!1,this.page="dashboard",this.notice="Session d’administration fermée."}open(t){this.actionError="",this.notice="",this.needsFirstPassword=!1,this.page=t}async load(t){try{const s=await t();return this.actionError="",this.lastFaults=[],s}catch(s){return this.lastFaults=s instanceof It?s.faults:[],this.#t(s),null}}report(t){this.lastFaults=t instanceof It?t.faults:[],this.#t(t)}#m(t){t instanceof It&&t.needsPassword&&(this.expert=!1)}}function Oa(n){return n instanceof It?n.message:n instanceof Error?"Le poste n’a pas répondu : "+n.message:"Le poste n’a pas répondu."}var fn=u('clé'),gn=u('');function at(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"busy",3,!1),h=vt(t,"disabled",3,!1),x=vt(t,"protected",3,!1),O=vt(t,"act",3,void 0);var A=gn();let T;var H=a(A),B=r(H);{var se=_e=>{var ye=fn();l(_e,ye)};m(B,_e=>{x()&&_e(se)})}p(()=>{T=Ht(A,1,`act ${s()??""}`,"svelte-5tpq0o",T,{"touch-target":s()==="destructive",busy:c()}),Be(A,"data-kind",s()),Be(A,"data-act",O()),A.disabled=h()||c(),i(H,`${(c()?"En cours…":t.label)??""} `)}),tt("click",A,function(..._e){t.onrun?.apply(this,_e)}),l(n,A)}At(["click"]);var mn=u(`

Le code de secours a été tiré à l’installation de ce poste et imprimé + sur sa fiche, rangée dans le classeur du magasin. Il n’existe nulle part ailleurs.

`,1),hn=u(`

Oublié ? Le code de secours de la fiche d’installation en repose un. + Un responsable peut aussi le faire en ligne de commande, avec openscale config password.

`,1),_n=u('

'),bn=u('');function wn(n,t){Tt(t,!0);const s=o(()=>t.admin.pending?.kind==="first-password");let c=$(""),h=$("");const x=8,O=o(()=>e(s)?e(h).trim().length>0&&e(c).length>=x:e(c).length>0);async function A(){!e(O)||t.admin.busy||(e(s)?await t.admin.answerRecovery(e(h),e(c)):await t.admin.answerPassword(e(c)),v(c,""),v(h,""))}var T=bn(),H=a(T),B=a(H),se=a(B),_e=a(se);dr(_e,{name:"settings",size:"1.5rem"});var ye=r(se,2),ce=a(ye),j=r(B,2),V=a(j),le=r(j,2);{var re=E=>{var b=mn(),R=r(X(b),2),J=r(a(R),2);Ea(J);var be=r(R,2),xe=a(be);xe.textContent="Nouveau mot de passe — 8 caractères au moins";var Ve=r(xe,2);tt("keydown",J,Ae=>Ae.key==="Enter"&&void A()),ia(J,()=>e(h),Ae=>v(h,Ae)),tt("keydown",Ve,Ae=>Ae.key==="Enter"&&void A()),ia(Ve,()=>e(c),Ae=>v(c,Ae)),l(E,b)},W=E=>{var b=hn(),R=X(b),J=r(a(R),2);Ea(J),tt("keydown",J,be=>be.key==="Enter"&&void A()),ia(J,()=>e(c),be=>v(c,be)),l(E,b)};m(le,E=>{e(s)?E(re):E(W,-1)})}var D=r(le,2);{var Y=E=>{var b=_n(),R=a(b);p(()=>i(R,t.admin.actionError)),l(E,b)};m(D,E=>{t.admin.actionError!==""&&E(Y)})}var z=r(D,2),P=a(z);at(P,{label:"Annuler",onrun:()=>t.admin.cancelPassword()});var G=r(P,2);{let E=o(()=>e(s)?"Poser ce mot de passe":"Continuer"),b=o(()=>!e(O)||t.admin.busy);at(G,{kind:"write",get label(){return e(E)},get disabled(){return e(b)},onrun:()=>{A()}})}p(()=>{i(ce,e(s)?"Ce poste n’a pas encore de mot de passe":"Mot de passe d’administration"),i(V,t.admin.pending?.message??"")}),l(n,T),Ct()}At(["keydown"]);var yn=u(' '),xn=u(""),kn=u(""),qn=u('

'),Sn=u(" "),Ln=u('

'),En=u('
');function _t(n,t){Tt(t,!0);const s=vt(t,"hint",3,""),c=vt(t,"kind",3,"text"),h=vt(t,"disabled",3,!1),x=vt(t,"fault",3,""),O=vt(t,"allowed",19,()=>[]),A=o(()=>`field-${t.path.replace(/\./gu,"-")}`);var T=En();let H;var B=a(T),se=a(B),_e=a(se),ye=r(se,2);{var ce=z=>{var P=yn(),G=a(P);p(()=>i(G,t.path)),l(z,P)};m(ye,z=>{Gt.showTechnicalNames&&z(ce)})}var j=r(B,2),V=r(j,2);{var le=z=>{var P=kn();rt(P,20,O,G=>G,(G,E)=>{var b=xn(),R={};p(()=>{R!==(R=E)&&(b.value=(b.__value=E)??"")}),l(G,b)}),p(()=>Be(P,"id",e(A)+"-allowed")),l(z,P)};m(V,z=>{O().length>0&&z(le)})}var re=r(V,2);{var W=z=>{var P=qn(),G=a(P);p(()=>i(G,s())),l(z,P)};m(re,z=>{s()!==""&&z(W)})}var D=r(re,2);{var Y=z=>{var P=Ln(),G=a(P),E=r(G);{var b=R=>{var J=Sn(),be=a(J);p(xe=>i(be,`Valeurs acceptées : ${xe??""}.`),[()=>O().join(", ")]),l(R,J)};m(E,R=>{O().length>0&&R(b)})}p(()=>i(G,`${x()??""} `)),l(z,P)};m(D,z=>{x()!==""&&z(Y)})}p(()=>{H=Ht(T,1,"field svelte-1oilv5s",null,H,{refused:x()!==""}),Be(B,"for",e(A)),i(_e,t.label),Be(j,"id",e(A)),Be(j,"type",c()),na(j,t.value),j.disabled=h(),Be(j,"list",O().length>0?e(A)+"-allowed":void 0)}),tt("input",j,z=>t.onchange(z.currentTarget.value)),l(n,T),Ct()}At(["input"]);const Ra=1e3;function da(n){const t=La(n);return t===null?"":[ta(t.getDate()),ta(t.getMonth()+1),String(t.getFullYear())].join("/")}function Mt(n){const t=La(n);return t===null?"":`${da(n)} à ${ta(t.getHours())}:${ta(t.getMinutes())}`}function zn(n){const t=La(n);return t===null?"":[ta(t.getHours()),ta(t.getMinutes()),ta(t.getSeconds())].join(":")}function wa(n){const t=n/1e6;return t<1e3?`${Math.round(t)} Mo`:`${Qa(t/1e3)} Go`}function me(n){const t=String(Math.abs(Math.trunc(n))),s=[];for(let c=t.length;c>0;c-=3)s.unshift(t.slice(Math.max(0,c-3),c));return(n<0?"-":"")+s.join(" ")}function ca(n){return n0&&x.push({count:Pt(n.anomalies_count),label:"anomalies",note:"à corriger dans Odoo",link:Aa(n.anomalies_count)}),n.unit_mismatches_count>0){const O=n.unit_mismatches_count;x.push({count:"+ "+Pt(O),label:O===1?"unité divergente":"unités divergentes",note:"pesable, unité à corriger",link:Aa(O)})}return{headline:`Catalogue du ${da(n.occurred_at)} — ${Pt(s)} produits reçus`,lines:x,oneLine:Pn(n)}}function Pn(n){const t=[`${Pt(n.rows_read_count)} reçus`,`${Pt(n.weighable_count)} pesables`,`${Pt(n.not_weighable_count)} non pesables`,`${Pt(n.anomalies_count)} anomalies`],s=n.unit_mismatches_count;return s>0&&t.push(`${Pt(s)} ${s===1?"unité divergente":"unités divergentes"}`),t.join(" · ")}function Aa(n){return n===1?"voir la ligne":`voir les ${Pt(n)} lignes`}function Tn(n){return n.map(t=>`${Cn(t)} (${Pt(t.count)})`).join(", ")}function Cn(n){switch(n.code){case"PREPACKAGED_PRODUCT":return"préemballés";case"INTERNAL_CODE_NOT_WEIGHABLE":return n.value===""?"code interne":"code interne "+n.value;case"NO_BARCODE":return"sans code-barres";default:return n.code}}function Pt(n){return String(n)}var On=u(' '),Rn=u(''),An=u('
'),Nn=u('

');function Za(n,t){Tt(t,!0);const s=o(()=>jn(t.record,t.motives));var c=Nn(),h=a(c),x=a(h),O=r(h,2);rt(O,21,()=>e(s).lines,H=>H.label,(H,B)=>{var se=An(),_e=a(se),ye=a(_e),ce=r(_e,2),j=a(ce),V=a(j),le=r(j,2);{var re=Y=>{var z=On(),P=a(z);p(()=>i(P,e(B).note)),l(Y,z)};m(le,Y=>{e(B).note!==""&&Y(re)})}var W=r(le,2);{var D=Y=>{var z=Rn(),P=a(z);p(()=>i(P,e(B).link)),tt("click",z,()=>t.onshowrows(e(B).label==="anomalies"?"anomalies":"units")),l(Y,z)};m(W,Y=>{e(B).link!==""&&t.onshowrows!==void 0&&Y(D)})}p(()=>{Be(se,"data-row",e(B).label),i(ye,e(B).count),i(V,e(B).label)}),l(H,se)});var A=r(O,2),T=a(A);p(()=>{i(x,e(s).headline),i(T,e(s).oneLine)}),l(n,c),Ct()}At(["click"]);var $n=u('

'),Dn=u('

');function Qe(n,t){const s=vt(t,"note",3,"");var c=Dn(),h=a(c),x=a(h),O=r(h,2);{var A=H=>{var B=$n(),se=a(B);p(()=>i(se,s())),l(H,B)};m(O,H=>{s()!==""&&H(A)})}var T=r(O,2);mr(T,()=>t.children),p(()=>i(x,t.title)),l(n,c)}const _a=(n,t=sa,s=sa)=>{var c=Un(),h=a(c);rt(h,21,t,fa,(x,O)=>{var A=In(),T=a(A),H=a(T),B=r(T,2),se=a(B),_e=r(B,2),ye=a(_e),ce=r(_e,2),j=a(ce);p(V=>{i(H,`ligne ${V??""}`),i(se,e(O).product_id),i(ye,e(O).value),i(j,e(O).message)},[()=>me(e(O).csv_line)]),l(x,A)}),p(()=>Be(c,"data-rows",s())),l(n,c)};var In=u('
  • '),Un=u('
      '),Fn=u('

      Lecture des réglages du poste…

      '),Mn=u(`

      Le poste y cherche le fichier , et le supprime + une fois lu : c’est ce qui dit au producteur que la livraison est prise.

      `,1),Bn=u(`

      Sur un serveur WebDAV, le dépôt d’un fichier CSV depuis cet écran n’est plus + possible : le poste n’a plus de répertoire local où l’écrire. C’est le seul recours + du jour de la mise en service.

      `,1),Vn=u(`

      Ce poste ne déclare aucune source : choisissez-en une ci-dessus, sinon il n’ira + chercher aucun catalogue.

      `),Wn=u('
      ',1),Gn=u('

      Aucun import enregistré sur ce poste.

      '),Hn=u(`

      « Oublier la quarantaine » fait relire un fichier que le poste avait écarté : c’est le + seul geste de cette page qui puisse remettre en service un catalogue refusé.

      `,1),Jn=u('

      '),Kn=u(`

      Déposez ici le fichier clé

      Ce dépôt remplace toute la grille par le fichier apporté : il change ce que le poste + vend, et le mot de passe est donc demandé au moment du dépôt.

      `,1),Xn=u('

      '),Yn=u('

      Aucune anomalie sur le dernier import.

      '),Qn=u('

      ',1),Zn=u('

      '),es=u('

      Aucune unité divergente sur le dernier import.

      '),ts=u('

      ',1),as=u('

      '),rs=u('

      Aucun produit non pesable sur le dernier import.

      '),ns=u('

      ',1),ss=u('

      Aucun import enregistré : rien n’a encore pu être retiré.

      '),ls=u('

      Aucun produit retiré par le dernier import.

      '),is=u(`

      Ils restent enregistrés avec leur historique : une étiquette déjà collée reste + lisible en caisse, et un produit qui revient dans un prochain fichier retrouve sa + tuile. Ce poste n’en publie encore que le nombre — leurs noms se lisent dans Odoo, + en comparant avec l’export précédent.

      `,1),os=u('
    • '),us=u(`

        Un produit retiré de la grille ne se trouve plus ici : il se reprend dans + « Décisions en vigueur », plus bas.

        `,1),cs=u(`

        Sans motif, le poste refuse la décision. Le seul acte qui s’en passe est celui qui + EFFACE la décision en vigueur : proposer de nouveau un produit qui ne porte aucune + dérogation.

        `),ds=u(`

        Produit choisi :

        Ce produit est-il proposé dans la grille ?

        La dérogation s’enregistre seule : elle ne remet pas dans la grille un produit + qui en a été retiré, et retirer un produit n’efface pas sa dérogation.

        `),vs=u(' ',1),ps=u('

        Aucune décision locale : la grille est celle du fichier.

        '),fs=u('
      • '),gs=u('

          ',1),ms=u('

          '),hs=u('

          Aucun import dans l’historique.

          '),_s=u(' '),bs=u('

          QuandFichierSourceRésultatMotifLuesPesablesNon pesablesAnomaliesRetirés
          ',1),ws=u('
          ');function ys(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=20,h=50,x=20,O=["NO_BARCODE","PREPACKAGED_PRODUCT","INTERNAL_CODE_NOT_WEIGHABLE"],A={applied:"appliqué",unchanged:"identique au précédent",rejected:"refusé",failed:"échec"},T={local_drop:"dépôt local",webdav:"WebDAV",manual:"déposé sur l’écran"},H={local_drop:["catalog.options.directory"],webdav:["catalog.options.url","catalog.options.username","catalog.options.password"]},B=o(()=>t.draft.text("catalog.type"));let se=$(Lt([])),_e=$(Lt([])),ye=$(Lt([])),ce=$(""),j=$(""),V=$(""),le=$(""),re=$(!1),W=$(""),D=$(""),Y=$("loading"),z=$("loading");const P=o(()=>s().busy||e(D)!==""),G=o(()=>e(_e).filter(k=>k.issue==="anomaly")),E=o(()=>e(_e).filter(k=>O.includes(k.code))),b=o(()=>e(_e).filter(k=>k.code==="UNIT_MISMATCH")),R=o(()=>new Map(e(ye).map(k=>[k.id,k.name]))),J=o(()=>e(ce)===""?[]:vr(e(ye),pr,e(ce))),be=o(()=>e(J).slice(0,c)),xe=o(()=>e(G).slice(0,h)),Ve=o(()=>e(b).slice(0,h)),Ae=o(()=>e(E).slice(0,h)),w=o(()=>[...t.health.decisions].sort((k,K)=>K.decided_at.localeCompare(k.decided_at))),N=o(()=>e(w).slice(0,x)),_=o(()=>e(j)===""?null:e(w).find(k=>k.product_id===e(j))??null),F=o(()=>e(_)===null?!0:e(_).offered),I=o(()=>e(_)?.min_weight_g??null),Q=o(()=>e(le).trim()===""?null:Number(e(le))),qe=o(()=>e(Q)!==null&&Number.isFinite(e(Q))&&e(Q)>0),oe=o(()=>e(V).trim()),S=o(()=>e(oe)!==""||!Le(!1,e(I))),Z=o(()=>e(oe)!==""||!Le(!0,e(I))),ie=o(()=>e(qe)&&(e(oe)!==""||!Le(e(F),e(Q)))),pe=o(()=>e(oe)!==""||!Le(e(F),null)),we=o(()=>t.health.catalog?.products_withdrawn_count??0),He=o(()=>e(se).find(k=>t.health.catalog!==null&&k.id`${me(e(we))} ${e(we)>1?"produits retirés":"produit retiré"}`+(e(He)===null?".":` depuis l’import du ${Mt(e(He).occurred_at)}.`)),fe=o(()=>e(J).length>e(be).length?`${me(e(be).length)} produits affichés sur ${me(e(J).length)} trouvés — précisez votre recherche.`:`${me(e(J).length)} ${e(J).length>1?"produits trouvés":"produit trouvé"}.`),d=o(()=>`${me(e(se).length)} ${e(se).length>1?"imports affichés":"import affiché"} : le poste n’en publie jamais plus de vingt.`),y=o(()=>e(Y)==="loading"?"Lecture des signalements du dernier import…":"Les signalements du dernier import n’ont pas pu être lus : cet écran ne sait pas ce qu’ils disent."),ee=o(()=>e(Y)==="loading"?"Lecture de l’historique des imports…":"L’historique des imports n’a pas pu être lu : cet écran ne sait pas ce qu’il contient.");let f;aa(()=>{const k=t.health.catalog?.id??null;k!==f&&(f=k,q())});async function q(){const k=await s().load(()=>Wr(t.health.catalog?.id));k===null?v(Y,"unread"):(v(se,k.imports,!0),v(_e,k.findings,!0),v(Y,"read"));const K=await s().load(()=>Ba());if(K===null){v(z,"unread");return}v(ye,K.products,!0),v(z,"read")}async function L(k,K){v(D,k,!0),s().actionError="",s().notice="";try{const ue=await s().protect(K);return ue===null?!1:(s().notice=ue.message,await s().refresh(),!0)}finally{v(D,"")}}function he(k){t.draft.set("catalog.type",k);for(const[K,ue]of Object.entries(H))if(K!==k)for(const Re of ue)t.draft.unset(Re)}function ne(k){return t.draft.faults.find(K=>K.field===k)?.message??""}function Se(k){v(j,k,!0),v(V,"");const K=e(w).find(ue=>ue.product_id===k)?.min_weight_g??null;v(le,K===null?"":String(K),!0)}function Le(k,K){return!(k&&K===null)}async function de(k){if(e(j)==="")return;const K=e(j),ue=e(I),Re=e(oe);await L("offered",()=>Pa(K,{offered:k,min_weight_g:ue,reason:Re}))&&v(V,"")}async function M(k){if(e(j)==="")return;const K=e(j),ue=e(F),Re=e(oe);await L(k===null?"waiver-off":"waiver",()=>Pa(K,{offered:ue,min_weight_g:k,reason:Re}))&&v(V,"")}async function Ie(k){if(v(re,!1),k!=null){v(W,""),s().actionError="",s().notice="",v(D,"import");try{const K=await s().protect(()=>Ga(k));if(K===null)return;v(W,`${k.name} : ${me(K.rows_read_count)} lignes lues, ${me(K.weighable_count)} pesables. `+(K.reason===""?"Le fichier est déposé ; son résultat s’inscrira dans l’historique des imports.":K.reason)),await s().refresh()}finally{v(D,"")}}}function Me(k){k.preventDefault(),v(re,!1);const K=k.dataTransfer?.files.item(0);if(e(P)){v(W,`${K?.name??"Ce fichier"} n’a pas été déposé : un acte est déjà en cours sur cette page. Réessayez quand il aura répondu.`);return}Ie(K)}function Ee(k){const K=k.files?.item(0);k.value="",Ie(K)}function Te(k){const K=e(R).get(k);return K!==void 0?K:e(z)==="loading"?"Lecture du nom…":e(z)==="unread"?"Nom inconnu : le catalogue n’a pas pu être lu":"Produit absent du catalogue en service"}function Je(k,K,ue,Re){const We=K>1?Re:ue;return k>=K?`${me(K)} ${We}.`:`${me(k)} lignes affichées sur ${me(K)} ${We}.`}function ae(k){const K=[];return k.offered||K.push("retiré de la grille"),k.min_weight_g!==null&&K.push(`peut peser à partir de ${me(k.min_weight_g)} g`),K.length===0?"aucune restriction":K.join(" · ")}function De(k){const K=A[k.result]??"résultat inconnu";return k.code===""?K:`${K} (${k.code})`}function ve(k){return T[k.source]??"source inconnue"}var ze=ws(),Oe=a(ze);Qe(Oe,{title:"Où le poste va chercher le catalogue",children:(k,K)=>{var ue=Wn(),Re=X(ue),We=a(Re),Ye=a(We),Ke=r(We,2),U=a(Ke),te=r(Re,2);{var ge=Pe=>{var Ue=Fn();l(Pe,Ue)},Ne=Pe=>{var Ue=Mn(),Ge=X(Ue);{let it=o(()=>Ft("catalog.options.directory")),dt=o(()=>t.draft.text("catalog.options.directory")),gt=o(()=>ne("catalog.options.directory"));_t(Ge,{get label(){return e(it)},path:"catalog.options.directory",get value(){return e(dt)},hint:"Laissez vide pour le répertoire du poste, celui que le service crée lui-même. Un répertoire nommé ici doit exister : le poste ne le crée pas.",get fault(){return e(gt)},onchange:xt=>t.draft.set("catalog.options.directory",xt)})}var ft=r(Ge,2),ot=r(a(ft)),ct=a(ot);p(()=>i(ct,`flv_${t.health.station??""}.csv`)),l(Pe,Ue)},ke=Pe=>{var Ue=Bn(),Ge=X(Ue);{let ct=o(()=>Ft("catalog.options.url")),it=o(()=>t.draft.text("catalog.options.url")),dt=o(()=>ne("catalog.options.url"));_t(Ge,{get label(){return e(ct)},path:"catalog.options.url",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.url",gt)})}var ft=r(Ge,2);{let ct=o(()=>Ft("catalog.options.username")),it=o(()=>t.draft.text("catalog.options.username")),dt=o(()=>ne("catalog.options.username"));_t(ft,{get label(){return e(ct)},path:"catalog.options.username",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.username",gt)})}var ot=r(ft,2);{let ct=o(()=>Ft("catalog.options.password")),it=o(()=>ne("catalog.options.password"));_t(ot,{get label(){return e(ct)},path:"catalog.options.password",kind:"password",value:"",hint:"Laissez vide : le mot de passe actuel est conservé.",get fault(){return e(it)},onchange:dt=>t.draft.set("catalog.options.password",dt)})}l(Pe,Ue)},$e=Pe=>{var Ue=Vn();l(Pe,Ue)};m(te,Pe=>{t.draft.config===null?Pe(ge):e(B)==="local_drop"?Pe(Ne,1):e(B)==="webdav"?Pe(ke,2):Pe($e,-1)})}p(()=>{ua(Ye,e(B)==="local_drop"),ua(U,e(B)==="webdav")}),tt("change",Ye,()=>he("local_drop")),tt("change",U,()=>he("webdav")),l(k,ue)},$$slots:{default:!0}});var nt=r(Oe,2);Qe(nt,{title:"Dernier import",children:(k,K)=>{var ue=Hn(),Re=X(ue);{var We=ke=>{var $e=Gn();l(ke,$e)},Ye=ke=>{Za(ke,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives}})};m(Re,ke=>{t.health.catalog===null?ke(We):ke(Ye,-1)})}var Ke=r(Re,2),U=a(Ke),te=r(Ke,2),ge=a(te);{let ke=o(()=>e(D)==="reload");at(ge,{act:"reload",kind:"write",label:"Recharger le catalogue",protected:!0,get busy(){return e(ke)},get disabled(){return e(P)},onrun:()=>{L("reload",Gr)}})}var Ne=r(ge,2);{let ke=o(()=>e(D)==="quarantine");at(Ne,{act:"quarantine",kind:"destructive",label:"Oublier la quarantaine",protected:!0,get busy(){return e(ke)},get disabled(){return e(P)},onrun:()=>{L("quarantine",Hr)}})}p(()=>i(U,t.health.catalog_source===null?"Aucune source de catalogue publiée par ce poste.":"Source : "+t.health.catalog_source.label)),l(k,ue)},$$slots:{default:!0}});var ut=r(nt,2);Qe(ut,{title:"Déposer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(k,K)=>{var ue=Kn(),Re=X(ue);{var We=Pe=>{var Ue=Jn(),Ge=a(Ue);p(()=>i(Ge,e(W))),l(Pe,Ue)};m(Re,Pe=>{e(W)!==""&&Pe(We)})}var Ye=r(Re,2);let Ke;var U=a(Ye),te=r(a(U)),ge=a(te),Ne=r(U,2),ke=a(Ne),$e=r(ke);p(()=>{Ke=Ht(Ye,1,"drop svelte-3gk7u7",null,Ke,{dropping:e(re),working:e(D)==="import"}),i(ge,`flv_${t.health.station??""}.csv`),i(ke,`${e(D)==="import"?"Import en cours…":"Choisir un fichier"} `),$e.disabled=e(P)}),Qt("dragover",Ye,Pe=>{Pe.preventDefault(),!e(P)&&v(re,!0)}),Qt("dragleave",Ye,()=>v(re,!1)),Qt("drop",Ye,Me),tt("change",$e,Pe=>Ee(Pe.currentTarget)),l(k,ue)},$$slots:{default:!0}});var pt=r(ut,2);Qe(pt,{title:"Anomalies à corriger dans Odoo",note:"Chaque ligne porte son numéro dans le CSV, son motif et la valeur fautive.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=Xn(),ge=a(te);p(()=>i(ge,e(y))),l(U,te)},Ye=U=>{var te=Yn();l(U,te)},Ke=U=>{var te=Qn(),ge=X(te),Ne=a(ge),ke=r(Ne);{var $e=Ue=>{var Ge=yt("Corrigez celles-ci dans Odoo : l’import suivant ne signalera que ce qui reste.");l(Ue,Ge)};m(ke,Ue=>{e(G).length>e(xe).length&&Ue($e)})}var Pe=r(ge,2);_a(Pe,()=>e(xe),()=>"anomalies"),p(Ue=>i(Ne,`${Ue??""} `),[()=>Je(e(xe).length,e(G).length,"anomalie","anomalies")]),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(G).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var je=r(pt,2);Qe(je,{title:"Unités divergentes",note:"Le produit reste proposé : le code-barres fait foi, seul le libellé du prix est faux.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=Zn(),ge=a(te);p(()=>i(ge,e(y))),l(U,te)},Ye=U=>{var te=es();l(U,te)},Ke=U=>{var te=ts(),ge=X(te),Ne=a(ge),ke=r(ge,2);_a(ke,()=>e(Ve),()=>"mismatches"),p($e=>i(Ne,$e),[()=>Je(e(Ve).length,e(b).length,"unité divergente","unités divergentes")]),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(b).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var st=r(je,2);Qe(st,{title:"Produits non pesables",note:"Un inventaire, pas une liste d’erreurs : ces produits portent déjà leur code-barres et n’ont aucune raison d’être pesés.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=as(),ge=a(te);p(()=>i(ge,e(y))),l(U,te)},Ye=U=>{var te=rs();l(U,te)},Ke=U=>{var te=ns(),ge=X(te),Ne=a(ge),ke=r(ge,2);_a(ke,()=>e(Ae),()=>"not-weighable"),p($e=>i(Ne,$e),[()=>Je(e(Ae).length,e(E).length,"produit non pesable","produits non pesables")]),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(E).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var Ze=r(st,2);Qe(Ze,{title:"Produits retirés depuis l’import précédent",note:"Un produit absent du nouveau fichier est marqué retiré à sa date, jamais supprimé.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=ss();l(U,te)},Ye=U=>{var te=ls();l(U,te)},Ke=U=>{var te=is(),ge=X(te),Ne=a(ge);p(()=>i(Ne,e(Xe))),l(U,te)};m(Re,U=>{t.health.catalog===null?U(We):e(we)===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var bt=r(Ze,2);Qe(bt,{title:"Décider d’un produit",note:"Retirer un produit et l’autoriser à peser moins sont deux décisions séparées : l’une n’efface pas l’autre.",children:(k,K)=>{var ue=vs(),Re=r(X(ue),2),We=r(Re,2);{var Ye=te=>{var ge=us(),Ne=X(ge),ke=a(Ne),$e=r(Ne,2),Pe=a($e);rt(Pe,21,()=>e(be),Ue=>Ue.id,(Ue,Ge)=>{var ft=os(),ot=a(ft),ct=a(ot),it=r(ot,2),dt=a(it),gt=r(it,2),xt=a(gt);p(()=>{i(ct,e(Ge).name),i(dt,e(Ge).id),i(xt,`${e(Ge).unit_price_text??""}${e(Ge).price_suffix??""}`)}),tt("click",ot,()=>Se(e(Ge).id)),l(Ue,ft)}),p(()=>i(ke,e(fe))),l(te,ge)};m(We,te=>{e(ce)!==""&&te(Ye)})}var Ke=r(We,2);{var U=te=>{var ge=ds(),Ne=a(ge),ke=r(a(Ne)),$e=a(ke),Pe=r(ke),Ue=r(Ne,2),Ge=a(Ue),ft=r(Ue,4),ot=r(ft,2);{var ct=et=>{var qt=cs();l(et,qt)};m(ot,et=>{e(oe)===""&&et(ct)})}var it=r(ot,2),dt=r(a(it),2),gt=a(dt);{var xt=et=>{{let qt=o(()=>e(D)==="offered"),$t=o(()=>e(P)||!e(S));at(et,{act:"offered",kind:"destructive",label:"Ne plus proposer ce produit",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{de(!1)}})}},Nt=et=>{{let qt=o(()=>e(D)==="offered"),$t=o(()=>e(P)||!e(Z));at(et,{act:"offered",kind:"write",label:"Le proposer de nouveau",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{de(!0)}})}};m(gt,et=>{e(F)?et(xt):et(Nt,-1)})}var Bt=r(it,2),Rt=r(a(Bt),2),Vt=r(Rt,2),kt=a(Vt);{let et=o(()=>e(D)==="waiver"),qt=o(()=>e(P)||!e(ie));at(kt,{act:"waiver",kind:"write",label:"Enregistrer la dérogation",protected:!0,get busy(){return e(et)},get disabled(){return e(qt)},onrun:()=>{M(e(Q))}})}var Et=r(kt,2);{var Wt=et=>{{let qt=o(()=>e(D)==="waiver-off"),$t=o(()=>e(P)||!e(pe));at(et,{act:"waiver-off",kind:"write",label:"Retirer la dérogation",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{M(null)}})}};m(Et,et=>{e(I)!==null&&et(Wt)})}p((et,qt)=>{Be(ge,"data-decision",e(j)),i($e,et),i(Pe,` (${e(j)??""})`),i(Ge,`En vigueur : ${qt??""}`),na(Rt,e(le))},[()=>Te(e(j)),()=>e(_)===null?"aucune décision — ce produit suit les règles générales":ae(e(_))]),ia(ft,()=>e(V),et=>v(V,et)),tt("input",Rt,et=>v(le,et.currentTarget.value,!0)),l(te,ge)};m(Ke,te=>{e(j)!==""&&te(U)})}ia(Re,()=>e(ce),te=>v(ce,te)),l(k,ue)},$$slots:{default:!0}});var Ot=r(bt,2);Qe(Ot,{title:"Décisions en vigueur",note:"Ce qu’un humain a décidé, produit par produit. C’est ici que se reprend un produit retiré de la grille.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=Ke=>{var U=ps();l(Ke,U)},Ye=Ke=>{var U=gs(),te=X(U),ge=a(te),Ne=r(te,2),ke=a(Ne);rt(ke,21,()=>e(N),$e=>$e.product_id,($e,Pe)=>{var Ue=fs(),Ge=a(Ue),ft=r(Ge,2),ot=a(ft),ct=r(ft,2),it=a(ct),dt=r(ct,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),Rt=a(Bt);p((Vt,kt,Et)=>{i(ot,Vt),i(it,e(Pe).product_id),i(gt,kt),i(Nt,e(Pe).reason),i(Rt,Et)},[()=>Te(e(Pe).product_id),()=>ae(e(Pe)),()=>da(e(Pe).decided_at)]),tt("click",Ge,()=>Se(e(Pe).product_id)),l($e,Ue)}),p($e=>i(ge,$e),[()=>Je(e(N).length,e(w).length,"décision en vigueur","décisions en vigueur")]),l(Ke,U)};m(Re,Ke=>{e(w).length===0?Ke(We):Ke(Ye,-1)})}l(k,ue)},$$slots:{default:!0}});var Ut=r(Ot,2);Qe(Ut,{title:"Vingt derniers imports",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=ms(),ge=a(te);p(()=>i(ge,e(ee))),l(U,te)},Ye=U=>{var te=hs();l(U,te)},Ke=U=>{var te=bs(),ge=X(te),Ne=a(ge),ke=r(ge,2),$e=a(ke),Pe=r(a($e));rt(Pe,21,()=>e(se),Ue=>Ue.id,(Ue,Ge)=>{var ft=_s(),ot=a(ft),ct=a(ot),it=r(ot),dt=a(it),gt=r(it),xt=a(gt),Nt=r(gt),Bt=a(Nt),Rt=r(Nt),Vt=a(Rt),kt=r(Rt),Et=a(kt),Wt=r(kt),et=a(Wt),qt=r(Wt),$t=a(qt),Zt=r(qt),ra=a(Zt),g=r(Zt),C=a(g);p((Ce,Fe,lt,mt,wt,zt,jt,St)=>{i(ct,Ce),i(dt,e(Ge).file_name),i(xt,Fe),i(Bt,lt),i(Vt,e(Ge).reason),i(Et,mt),i(et,wt),i($t,zt),i(ra,jt),i(C,St)},[()=>Mt(e(Ge).occurred_at),()=>ve(e(Ge)),()=>De(e(Ge)),()=>me(e(Ge).rows_read_count),()=>me(e(Ge).weighable_count),()=>me(e(Ge).not_weighable_count),()=>me(e(Ge).anomalies_count),()=>me(e(Ge).products_withdrawn_count)]),l(Ue,ft)}),p(()=>i(Ne,e(d))),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(se).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}}),l(n,ze),Ct()}At(["change","click","input"]);function xs(n){switch(n){case"ok":return"OK";case"warn":return"À SURVEILLER";case"fault":return"EN PANNE";case"off":return"SANS OBJET";default:return"INCONNU"}}var ks=u('

          '),qs=u('

          ');function Ss(n,t){Tt(t,!0);var s=qs(),c=a(s),h=a(c),x=r(h,2),O=a(x),A=r(x,2),T=a(A),H=r(c,2),B=a(H),se=r(H,2);{var _e=ye=>{var ce=ks(),j=a(ce);p(()=>i(j,t.light.remedy)),l(ye,ce)};m(se,ye=>{t.light.remedy!==""&&ye(_e)})}p(ye=>{Be(s,"data-light",t.light.id),Be(s,"data-level",t.light.level),Be(h,"data-level",t.light.level),i(O,t.light.label),i(T,ye),i(B,t.light.value)},[()=>xs(t.light.level)]),l(n,s),Ct()}function Ls(n){return[Es(n),zs(n),Ps(n),Ts(n),Os(n),Rs(n)]}function Es(n){const t=n.state.scale,s=ca(t.median_ms);return n.scale_present?t.connected?t.too_slow?{id:"scale",label:"Balance",level:"warn",value:`une mesure toutes les ${s}, plus lent que la péremption`,remedy:"À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante. Vérifiez le câble et l’adaptateur USB, puis la cadence sur la page Matériel."}:{id:"scale",label:"Balance",level:"ok",value:As(t.provisional,`une mesure toutes les ${s}`),remedy:""}:{id:"scale",label:"Balance",level:"fault",value:"elle ne répond plus",remedy:"Vérifiez le câble et l’alimentation de la balance, puis touchez « Tester la balance ». En attendant, « Basculer en saisie manuelle » permet de continuer à servir."}:{id:"scale",label:"Balance",level:"off",value:"ce poste est déclaré sans balance",remedy:""}}function zs(n){const t=n.state.printer;switch(t.health){case"faulted":return{id:"printer",label:"Imprimante",level:"fault",value:t.detail===""?"elle ne peut pas imprimer":t.detail,remedy:js(n)};case"consumable":return{id:"printer",label:"Imprimante",level:"warn",value:"elle imprime, mais le rouleau arrive en fin de vie",remedy:"Changez le rouleau quand vous passez derrière le comptoir, puis touchez « J’ai changé le rouleau ». Le poste continue de servir en attendant."};case"ready":return{id:"printer",label:"Imprimante",level:"ok",value:"elle répond et n’a rien à signaler",remedy:""};default:return{id:"printer",label:"Imprimante",level:"unknown",value:"elle prend les étiquettes et ne dit rien en retour",remedy:"C’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne. Pour savoir si elle imprime, touchez « Imprimer une étiquette de test »."}}}function js(n){const t="Regardez le capot, le rouleau et le câble, puis touchez « Tester l’imprimante ».";return n.printing?.fallback_available===!0?t+" Le poste peut servir en attendant : « Imprimer sur l’imprimante du poste voisin ».":t}function Ps(n){const t=n.roll;return t===null?{id:"roll",label:"Rouleau",level:"unknown",value:"aucun compteur d’étiquettes sur ce poste",remedy:`Sans imprimante construite, il n’y a pas de rouleau à compter : vérifiez le « ${Ft("printer.type")} » et ses réglages sur la page Matériel.`}:t.known?t.level==="warn"?{id:"roll",label:"Rouleau",level:"warn",value:t.message,remedy:"Changez le rouleau, puis touchez « J’ai changé le rouleau »."}:{id:"roll",label:"Rouleau",level:"ok",value:t.message,remedy:""}:{id:"roll",label:"Rouleau",level:"unknown",value:t.message,remedy:"Touchez « J’ai changé le rouleau » en mettant un rouleau neuf : c’est le seul geste qui dise quelque chose de vrai du papier."}}function Ts(n){const t=n.catalog_source?.label??"";if(n.state.catalog_count===0)return{id:"catalog",label:"Catalogue",level:"fault",value:"aucun produit dans la grille",remedy:Cs(t)};const s=n.catalog;return s!==null&&s.result!=="applied"&&s.result!=="unchanged"?{id:"catalog",label:"Catalogue",level:"warn",value:`dernier fichier refusé : ${s.reason===""?s.result:s.reason}`,remedy:"La grille tourne toujours sur le catalogue précédent. Corrigez le fichier dans Odoo, ou touchez « Oublier la quarantaine » puis redéposez-le."}:{id:"catalog",label:"Catalogue",level:"ok",value:`${me(n.state.catalog_count)} produits dans la grille`,remedy:""}}function Cs(n){return"Déposez le fichier du catalogue là où le poste le guette, ou utilisez « Importer un catalogue » ci-contre pour le glisser depuis une clé USB."+(n===""?"":` Ce poste surveille : ${n}.`)}function Os(n){const t=n.disk;if(t===null)return{id:"disk",label:"Disque",level:"unknown",value:"la place libre n’a pas pu être mesurée",remedy:"Téléchargez le fichier de diagnostic : il porte ce que le poste a pu lire du volume, et c’est la pièce qu’un support demandera."};const s=wa(t.free_bytes),c=`seuil ${me(t.alert_mb)} Mo`;return t.free_bytes===0?{id:"disk",label:"Disque",level:"fault",value:`plus un octet libre sur ${t.path}`,remedy:"Le journal ne peut plus rien écrire, alors que les étiquettes continuent de sortir. Faites de la place sur le disque, puis rechargez cette page."}:t.alert_mb>0&&t.free_bytes0?{id:"journal",label:"Journal",level:"fault",value:`${me(t)} pesées imprimées mais non enregistrées`,remedy:"Les étiquettes sortent, les ventes sont bonnes, c’est la trace qui manque. Téléchargez le fichier de diagnostic et prévenez le support : ne redémarrez rien."}:n.counters.journal_rows_count<0?{id:"journal",label:"Journal",level:"unknown",value:"ce poste n’a pas de journal ouvert",remedy:"Le poste pèse et imprime quand même. Téléchargez le fichier de diagnostic : il dit pourquoi la base n’a pas pu être ouverte."}:{id:"journal",label:"Journal",level:"ok",value:`${me(n.counters.journal_rows_count)} pesées enregistrées`,remedy:""}}function As(n,t){return n?t+" (cadence encore provisoire)":t}function Ns(n){return n===null||!n.known?"unknown":n.configured?"ok":"missing"}function $s(n){switch(n){case"applied":return"appliqué";case"unchanged":return"inchangé, déjà appliqué";case"rejected":return"refusé";case"failed":return"échec";default:return n}}var Ds=u("Une mesure toutes les ",1),Is=u('

          '),Us=u("Source : ",1),Fs=u(`

          Aucun import enregistré sur ce poste : le catalogue n’est jamais arrivé, ou le + journal ne le porte pas.

          `),Ms=u('

          ',1),Bs=u('

          ',1),Vs=u('

          Aucune décision locale : le catalogue est proposé tel qu’il arrive.

          '),Ws=u('

          '),Gs=u(' '),Hs=u('
        • '),Js=u('
            ',1),Ks=u(`INCONNU — la question n’a pas pu être posée à ce système. Ce + n’est pas « non configuré » : personne ne sait encore.`,1),Xs=u('OK ',1),Ys=u('NON CONFIGURÉ ',1),Qs=u('

            '),Zs=u('

            ',1),el=u('

            Rien à signaler depuis le démarrage du poste.

            '),tl=u(' '),al=u('
          • '),rl=u('
              '),nl=u('
              Numéro de poste
              Nom
              Coopérative
              Version
              Empreinte de configuration
              '),sl=u('
              ');function ll(n,t){Tt(t,!0);const s=o(()=>Ls(t.health)),c=o(()=>t.health.state.scale),h=o(()=>t.health.unattended_restart),x=o(()=>t.health.decisions),O=5,A=o(()=>e(x).slice(0,O));var T=sl(),H=a(T);rt(H,21,()=>e(s),V=>V.id,(V,le)=>{Ss(V,{get light(){return e(le)}})});var B=r(H,2);Qe(B,{title:"Cadence de la balance",children:(V,le)=>{var re=Is(),W=a(re);{var D=G=>{var E=yt("Ce poste est déclaré sans balance : le poids est saisi à la main.");l(G,E)},Y=G=>{var E=yt(`La balance ne répond pas : aucune cadence n’est mesurable tant qu’aucune trame + n’arrive.`);l(G,E)},z=G=>{var E=yt(`Aucun intervalle n’a encore été mesuré : la cadence apparaîtra dès les premières + trames.`);l(G,E)},P=G=>{var E=Ds(),b=r(X(E)),R=a(b),J=r(b),be=r(J);{var xe=w=>{var N=yt("C’est encore une valeur d’attente : moins de huit intervalles ont été observés.");l(w,N)};m(be,w=>{e(c).provisional&&w(xe)})}var Ve=r(be,2);{var Ae=w=>{var N=yt("À cette cadence, un poids serait périmé avant l’arrivée de la mesure suivante.");l(w,N)};m(Ve,w=>{e(c).too_slow&&w(Ae)})}p((w,N)=>{i(R,w),i(J,`, médiane + observée sur ${N??""} intervalles. `)},[()=>ca(e(c).median_ms),()=>me(e(c).observations_count)]),l(G,E)};m(W,G=>{t.health.scale_present?e(c).connected?e(c).observations_count===0?G(z,2):G(P,-1):G(Y,1):G(D)})}l(V,re)},$$slots:{default:!0}});var se=r(B,2);Qe(se,{title:"Catalogue",children:(V,le)=>{var re=Bs(),W=X(re),D=a(W);{var Y=b=>{var R=yt("Aucune source de catalogue n’est publiée par ce poste.");l(b,R)},z=b=>{var R=Us(),J=r(X(R)),be=a(J);p(()=>i(be,t.health.catalog_source.label)),l(b,R)};m(D,b=>{t.health.catalog_source===null?b(Y):b(z,-1)})}var P=r(W,2);{var G=b=>{var R=Fs();l(b,R)},E=b=>{var R=Ms(),J=X(R),be=a(J),xe=r(J,2);Za(xe,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives},get onshowrows(){return t.onshowrows}}),p((Ve,Ae)=>i(be,`Dernier essai : ${Ve??""} — + ${Ae??""}${t.health.catalog.file_name===""?"":" ("+t.health.catalog.file_name+")"}${t.health.catalog.reason===""?"":" : "+t.health.catalog.reason}`),[()=>Mt(t.health.catalog.occurred_at),()=>$s(t.health.catalog.result)]),l(b,R)};m(P,b=>{t.health.catalog===null?b(G):b(E,-1)})}l(V,re)},$$slots:{default:!0}});var _e=r(se,2);Qe(_e,{title:"Décisions locales en vigueur",note:"Ce qu’un humain a décidé de ce catalogue, avec son motif et sa date.",children:(V,le)=>{var re=Dt(),W=X(re);{var D=z=>{var P=Vs();l(z,P)},Y=z=>{var P=Js(),G=X(P);{var E=R=>{var J=Ws(),be=a(J);p(xe=>i(be,`${xe??""} décisions en vigueur — les + 5 plus récentes ci-dessous. La liste entière est sur la page + Catalogue.`),[()=>me(e(x).length)]),l(R,J)};m(G,R=>{e(x).length>O&&R(E)})}var b=r(G,2);rt(b,21,()=>e(A),R=>R.product_id,(R,J)=>{var be=Hs(),xe=a(be),Ve=a(xe),Ae=r(xe,2);{var w=Q=>{var qe=Gs(),oe=a(qe);p(S=>i(oe,`peut peser à partir de ${S??""} g`),[()=>me(e(J).min_weight_g)]),l(Q,qe)};m(Ae,Q=>{e(J).min_weight_g!==null&&Q(w)})}var N=r(Ae,2),_=a(N),F=r(N,2),I=a(F);p(Q=>{i(Ve,`${e(J).offered?"Dérogation de poids":"Produit retiré"} — ${e(J).product_id??""}`),i(_,e(J).reason),i(I,`${Q??""}, ${e(J).decided_by??""}`)},[()=>da(e(J).decided_at)]),l(R,be)}),l(z,P)};m(W,z=>{e(x).length===0?z(D):z(Y,-1)})}l(V,re)},$$slots:{default:!0}});var ye=r(_e,2);Qe(ye,{title:"Redémarrage sans intervention",children:(V,le)=>{var re=Zs(),W=X(re),D=a(W);{var Y=b=>{var R=Ks();l(b,R)},z=b=>{var R=Xs(),J=r(X(R));p(()=>i(J,` — après une coupure de courant, ce poste revient seul sur + l’écran client. ${e(h).detail??""}`)),l(b,R)},P=b=>{var R=Ys(),J=r(X(R));p(()=>i(J,` — ${e(h).detail??""}`)),l(b,R)};m(D,b=>{e(h)===null||!e(h).known?b(Y):e(h).configured?b(z,1):b(P,-1)})}var G=r(W,2);{var E=b=>{var R=Qs(),J=a(R);p(()=>i(J,e(h).remedy)),l(b,R)};m(G,b=>{e(h)!==null&&e(h).known&&!e(h).configured&&e(h).remedy!==""&&b(E)})}p(b=>Be(W,"data-verdict",b),[()=>Ns(e(h))]),l(V,re)},$$slots:{default:!0}});var ce=r(ye,2);Qe(ce,{title:"Dix derniers événements",children:(V,le)=>{var re=Dt(),W=X(re);{var D=z=>{var P=el();l(z,P)},Y=z=>{var P=rl();rt(P,21,()=>t.health.events,G=>G.id,(G,E)=>{var b=al(),R=a(b),J=a(R),be=r(R,2),xe=a(be),Ve=r(be,2);{var Ae=_=>{var F=tl(),I=a(F);p(()=>i(I,e(E).code)),l(_,F)};m(Ve,_=>{e(E).code!==""&&Gt.showTechnicalNames&&_(Ae)})}var w=r(Ve,2),N=a(w);p((_,F)=>{Be(b,"data-level",e(E).level),i(J,_),i(xe,F),i(N,e(E).message)},[()=>zn(e(E).occurred_at),()=>Xa(e(E).source)]),l(G,b)}),l(z,P)};m(W,z=>{t.health.events.length===0?z(D):z(Y,-1)})}l(V,re)},$$slots:{default:!0}});var j=r(ce,2);Qe(j,{title:"Ce poste",children:(V,le)=>{var re=nl(),W=r(a(re),2),D=a(W),Y=r(W,4),z=a(Y),P=r(Y,4),G=a(P),E=r(P,4),b=a(E),R=r(E,4),J=a(R);p(()=>{i(D,t.health.station),i(z,t.health.station_name===""?"non renseigné":t.health.station_name),i(G,t.health.coop),i(b,t.health.version),i(J,t.health.config_fingerprint)}),l(V,re)},$$slots:{default:!0}}),l(n,T),Ct()}var il=u(''),ol=u(`

              Lecture de la configuration en cours… tant qu’elle n’est pas arrivée, cette page ne + déclare rien de ce poste.

              `),Na=u('
            • '),ul=u('
                '),cl=u('

                ',1),dl=u('
              • '),vl=u('

                  ',1),pl=u('

                  '),fl=u('
                • '),gl=u('
                    '),ml=u('

                      ',1),hl=u('

                      Balance

                      L’état et la cadence viennent de ce que le poste observe vraiment, jamais d’un réglage.

                      Réglages série de la balance

                      Imprimante

                      Réglages de l’imprimante
                      ');function _l(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=20,h=3,x=250,O=50,A=h*1e3+x+1e3,T=12;let H=$(Lt([])),B=$(Lt([])),se=$(Lt([])),_e=$(Lt([])),ye=$(""),ce=$(!1),j=$(""),V=$(0),le=$(0),re=$(!1),W=$(null),D=!1;const Y=o(()=>t.health.state.scale),z=o(()=>t.health.state.printer),P=o(()=>t.draft.config!==null),G=o(()=>e(P)&&!t.draft.flag("scale.present")),E=o(()=>t.draft.text("scale.options.port")),b=o(()=>e(W)!==null&&e(W).port===e(E)?e(W).reason:""),R=o(()=>e(ye)===e(E)?e(_e):[]),J=o(d),be=o(ee),xe=o(L),Ve=o(Se),Ae=o(()=>Ee("scale.type")!==""||Ee("scale.options.port")!==""),w=o(()=>Ee("printer.type")!==""||Ee("printer.options.transport")!==""||Ee("printer.options.queue")!=="");br(()=>{D=!0}),ka(()=>{S("ports",Z)}),aa(()=>{e(re)||!N(e(E))||F(e(E))});function N(g){return!D&&e(P)&&!e(G)&&g!==""&&g===e(E)&&_(g)&&e(b)===""&&e(j)===""}function _(g){return e(H).some(C=>C.name===g)}async function F(g){v(re,!0);try{for(;N(g);){try{Q(g,await ja(g,h))}catch(C){v(W,{port:g,reason:Je(C)},!0);return}await qe()}}finally{v(re,!1)}}async function I(){const g=e(E),C=await s().protect(()=>ja(g,h));C!==null&&(Q(g,C),v(W,null))}function Q(g,C){if(D||C.length===0)return;const Ce=g===e(ye)?e(_e):[];v(ye,g,!0),v(_e,[...Ce,...C].slice(-c),!0)}function qe(g=x){return new Promise(C=>setTimeout(C,g))}async function oe(){const g=Date.now()+A;for(;e(re)&&Date.now()Ha(g));C!==null&&(s().notice=C.message,await s().refresh())}async function He(){v(V,0),v(le,0),e(ce)||await Z();const g=e(H);if(g.length===0){v(se,[],!0);return}await s().protect(()=>Xe(g))}async function Xe(g){v(se,[],!0),v(le,g.length,!0);for(const[C,Ce]of g.entries()){v(V,C+1);try{const Fe=await Ur(Ce.name);v(se,[...e(se),{port:Ce.name,message:Fe.message,refused:!1}],!0)}catch(Fe){if(ae(Fe))throw Fe;v(se,[...e(se),{port:Ce.name,message:Je(Fe),refused:!0}],!0)}}}function fe(){return e(j)!=="detect"?"Détecter automatiquement":e(re)?"Détection : le port se libère…":e(le)===0?"Détection : énumération des ports…":`Détection : port ${me(e(V))} sur ${me(e(le))}…`}function d(){return t.health.scale_present?e(Y).connected?e(Y).too_slow?{level:"warn",word:"Trop lente",detail:y()+" À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante."}:{level:"ok",word:"Connectée",detail:"Elle répond. "+y()}:{level:"fault",word:"Sans réponse",detail:"Elle ne répond plus. Vérifiez le câble et l’alimentation, puis « Tester la balance » sur la page Dépannage."}:{level:"off",word:"Sans balance",detail:"Ce poste est déclaré sans balance : le feu est éteint et le poids se saisit à la main."}}function y(){return e(Y).observations_count===0?"Aucun intervalle n’a encore été mesuré : la cadence sera connue dès les premières trames.":`Une mesure toutes les ${ca(e(Y).median_ms)} sur ${me(e(Y).observations_count)} intervalles`+(e(Y).provisional?", cadence encore provisoire.":".")}function ee(){const g=f[e(z).health]??{level:"unknown",word:"État inconnu",detail:"Le poste a répondu un état que cet écran ne sait pas nommer."};return{...g,detail:e(z).detail===""?g.detail:e(z).detail}}const f={ready:{level:"ok",word:"Prête",detail:"Elle répond et n’a rien à signaler."},consumable:{level:"warn",word:"Rouleau en fin de vie",detail:"Elle imprime, mais le rouleau arrive en fin de vie."},faulted:{level:"fault",word:"En panne",detail:"Elle ne peut pas imprimer."},unknown:{level:"unknown",word:"Silencieuse",detail:"Elle prend les étiquettes et ne dit rien en retour : c’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne."}};function q(){const g=e(z).observed_at===""?"Jamais observée depuis le démarrage":`Observée le ${Mt(e(z).observed_at)}`,C=e(z).pending_jobs_count;return`${g}, ${me(C)} ${C>1?"travaux":"travail"} en attente.`}function L(){const g=ne();return g===""?he():`${he()} ${g}`}function he(){return e(P)?e(G)?"Ce poste est déclaré sans balance : aucun port n’est écouté.":e(E)===""?"Aucun port n’est indiqué : choisissez-en un dans la liste ci-dessus pour écouter les trames.":e(ce)?_(e(E))?e(b)!==""?`L’écoute de ${e(E)} est arrêtée.`:e(j)!==""?`L’écoute de ${e(E)} est suspendue le temps de l’acte en cours.`:`Écoute de ${e(E)}.`:`${e(E)} n’est pas visible depuis ce poste : rien n’est écouté en continu.`:e(j)==="ports"?`Énumération des ports en cours : l’écoute de ${e(E)} démarre dès qu’il est vu.`:`Les ports de ce poste n’ont pas été énumérés : « Lister les ports » dira si ${e(E)} existe.`:"Lecture de la configuration en cours : le port à écouter n’est pas encore connu."}function ne(){return e(R).length===0?"Aucune trame reçue pour l’instant.":e(R).length===1?`Une seule trame reçue — ${me(c)} au plus sont gardées.`:`Les ${me(e(R).length)} dernières trames — ${me(c)} au plus, la plus récente en bas.`}function Se(){return!e(P)||e(G)||e(E)===""?"":e(b)!==""?"Reprendre l’écoute":e(ce)&&!_(e(E))?"Écouter ce port une fois":""}function Le(g,C,Ce){const Fe=`${me(Ce)} ${Ce>1?C:g}`;return Ce<=T?Fe+".":`${Fe} — seules les ${me(T)} premières lignes sont affichées.`}function de(g,C){const Ce=Te(g);return Ce.length>0?Ce:C.slice(0,T)}function M(g){return[...new TextEncoder().encode(g)].map(C=>C.toString(16).toUpperCase().padStart(2,"0")).join(" ")}const Ie={0:"NUL",2:"STX",3:"ETX",4:"EOT",5:"ENQ",6:"ACK",9:"TAB",10:"LF",13:"CR",21:"NAK",27:"ESC",127:"DEL"};function Me(g){let C="";for(const Ce of g){const Fe=Ce.codePointAt(0)??0;if(Fe>=32&&Fe!==127){C+=Ce;continue}C+=`⟨${Ie[Fe]??Fe.toString(16).toUpperCase().padStart(2,"0")}⟩`}return C}function Ee(g){return t.draft.faults.find(C=>C.field===g)?.message??""}function Te(g){return t.draft.faults.find(C=>C.field===g)?.allowed??[]}function Je(g){return g instanceof It?g.message:g instanceof Error?"Le poste n’a pas répondu : "+g.message:"Le poste n’a pas répondu."}function ae(g){return g instanceof It&&(g.status===401||g.status===409)}const De=[{what:"label",name:"étiquette"},{what:"alignment",name:"alignement"},{what:"ruler",name:"réglette"}];var ve=hl(),ze=a(ve),Oe=a(ze),nt=r(a(Oe),2),ut=r(a(nt),1,!0),pt=r(Oe,2),je=a(pt),st=r(pt,4);{var Ze=g=>{var C=il(),Ce=a(C);p(()=>ua(Ce,e(G))),tt("change",Ce,Fe=>t.draft.set("scale.present",!Fe.currentTarget.checked)),l(g,C)},bt=g=>{var C=ol();l(g,C)};m(st,g=>{e(P)?g(Ze):g(bt,-1)})}var Ot=r(st,2),Ut=a(Ot);{let g=o(()=>e(j)==="ports"),C=o(()=>e(j)!=="");at(Ut,{label:"Lister les ports",get busy(){return e(g)},get disabled(){return e(C)},onrun:()=>{S("ports",Z)}})}var k=r(Ut,2);{let g=o(fe),C=o(()=>e(j)!=="");at(k,{act:"detect",get label(){return e(g)},protected:!0,get disabled(){return e(C)},onrun:()=>{S("detect",He)}})}var K=r(Ot,2);{var ue=g=>{var C=cl(),Ce=X(C),Fe=a(Ce),lt=r(Ce,2);{var mt=wt=>{var zt=ul();rt(zt,21,()=>e(H).slice(0,T),jt=>jt.name,(jt,St)=>{var Kt=Na(),ea=a(Kt),ga=a(ea),er=r(ea,2),tr=a(er);p(()=>{i(ga,e(St).name),i(tr,`${(e(St).description===""?"aucune description USB":e(St).description)??""} + ${e(St).vid===""?"":` — VID ${e(St).vid} PID ${e(St).pid}`}`)}),tt("click",ea,()=>t.draft.set("scale.options.port",e(St).name)),l(jt,Kt)}),l(wt,zt)};m(lt,wt=>{e(H).length>0&&wt(mt)})}p(wt=>i(Fe,wt),[()=>e(H).length===0?"Aucun port série n’est visible depuis ce poste.":Le("port détecté","ports détectés",e(H).length)]),l(g,C)};m(K,g=>{e(ce)&&g(ue)})}var Re=r(K,2);{var We=g=>{var C=vl(),Ce=X(C),Fe=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(se).slice(0,T),mt=>mt.port,(mt,wt)=>{var zt=dl();let jt;var St=a(zt),Kt=a(St),ea=r(St,2),ga=a(ea);p(()=>{jt=Ht(zt,1,"svelte-1p4i0xm",null,jt,{refused:e(wt).refused}),i(Kt,e(wt).port),i(ga,e(wt).message)}),l(mt,zt)}),p(mt=>i(Fe,mt),[()=>Le("port interrogé","ports interrogés",e(se).length)]),l(g,C)};m(Re,g=>{e(se).length>0&&g(We)})}var Ye=r(Re,2),Ke=r(a(Ye),2),U=a(Ke);{let g=o(()=>t.draft.text("scale.type")),C=o(()=>Ee("scale.type")),Ce=o(()=>Te("scale.type")),Fe=o(()=>!e(P));_t(U,{label:"Protocole",path:"scale.type",get value(){return e(g)},hint:"Les valeurs acceptées apparaissent ici si l’enregistrement est refusé.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("scale.type",lt)})}var te=r(U,2);{let g=o(()=>t.draft.text("scale.options.port")),C=o(()=>Ee("scale.options.port")),Ce=o(()=>de("scale.options.port",e(H).map(lt=>lt.name))),Fe=o(()=>!e(P));_t(te,{label:"Port série",path:"scale.options.port",get value(){return e(g)},hint:"Choisissez-le dans la liste détectée ci-dessus plutôt que de le taper : l’écoute permanente ne suit que des ports détectés.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("scale.options.port",lt)})}var ge=r(Ye,2),Ne=a(ge),ke=a(Ne),$e=r(Ne,2);{var Pe=g=>{var C=pl(),Ce=a(C),Fe=r(Ce);{var lt=mt=>{{let wt=o(()=>e(j)==="listen"),zt=o(()=>e(j)!=="");at(mt,{act:"listen",get label(){return e(Ve)},protected:!0,get busy(){return e(wt)},get disabled(){return e(zt)},onrun:()=>{S("listen",I)}})}};m(Fe,mt=>{e(Ve)!==""&&mt(lt)})}p(()=>i(Ce,`${e(b)??""} `)),l(g,C)};m($e,g=>{(e(b)!==""||e(Ve)!=="")&&g(Pe)})}var Ue=r($e,2);{var Ge=g=>{var C=gl();rt(C,21,()=>e(R),fa,(Ce,Fe)=>{var lt=fl(),mt=a(lt),wt=a(mt),zt=r(mt,2),jt=a(zt);p((St,Kt)=>{i(wt,St),i(jt,Kt)},[()=>M(e(Fe)),()=>Me(e(Fe))]),l(Ce,lt)}),l(g,C)};m(Ue,g=>{e(R).length>0&&g(Ge)})}var ft=r(ze,2),ot=a(ft),ct=r(a(ot),2),it=r(a(ct),1,!0),dt=r(ot,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),Rt=a(Bt);{let g=o(()=>e(j)==="printers"),C=o(()=>e(j)!=="");at(Rt,{label:"Lister les files",get busy(){return e(g)},get disabled(){return e(C)},onrun:()=>{S("printers",ie)}})}var Vt=r(Rt,2);{let g=o(()=>e(j)==="discover"),C=o(()=>e(j)!=="");at(Vt,{label:"Rechercher l’imprimante",protected:!0,get busy(){return e(g)},get disabled(){return e(C)},onrun:()=>{S("discover",pe)}})}var kt=r(Vt,2);rt(kt,17,()=>De,g=>g.what,(g,C)=>{{let Ce=o(()=>`Auto-test : ${e(C).name}${e(j)===e(C).what?" — en cours…":""}`),Fe=o(()=>e(j)!=="");at(g,{get act(){return e(C).what},get label(){return e(Ce)},protected:!0,get disabled(){return e(Fe)},onrun:()=>{S(e(C).what,()=>we(e(C).what))}})}});var Et=r(Bt,2);{var Wt=g=>{var C=ml(),Ce=X(C),Fe=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(B).slice(0,T),mt=>mt.name,(mt,wt)=>{var zt=Na(),jt=a(zt),St=a(jt),Kt=r(jt,2),ea=a(Kt);p(()=>{i(St,e(wt).name),i(ea,`${e(wt).detail??""}${e(wt).default?" — file par défaut du système":""}`)}),tt("click",jt,()=>t.draft.set("printer.options.queue",e(wt).name)),l(mt,zt)}),p(mt=>i(Fe,mt),[()=>Le("file d’impression","files d’impression",e(B).length)]),l(g,C)};m(Et,g=>{e(B).length>0&&g(Wt)})}var et=r(Et,2),qt=r(a(et),2),$t=a(qt);{let g=o(()=>t.draft.text("printer.type")),C=o(()=>Ee("printer.type")),Ce=o(()=>Te("printer.type")),Fe=o(()=>!e(P));_t($t,{label:"Driver",path:"printer.type",get value(){return e(g)},hint:"Gardez le driver raster : c’est celui que les postes en service utilisent.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("printer.type",lt)})}var Zt=r($t,2);{let g=o(()=>t.draft.text("printer.options.transport")),C=o(()=>Ee("printer.options.transport")),Ce=o(()=>Te("printer.options.transport")),Fe=o(()=>!e(P));_t(Zt,{label:"Transport",path:"printer.options.transport",get value(){return e(g)},hint:"Local par défaut : une file Windows ou un fichier de périphérique de ce poste.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("printer.options.transport",lt)})}var ra=r(Zt,2);{let g=o(()=>t.draft.text("printer.options.queue")),C=o(()=>Ee("printer.options.queue")),Ce=o(()=>de("printer.options.queue",e(B).map(lt=>lt.name))),Fe=o(()=>!e(P));_t(ra,{label:"File ou chemin",path:"printer.options.queue",get value(){return e(g)},hint:"Choisissez-la dans la liste ci-dessus : une file mal orthographiée ne s’imprime pas.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("printer.options.queue",lt)})}p(g=>{Be(nt,"data-level",e(J).level),i(ut,e(J).word),i(je,e(J).detail),Ye.open=e(Ae),i(ke,e(xe)),Be(ct,"data-level",e(be).level),i(it,e(be).word),i(gt,e(be).detail),i(Nt,g),et.open=e(w)},[()=>q()]),l(n,ve),Ct()}At(["change","click"]);var bl=u(""),wl=u('L’export sera proposé quand le journal aura répondu.'),yl=u('L’export n’est pas proposé : ce poste n’a pas répondu à la lecture du journal.'),xl=u('Exporter en CSV'),kl=u('

                      '),ql=u('

                      '),Sl=u(`

                      Aucune trame brute n’a été enregistrée pour cette pesée : il n’y a + rien à rejouer.

                      `),Ll=u(`

                      La trame repart dans le décodeur du poste EN SERVICE : le poids + affiché au client change, et rien ne le remet comme il était. C’est + ce qui fait d’un refus inexpliqué un test permanent, sans + déplacement au magasin et sans balance.

                      `,1),El=u('

                      Produit
                      Référence
                      Vente
                      Brut / tare / net
                      Stabilité
                      Origine du poids
                      Résultat
                      Trame brute
                      '),zl=u(' ',1),jl=u('

                      QuandProduitNetCode-barresRésultatDuréeDétail
                      ',1),Pl=u('
                      ',1),Tl=u('

                      '),Cl=u(' '),Ol=u(' '),Rl=u('
                    • '),Al=u('

                        ',1),Nl=u('
                        ');function $l(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=200,h=5e3,x=50,O=500,A=7,T={sent:"envoyée à l’imprimante",rejected:"refusée",failed:"en échec",reprint:"réimpression"},H=[{value:"",label:"toutes"},{value:"sent",label:"envoyées à l’imprimante"},{value:"rejected",label:"refusées"},{value:"failed",label:"en échec"},{value:"reprint",label:"réimpressions"}],B={scale:"balance",manual:"saisie manuelle",replay:"trame rejouée"},se={stable:"stable",unstable:"instable",unknown:"non déclarée par la balance",not_applicable:"sans objet — saisie manuelle"},_e={by_weight:"au poids",by_unit:"à l’unité"},ye={debug:"mise au point",info:"information",warn:"avertissement",error:"erreur",critical:"critique"};let ce=$(Lt([])),j=$(Lt([])),V=$(""),le=$(null),re=$(!1),W=$(!1),D=$("loading"),Y=$("loading"),z=$(""),P=$("");const G=o(()=>({limit:String(c),...e(V)===""?{}:{result:e(V)}})),E=o(()=>({...e(G),limit:String(h)})),b=o(()=>e(ce).find(I=>I.id===e(le))??null),R=o(()=>H.find(I=>I.value===e(V))?.label??""),J=o(()=>Ae(e(ce).length,c,"pesée","pesées",`L’export CSV en emporte jusqu’à ${me(h)}.`)),be=o(()=>Ae(e(j).length,x,"ligne","lignes",`Le fichier de diagnostic emporte les ${me(O)} dernières.`));xe();async function xe(){v(le,null),v(W,!0),v(D,"loading"),v(Y,"loading"),v(ce,[],!0),v(j,[],!0),v(z,""),v(P,"");try{const I=await s().load(()=>Mr(e(G)));v(D,I===null?"unread":"read",!0),v(z,I===null?s().actionError:"",!0),v(ce,I??[],!0);const Q=await s().load(()=>Vr({limit:String(x)}));v(Y,Q===null?"unread":"read",!0),v(P,Q===null?s().actionError:"",!0),v(j,Q??[],!0)}finally{v(W,!1),s().actionError=""}}async function Ve(I){s().notice="",s().actionError="",v(re,!0);try{const Q=await s().protect(()=>Jr(I));Q!==null&&(s().notice=Q.message)}finally{v(re,!1)}}function Ae(I,Q,qe,oe,S){const Z=I>1?oe:qe;return I{var qe=Pl(),oe=X(qe),S=r(a(oe),2);rt(S,21,()=>H,f=>f.value,(f,q)=>{var L=bl(),he=a(L),ne={};p(()=>{i(he,e(q).label),ne!==(ne=e(q).value)&&(L.value=(L.__value=e(q).value)??"")}),l(f,L)});var Z=r(S,2);at(Z,{label:"Rafraîchir",get busy(){return e(W)},onrun:()=>{xe()}});var ie=r(Z,2);{var pe=f=>{var q=wl();l(f,q)},we=f=>{var q=yl();l(f,q)},He=f=>{var q=xl();p(L=>Be(q,"href",L),[()=>Br(e(E))]),l(f,q)};m(ie,f=>{e(D)==="loading"?f(pe):e(D)==="unread"?f(we,1):f(He,-1)})}var Xe=r(oe,2);{var fe=f=>{var q=kl(),L=a(q);p(he=>i(L,`L’export emporte le même filtre que le tableau, mais pas son plafond : il descend + jusqu’à ${he??""} pesées, en point-virgule et en UTF-8 — il s’ouvre + tel quel dans le tableur d’un Windows français. Il ne demande aucun mot de passe : la + lecture du journal n’en demande pas non plus, et le fichier de diagnostic emporte déjà + les deux cents dernières pesées.`),[()=>me(h)]),l(f,q)};m(Xe,f=>{e(D)==="read"&&f(fe)})}var d=r(Xe,2);{var y=f=>{var q=ql(),L=a(q);{var he=de=>{var M=yt("Lecture du journal…");l(de,M)},ne=de=>{var M=yt();p(()=>i(M,`Le journal n’a pas pu être lu : ${e(z)??""} Ce n’est pas « aucune pesée ».`)),l(de,M)},Se=de=>{var M=yt("Le journal ne contient aucune pesée.");l(de,M)},Le=de=>{var M=yt();p(()=>i(M,`Aucune pesée ne correspond au filtre « ${e(R)??""} ».`)),l(de,M)};m(L,de=>{e(D)==="loading"?de(he):e(D)==="unread"?de(ne,1):e(V)===""?de(Se,2):de(Le,-1)})}l(f,q)},ee=f=>{var q=jl(),L=X(q),he=a(L),ne=r(L,2),Se=a(ne),Le=r(a(Se));rt(Le,21,()=>e(ce),de=>de.id,(de,M)=>{var Ie=zl(),Me=X(Ie);let Ee;var Te=a(Me),Je=a(Te),ae=r(Te),De=a(ae),ve=r(ae),ze=a(ve),Oe=r(ve),nt=a(Oe),ut=r(Oe),pt=a(ut),je=r(ut),st=a(je),Ze=r(je),bt=a(Ze),Ot=a(bt),Ut=r(Me,2);{var k=K=>{var ue=El(),Re=a(ue);Be(Re,"colspan",A);var We=a(Re),Ye=a(We),Ke=r(We,2),U=r(a(Ke),2),te=a(U),ge=r(U,4),Ne=a(ge),ke=r(ge,4),$e=a(ke),Pe=r(ke,4),Ue=a(Pe),Ge=r(Pe,4),ft=a(Ge),ot=r(Ge,4),ct=a(ot),it=r(ot,4),dt=a(it),gt=r(it,4),xt=a(gt),Nt=a(xt),Bt=r(Ke,2);{var Rt=kt=>{var Et=Sl();l(kt,Et)},Vt=kt=>{var Et=Ll(),Wt=X(Et),et=a(Wt);at(et,{kind:"destructive",label:"Rejouer cette trame",protected:!0,get busy(){return e(re)},onrun:()=>{Ve(e(b).frame)}}),l(kt,Et)};m(Bt,kt=>{e(b).frame===""?kt(Rt):kt(Vt,-1)})}p((kt,Et,Wt,et,qt,$t,Zt,ra,g)=>{Be(ue,"data-detail",e(M).id),i(Ye,`Pesée ${e(b).id??""}`),i(te,`${e(b).product_name??""} (${e(b).product_id??""})`),i(Ne,e(b).reference),i($e,`${kt??""} — + ${Et??""} + ${e(b).quantity>1?"unités":"unité"}`),i(Ue,`${Wt??""} g / ${et??""} g / + ${qt??""} g`),i(ft,`${$t??""} — cadence + médiane ${Zt??""}`),i(ct,ra),i(dt,`${g??""}${e(b).detail===""?"":` — ${e(b).detail}`}`),i(Nt,e(b).frame===""?"aucune trame enregistrée":e(b).frame)},[()=>w(_e,e(b).mode,"mode de vente inconnu"),()=>me(e(b).quantity),()=>me(e(b).gross_g),()=>me(e(b).tare_g),()=>me(e(b).net_g),()=>w(se,e(b).stability,"stabilité inconnue"),()=>ca(e(b).rate_ms),()=>w(B,e(b).source,"origine inconnue"),()=>w(T,e(b).result,"résultat inconnu")]),l(K,ue)};m(Ut,K=>{e(le)===e(M).id&&e(b)!==null&&K(k)})}p((K,ue,Re,We)=>{Ee=Ht(Me,1,"svelte-86o8oz",null,Ee,{open:e(le)===e(M).id}),i(Je,K),i(De,e(M).product_name),i(ze,`${ue??""} g`),i(nt,e(M).barcode),i(pt,Re),i(st,We),Be(bt,"aria-expanded",e(le)===e(M).id),i(Ot,e(le)===e(M).id?"fermer":"détail")},[()=>Mt(e(M).occurred_at),()=>me(e(M).net_g),()=>w(T,e(M).result,"résultat inconnu"),()=>ca(e(M).duration_ms)]),tt("click",bt,()=>v(le,e(le)===e(M).id?null:e(M).id,!0)),l(de,Ie)}),p(()=>i(he,e(J))),l(f,q)};m(d,f=>{e(ce).length===0?f(y):f(ee,-1)})}tt("change",S,()=>{xe()}),_r(S,()=>e(V),f=>v(V,f)),l(I,qe)},$$slots:{default:!0}});var F=r(_,2);Qe(F,{title:"Journal technique",children:(I,Q)=>{var qe=Dt(),oe=X(qe);{var S=ie=>{var pe=Tl(),we=a(pe);{var He=d=>{var y=yt("Lecture du journal technique…");l(d,y)},Xe=d=>{var y=yt();p(()=>i(y,`Le journal technique n’a pas pu être lu : ${e(P)??""} Ce n’est pas « aucune + ligne ».`)),l(d,y)},fe=d=>{var y=yt("Aucune ligne technique.");l(d,y)};m(we,d=>{e(Y)==="loading"?d(He):e(Y)==="unread"?d(Xe,1):d(fe,-1)})}l(ie,pe)},Z=ie=>{var pe=Al(),we=X(pe),He=a(we),Xe=r(we,2),fe=a(Xe);rt(fe,21,()=>e(j),d=>d.id,(d,y)=>{var ee=Rl(),f=a(ee),q=a(f),L=r(f,2),he=a(L),ne=r(L,2),Se=a(ne),Le=r(ne,2);{var de=Te=>{var Je=Cl(),ae=a(Je);p(()=>i(ae,e(y).code)),l(Te,Je)};m(Le,Te=>{e(y).code!==""&&Gt.showTechnicalNames&&Te(de)})}var M=r(Le,2),Ie=a(M),Me=r(M,2);{var Ee=Te=>{var Je=Ol(),ae=a(Je);p(()=>i(ae,e(y).detail)),l(Te,Je)};m(Me,Te=>{e(y).detail!==""&&Te(Ee)})}p((Te,Je,ae)=>{Be(ee,"data-level",e(y).level),i(q,Te),i(he,Je),i(Se,ae),i(Ie,e(y).message)},[()=>Mt(e(y).occurred_at),()=>w(ye,e(y).level,"niveau inconnu"),()=>Xa(e(y).source)]),l(d,ee)}),p(()=>i(He,e(be))),l(ie,pe)};m(oe,ie=>{e(j).length===0?ie(S):ie(Z,-1)})}l(I,qe)},$$slots:{default:!0}}),l(n,N),Ct()}At(["change","click"]);var Dl=u('

                        '),Il=u('

                        '),Ul=u('

                        '),Fl=u('

                        '),Ml=u('

                        Lecture de la configuration en cours… les flèches attendent qu’elle soit arrivée.

                        '),Bl=u(`
                        Aperçu de l’étiquette telle qu’elle serait imprimée

                        Décalage horizontal

                        Décalage vertical

                        Le décalage ne descend pas sous zéro : le poste refuse un décalage négatif quel que + soit le gabarit. Le maximum, lui, dépend de la géométrie du gabarit ; il est annoncé + ici si l’enregistrement le dépasse.

                        Le symbole code-barres est volontairement tronqué : un symbole conforme n’entre pas + sur 40 × 25 mm avec les cinq champs texte. Ce n’est pas un défaut de rendu et il n’y + a rien à corriger.

                        Le détail chiffré du symbole — largeur de module, modules rendus, modules attendus — + n’est pas servi par ce poste. Cet écran n’affiche pas un chiffre qu’il aurait deviné.

                        `,1),Vl=u(`

                        Chaque appui sort une étiquette pour de bon : le mot de passe est demandé au moment + d’imprimer, et l’impression repart d’elle-même une fois la session ouverte.

                        `,1),Wl=u('
                        ');function Gl(n,t){Tt(t,!0);const s=vt(t,"admin",7),c="printer.template",h="printer.options.offset_x",x="printer.options.offset_y",O=0,A="L’aperçu n’a pas pu être rendu par le poste.",T="Le poste a rendu l’aperçu, mais le navigateur ne l’a pas affiché.",H=[{what:"alignment",label:"Imprimer la mire d’alignement"},{what:"ruler",label:"Imprimer la réglette"}];let B=$(1),se=$(!0),_e=$(!1),ye=$(""),ce=$(null),j=$(""),V=null,le=!1;const re=o(()=>t.draft.text(c)),W=o(()=>Fr(e(re),e(se),e(_e),e(B))),D=o(()=>t.draft.config!==null),Y=o(()=>t.draft.number(h)),z=o(()=>t.draft.number(x)),P=o(()=>F(h)),G=o(()=>F(x)),E=o(w),b=o(()=>s().busy||e(j)!=="");aa(()=>{if(t.draft.config===null)return;if(t.draft.dirty){V===null&&R();return}const S={x:t.draft.number(h),y:t.draft.number(x)};if(V!==null&&V.x===S.x&&V.y===S.y)return;const Z=V===null;V=S,v(ce,S,!0),Z||xe()});async function R(){if(!le){le=!0;try{const S=await qa(),Z={x:J(S.config,h),y:J(S.config,x)};V=Z,v(ce,Z,!0)}catch{}finally{le=!1}}}function J(S,Z){let ie=S;for(const pe of Z.split(".")){if(ie===null||typeof ie!="object")return 0;ie=ie[pe]}return typeof ie=="number"?ie:0}function be(S,Z){const ie=t.draft.number(S)+Z;ie=2?"s":""}`}function _(S,Z){const ie=Number(Z);Z.trim()===""||Number.isNaN(ie)||t.draft.set(S,ie)}function F(S){return t.draft.faults.find(Z=>Z.field===S)?.message??""}async function I(S){if(!e(b)){v(j,S,!0),s().actionError="",s().notice="";try{const Z=await s().protect(()=>Ha(S));if(Z===null)return;s().notice=Z.message,await s().refresh()}finally{v(j,"")}}}var Q=Wl(),qe=a(Q);Qe(qe,{title:"Aperçu de l’étiquette",note:"Le même moteur que l’impression (A2) : le décalage se voit parce qu’il est cuit dans le bitmap. L’image porte le gabarit en cours d’édition, mais le décalage ENREGISTRÉ — jamais celui que les flèches sont en train de régler.",children:(S,Z)=>{var ie=Bl(),pe=X(ie);{var we=je=>{var st=Dl(),Ze=a(st);p(()=>i(Ze,e(E))),l(je,st)};m(pe,je=>{e(E)!==""&&je(we)})}var He=r(pe,2),Xe=a(He),fe=a(Xe),d=r(fe,2);{var y=je=>{var st=Il(),Ze=a(st);p(()=>i(Ze,e(ye))),l(je,st)};m(d,je=>{e(ye)!==""&&je(y)})}var ee=r(Xe,2),f=a(ee),q=r(a(f),2),L=a(q),he=r(f,2),ne=a(he);{let je=o(()=>!e(D)||e(Y)<=O);at(ne,{label:"← 1 dot",get disabled(){return e(je)},onrun:()=>be(h,-1)})}var Se=r(ne,2);{let je=o(()=>!e(D));at(Se,{label:"1 dot →",get disabled(){return e(je)},onrun:()=>be(h,1)})}var Le=r(he,2);{var de=je=>{var st=Ul(),Ze=a(st);p(()=>i(Ze,e(P))),l(je,st)};m(Le,je=>{e(P)!==""&&je(de)})}var M=r(Le,2),Ie=r(a(M),2),Me=a(Ie),Ee=r(M,2),Te=a(Ee);{let je=o(()=>!e(D)||e(z)<=O);at(Te,{label:"↑ 1 dot",get disabled(){return e(je)},onrun:()=>be(x,-1)})}var Je=r(Te,2);{let je=o(()=>!e(D));at(Je,{label:"1 dot ↓",get disabled(){return e(je)},onrun:()=>be(x,1)})}var ae=r(Ee,2);{var De=je=>{var st=Fl(),Ze=a(st);p(()=>i(Ze,e(G))),l(je,st)};m(ae,je=>{e(G)!==""&&je(De)})}var ve=r(ae,4),ze=a(ve),Oe=r(ve,2),nt=a(Oe),ut=r(Oe,2);{var pt=je=>{var st=Ml();l(je,st)};m(ut,je=>{e(D)||je(pt)})}p((je,st)=>{Be(fe,"src",e(W)),i(L,je),i(Me,st)},[()=>N(e(Y)),()=>N(e(z))]),Qt("load",fe,()=>v(ye,"")),Qt("error",fe,()=>{Ve(e(W))}),tt("change",ze,()=>xe()),za(ze,()=>e(se),je=>v(se,je)),tt("change",nt,()=>xe()),za(nt,()=>e(_e),je=>v(_e,je)),l(S,ie)},$$slots:{default:!0}});var oe=r(qe,2);Qe(oe,{title:"Gabarit et impression",children:(S,Z)=>{var ie=Vl(),pe=X(ie);{let d=o(()=>F(c)),y=o(()=>!e(D));_t(pe,{label:"Gabarit",path:c,get value(){return e(re)},hint:"Le gabarit reproduit à l’identique s’appelle weighing_identical (A1).",get fault(){return e(d)},get disabled(){return e(y)},onchange:ee=>{t.draft.set(c,ee),xe()}})}var we=r(pe,2);{let d=o(()=>t.draft.text("printer.options.darkness")),y=o(()=>F("printer.options.darkness")),ee=o(()=>!e(D));_t(we,{label:"Noircissement",path:"printer.options.darkness",kind:"number",get value(){return e(d)},hint:"Trop bas, l’étiquette pâlit au soleil ; trop haut, elle bave et le scanner refuse.",get fault(){return e(y)},get disabled(){return e(ee)},onchange:f=>_("printer.options.darkness",f)})}var He=r(we,2);{let d=o(()=>t.draft.text("printer.options.speed")),y=o(()=>F("printer.options.speed")),ee=o(()=>!e(D));_t(He,{label:"Vitesse",path:"printer.options.speed",kind:"number",get value(){return e(d)},get fault(){return e(y)},get disabled(){return e(ee)},onchange:f=>_("printer.options.speed",f)})}var Xe=r(He,2);{let d=o(()=>t.draft.text("printer.options.copies")),y=o(()=>F("printer.options.copies")),ee=o(()=>!e(D));_t(Xe,{label:"Exemplaires",path:"printer.options.copies",kind:"number",get value(){return e(d)},hint:"Un client repart avec une étiquette : deux exemplaires se justifient, pas se devinent.",get fault(){return e(y)},get disabled(){return e(ee)},onchange:f=>_("printer.options.copies",f)})}var fe=r(Xe,2);rt(fe,21,()=>H,d=>d.what,(d,y)=>{{let ee=o(()=>e(j)===e(y).what);at(d,{get act(){return e(y).what},get label(){return e(y).label},protected:!0,get busy(){return e(ee)},get disabled(){return e(b)},onrun:()=>{I(e(y).what)}})}}),l(S,ie)},$$slots:{default:!0}}),l(n,Q),Ct()}At(["change"]);var Hl=u(" "),Jl=u(' '),Kl=u(''),Xl=u('

                        Aucun tarif déclaré dans la configuration lue.

                        '),Yl=u('Prix du catalogue Odoo — pas de remise'),$a=u(' '),Ql=u(' % ',1),Zl=u(' '),ei=u(`

                        CodeLibelléAbrégéRemiseOrdre

                        Un champ vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la + retrouve dès qu’on quitte le champ. Une remise effacée serait le plein tarif pour + tous les adhérents.

                        `,1),ti=u(" ",1),ai=u(`

                        Les deux arrondis sont distincts parce qu’ils tombent à des endroits différents du + calcul : arrondir le prix au kilo puis le multiplier, ou multiplier puis arrondir, ne + donne pas le même centime sur une étiquette. Le poste applique l’arrondi commercial, + et l’écart se voit au centime près sur la même pesée.

                        `,1),ri=u('

                        Rien ne s’affiche au client : c’est une information.

                        '),ni=u('

                        '),si=u('
                        '),li=u('

                        '),ii=u('
                      • '),oi=u(`

                        Aucun verdict en ce moment : ces lignes portent sur la pesée EN COURS, et le poste + est au repos. Elles apparaissent le temps d’un cycle, quand un client pose son sac + et touche une tuile.

                        `),ui=u(' '),ci=u(' ',1),di=u('
                      • '),vi=u('

                          ',1),pi=u(`

                          L’ordre compte : le premier verdict bloquant décide de ce que le client lit. Les + garde-fous 1 à 7 portent sur l’état de la balance, c’est-à-dire le poids brut ; les + garde-fous 8 à 14 portent sur la vente, c’est-à-dire le net. Le code en gris est + celui du journal, celui qu’on lit au téléphone.

                            Un seuil vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la retrouve + dès qu’on quitte le champ. Pour changer un seuil, on tape l’autre valeur.

                            Ce que les garde-fous disent de la pesée en cours

                            `,1),fi=u(`

                            Le plan de numérotation n’est PAS ici, et c’est tout l’intérêt : préfixes, largeur de + la référence, largeur de la charge utile, décimales et mode de vente sont une + constante du binaire, indexée par préfixe et vérifiée au démarrage. + Un champ qui change le SENS du code lu par la caisse n’est pas un réglage, c’est un + contrat externe : il change avec une version du binaire, relue et testée, jamais + depuis l’écran d’un poste.

                            `,1),gi=u('

                            Aucune dérogation : la limite générale s’applique à tous les produits.

                            '),mi=u(`

                            Les noms de produits n’ont pas pu être lus : le catalogue en service n’a pas + répondu. Les identifiants Odoo restent affichés.

                            `),hi=u(`Produit retiré : le garde-fou 14 refuse le produit avant que cette + dérogation ait un sens.`),_i=u('
                          1. '),bi=u('

                              ',1),wi=u('
                              ');function yi(n,t){Tt(t,!0);const s=(_,F=sa,I=sa,Q=sa)=>{var qe=Kl(),oe=a(qe),S=r(oe,2),Z=a(S),ie=a(Z),pe=r(Z,2);{var we=fe=>{var d=Hl(),y=a(d);p(()=>i(y,F())),l(fe,d)};m(pe,fe=>{Gt.showTechnicalNames&&fe(we)})}var He=r(pe,2);{var Xe=fe=>{var d=Jl(),y=a(d);p(()=>i(y,Q())),l(fe,d)};m(He,fe=>{Q()!==""&&fe(Xe)})}p((fe,d)=>{Be(qe,"data-flag",F()),Be(qe,"data-on",fe),ua(oe,d),i(ie,I())},[()=>String(t.draft.flag(F())),()=>t.draft.flag(F())]),tt("change",oe,fe=>t.draft.set(F(),fe.currentTarget.checked)),l(_,qe)},c=20,h=o(()=>se(t.draft)),x=o(()=>t.health.state.diagnostics),O=o(()=>t.health.decisions.filter(_=>_.min_weight_g!==null)),A=o(()=>e(O).filter(_=>!_.offered)),T=o(()=>e(O).slice(0,c));let H=$(Lt({})),B=$("loading");W();function se(_){const F=_.value("pricing.tiers");return Array.isArray(F)?F.map(I=>{const Q=I??{},qe=Q.discount_percent;return{code:String(Q.code??""),label:String(Q.label??""),abbrev:String(Q.abbrev??""),written:qe===void 0?null:String(qe),discount:_e(qe)?qe:null,rank:Number(Q.rank??0)}}):[]}function _e(_){return typeof _!="number"||!Number.isFinite(_)||_<0||_>100?!1:Math.abs(_*10-Math.round(_*10))<1e-9}const ye=o(()=>String(t.draft.value("pricing.reference_code")??""));function ce(_){return _===null?"":String(_).replace(".",",")}function j(_){const F=1e3-Math.round(_*10);return`${String(Math.trunc(F/100))},${String(F%100).padStart(2,"0")}`}function V(_,F){const I=Number(F);F.trim()===""||Number.isNaN(I)||t.draft.set(_,I)}function le(_,F){const I=F.trim().replace(",",".");if(!/^\d{1,3}(\.\d)?$/u.test(I))return;const Q=Number(I);Q>100||t.draft.set(_,Q)}function re(_,F){!(_ instanceof HTMLInputElement)||_.value===F||(_.value=F)}async function W(){try{const _=await Ba();v(H,Object.fromEntries(_.products.map(F=>[F.id,F.name])),!0),v(B,"read")}catch{v(B,"unread")}}function D(_){const F=e(H)[_];return F!==void 0?F:e(B)==="loading"?"Lecture du nom…":e(B)==="read"?"Produit absent du catalogue en service":"Nom non lu"}function Y(_){return _<=1?`${String(_)} tarif déclaré`:`${String(_)} tarifs déclarés`}const z=o(()=>`${me(e(x).length)} ${e(x).length>1?"verdicts":"verdict"} sur les quatorze garde-fous.`),P=o(()=>`${me(e(T).length)} ${e(T).length>1?"lignes affichées":"ligne affichée"} sur ${me(e(O).length)} `+(e(A).length===0?`${e(O).length>1?"dérogations en vigueur":"dérogation en vigueur"}.`:`${e(O).length>1?"dérogations enregistrées":"dérogation enregistrée"}, dont ${me(e(A).length)} sur un produit retiré, sans effet : le garde-fou 14 refuse le produit avant que le 8 ait un sens.`)+(e(O).length>e(T).length?" Les autres se lisent produit par produit depuis l’onglet Catalogue.":""));function G(_){return`plancher ${me(_)} g : refusé à ${me(_)} g et en dessous`}const E=[{rank:1,code:"OVERLOAD",label:"Surcharge",when:"La balance annonce elle-même OL, ou le poids brut dépasse la capacité.",severity:"Bloquant",blocking:!0,message:"La balance est en surcharge. Retirez votre article.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la capacité, réglée au garde-fou 9 sous « ${Ft("limits.max_weight_g")} ».`},{rank:2,code:"MEASUREMENT_EXPIRED",label:"Poids périmé",when:"La mesure est plus vieille que la péremption, dans les deux modes de stabilité.",severity:"Bloquant",blocking:!0,message:"Poids indisponible. Patientez ou appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil à régler : le poste calcule lui-même à partir du rythme de la balance."},{rank:3,code:"BASKET_MISSING",label:"Panier absent",when:"Le poids brut tombe dans la fenêtre négative du panier : il a été soulevé.",severity:"Bloquant",blocking:!0,message:"Le panier n'est pas sur la balance. Reposez-le.",thresholds:[{path:"limits.basket_min_g",label:"Bas de la fenêtre du panier",hint:"En grammes, NÉGATIF : c’est le poids du panier que la balance a perdu."},{path:"limits.basket_max_g",label:"Haut de la fenêtre du panier",hint:"Négatif lui aussi, et plus proche de zéro que le bas."}],switchPath:"limits.basket_check_enabled",switchLabel:"Ce poste travaille avec un panier taré",note:"La règle s’active ou non, en bloc : il n’y a pas de demi-mesure à régler."},{rank:4,code:"SCALE_EMPTY",label:"Plateau vide",when:"Le poids brut ne sort pas de la bande « il n’y a rien sur le plateau ».",severity:"Bloquant — un filet, hors parcours nominal",blocking:!0,message:"Posez votre produit.",thresholds:[{path:"limits.empty_max_g",label:"Plateau considéré vide",hint:"En dessous, le poste considère qu’il n’y a rien sur le plateau."}],switchPath:"",switchLabel:"",note:"Toucher une tuile sur un plateau vide ARME la sélection au lieu d’être refusé : la règle reste évaluée pour la saisie manuelle et les chemins dérivés."},{rank:5,code:"TARE_REQUIRED",label:"Remise à zéro nécessaire",when:"Le brut est sous la bande du plateau vide, et hors de la fenêtre du panier.",severity:"Bloquant",blocking:!0,message:"La balance doit être remise à zéro.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la valeur négative de celui du garde-fou 4, « ${Ft("limits.empty_max_g")} ».`},{rank:6,code:"WEIGHT_UNSTABLE",label:"Pesée instable",when:"La trame déclare la mesure instable.",severity:"Information par défaut (A3)",blocking:!1,message:"Pesée en cours…",thresholds:[],switchPath:"",switchLabel:"",note:"La sévérité suit l’exigence de stabilité : elle passe à Bloquant quand celle-ci est réglée sur « blocking ». L’impression n’est jamais bloquée par défaut."},{rank:7,code:"TARE_INVALID",label:"Emballage incohérent",when:"Une tare a été saisie, et elle atteint la pesée ou dépasse le maximum.",severity:"Bloquant",blocking:!0,message:"Le poids de l'emballage est supérieur ou égal à la pesée.",thresholds:[{path:"limits.max_tare_g",label:"Tare maximum",hint:"Une tare plus lourde que le maximum est une faute de frappe."}],switchPath:"",switchLabel:"",note:""},{rank:8,code:"WEIGHT_TOO_LOW",label:"Poids trop faible",when:"Vente au poids : le NET est strictement positif et ne dépasse pas le plancher.",severity:"Bloquant",blocking:!0,message:"La balance doit être retarée, ou l'emballage est trop lourd.",thresholds:[{path:"limits.min_weight_g",label:"Poids minimum",hint:"Une dérogation par produit existe, dans l’onglet Catalogue."}],switchPath:"",switchLabel:"",note:""},{rank:9,code:"WEIGHT_TOO_HIGH",label:"Poids trop élevé",when:"Le NET dépasse la capacité — strictement, pour que la capacité reste atteignable.",severity:"Bloquant",blocking:!0,message:"{{.Weight}} kg, ça paraît un peu lourd !",thresholds:[{path:"limits.max_weight_g",label:"Poids maximum",hint:"C’est la capacité du champ NNDDD du code-barres, pas un seuil de vraisemblance."}],switchPath:"",switchLabel:"",note:""},{rank:10,code:"UNITS_OUT_OF_RANGE",label:"Nombre d’unités hors plage",when:"Vente à l’unité : la quantité sort de la plage.",severity:"Bloquant",blocking:!0,message:"{{.Quantity}} unités, ça paraît un peu beaucoup !",thresholds:[{path:"limits.min_units",label:"Unités minimum",hint:""},{path:"limits.max_units",label:"Unités maximum",hint:""}],switchPath:"",switchLabel:"",note:""},{rank:11,code:"AMOUNT_OUT_OF_CAPACITY",label:"Montant hors capacité du code-barres",when:"La charge utile encode un PRIX, et il dépasse ce que le champ peut porter.",severity:"Bloquant",blocking:!0,message:"Prix trop élevé pour le code-barres.",thresholds:[{path:"limits.max_amount_cents",label:"Montant maximum",hint:"En centimes. Aucun préfixe du plan livré n’encode un prix : la règle est éprouvée sans qu’aucun produit puisse l’atteindre."}],switchPath:"",switchLabel:"",note:""},{rank:12,code:"ZERO_PRICE",label:"Prix nul",when:"Le montant du tarif imprimé en grand vaut zéro.",severity:"Bloquant",blocking:!0,message:"Prix nul. Appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : un produit à 0 € est une anomalie sans nuance."},{rank:13,code:"LIGHT_PRODUCT_ALLOWED",label:"Produit léger autorisé",when:"Le garde-fou 8 n’a pas déclenché grâce à la dérogation du produit.",severity:"Information",blocking:!1,message:"",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil général : c’est la dérogation par produit, listée plus bas et posée depuis l’onglet Catalogue. Rien ne s’affiche au client ; l’id du produit est journalisé."},{rank:14,code:"PRODUCT_WITHDRAWN",label:"Produit retiré",when:"Quelqu’un a décidé de ne plus proposer ce produit.",severity:"Bloquant",blocking:!0,message:"Ce produit n'est pas disponible.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : c’est une décision humaine, prise depuis l’onglet Catalogue. Aucune règle d’import ne peut la déduire."}];function b(_){return E.find(F=>F.code===_)?.label??"Garde-fou inconnu de cet écran"}const R=["{{.Weight}}","{{.Quantity}}"];function J(_){return _==="blocking"?"Bloquant":_==="info"?"Information":"Sévérité inconnue de cet écran"}var be=wi(),xe=a(be);Qe(xe,{title:"Grille de tarifs",note:"Un second tarif n’est pas une case à cocher : c’est une ligne de plus dans cette grille.",children:(_,F)=>{var I=ti(),Q=X(I);{var qe=ie=>{var pe=Xl();l(ie,pe)},oe=ie=>{var pe=ei(),we=X(pe),He=a(we),Xe=r(we,2),fe=a(Xe),d=r(a(fe));rt(d,21,()=>e(h),fa,(y,ee,f)=>{var q=Zl(),L=a(q),he=a(L),ne=r(L),Se=a(ne);Be(Se,"aria-label",`Libellé du tarif ${f+1}`);var Le=r(ne),de=a(Le);Be(de,"aria-label",`Abrégé du tarif ${f+1}`);var M=r(Le),Ie=a(M);{var Me=ve=>{var ze=Yl();l(ve,ze)},Ee=ve=>{var ze=$a(),Oe=a(ze);p(()=>i(Oe,`${e(ee).written??""} — le tarif de référence est le prix du catalogue : il + ne peut pas porter de remise, et l’enregistrement la refusera.`)),l(ve,ze)},Te=ve=>{var ze=$a(),Oe=a(ze);p(()=>i(Oe,`${e(ee).written??""} — une remise s’écrit au dixième de point ; celle-ci se + change dans le fichier de configuration.`)),l(ve,ze)},Je=ve=>{var ze=Ql(),Oe=X(ze);Be(Oe,"aria-label",`Remise du tarif ${f+1}`);var nt=r(Oe,2),ut=a(nt);p((pt,je)=>{na(Oe,pt),i(ut,`un produit à 10,00 €/kg s’affiche ${je??""} €/kg`)},[()=>ce(e(ee).discount??0),()=>j(e(ee).discount??0)]),tt("input",Oe,pt=>le(`pricing.tiers.${String(f)}.discount_percent`,pt.currentTarget.value)),tt("focusout",Oe,pt=>re(pt.currentTarget,ce(e(ee).discount??0))),l(ve,ze)};m(Ie,ve=>{e(ee).code===e(ye)&&e(ee).written===null?ve(Me):e(ee).code===e(ye)?ve(Ee,1):e(ee).written!==null&&e(ee).discount===null?ve(Te,2):ve(Je,-1)})}var ae=r(M),De=a(ae);p(()=>{i(he,e(ee).code),na(Se,e(ee).label),na(de,e(ee).abbrev),i(De,e(ee).rank)}),tt("input",Se,ve=>t.draft.set(`pricing.tiers.${String(f)}.label`,ve.currentTarget.value)),tt("input",de,ve=>t.draft.set(`pricing.tiers.${String(f)}.abbrev`,ve.currentTarget.value)),l(y,q)}),p(y=>i(He,`${y??""}.`),[()=>Y(e(h).length)]),l(ie,pe)};m(Q,ie=>{e(h).length===0?ie(qe):ie(oe,-1)})}var S=r(Q,2);{let ie=o(()=>t.draft.text("pricing.primary_code"));_t(S,{label:"Tarif imprimé en grand",path:"pricing.primary_code",get value(){return e(ie)},hint:"Le prix que le client lit sur l’étiquette (A7).",onchange:pe=>t.draft.set("pricing.primary_code",pe)})}var Z=r(S,2);{let ie=o(()=>t.draft.text("pricing.reference_code"));_t(Z,{label:"Tarif qui serait encodé si le code-barres portait un prix",path:"pricing.reference_code",get value(){return e(ie)},hint:"Le plan livré ne porte pas de prix, mais un poids ou un nombre d’unités : la caisse retrouve le prix par la référence, dans Odoo, et ne doit jamais sous-facturer.",onchange:pe=>t.draft.set("pricing.reference_code",pe)})}l(_,I)},$$slots:{default:!0}});var Ve=r(xe,2);Qe(Ve,{title:"Les deux arrondis",children:(_,F)=>{var I=ai(),Q=X(I);{let oe=o(()=>t.draft.text("pricing.unit_price_rounding"));_t(Q,{label:"Arrondi du prix unitaire dérivé",path:"pricing.unit_price_rounding",get value(){return e(oe)},hint:"Il s’applique au prix au kilo calculé par la remise.",onchange:S=>t.draft.set("pricing.unit_price_rounding",S)})}var qe=r(Q,2);{let oe=o(()=>t.draft.text("pricing.amount_rounding"));_t(qe,{label:"Arrondi du montant",path:"pricing.amount_rounding",get value(){return e(oe)},hint:"Il s’applique au montant de l’étiquette.",onchange:S=>t.draft.set("pricing.amount_rounding",S)})}l(_,I)},$$slots:{default:!0}});var Ae=r(Ve,2);Qe(Ae,{title:"Les quatorze garde-fous, dans l’ordre d’évaluation",note:"Le seuil se modifie ici. La sévérité ne se règle pas : elle dit si le poste refuse ou avertit, et cela ne dépend pas du magasin. Le message affiché au client n’est pas encore modifiable depuis cet écran.",children:(_,F)=>{var I=pi(),Q=r(X(I),2);rt(Q,21,()=>E,pe=>pe.code,(pe,we)=>{var He=ii(),Xe=a(He),fe=a(Xe),d=a(fe),y=r(fe,2),ee=a(y),f=r(y,2),q=a(f),L=r(f,2),he=a(L),ne=r(Xe,2),Se=a(ne),Le=r(ne,2);{var de=ae=>{var De=ri();l(ae,De)},M=ae=>{var De=ni(),ve=a(De);p(()=>i(ve,`« ${e(we).message??""} »`)),l(ae,De)};m(Le,ae=>{e(we).message===""?ae(de):ae(M,-1)})}var Ie=r(Le,2);{var Me=ae=>{s(ae,()=>e(we).switchPath,()=>e(we).switchLabel,()=>"")};m(Ie,ae=>{e(we).switchPath!==""&&ae(Me)})}var Ee=r(Ie,2);rt(Ee,17,()=>e(we).thresholds,ae=>ae.path,(ae,De)=>{var ve=si(),ze=a(ve);{let Oe=o(()=>t.draft.text(e(De).path));_t(ze,{get label(){return e(De).label},get path(){return e(De).path},kind:"number",get value(){return e(Oe)},get hint(){return e(De).hint},onchange:nt=>V(e(De).path,nt)})}tt("focusout",ve,Oe=>re(Oe.target,t.draft.text(e(De).path))),l(ae,ve)});var Te=r(Ee,2);{var Je=ae=>{var De=li(),ve=a(De);p(()=>i(ve,e(we).note)),l(ae,De)};m(Te,ae=>{e(we).note!==""&&ae(Je)})}p(ae=>{Be(He,"data-code",e(we).code),i(d,e(we).rank),i(ee,e(we).label),i(q,e(we).code),Be(L,"data-blocking",ae),i(he,e(we).severity),i(Se,e(we).when)},[()=>String(e(we).blocking)]),l(pe,He)});var qe=r(Q,4),oe=a(qe),S=r(qe,4);{var Z=pe=>{var we=oi();l(pe,we)},ie=pe=>{var we=vi(),He=X(we),Xe=a(He),fe=r(He,2);rt(fe,21,()=>e(x),d=>d.code,(d,y)=>{var ee=di(),f=a(ee),q=a(f),L=r(f,2),he=a(L),ne=r(L,2),Se=a(ne),Le=r(ne,2);{var de=Me=>{var Ee=ui(),Te=a(Ee);p(()=>i(Te,`« ${e(y).message??""} »`)),l(Me,Ee)};m(Le,Me=>{e(y).message!==""&&Me(de)})}var M=r(Le,2);{var Ie=Me=>{var Ee=ci(),Te=X(Ee),Je=a(Te),ae=r(Te,2),De=a(ae);p(ve=>{i(Je,ve),i(De,e(y).product_id)},[()=>D(e(y).product_id)]),l(Me,Ee)};m(M,Me=>{e(y).product_id!==""&&Me(Ie)})}p((Me,Ee,Te)=>{Be(ee,"data-blocking",Me),i(q,Ee),i(he,e(y).code),i(Se,Te)},[()=>String(e(y).blocking),()=>b(e(y).code),()=>J(e(y).severity)]),l(d,ee)}),p(d=>{Be(He,"data-verdicts",d),i(Xe,e(z))},[()=>String(e(x).length)]),l(pe,we)};m(S,pe=>{e(x).length===0?pe(Z):pe(ie,-1)})}p(pe=>i(oe,`Les marqueurs ${pe??""} sont remplacés par les valeurs de la pesée au + moment où le message s’affiche.`),[()=>R.join(" et ")]),l(_,I)},$$slots:{default:!0}});var w=r(Ae,2);Qe(w,{title:"Code-barres",note:"Un seul réglage, et c’est voulu : le reste du plan de numérotation n’est pas de la configuration.",children:(_,F)=>{var I=fi(),Q=X(I);s(Q,()=>"barcode.verify_reference_check_digit",()=>"Refuser une référence dont la clé de contrôle est fausse",()=>"Décoché, le poste recalcule une clé juste sur une référence fausse, en silence — et la caisse encaisse un autre article."),l(_,I)},$$slots:{default:!0}});var N=r(w,2);Qe(N,{title:"Dérogations de poids minimum",note:"En lecture ici ; elles se modifient depuis l’onglet Catalogue, là où se trouve le produit.",children:(_,F)=>{var I=Dt(),Q=X(I);{var qe=S=>{var Z=gi();l(S,Z)},oe=S=>{var Z=bi(),ie=X(Z),pe=a(ie),we=r(ie,2);{var He=fe=>{var d=mi();l(fe,d)};m(we,fe=>{e(B)==="unread"&&fe(He)})}var Xe=r(we,2);rt(Xe,21,()=>e(T),fe=>fe.product_id,(fe,d)=>{var y=_i(),ee=a(y),f=a(ee),q=r(ee,2),L=a(q),he=r(q,2),ne=a(he),Se=r(he,2),Le=a(Se),de=r(Se,2),M=a(de),Ie=r(de,2);{var Me=Ee=>{var Te=hi();l(Ee,Te)};m(Ie,Ee=>{e(d).offered||Ee(Me)})}p((Ee,Te,Je,ae)=>{Be(y,"data-withdrawn",Ee),i(f,Te),i(L,e(d).product_id),i(ne,Je),i(Le,e(d).reason),i(M,`${ae??""}, ${e(d).decided_by??""}`)},[()=>String(!e(d).offered),()=>D(e(d).product_id),()=>G(e(d).min_weight_g??0),()=>da(e(d).decided_at)]),l(fe,y)}),p(()=>i(pe,e(P))),l(S,Z)};m(Q,S=>{e(O).length===0?S(qe):S(oe,-1)})}l(_,I)},$$slots:{default:!0}}),l(n,be),Ct()}At(["change","input","focusout"]);function xi(n,t){const s=[];for(const c of[...ya(n),...ya(t)]){if(s.some(O=>O.path===c))continue;const h=Da(va(n,c)),x=Da(va(t,c));h!==x&&s.push({path:c,before:h,after:x})}return s}function va(n,t){let s=n;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}function ya(n,t=""){if(n===null||typeof n!="object"||Array.isArray(n))return t===""?[]:[t];const s=[];for(const[c,h]of Object.entries(n))s.push(...ya(h,t===""?c:`${t}.${c}`));return s}function Da(n){return n===void 0?"—":n===null?"vide":typeof n=="boolean"?n?"oui":"non":typeof n=="object"?JSON.stringify(n):String(n)}var ki=u('
                              Empreinte de la configuration en service
                              Version du binaire
                              Répertoire de données
                              Espace disque
                              ',1),qi=u('

                              '),Ia=u(" "),Si=u(' '),Li=u("
                            • "),Ei=u(`

                                Recopier reste possible : les valeurs entrent dans le brouillon, où elles se + corrigent champ par champ avant l’enregistrement.

                                `),zi=u(`

                                Ce fichier n’a été comparé à rien : la configuration en service n’a pas pu être + lue. Ni « identique », ni « n champs changent » — ce que ce fichier changerait + sur ce poste reste inconnu.

                                `),ji=u(`

                                Ce fichier décrit la même configuration que celle en service : il n’y a rien à + recopier. C’est ce qu’on veut lire à la fin d’un clonage. Deux champs ne sont pas + comparés : la date du dernier enregistrement, que chaque poste écrit lui-même, et + le mot de passe du catalogue, qu’aucun des deux ne porte en clair.

                                `),Pi=u(' '),Ti=u('

                                '),Ci=u(`

                                ChampEn serviceDans le fichier

                                Recopier n’applique rien : les valeurs entrent dans le brouillon, et c’est + « Enregistrer » qui les met en service.

                                `,1),Oi=u('

                                ',1),Ri=u(`

                                L’export emporte encore l’empreinte du mot de passe : c’est la seule lecture que le + poste garde derrière la clé. L’import, lui, est lu PAR LE POSTE, qui écarte les deux + secrets et le numéro de poste avant de dire ce qui changerait.

                                `,1),Ai=u('

                                Lecture des versions enregistrées…

                                '),Ni=u(`

                                Les versions enregistrées n’ont pas pu être lues : cette liste ne dit donc rien de + ce que ce poste garde. Ce n’est pas « aucune version ».

                                `),$i=u('

                                Aucune version enregistrée : ce poste n’a jamais été reconfiguré.

                                '),Di=u('
                              • '),Ii=u(`

                                  Remettre une version en service remplace la configuration du poste sur-le-champ, et + ce qui n’a pas été enregistré est perdu : c’est le seul geste de cette page qui + change le poste tout de suite, et le seul qui garde ses 72 px.

                                  `,1),Ui=u('
                                  ');function Fi(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=40,h=20,x=5,O=new Set(["modified_at","catalog.options.password"]),A=[{path:"station.name",name:"le nom du poste"},{path:"network.listen",name:"l’adresse d’écoute"},{path:"scale.options",name:"les réglages de la balance"},{path:"printer.options",name:"les réglages de l’imprimante"},{path:"catalog.options",name:"la source du catalogue, compte compris"}];let T=$(Lt([])),H=$("reading"),B=$(null),se=$(""),_e=$(null),ye=$(""),ce=$(Lt([])),j=$(""),V="";const le=o(()=>s().busy||e(j)!==""),re=o(()=>J(e(B),e(_e))),W=o(()=>e(B)!==null&&e(_e)!==null),D=o(()=>be(e(B),e(_e))),Y=o(()=>e(re).slice(0,c)),z=o(()=>e(ce).slice(0,h)),P=o(()=>e(T).slice(0,x)),G=o(()=>Xe(e(Y).length,e(re).length,"champ qui change","champs qui changent")),E=o(()=>Xe(e(z).length,e(ce).length,"contrôle refuse une clé","contrôles refusent une clé")),b=o(()=>Xe(e(P).length,e(T).length,"version enregistrée","versions enregistrées")),R=o(()=>e(re).length>1?`Recopier ces ${me(e(re).length)} champs dans le brouillon`:"Recopier ce champ dans le brouillon");Ae();function J(f,q){return f===null||q===null?[]:xi(f,q).filter(L=>!O.has(L.path))}function be(f,q){if(f===null||q===null)return[];const L=f,he=q;return A.filter(ne=>xe(he,ne.path)&&!xe(L,ne.path))}function xe(f,q){const L=va(f,q);return L==null||L===""?!0:typeof L!="object"||Array.isArray(L)?!1:Object.keys(L).length===0}function Ve(f){return f.length<2?f.join(""):`${f.slice(0,-1).join(", ")} et ${f[f.length-1]??""}`}aa(()=>{const f=t.health.config_fingerprint;f!==V&&(V=f,w())});async function Ae(){const f=await s().load(Ar);if(f===null){v(H,"unreadable");return}v(T,f,!0),v(H,"read")}async function w(){try{v(B,(await qa()).config,!0),v(se,"")}catch(f){v(B,null),v(se,f instanceof Error?f.message:"Le poste n’a pas répondu.",!0)}}async function N(f){v(j,f?"export-all":"export-clone",!0),s().notice="",s().actionError="";try{const q=await s().protect(()=>_(f));if(q===null)return;Q(q.name,q.blob),s().notice=`${q.name} est remis au navigateur : c’est lui qui l’enregistre, voyez ses téléchargements.`}finally{v(j,"")}}async function _(f){const L=await fetch(`/admin/api/config/export?hardware=${f?"1":"0"}`,{headers:{accept:"application/json"}});if(!L.ok)throw new It(L.status,pe(await L.text(),"L’export"));return{name:F(f),blob:await L.blob()}}function F(f){const q=f?"":"-sans-materiel";return`config-poste${String(t.health.station)}${q}-${I()}.json`}function I(){const f=new Date,q=String(f.getMonth()+1).padStart(2,"0"),L=String(f.getDate()).padStart(2,"0");return`${String(f.getFullYear())}-${q}-${L}`}function Q(f,q){const L=URL.createObjectURL(q),he=document.createElement("a");he.href=L,he.download=f,document.body.appendChild(he),he.click(),he.remove(),setTimeout(()=>{URL.revokeObjectURL(L)},0)}async function qe(f){const q=await fetch("/admin/api/config/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)}),L=await q.text();if(!q.ok)throw new It(q.status,pe(L,"L’import"));return JSON.parse(L)}async function oe(f){if(f==null)return;s().notice="",s().actionError="";let q;try{q=JSON.parse(await f.text())}catch{s().actionError=`${f.name} n’est pas un fichier JSON lisible.`;return}v(j,"import");try{const L=await s().protect(()=>qe(q));if(L===null)return;v(_e,L.config,!0),v(ye,f.name,!0),v(ce,L.faults,!0),await w(),s().notice=S(f.name)}finally{v(j,"")}}function S(f){return e(W)?e(re).length===0?`${f} décrit la même configuration que celle en service.`:`${f} est lu. Rien n’est appliqué : relisez le tableau.`:`${f} est lu, mais la configuration en service ne l’est pas : rien ne peut être comparé.`}async function Z(){if(e(_e)===null)return;const f=e(_e),q=[...e(re)];for(const L of q)t.draft.set(L.path,va(f,L.path));s().actionError="",s().notice="Le fichier est recopié dans le brouillon. Rien n’est appliqué avant « Enregistrer ».",await w()}async function ie(f){v(j,`restore-${String(f)}`),s().notice="",s().actionError="";try{if(await s().protect(()=>Nr(f))===null)return;await t.draft.load(),await Ae(),await w(),await s().refresh(),s().notice=`La version ${String(f)} est remise en service.`}finally{v(j,"")}}function pe(f,q){try{const L=JSON.parse(f);if(typeof L?.message=="string"&&L.message!=="")return L.message}catch{}return`${q} a été refusé par le poste.`}function we(f){return t.draft.faults.find(q=>q.field===f)?.message??""}function He(f){return t.draft.faults.find(q=>q.field===f)?.allowed??[]}function Xe(f,q,L,he){const ne=q>1?he:L;return f>=q?`${me(q)} ${ne}.`:`${me(f)} lignes affichées sur ${me(q)} ${ne}.`}var fe=Ui(),d=a(fe);Qe(d,{title:"Identité du poste",children:(f,q)=>{var L=ki(),he=X(L);{let ze=o(()=>t.draft.text("station.number")),Oe=o(()=>we("station.number")),nt=o(()=>He("station.number"));_t(he,{label:"Numéro du poste",path:"station.number",kind:"number",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},hint:"C’est de lui que dérive le nom du fichier de catalogue attendu, flv_.csv.",onchange:ut=>t.draft.set("station.number",Number(ut))})}var ne=r(he,2);{let ze=o(()=>t.draft.text("station.name")),Oe=o(()=>we("station.name")),nt=o(()=>He("station.name"));_t(ne,{label:"Nom du poste",path:"station.name",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},hint:"Ce que lit un bénévole : « Poste 2 — fruits ».",onchange:ut=>t.draft.set("station.name",ut)})}var Se=r(ne,2);{let ze=o(()=>t.draft.text("station.coop")),Oe=o(()=>we("station.coop")),nt=o(()=>He("station.coop"));_t(Se,{label:"Coopérative",path:"station.coop",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},onchange:ut=>t.draft.set("station.coop",ut)})}var Le=r(Se,2),de=r(a(Le),2),M=a(de),Ie=r(de,4),Me=a(Ie),Ee=r(Ie,4),Te=a(Ee),Je=r(Ee,4),ae=a(Je);{var De=ze=>{var Oe=yt("non mesuré");l(ze,Oe)},ve=ze=>{var Oe=yt();p((nt,ut,pt)=>i(Oe,`${nt??""} libres sur + ${ut??""} — seuil d’alerte + ${pt??""} Mo`),[()=>wa(t.health.disk.free_bytes),()=>wa(t.health.disk.total_bytes),()=>me(t.health.disk.alert_mb)]),l(ze,Oe)};m(ae,ze=>{t.health.disk===null?ze(De):ze(ve,-1)})}p(()=>{i(M,t.health.config_fingerprint),i(Me,t.health.version),i(Te,t.health.disk===null?"non publié par ce poste":t.health.disk.path)}),l(f,L)},$$slots:{default:!0}});var y=r(d,2);Qe(y,{title:"Exporter, importer",note:"Pour installer un autre poste : ce fichier emporte les tarifs, les garde-fous, l’étiquette et les catégories. Tout ce qui est propre à ce poste-ci reste ici — le mot de passe, le code de secours, le numéro et le nom du poste, les réglages de la balance, ceux de l’imprimante, la source du catalogue et le réseau.",children:(f,q)=>{var L=Ri(),he=X(L),ne=a(he);{let ae=o(()=>e(j)==="export-all");at(ne,{label:"Exporter tout",protected:!0,get busy(){return e(ae)},get disabled(){return e(le)},onrun:()=>{N(!0)}})}var Se=r(ne,2);{let ae=o(()=>e(j)==="export-clone");at(Se,{label:"Exporter sans le matériel",protected:!0,get busy(){return e(ae)},get disabled(){return e(le)},onrun:()=>{N(!1)}})}var Le=r(Se,2);let de;var M=a(Le),Ie=r(M,3),Me=r(he,4);{var Ee=ae=>{var De=qi(),ve=a(De);p(()=>i(ve,`La configuration en service n’a pas pu être lue : ${e(se)??""} La colonne + « En service » ne peut donc rien affirmer.`)),l(ae,De)};m(Me,ae=>{e(se)!==""&&ae(Ee)})}var Te=r(Me,2);{var Je=ae=>{var De=Oi(),ve=X(De),ze=a(ve),Oe=r(ve,2);{var nt=Ze=>{var bt=Ei(),Ot=a(bt),Ut=a(Ot),k=r(Ot,2);rt(k,21,()=>e(z),fa,(K,ue)=>{var Re=Li(),We=a(Re),Ye=a(We),Ke=r(We,2);{var U=ke=>{var $e=Ia(),Pe=a($e);p(()=>i(Pe,e(ue).field)),l(ke,$e)};m(Ke,ke=>{Gt.showTechnicalNames&&ke(U)})}var te=r(Ke),ge=r(te);{var Ne=ke=>{var $e=Si(),Pe=a($e);p(Ue=>i(Pe,`Valeurs acceptées : ${Ue??""}.`),[()=>e(ue).allowed.join(", ")]),l(ke,$e)};m(ge,ke=>{e(ue).allowed!==void 0&&e(ue).allowed.length>0&&ke(Ne)})}p(ke=>{i(Ye,ke),i(te,` ${e(ue).message??""} `)},[()=>Ft(e(ue).field)]),l(K,Re)}),p(()=>i(Ut,`Ce fichier serait refusé en l’état : ${e(E)??""}`)),l(Ze,bt)};m(Oe,Ze=>{e(ce).length>0&&Ze(nt)})}var ut=r(Oe,2);{var pt=Ze=>{var bt=zi();l(Ze,bt)},je=Ze=>{var bt=ji();l(Ze,bt)},st=Ze=>{var bt=Ci(),Ot=X(bt),Ut=a(Ot),k=r(Ut);{var K=ge=>{var Ne=yt("Les autres sont dans le fichier, et « Recopier » les prend tous.");l(ge,Ne)};m(k,ge=>{e(re).length>e(Y).length&&ge(K)})}var ue=r(Ot,2),Re=a(ue),We=r(a(Re));rt(We,21,()=>e(Y),ge=>ge.path,(ge,Ne)=>{const ke=o(()=>Ft(e(Ne).path));var $e=Pi(),Pe=a($e),Ue=a(Pe),Ge=r(Ue);{var ft=gt=>{var xt=Ia(),Nt=a(xt);p(()=>i(Nt,e(Ne).path)),l(gt,xt)};m(Ge,gt=>{Gt.showTechnicalNames&&e(ke)!==e(Ne).path&>(ft)})}var ot=r(Pe),ct=a(ot),it=r(ot),dt=a(it);p(()=>{Be($e,"data-path",e(Ne).path),i(Ue,`${e(ke)??""} `),i(ct,e(Ne).before),i(dt,e(Ne).after)}),l(ge,$e)});var Ye=r(ue,2);{var Ke=ge=>{var Ne=Ti(),ke=a(Ne);p($e=>i(ke,`Ce fichier ne porte pas ${$e??""} : + l’export sans le matériel les retire, et l’import ne remet que le numéro du + poste. Les lignes correspondantes sont VIDES ci-dessus, et + « Recopier » recopie ce vide dans le brouillon.`),[()=>Ve(e(D).map($e=>$e.name))]),l(ge,Ne)};m(Ye,ge=>{e(D).length>0&&ge(Ke)})}var U=r(Ye,2),te=a(U);at(te,{kind:"write",get label(){return e(R)},get disabled(){return e(le)},onrun:()=>{Z()}}),p(()=>i(Ut,`${e(G)??""} `)),l(Ze,bt)};m(ut,Ze=>{e(W)?e(re).length===0?Ze(je,1):Ze(st,-1):Ze(pt)})}p(()=>i(ze,`Fichier lu : ${e(ye)??""}`)),l(ae,De)};m(Te,ae=>{e(_e)!==null&&ae(Je)})}p(()=>{de=Ht(Le,1,"choose svelte-18wbxwu",null,de,{working:e(j)==="import",off:e(le)}),i(M,`${e(j)==="import"?"Lecture du fichier…":"Importer un fichier"} `),Ie.disabled=e(le)}),tt("change",Ie,ae=>{oe(ae.currentTarget.files?.item(0))}),l(f,L)},$$slots:{default:!0}});var ee=r(y,2);Qe(ee,{title:"Cinq versions restaurables",note:"Chaque enregistrement fait tourner les versions : la plus récente est la 1.",children:(f,q)=>{var L=Dt(),he=X(L);{var ne=M=>{var Ie=Ai();l(M,Ie)},Se=M=>{var Ie=Ni();l(M,Ie)},Le=M=>{var Ie=$i();l(M,Ie)},de=M=>{var Ie=Ii(),Me=X(Ie),Ee=a(Me),Te=r(Me,2),Je=a(Te);rt(Je,21,()=>e(P),ae=>ae.version,(ae,De)=>{var ve=Di(),ze=a(ve),Oe=a(ze),nt=r(ze,2),ut=a(nt),pt=r(nt,2),je=a(pt),st=r(pt,2);{let Ze=o(()=>e(j)===`restore-${String(e(De).version)}`);at(st,{kind:"destructive",label:"Remettre cette version en service",protected:!0,get busy(){return e(Ze)},get disabled(){return e(le)},onrun:()=>{ie(e(De).version)}})}p((Ze,bt)=>{i(Oe,`version ${Ze??""}`),i(ut,bt),i(je,e(De).config_fingerprint)},[()=>me(e(De).version),()=>Mt(e(De).modified_at)]),l(ae,ve)}),p(()=>i(Ee,`${e(b)??""} Le poste n’en garde jamais plus de cinq.`)),l(M,Ie)};m(he,M=>{e(H)==="reading"?M(ne):e(H)==="unreadable"?M(Se,1):e(T).length===0?M(Le,2):M(de,-1)})}l(f,L)},$$slots:{default:!0}}),l(n,fe),Ct()}At(["change"]);var Mi=u('clé'),Bi=u(' '),Vi=u('');function Xt(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"hint",3,""),h=vt(t,"disabled",3,!1),x=vt(t,"engaged",3,!1),O=vt(t,"busy",3,!1),A=vt(t,"protected",3,!1);var T=Vi();let H;var B=a(T),se=a(B),_e=r(se);{var ye=V=>{var le=Mi();l(V,le)};m(_e,V=>{A()&&V(ye)})}var ce=r(B,2);{var j=V=>{var le=Bi(),re=a(le);p(()=>i(re,O()?"En cours…":c())),l(V,le)};m(ce,V=>{c()!==""&&V(j)})}p(()=>{H=Ht(T,1,`big touch-target ${s()??""}`,"svelte-15nlt6i",H,{engaged:x(),busy:O()}),Be(T,"data-kind",s()),T.disabled=h()||O(),i(se,`${t.label??""} `)}),tt("click",T,function(...V){t.onrun?.apply(this,V)}),l(n,T)}At(["click"]);const Wi="ERR-SCL-09";var Gi=u('

                                  '),Hi=u('

                                  '),Ji=u('

                                  Déposez ici le fichier clé

                                  '),Ki=u('

                                  ',1),Xi=u('');function Yi(n,t){Tt(t,!0);const s=vt(t,"admin",7);let c=$("");const h=o(()=>t.health.state.degraded?.code===Wi),x=o(()=>t.health.printing),O=o(()=>e(x)?.on_fallback===!0);let A=$(!1),T=$("");const H=o(()=>s().busy||e(T)!=="");async function B(w,N){v(c,""),v(T,w,!0);try{await s().run(N)}finally{v(T,"")}}async function se(w,N){v(c,""),v(T,w,!0);try{const _=await s().load(N);_!==null&&v(c,_.message,!0)}finally{v(T,"")}}async function _e(w,N){v(c,""),v(T,w,!0);try{const _=await s().protect(N);_!==null&&(s().notice=_.message,await s().refresh())}finally{v(T,"")}}async function ye(w){if(v(A,!1),w!=null){v(c,""),v(T,"import");try{const N=await s().protect(()=>Ga(w));if(N===null)return;v(c,N.result==="rejected"||N.result==="failed"?`${w.name} : REFUSÉ${N.reason===""?"":" — "+N.reason}. Le catalogue en service n’a pas changé.`:`${w.name} : ${String(N.rows_read_count)} lignes lues, ${String(N.weighable_count)} pesables. La veille l’appliquera dans la seconde.`,!0),await s().refresh()}finally{v(T,"")}}}var ce=Xi(),j=a(ce);{var V=w=>{var N=Gi(),_=a(N);p(()=>i(_,e(c))),l(w,N)};m(j,w=>{e(c)!==""&&w(V)})}var le=r(j,2);{var re=w=>{var N=Hi(),_=a(N);p(()=>i(_,e(x).banner)),l(w,N)};m(le,w=>{e(x)!==null&&e(x).banner!==""&&w(re)})}var W=r(le,2),D=a(W);{let w=o(()=>e(T)==="scale");Xt(D,{label:"Tester la balance",hint:"Ce que le poste a déjà observé — le port n’est pas rouvert.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{se("scale",Lr)}})}var Y=r(D,2);{let w=o(()=>e(T)==="printer");Xt(Y,{label:"Tester l’imprimante",hint:"Ce que le superviseur a vu il y a moins d’une seconde.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{se("printer",Er)}})}var z=r(Y,2);{let w=o(()=>e(T)==="label");Xt(z,{label:"Imprimer une étiquette de test",hint:"Une étiquette de démonstration sort de l’imprimante du poste.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("label",zr)}})}var P=r(z,2);{let w=o(()=>e(T)==="reprint");Xt(P,{label:"Réimprimer la dernière",hint:"La dernière étiquette imprimée sort une seconde fois.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("reprint",yr)}})}var G=r(P,2);{let w=o(()=>e(T)==="reload");Xt(G,{label:"Recharger le catalogue",kind:"write",hint:"La veille refait tout de suite le contrôle qu’elle fait toutes les cinq secondes.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("reload",xr)}})}var E=r(G,2);{let w=o(()=>e(h)?"Revenir à la balance":"Basculer en saisie manuelle"),N=o(()=>e(h)?"Le poids sera de nouveau lu sur la balance.":"Le poids se tape à la main : le poste continue de servir sans balance."),_=o(()=>e(T)==="manual");Xt(E,{get label(){return e(w)},kind:"destructive",get hint(){return e(N)},get engaged(){return e(h)},protected:!0,get busy(){return e(_)},get disabled(){return e(H)},onrun:()=>{_e("manual",()=>kr(!e(h)))}})}var b=r(E,2);{let w=o(()=>e(T)==="roll");Xt(b,{label:"J’ai changé le rouleau",kind:"write",hint:"Le compteur d’étiquettes repart à zéro. C’est le seul geste qui dise quelque chose de vrai du papier.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("roll",qr)}})}var R=r(b,2);{var J=w=>{{let N=o(()=>e(O)?"Revenir à l’imprimante du poste":"Imprimer sur l’imprimante du poste voisin"),_=o(()=>e(O)?"Les étiquettes repartiront sur l’imprimante de ce poste.":"Les étiquettes sortiront sur l’imprimante voisine, pour cette session seulement."),F=o(()=>e(T)==="fallback");Xt(w,{get label(){return e(N)},kind:"write",get hint(){return e(_)},get engaged(){return e(O)},get busy(){return e(F)},get disabled(){return e(H)},onrun:()=>{B("fallback",()=>Sr(!e(O)))}})}};m(R,w=>{e(x)!==null&&e(x).fallback_available&&w(J)})}var be=r(R,2),xe=r(W,2);Qe(xe,{title:"Importer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(w,N)=>{var _=Ji();let F;var I=a(_),Q=r(a(I)),qe=a(Q),oe=r(I,2),S=r(a(oe));p(()=>{F=Ht(_,1,"drop svelte-1yd0nzg",null,F,{dropping:e(A)}),i(qe,`flv_${t.health.station??""}.csv`)}),Qt("dragover",_,Z=>{Z.preventDefault(),v(A,!0)}),Qt("dragleave",_,()=>v(A,!1)),Qt("drop",_,Z=>{Z.preventDefault(),ye(Z.dataTransfer?.files.item(0))}),tt("change",S,Z=>{ye(Z.currentTarget.files?.item(0))}),l(w,_)},$$slots:{default:!0}});var Ve=r(xe,2);{var Ae=w=>{Qe(w,{title:"Le dernier fichier a été refusé",children:(N,_)=>{var F=Ki(),I=X(F),Q=a(I),qe=r(I,2),oe=a(qe);p(S=>{i(Q,`${(t.health.catalog.reason===""?"Aucun motif enregistré.":t.health.catalog.reason)??""} + Le catalogue en service n’a pas changé.`),i(oe,`Dernier essai : ${S??""}.`)},[()=>Mt(t.health.catalog.occurred_at)]),l(N,F)},$$slots:{default:!0}})};m(Ve,w=>{t.health.catalog!==null&&t.health.catalog.result==="rejected"&&w(Ae)})}p(()=>Be(be,"href",jr)),l(n,ce),Ct()}At(["change"]);const Qi={succeeded:"La dernière mise à jour a réussi.","rolled-back":"La dernière mise à jour a échoué. La version précédente a été remise et le poste fonctionne.","rolled-back-unhealthy":"La dernière mise à jour a échoué et le poste n’a pas redémarré. Appelez le support.","not-started":"La dernière mise à jour n’a pas démarré : rien n’a été remplacé. Vous pouvez réessayer."},Zi=2e3,eo=300*1e3;var to=u('

                                  '),Ua=u('

                                  '),ao=u("
                                  Dernière vérification
                                  ",1),ro=u(`

                                  La mise à jour depuis cet écran n’existe que sur les postes Windows. Sur les + autres, elle se fait à la main — voir la notice d’installation.

                                  `),no=u('Voir les nouveautés'),so=u('

                                  Version disponible : .

                                  '),lo=u('

                                  Ce poste est à jour.

                                  '),io=u("
                                  Version installée
                                  Dépôt suivi
                                  ",1),oo=u(`

                                  Le poste va s’arrêter environ une minute. L’écran client s’éteindra puis reviendra + tout seul. Si la nouvelle version ne démarre pas, la précédente sera remise + automatiquement — mais les données enregistrées, elles, ne reviendront pas en + arrière.

                                  `),uo=u('

                                  Mise à jour en cours. Le poste redémarre, ne le débranchez pas.

                                  '),co=u('
                                  ',1),vo=u("
                                  Terminée le
                                  ",1),po=u("
                                  Raison
                                  ",1),fo=u('

                                  Depuis
                                  Vers
                                  ',1),go=u(" ",1);function mo(n,t){Tt(t,!0);let s=$(null),c=$(""),h=$(""),x=$(!1),O=$("");const A=o(()=>e(s)?.outcome??null),T=o(()=>e(s)?.supported===!0&&e(s).available);aa(()=>{H()});async function H(){try{v(s,await Yr(),!0),v(c,"")}catch(W){v(c,W instanceof Error?W.message:"Lecture impossible.",!0)}}async function B(){v(h,"check");try{const W=await t.admin.protect(()=>Qr());W!==null&&(v(s,W,!0),v(c,""))}finally{v(h,"")}}async function se(W){v(h,"apply"),v(O,"");try{if(await t.admin.protect(()=>Zr(W))===null)return;v(x,!0),v(O,await _e()?"":"Le poste n’a pas répondu dans les cinq minutes. Allez le voir.",!0),await H()}finally{v(h,""),v(x,!1)}}async function _e(){const W=Date.now()+eo;for(;Date.now()setTimeout(D,Zi));try{if((await fetch("/healthz",{cache:"no-store"})).ok)return!0}catch{}}return!1}var ye=go(),ce=X(ye);Qe(ce,{title:"Version de ce poste",children:(W,D)=>{var Y=Dt(),z=X(Y);{var P=E=>{var b=to(),R=a(b);p(()=>i(R,e(c)===""?"Lecture…":e(c))),l(E,b)},G=E=>{var b=io(),R=X(b);{var J=oe=>{var S=Ua(),Z=a(S);p(()=>i(Z,e(c))),l(oe,S)};m(R,oe=>{e(c)!==""&&oe(J)})}var be=r(R,2),xe=r(a(be),2),Ve=a(xe),Ae=r(xe,4),w=a(Ae),N=r(Ae,2);{var _=oe=>{var S=ao(),Z=r(X(S),2),ie=a(Z);p(pe=>i(ie,pe),[()=>Mt(e(s).checked_at)]),l(oe,S)};m(N,oe=>{e(s).checked_at!==""&&oe(_)})}var F=r(be,2);{var I=oe=>{var S=ro();l(oe,S)},Q=oe=>{var S=so(),Z=r(a(S)),ie=a(Z),pe=r(Z,2);{var we=fe=>{var d=yt();p(y=>i(d,`, publiée le ${y??""}`),[()=>Mt(e(s).published_at)]),l(fe,d)};m(pe,fe=>{e(s).published_at!==""&&fe(we)})}var He=r(pe,2);{var Xe=fe=>{var d=no();p(()=>Be(d,"href",e(s).html_url)),l(fe,d)};m(He,fe=>{e(s).html_url!==""&&fe(Xe)})}p(()=>i(ie,e(s).latest)),l(oe,S)},qe=oe=>{var S=lo();l(oe,S)};m(F,oe=>{e(s).supported?e(s).available?oe(Q,1):oe(qe,-1):oe(I)})}p(()=>{i(Ve,e(s).running),i(w,e(s).repository)}),l(E,b)};m(z,E=>{e(s)===null?E(P):E(G,-1)})}l(W,Y)},$$slots:{default:!0}});var j=r(ce,2);{var V=W=>{Qe(W,{title:"Installer",children:(D,Y)=>{var z=co(),P=X(z),G=a(P);{let w=o(()=>e(h)==="check");at(G,{kind:"read",label:"Vérifier maintenant",protected:!0,act:"check",get busy(){return e(w)},get disabled(){return e(x)},onrun:()=>{B()}})}var E=r(G,2);{var b=w=>{{let N=o(()=>`Installer la version ${e(s).latest}`),_=o(()=>e(h)==="apply");at(w,{kind:"destructive",get label(){return e(N)},protected:!0,act:"apply",get busy(){return e(_)},onrun:()=>{se(e(s).latest)}})}};m(E,w=>{e(T)&&w(b)})}var R=r(P,2);{var J=w=>{var N=oo();l(w,N)};m(R,w=>{e(T)&&w(J)})}var be=r(R,2);{var xe=w=>{var N=uo();l(w,N)};m(be,w=>{e(x)&&w(xe)})}var Ve=r(be,2);{var Ae=w=>{var N=Ua(),_=a(N);p(()=>i(_,e(O))),l(w,N)};m(Ve,w=>{e(O)!==""&&w(Ae)})}l(D,z)},$$slots:{default:!0}})};m(j,W=>{e(s)!==null&&e(s).supported&&W(V)})}var le=r(j,2);{var re=W=>{Qe(W,{title:"Dernière tentative",children:(D,Y)=>{var z=fo(),P=X(z),G=a(P),E=r(P,2),b=r(a(E),2),R=a(b),J=r(b,4),be=a(J),xe=r(J,2);{var Ve=N=>{var _=vo(),F=r(X(_),2),I=a(F);p(Q=>i(I,Q),[()=>Mt(e(A).finished_at)]),l(N,_)};m(xe,N=>{e(A).finished_at!==""&&N(Ve)})}var Ae=r(xe,2);{var w=N=>{var _=po(),F=r(X(_),2),I=a(F);p(()=>i(I,e(A).reason)),l(N,_)};m(Ae,N=>{e(A).reason!==""&&N(w)})}p(()=>{Be(P,"data-outcome",e(A).status),i(G,Qi[e(A).status]),i(R,e(A).from===""?"—":e(A).from),i(be,e(A).to===""?"—":e(A).to)}),l(D,z)},$$slots:{default:!0}})};m(le,W=>{e(A)!==null&&W(re)})}l(n,ye),Ct()}var ho=u(''),_o=u('

                                  ',1),bo=u('

                                  ',1),wo=u(''),yo=u(''),xo=u(''),ko=u(''),qo=u(' '),So=u(''),Lo=u('

                                  Lecture de l’état du poste…

                                  '),Eo=u('

                                  '),zo=u(" "),jo=u("
                                • "),Po=u('
                                    '),To=u('
                                    '),Co=u('

                                    ');function Oo(n,t){Tt(t,!0);const s=new pn,c=new en(s),h=[{title:"Au quotidien",pages:[{id:"dashboard",label:"Tableau de bord"},{id:"troubleshooting",label:"Dépannage"}]},{title:"Réglages",pages:[{id:"hardware",label:"Matériel"},{id:"label",label:"Étiquette"},{id:"rules",label:"Règles"},{id:"catalog",label:"Catalogue"},{id:"journal",label:"Journal"},{id:"station",label:"Poste"},{id:"update",label:"Mise à jour"}]}],x=o(()=>h.flatMap(d=>d.pages).find(d=>d.id===s.page)?.label??""),O=o(()=>s.health===null||s.health.station_name===""?`Poste ${String(s.health?.station??"")}`:s.health.station_name),A=o(()=>(c.pending?.changed_blocks??[]).map(d=>nn(d)).join(", ")),T=o(()=>(c.pending?.changed_blocks??[]).join(", "));ka(()=>(s.start(),()=>s.stop()));async function H(d){s.open(d),Ca(d)&&c.config===null&&await c.load()}async function B(){await s.protect(()=>c.save())===!0&&(s.notice="La configuration est enregistrée et appliquée.")}var se=Co(),_e=a(se),ye=r(a(_e),2);rt(ye,17,()=>h,d=>d.title,(d,y)=>{var ee=_o(),f=X(ee),q=a(f),L=r(f,2);rt(L,17,()=>e(y).pages,he=>he.id,(he,ne)=>{var Se=ho();let Le;var de=a(Se);p(()=>{Le=Ht(Se,1,"entry svelte-9b2mjq",null,Le,{current:s.page===e(ne).id}),Be(Se,"aria-current",s.page===e(ne).id?"page":void 0),i(de,e(ne).label)}),tt("click",Se,()=>{H(e(ne).id)}),l(he,Se)}),p(()=>i(q,e(y).title)),l(d,ee)});var ce=r(ye,2),j=a(ce);{var V=d=>{var y=bo(),ee=X(y),f=a(ee),q=r(ee,2),L=a(q),he=r(q,2),ne=a(he);p(()=>{i(f,e(O)),i(L,`${s.health.coop??""} · version ${s.health.version??""}`),i(ne,`configuration ${s.health.config_fingerprint??""}`)}),l(d,y)};m(j,d=>{s.health!==null&&d(V)})}var le=r(j,2),re=a(le),W=r(le,2);{var D=d=>{var y=wo();tt("click",y,function(...ee){t.onclose?.apply(this,ee)}),l(d,y)};m(W,d=>{t.onclose!==void 0&&d(D)})}var Y=r(_e,2),z=a(Y);{var P=d=>{var y=yo(),ee=a(y);p(()=>i(ee,s.notice)),l(d,y)};m(z,d=>{s.notice!==""&&d(P)})}var G=r(z,2);{var E=d=>{var y=xo(),ee=a(y);p(()=>i(ee,s.linkError)),l(d,y)};m(G,d=>{s.linkError!==""&&d(E)})}var b=r(G,2);{var R=d=>{var y=ko(),ee=a(y);p(()=>i(ee,s.actionError)),l(d,y)};m(b,d=>{s.actionError!==""&&d(R)})}var J=r(b,2);{var be=d=>{var y=So(),ee=a(y),f=a(ee),q=r(f);{var L=Se=>{var Le=qo(),de=a(Le);p(()=>i(de,e(T))),l(Se,Le)};m(q,Se=>{Gt.showTechnicalNames&&Se(L)})}var he=r(q),ne=r(ee,2);at(ne,{kind:"write",label:"Tout fonctionne : confirmer",onrun:()=>{s.protect(()=>c.confirm())}}),p(()=>{i(f,`Configuration appliquée mais NON CONFIRMÉE. Ce qui a changé : ${e(A)??""}. `),i(he,` Le poste reviendra tout seul à la version précédente dans + ${c.pending.seconds_left??""} secondes si personne ne confirme.`)}),l(d,y)};m(J,d=>{c.pending!==null&&d(be)})}var xe=r(J,2),Ve=a(xe),Ae=a(Ve),w=r(Ve,2);{var N=d=>{var y=Lo();l(d,y)},_=d=>{ll(d,{get health(){return s.health},onshowrows:()=>{H("catalog")}})},F=d=>{Yi(d,{get admin(){return s},get health(){return s.health}})},I=d=>{_l(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},Q=d=>{Gl(d,{get admin(){return s},get draft(){return c}})},qe=d=>{yi(d,{get draft(){return c},get health(){return s.health}})},oe=d=>{ys(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},S=d=>{$l(d,{get admin(){return s}})},Z=d=>{Fi(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},ie=d=>{mo(d,{get admin(){return s}})};m(w,d=>{s.health===null?d(N):s.page==="dashboard"?d(_,1):s.page==="troubleshooting"?d(F,2):s.page==="hardware"?d(I,3):s.page==="label"?d(Q,4):s.page==="rules"?d(qe,5):s.page==="catalog"?d(oe,6):s.page==="journal"?d(S,7):s.page==="station"?d(Z,8):s.page==="update"&&d(ie,9)})}var pe=r(xe,2);{var we=d=>{var y=To(),ee=a(y);{var f=ne=>{var Se=Eo(),Le=a(Se),de=r(Le);rt(de,16,()=>c.retired,M=>M,(M,Ie)=>{{let Me=o(()=>`retirer ${Ie}`);at(M,{kind:"write",get label(){return e(Me)},onrun:()=>c.dropRetired(Ie)})}}),p(M=>i(Le,`Ce fichier porte des réglages que cette version du poste ne connaît plus : + ${M??""}. `),[()=>c.retired.map(M=>Ft(M)).join(", ")]),l(ne,Se)};m(ee,ne=>{c.retired.length>0&&ne(f)})}var q=r(ee,2);{var L=ne=>{var Se=Po();rt(Se,21,()=>c.faults,Le=>Le.field,(Le,de)=>{var M=jo(),Ie=a(M),Me=a(Ie),Ee=r(Ie,2);{var Te=ve=>{var ze=zo(),Oe=a(ze);p(()=>i(Oe,e(de).field)),l(ve,ze)};m(Ee,ve=>{Gt.showTechnicalNames&&ve(Te)})}var Je=r(Ee),ae=r(Je);{var De=ve=>{var ze=yt();p(Oe=>i(ze,`— valeurs acceptées : ${Oe??""}`),[()=>e(de).allowed.join(", ")]),l(ve,ze)};m(ae,ve=>{e(de).allowed!==void 0&&e(de).allowed.length>0&&ve(De)})}p(ve=>{i(Me,ve),i(Je,` ${e(de).message??""} `)},[()=>Ft(e(de).field)]),l(Le,M)}),l(ne,Se)};m(q,ne=>{c.faults.length>0&&ne(L)})}var he=r(q,2);{let ne=o(()=>c.dirty?"Enregistrer la configuration":"Aucune modification à enregistrer"),Se=o(()=>!c.dirty||s.busy);at(he,{kind:"write",get label(){return e(ne)},get disabled(){return e(Se)},onrun:()=>{B()}})}l(d,y)},He=o(()=>Ca(s.page)&&c.config!==null);m(pe,d=>{e(He)&&d(we)})}var Xe=r(Y,2);{var fe=d=>{wn(d,{get admin(){return s}})};m(Xe,d=>{s.pending!==null&&d(fe)})}p(()=>{ua(re,Gt.showTechnicalNames),i(Ae,e(x))}),tt("change",re,()=>Gt.toggleTechnicalNames()),l(n,se),Ct()}At(["click","change"]);let oa=null;function No(n){if(oa!==null||n.querySelector("[data-admin]")!==null)return;const t=document.createElement("div");n.appendChild(t),oa={component:fr(Oo,{target:t,props:{onclose:Ro}}),host:t}}function Ro(){if(oa===null)return;const{component:n,host:t}=oa;oa=null,gr(n),t.remove()}export{Ro as closeAdmin,No as mountAdmin}; diff --git a/internal/web/dist/assets/mount-DFYVhACB.css b/internal/web/dist/assets/mount-BCv4PVId.css similarity index 91% rename from internal/web/dist/assets/mount-DFYVhACB.css rename to internal/web/dist/assets/mount-BCv4PVId.css index e97983a..dfa5431 100644 --- a/internal/web/dist/assets/mount-DFYVhACB.css +++ b/internal/web/dist/assets/mount-BCv4PVId.css @@ -1 +1 @@ -.act.svelte-5tpq0o{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.read.svelte-5tpq0o{color:var(--ink);background:var(--surface);border:1px solid var(--border)}.write.svelte-5tpq0o{color:var(--surface);background:var(--action);border:1px solid var(--action)}.destructive.svelte-5tpq0o{min-height:var(--touch-min);color:var(--surface);background:var(--danger);border:1px solid var(--danger)}@media(hover:hover){.read.svelte-5tpq0o:hover:not(:disabled){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}.write.svelte-5tpq0o:hover:not(:disabled){border-color:var(--action)}.destructive.svelte-5tpq0o:hover:not(:disabled){border-color:var(--danger)}.write.svelte-5tpq0o:hover:not(:disabled),.destructive.svelte-5tpq0o:hover:not(:disabled){box-shadow:var(--shadow-2);filter:brightness(.92)}}.act.svelte-5tpq0o:disabled{opacity:.5;box-shadow:none;cursor:default}.act.busy.svelte-5tpq0o:disabled{opacity:1}.key.svelte-5tpq0o{padding:.0625rem .375rem;border-radius:var(--radius-pill);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;background:var(--bg);color:var(--ink-muted)}.write.svelte-5tpq0o .key:where(.svelte-5tpq0o),.destructive.svelte-5tpq0o .key:where(.svelte-5tpq0o){background:var(--surface);color:var(--ink)}.scrim.svelte-zyka3t{position:fixed;inset:0;z-index:95;display:flex;align-items:center;justify-content:center;padding:2rem;background:#1c1b1973}.panel.svelte-zyka3t{display:flex;flex-direction:column;gap:.75rem;width:min(34rem,100%);padding:1.75rem;background:var(--surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-2)}header.svelte-zyka3t{display:flex;align-items:center;gap:.75rem}.glyph.svelte-zyka3t{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;flex:none;border-radius:var(--radius-sm);background:var(--bg);color:var(--ink-muted)}h2.svelte-zyka3t{margin:0;font-size:1.375rem;line-height:1.2}.why.svelte-zyka3t,.how.svelte-zyka3t{margin:0;color:var(--ink-muted);font-size:1rem;line-height:1.4}.field.svelte-zyka3t{display:flex;flex-direction:column;gap:.375rem;font-size:1rem;color:var(--ink-muted)}input.svelte-zyka3t{height:2.75rem;padding:0 .75rem;font:inherit;font-size:1.125rem;color:var(--ink);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm)}input.svelte-zyka3t:focus-visible{outline:3px solid var(--focus);outline-offset:1px}.refusal.svelte-zyka3t{margin:0;padding:.625rem .75rem;border-left:.25rem solid var(--fault);background:var(--fault-wash);font-size:1rem}footer.svelte-zyka3t{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.25rem}.field.svelte-1oilv5s{display:flex;flex-direction:column;gap:.25rem;padding:.5rem 0}label.svelte-1oilv5s{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}.name.svelte-1oilv5s{font-size:1.125rem;font-weight:700}code.svelte-1oilv5s{font-size:.9375rem;color:var(--ink-muted)}input.svelte-1oilv5s{min-height:3rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.refused.svelte-1oilv5s input:where(.svelte-1oilv5s){border-left:.5rem solid var(--fault)}.hint.svelte-1oilv5s,.fault.svelte-1oilv5s{margin:0;font-size:1rem;color:var(--ink-muted)}.fault.svelte-1oilv5s{padding-left:.5rem;border-left:.25rem solid var(--fault)}.inventory.svelte-tuh4jc{display:flex;flex-direction:column;gap:.5rem}.headline.svelte-tuh4jc{margin:0;font-size:1.375rem;font-weight:700}dl.svelte-tuh4jc{margin:0;display:flex;flex-direction:column;gap:.25rem}.row.svelte-tuh4jc{display:flex;align-items:baseline;gap:.75rem}dt.svelte-tuh4jc{flex:none;width:4.5rem;text-align:right;font-size:1.375rem;font-weight:700}dd.svelte-tuh4jc{margin:0;display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem;font-size:1.125rem}.label.svelte-tuh4jc{font-weight:700}.note.svelte-tuh4jc{color:var(--ink-muted)}.rows.svelte-tuh4jc{padding:0 .5rem;min-height:var(--touch-min);text-decoration:underline;color:var(--ink-muted)}.oneline.svelte-tuh4jc{margin:0;font-size:1rem;color:var(--ink-muted)}.panel.svelte-ww3z5u{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}h2.svelte-ww3z5u{margin:0 0 .5rem;font-size:1.5rem;font-weight:700}.note.svelte-ww3z5u{margin:0 0 .75rem;font-size:1rem;color:var(--ink-muted)}.pages.svelte-3gk7u7{display:flex;flex-direction:column;gap:1rem}.fact.svelte-3gk7u7{margin:.5rem 0;font-size:1.125rem}.muted.svelte-3gk7u7{color:var(--ink-muted);font-size:1rem}.actions.svelte-3gk7u7{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.key.svelte-3gk7u7{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.scroll.svelte-3gk7u7{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--bg)}.rows.svelte-3gk7u7{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7):first-child{border-top:none}.line.svelte-3gk7u7,.id.svelte-3gk7u7,.value.svelte-3gk7u7{color:var(--ink-muted)}.line.svelte-3gk7u7{flex:none;width:7rem}.what.svelte-3gk7u7{font-weight:700}.message.svelte-3gk7u7{flex:1 1 20rem}.pick.svelte-3gk7u7{height:2.75rem;padding:0 .5rem;font-weight:700;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-3gk7u7:hover:not(:disabled){background:var(--surface)}}.search.svelte-3gk7u7,label.svelte-3gk7u7{display:block;margin-top:.5rem;font-size:1.0625rem;font-weight:700}.choice.svelte-3gk7u7{display:flex;flex-direction:column}.choice.svelte-3gk7u7 label:where(.svelte-3gk7u7){display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin:0;font-weight:400}.choice.svelte-3gk7u7 input:where(.svelte-3gk7u7){width:1.5rem;height:1.5rem;min-height:0;flex:0 0 auto;padding:0;background:none;border:none;border-radius:0}input.svelte-3gk7u7{min-height:2.75rem;width:100%;max-width:34rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.decision.svelte-3gk7u7{margin-top:.75rem;padding-top:.75rem;border-top:1px solid var(--border)}.act-block.svelte-3gk7u7{margin-top:1rem;padding:.75rem 1rem 1rem;background:var(--bg);border-radius:var(--radius)}.act-block.svelte-3gk7u7 .what:where(.svelte-3gk7u7){margin:0;font-size:1.0625rem;font-weight:700}.drop.svelte-3gk7u7{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius);transition:border-color var(--tap) var(--ease),background-color var(--tap) var(--ease)}.drop.dropping.svelte-3gk7u7{border-color:var(--focus);background:var(--waiting-wash)}.drop.working.svelte-3gk7u7{border-color:var(--waiting);background:var(--waiting-wash)}.drop.svelte-3gk7u7 p:where(.svelte-3gk7u7){margin:0;font-size:1.125rem}.choose.svelte-3gk7u7{display:inline-flex;align-items:center;margin:0;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease)}.choose.svelte-3gk7u7:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){.choose.svelte-3gk7u7:active{transform:none}}@media(hover:hover){.choose.svelte-3gk7u7:hover{filter:brightness(.92)}}.choose.svelte-3gk7u7 input:where(.svelte-3gk7u7){display:none}table.svelte-3gk7u7{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-3gk7u7,td.svelte-3gk7u7{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}.why.svelte-3gk7u7{white-space:normal;min-width:16rem;color:var(--ink-muted)}th.svelte-3gk7u7{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.light.svelte-1421wa8{display:flex;flex-direction:column;gap:.375rem;padding:.875rem 1rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--waiting);border-radius:var(--radius)}.light[data-level=ok].svelte-1421wa8{border-left-color:var(--ready)}.light[data-level=warn].svelte-1421wa8{border-left-color:var(--warning)}.light[data-level=fault].svelte-1421wa8{border-left-color:var(--fault)}header.svelte-1421wa8{display:flex;align-items:center;gap:.5rem}h3.svelte-1421wa8{margin:0;font-size:1.25rem;font-weight:700}.dot.svelte-1421wa8{width:1rem;height:1rem;border-radius:50%;background:var(--waiting);flex:none}.dot[data-level=ok].svelte-1421wa8{background:var(--ready)}.dot[data-level=warn].svelte-1421wa8{background:var(--warning)}.dot[data-level=fault].svelte-1421wa8{background:var(--fault)}.dot[data-level=unknown].svelte-1421wa8,.dot[data-level=off].svelte-1421wa8{background:transparent;box-shadow:inset 0 0 0 3px var(--waiting)}.verdict.svelte-1421wa8{margin-left:auto;font-size:1rem;font-weight:700;color:var(--ink-muted);letter-spacing:.03em}.value.svelte-1421wa8{margin:0;font-size:1.125rem}.remedy.svelte-1421wa8{margin:0;font-size:1rem;color:var(--ink-muted)}.dashboard.svelte-1w44m0y{display:flex;flex-direction:column;gap:1rem}.lights.svelte-1w44m0y{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));grid-auto-rows:1fr;gap:var(--touch-gap)}.fact.svelte-1w44m0y{margin:0 0 .5rem;font-size:1.125rem;overflow-wrap:break-word}.remedy.svelte-1w44m0y,.count.svelte-1w44m0y{color:var(--ink-muted);font-size:1rem}.restart.svelte-1w44m0y strong:where(.svelte-1w44m0y){letter-spacing:.03em}.restart[data-verdict=missing].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--warning);background:var(--warning-wash)}.restart[data-verdict=unknown].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--waiting);background:var(--waiting-wash)}.decisions.svelte-1w44m0y,.events.svelte-1w44m0y{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.375rem}.decisions.svelte-1w44m0y li:where(.svelte-1w44m0y),.events.svelte-1w44m0y li:where(.svelte-1w44m0y){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.what.svelte-1w44m0y,.message.svelte-1w44m0y{font-weight:700}.detail.svelte-1w44m0y,.time.svelte-1w44m0y,.source.svelte-1w44m0y,.code.svelte-1w44m0y{color:var(--ink-muted)}.events.svelte-1w44m0y li[data-level=error]:where(.svelte-1w44m0y){border-left:.25rem solid var(--fault);padding-left:.5rem}.events.svelte-1w44m0y li[data-level=warn]:where(.svelte-1w44m0y){border-left:.25rem solid var(--warning);padding-left:.5rem}.identity.svelte-1w44m0y{margin:0;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.125rem}.identity.svelte-1w44m0y dt:where(.svelte-1w44m0y){color:var(--ink-muted)}.identity.svelte-1w44m0y dd:where(.svelte-1w44m0y){margin:0;font-weight:700}.pages.svelte-1p4i0xm{display:flex;flex-direction:column;gap:1rem}.panel.svelte-1p4i0xm{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}.head.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;justify-content:space-between}h2.svelte-1p4i0xm{margin:0;font-size:1.5rem;font-weight:700}.standing.svelte-1p4i0xm{display:inline-flex;align-items:center;gap:.5rem;height:2rem;padding:0 .75rem;border-radius:var(--radius-pill);background:var(--waiting-wash);font-size:1rem;font-weight:700}.standing[data-level=ok].svelte-1p4i0xm{background:var(--ready-wash)}.standing[data-level=warn].svelte-1p4i0xm{background:var(--warning-wash)}.standing[data-level=fault].svelte-1p4i0xm{background:var(--fault-wash)}.dot.svelte-1p4i0xm{width:.625rem;height:.625rem;border-radius:var(--radius-pill);background:var(--waiting)}.standing[data-level=ok].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--ready)}.standing[data-level=warn].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--warning)}.standing[data-level=fault].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--fault)}.fact.svelte-1p4i0xm{margin:.75rem 0 0;font-size:1.125rem}.note.svelte-1p4i0xm,.count.svelte-1p4i0xm,.waiting.svelte-1p4i0xm{margin:.25rem 0 0;font-size:1rem;color:var(--ink-muted)}.check.svelte-1p4i0xm{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin-top:.5rem;font-size:1.0625rem}.check.svelte-1p4i0xm input:where(.svelte-1p4i0xm){width:1.5rem;height:1.5rem;flex:0 0 auto}.check.svelte-1p4i0xm small:where(.svelte-1p4i0xm){display:block;font-size:1rem;color:var(--ink-muted)}.actions.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.5rem;margin:.75rem 0 0}@media(hover:hover){.pick.svelte-1p4i0xm:hover,summary.svelte-1p4i0xm:hover{background:var(--bg)}}.list.svelte-1p4i0xm{margin:.25rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column}.list.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.125rem 0;border-top:1px solid var(--border-soft);font-size:1.0625rem}.list.svelte-1p4i0xm li.refused:where(.svelte-1p4i0xm){padding-left:.5rem;border-left:.25rem solid var(--fault);background:var(--fault-wash)}.pick.svelte-1p4i0xm{height:2.75rem;padding:0 .75rem;font-weight:700;text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}.what.svelte-1p4i0xm{font-weight:700}.detail.svelte-1p4i0xm{color:var(--ink-muted)}.folded.svelte-1p4i0xm{margin-top:1rem;border:1px solid var(--border);border-radius:var(--radius)}summary.svelte-1p4i0xm{display:flex;align-items:center;height:2.75rem;padding:0 .75rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius);cursor:pointer;transition:background-color var(--tap) var(--ease),transform var(--tap) var(--ease)}summary.svelte-1p4i0xm:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){summary.svelte-1p4i0xm:active{transform:none}}.frames.svelte-1p4i0xm{margin-top:1rem;max-height:18rem;overflow-y:auto;background:var(--waiting-wash);border:1px solid var(--border);border-radius:var(--radius)}.frames-head.svelte-1p4i0xm{position:sticky;top:0;margin:0;padding:.5rem .75rem;background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;box-shadow:var(--shadow-2);font-size:1rem;color:var(--ink-muted)}.interrupted.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;margin:0;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);font-size:1rem}.frame-rows.svelte-1p4i0xm{margin:0;padding:.5rem .75rem;list-style:none;display:flex;flex-direction:column;gap:.25rem}.frame-rows.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);gap:.75rem;font-size:.9375rem}.hex.svelte-1p4i0xm,.decoded.svelte-1p4i0xm{overflow-x:auto;white-space:pre}.decoded.svelte-1p4i0xm{color:var(--ink-muted)}div.journal.svelte-86o8oz{display:flex;flex-direction:column;gap:1rem;max-width:none;--reading-column: 68rem}.filters.svelte-86o8oz{display:flex;flex-wrap:wrap;gap:var(--touch-gap);align-items:center;margin-bottom:.75rem;font-size:1.0625rem}.filters.svelte-86o8oz label:where(.svelte-86o8oz){font-weight:700}select.svelte-86o8oz{min-height:2.75rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.export.svelte-86o8oz{display:inline-flex;align-items:center;gap:.5rem;height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.export.svelte-86o8oz:active{transform:scale(.975)}@media(hover:hover){.export.svelte-86o8oz:hover{border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.replay.svelte-86o8oz{margin-top:.5rem}.fact.svelte-86o8oz{margin:.5rem 0;max-width:var(--reading-column);font-size:1.125rem}.muted.svelte-86o8oz{color:var(--ink-muted);font-size:1rem}.table-box.svelte-86o8oz,.lines-box.svelte-86o8oz{overflow:auto;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.table-box.svelte-86o8oz{max-height:34rem}.lines-box.svelte-86o8oz{max-height:28rem}table.svelte-86o8oz{border-collapse:collapse;min-width:100%;font-size:1.0625rem}th.svelte-86o8oz,td.svelte-86o8oz{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}th.svelte-86o8oz{position:sticky;top:0;z-index:1;color:var(--ink-muted);font-size:1rem;background:var(--bg)}tbody.svelte-86o8oz tr:where(.svelte-86o8oz){transition:background-color var(--tap) var(--ease)}@media(hover:hover){tbody.svelte-86o8oz tr:where(.svelte-86o8oz):hover{background:var(--surface)}}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz){background:var(--surface);font-weight:700}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz)>td:where(.svelte-86o8oz):first-child{box-shadow:inset .1875rem 0 0 0 var(--focus)}.pick.svelte-86o8oz{height:2.75rem;padding:0 .5rem;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-86o8oz:hover:not(:disabled){background:var(--surface)}}.detail-row.svelte-86o8oz{background:var(--surface)}.detail-cell.svelte-86o8oz{padding:.75rem 1rem 1rem;white-space:normal;box-shadow:inset .1875rem 0 0 0 var(--focus)}.detail-cell.svelte-86o8oz h3:where(.svelte-86o8oz){margin:0 0 .5rem;font-size:1.25rem}.detail-cell.svelte-86o8oz dl:where(.svelte-86o8oz){display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;max-width:var(--reading-column);margin:0;font-size:1.0625rem;font-weight:400}.detail-cell.svelte-86o8oz dt:where(.svelte-86o8oz){color:var(--ink-muted)}.detail-cell.svelte-86o8oz dd:where(.svelte-86o8oz){margin:0}.detail-cell.svelte-86o8oz code:where(.svelte-86o8oz){overflow-wrap:anywhere}.lines.svelte-86o8oz{margin:0;padding:0 .75rem;list-style:none}.lines.svelte-86o8oz li:where(.svelte-86o8oz){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.lines.svelte-86o8oz li:where(.svelte-86o8oz):first-child{border-top:none}.lines.svelte-86o8oz li[data-level=error]:where(.svelte-86o8oz),.lines.svelte-86o8oz li[data-level=critical]:where(.svelte-86o8oz){border-left:.25rem solid var(--fault);padding-left:.5rem}.lines.svelte-86o8oz li[data-level=warn]:where(.svelte-86o8oz){border-left:.25rem solid var(--warning);padding-left:.5rem}.when.svelte-86o8oz,.level.svelte-86o8oz,.from.svelte-86o8oz,.code.svelte-86o8oz,.detail-text.svelte-86o8oz{color:var(--ink-muted)}.level.svelte-86o8oz{flex:none;width:8rem}.message.svelte-86o8oz{flex:1 1 20rem;font-weight:700}.sr-only.svelte-86o8oz{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.pages.svelte-llv1r8{display:flex;flex-direction:column;gap:1rem}.stale.svelte-llv1r8{margin:0 0 .75rem;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);border-radius:var(--radius-sm);font-size:1.0625rem}.preview.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:1.5rem;align-items:flex-start}.sheet.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0;padding:.75rem;background:var(--waiting-wash);border-radius:var(--radius-lg)}img.svelte-llv1r8{width:40rem;max-width:100%;image-rendering:pixelated;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-1)}.refused.svelte-llv1r8{margin:0;padding:.5rem .75rem;background:var(--fault-wash);border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);font-size:1.0625rem}.nudges.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0}.axis.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0;font-size:1.125rem}.pair.svelte-llv1r8{display:flex;gap:var(--touch-gap)}.fault.svelte-llv1r8{margin:0;padding-left:.5rem;border-left:.25rem solid var(--fault);font-size:1rem;color:var(--ink-muted)}.check.svelte-llv1r8{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;font-size:1.0625rem}.check.svelte-llv1r8 input:where(.svelte-llv1r8){width:1.5rem;height:1.5rem;flex:0 0 auto}.truncation.svelte-llv1r8,.absent.svelte-llv1r8,.waiting.svelte-llv1r8,.bound.svelte-llv1r8,.cost.svelte-llv1r8{margin:1rem 0 0;font-size:1rem;color:var(--ink-muted)}.absent.svelte-llv1r8{margin-top:.5rem}.bound.svelte-llv1r8{margin-top:.25rem}.cost.svelte-llv1r8{margin-top:.5rem}.actions.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin-top:.75rem}.pages.svelte-hzfkjn{display:flex;flex-direction:column;gap:1rem}.fact.svelte-hzfkjn{margin:.5rem 0;font-size:1.125rem}.muted.svelte-hzfkjn,.locked.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}h3.svelte-hzfkjn{margin:1.5rem 0 .5rem;font-size:1.25rem}.scroll.svelte-hzfkjn{overflow-x:auto}table.svelte-hzfkjn{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-hzfkjn,td.svelte-hzfkjn{padding:.375rem .5rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}input.svelte-hzfkjn{min-height:2.75rem;width:100%;min-width:6rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.rules.svelte-hzfkjn{margin:.75rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.75rem}.rule.svelte-hzfkjn{padding:.75rem 1rem 1rem;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.rule-head.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0}.rank.svelte-hzfkjn{flex:none;min-width:1.75rem;color:var(--ink-muted);font-variant-numeric:tabular-nums;font-weight:700}.rule-label.svelte-hzfkjn{font-size:1.125rem;font-weight:700}.token.svelte-hzfkjn{color:var(--ink-muted);font-size:.9375rem}.severity.svelte-hzfkjn{margin-left:auto;padding:.125rem .625rem;font-size:.9375rem;border-radius:var(--radius-pill);background:var(--waiting-wash)}.severity[data-blocking=true].svelte-hzfkjn{background:var(--warning-wash)}.when.svelte-hzfkjn{margin:.375rem 0 0;font-size:1rem}.quote.svelte-hzfkjn{margin:.5rem 0 0;padding:.5rem .75rem;font-size:1.0625rem;background:var(--surface);border-radius:var(--radius-sm);box-shadow:var(--shadow-1)}.quote.silent.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem;box-shadow:none;background:var(--waiting-wash)}.note.svelte-hzfkjn{margin:.5rem 0 0;font-size:1rem;color:var(--ink-muted)}.toggle.svelte-hzfkjn{display:flex;align-items:center;gap:.75rem;min-height:2.75rem;margin:.5rem 0 0;padding:.375rem .75rem;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--waiting-wash);cursor:pointer;transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease)}.toggle[data-on=true].svelte-hzfkjn{background:var(--ready-wash)}@media(hover:hover){.toggle.svelte-hzfkjn:hover{border-color:var(--ink-muted)}}.toggle.svelte-hzfkjn input:where(.svelte-hzfkjn){flex:none;width:1.5rem;height:1.5rem;min-height:0;min-width:0;padding:0;accent-color:var(--focus)}.toggle-text.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline}.toggle-label.svelte-hzfkjn{font-size:1.0625rem;font-weight:700}.hint.svelte-hzfkjn{flex:1 1 20rem;color:var(--ink-muted);font-size:1rem}.box.svelte-hzfkjn{display:contents}.verdicts.svelte-hzfkjn{margin:0;padding:0;list-style:none}.waivers.svelte-hzfkjn{max-height:24rem;overflow-y:auto}.verdicts.svelte-hzfkjn li:where(.svelte-hzfkjn){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.verdicts.svelte-hzfkjn li[data-blocking=true]:where(.svelte-hzfkjn){border-left:.25rem solid var(--warning);background:var(--warning-wash);padding-left:.5rem}.what.svelte-hzfkjn,.message.svelte-hzfkjn{font-weight:700}.detail.svelte-hzfkjn{color:var(--ink-muted)}.dead.svelte-hzfkjn{flex:1 1 20rem;padding:.125rem .625rem;font-size:1rem;background:var(--warning-wash);border-radius:var(--radius-sm)}.unread.svelte-hzfkjn{margin:.5rem 0;padding:.5rem .75rem;font-size:1rem;background:var(--fault-wash);border-left:.25rem solid var(--fault);border-radius:var(--radius-sm)}.pages.svelte-18wbxwu{display:flex;flex-direction:column;gap:1rem}.fact.svelte-18wbxwu{margin:.5rem 0;font-size:1.125rem}.muted.svelte-18wbxwu{color:var(--ink-muted);font-size:1rem}.filename.svelte-18wbxwu{margin-top:1rem;font-weight:700}.identity.svelte-18wbxwu{margin:1rem 0 0;padding:.75rem 1rem;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.0625rem;background:var(--bg);border-radius:var(--radius)}.identity.svelte-18wbxwu dt:where(.svelte-18wbxwu){color:var(--ink-muted)}.identity.svelte-18wbxwu dd:where(.svelte-18wbxwu){margin:0;font-weight:700}.actions.svelte-18wbxwu{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.choose.svelte-18wbxwu{display:inline-flex;align-items:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.choose.svelte-18wbxwu:active:not(.off){transform:scale(.975)}@media(hover:hover){.choose.svelte-18wbxwu:hover:not(.off){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.choose.off.svelte-18wbxwu{opacity:.5;box-shadow:none;cursor:default}.choose.working.svelte-18wbxwu{opacity:1;border-color:var(--waiting)}.choose.svelte-18wbxwu input:where(.svelte-18wbxwu){display:none}.key.svelte-18wbxwu{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.same.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--ready);border-radius:var(--radius-sm);background:var(--ready-wash)}.warned.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);background:var(--fault-wash)}.faults.svelte-18wbxwu{margin-top:.75rem;padding:.25rem 1rem .5rem;border-left:.375rem solid var(--warning);border-radius:var(--radius);background:var(--warning-wash)}.faults.svelte-18wbxwu ul:where(.svelte-18wbxwu){margin:0;padding-left:1.25rem;font-size:1.0625rem}.allowed.svelte-18wbxwu{display:block;color:var(--ink-muted);font-size:1rem}.scroll.svelte-18wbxwu{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-lg);background:var(--bg)}table.svelte-18wbxwu{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-18wbxwu,td.svelte-18wbxwu{padding:.375rem .75rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-18wbxwu{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.rows.svelte-18wbxwu{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.5rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu):first-child{border-top:none}.what.svelte-18wbxwu{font-weight:700}.detail.svelte-18wbxwu{color:var(--ink-muted)}.big.svelte-15nlt6i{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.write.svelte-15nlt6i{color:var(--surface);background:var(--action);border-color:var(--action)}.destructive.svelte-15nlt6i{color:var(--surface);background:var(--danger);border-color:var(--danger)}.write.svelte-15nlt6i .hint:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .hint:where(.svelte-15nlt6i){color:var(--surface);opacity:.85}.write.svelte-15nlt6i .guarded:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .guarded:where(.svelte-15nlt6i){background:var(--surface);color:var(--ink)}.big.svelte-15nlt6i:disabled{opacity:.5}.big.busy.svelte-15nlt6i{opacity:1;border-color:var(--waiting);background:var(--waiting-wash)}.big.busy.write.svelte-15nlt6i{background:var(--action)}.big.busy.destructive.svelte-15nlt6i{background:var(--danger)}.guarded.svelte-15nlt6i{margin-left:.5rem;padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;vertical-align:middle}.big.engaged.svelte-15nlt6i{border-left:.5rem solid var(--warning)}.label.svelte-15nlt6i{font-size:1.375rem;font-weight:700}.hint.svelte-15nlt6i{font-size:1rem;color:var(--ink-muted)}.troubleshooting.svelte-1yd0nzg{display:flex;flex-direction:column;gap:1rem}.buttons.svelte-1yd0nzg{display:grid;grid-template-columns:repeat(auto-fit,minmax(22rem,1fr));gap:var(--touch-gap)}.big.svelte-1yd0nzg{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.label.svelte-1yd0nzg{font-size:1.375rem;font-weight:700}.hint.svelte-1yd0nzg{font-size:1rem;color:var(--ink-muted)}.report.svelte-1yd0nzg{margin:0;padding:.75rem 1rem;font-size:1.125rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--ready);border-radius:var(--radius)}.drop.svelte-1yd0nzg{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius)}.drop.dropping.svelte-1yd0nzg{border-color:var(--focus)}.drop.svelte-1yd0nzg p:where(.svelte-1yd0nzg){margin:0;font-size:1.125rem}.key.svelte-1yd0nzg{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.choose.svelte-1yd0nzg{display:inline-flex;align-items:center;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius);cursor:pointer}@media(hover:hover){.choose.svelte-1yd0nzg:hover{filter:brightness(.92)}}.choose.svelte-1yd0nzg input:where(.svelte-1yd0nzg){display:none}.fact.svelte-1yd0nzg{margin:0 0 .5rem;font-size:1.125rem}.muted.svelte-1yd0nzg{color:var(--ink-muted)}.admin.svelte-9b2mjq{position:fixed;inset:0;z-index:90;display:grid;grid-template-columns:16rem 1fr;background:var(--bg);color:var(--ink);overflow:hidden}.rail.svelte-9b2mjq{display:flex;flex-direction:column;gap:.125rem;padding:1rem .75rem;overflow-y:auto;background:var(--surface);border-right:1px solid var(--border)}h1.svelte-9b2mjq{margin:0 .5rem 1rem;font-size:1.25rem;letter-spacing:-.01em}.group.svelte-9b2mjq{margin:1rem .5rem .375rem;color:var(--ink-muted);font-size:.8125rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.entry.svelte-9b2mjq{padding:0 .75rem;height:2.5rem;display:flex;align-items:center;text-align:left;font-size:1.0625rem;border-radius:var(--radius-sm);color:var(--ink-muted);transition:background-color var(--tap) var(--ease),color var(--tap) var(--ease)}@media(hover:hover){.entry.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.entry.current.svelte-9b2mjq{background:var(--bg);color:var(--ink);font-weight:700;box-shadow:inset .1875rem 0 0 0 var(--focus)}.foot.svelte-9b2mjq{margin-top:auto;padding:1rem .5rem 0;border-top:1px solid var(--border)}.station.svelte-9b2mjq{margin:0;font-size:1rem;font-weight:600}.build.svelte-9b2mjq{margin:.125rem 0 0;font-size:.8125rem;color:var(--ink-muted)}.technical.svelte-9b2mjq{display:flex;align-items:center;gap:.5rem;min-height:2.75rem;margin-top:.75rem;color:var(--ink-muted);font-size:.9375rem}.technical.svelte-9b2mjq input:where(.svelte-9b2mjq){width:1.5rem;height:1.5rem;flex:0 0 auto}.back.svelte-9b2mjq{margin-top:.75rem;padding:0 .5rem;height:2.25rem;font-size:.9375rem;color:var(--ink-muted);border-radius:var(--radius-sm)}@media(hover:hover){.back.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.body.svelte-9b2mjq{display:flex;flex-direction:column;min-width:0;overflow:hidden}.page.svelte-9b2mjq{flex:1;min-height:0;overflow-y:auto;padding:1.5rem 2rem 3rem}.page.svelte-9b2mjq>*{max-width:68rem}.page-title.svelte-9b2mjq{margin:0 0 1.25rem;font-size:1.75rem;letter-spacing:-.01em}.waiting.svelte-9b2mjq{margin:0;font-size:1.125rem;color:var(--ink-muted)}.banner.svelte-9b2mjq{flex:none;margin:0;padding:.75rem 2rem;font-size:1.0625rem;background:var(--surface);border-bottom:1px solid var(--border)}.notice.svelte-9b2mjq{border-left:.375rem solid var(--ready);background:var(--ready-wash)}.failure.svelte-9b2mjq{border-left:.375rem solid var(--fault);background:var(--fault-wash)}.pending.svelte-9b2mjq{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;border-left:.375rem solid var(--warning);background:var(--warning-wash)}.pending.svelte-9b2mjq p:where(.svelte-9b2mjq){margin:0;flex:1 1 24rem}.save-bar.svelte-9b2mjq{flex:none;padding:.75rem 2rem;background:var(--surface);border-top:1px solid var(--border);box-shadow:var(--shadow-1)}.retired.svelte-9b2mjq{margin:0 0 .5rem;font-size:.9375rem;color:var(--ink-muted)}.faults.svelte-9b2mjq{margin:0 0 .5rem;padding-left:1.25rem;font-size:1rem} +.act.svelte-5tpq0o{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.read.svelte-5tpq0o{color:var(--ink);background:var(--surface);border:1px solid var(--border)}.write.svelte-5tpq0o{color:var(--surface);background:var(--action);border:1px solid var(--action)}.destructive.svelte-5tpq0o{min-height:var(--touch-min);color:var(--surface);background:var(--danger);border:1px solid var(--danger)}@media(hover:hover){.read.svelte-5tpq0o:hover:not(:disabled){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}.write.svelte-5tpq0o:hover:not(:disabled){border-color:var(--action)}.destructive.svelte-5tpq0o:hover:not(:disabled){border-color:var(--danger)}.write.svelte-5tpq0o:hover:not(:disabled),.destructive.svelte-5tpq0o:hover:not(:disabled){box-shadow:var(--shadow-2);filter:brightness(.92)}}.act.svelte-5tpq0o:disabled{opacity:.5;box-shadow:none;cursor:default}.act.busy.svelte-5tpq0o:disabled{opacity:1}.key.svelte-5tpq0o{padding:.0625rem .375rem;border-radius:var(--radius-pill);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;background:var(--bg);color:var(--ink-muted)}.write.svelte-5tpq0o .key:where(.svelte-5tpq0o),.destructive.svelte-5tpq0o .key:where(.svelte-5tpq0o){background:var(--surface);color:var(--ink)}.scrim.svelte-zyka3t{position:fixed;inset:0;z-index:95;display:flex;align-items:center;justify-content:center;padding:2rem;background:#1c1b1973}.panel.svelte-zyka3t{display:flex;flex-direction:column;gap:.75rem;width:min(34rem,100%);padding:1.75rem;background:var(--surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-2)}header.svelte-zyka3t{display:flex;align-items:center;gap:.75rem}.glyph.svelte-zyka3t{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;flex:none;border-radius:var(--radius-sm);background:var(--bg);color:var(--ink-muted)}h2.svelte-zyka3t{margin:0;font-size:1.375rem;line-height:1.2}.why.svelte-zyka3t,.how.svelte-zyka3t{margin:0;color:var(--ink-muted);font-size:1rem;line-height:1.4}.field.svelte-zyka3t{display:flex;flex-direction:column;gap:.375rem;font-size:1rem;color:var(--ink-muted)}input.svelte-zyka3t{height:2.75rem;padding:0 .75rem;font:inherit;font-size:1.125rem;color:var(--ink);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm)}input.svelte-zyka3t:focus-visible{outline:3px solid var(--focus);outline-offset:1px}.refusal.svelte-zyka3t{margin:0;padding:.625rem .75rem;border-left:.25rem solid var(--fault);background:var(--fault-wash);font-size:1rem}footer.svelte-zyka3t{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.25rem}.field.svelte-1oilv5s{display:flex;flex-direction:column;gap:.25rem;padding:.5rem 0}label.svelte-1oilv5s{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}.name.svelte-1oilv5s{font-size:1.125rem;font-weight:700}code.svelte-1oilv5s{font-size:.9375rem;color:var(--ink-muted)}input.svelte-1oilv5s{min-height:3rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.refused.svelte-1oilv5s input:where(.svelte-1oilv5s){border-left:.5rem solid var(--fault)}.hint.svelte-1oilv5s,.fault.svelte-1oilv5s{margin:0;font-size:1rem;color:var(--ink-muted)}.fault.svelte-1oilv5s{padding-left:.5rem;border-left:.25rem solid var(--fault)}.inventory.svelte-tuh4jc{display:flex;flex-direction:column;gap:.5rem}.headline.svelte-tuh4jc{margin:0;font-size:1.375rem;font-weight:700}dl.svelte-tuh4jc{margin:0;display:flex;flex-direction:column;gap:.25rem}.row.svelte-tuh4jc{display:flex;align-items:baseline;gap:.75rem}dt.svelte-tuh4jc{flex:none;width:4.5rem;text-align:right;font-size:1.375rem;font-weight:700}dd.svelte-tuh4jc{margin:0;display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem;font-size:1.125rem}.label.svelte-tuh4jc{font-weight:700}.note.svelte-tuh4jc{color:var(--ink-muted)}.rows.svelte-tuh4jc{padding:0 .5rem;min-height:var(--touch-min);text-decoration:underline;color:var(--ink-muted)}.oneline.svelte-tuh4jc{margin:0;font-size:1rem;color:var(--ink-muted)}.panel.svelte-ww3z5u{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}h2.svelte-ww3z5u{margin:0 0 .5rem;font-size:1.5rem;font-weight:700}.note.svelte-ww3z5u{margin:0 0 .75rem;font-size:1rem;color:var(--ink-muted)}.pages.svelte-3gk7u7{display:flex;flex-direction:column;gap:1rem}.fact.svelte-3gk7u7{margin:.5rem 0;font-size:1.125rem}.muted.svelte-3gk7u7{color:var(--ink-muted);font-size:1rem}.actions.svelte-3gk7u7{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.key.svelte-3gk7u7{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.scroll.svelte-3gk7u7{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--bg)}.rows.svelte-3gk7u7{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7):first-child{border-top:none}.line.svelte-3gk7u7,.id.svelte-3gk7u7,.value.svelte-3gk7u7{color:var(--ink-muted)}.line.svelte-3gk7u7{flex:none;width:7rem}.what.svelte-3gk7u7{font-weight:700}.message.svelte-3gk7u7{flex:1 1 20rem}.pick.svelte-3gk7u7{height:2.75rem;padding:0 .5rem;font-weight:700;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-3gk7u7:hover:not(:disabled){background:var(--surface)}}.search.svelte-3gk7u7,label.svelte-3gk7u7{display:block;margin-top:.5rem;font-size:1.0625rem;font-weight:700}.choice.svelte-3gk7u7{display:flex;flex-direction:column}.choice.svelte-3gk7u7 label:where(.svelte-3gk7u7){display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin:0;font-weight:400}.choice.svelte-3gk7u7 input:where(.svelte-3gk7u7){width:1.5rem;height:1.5rem;min-height:0;flex:0 0 auto;padding:0;background:none;border:none;border-radius:0}input.svelte-3gk7u7{min-height:2.75rem;width:100%;max-width:34rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.decision.svelte-3gk7u7{margin-top:.75rem;padding-top:.75rem;border-top:1px solid var(--border)}.act-block.svelte-3gk7u7{margin-top:1rem;padding:.75rem 1rem 1rem;background:var(--bg);border-radius:var(--radius)}.act-block.svelte-3gk7u7 .what:where(.svelte-3gk7u7){margin:0;font-size:1.0625rem;font-weight:700}.drop.svelte-3gk7u7{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius);transition:border-color var(--tap) var(--ease),background-color var(--tap) var(--ease)}.drop.dropping.svelte-3gk7u7{border-color:var(--focus);background:var(--waiting-wash)}.drop.working.svelte-3gk7u7{border-color:var(--waiting);background:var(--waiting-wash)}.drop.svelte-3gk7u7 p:where(.svelte-3gk7u7){margin:0;font-size:1.125rem}.choose.svelte-3gk7u7{display:inline-flex;align-items:center;margin:0;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease)}.choose.svelte-3gk7u7:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){.choose.svelte-3gk7u7:active{transform:none}}@media(hover:hover){.choose.svelte-3gk7u7:hover{filter:brightness(.92)}}.choose.svelte-3gk7u7 input:where(.svelte-3gk7u7){display:none}table.svelte-3gk7u7{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-3gk7u7,td.svelte-3gk7u7{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}.why.svelte-3gk7u7{white-space:normal;min-width:16rem;color:var(--ink-muted)}th.svelte-3gk7u7{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.light.svelte-1421wa8{display:flex;flex-direction:column;gap:.375rem;padding:.875rem 1rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--waiting);border-radius:var(--radius)}.light[data-level=ok].svelte-1421wa8{border-left-color:var(--ready)}.light[data-level=warn].svelte-1421wa8{border-left-color:var(--warning)}.light[data-level=fault].svelte-1421wa8{border-left-color:var(--fault)}header.svelte-1421wa8{display:flex;align-items:center;gap:.5rem}h3.svelte-1421wa8{margin:0;font-size:1.25rem;font-weight:700}.dot.svelte-1421wa8{width:1rem;height:1rem;border-radius:50%;background:var(--waiting);flex:none}.dot[data-level=ok].svelte-1421wa8{background:var(--ready)}.dot[data-level=warn].svelte-1421wa8{background:var(--warning)}.dot[data-level=fault].svelte-1421wa8{background:var(--fault)}.dot[data-level=unknown].svelte-1421wa8,.dot[data-level=off].svelte-1421wa8{background:transparent;box-shadow:inset 0 0 0 3px var(--waiting)}.verdict.svelte-1421wa8{margin-left:auto;font-size:1rem;font-weight:700;color:var(--ink-muted);letter-spacing:.03em}.value.svelte-1421wa8{margin:0;font-size:1.125rem}.remedy.svelte-1421wa8{margin:0;font-size:1rem;color:var(--ink-muted)}.dashboard.svelte-1w44m0y{display:flex;flex-direction:column;gap:1rem}.lights.svelte-1w44m0y{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));grid-auto-rows:1fr;gap:var(--touch-gap)}.fact.svelte-1w44m0y{margin:0 0 .5rem;font-size:1.125rem;overflow-wrap:break-word}.remedy.svelte-1w44m0y,.count.svelte-1w44m0y{color:var(--ink-muted);font-size:1rem}.restart.svelte-1w44m0y strong:where(.svelte-1w44m0y){letter-spacing:.03em}.restart[data-verdict=missing].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--warning);background:var(--warning-wash)}.restart[data-verdict=unknown].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--waiting);background:var(--waiting-wash)}.decisions.svelte-1w44m0y,.events.svelte-1w44m0y{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.375rem}.decisions.svelte-1w44m0y li:where(.svelte-1w44m0y),.events.svelte-1w44m0y li:where(.svelte-1w44m0y){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.what.svelte-1w44m0y,.message.svelte-1w44m0y{font-weight:700}.detail.svelte-1w44m0y,.time.svelte-1w44m0y,.source.svelte-1w44m0y,.code.svelte-1w44m0y{color:var(--ink-muted)}.events.svelte-1w44m0y li[data-level=error]:where(.svelte-1w44m0y){border-left:.25rem solid var(--fault);padding-left:.5rem}.events.svelte-1w44m0y li[data-level=warn]:where(.svelte-1w44m0y){border-left:.25rem solid var(--warning);padding-left:.5rem}.identity.svelte-1w44m0y{margin:0;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.125rem}.identity.svelte-1w44m0y dt:where(.svelte-1w44m0y){color:var(--ink-muted)}.identity.svelte-1w44m0y dd:where(.svelte-1w44m0y){margin:0;font-weight:700}.pages.svelte-1p4i0xm{display:flex;flex-direction:column;gap:1rem}.panel.svelte-1p4i0xm{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}.head.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;justify-content:space-between}h2.svelte-1p4i0xm{margin:0;font-size:1.5rem;font-weight:700}.standing.svelte-1p4i0xm{display:inline-flex;align-items:center;gap:.5rem;height:2rem;padding:0 .75rem;border-radius:var(--radius-pill);background:var(--waiting-wash);font-size:1rem;font-weight:700}.standing[data-level=ok].svelte-1p4i0xm{background:var(--ready-wash)}.standing[data-level=warn].svelte-1p4i0xm{background:var(--warning-wash)}.standing[data-level=fault].svelte-1p4i0xm{background:var(--fault-wash)}.dot.svelte-1p4i0xm{width:.625rem;height:.625rem;border-radius:var(--radius-pill);background:var(--waiting)}.standing[data-level=ok].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--ready)}.standing[data-level=warn].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--warning)}.standing[data-level=fault].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--fault)}.fact.svelte-1p4i0xm{margin:.75rem 0 0;font-size:1.125rem}.note.svelte-1p4i0xm,.count.svelte-1p4i0xm,.waiting.svelte-1p4i0xm{margin:.25rem 0 0;font-size:1rem;color:var(--ink-muted)}.check.svelte-1p4i0xm{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin-top:.5rem;font-size:1.0625rem}.check.svelte-1p4i0xm input:where(.svelte-1p4i0xm){width:1.5rem;height:1.5rem;flex:0 0 auto}.check.svelte-1p4i0xm small:where(.svelte-1p4i0xm){display:block;font-size:1rem;color:var(--ink-muted)}.actions.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.5rem;margin:.75rem 0 0}@media(hover:hover){.pick.svelte-1p4i0xm:hover,summary.svelte-1p4i0xm:hover{background:var(--bg)}}.list.svelte-1p4i0xm{margin:.25rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column}.list.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.125rem 0;border-top:1px solid var(--border-soft);font-size:1.0625rem}.list.svelte-1p4i0xm li.refused:where(.svelte-1p4i0xm){padding-left:.5rem;border-left:.25rem solid var(--fault);background:var(--fault-wash)}.pick.svelte-1p4i0xm{height:2.75rem;padding:0 .75rem;font-weight:700;text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}.what.svelte-1p4i0xm{font-weight:700}.detail.svelte-1p4i0xm{color:var(--ink-muted)}.folded.svelte-1p4i0xm{margin-top:1rem;border:1px solid var(--border);border-radius:var(--radius)}summary.svelte-1p4i0xm{display:flex;align-items:center;height:2.75rem;padding:0 .75rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius);cursor:pointer;transition:background-color var(--tap) var(--ease),transform var(--tap) var(--ease)}summary.svelte-1p4i0xm:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){summary.svelte-1p4i0xm:active{transform:none}}.frames.svelte-1p4i0xm{margin-top:1rem;max-height:18rem;overflow-y:auto;background:var(--waiting-wash);border:1px solid var(--border);border-radius:var(--radius)}.frames-head.svelte-1p4i0xm{position:sticky;top:0;margin:0;padding:.5rem .75rem;background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;box-shadow:var(--shadow-2);font-size:1rem;color:var(--ink-muted)}.interrupted.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;margin:0;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);font-size:1rem}.frame-rows.svelte-1p4i0xm{margin:0;padding:.5rem .75rem;list-style:none;display:flex;flex-direction:column;gap:.25rem}.frame-rows.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);gap:.75rem;font-size:.9375rem}.hex.svelte-1p4i0xm,.decoded.svelte-1p4i0xm{overflow-x:auto;white-space:pre}.decoded.svelte-1p4i0xm{color:var(--ink-muted)}div.journal.svelte-86o8oz{display:flex;flex-direction:column;gap:1rem;max-width:none;--reading-column: 68rem}.filters.svelte-86o8oz{display:flex;flex-wrap:wrap;gap:var(--touch-gap);align-items:center;margin-bottom:.75rem;font-size:1.0625rem}.filters.svelte-86o8oz label:where(.svelte-86o8oz){font-weight:700}select.svelte-86o8oz{min-height:2.75rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.export.svelte-86o8oz{display:inline-flex;align-items:center;gap:.5rem;height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.export.svelte-86o8oz:active{transform:scale(.975)}@media(hover:hover){.export.svelte-86o8oz:hover{border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.replay.svelte-86o8oz{margin-top:.5rem}.fact.svelte-86o8oz{margin:.5rem 0;max-width:var(--reading-column);font-size:1.125rem}.muted.svelte-86o8oz{color:var(--ink-muted);font-size:1rem}.table-box.svelte-86o8oz,.lines-box.svelte-86o8oz{overflow:auto;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.table-box.svelte-86o8oz{max-height:34rem}.lines-box.svelte-86o8oz{max-height:28rem}table.svelte-86o8oz{border-collapse:collapse;min-width:100%;font-size:1.0625rem}th.svelte-86o8oz,td.svelte-86o8oz{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}th.svelte-86o8oz{position:sticky;top:0;z-index:1;color:var(--ink-muted);font-size:1rem;background:var(--bg)}tbody.svelte-86o8oz tr:where(.svelte-86o8oz){transition:background-color var(--tap) var(--ease)}@media(hover:hover){tbody.svelte-86o8oz tr:where(.svelte-86o8oz):hover{background:var(--surface)}}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz){background:var(--surface);font-weight:700}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz)>td:where(.svelte-86o8oz):first-child{box-shadow:inset .1875rem 0 0 0 var(--focus)}.pick.svelte-86o8oz{height:2.75rem;padding:0 .5rem;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-86o8oz:hover:not(:disabled){background:var(--surface)}}.detail-row.svelte-86o8oz{background:var(--surface)}.detail-cell.svelte-86o8oz{padding:.75rem 1rem 1rem;white-space:normal;box-shadow:inset .1875rem 0 0 0 var(--focus)}.detail-cell.svelte-86o8oz h3:where(.svelte-86o8oz){margin:0 0 .5rem;font-size:1.25rem}.detail-cell.svelte-86o8oz dl:where(.svelte-86o8oz){display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;max-width:var(--reading-column);margin:0;font-size:1.0625rem;font-weight:400}.detail-cell.svelte-86o8oz dt:where(.svelte-86o8oz){color:var(--ink-muted)}.detail-cell.svelte-86o8oz dd:where(.svelte-86o8oz){margin:0}.detail-cell.svelte-86o8oz code:where(.svelte-86o8oz){overflow-wrap:anywhere}.lines.svelte-86o8oz{margin:0;padding:0 .75rem;list-style:none}.lines.svelte-86o8oz li:where(.svelte-86o8oz){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.lines.svelte-86o8oz li:where(.svelte-86o8oz):first-child{border-top:none}.lines.svelte-86o8oz li[data-level=error]:where(.svelte-86o8oz),.lines.svelte-86o8oz li[data-level=critical]:where(.svelte-86o8oz){border-left:.25rem solid var(--fault);padding-left:.5rem}.lines.svelte-86o8oz li[data-level=warn]:where(.svelte-86o8oz){border-left:.25rem solid var(--warning);padding-left:.5rem}.when.svelte-86o8oz,.level.svelte-86o8oz,.from.svelte-86o8oz,.code.svelte-86o8oz,.detail-text.svelte-86o8oz{color:var(--ink-muted)}.level.svelte-86o8oz{flex:none;width:8rem}.message.svelte-86o8oz{flex:1 1 20rem;font-weight:700}.sr-only.svelte-86o8oz{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.pages.svelte-llv1r8{display:flex;flex-direction:column;gap:1rem}.stale.svelte-llv1r8{margin:0 0 .75rem;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);border-radius:var(--radius-sm);font-size:1.0625rem}.preview.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:1.5rem;align-items:flex-start}.sheet.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0;padding:.75rem;background:var(--waiting-wash);border-radius:var(--radius-lg)}img.svelte-llv1r8{width:40rem;max-width:100%;image-rendering:pixelated;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-1)}.refused.svelte-llv1r8{margin:0;padding:.5rem .75rem;background:var(--fault-wash);border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);font-size:1.0625rem}.nudges.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0}.axis.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0;font-size:1.125rem}.pair.svelte-llv1r8{display:flex;gap:var(--touch-gap)}.fault.svelte-llv1r8{margin:0;padding-left:.5rem;border-left:.25rem solid var(--fault);font-size:1rem;color:var(--ink-muted)}.check.svelte-llv1r8{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;font-size:1.0625rem}.check.svelte-llv1r8 input:where(.svelte-llv1r8){width:1.5rem;height:1.5rem;flex:0 0 auto}.truncation.svelte-llv1r8,.absent.svelte-llv1r8,.waiting.svelte-llv1r8,.bound.svelte-llv1r8,.cost.svelte-llv1r8{margin:1rem 0 0;font-size:1rem;color:var(--ink-muted)}.absent.svelte-llv1r8{margin-top:.5rem}.bound.svelte-llv1r8{margin-top:.25rem}.cost.svelte-llv1r8{margin-top:.5rem}.actions.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin-top:.75rem}.pages.svelte-hzfkjn{display:flex;flex-direction:column;gap:1rem}.fact.svelte-hzfkjn{margin:.5rem 0;font-size:1.125rem}.muted.svelte-hzfkjn,.locked.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}h3.svelte-hzfkjn{margin:1.5rem 0 .5rem;font-size:1.25rem}.scroll.svelte-hzfkjn{overflow-x:auto}table.svelte-hzfkjn{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-hzfkjn,td.svelte-hzfkjn{padding:.375rem .5rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}input.svelte-hzfkjn{min-height:2.75rem;width:100%;min-width:6rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.rules.svelte-hzfkjn{margin:.75rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.75rem}.rule.svelte-hzfkjn{padding:.75rem 1rem 1rem;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.rule-head.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0}.rank.svelte-hzfkjn{flex:none;min-width:1.75rem;color:var(--ink-muted);font-variant-numeric:tabular-nums;font-weight:700}.rule-label.svelte-hzfkjn{font-size:1.125rem;font-weight:700}.token.svelte-hzfkjn{color:var(--ink-muted);font-size:.9375rem}.severity.svelte-hzfkjn{margin-left:auto;padding:.125rem .625rem;font-size:.9375rem;border-radius:var(--radius-pill);background:var(--waiting-wash)}.severity[data-blocking=true].svelte-hzfkjn{background:var(--warning-wash)}.when.svelte-hzfkjn{margin:.375rem 0 0;font-size:1rem}.quote.svelte-hzfkjn{margin:.5rem 0 0;padding:.5rem .75rem;font-size:1.0625rem;background:var(--surface);border-radius:var(--radius-sm);box-shadow:var(--shadow-1)}.quote.silent.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem;box-shadow:none;background:var(--waiting-wash)}.note.svelte-hzfkjn{margin:.5rem 0 0;font-size:1rem;color:var(--ink-muted)}.toggle.svelte-hzfkjn{display:flex;align-items:center;gap:.75rem;min-height:2.75rem;margin:.5rem 0 0;padding:.375rem .75rem;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--waiting-wash);cursor:pointer;transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease)}.toggle[data-on=true].svelte-hzfkjn{background:var(--ready-wash)}@media(hover:hover){.toggle.svelte-hzfkjn:hover{border-color:var(--ink-muted)}}.toggle.svelte-hzfkjn input:where(.svelte-hzfkjn){flex:none;width:1.5rem;height:1.5rem;min-height:0;min-width:0;padding:0;accent-color:var(--focus)}.toggle-text.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline}.toggle-label.svelte-hzfkjn{font-size:1.0625rem;font-weight:700}.hint.svelte-hzfkjn{flex:1 1 20rem;color:var(--ink-muted);font-size:1rem}.box.svelte-hzfkjn{display:contents}.verdicts.svelte-hzfkjn{margin:0;padding:0;list-style:none}.waivers.svelte-hzfkjn{max-height:24rem;overflow-y:auto}.verdicts.svelte-hzfkjn li:where(.svelte-hzfkjn){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.verdicts.svelte-hzfkjn li[data-blocking=true]:where(.svelte-hzfkjn){border-left:.25rem solid var(--warning);background:var(--warning-wash);padding-left:.5rem}.what.svelte-hzfkjn,.message.svelte-hzfkjn{font-weight:700}.detail.svelte-hzfkjn{color:var(--ink-muted)}.dead.svelte-hzfkjn{flex:1 1 20rem;padding:.125rem .625rem;font-size:1rem;background:var(--warning-wash);border-radius:var(--radius-sm)}.unread.svelte-hzfkjn{margin:.5rem 0;padding:.5rem .75rem;font-size:1rem;background:var(--fault-wash);border-left:.25rem solid var(--fault);border-radius:var(--radius-sm)}.pages.svelte-18wbxwu{display:flex;flex-direction:column;gap:1rem}.fact.svelte-18wbxwu{margin:.5rem 0;font-size:1.125rem}.muted.svelte-18wbxwu{color:var(--ink-muted);font-size:1rem}.filename.svelte-18wbxwu{margin-top:1rem;font-weight:700}.identity.svelte-18wbxwu{margin:1rem 0 0;padding:.75rem 1rem;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.0625rem;background:var(--bg);border-radius:var(--radius)}.identity.svelte-18wbxwu dt:where(.svelte-18wbxwu){color:var(--ink-muted)}.identity.svelte-18wbxwu dd:where(.svelte-18wbxwu){margin:0;font-weight:700}.actions.svelte-18wbxwu{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.choose.svelte-18wbxwu{display:inline-flex;align-items:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.choose.svelte-18wbxwu:active:not(.off){transform:scale(.975)}@media(hover:hover){.choose.svelte-18wbxwu:hover:not(.off){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.choose.off.svelte-18wbxwu{opacity:.5;box-shadow:none;cursor:default}.choose.working.svelte-18wbxwu{opacity:1;border-color:var(--waiting)}.choose.svelte-18wbxwu input:where(.svelte-18wbxwu){display:none}.key.svelte-18wbxwu{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.same.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--ready);border-radius:var(--radius-sm);background:var(--ready-wash)}.warned.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);background:var(--fault-wash)}.faults.svelte-18wbxwu{margin-top:.75rem;padding:.25rem 1rem .5rem;border-left:.375rem solid var(--warning);border-radius:var(--radius);background:var(--warning-wash)}.faults.svelte-18wbxwu ul:where(.svelte-18wbxwu){margin:0;padding-left:1.25rem;font-size:1.0625rem}.allowed.svelte-18wbxwu{display:block;color:var(--ink-muted);font-size:1rem}.scroll.svelte-18wbxwu{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-lg);background:var(--bg)}table.svelte-18wbxwu{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-18wbxwu,td.svelte-18wbxwu{padding:.375rem .75rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-18wbxwu{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.rows.svelte-18wbxwu{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.5rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu):first-child{border-top:none}.what.svelte-18wbxwu{font-weight:700}.detail.svelte-18wbxwu{color:var(--ink-muted)}.big.svelte-15nlt6i{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.write.svelte-15nlt6i{color:var(--surface);background:var(--action);border-color:var(--action)}.destructive.svelte-15nlt6i{color:var(--surface);background:var(--danger);border-color:var(--danger)}.write.svelte-15nlt6i .hint:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .hint:where(.svelte-15nlt6i){color:var(--surface);opacity:.85}.write.svelte-15nlt6i .guarded:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .guarded:where(.svelte-15nlt6i){background:var(--surface);color:var(--ink)}.big.svelte-15nlt6i:disabled{opacity:.5}.big.busy.svelte-15nlt6i{opacity:1;border-color:var(--waiting);background:var(--waiting-wash)}.big.busy.write.svelte-15nlt6i{background:var(--action)}.big.busy.destructive.svelte-15nlt6i{background:var(--danger)}.guarded.svelte-15nlt6i{margin-left:.5rem;padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;vertical-align:middle}.big.engaged.svelte-15nlt6i{border-left:.5rem solid var(--warning)}.label.svelte-15nlt6i{font-size:1.375rem;font-weight:700}.hint.svelte-15nlt6i{font-size:1rem;color:var(--ink-muted)}.troubleshooting.svelte-1yd0nzg{display:flex;flex-direction:column;gap:1rem}.buttons.svelte-1yd0nzg{display:grid;grid-template-columns:repeat(auto-fit,minmax(22rem,1fr));gap:var(--touch-gap)}.big.svelte-1yd0nzg{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.label.svelte-1yd0nzg{font-size:1.375rem;font-weight:700}.hint.svelte-1yd0nzg{font-size:1rem;color:var(--ink-muted)}.report.svelte-1yd0nzg{margin:0;padding:.75rem 1rem;font-size:1.125rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--ready);border-radius:var(--radius)}.drop.svelte-1yd0nzg{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius)}.drop.dropping.svelte-1yd0nzg{border-color:var(--focus)}.drop.svelte-1yd0nzg p:where(.svelte-1yd0nzg){margin:0;font-size:1.125rem}.key.svelte-1yd0nzg{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.choose.svelte-1yd0nzg{display:inline-flex;align-items:center;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius);cursor:pointer}@media(hover:hover){.choose.svelte-1yd0nzg:hover{filter:brightness(.92)}}.choose.svelte-1yd0nzg input:where(.svelte-1yd0nzg){display:none}.fact.svelte-1yd0nzg{margin:0 0 .5rem;font-size:1.125rem}.muted.svelte-1yd0nzg{color:var(--ink-muted)}.row.svelte-o9s8nz{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:.75rem}.admin.svelte-9b2mjq{position:fixed;inset:0;z-index:90;display:grid;grid-template-columns:16rem 1fr;background:var(--bg);color:var(--ink);overflow:hidden}.rail.svelte-9b2mjq{display:flex;flex-direction:column;gap:.125rem;padding:1rem .75rem;overflow-y:auto;background:var(--surface);border-right:1px solid var(--border)}h1.svelte-9b2mjq{margin:0 .5rem 1rem;font-size:1.25rem;letter-spacing:-.01em}.group.svelte-9b2mjq{margin:1rem .5rem .375rem;color:var(--ink-muted);font-size:.8125rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.entry.svelte-9b2mjq{padding:0 .75rem;height:2.5rem;display:flex;align-items:center;text-align:left;font-size:1.0625rem;border-radius:var(--radius-sm);color:var(--ink-muted);transition:background-color var(--tap) var(--ease),color var(--tap) var(--ease)}@media(hover:hover){.entry.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.entry.current.svelte-9b2mjq{background:var(--bg);color:var(--ink);font-weight:700;box-shadow:inset .1875rem 0 0 0 var(--focus)}.foot.svelte-9b2mjq{margin-top:auto;padding:1rem .5rem 0;border-top:1px solid var(--border)}.station.svelte-9b2mjq{margin:0;font-size:1rem;font-weight:600}.build.svelte-9b2mjq{margin:.125rem 0 0;font-size:.8125rem;color:var(--ink-muted)}.technical.svelte-9b2mjq{display:flex;align-items:center;gap:.5rem;min-height:2.75rem;margin-top:.75rem;color:var(--ink-muted);font-size:.9375rem}.technical.svelte-9b2mjq input:where(.svelte-9b2mjq){width:1.5rem;height:1.5rem;flex:0 0 auto}.back.svelte-9b2mjq{margin-top:.75rem;padding:0 .5rem;height:2.25rem;font-size:.9375rem;color:var(--ink-muted);border-radius:var(--radius-sm)}@media(hover:hover){.back.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.body.svelte-9b2mjq{display:flex;flex-direction:column;min-width:0;overflow:hidden}.page.svelte-9b2mjq{flex:1;min-height:0;overflow-y:auto;padding:1.5rem 2rem 3rem}.page.svelte-9b2mjq>*{max-width:68rem}.page-title.svelte-9b2mjq{margin:0 0 1.25rem;font-size:1.75rem;letter-spacing:-.01em}.waiting.svelte-9b2mjq{margin:0;font-size:1.125rem;color:var(--ink-muted)}.banner.svelte-9b2mjq{flex:none;margin:0;padding:.75rem 2rem;font-size:1.0625rem;background:var(--surface);border-bottom:1px solid var(--border)}.notice.svelte-9b2mjq{border-left:.375rem solid var(--ready);background:var(--ready-wash)}.failure.svelte-9b2mjq{border-left:.375rem solid var(--fault);background:var(--fault-wash)}.pending.svelte-9b2mjq{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;border-left:.375rem solid var(--warning);background:var(--warning-wash)}.pending.svelte-9b2mjq p:where(.svelte-9b2mjq){margin:0;flex:1 1 24rem}.save-bar.svelte-9b2mjq{flex:none;padding:.75rem 2rem;background:var(--surface);border-top:1px solid var(--border);box-shadow:var(--shadow-1)}.retired.svelte-9b2mjq{margin:0 0 .5rem;font-size:.9375rem;color:var(--ink-muted)}.faults.svelte-9b2mjq{margin:0 0 .5rem;padding-left:1.25rem;font-size:1rem} diff --git a/internal/web/dist/assets/mount-DKnEPDGN.js b/internal/web/dist/assets/mount-DKnEPDGN.js deleted file mode 100644 index 1852872..0000000 --- a/internal/web/dist/assets/mount-DKnEPDGN.js +++ /dev/null @@ -1,89 +0,0 @@ -import{R as tr,T as ar,U as rr,V as xa,W as Kt,e as nr,X as sr,Y as lr,Z as ir,_ as or,u as pa,r as Ua,$ as ur,c as Fa,B as ca,A as N,k as e,z as p,F as Lt,d as Ot,b as vt,s as r,i as h,t as v,n as Gt,l as Ve,o as i,f as et,g as l,v as a,j as u,p as Rt,I as cr,h as At,q as o,w as ae,a0 as Ea,E as ra,x as at,G as Ma,a1 as oa,y as Xt,a2 as Ut,a3 as qt,a4 as na,C as fa,K as dr,H as vr,P as pr,a5 as fr}from"./app-Dn-BjSsJ.js";function Ba(n){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function gr(n,t,...s){var c=new rr(n);tr(()=>{const m=t()??null;c.ensure(m,m&&(k=>m(k,...s)))},ar)}function Va(n,t,s=!1){if(n.multiple){if(t==null)return;if(!sr(t))return lr();for(var c of n.options)c.selected=t.includes(sa(c));return}for(c of n.options){var m=sa(c);if(ir(m,t)){c.selected=!0;return}}(!s||t!==void 0)&&(n.selectedIndex=-1)}function mr(n){var t=new MutationObserver(()=>{"__value"in n&&Va(n,n.__value)});t.observe(n,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),or(()=>{t.disconnect()})}function hr(n,t,s=t){var c=new WeakSet,m=!0;xa(n,"change",k=>{var R=k?"[selected]":":checked",M;if(n.multiple)M=[].map.call(n.querySelectorAll(R),sa);else{var P=n.querySelector(R)??n.querySelector("option:not([disabled])");M=P&&sa(P)}s(M),n.__value=M,Kt!==null&&c.add(Kt)}),nr(()=>{var k=t();if(n===document.activeElement){var R=Kt;if(c.has(R))return}if(Va(n,k,m),m&&k===void 0){var M=n.querySelector(":checked");M!==null&&(k=sa(M),s(k))}n.__value=k,m=!1}),mr(n)}function sa(n){return"__value"in n?n.__value:n.value}function la(n,t,s=t){var c=new WeakSet;xa(n,"input",async m=>{var k=m?n.defaultValue:n.value;if(k=ma(n)?ha(k):k,s(k),Kt!==null&&c.add(Kt),await ur(),k!==(k=t())){var R=n.selectionStart,M=n.selectionEnd,P=n.value.length;if(n.value=k??"",M!==null){var B=n.value.length;R===M&&M===P&&B>P?(n.selectionStart=B,n.selectionEnd=B):(n.selectionStart=R,n.selectionEnd=Math.min(M,B))}}}),pa(t)==null&&n.value&&(s(ma(n)?ha(n.value):n.value),Kt!==null&&c.add(Kt)),Ua(()=>{var m=t();if(n===document.activeElement){var k=Kt;if(c.has(k))return}ma(n)&&m===ha(n.value)||n.type==="date"&&!m&&!n.value||m!==n.value&&(n.value=m??"")})}function za(n,t,s=t){xa(n,"change",c=>{var m=c?n.defaultChecked:n.checked;s(m)}),pa(t)==null&&s(n.checked),Ua(()=>{var c=t();n.checked=!!c})}function ma(n){var t=n.type;return t==="number"||t==="range"}function ha(n){return n===""?null:+n}function ka(n){Fa===null&&Ba(),ca(()=>{const t=pa(n);if(typeof t=="function")return t})}function _r(n){Fa===null&&Ba(),ka(()=>()=>pa(n))}class $t extends Error{constructor(t,s,c="",m=[]){super(s),this.status=t,this.code=c,this.faults=m,this.name="AdminError"}status;code;faults;get needsPassword(){return this.status===401}}function br(){return Qt("/admin/api/health")}function wr(){return wt("/admin/api/troubleshooting/reprint",{})}function yr(){return wt("/admin/api/troubleshooting/reload-catalog",{})}function xr(n){return wt("/admin/api/troubleshooting/manual-entry",{on:n})}function kr(){return wt("/admin/api/troubleshooting/roll-changed",{})}function qr(n){return wt("/admin/api/troubleshooting/fallback-printer",{on:n})}function Sr(){return wt("/admin/api/troubleshooting/test-scale",{})}function Lr(){return wt("/admin/api/troubleshooting/test-printer",{})}function Er(){return wt("/admin/api/troubleshooting/test-label",{})}async function Wa(n){const t=new FormData;t.append("file",n,n.name);const s=await fetch("/admin/api/catalog/import",{method:"POST",body:t});return Sa(s,"POST /admin/api/catalog/import")}const zr="/admin/api/diagnostic.zip";function jr(n){return wt("/admin/api/session",{password:n})}async function Pr(){await fetch("/admin/api/session",{method:"DELETE"})}function Tr(n,t){return wt("/admin/api/session/recovery",{code:n,password:t})}function qa(){return Qt("/admin/api/config")}function Cr(n){return Ha("PUT","/admin/api/config",n)}function Or(){return wt("/admin/api/config/confirm",{})}async function Rr(){return(await Qt("/admin/api/config/versions")).versions}function Ar(n){return wt("/admin/api/config/restore",{version:n})}async function Nr(){return(await Qt("/admin/api/ports")).ports}async function Ir(){return(await Qt("/admin/api/printers")).printers}async function $r(){return(await wt("/admin/api/printers/discover",{})).printers}function Dr(n){return wt("/admin/api/scale/detect",{port:n})}async function ja(n,t){return(await wt("/admin/api/scale/capture",{port:n,seconds:t})).frames}function Ga(n){return wt("/admin/api/printer/test?what="+n,{})}function Ur(n,t,s,c){const m=new URLSearchParams({template:n,demo:t?"1":"0",dual:s?"1":"0"});return m.set("t",String(c)),"/admin/api/label/preview.png?"+m.toString()}async function Fr(n){const t=new URLSearchParams(n);return(await Qt("/admin/api/journal?"+t)).weighings}function Mr(n){return"/admin/api/journal/export.csv?"+new URLSearchParams(n).toString()}async function Br(n){const t=new URLSearchParams(n);return(await Qt("/admin/api/technical?"+t)).entries}function Vr(n){const t=n===void 0?"":"?id="+String(n);return Qt("/admin/api/imports"+t)}function Wr(){return wt("/admin/api/catalog/reload",{})}function Gr(){return wt("/admin/api/catalog/forget-quarantine",{})}function Pa(n,t){return wt("/admin/api/products/"+encodeURIComponent(n)+"/decision",t)}function Hr(n){return wt("/admin/api/replay",{frame:n})}async function Qt(n){const t=await fetch(n,{headers:{accept:"application/json"}});return Sa(t,"GET "+n)}function wt(n,t){return Ha("POST",n,t)}async function Ha(n,t,s){const c=await fetch(t,{method:n,headers:{"content-type":"application/json"},body:JSON.stringify(s)});return Sa(c,n+" "+t)}async function Sa(n,t){const s=await n.text();if(!n.ok){const c=Ja(s);throw new $t(n.status,Kr(s,t,n.status)+Jr(n),c?.code??"",c?.faults??[])}if(s!=="")return JSON.parse(s)}function Jr(n){if(n.status!==429)return"";const t=Number(n.headers.get("Retry-After")??"");if(!Number.isFinite(t)||t<=0)return"";const s=Math.ceil(t/60);return s>1?` Réessayez dans ${String(s)} minutes.`:" Réessayez dans une minute."}function Kr(n,t,s){const c=Ja(n);return c?.message!==void 0&&c.message!==""?c.message:`${t} a répondu ${String(s)}.`}function Ja(n){try{return JSON.parse(n)}catch{return null}}class Xr{constructor(t){this.admin=t}admin;#e=N(null);get config(){return e(this.#e)}set config(t){p(this.#e,t,!0)}#a=N("");get fingerprint(){return e(this.#a)}set fingerprint(t){p(this.#a,t,!0)}#r=N(Lt([]));get retired(){return e(this.#r)}set retired(t){p(this.#r,t,!0)}#n=N(null);get pending(){return e(this.#n)}set pending(t){p(this.#n,t,!0)}#s=N(Lt([]));get faults(){return e(this.#s)}set faults(t){p(this.#s,t,!0)}#l=N(!1);get dirty(){return e(this.#l)}set dirty(t){p(this.#l,t,!0)}async load(){const t=await this.admin.load(()=>qa());t!==null&&(this.config=t.config,this.fingerprint=t.config_fingerprint,this.retired=t.retired_keys??[],this.pending=t.pending_confirmation,this.faults=[],this.dirty=!1)}value(t){let s=this.config;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}text(t){const s=this.value(t);return s==null?"":String(s)}number(t){const s=this.value(t);return typeof s=="number"?s:Number(s??0)}flag(t){return this.value(t)===!0}set(t,s){if(this.config===null)return;const c=t.split("."),m=c.pop();if(m===void 0)return;let k=this.config;for(const R of c){const M=k[R];(M===null||typeof M!="object")&&(k[R]={}),k=k[R]}k[m]=s,this.dirty=!0}unset(t){const s=t.split("."),c=s.pop();if(c===void 0||this.config===null)return!1;let m=this.config;for(const k of s){if(m===null||typeof m!="object")return!1;m=m[k]}return m===null||typeof m!="object"?!1:(delete m[c],this.dirty=!0,!0)}async save(){if(this.config===null)return!1;this.faults=[];const t=this.config;let s;try{s=await Cr(t)}catch(c){if(Ta(c))throw c;return this.admin.report(c),this.faults=Yr(this.admin),!1}return this.config=s.config,this.fingerprint=s.config_fingerprint,this.retired=s.retired_keys??[],this.pending=s.pending_confirmation,this.dirty=!1,!0}async confirm(){try{await Or()}catch(t){if(Ta(t))throw t;this.admin.report(t);return}this.admin.notice="La configuration est confirmée.",this.pending=null,await this.admin.refresh()}dropRetired(t){if(t.includes("[")){this.admin.actionError=`« ${t} » est logée dans un tableau : cet écran ne sait pas la retirer de là. Modifiez le fichier de configuration lui-même.`;return}this.unset(t)&&(this.retired=this.retired.filter(s=>s!==t))}}function Yr(n){return n.lastFaults}function Ta(n){return n instanceof $t&&(n.status===401||n.status===409)}const Qr={"station.number":"Numéro du poste","station.name":"Nom du poste","station.coop":"Nom de la coopérative","network.listen":"Adresse d’écoute","network.admin_on_lan":"Administration accessible depuis le réseau","ui.language":"Langue","ui.sound":"Son","ui.idle_timeout_s":"Retour à l’accueil après (secondes)","ui.reprint_window_s":"Réimpression possible pendant (secondes)","ui.show_grid_prices":"Afficher les prix sur les tuiles","scale.type":"Protocole de la balance","scale.present":"Ce poste a une balance","scale.manual_entry_allowed":"Saisie manuelle autorisée","scale.degrade_after_s":"Passer en mode dégradé après (secondes)","scale.options.port":"Port série","scale.options.baud":"Vitesse (bauds)","scale.options.bits":"Bits de données","scale.options.parity":"Parité","scale.options.stop":"Bits d’arrêt","scale.options.backoff_min_ms":"Attente minimale avant réessai (ms)","scale.options.backoff_max_ms":"Attente maximale avant réessai (ms)","printer.type":"Pilote d’impression","printer.template":"Gabarit d’étiquette","printer.options.transport":"Transport","printer.options.queue":"File d’impression","printer.options.path":"Fichier de périphérique","printer.options.address":"Adresse réseau","printer.options.darkness":"Noircissement","printer.options.speed":"Vitesse d’impression","printer.options.offset_x":"Décalage horizontal (dots)","printer.options.offset_y":"Décalage vertical (dots)","printer.options.invert_bits":"Inverser les points","printer.options.copies":"Exemplaires","printer.options.roll_capacity":"Étiquettes par rouleau","pricing.amount_rounding":"Arrondi du montant","pricing.unit_price_rounding":"Arrondi du prix au kilo","pricing.primary_code":"Tarif principal","pricing.reference_code":"Tarif de référence","barcode.verify_reference_check_digit":"Vérifier la clé de contrôle","limits.empty_max_g":"Plateau considéré vide en dessous de (g)","limits.basket_check_enabled":"Vérifier la présence du panier","limits.basket_min_g":"Poids du panier, borne basse (g)","limits.basket_max_g":"Poids du panier, borne haute (g)","limits.min_weight_g":"Poids minimum accepté","limits.max_weight_g":"Poids maximum accepté","limits.max_tare_g":"Tare maximale","limits.min_units":"Unités minimum","limits.max_units":"Unités maximum","limits.max_amount_cents":"Montant maximum (centimes)","stability.mode":"Exigence de stabilité","stability.min_duration_ms":"Durée de stabilité (ms)","stability.tolerance_g":"Tolérance de stabilité (g)","stability.timeout_ms":"Délai d’attente de stabilité (ms)","stability.on_timeout":"Au bout du délai","stability.min_latch_rate":"Taux d’accroche minimal","stability.latch_rate_window_ms":"Fenêtre de mesure du taux (ms)","stability.expiry_floor_ms":"Péremption, plancher (ms)","stability.expiry_ceiling_ms":"Péremption, plafond (ms)","stability.expiry_factor":"Péremption, facteur","catalog.type":"Où le poste va chercher le catalogue","catalog.options.directory":"Répertoire surveillé","catalog.options.url":"Adresse du serveur","catalog.options.username":"Compte","catalog.options.password":"Mot de passe","catalog.options.separator":"Séparateur du CSV","catalog.options.poll_interval_s":"Vérifier toutes les (secondes)","catalog.options.stable_polls":"Vérifications avant lecture","catalog.options.max_file_size_mb":"Taille maximale du fichier (Mo)","catalog.options.max_image_size_kb":"Taille maximale d’une image (Ko)","catalog.options.min_readable_ratio":"Part minimale de lignes lisibles","catalog.options.max_weighable_drop":"Baisse maximale des produits pesables","catalog.options.max_archives":"Archives conservées","catalog.options.archive_days":"Archives conservées (jours)","catalog.options.failures_before_reject":"Échecs avant mise en quarantaine","catalog.images.source":"Origine des photos","catalog.images.path":"Répertoire des photos","catalog.fallback_category":"Rayon par défaut","journal.max_rows":"Pesées conservées","journal.max_days":"Pesées conservées (jours)","journal.max_technical":"Événements techniques conservés","admin.session_minutes":"Durée d’une session (minutes)","admin.attempts_per_minute":"Tentatives par minute","maintenance.weekly_integrity_check":"Contrôle d’intégrité hebdomadaire","maintenance.disk_alert_mb":"Alerte disque en dessous de (Mo)"};function Ft(n){return Qr[n]??n}const Zr={station:"l’identité du poste",network:"le réseau",ui:"l’écran client",scale:"la balance",printer:"l’imprimante",pricing:"les tarifs",barcode:"le code-barres",limits:"les garde-fous",stability:"la stabilité",catalog:"le catalogue",journal:"le journal",maintenance:"la maintenance"};function en(n){return Zr[n]??n}const tn={scale:"balance",printer:"imprimante",catalog:"catalogue",ui:"écran",config:"configuration",http:"réseau",system:"système"};function Ka(n){return tn[n]??"origine inconnue"}const ba="openscale.admin.preferences",Xa="technical";class an{#e=N(Lt(rn()));get showTechnicalNames(){return e(this.#e)}set showTechnicalNames(t){p(this.#e,t,!0)}toggleTechnicalNames(){this.showTechnicalNames=!this.showTechnicalNames,nn(this.showTechnicalNames)}}function rn(){try{return globalThis.localStorage?.getItem(ba)===Xa}catch{return!1}}function nn(n){try{n?globalThis.localStorage?.setItem(ba,Xa):globalThis.localStorage?.removeItem(ba)}catch{}}const Wt=new an,sn=3e3,ln=["hardware","label","rules","catalog","journal","station"];function Ca(n){return ln.includes(n)}function on(n){return n instanceof $t&&(n.status===401||n.status===409)}class un{constructor(t=sn){this.periodMs=t}periodMs;#e=N(null);get health(){return e(this.#e)}set health(t){p(this.#e,t,!0)}#a=N("dashboard");get page(){return e(this.#a)}set page(t){p(this.#a,t,!0)}#r=N(!1);get expert(){return e(this.#r)}set expert(t){p(this.#r,t,!0)}#n=N("");get linkError(){return e(this.#n)}set linkError(t){p(this.#n,t,!0)}#s=N("");get actionError(){return e(this.#s)}set actionError(t){p(this.#s,t,!0)}#l=N(!1);get needsFirstPassword(){return e(this.#l)}set needsFirstPassword(t){p(this.#l,t,!0)}#d=N("");get notice(){return e(this.#d)}set notice(t){p(this.#d,t,!0)}#v=N(!1);get busy(){return e(this.#v)}set busy(t){p(this.#v,t,!0)}#p=N(Lt([]));get lastFaults(){return e(this.#p)}set lastFaults(t){p(this.#p,t,!0)}#f=N(null);get pending(){return e(this.#f)}set pending(t){p(this.#f,t,!0)}#i=null;#o=null;start(){this.refresh(),this.#i=setInterval(()=>{this.refresh()},this.periodMs)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null}async refresh(){try{this.health=await br(),this.linkError=""}catch(t){this.linkError=Oa(t)}}#u(){this.busy=!0,this.notice="",this.actionError="",this.needsFirstPassword=!1}#t(t){this.actionError=Oa(t),this.needsFirstPassword=t instanceof $t&&t.status===409,this.#m(t)}async run(t){this.#u();try{const s=await t();this.notice=s.message}catch(s){this.#t(s)}finally{this.busy=!1}await this.refresh()}async login(t){this.#u();try{return await jr(t),this.expert=!0,this.notice="Session d’administration ouverte.",!0}catch(s){return this.expert=!1,this.#t(s),!1}finally{this.busy=!1}}async recover(t,s){this.#u();try{const c=await Tr(t,s);return this.expert=!0,this.notice=c.warning??"Le mot de passe est remplacé et la session est ouverte.",!0}catch(c){return this.#t(c),!1}finally{this.busy=!1}}async protect(t){try{return await t()}catch(s){if(!on(s))return this.#t(s),null;if(!await this.#g(s))return null;try{return await t()}catch(c){return this.#t(c),null}}}#g(t){return this.pending={kind:t.status===409?"first-password":"password",message:t.message},new Promise(s=>{this.#o=s})}async answerPassword(t){await this.login(t)&&this.#c(!0)}async answerRecovery(t,s){await this.recover(t,s)&&this.#c(!0)}cancelPassword(){this.#c(!1)}#c(t){this.pending=null,this.notice="";const s=this.#o;this.#o=null,s?.(t)}async logout(){await Pr(),this.expert=!1,this.page="dashboard",this.notice="Session d’administration fermée."}open(t){this.actionError="",this.notice="",this.needsFirstPassword=!1,this.page=t}async load(t){try{const s=await t();return this.actionError="",this.lastFaults=[],s}catch(s){return this.lastFaults=s instanceof $t?s.faults:[],this.#t(s),null}}report(t){this.lastFaults=t instanceof $t?t.faults:[],this.#t(t)}#m(t){t instanceof $t&&t.needsPassword&&(this.expert=!1)}}function Oa(n){return n instanceof $t?n.message:n instanceof Error?"Le poste n’a pas répondu : "+n.message:"Le poste n’a pas répondu."}var cn=u('clé'),dn=u('');function rt(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"busy",3,!1),m=vt(t,"disabled",3,!1),k=vt(t,"protected",3,!1),R=vt(t,"act",3,void 0);var M=dn();let P;var B=a(M),$=r(B);{var le=ge=>{var xe=cn();l(ge,xe)};h($,ge=>{k()&&ge(le)})}v(()=>{P=Gt(M,1,`act ${s()??""}`,"svelte-5tpq0o",P,{"touch-target":s()==="destructive",busy:c()}),Ve(M,"data-kind",s()),Ve(M,"data-act",R()),M.disabled=m()||c(),i(B,`${(c()?"En cours…":t.label)??""} `)}),et("click",M,function(...ge){t.onrun?.apply(this,ge)}),l(n,M)}Ot(["click"]);var vn=u(`

                                    Le code de secours a été tiré à l’installation de ce poste et imprimé - sur sa fiche, rangée dans le classeur du magasin. Il n’existe nulle part ailleurs.

                                    `,1),pn=u(`

                                    Oublié ? Le code de secours de la fiche d’installation en repose un. - Un responsable peut aussi le faire en ligne de commande, avec openscale config password.

                                    `,1),fn=u('

                                    '),gn=u('');function mn(n,t){Rt(t,!0);const s=o(()=>t.admin.pending?.kind==="first-password");let c=N(""),m=N("");const k=8,R=o(()=>e(s)?e(m).trim().length>0&&e(c).length>=k:e(c).length>0);async function M(){!e(R)||t.admin.busy||(e(s)?await t.admin.answerRecovery(e(m),e(c)):await t.admin.answerPassword(e(c)),p(c,""),p(m,""))}var P=gn(),B=a(P),$=a(B),le=a($),ge=a(le);cr(ge,{name:"settings",size:"1.5rem"});var xe=r(le,2),pe=a(xe),L=r($,2),D=a(L),ie=r(L,2);{var Z=T=>{var b=vn(),I=r(ae(b),2),Y=r(a(I),2);Ea(Y);var ze=r(I,2),Ae=a(ze);Ae.textContent="Nouveau mot de passe — 8 caractères au moins";var Ye=r(Ae,2);et("keydown",Y,We=>We.key==="Enter"&&void M()),la(Y,()=>e(m),We=>p(m,We)),et("keydown",Ye,We=>We.key==="Enter"&&void M()),la(Ye,()=>e(c),We=>p(c,We)),l(T,b)},he=T=>{var b=pn(),I=ae(b),Y=r(a(I),2);Ea(Y),et("keydown",Y,ze=>ze.key==="Enter"&&void M()),la(Y,()=>e(c),ze=>p(c,ze)),l(T,b)};h(ie,T=>{e(s)?T(Z):T(he,-1)})}var H=r(ie,2);{var ee=T=>{var b=fn(),I=a(b);v(()=>i(I,t.admin.actionError)),l(T,b)};h(H,T=>{t.admin.actionError!==""&&T(ee)})}var j=r(H,2),O=a(j);rt(O,{label:"Annuler",onrun:()=>t.admin.cancelPassword()});var K=r(O,2);{let T=o(()=>e(s)?"Poser ce mot de passe":"Continuer"),b=o(()=>!e(R)||t.admin.busy);rt(K,{kind:"write",get label(){return e(T)},get disabled(){return e(b)},onrun:()=>{M()}})}v(()=>{i(pe,e(s)?"Ce poste n’a pas encore de mot de passe":"Mot de passe d’administration"),i(D,t.admin.pending?.message??"")}),l(n,P),At()}Ot(["keydown"]);var hn=u(' '),_n=u(""),bn=u(""),wn=u('

                                    '),yn=u(" "),xn=u('

                                    '),kn=u('
                                    ');function ht(n,t){Rt(t,!0);const s=vt(t,"hint",3,""),c=vt(t,"kind",3,"text"),m=vt(t,"disabled",3,!1),k=vt(t,"fault",3,""),R=vt(t,"allowed",19,()=>[]),M=o(()=>`field-${t.path.replace(/\./gu,"-")}`);var P=kn();let B;var $=a(P),le=a($),ge=a(le),xe=r(le,2);{var pe=j=>{var O=hn(),K=a(O);v(()=>i(K,t.path)),l(j,O)};h(xe,j=>{Wt.showTechnicalNames&&j(pe)})}var L=r($,2),D=r(L,2);{var ie=j=>{var O=bn();at(O,20,R,K=>K,(K,T)=>{var b=_n(),I={};v(()=>{I!==(I=T)&&(b.value=(b.__value=T)??"")}),l(K,b)}),v(()=>Ve(O,"id",e(M)+"-allowed")),l(j,O)};h(D,j=>{R().length>0&&j(ie)})}var Z=r(D,2);{var he=j=>{var O=wn(),K=a(O);v(()=>i(K,s())),l(j,O)};h(Z,j=>{s()!==""&&j(he)})}var H=r(Z,2);{var ee=j=>{var O=xn(),K=a(O),T=r(K);{var b=I=>{var Y=yn(),ze=a(Y);v(Ae=>i(ze,`Valeurs acceptées : ${Ae??""}.`),[()=>R().join(", ")]),l(I,Y)};h(T,I=>{R().length>0&&I(b)})}v(()=>i(K,`${k()??""} `)),l(j,O)};h(H,j=>{k()!==""&&j(ee)})}v(()=>{B=Gt(P,1,"field svelte-1oilv5s",null,B,{refused:k()!==""}),Ve($,"for",e(M)),i(ge,t.label),Ve(L,"id",e(M)),Ve(L,"type",c()),ra(L,t.value),L.disabled=m(),Ve(L,"list",R().length>0?e(M)+"-allowed":void 0)}),et("input",L,j=>t.onchange(j.currentTarget.value)),l(n,P),At()}Ot(["input"]);const Ra=1e3;function da(n){const t=La(n);return t===null?"":[ta(t.getDate()),ta(t.getMonth()+1),String(t.getFullYear())].join("/")}function Yt(n){const t=La(n);return t===null?"":`${da(n)} à ${ta(t.getHours())}:${ta(t.getMinutes())}`}function qn(n){const t=La(n);return t===null?"":[ta(t.getHours()),ta(t.getMinutes()),ta(t.getSeconds())].join(":")}function wa(n){const t=n/1e6;return t<1e3?`${Math.round(t)} Mo`:`${Ya(t/1e3)} Go`}function ve(n){const t=String(Math.abs(Math.trunc(n))),s=[];for(let c=t.length;c>0;c-=3)s.unshift(t.slice(Math.max(0,c-3),c));return(n<0?"-":"")+s.join(" ")}function ua(n){return n0&&k.push({count:Pt(n.anomalies_count),label:"anomalies",note:"à corriger dans Odoo",link:Aa(n.anomalies_count)}),n.unit_mismatches_count>0){const R=n.unit_mismatches_count;k.push({count:"+ "+Pt(R),label:R===1?"unité divergente":"unités divergentes",note:"pesable, unité à corriger",link:Aa(R)})}return{headline:`Catalogue du ${da(n.occurred_at)} — ${Pt(s)} produits reçus`,lines:k,oneLine:Ln(n)}}function Ln(n){const t=[`${Pt(n.rows_read_count)} reçus`,`${Pt(n.weighable_count)} pesables`,`${Pt(n.not_weighable_count)} non pesables`,`${Pt(n.anomalies_count)} anomalies`],s=n.unit_mismatches_count;return s>0&&t.push(`${Pt(s)} ${s===1?"unité divergente":"unités divergentes"}`),t.join(" · ")}function Aa(n){return n===1?"voir la ligne":`voir les ${Pt(n)} lignes`}function En(n){return n.map(t=>`${zn(t)} (${Pt(t.count)})`).join(", ")}function zn(n){switch(n.code){case"PREPACKAGED_PRODUCT":return"préemballés";case"INTERNAL_CODE_NOT_WEIGHABLE":return n.value===""?"code interne":"code interne "+n.value;case"NO_BARCODE":return"sans code-barres";default:return n.code}}function Pt(n){return String(n)}var jn=u(' '),Pn=u(''),Tn=u('
                                    '),Cn=u('

                                    ');function Qa(n,t){Rt(t,!0);const s=o(()=>Sn(t.record,t.motives));var c=Cn(),m=a(c),k=a(m),R=r(m,2);at(R,21,()=>e(s).lines,B=>B.label,(B,$)=>{var le=Tn(),ge=a(le),xe=a(ge),pe=r(ge,2),L=a(pe),D=a(L),ie=r(L,2);{var Z=ee=>{var j=jn(),O=a(j);v(()=>i(O,e($).note)),l(ee,j)};h(ie,ee=>{e($).note!==""&&ee(Z)})}var he=r(ie,2);{var H=ee=>{var j=Pn(),O=a(j);v(()=>i(O,e($).link)),et("click",j,()=>t.onshowrows(e($).label==="anomalies"?"anomalies":"units")),l(ee,j)};h(he,ee=>{e($).link!==""&&t.onshowrows!==void 0&&ee(H)})}v(()=>{Ve(le,"data-row",e($).label),i(xe,e($).count),i(D,e($).label)}),l(B,le)});var M=r(R,2),P=a(M);v(()=>{i(k,e(s).headline),i(P,e(s).oneLine)}),l(n,c),At()}Ot(["click"]);var On=u('

                                    '),Rn=u('

                                    ');function tt(n,t){const s=vt(t,"note",3,"");var c=Rn(),m=a(c),k=a(m),R=r(m,2);{var M=B=>{var $=On(),le=a($);v(()=>i(le,s())),l(B,$)};h(R,B=>{s()!==""&&B(M)})}var P=r(R,2);gr(P,()=>t.children),v(()=>i(k,t.title)),l(n,c)}const _a=(n,t=na,s=na)=>{var c=Nn(),m=a(c);at(m,21,t,fa,(k,R)=>{var M=An(),P=a(M),B=a(P),$=r(P,2),le=a($),ge=r($,2),xe=a(ge),pe=r(ge,2),L=a(pe);v(D=>{i(B,`ligne ${D??""}`),i(le,e(R).product_id),i(xe,e(R).value),i(L,e(R).message)},[()=>ve(e(R).csv_line)]),l(k,M)}),v(()=>Ve(c,"data-rows",s())),l(n,c)};var An=u('
                                  • '),Nn=u('
                                      '),In=u('

                                      Lecture des réglages du poste…

                                      '),$n=u(`

                                      Le poste y cherche le fichier , et le supprime - une fois lu : c’est ce qui dit au producteur que la livraison est prise.

                                      `,1),Dn=u(`

                                      Sur un serveur WebDAV, le dépôt d’un fichier CSV depuis cet écran n’est plus - possible : le poste n’a plus de répertoire local où l’écrire. C’est le seul recours - du jour de la mise en service.

                                      `,1),Un=u(`

                                      Ce poste ne déclare aucune source : choisissez-en une ci-dessus, sinon il n’ira - chercher aucun catalogue.

                                      `),Fn=u('
                                      ',1),Mn=u('

                                      Aucun import enregistré sur ce poste.

                                      '),Bn=u(`

                                      « Oublier la quarantaine » fait relire un fichier que le poste avait écarté : c’est le - seul geste de cette page qui puisse remettre en service un catalogue refusé.

                                      `,1),Vn=u('

                                      '),Wn=u(`

                                      Déposez ici le fichier clé

                                      Ce dépôt remplace toute la grille par le fichier apporté : il change ce que le poste - vend, et le mot de passe est donc demandé au moment du dépôt.

                                      `,1),Gn=u('

                                      '),Hn=u('

                                      Aucune anomalie sur le dernier import.

                                      '),Jn=u('

                                      ',1),Kn=u('

                                      '),Xn=u('

                                      Aucune unité divergente sur le dernier import.

                                      '),Yn=u('

                                      ',1),Qn=u('

                                      '),Zn=u('

                                      Aucun produit non pesable sur le dernier import.

                                      '),es=u('

                                      ',1),ts=u('

                                      Aucun import enregistré : rien n’a encore pu être retiré.

                                      '),as=u('

                                      Aucun produit retiré par le dernier import.

                                      '),rs=u(`

                                      Ils restent enregistrés avec leur historique : une étiquette déjà collée reste - lisible en caisse, et un produit qui revient dans un prochain fichier retrouve sa - tuile. Ce poste n’en publie encore que le nombre — leurs noms se lisent dans Odoo, - en comparant avec l’export précédent.

                                      `,1),ns=u('
                                    • '),ss=u(`

                                        Un produit retiré de la grille ne se trouve plus ici : il se reprend dans - « Décisions en vigueur », plus bas.

                                        `,1),ls=u(`

                                        Sans motif, le poste refuse la décision. Le seul acte qui s’en passe est celui qui - EFFACE la décision en vigueur : proposer de nouveau un produit qui ne porte aucune - dérogation.

                                        `),is=u(`

                                        Produit choisi :

                                        Ce produit est-il proposé dans la grille ?

                                        La dérogation s’enregistre seule : elle ne remet pas dans la grille un produit - qui en a été retiré, et retirer un produit n’efface pas sa dérogation.

                                        `),os=u(' ',1),us=u('

                                        Aucune décision locale : la grille est celle du fichier.

                                        '),cs=u('
                                      • '),ds=u('

                                          ',1),vs=u('

                                          '),ps=u('

                                          Aucun import dans l’historique.

                                          '),fs=u(' '),gs=u('

                                          QuandFichierSourceRésultatMotifLuesPesablesNon pesablesAnomaliesRetirés
                                          ',1),ms=u('
                                          ');function hs(n,t){Rt(t,!0);const s=vt(t,"admin",7),c=20,m=50,k=20,R=["NO_BARCODE","PREPACKAGED_PRODUCT","INTERNAL_CODE_NOT_WEIGHABLE"],M={applied:"appliqué",unchanged:"identique au précédent",rejected:"refusé",failed:"échec"},P={local_drop:"dépôt local",webdav:"WebDAV",manual:"déposé sur l’écran"},B={local_drop:["catalog.options.directory"],webdav:["catalog.options.url","catalog.options.username","catalog.options.password"]},$=o(()=>t.draft.text("catalog.type"));let le=N(Lt([])),ge=N(Lt([])),xe=N(Lt([])),pe=N(""),L=N(""),D=N(""),ie=N(""),Z=N(!1),he=N(""),H=N(""),ee=N("loading"),j=N("loading");const O=o(()=>s().busy||e(H)!==""),K=o(()=>e(ge).filter(w=>w.issue==="anomaly")),T=o(()=>e(ge).filter(w=>R.includes(w.code))),b=o(()=>e(ge).filter(w=>w.code==="UNIT_MISMATCH")),I=o(()=>new Map(e(xe).map(w=>[w.id,w.name]))),Y=o(()=>e(pe)===""?[]:dr(e(xe),vr,e(pe))),ze=o(()=>e(Y).slice(0,c)),Ae=o(()=>e(K).slice(0,m)),Ye=o(()=>e(b).slice(0,m)),We=o(()=>e(T).slice(0,m)),E=o(()=>[...t.health.decisions].sort((w,U)=>U.decided_at.localeCompare(w.decided_at))),re=o(()=>e(E).slice(0,k)),_=o(()=>e(L)===""?null:e(E).find(w=>w.product_id===e(L))??null),V=o(()=>e(_)===null?!0:e(_).offered),F=o(()=>e(_)?.min_weight_g??null),Q=o(()=>e(ie).trim()===""?null:Number(e(ie))),qe=o(()=>e(Q)!==null&&Number.isFinite(e(Q))&&e(Q)>0),Ne=o(()=>e(D).trim()),C=o(()=>e(Ne)!==""||!ye(!1,e(F))),ne=o(()=>e(Ne)!==""||!ye(!0,e(F))),oe=o(()=>e(qe)&&(e(Ne)!==""||!ye(e(V),e(Q)))),_e=o(()=>e(Ne)!==""||!ye(e(V),null)),me=o(()=>t.health.catalog?.products_withdrawn_count??0),He=o(()=>e(le).find(w=>t.health.catalog!==null&&w.id`${ve(e(me))} ${e(me)>1?"produits retirés":"produit retiré"}`+(e(He)===null?".":` depuis l’import du ${Yt(e(He).occurred_at)}.`)),g=o(()=>e(Y).length>e(ze).length?`${ve(e(ze).length)} produits affichés sur ${ve(e(Y).length)} trouvés — précisez votre recherche.`:`${ve(e(Y).length)} ${e(Y).length>1?"produits trouvés":"produit trouvé"}.`),x=o(()=>`${ve(e(le).length)} ${e(le).length>1?"imports affichés":"import affiché"} : le poste n’en publie jamais plus de vingt.`),S=o(()=>e(ee)==="loading"?"Lecture des signalements du dernier import…":"Les signalements du dernier import n’ont pas pu être lus : cet écran ne sait pas ce qu’ils disent."),ue=o(()=>e(ee)==="loading"?"Lecture de l’historique des imports…":"L’historique des imports n’a pas pu être lu : cet écran ne sait pas ce qu’il contient.");let d;ca(()=>{const w=t.health.catalog?.id??null;w!==d&&(d=w,q())});async function q(){const w=await s().load(()=>Vr(t.health.catalog?.id));w===null?p(ee,"unread"):(p(le,w.imports,!0),p(ge,w.findings,!0),p(ee,"read"));const U=await s().load(()=>Ma());if(U===null){p(j,"unread");return}p(xe,U.products,!0),p(j,"read")}async function y(w,U){p(H,w,!0),s().actionError="",s().notice="";try{const se=await s().protect(U);return se===null?!1:(s().notice=se.message,await s().refresh(),!0)}finally{p(H,"")}}function X(w){t.draft.set("catalog.type",w);for(const[U,se]of Object.entries(B))if(U!==w)for(const Ce of se)t.draft.unset(Ce)}function te(w){return t.draft.faults.find(U=>U.field===w)?.message??""}function Te(w){p(L,w,!0),p(D,"");const U=e(E).find(se=>se.product_id===w)?.min_weight_g??null;p(ie,U===null?"":String(U),!0)}function ye(w,U){return!(w&&U===null)}async function ce(w){if(e(L)==="")return;const U=e(L),se=e(F),Ce=e(Ne);await y("offered",()=>Pa(U,{offered:w,min_weight_g:se,reason:Ce}))&&p(D,"")}async function W(w){if(e(L)==="")return;const U=e(L),se=e(V),Ce=e(Ne);await y(w===null?"waiver-off":"waiver",()=>Pa(U,{offered:se,min_weight_g:w,reason:Ce}))&&p(D,"")}async function Ue(w){if(p(Z,!1),w!=null){p(he,""),s().actionError="",s().notice="",p(H,"import");try{const U=await s().protect(()=>Wa(w));if(U===null)return;p(he,`${w.name} : ${ve(U.rows_read_count)} lignes lues, ${ve(U.weighable_count)} pesables. `+(U.reason===""?"Le fichier est déposé ; son résultat s’inscrira dans l’historique des imports.":U.reason)),await s().refresh()}finally{p(H,"")}}}function Fe(w){w.preventDefault(),p(Z,!1);const U=w.dataTransfer?.files.item(0);if(e(O)){p(he,`${U?.name??"Ce fichier"} n’a pas été déposé : un acte est déjà en cours sur cette page. Réessayez quand il aura répondu.`);return}Ue(U)}function ke(w){const U=w.files?.item(0);w.value="",Ue(U)}function Se(w){const U=e(I).get(w);return U!==void 0?U:e(j)==="loading"?"Lecture du nom…":e(j)==="unread"?"Nom inconnu : le catalogue n’a pas pu être lu":"Produit absent du catalogue en service"}function Je(w,U,se,Ce){const Me=U>1?Ce:se;return w>=U?`${ve(U)} ${Me}.`:`${ve(w)} lignes affichées sur ${ve(U)} ${Me}.`}function J(w){const U=[];return w.offered||U.push("retiré de la grille"),w.min_weight_g!==null&&U.push(`peut peser à partir de ${ve(w.min_weight_g)} g`),U.length===0?"aucune restriction":U.join(" · ")}function be(w){const U=M[w.result]??"résultat inconnu";return w.code===""?U:`${U} (${w.code})`}function fe(w){return P[w.source]??"source inconnue"}var Pe=ms(),Ie=a(Pe);tt(Ie,{title:"Où le poste va chercher le catalogue",children:(w,U)=>{var se=Fn(),Ce=ae(se),Me=a(Ce),Ke=a(Me),Ge=r(Me,2),A=a(Ge),G=r(Ce,2);{var de=Ee=>{var $e=In();l(Ee,$e)},Oe=Ee=>{var $e=$n(),Be=ae($e);{let it=o(()=>Ft("catalog.options.directory")),dt=o(()=>t.draft.text("catalog.options.directory")),gt=o(()=>te("catalog.options.directory"));ht(Be,{get label(){return e(it)},path:"catalog.options.directory",get value(){return e(dt)},hint:"Laissez vide pour le répertoire du poste, celui que le service crée lui-même. Un répertoire nommé ici doit exister : le poste ne le crée pas.",get fault(){return e(gt)},onchange:yt=>t.draft.set("catalog.options.directory",yt)})}var ft=r(Be,2),ot=r(a(ft)),ct=a(ot);v(()=>i(ct,`flv_${t.health.station??""}.csv`)),l(Ee,$e)},we=Ee=>{var $e=Dn(),Be=ae($e);{let ct=o(()=>Ft("catalog.options.url")),it=o(()=>t.draft.text("catalog.options.url")),dt=o(()=>te("catalog.options.url"));ht(Be,{get label(){return e(ct)},path:"catalog.options.url",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.url",gt)})}var ft=r(Be,2);{let ct=o(()=>Ft("catalog.options.username")),it=o(()=>t.draft.text("catalog.options.username")),dt=o(()=>te("catalog.options.username"));ht(ft,{get label(){return e(ct)},path:"catalog.options.username",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.username",gt)})}var ot=r(ft,2);{let ct=o(()=>Ft("catalog.options.password")),it=o(()=>te("catalog.options.password"));ht(ot,{get label(){return e(ct)},path:"catalog.options.password",kind:"password",value:"",hint:"Laissez vide : le mot de passe actuel est conservé.",get fault(){return e(it)},onchange:dt=>t.draft.set("catalog.options.password",dt)})}l(Ee,$e)},Re=Ee=>{var $e=Un();l(Ee,$e)};h(G,Ee=>{t.draft.config===null?Ee(de):e($)==="local_drop"?Ee(Oe,1):e($)==="webdav"?Ee(we,2):Ee(Re,-1)})}v(()=>{oa(Ke,e($)==="local_drop"),oa(A,e($)==="webdav")}),et("change",Ke,()=>X("local_drop")),et("change",A,()=>X("webdav")),l(w,se)},$$slots:{default:!0}});var nt=r(Ie,2);tt(nt,{title:"Dernier import",children:(w,U)=>{var se=Bn(),Ce=ae(se);{var Me=we=>{var Re=Mn();l(we,Re)},Ke=we=>{Qa(we,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives}})};h(Ce,we=>{t.health.catalog===null?we(Me):we(Ke,-1)})}var Ge=r(Ce,2),A=a(Ge),G=r(Ge,2),de=a(G);{let we=o(()=>e(H)==="reload");rt(de,{act:"reload",kind:"write",label:"Recharger le catalogue",protected:!0,get busy(){return e(we)},get disabled(){return e(O)},onrun:()=>{y("reload",Wr)}})}var Oe=r(de,2);{let we=o(()=>e(H)==="quarantine");rt(Oe,{act:"quarantine",kind:"destructive",label:"Oublier la quarantaine",protected:!0,get busy(){return e(we)},get disabled(){return e(O)},onrun:()=>{y("quarantine",Gr)}})}v(()=>i(A,t.health.catalog_source===null?"Aucune source de catalogue publiée par ce poste.":"Source : "+t.health.catalog_source.label)),l(w,se)},$$slots:{default:!0}});var ut=r(nt,2);tt(ut,{title:"Déposer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(w,U)=>{var se=Wn(),Ce=ae(se);{var Me=Ee=>{var $e=Vn(),Be=a($e);v(()=>i(Be,e(he))),l(Ee,$e)};h(Ce,Ee=>{e(he)!==""&&Ee(Me)})}var Ke=r(Ce,2);let Ge;var A=a(Ke),G=r(a(A)),de=a(G),Oe=r(A,2),we=a(Oe),Re=r(we);v(()=>{Ge=Gt(Ke,1,"drop svelte-3gk7u7",null,Ge,{dropping:e(Z),working:e(H)==="import"}),i(de,`flv_${t.health.station??""}.csv`),i(we,`${e(H)==="import"?"Import en cours…":"Choisir un fichier"} `),Re.disabled=e(O)}),Xt("dragover",Ke,Ee=>{Ee.preventDefault(),!e(O)&&p(Z,!0)}),Xt("dragleave",Ke,()=>p(Z,!1)),Xt("drop",Ke,Fe),et("change",Re,Ee=>ke(Ee.currentTarget)),l(w,se)},$$slots:{default:!0}});var pt=r(ut,2);tt(pt,{title:"Anomalies à corriger dans Odoo",note:"Chaque ligne porte son numéro dans le CSV, son motif et la valeur fautive.",children:(w,U)=>{var se=Ut(),Ce=ae(se);{var Me=A=>{var G=Gn(),de=a(G);v(()=>i(de,e(S))),l(A,G)},Ke=A=>{var G=Hn();l(A,G)},Ge=A=>{var G=Jn(),de=ae(G),Oe=a(de),we=r(Oe);{var Re=$e=>{var Be=qt("Corrigez celles-ci dans Odoo : l’import suivant ne signalera que ce qui reste.");l($e,Be)};h(we,$e=>{e(K).length>e(Ae).length&&$e(Re)})}var Ee=r(de,2);_a(Ee,()=>e(Ae),()=>"anomalies"),v($e=>i(Oe,`${$e??""} `),[()=>Je(e(Ae).length,e(K).length,"anomalie","anomalies")]),l(A,G)};h(Ce,A=>{e(ee)!=="read"?A(Me):e(K).length===0?A(Ke,1):A(Ge,-1)})}l(w,se)},$$slots:{default:!0}});var Le=r(pt,2);tt(Le,{title:"Unités divergentes",note:"Le produit reste proposé : le code-barres fait foi, seul le libellé du prix est faux.",children:(w,U)=>{var se=Ut(),Ce=ae(se);{var Me=A=>{var G=Kn(),de=a(G);v(()=>i(de,e(S))),l(A,G)},Ke=A=>{var G=Xn();l(A,G)},Ge=A=>{var G=Yn(),de=ae(G),Oe=a(de),we=r(de,2);_a(we,()=>e(Ye),()=>"mismatches"),v(Re=>i(Oe,Re),[()=>Je(e(Ye).length,e(b).length,"unité divergente","unités divergentes")]),l(A,G)};h(Ce,A=>{e(ee)!=="read"?A(Me):e(b).length===0?A(Ke,1):A(Ge,-1)})}l(w,se)},$$slots:{default:!0}});var st=r(Le,2);tt(st,{title:"Produits non pesables",note:"Un inventaire, pas une liste d’erreurs : ces produits portent déjà leur code-barres et n’ont aucune raison d’être pesés.",children:(w,U)=>{var se=Ut(),Ce=ae(se);{var Me=A=>{var G=Qn(),de=a(G);v(()=>i(de,e(S))),l(A,G)},Ke=A=>{var G=Zn();l(A,G)},Ge=A=>{var G=es(),de=ae(G),Oe=a(de),we=r(de,2);_a(we,()=>e(We),()=>"not-weighable"),v(Re=>i(Oe,Re),[()=>Je(e(We).length,e(T).length,"produit non pesable","produits non pesables")]),l(A,G)};h(Ce,A=>{e(ee)!=="read"?A(Me):e(T).length===0?A(Ke,1):A(Ge,-1)})}l(w,se)},$$slots:{default:!0}});var Qe=r(st,2);tt(Qe,{title:"Produits retirés depuis l’import précédent",note:"Un produit absent du nouveau fichier est marqué retiré à sa date, jamais supprimé.",children:(w,U)=>{var se=Ut(),Ce=ae(se);{var Me=A=>{var G=ts();l(A,G)},Ke=A=>{var G=as();l(A,G)},Ge=A=>{var G=rs(),de=ae(G),Oe=a(de);v(()=>i(Oe,e(Xe))),l(A,G)};h(Ce,A=>{t.health.catalog===null?A(Me):e(me)===0?A(Ke,1):A(Ge,-1)})}l(w,se)},$$slots:{default:!0}});var _t=r(Qe,2);tt(_t,{title:"Décider d’un produit",note:"Retirer un produit et l’autoriser à peser moins sont deux décisions séparées : l’une n’efface pas l’autre.",children:(w,U)=>{var se=os(),Ce=r(ae(se),2),Me=r(Ce,2);{var Ke=G=>{var de=ss(),Oe=ae(de),we=a(Oe),Re=r(Oe,2),Ee=a(Re);at(Ee,21,()=>e(ze),$e=>$e.id,($e,Be)=>{var ft=ns(),ot=a(ft),ct=a(ot),it=r(ot,2),dt=a(it),gt=r(it,2),yt=a(gt);v(()=>{i(ct,e(Be).name),i(dt,e(Be).id),i(yt,`${e(Be).unit_price_text??""}${e(Be).price_suffix??""}`)}),et("click",ot,()=>Te(e(Be).id)),l($e,ft)}),v(()=>i(we,e(g))),l(G,de)};h(Me,G=>{e(pe)!==""&&G(Ke)})}var Ge=r(Me,2);{var A=G=>{var de=is(),Oe=a(de),we=r(a(Oe)),Re=a(we),Ee=r(we),$e=r(Oe,2),Be=a($e),ft=r($e,4),ot=r(ft,2);{var ct=Ze=>{var kt=ls();l(Ze,kt)};h(ot,Ze=>{e(Ne)===""&&Ze(ct)})}var it=r(ot,2),dt=r(a(it),2),gt=a(dt);{var yt=Ze=>{{let kt=o(()=>e(H)==="offered"),It=o(()=>e(O)||!e(C));rt(Ze,{act:"offered",kind:"destructive",label:"Ne plus proposer ce produit",protected:!0,get busy(){return e(kt)},get disabled(){return e(It)},onrun:()=>{ce(!1)}})}},Nt=Ze=>{{let kt=o(()=>e(H)==="offered"),It=o(()=>e(O)||!e(ne));rt(Ze,{act:"offered",kind:"write",label:"Le proposer de nouveau",protected:!0,get busy(){return e(kt)},get disabled(){return e(It)},onrun:()=>{ce(!0)}})}};h(gt,Ze=>{e(V)?Ze(yt):Ze(Nt,-1)})}var Mt=r(it,2),Ct=r(a(Mt),2),Bt=r(Ct,2),xt=a(Bt);{let Ze=o(()=>e(H)==="waiver"),kt=o(()=>e(O)||!e(oe));rt(xt,{act:"waiver",kind:"write",label:"Enregistrer la dérogation",protected:!0,get busy(){return e(Ze)},get disabled(){return e(kt)},onrun:()=>{W(e(Q))}})}var Et=r(xt,2);{var Vt=Ze=>{{let kt=o(()=>e(H)==="waiver-off"),It=o(()=>e(O)||!e(_e));rt(Ze,{act:"waiver-off",kind:"write",label:"Retirer la dérogation",protected:!0,get busy(){return e(kt)},get disabled(){return e(It)},onrun:()=>{W(null)}})}};h(Et,Ze=>{e(F)!==null&&Ze(Vt)})}v((Ze,kt)=>{Ve(de,"data-decision",e(L)),i(Re,Ze),i(Ee,` (${e(L)??""})`),i(Be,`En vigueur : ${kt??""}`),ra(Ct,e(ie))},[()=>Se(e(L)),()=>e(_)===null?"aucune décision — ce produit suit les règles générales":J(e(_))]),la(ft,()=>e(D),Ze=>p(D,Ze)),et("input",Ct,Ze=>p(ie,Ze.currentTarget.value,!0)),l(G,de)};h(Ge,G=>{e(L)!==""&&G(A)})}la(Ce,()=>e(pe),G=>p(pe,G)),l(w,se)},$$slots:{default:!0}});var Tt=r(_t,2);tt(Tt,{title:"Décisions en vigueur",note:"Ce qu’un humain a décidé, produit par produit. C’est ici que se reprend un produit retiré de la grille.",children:(w,U)=>{var se=Ut(),Ce=ae(se);{var Me=Ge=>{var A=us();l(Ge,A)},Ke=Ge=>{var A=ds(),G=ae(A),de=a(G),Oe=r(G,2),we=a(Oe);at(we,21,()=>e(re),Re=>Re.product_id,(Re,Ee)=>{var $e=cs(),Be=a($e),ft=r(Be,2),ot=a(ft),ct=r(ft,2),it=a(ct),dt=r(ct,2),gt=a(dt),yt=r(dt,2),Nt=a(yt),Mt=r(yt,2),Ct=a(Mt);v((Bt,xt,Et)=>{i(ot,Bt),i(it,e(Ee).product_id),i(gt,xt),i(Nt,e(Ee).reason),i(Ct,Et)},[()=>Se(e(Ee).product_id),()=>J(e(Ee)),()=>da(e(Ee).decided_at)]),et("click",Be,()=>Te(e(Ee).product_id)),l(Re,$e)}),v(Re=>i(de,Re),[()=>Je(e(re).length,e(E).length,"décision en vigueur","décisions en vigueur")]),l(Ge,A)};h(Ce,Ge=>{e(E).length===0?Ge(Me):Ge(Ke,-1)})}l(w,se)},$$slots:{default:!0}});var Dt=r(Tt,2);tt(Dt,{title:"Vingt derniers imports",children:(w,U)=>{var se=Ut(),Ce=ae(se);{var Me=A=>{var G=vs(),de=a(G);v(()=>i(de,e(ue))),l(A,G)},Ke=A=>{var G=ps();l(A,G)},Ge=A=>{var G=gs(),de=ae(G),Oe=a(de),we=r(de,2),Re=a(we),Ee=r(a(Re));at(Ee,21,()=>e(le),$e=>$e.id,($e,Be)=>{var ft=fs(),ot=a(ft),ct=a(ot),it=r(ot),dt=a(it),gt=r(it),yt=a(gt),Nt=r(gt),Mt=a(Nt),Ct=r(Nt),Bt=a(Ct),xt=r(Ct),Et=a(xt),Vt=r(xt),Ze=a(Vt),kt=r(Vt),It=a(kt),Zt=r(kt),aa=a(Zt),f=r(Zt),z=a(f);v((je,De,lt,mt,bt,zt,jt,St)=>{i(ct,je),i(dt,e(Be).file_name),i(yt,De),i(Mt,lt),i(Bt,e(Be).reason),i(Et,mt),i(Ze,bt),i(It,zt),i(aa,jt),i(z,St)},[()=>Yt(e(Be).occurred_at),()=>fe(e(Be)),()=>be(e(Be)),()=>ve(e(Be).rows_read_count),()=>ve(e(Be).weighable_count),()=>ve(e(Be).not_weighable_count),()=>ve(e(Be).anomalies_count),()=>ve(e(Be).products_withdrawn_count)]),l($e,ft)}),v(()=>i(Oe,e(x))),l(A,G)};h(Ce,A=>{e(ee)!=="read"?A(Me):e(le).length===0?A(Ke,1):A(Ge,-1)})}l(w,se)},$$slots:{default:!0}}),l(n,Pe),At()}Ot(["change","click","input"]);function _s(n){switch(n){case"ok":return"OK";case"warn":return"À SURVEILLER";case"fault":return"EN PANNE";case"off":return"SANS OBJET";default:return"INCONNU"}}var bs=u('

                                          '),ws=u('

                                          ');function ys(n,t){Rt(t,!0);var s=ws(),c=a(s),m=a(c),k=r(m,2),R=a(k),M=r(k,2),P=a(M),B=r(c,2),$=a(B),le=r(B,2);{var ge=xe=>{var pe=bs(),L=a(pe);v(()=>i(L,t.light.remedy)),l(xe,pe)};h(le,xe=>{t.light.remedy!==""&&xe(ge)})}v(xe=>{Ve(s,"data-light",t.light.id),Ve(s,"data-level",t.light.level),Ve(m,"data-level",t.light.level),i(R,t.light.label),i(P,xe),i($,t.light.value)},[()=>_s(t.light.level)]),l(n,s),At()}function xs(n){return[ks(n),qs(n),Ls(n),Es(n),js(n),Ps(n)]}function ks(n){const t=n.state.scale,s=ua(t.median_ms);return n.scale_present?t.connected?t.too_slow?{id:"scale",label:"Balance",level:"warn",value:`une mesure toutes les ${s}, plus lent que la péremption`,remedy:"À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante. Vérifiez le câble et l’adaptateur USB, puis la cadence sur la page Matériel."}:{id:"scale",label:"Balance",level:"ok",value:Ts(t.provisional,`une mesure toutes les ${s}`),remedy:""}:{id:"scale",label:"Balance",level:"fault",value:"elle ne répond plus",remedy:"Vérifiez le câble et l’alimentation de la balance, puis touchez « Tester la balance ». En attendant, « Basculer en saisie manuelle » permet de continuer à servir."}:{id:"scale",label:"Balance",level:"off",value:"ce poste est déclaré sans balance",remedy:""}}function qs(n){const t=n.state.printer;switch(t.health){case"faulted":return{id:"printer",label:"Imprimante",level:"fault",value:t.detail===""?"elle ne peut pas imprimer":t.detail,remedy:Ss(n)};case"consumable":return{id:"printer",label:"Imprimante",level:"warn",value:"elle imprime, mais le rouleau arrive en fin de vie",remedy:"Changez le rouleau quand vous passez derrière le comptoir, puis touchez « J’ai changé le rouleau ». Le poste continue de servir en attendant."};case"ready":return{id:"printer",label:"Imprimante",level:"ok",value:"elle répond et n’a rien à signaler",remedy:""};default:return{id:"printer",label:"Imprimante",level:"unknown",value:"elle prend les étiquettes et ne dit rien en retour",remedy:"C’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne. Pour savoir si elle imprime, touchez « Imprimer une étiquette de test »."}}}function Ss(n){const t="Regardez le capot, le rouleau et le câble, puis touchez « Tester l’imprimante ».";return n.printing?.fallback_available===!0?t+" Le poste peut servir en attendant : « Imprimer sur l’imprimante du poste voisin ».":t}function Ls(n){const t=n.roll;return t===null?{id:"roll",label:"Rouleau",level:"unknown",value:"aucun compteur d’étiquettes sur ce poste",remedy:`Sans imprimante construite, il n’y a pas de rouleau à compter : vérifiez le « ${Ft("printer.type")} » et ses réglages sur la page Matériel.`}:t.known?t.level==="warn"?{id:"roll",label:"Rouleau",level:"warn",value:t.message,remedy:"Changez le rouleau, puis touchez « J’ai changé le rouleau »."}:{id:"roll",label:"Rouleau",level:"ok",value:t.message,remedy:""}:{id:"roll",label:"Rouleau",level:"unknown",value:t.message,remedy:"Touchez « J’ai changé le rouleau » en mettant un rouleau neuf : c’est le seul geste qui dise quelque chose de vrai du papier."}}function Es(n){const t=n.catalog_source?.label??"";if(n.state.catalog_count===0)return{id:"catalog",label:"Catalogue",level:"fault",value:"aucun produit dans la grille",remedy:zs(t)};const s=n.catalog;return s!==null&&s.result!=="applied"&&s.result!=="unchanged"?{id:"catalog",label:"Catalogue",level:"warn",value:`dernier fichier refusé : ${s.reason===""?s.result:s.reason}`,remedy:"La grille tourne toujours sur le catalogue précédent. Corrigez le fichier dans Odoo, ou touchez « Oublier la quarantaine » puis redéposez-le."}:{id:"catalog",label:"Catalogue",level:"ok",value:`${ve(n.state.catalog_count)} produits dans la grille`,remedy:""}}function zs(n){return"Déposez le fichier du catalogue là où le poste le guette, ou utilisez « Importer un catalogue » ci-contre pour le glisser depuis une clé USB."+(n===""?"":` Ce poste surveille : ${n}.`)}function js(n){const t=n.disk;if(t===null)return{id:"disk",label:"Disque",level:"unknown",value:"la place libre n’a pas pu être mesurée",remedy:"Téléchargez le fichier de diagnostic : il porte ce que le poste a pu lire du volume, et c’est la pièce qu’un support demandera."};const s=wa(t.free_bytes),c=`seuil ${ve(t.alert_mb)} Mo`;return t.free_bytes===0?{id:"disk",label:"Disque",level:"fault",value:`plus un octet libre sur ${t.path}`,remedy:"Le journal ne peut plus rien écrire, alors que les étiquettes continuent de sortir. Faites de la place sur le disque, puis rechargez cette page."}:t.alert_mb>0&&t.free_bytes0?{id:"journal",label:"Journal",level:"fault",value:`${ve(t)} pesées imprimées mais non enregistrées`,remedy:"Les étiquettes sortent, les ventes sont bonnes, c’est la trace qui manque. Téléchargez le fichier de diagnostic et prévenez le support : ne redémarrez rien."}:n.counters.journal_rows_count<0?{id:"journal",label:"Journal",level:"unknown",value:"ce poste n’a pas de journal ouvert",remedy:"Le poste pèse et imprime quand même. Téléchargez le fichier de diagnostic : il dit pourquoi la base n’a pas pu être ouverte."}:{id:"journal",label:"Journal",level:"ok",value:`${ve(n.counters.journal_rows_count)} pesées enregistrées`,remedy:""}}function Ts(n,t){return n?t+" (cadence encore provisoire)":t}function Cs(n){return n===null||!n.known?"unknown":n.configured?"ok":"missing"}function Os(n){switch(n){case"applied":return"appliqué";case"unchanged":return"inchangé, déjà appliqué";case"rejected":return"refusé";case"failed":return"échec";default:return n}}var Rs=u("Une mesure toutes les ",1),As=u('

                                          '),Ns=u("Source : ",1),Is=u(`

                                          Aucun import enregistré sur ce poste : le catalogue n’est jamais arrivé, ou le - journal ne le porte pas.

                                          `),$s=u('

                                          ',1),Ds=u('

                                          ',1),Us=u('

                                          Aucune décision locale : le catalogue est proposé tel qu’il arrive.

                                          '),Fs=u('

                                          '),Ms=u(' '),Bs=u('
                                        • '),Vs=u('
                                            ',1),Ws=u(`INCONNU — la question n’a pas pu être posée à ce système. Ce - n’est pas « non configuré » : personne ne sait encore.`,1),Gs=u('OK ',1),Hs=u('NON CONFIGURÉ ',1),Js=u('

                                            '),Ks=u('

                                            ',1),Xs=u('

                                            Rien à signaler depuis le démarrage du poste.

                                            '),Ys=u(' '),Qs=u('
                                          • '),Zs=u('
                                              '),el=u('
                                              Numéro de poste
                                              Nom
                                              Coopérative
                                              Version
                                              Empreinte de configuration
                                              '),tl=u('
                                              ');function al(n,t){Rt(t,!0);const s=o(()=>xs(t.health)),c=o(()=>t.health.state.scale),m=o(()=>t.health.unattended_restart),k=o(()=>t.health.decisions),R=5,M=o(()=>e(k).slice(0,R));var P=tl(),B=a(P);at(B,21,()=>e(s),D=>D.id,(D,ie)=>{ys(D,{get light(){return e(ie)}})});var $=r(B,2);tt($,{title:"Cadence de la balance",children:(D,ie)=>{var Z=As(),he=a(Z);{var H=K=>{var T=qt("Ce poste est déclaré sans balance : le poids est saisi à la main.");l(K,T)},ee=K=>{var T=qt(`La balance ne répond pas : aucune cadence n’est mesurable tant qu’aucune trame - n’arrive.`);l(K,T)},j=K=>{var T=qt(`Aucun intervalle n’a encore été mesuré : la cadence apparaîtra dès les premières - trames.`);l(K,T)},O=K=>{var T=Rs(),b=r(ae(T)),I=a(b),Y=r(b),ze=r(Y);{var Ae=E=>{var re=qt("C’est encore une valeur d’attente : moins de huit intervalles ont été observés.");l(E,re)};h(ze,E=>{e(c).provisional&&E(Ae)})}var Ye=r(ze,2);{var We=E=>{var re=qt("À cette cadence, un poids serait périmé avant l’arrivée de la mesure suivante.");l(E,re)};h(Ye,E=>{e(c).too_slow&&E(We)})}v((E,re)=>{i(I,E),i(Y,`, médiane - observée sur ${re??""} intervalles. `)},[()=>ua(e(c).median_ms),()=>ve(e(c).observations_count)]),l(K,T)};h(he,K=>{t.health.scale_present?e(c).connected?e(c).observations_count===0?K(j,2):K(O,-1):K(ee,1):K(H)})}l(D,Z)},$$slots:{default:!0}});var le=r($,2);tt(le,{title:"Catalogue",children:(D,ie)=>{var Z=Ds(),he=ae(Z),H=a(he);{var ee=b=>{var I=qt("Aucune source de catalogue n’est publiée par ce poste.");l(b,I)},j=b=>{var I=Ns(),Y=r(ae(I)),ze=a(Y);v(()=>i(ze,t.health.catalog_source.label)),l(b,I)};h(H,b=>{t.health.catalog_source===null?b(ee):b(j,-1)})}var O=r(he,2);{var K=b=>{var I=Is();l(b,I)},T=b=>{var I=$s(),Y=ae(I),ze=a(Y),Ae=r(Y,2);Qa(Ae,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives},get onshowrows(){return t.onshowrows}}),v((Ye,We)=>i(ze,`Dernier essai : ${Ye??""} — - ${We??""}${t.health.catalog.file_name===""?"":" ("+t.health.catalog.file_name+")"}${t.health.catalog.reason===""?"":" : "+t.health.catalog.reason}`),[()=>Yt(t.health.catalog.occurred_at),()=>Os(t.health.catalog.result)]),l(b,I)};h(O,b=>{t.health.catalog===null?b(K):b(T,-1)})}l(D,Z)},$$slots:{default:!0}});var ge=r(le,2);tt(ge,{title:"Décisions locales en vigueur",note:"Ce qu’un humain a décidé de ce catalogue, avec son motif et sa date.",children:(D,ie)=>{var Z=Ut(),he=ae(Z);{var H=j=>{var O=Us();l(j,O)},ee=j=>{var O=Vs(),K=ae(O);{var T=I=>{var Y=Fs(),ze=a(Y);v(Ae=>i(ze,`${Ae??""} décisions en vigueur — les - 5 plus récentes ci-dessous. La liste entière est sur la page - Catalogue.`),[()=>ve(e(k).length)]),l(I,Y)};h(K,I=>{e(k).length>R&&I(T)})}var b=r(K,2);at(b,21,()=>e(M),I=>I.product_id,(I,Y)=>{var ze=Bs(),Ae=a(ze),Ye=a(Ae),We=r(Ae,2);{var E=Q=>{var qe=Ms(),Ne=a(qe);v(C=>i(Ne,`peut peser à partir de ${C??""} g`),[()=>ve(e(Y).min_weight_g)]),l(Q,qe)};h(We,Q=>{e(Y).min_weight_g!==null&&Q(E)})}var re=r(We,2),_=a(re),V=r(re,2),F=a(V);v(Q=>{i(Ye,`${e(Y).offered?"Dérogation de poids":"Produit retiré"} — ${e(Y).product_id??""}`),i(_,e(Y).reason),i(F,`${Q??""}, ${e(Y).decided_by??""}`)},[()=>da(e(Y).decided_at)]),l(I,ze)}),l(j,O)};h(he,j=>{e(k).length===0?j(H):j(ee,-1)})}l(D,Z)},$$slots:{default:!0}});var xe=r(ge,2);tt(xe,{title:"Redémarrage sans intervention",children:(D,ie)=>{var Z=Ks(),he=ae(Z),H=a(he);{var ee=b=>{var I=Ws();l(b,I)},j=b=>{var I=Gs(),Y=r(ae(I));v(()=>i(Y,` — après une coupure de courant, ce poste revient seul sur - l’écran client. ${e(m).detail??""}`)),l(b,I)},O=b=>{var I=Hs(),Y=r(ae(I));v(()=>i(Y,` — ${e(m).detail??""}`)),l(b,I)};h(H,b=>{e(m)===null||!e(m).known?b(ee):e(m).configured?b(j,1):b(O,-1)})}var K=r(he,2);{var T=b=>{var I=Js(),Y=a(I);v(()=>i(Y,e(m).remedy)),l(b,I)};h(K,b=>{e(m)!==null&&e(m).known&&!e(m).configured&&e(m).remedy!==""&&b(T)})}v(b=>Ve(he,"data-verdict",b),[()=>Cs(e(m))]),l(D,Z)},$$slots:{default:!0}});var pe=r(xe,2);tt(pe,{title:"Dix derniers événements",children:(D,ie)=>{var Z=Ut(),he=ae(Z);{var H=j=>{var O=Xs();l(j,O)},ee=j=>{var O=Zs();at(O,21,()=>t.health.events,K=>K.id,(K,T)=>{var b=Qs(),I=a(b),Y=a(I),ze=r(I,2),Ae=a(ze),Ye=r(ze,2);{var We=_=>{var V=Ys(),F=a(V);v(()=>i(F,e(T).code)),l(_,V)};h(Ye,_=>{e(T).code!==""&&Wt.showTechnicalNames&&_(We)})}var E=r(Ye,2),re=a(E);v((_,V)=>{Ve(b,"data-level",e(T).level),i(Y,_),i(Ae,V),i(re,e(T).message)},[()=>qn(e(T).occurred_at),()=>Ka(e(T).source)]),l(K,b)}),l(j,O)};h(he,j=>{t.health.events.length===0?j(H):j(ee,-1)})}l(D,Z)},$$slots:{default:!0}});var L=r(pe,2);tt(L,{title:"Ce poste",children:(D,ie)=>{var Z=el(),he=r(a(Z),2),H=a(he),ee=r(he,4),j=a(ee),O=r(ee,4),K=a(O),T=r(O,4),b=a(T),I=r(T,4),Y=a(I);v(()=>{i(H,t.health.station),i(j,t.health.station_name===""?"non renseigné":t.health.station_name),i(K,t.health.coop),i(b,t.health.version),i(Y,t.health.config_fingerprint)}),l(D,Z)},$$slots:{default:!0}}),l(n,P),At()}var rl=u(''),nl=u(`

                                              Lecture de la configuration en cours… tant qu’elle n’est pas arrivée, cette page ne - déclare rien de ce poste.

                                              `),Na=u('
                                            • '),sl=u('
                                                '),ll=u('

                                                ',1),il=u('
                                              • '),ol=u('

                                                  ',1),ul=u('

                                                  '),cl=u('
                                                • '),dl=u('
                                                    '),vl=u('

                                                      ',1),pl=u('

                                                      Balance

                                                      L’état et la cadence viennent de ce que le poste observe vraiment, jamais d’un réglage.

                                                      Réglages série de la balance

                                                      Imprimante

                                                      Réglages de l’imprimante
                                                      ');function fl(n,t){Rt(t,!0);const s=vt(t,"admin",7),c=20,m=3,k=250,R=50,M=m*1e3+k+1e3,P=12;let B=N(Lt([])),$=N(Lt([])),le=N(Lt([])),ge=N(Lt([])),xe=N(""),pe=N(!1),L=N(""),D=N(0),ie=N(0),Z=N(!1),he=N(null),H=!1;const ee=o(()=>t.health.state.scale),j=o(()=>t.health.state.printer),O=o(()=>t.draft.config!==null),K=o(()=>e(O)&&!t.draft.flag("scale.present")),T=o(()=>t.draft.text("scale.options.port")),b=o(()=>e(he)!==null&&e(he).port===e(T)?e(he).reason:""),I=o(()=>e(xe)===e(T)?e(ge):[]),Y=o(x),ze=o(ue),Ae=o(y),Ye=o(Te),We=o(()=>ke("scale.type")!==""||ke("scale.options.port")!==""),E=o(()=>ke("printer.type")!==""||ke("printer.options.transport")!==""||ke("printer.options.queue")!=="");_r(()=>{H=!0}),ka(()=>{C("ports",ne)}),ca(()=>{e(Z)||!re(e(T))||V(e(T))});function re(f){return!H&&e(O)&&!e(K)&&f!==""&&f===e(T)&&_(f)&&e(b)===""&&e(L)===""}function _(f){return e(B).some(z=>z.name===f)}async function V(f){p(Z,!0);try{for(;re(f);){try{Q(f,await ja(f,m))}catch(z){p(he,{port:f,reason:Je(z)},!0);return}await qe()}}finally{p(Z,!1)}}async function F(){const f=e(T),z=await s().protect(()=>ja(f,m));z!==null&&(Q(f,z),p(he,null))}function Q(f,z){if(H||z.length===0)return;const je=f===e(xe)?e(ge):[];p(xe,f,!0),p(ge,[...je,...z].slice(-c),!0)}function qe(f=k){return new Promise(z=>setTimeout(z,f))}async function Ne(){const f=Date.now()+M;for(;e(Z)&&Date.now()Ga(f));z!==null&&(s().notice=z.message,await s().refresh())}async function He(){p(D,0),p(ie,0),e(pe)||await ne();const f=e(B);if(f.length===0){p(le,[],!0);return}await s().protect(()=>Xe(f))}async function Xe(f){p(le,[],!0),p(ie,f.length,!0);for(const[z,je]of f.entries()){p(D,z+1);try{const De=await Dr(je.name);p(le,[...e(le),{port:je.name,message:De.message,refused:!1}],!0)}catch(De){if(J(De))throw De;p(le,[...e(le),{port:je.name,message:Je(De),refused:!0}],!0)}}}function g(){return e(L)!=="detect"?"Détecter automatiquement":e(Z)?"Détection : le port se libère…":e(ie)===0?"Détection : énumération des ports…":`Détection : port ${ve(e(D))} sur ${ve(e(ie))}…`}function x(){return t.health.scale_present?e(ee).connected?e(ee).too_slow?{level:"warn",word:"Trop lente",detail:S()+" À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante."}:{level:"ok",word:"Connectée",detail:"Elle répond. "+S()}:{level:"fault",word:"Sans réponse",detail:"Elle ne répond plus. Vérifiez le câble et l’alimentation, puis « Tester la balance » sur la page Dépannage."}:{level:"off",word:"Sans balance",detail:"Ce poste est déclaré sans balance : le feu est éteint et le poids se saisit à la main."}}function S(){return e(ee).observations_count===0?"Aucun intervalle n’a encore été mesuré : la cadence sera connue dès les premières trames.":`Une mesure toutes les ${ua(e(ee).median_ms)} sur ${ve(e(ee).observations_count)} intervalles`+(e(ee).provisional?", cadence encore provisoire.":".")}function ue(){const f=d[e(j).health]??{level:"unknown",word:"État inconnu",detail:"Le poste a répondu un état que cet écran ne sait pas nommer."};return{...f,detail:e(j).detail===""?f.detail:e(j).detail}}const d={ready:{level:"ok",word:"Prête",detail:"Elle répond et n’a rien à signaler."},consumable:{level:"warn",word:"Rouleau en fin de vie",detail:"Elle imprime, mais le rouleau arrive en fin de vie."},faulted:{level:"fault",word:"En panne",detail:"Elle ne peut pas imprimer."},unknown:{level:"unknown",word:"Silencieuse",detail:"Elle prend les étiquettes et ne dit rien en retour : c’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne."}};function q(){const f=e(j).observed_at===""?"Jamais observée depuis le démarrage":`Observée le ${Yt(e(j).observed_at)}`,z=e(j).pending_jobs_count;return`${f}, ${ve(z)} ${z>1?"travaux":"travail"} en attente.`}function y(){const f=te();return f===""?X():`${X()} ${f}`}function X(){return e(O)?e(K)?"Ce poste est déclaré sans balance : aucun port n’est écouté.":e(T)===""?"Aucun port n’est indiqué : choisissez-en un dans la liste ci-dessus pour écouter les trames.":e(pe)?_(e(T))?e(b)!==""?`L’écoute de ${e(T)} est arrêtée.`:e(L)!==""?`L’écoute de ${e(T)} est suspendue le temps de l’acte en cours.`:`Écoute de ${e(T)}.`:`${e(T)} n’est pas visible depuis ce poste : rien n’est écouté en continu.`:e(L)==="ports"?`Énumération des ports en cours : l’écoute de ${e(T)} démarre dès qu’il est vu.`:`Les ports de ce poste n’ont pas été énumérés : « Lister les ports » dira si ${e(T)} existe.`:"Lecture de la configuration en cours : le port à écouter n’est pas encore connu."}function te(){return e(I).length===0?"Aucune trame reçue pour l’instant.":e(I).length===1?`Une seule trame reçue — ${ve(c)} au plus sont gardées.`:`Les ${ve(e(I).length)} dernières trames — ${ve(c)} au plus, la plus récente en bas.`}function Te(){return!e(O)||e(K)||e(T)===""?"":e(b)!==""?"Reprendre l’écoute":e(pe)&&!_(e(T))?"Écouter ce port une fois":""}function ye(f,z,je){const De=`${ve(je)} ${je>1?z:f}`;return je<=P?De+".":`${De} — seules les ${ve(P)} premières lignes sont affichées.`}function ce(f,z){const je=Se(f);return je.length>0?je:z.slice(0,P)}function W(f){return[...new TextEncoder().encode(f)].map(z=>z.toString(16).toUpperCase().padStart(2,"0")).join(" ")}const Ue={0:"NUL",2:"STX",3:"ETX",4:"EOT",5:"ENQ",6:"ACK",9:"TAB",10:"LF",13:"CR",21:"NAK",27:"ESC",127:"DEL"};function Fe(f){let z="";for(const je of f){const De=je.codePointAt(0)??0;if(De>=32&&De!==127){z+=je;continue}z+=`⟨${Ue[De]??De.toString(16).toUpperCase().padStart(2,"0")}⟩`}return z}function ke(f){return t.draft.faults.find(z=>z.field===f)?.message??""}function Se(f){return t.draft.faults.find(z=>z.field===f)?.allowed??[]}function Je(f){return f instanceof $t?f.message:f instanceof Error?"Le poste n’a pas répondu : "+f.message:"Le poste n’a pas répondu."}function J(f){return f instanceof $t&&(f.status===401||f.status===409)}const be=[{what:"label",name:"étiquette"},{what:"alignment",name:"alignement"},{what:"ruler",name:"réglette"}];var fe=pl(),Pe=a(fe),Ie=a(Pe),nt=r(a(Ie),2),ut=r(a(nt),1,!0),pt=r(Ie,2),Le=a(pt),st=r(pt,4);{var Qe=f=>{var z=rl(),je=a(z);v(()=>oa(je,e(K))),et("change",je,De=>t.draft.set("scale.present",!De.currentTarget.checked)),l(f,z)},_t=f=>{var z=nl();l(f,z)};h(st,f=>{e(O)?f(Qe):f(_t,-1)})}var Tt=r(st,2),Dt=a(Tt);{let f=o(()=>e(L)==="ports"),z=o(()=>e(L)!=="");rt(Dt,{label:"Lister les ports",get busy(){return e(f)},get disabled(){return e(z)},onrun:()=>{C("ports",ne)}})}var w=r(Dt,2);{let f=o(g),z=o(()=>e(L)!=="");rt(w,{act:"detect",get label(){return e(f)},protected:!0,get disabled(){return e(z)},onrun:()=>{C("detect",He)}})}var U=r(Tt,2);{var se=f=>{var z=ll(),je=ae(z),De=a(je),lt=r(je,2);{var mt=bt=>{var zt=sl();at(zt,21,()=>e(B).slice(0,P),jt=>jt.name,(jt,St)=>{var Ht=Na(),ea=a(Ht),ga=a(ea),Za=r(ea,2),er=a(Za);v(()=>{i(ga,e(St).name),i(er,`${(e(St).description===""?"aucune description USB":e(St).description)??""} - ${e(St).vid===""?"":` — VID ${e(St).vid} PID ${e(St).pid}`}`)}),et("click",ea,()=>t.draft.set("scale.options.port",e(St).name)),l(jt,Ht)}),l(bt,zt)};h(lt,bt=>{e(B).length>0&&bt(mt)})}v(bt=>i(De,bt),[()=>e(B).length===0?"Aucun port série n’est visible depuis ce poste.":ye("port détecté","ports détectés",e(B).length)]),l(f,z)};h(U,f=>{e(pe)&&f(se)})}var Ce=r(U,2);{var Me=f=>{var z=ol(),je=ae(z),De=a(je),lt=r(je,2);at(lt,21,()=>e(le).slice(0,P),mt=>mt.port,(mt,bt)=>{var zt=il();let jt;var St=a(zt),Ht=a(St),ea=r(St,2),ga=a(ea);v(()=>{jt=Gt(zt,1,"svelte-1p4i0xm",null,jt,{refused:e(bt).refused}),i(Ht,e(bt).port),i(ga,e(bt).message)}),l(mt,zt)}),v(mt=>i(De,mt),[()=>ye("port interrogé","ports interrogés",e(le).length)]),l(f,z)};h(Ce,f=>{e(le).length>0&&f(Me)})}var Ke=r(Ce,2),Ge=r(a(Ke),2),A=a(Ge);{let f=o(()=>t.draft.text("scale.type")),z=o(()=>ke("scale.type")),je=o(()=>Se("scale.type")),De=o(()=>!e(O));ht(A,{label:"Protocole",path:"scale.type",get value(){return e(f)},hint:"Les valeurs acceptées apparaissent ici si l’enregistrement est refusé.",get fault(){return e(z)},get allowed(){return e(je)},get disabled(){return e(De)},onchange:lt=>t.draft.set("scale.type",lt)})}var G=r(A,2);{let f=o(()=>t.draft.text("scale.options.port")),z=o(()=>ke("scale.options.port")),je=o(()=>ce("scale.options.port",e(B).map(lt=>lt.name))),De=o(()=>!e(O));ht(G,{label:"Port série",path:"scale.options.port",get value(){return e(f)},hint:"Choisissez-le dans la liste détectée ci-dessus plutôt que de le taper : l’écoute permanente ne suit que des ports détectés.",get fault(){return e(z)},get allowed(){return e(je)},get disabled(){return e(De)},onchange:lt=>t.draft.set("scale.options.port",lt)})}var de=r(Ke,2),Oe=a(de),we=a(Oe),Re=r(Oe,2);{var Ee=f=>{var z=ul(),je=a(z),De=r(je);{var lt=mt=>{{let bt=o(()=>e(L)==="listen"),zt=o(()=>e(L)!=="");rt(mt,{act:"listen",get label(){return e(Ye)},protected:!0,get busy(){return e(bt)},get disabled(){return e(zt)},onrun:()=>{C("listen",F)}})}};h(De,mt=>{e(Ye)!==""&&mt(lt)})}v(()=>i(je,`${e(b)??""} `)),l(f,z)};h(Re,f=>{(e(b)!==""||e(Ye)!=="")&&f(Ee)})}var $e=r(Re,2);{var Be=f=>{var z=dl();at(z,21,()=>e(I),fa,(je,De)=>{var lt=cl(),mt=a(lt),bt=a(mt),zt=r(mt,2),jt=a(zt);v((St,Ht)=>{i(bt,St),i(jt,Ht)},[()=>W(e(De)),()=>Fe(e(De))]),l(je,lt)}),l(f,z)};h($e,f=>{e(I).length>0&&f(Be)})}var ft=r(Pe,2),ot=a(ft),ct=r(a(ot),2),it=r(a(ct),1,!0),dt=r(ot,2),gt=a(dt),yt=r(dt,2),Nt=a(yt),Mt=r(yt,2),Ct=a(Mt);{let f=o(()=>e(L)==="printers"),z=o(()=>e(L)!=="");rt(Ct,{label:"Lister les files",get busy(){return e(f)},get disabled(){return e(z)},onrun:()=>{C("printers",oe)}})}var Bt=r(Ct,2);{let f=o(()=>e(L)==="discover"),z=o(()=>e(L)!=="");rt(Bt,{label:"Rechercher l’imprimante",protected:!0,get busy(){return e(f)},get disabled(){return e(z)},onrun:()=>{C("discover",_e)}})}var xt=r(Bt,2);at(xt,17,()=>be,f=>f.what,(f,z)=>{{let je=o(()=>`Auto-test : ${e(z).name}${e(L)===e(z).what?" — en cours…":""}`),De=o(()=>e(L)!=="");rt(f,{get act(){return e(z).what},get label(){return e(je)},protected:!0,get disabled(){return e(De)},onrun:()=>{C(e(z).what,()=>me(e(z).what))}})}});var Et=r(Mt,2);{var Vt=f=>{var z=vl(),je=ae(z),De=a(je),lt=r(je,2);at(lt,21,()=>e($).slice(0,P),mt=>mt.name,(mt,bt)=>{var zt=Na(),jt=a(zt),St=a(jt),Ht=r(jt,2),ea=a(Ht);v(()=>{i(St,e(bt).name),i(ea,`${e(bt).detail??""}${e(bt).default?" — file par défaut du système":""}`)}),et("click",jt,()=>t.draft.set("printer.options.queue",e(bt).name)),l(mt,zt)}),v(mt=>i(De,mt),[()=>ye("file d’impression","files d’impression",e($).length)]),l(f,z)};h(Et,f=>{e($).length>0&&f(Vt)})}var Ze=r(Et,2),kt=r(a(Ze),2),It=a(kt);{let f=o(()=>t.draft.text("printer.type")),z=o(()=>ke("printer.type")),je=o(()=>Se("printer.type")),De=o(()=>!e(O));ht(It,{label:"Driver",path:"printer.type",get value(){return e(f)},hint:"Gardez le driver raster : c’est celui que les postes en service utilisent.",get fault(){return e(z)},get allowed(){return e(je)},get disabled(){return e(De)},onchange:lt=>t.draft.set("printer.type",lt)})}var Zt=r(It,2);{let f=o(()=>t.draft.text("printer.options.transport")),z=o(()=>ke("printer.options.transport")),je=o(()=>Se("printer.options.transport")),De=o(()=>!e(O));ht(Zt,{label:"Transport",path:"printer.options.transport",get value(){return e(f)},hint:"Local par défaut : une file Windows ou un fichier de périphérique de ce poste.",get fault(){return e(z)},get allowed(){return e(je)},get disabled(){return e(De)},onchange:lt=>t.draft.set("printer.options.transport",lt)})}var aa=r(Zt,2);{let f=o(()=>t.draft.text("printer.options.queue")),z=o(()=>ke("printer.options.queue")),je=o(()=>ce("printer.options.queue",e($).map(lt=>lt.name))),De=o(()=>!e(O));ht(aa,{label:"File ou chemin",path:"printer.options.queue",get value(){return e(f)},hint:"Choisissez-la dans la liste ci-dessus : une file mal orthographiée ne s’imprime pas.",get fault(){return e(z)},get allowed(){return e(je)},get disabled(){return e(De)},onchange:lt=>t.draft.set("printer.options.queue",lt)})}v(f=>{Ve(nt,"data-level",e(Y).level),i(ut,e(Y).word),i(Le,e(Y).detail),Ke.open=e(We),i(we,e(Ae)),Ve(ct,"data-level",e(ze).level),i(it,e(ze).word),i(gt,e(ze).detail),i(Nt,f),Ze.open=e(E)},[()=>q()]),l(n,fe),At()}Ot(["change","click"]);var gl=u(""),ml=u('L’export sera proposé quand le journal aura répondu.'),hl=u('L’export n’est pas proposé : ce poste n’a pas répondu à la lecture du journal.'),_l=u('Exporter en CSV'),bl=u('

                                                      '),wl=u('

                                                      '),yl=u(`

                                                      Aucune trame brute n’a été enregistrée pour cette pesée : il n’y a - rien à rejouer.

                                                      `),xl=u(`

                                                      La trame repart dans le décodeur du poste EN SERVICE : le poids - affiché au client change, et rien ne le remet comme il était. C’est - ce qui fait d’un refus inexpliqué un test permanent, sans - déplacement au magasin et sans balance.

                                                      `,1),kl=u('

                                                      Produit
                                                      Référence
                                                      Vente
                                                      Brut / tare / net
                                                      Stabilité
                                                      Origine du poids
                                                      Résultat
                                                      Trame brute
                                                      '),ql=u(' ',1),Sl=u('

                                                      QuandProduitNetCode-barresRésultatDuréeDétail
                                                      ',1),Ll=u('
                                                      ',1),El=u('

                                                      '),zl=u(' '),jl=u(' '),Pl=u('
                                                    • '),Tl=u('

                                                        ',1),Cl=u('
                                                        ');function Ol(n,t){Rt(t,!0);const s=vt(t,"admin",7),c=200,m=5e3,k=50,R=500,M=7,P={sent:"envoyée à l’imprimante",rejected:"refusée",failed:"en échec",reprint:"réimpression"},B=[{value:"",label:"toutes"},{value:"sent",label:"envoyées à l’imprimante"},{value:"rejected",label:"refusées"},{value:"failed",label:"en échec"},{value:"reprint",label:"réimpressions"}],$={scale:"balance",manual:"saisie manuelle",replay:"trame rejouée"},le={stable:"stable",unstable:"instable",unknown:"non déclarée par la balance",not_applicable:"sans objet — saisie manuelle"},ge={by_weight:"au poids",by_unit:"à l’unité"},xe={debug:"mise au point",info:"information",warn:"avertissement",error:"erreur",critical:"critique"};let pe=N(Lt([])),L=N(Lt([])),D=N(""),ie=N(null),Z=N(!1),he=N(!1),H=N("loading"),ee=N("loading"),j=N(""),O=N("");const K=o(()=>({limit:String(c),...e(D)===""?{}:{result:e(D)}})),T=o(()=>({...e(K),limit:String(m)})),b=o(()=>e(pe).find(F=>F.id===e(ie))??null),I=o(()=>B.find(F=>F.value===e(D))?.label??""),Y=o(()=>We(e(pe).length,c,"pesée","pesées",`L’export CSV en emporte jusqu’à ${ve(m)}.`)),ze=o(()=>We(e(L).length,k,"ligne","lignes",`Le fichier de diagnostic emporte les ${ve(R)} dernières.`));Ae();async function Ae(){p(ie,null),p(he,!0),p(H,"loading"),p(ee,"loading"),p(pe,[],!0),p(L,[],!0),p(j,""),p(O,"");try{const F=await s().load(()=>Fr(e(K)));p(H,F===null?"unread":"read",!0),p(j,F===null?s().actionError:"",!0),p(pe,F??[],!0);const Q=await s().load(()=>Br({limit:String(k)}));p(ee,Q===null?"unread":"read",!0),p(O,Q===null?s().actionError:"",!0),p(L,Q??[],!0)}finally{p(he,!1),s().actionError=""}}async function Ye(F){s().notice="",s().actionError="",p(Z,!0);try{const Q=await s().protect(()=>Hr(F));Q!==null&&(s().notice=Q.message)}finally{p(Z,!1)}}function We(F,Q,qe,Ne,C){const ne=F>1?Ne:qe;return F{var qe=Ll(),Ne=ae(qe),C=r(a(Ne),2);at(C,21,()=>B,d=>d.value,(d,q)=>{var y=gl(),X=a(y),te={};v(()=>{i(X,e(q).label),te!==(te=e(q).value)&&(y.value=(y.__value=e(q).value)??"")}),l(d,y)});var ne=r(C,2);rt(ne,{label:"Rafraîchir",get busy(){return e(he)},onrun:()=>{Ae()}});var oe=r(ne,2);{var _e=d=>{var q=ml();l(d,q)},me=d=>{var q=hl();l(d,q)},He=d=>{var q=_l();v(y=>Ve(q,"href",y),[()=>Mr(e(T))]),l(d,q)};h(oe,d=>{e(H)==="loading"?d(_e):e(H)==="unread"?d(me,1):d(He,-1)})}var Xe=r(Ne,2);{var g=d=>{var q=bl(),y=a(q);v(X=>i(y,`L’export emporte le même filtre que le tableau, mais pas son plafond : il descend - jusqu’à ${X??""} pesées, en point-virgule et en UTF-8 — il s’ouvre - tel quel dans le tableur d’un Windows français. Il ne demande aucun mot de passe : la - lecture du journal n’en demande pas non plus, et le fichier de diagnostic emporte déjà - les deux cents dernières pesées.`),[()=>ve(m)]),l(d,q)};h(Xe,d=>{e(H)==="read"&&d(g)})}var x=r(Xe,2);{var S=d=>{var q=wl(),y=a(q);{var X=ce=>{var W=qt("Lecture du journal…");l(ce,W)},te=ce=>{var W=qt();v(()=>i(W,`Le journal n’a pas pu être lu : ${e(j)??""} Ce n’est pas « aucune pesée ».`)),l(ce,W)},Te=ce=>{var W=qt("Le journal ne contient aucune pesée.");l(ce,W)},ye=ce=>{var W=qt();v(()=>i(W,`Aucune pesée ne correspond au filtre « ${e(I)??""} ».`)),l(ce,W)};h(y,ce=>{e(H)==="loading"?ce(X):e(H)==="unread"?ce(te,1):e(D)===""?ce(Te,2):ce(ye,-1)})}l(d,q)},ue=d=>{var q=Sl(),y=ae(q),X=a(y),te=r(y,2),Te=a(te),ye=r(a(Te));at(ye,21,()=>e(pe),ce=>ce.id,(ce,W)=>{var Ue=ql(),Fe=ae(Ue);let ke;var Se=a(Fe),Je=a(Se),J=r(Se),be=a(J),fe=r(J),Pe=a(fe),Ie=r(fe),nt=a(Ie),ut=r(Ie),pt=a(ut),Le=r(ut),st=a(Le),Qe=r(Le),_t=a(Qe),Tt=a(_t),Dt=r(Fe,2);{var w=U=>{var se=kl(),Ce=a(se);Ve(Ce,"colspan",M);var Me=a(Ce),Ke=a(Me),Ge=r(Me,2),A=r(a(Ge),2),G=a(A),de=r(A,4),Oe=a(de),we=r(de,4),Re=a(we),Ee=r(we,4),$e=a(Ee),Be=r(Ee,4),ft=a(Be),ot=r(Be,4),ct=a(ot),it=r(ot,4),dt=a(it),gt=r(it,4),yt=a(gt),Nt=a(yt),Mt=r(Ge,2);{var Ct=xt=>{var Et=yl();l(xt,Et)},Bt=xt=>{var Et=xl(),Vt=ae(Et),Ze=a(Vt);rt(Ze,{kind:"destructive",label:"Rejouer cette trame",protected:!0,get busy(){return e(Z)},onrun:()=>{Ye(e(b).frame)}}),l(xt,Et)};h(Mt,xt=>{e(b).frame===""?xt(Ct):xt(Bt,-1)})}v((xt,Et,Vt,Ze,kt,It,Zt,aa,f)=>{Ve(se,"data-detail",e(W).id),i(Ke,`Pesée ${e(b).id??""}`),i(G,`${e(b).product_name??""} (${e(b).product_id??""})`),i(Oe,e(b).reference),i(Re,`${xt??""} — - ${Et??""} - ${e(b).quantity>1?"unités":"unité"}`),i($e,`${Vt??""} g / ${Ze??""} g / - ${kt??""} g`),i(ft,`${It??""} — cadence - médiane ${Zt??""}`),i(ct,aa),i(dt,`${f??""}${e(b).detail===""?"":` — ${e(b).detail}`}`),i(Nt,e(b).frame===""?"aucune trame enregistrée":e(b).frame)},[()=>E(ge,e(b).mode,"mode de vente inconnu"),()=>ve(e(b).quantity),()=>ve(e(b).gross_g),()=>ve(e(b).tare_g),()=>ve(e(b).net_g),()=>E(le,e(b).stability,"stabilité inconnue"),()=>ua(e(b).rate_ms),()=>E($,e(b).source,"origine inconnue"),()=>E(P,e(b).result,"résultat inconnu")]),l(U,se)};h(Dt,U=>{e(ie)===e(W).id&&e(b)!==null&&U(w)})}v((U,se,Ce,Me)=>{ke=Gt(Fe,1,"svelte-86o8oz",null,ke,{open:e(ie)===e(W).id}),i(Je,U),i(be,e(W).product_name),i(Pe,`${se??""} g`),i(nt,e(W).barcode),i(pt,Ce),i(st,Me),Ve(_t,"aria-expanded",e(ie)===e(W).id),i(Tt,e(ie)===e(W).id?"fermer":"détail")},[()=>Yt(e(W).occurred_at),()=>ve(e(W).net_g),()=>E(P,e(W).result,"résultat inconnu"),()=>ua(e(W).duration_ms)]),et("click",_t,()=>p(ie,e(ie)===e(W).id?null:e(W).id,!0)),l(ce,Ue)}),v(()=>i(X,e(Y))),l(d,q)};h(x,d=>{e(pe).length===0?d(S):d(ue,-1)})}et("change",C,()=>{Ae()}),hr(C,()=>e(D),d=>p(D,d)),l(F,qe)},$$slots:{default:!0}});var V=r(_,2);tt(V,{title:"Journal technique",children:(F,Q)=>{var qe=Ut(),Ne=ae(qe);{var C=oe=>{var _e=El(),me=a(_e);{var He=x=>{var S=qt("Lecture du journal technique…");l(x,S)},Xe=x=>{var S=qt();v(()=>i(S,`Le journal technique n’a pas pu être lu : ${e(O)??""} Ce n’est pas « aucune - ligne ».`)),l(x,S)},g=x=>{var S=qt("Aucune ligne technique.");l(x,S)};h(me,x=>{e(ee)==="loading"?x(He):e(ee)==="unread"?x(Xe,1):x(g,-1)})}l(oe,_e)},ne=oe=>{var _e=Tl(),me=ae(_e),He=a(me),Xe=r(me,2),g=a(Xe);at(g,21,()=>e(L),x=>x.id,(x,S)=>{var ue=Pl(),d=a(ue),q=a(d),y=r(d,2),X=a(y),te=r(y,2),Te=a(te),ye=r(te,2);{var ce=Se=>{var Je=zl(),J=a(Je);v(()=>i(J,e(S).code)),l(Se,Je)};h(ye,Se=>{e(S).code!==""&&Wt.showTechnicalNames&&Se(ce)})}var W=r(ye,2),Ue=a(W),Fe=r(W,2);{var ke=Se=>{var Je=jl(),J=a(Je);v(()=>i(J,e(S).detail)),l(Se,Je)};h(Fe,Se=>{e(S).detail!==""&&Se(ke)})}v((Se,Je,J)=>{Ve(ue,"data-level",e(S).level),i(q,Se),i(X,Je),i(Te,J),i(Ue,e(S).message)},[()=>Yt(e(S).occurred_at),()=>E(xe,e(S).level,"niveau inconnu"),()=>Ka(e(S).source)]),l(x,ue)}),v(()=>i(He,e(ze))),l(oe,_e)};h(Ne,oe=>{e(L).length===0?oe(C):oe(ne,-1)})}l(F,qe)},$$slots:{default:!0}}),l(n,re),At()}Ot(["change","click"]);var Rl=u('

                                                        '),Al=u('

                                                        '),Nl=u('

                                                        '),Il=u('

                                                        '),$l=u('

                                                        Lecture de la configuration en cours… les flèches attendent qu’elle soit arrivée.

                                                        '),Dl=u(`
                                                        Aperçu de l’étiquette telle qu’elle serait imprimée

                                                        Décalage horizontal

                                                        Décalage vertical

                                                        Le décalage ne descend pas sous zéro : le poste refuse un décalage négatif quel que - soit le gabarit. Le maximum, lui, dépend de la géométrie du gabarit ; il est annoncé - ici si l’enregistrement le dépasse.

                                                        Le symbole code-barres est volontairement tronqué : un symbole conforme n’entre pas - sur 40 × 25 mm avec les cinq champs texte. Ce n’est pas un défaut de rendu et il n’y - a rien à corriger.

                                                        Le détail chiffré du symbole — largeur de module, modules rendus, modules attendus — - n’est pas servi par ce poste. Cet écran n’affiche pas un chiffre qu’il aurait deviné.

                                                        `,1),Ul=u(`

                                                        Chaque appui sort une étiquette pour de bon : le mot de passe est demandé au moment - d’imprimer, et l’impression repart d’elle-même une fois la session ouverte.

                                                        `,1),Fl=u('
                                                        ');function Ml(n,t){Rt(t,!0);const s=vt(t,"admin",7),c="printer.template",m="printer.options.offset_x",k="printer.options.offset_y",R=0,M="L’aperçu n’a pas pu être rendu par le poste.",P="Le poste a rendu l’aperçu, mais le navigateur ne l’a pas affiché.",B=[{what:"alignment",label:"Imprimer la mire d’alignement"},{what:"ruler",label:"Imprimer la réglette"}];let $=N(1),le=N(!0),ge=N(!1),xe=N(""),pe=N(null),L=N(""),D=null,ie=!1;const Z=o(()=>t.draft.text(c)),he=o(()=>Ur(e(Z),e(le),e(ge),e($))),H=o(()=>t.draft.config!==null),ee=o(()=>t.draft.number(m)),j=o(()=>t.draft.number(k)),O=o(()=>V(m)),K=o(()=>V(k)),T=o(E),b=o(()=>s().busy||e(L)!=="");ca(()=>{if(t.draft.config===null)return;if(t.draft.dirty){D===null&&I();return}const C={x:t.draft.number(m),y:t.draft.number(k)};if(D!==null&&D.x===C.x&&D.y===C.y)return;const ne=D===null;D=C,p(pe,C,!0),ne||Ae()});async function I(){if(!ie){ie=!0;try{const C=await qa(),ne={x:Y(C.config,m),y:Y(C.config,k)};D=ne,p(pe,ne,!0)}catch{}finally{ie=!1}}}function Y(C,ne){let oe=C;for(const _e of ne.split(".")){if(oe===null||typeof oe!="object")return 0;oe=oe[_e]}return typeof oe=="number"?oe:0}function ze(C,ne){const oe=t.draft.number(C)+ne;oe=2?"s":""}`}function _(C,ne){const oe=Number(ne);ne.trim()===""||Number.isNaN(oe)||t.draft.set(C,oe)}function V(C){return t.draft.faults.find(ne=>ne.field===C)?.message??""}async function F(C){if(!e(b)){p(L,C,!0),s().actionError="",s().notice="";try{const ne=await s().protect(()=>Ga(C));if(ne===null)return;s().notice=ne.message,await s().refresh()}finally{p(L,"")}}}var Q=Fl(),qe=a(Q);tt(qe,{title:"Aperçu de l’étiquette",note:"Le même moteur que l’impression (A2) : le décalage se voit parce qu’il est cuit dans le bitmap. L’image porte le gabarit en cours d’édition, mais le décalage ENREGISTRÉ — jamais celui que les flèches sont en train de régler.",children:(C,ne)=>{var oe=Dl(),_e=ae(oe);{var me=Le=>{var st=Rl(),Qe=a(st);v(()=>i(Qe,e(T))),l(Le,st)};h(_e,Le=>{e(T)!==""&&Le(me)})}var He=r(_e,2),Xe=a(He),g=a(Xe),x=r(g,2);{var S=Le=>{var st=Al(),Qe=a(st);v(()=>i(Qe,e(xe))),l(Le,st)};h(x,Le=>{e(xe)!==""&&Le(S)})}var ue=r(Xe,2),d=a(ue),q=r(a(d),2),y=a(q),X=r(d,2),te=a(X);{let Le=o(()=>!e(H)||e(ee)<=R);rt(te,{label:"← 1 dot",get disabled(){return e(Le)},onrun:()=>ze(m,-1)})}var Te=r(te,2);{let Le=o(()=>!e(H));rt(Te,{label:"1 dot →",get disabled(){return e(Le)},onrun:()=>ze(m,1)})}var ye=r(X,2);{var ce=Le=>{var st=Nl(),Qe=a(st);v(()=>i(Qe,e(O))),l(Le,st)};h(ye,Le=>{e(O)!==""&&Le(ce)})}var W=r(ye,2),Ue=r(a(W),2),Fe=a(Ue),ke=r(W,2),Se=a(ke);{let Le=o(()=>!e(H)||e(j)<=R);rt(Se,{label:"↑ 1 dot",get disabled(){return e(Le)},onrun:()=>ze(k,-1)})}var Je=r(Se,2);{let Le=o(()=>!e(H));rt(Je,{label:"1 dot ↓",get disabled(){return e(Le)},onrun:()=>ze(k,1)})}var J=r(ke,2);{var be=Le=>{var st=Il(),Qe=a(st);v(()=>i(Qe,e(K))),l(Le,st)};h(J,Le=>{e(K)!==""&&Le(be)})}var fe=r(J,4),Pe=a(fe),Ie=r(fe,2),nt=a(Ie),ut=r(Ie,2);{var pt=Le=>{var st=$l();l(Le,st)};h(ut,Le=>{e(H)||Le(pt)})}v((Le,st)=>{Ve(g,"src",e(he)),i(y,Le),i(Fe,st)},[()=>re(e(ee)),()=>re(e(j))]),Xt("load",g,()=>p(xe,"")),Xt("error",g,()=>{Ye(e(he))}),et("change",Pe,()=>Ae()),za(Pe,()=>e(le),Le=>p(le,Le)),et("change",nt,()=>Ae()),za(nt,()=>e(ge),Le=>p(ge,Le)),l(C,oe)},$$slots:{default:!0}});var Ne=r(qe,2);tt(Ne,{title:"Gabarit et impression",children:(C,ne)=>{var oe=Ul(),_e=ae(oe);{let x=o(()=>V(c)),S=o(()=>!e(H));ht(_e,{label:"Gabarit",path:c,get value(){return e(Z)},hint:"Le gabarit reproduit à l’identique s’appelle weighing_identical (A1).",get fault(){return e(x)},get disabled(){return e(S)},onchange:ue=>{t.draft.set(c,ue),Ae()}})}var me=r(_e,2);{let x=o(()=>t.draft.text("printer.options.darkness")),S=o(()=>V("printer.options.darkness")),ue=o(()=>!e(H));ht(me,{label:"Noircissement",path:"printer.options.darkness",kind:"number",get value(){return e(x)},hint:"Trop bas, l’étiquette pâlit au soleil ; trop haut, elle bave et le scanner refuse.",get fault(){return e(S)},get disabled(){return e(ue)},onchange:d=>_("printer.options.darkness",d)})}var He=r(me,2);{let x=o(()=>t.draft.text("printer.options.speed")),S=o(()=>V("printer.options.speed")),ue=o(()=>!e(H));ht(He,{label:"Vitesse",path:"printer.options.speed",kind:"number",get value(){return e(x)},get fault(){return e(S)},get disabled(){return e(ue)},onchange:d=>_("printer.options.speed",d)})}var Xe=r(He,2);{let x=o(()=>t.draft.text("printer.options.copies")),S=o(()=>V("printer.options.copies")),ue=o(()=>!e(H));ht(Xe,{label:"Exemplaires",path:"printer.options.copies",kind:"number",get value(){return e(x)},hint:"Un client repart avec une étiquette : deux exemplaires se justifient, pas se devinent.",get fault(){return e(S)},get disabled(){return e(ue)},onchange:d=>_("printer.options.copies",d)})}var g=r(Xe,2);at(g,21,()=>B,x=>x.what,(x,S)=>{{let ue=o(()=>e(L)===e(S).what);rt(x,{get act(){return e(S).what},get label(){return e(S).label},protected:!0,get busy(){return e(ue)},get disabled(){return e(b)},onrun:()=>{F(e(S).what)}})}}),l(C,oe)},$$slots:{default:!0}}),l(n,Q),At()}Ot(["change"]);var Bl=u(" "),Vl=u(' '),Wl=u(''),Gl=u('

                                                        Aucun tarif déclaré dans la configuration lue.

                                                        '),Hl=u('Prix du catalogue Odoo — pas de remise'),Ia=u(' '),Jl=u(' % ',1),Kl=u(' '),Xl=u(`

                                                        CodeLibelléAbrégéRemiseOrdre

                                                        Un champ vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la - retrouve dès qu’on quitte le champ. Une remise effacée serait le plein tarif pour - tous les adhérents.

                                                        `,1),Yl=u(" ",1),Ql=u(`

                                                        Les deux arrondis sont distincts parce qu’ils tombent à des endroits différents du - calcul : arrondir le prix au kilo puis le multiplier, ou multiplier puis arrondir, ne - donne pas le même centime sur une étiquette. Le poste applique l’arrondi commercial, - et l’écart se voit au centime près sur la même pesée.

                                                        `,1),Zl=u('

                                                        Rien ne s’affiche au client : c’est une information.

                                                        '),ei=u('

                                                        '),ti=u('
                                                        '),ai=u('

                                                        '),ri=u('
                                                      • '),ni=u(`

                                                        Aucun verdict en ce moment : ces lignes portent sur la pesée EN COURS, et le poste - est au repos. Elles apparaissent le temps d’un cycle, quand un client pose son sac - et touche une tuile.

                                                        `),si=u(' '),li=u(' ',1),ii=u('
                                                      • '),oi=u('

                                                          ',1),ui=u(`

                                                          L’ordre compte : le premier verdict bloquant décide de ce que le client lit. Les - garde-fous 1 à 7 portent sur l’état de la balance, c’est-à-dire le poids brut ; les - garde-fous 8 à 14 portent sur la vente, c’est-à-dire le net. Le code en gris est - celui du journal, celui qu’on lit au téléphone.

                                                            Un seuil vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la retrouve - dès qu’on quitte le champ. Pour changer un seuil, on tape l’autre valeur.

                                                            Ce que les garde-fous disent de la pesée en cours

                                                            `,1),ci=u(`

                                                            Le plan de numérotation n’est PAS ici, et c’est tout l’intérêt : préfixes, largeur de - la référence, largeur de la charge utile, décimales et mode de vente sont une - constante du binaire, indexée par préfixe et vérifiée au démarrage. - Un champ qui change le SENS du code lu par la caisse n’est pas un réglage, c’est un - contrat externe : il change avec une version du binaire, relue et testée, jamais - depuis l’écran d’un poste.

                                                            `,1),di=u('

                                                            Aucune dérogation : la limite générale s’applique à tous les produits.

                                                            '),vi=u(`

                                                            Les noms de produits n’ont pas pu être lus : le catalogue en service n’a pas - répondu. Les identifiants Odoo restent affichés.

                                                            `),pi=u(`Produit retiré : le garde-fou 14 refuse le produit avant que cette - dérogation ait un sens.`),fi=u('
                                                          1. '),gi=u('

                                                              ',1),mi=u('
                                                              ');function hi(n,t){Rt(t,!0);const s=(_,V=na,F=na,Q=na)=>{var qe=Wl(),Ne=a(qe),C=r(Ne,2),ne=a(C),oe=a(ne),_e=r(ne,2);{var me=g=>{var x=Bl(),S=a(x);v(()=>i(S,V())),l(g,x)};h(_e,g=>{Wt.showTechnicalNames&&g(me)})}var He=r(_e,2);{var Xe=g=>{var x=Vl(),S=a(x);v(()=>i(S,Q())),l(g,x)};h(He,g=>{Q()!==""&&g(Xe)})}v((g,x)=>{Ve(qe,"data-flag",V()),Ve(qe,"data-on",g),oa(Ne,x),i(oe,F())},[()=>String(t.draft.flag(V())),()=>t.draft.flag(V())]),et("change",Ne,g=>t.draft.set(V(),g.currentTarget.checked)),l(_,qe)},c=20,m=o(()=>le(t.draft)),k=o(()=>t.health.state.diagnostics),R=o(()=>t.health.decisions.filter(_=>_.min_weight_g!==null)),M=o(()=>e(R).filter(_=>!_.offered)),P=o(()=>e(R).slice(0,c));let B=N(Lt({})),$=N("loading");he();function le(_){const V=_.value("pricing.tiers");return Array.isArray(V)?V.map(F=>{const Q=F??{},qe=Q.discount_percent;return{code:String(Q.code??""),label:String(Q.label??""),abbrev:String(Q.abbrev??""),written:qe===void 0?null:String(qe),discount:ge(qe)?qe:null,rank:Number(Q.rank??0)}}):[]}function ge(_){return typeof _!="number"||!Number.isFinite(_)||_<0||_>100?!1:Math.abs(_*10-Math.round(_*10))<1e-9}const xe=o(()=>String(t.draft.value("pricing.reference_code")??""));function pe(_){return _===null?"":String(_).replace(".",",")}function L(_){const V=1e3-Math.round(_*10);return`${String(Math.trunc(V/100))},${String(V%100).padStart(2,"0")}`}function D(_,V){const F=Number(V);V.trim()===""||Number.isNaN(F)||t.draft.set(_,F)}function ie(_,V){const F=V.trim().replace(",",".");if(!/^\d{1,3}(\.\d)?$/u.test(F))return;const Q=Number(F);Q>100||t.draft.set(_,Q)}function Z(_,V){!(_ instanceof HTMLInputElement)||_.value===V||(_.value=V)}async function he(){try{const _=await Ma();p(B,Object.fromEntries(_.products.map(V=>[V.id,V.name])),!0),p($,"read")}catch{p($,"unread")}}function H(_){const V=e(B)[_];return V!==void 0?V:e($)==="loading"?"Lecture du nom…":e($)==="read"?"Produit absent du catalogue en service":"Nom non lu"}function ee(_){return _<=1?`${String(_)} tarif déclaré`:`${String(_)} tarifs déclarés`}const j=o(()=>`${ve(e(k).length)} ${e(k).length>1?"verdicts":"verdict"} sur les quatorze garde-fous.`),O=o(()=>`${ve(e(P).length)} ${e(P).length>1?"lignes affichées":"ligne affichée"} sur ${ve(e(R).length)} `+(e(M).length===0?`${e(R).length>1?"dérogations en vigueur":"dérogation en vigueur"}.`:`${e(R).length>1?"dérogations enregistrées":"dérogation enregistrée"}, dont ${ve(e(M).length)} sur un produit retiré, sans effet : le garde-fou 14 refuse le produit avant que le 8 ait un sens.`)+(e(R).length>e(P).length?" Les autres se lisent produit par produit depuis l’onglet Catalogue.":""));function K(_){return`plancher ${ve(_)} g : refusé à ${ve(_)} g et en dessous`}const T=[{rank:1,code:"OVERLOAD",label:"Surcharge",when:"La balance annonce elle-même OL, ou le poids brut dépasse la capacité.",severity:"Bloquant",blocking:!0,message:"La balance est en surcharge. Retirez votre article.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la capacité, réglée au garde-fou 9 sous « ${Ft("limits.max_weight_g")} ».`},{rank:2,code:"MEASUREMENT_EXPIRED",label:"Poids périmé",when:"La mesure est plus vieille que la péremption, dans les deux modes de stabilité.",severity:"Bloquant",blocking:!0,message:"Poids indisponible. Patientez ou appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil à régler : le poste calcule lui-même à partir du rythme de la balance."},{rank:3,code:"BASKET_MISSING",label:"Panier absent",when:"Le poids brut tombe dans la fenêtre négative du panier : il a été soulevé.",severity:"Bloquant",blocking:!0,message:"Le panier n'est pas sur la balance. Reposez-le.",thresholds:[{path:"limits.basket_min_g",label:"Bas de la fenêtre du panier",hint:"En grammes, NÉGATIF : c’est le poids du panier que la balance a perdu."},{path:"limits.basket_max_g",label:"Haut de la fenêtre du panier",hint:"Négatif lui aussi, et plus proche de zéro que le bas."}],switchPath:"limits.basket_check_enabled",switchLabel:"Ce poste travaille avec un panier taré",note:"La règle s’active ou non, en bloc : il n’y a pas de demi-mesure à régler."},{rank:4,code:"SCALE_EMPTY",label:"Plateau vide",when:"Le poids brut ne sort pas de la bande « il n’y a rien sur le plateau ».",severity:"Bloquant — un filet, hors parcours nominal",blocking:!0,message:"Posez votre produit.",thresholds:[{path:"limits.empty_max_g",label:"Plateau considéré vide",hint:"En dessous, le poste considère qu’il n’y a rien sur le plateau."}],switchPath:"",switchLabel:"",note:"Toucher une tuile sur un plateau vide ARME la sélection au lieu d’être refusé : la règle reste évaluée pour la saisie manuelle et les chemins dérivés."},{rank:5,code:"TARE_REQUIRED",label:"Remise à zéro nécessaire",when:"Le brut est sous la bande du plateau vide, et hors de la fenêtre du panier.",severity:"Bloquant",blocking:!0,message:"La balance doit être remise à zéro.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la valeur négative de celui du garde-fou 4, « ${Ft("limits.empty_max_g")} ».`},{rank:6,code:"WEIGHT_UNSTABLE",label:"Pesée instable",when:"La trame déclare la mesure instable.",severity:"Information par défaut (A3)",blocking:!1,message:"Pesée en cours…",thresholds:[],switchPath:"",switchLabel:"",note:"La sévérité suit l’exigence de stabilité : elle passe à Bloquant quand celle-ci est réglée sur « blocking ». L’impression n’est jamais bloquée par défaut."},{rank:7,code:"TARE_INVALID",label:"Emballage incohérent",when:"Une tare a été saisie, et elle atteint la pesée ou dépasse le maximum.",severity:"Bloquant",blocking:!0,message:"Le poids de l'emballage est supérieur ou égal à la pesée.",thresholds:[{path:"limits.max_tare_g",label:"Tare maximum",hint:"Une tare plus lourde que le maximum est une faute de frappe."}],switchPath:"",switchLabel:"",note:""},{rank:8,code:"WEIGHT_TOO_LOW",label:"Poids trop faible",when:"Vente au poids : le NET est strictement positif et ne dépasse pas le plancher.",severity:"Bloquant",blocking:!0,message:"La balance doit être retarée, ou l'emballage est trop lourd.",thresholds:[{path:"limits.min_weight_g",label:"Poids minimum",hint:"Une dérogation par produit existe, dans l’onglet Catalogue."}],switchPath:"",switchLabel:"",note:""},{rank:9,code:"WEIGHT_TOO_HIGH",label:"Poids trop élevé",when:"Le NET dépasse la capacité — strictement, pour que la capacité reste atteignable.",severity:"Bloquant",blocking:!0,message:"{{.Weight}} kg, ça paraît un peu lourd !",thresholds:[{path:"limits.max_weight_g",label:"Poids maximum",hint:"C’est la capacité du champ NNDDD du code-barres, pas un seuil de vraisemblance."}],switchPath:"",switchLabel:"",note:""},{rank:10,code:"UNITS_OUT_OF_RANGE",label:"Nombre d’unités hors plage",when:"Vente à l’unité : la quantité sort de la plage.",severity:"Bloquant",blocking:!0,message:"{{.Quantity}} unités, ça paraît un peu beaucoup !",thresholds:[{path:"limits.min_units",label:"Unités minimum",hint:""},{path:"limits.max_units",label:"Unités maximum",hint:""}],switchPath:"",switchLabel:"",note:""},{rank:11,code:"AMOUNT_OUT_OF_CAPACITY",label:"Montant hors capacité du code-barres",when:"La charge utile encode un PRIX, et il dépasse ce que le champ peut porter.",severity:"Bloquant",blocking:!0,message:"Prix trop élevé pour le code-barres.",thresholds:[{path:"limits.max_amount_cents",label:"Montant maximum",hint:"En centimes. Aucun préfixe du plan livré n’encode un prix : la règle est éprouvée sans qu’aucun produit puisse l’atteindre."}],switchPath:"",switchLabel:"",note:""},{rank:12,code:"ZERO_PRICE",label:"Prix nul",when:"Le montant du tarif imprimé en grand vaut zéro.",severity:"Bloquant",blocking:!0,message:"Prix nul. Appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : un produit à 0 € est une anomalie sans nuance."},{rank:13,code:"LIGHT_PRODUCT_ALLOWED",label:"Produit léger autorisé",when:"Le garde-fou 8 n’a pas déclenché grâce à la dérogation du produit.",severity:"Information",blocking:!1,message:"",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil général : c’est la dérogation par produit, listée plus bas et posée depuis l’onglet Catalogue. Rien ne s’affiche au client ; l’id du produit est journalisé."},{rank:14,code:"PRODUCT_WITHDRAWN",label:"Produit retiré",when:"Quelqu’un a décidé de ne plus proposer ce produit.",severity:"Bloquant",blocking:!0,message:"Ce produit n'est pas disponible.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : c’est une décision humaine, prise depuis l’onglet Catalogue. Aucune règle d’import ne peut la déduire."}];function b(_){return T.find(V=>V.code===_)?.label??"Garde-fou inconnu de cet écran"}const I=["{{.Weight}}","{{.Quantity}}"];function Y(_){return _==="blocking"?"Bloquant":_==="info"?"Information":"Sévérité inconnue de cet écran"}var ze=mi(),Ae=a(ze);tt(Ae,{title:"Grille de tarifs",note:"Un second tarif n’est pas une case à cocher : c’est une ligne de plus dans cette grille.",children:(_,V)=>{var F=Yl(),Q=ae(F);{var qe=oe=>{var _e=Gl();l(oe,_e)},Ne=oe=>{var _e=Xl(),me=ae(_e),He=a(me),Xe=r(me,2),g=a(Xe),x=r(a(g));at(x,21,()=>e(m),fa,(S,ue,d)=>{var q=Kl(),y=a(q),X=a(y),te=r(y),Te=a(te);Ve(Te,"aria-label",`Libellé du tarif ${d+1}`);var ye=r(te),ce=a(ye);Ve(ce,"aria-label",`Abrégé du tarif ${d+1}`);var W=r(ye),Ue=a(W);{var Fe=fe=>{var Pe=Hl();l(fe,Pe)},ke=fe=>{var Pe=Ia(),Ie=a(Pe);v(()=>i(Ie,`${e(ue).written??""} — le tarif de référence est le prix du catalogue : il - ne peut pas porter de remise, et l’enregistrement la refusera.`)),l(fe,Pe)},Se=fe=>{var Pe=Ia(),Ie=a(Pe);v(()=>i(Ie,`${e(ue).written??""} — une remise s’écrit au dixième de point ; celle-ci se - change dans le fichier de configuration.`)),l(fe,Pe)},Je=fe=>{var Pe=Jl(),Ie=ae(Pe);Ve(Ie,"aria-label",`Remise du tarif ${d+1}`);var nt=r(Ie,2),ut=a(nt);v((pt,Le)=>{ra(Ie,pt),i(ut,`un produit à 10,00 €/kg s’affiche ${Le??""} €/kg`)},[()=>pe(e(ue).discount??0),()=>L(e(ue).discount??0)]),et("input",Ie,pt=>ie(`pricing.tiers.${String(d)}.discount_percent`,pt.currentTarget.value)),et("focusout",Ie,pt=>Z(pt.currentTarget,pe(e(ue).discount??0))),l(fe,Pe)};h(Ue,fe=>{e(ue).code===e(xe)&&e(ue).written===null?fe(Fe):e(ue).code===e(xe)?fe(ke,1):e(ue).written!==null&&e(ue).discount===null?fe(Se,2):fe(Je,-1)})}var J=r(W),be=a(J);v(()=>{i(X,e(ue).code),ra(Te,e(ue).label),ra(ce,e(ue).abbrev),i(be,e(ue).rank)}),et("input",Te,fe=>t.draft.set(`pricing.tiers.${String(d)}.label`,fe.currentTarget.value)),et("input",ce,fe=>t.draft.set(`pricing.tiers.${String(d)}.abbrev`,fe.currentTarget.value)),l(S,q)}),v(S=>i(He,`${S??""}.`),[()=>ee(e(m).length)]),l(oe,_e)};h(Q,oe=>{e(m).length===0?oe(qe):oe(Ne,-1)})}var C=r(Q,2);{let oe=o(()=>t.draft.text("pricing.primary_code"));ht(C,{label:"Tarif imprimé en grand",path:"pricing.primary_code",get value(){return e(oe)},hint:"Le prix que le client lit sur l’étiquette (A7).",onchange:_e=>t.draft.set("pricing.primary_code",_e)})}var ne=r(C,2);{let oe=o(()=>t.draft.text("pricing.reference_code"));ht(ne,{label:"Tarif qui serait encodé si le code-barres portait un prix",path:"pricing.reference_code",get value(){return e(oe)},hint:"Le plan livré ne porte pas de prix, mais un poids ou un nombre d’unités : la caisse retrouve le prix par la référence, dans Odoo, et ne doit jamais sous-facturer.",onchange:_e=>t.draft.set("pricing.reference_code",_e)})}l(_,F)},$$slots:{default:!0}});var Ye=r(Ae,2);tt(Ye,{title:"Les deux arrondis",children:(_,V)=>{var F=Ql(),Q=ae(F);{let Ne=o(()=>t.draft.text("pricing.unit_price_rounding"));ht(Q,{label:"Arrondi du prix unitaire dérivé",path:"pricing.unit_price_rounding",get value(){return e(Ne)},hint:"Il s’applique au prix au kilo calculé par la remise.",onchange:C=>t.draft.set("pricing.unit_price_rounding",C)})}var qe=r(Q,2);{let Ne=o(()=>t.draft.text("pricing.amount_rounding"));ht(qe,{label:"Arrondi du montant",path:"pricing.amount_rounding",get value(){return e(Ne)},hint:"Il s’applique au montant de l’étiquette.",onchange:C=>t.draft.set("pricing.amount_rounding",C)})}l(_,F)},$$slots:{default:!0}});var We=r(Ye,2);tt(We,{title:"Les quatorze garde-fous, dans l’ordre d’évaluation",note:"Le seuil se modifie ici. La sévérité ne se règle pas : elle dit si le poste refuse ou avertit, et cela ne dépend pas du magasin. Le message affiché au client n’est pas encore modifiable depuis cet écran.",children:(_,V)=>{var F=ui(),Q=r(ae(F),2);at(Q,21,()=>T,_e=>_e.code,(_e,me)=>{var He=ri(),Xe=a(He),g=a(Xe),x=a(g),S=r(g,2),ue=a(S),d=r(S,2),q=a(d),y=r(d,2),X=a(y),te=r(Xe,2),Te=a(te),ye=r(te,2);{var ce=J=>{var be=Zl();l(J,be)},W=J=>{var be=ei(),fe=a(be);v(()=>i(fe,`« ${e(me).message??""} »`)),l(J,be)};h(ye,J=>{e(me).message===""?J(ce):J(W,-1)})}var Ue=r(ye,2);{var Fe=J=>{s(J,()=>e(me).switchPath,()=>e(me).switchLabel,()=>"")};h(Ue,J=>{e(me).switchPath!==""&&J(Fe)})}var ke=r(Ue,2);at(ke,17,()=>e(me).thresholds,J=>J.path,(J,be)=>{var fe=ti(),Pe=a(fe);{let Ie=o(()=>t.draft.text(e(be).path));ht(Pe,{get label(){return e(be).label},get path(){return e(be).path},kind:"number",get value(){return e(Ie)},get hint(){return e(be).hint},onchange:nt=>D(e(be).path,nt)})}et("focusout",fe,Ie=>Z(Ie.target,t.draft.text(e(be).path))),l(J,fe)});var Se=r(ke,2);{var Je=J=>{var be=ai(),fe=a(be);v(()=>i(fe,e(me).note)),l(J,be)};h(Se,J=>{e(me).note!==""&&J(Je)})}v(J=>{Ve(He,"data-code",e(me).code),i(x,e(me).rank),i(ue,e(me).label),i(q,e(me).code),Ve(y,"data-blocking",J),i(X,e(me).severity),i(Te,e(me).when)},[()=>String(e(me).blocking)]),l(_e,He)});var qe=r(Q,4),Ne=a(qe),C=r(qe,4);{var ne=_e=>{var me=ni();l(_e,me)},oe=_e=>{var me=oi(),He=ae(me),Xe=a(He),g=r(He,2);at(g,21,()=>e(k),x=>x.code,(x,S)=>{var ue=ii(),d=a(ue),q=a(d),y=r(d,2),X=a(y),te=r(y,2),Te=a(te),ye=r(te,2);{var ce=Fe=>{var ke=si(),Se=a(ke);v(()=>i(Se,`« ${e(S).message??""} »`)),l(Fe,ke)};h(ye,Fe=>{e(S).message!==""&&Fe(ce)})}var W=r(ye,2);{var Ue=Fe=>{var ke=li(),Se=ae(ke),Je=a(Se),J=r(Se,2),be=a(J);v(fe=>{i(Je,fe),i(be,e(S).product_id)},[()=>H(e(S).product_id)]),l(Fe,ke)};h(W,Fe=>{e(S).product_id!==""&&Fe(Ue)})}v((Fe,ke,Se)=>{Ve(ue,"data-blocking",Fe),i(q,ke),i(X,e(S).code),i(Te,Se)},[()=>String(e(S).blocking),()=>b(e(S).code),()=>Y(e(S).severity)]),l(x,ue)}),v(x=>{Ve(He,"data-verdicts",x),i(Xe,e(j))},[()=>String(e(k).length)]),l(_e,me)};h(C,_e=>{e(k).length===0?_e(ne):_e(oe,-1)})}v(_e=>i(Ne,`Les marqueurs ${_e??""} sont remplacés par les valeurs de la pesée au - moment où le message s’affiche.`),[()=>I.join(" et ")]),l(_,F)},$$slots:{default:!0}});var E=r(We,2);tt(E,{title:"Code-barres",note:"Un seul réglage, et c’est voulu : le reste du plan de numérotation n’est pas de la configuration.",children:(_,V)=>{var F=ci(),Q=ae(F);s(Q,()=>"barcode.verify_reference_check_digit",()=>"Refuser une référence dont la clé de contrôle est fausse",()=>"Décoché, le poste recalcule une clé juste sur une référence fausse, en silence — et la caisse encaisse un autre article."),l(_,F)},$$slots:{default:!0}});var re=r(E,2);tt(re,{title:"Dérogations de poids minimum",note:"En lecture ici ; elles se modifient depuis l’onglet Catalogue, là où se trouve le produit.",children:(_,V)=>{var F=Ut(),Q=ae(F);{var qe=C=>{var ne=di();l(C,ne)},Ne=C=>{var ne=gi(),oe=ae(ne),_e=a(oe),me=r(oe,2);{var He=g=>{var x=vi();l(g,x)};h(me,g=>{e($)==="unread"&&g(He)})}var Xe=r(me,2);at(Xe,21,()=>e(P),g=>g.product_id,(g,x)=>{var S=fi(),ue=a(S),d=a(ue),q=r(ue,2),y=a(q),X=r(q,2),te=a(X),Te=r(X,2),ye=a(Te),ce=r(Te,2),W=a(ce),Ue=r(ce,2);{var Fe=ke=>{var Se=pi();l(ke,Se)};h(Ue,ke=>{e(x).offered||ke(Fe)})}v((ke,Se,Je,J)=>{Ve(S,"data-withdrawn",ke),i(d,Se),i(y,e(x).product_id),i(te,Je),i(ye,e(x).reason),i(W,`${J??""}, ${e(x).decided_by??""}`)},[()=>String(!e(x).offered),()=>H(e(x).product_id),()=>K(e(x).min_weight_g??0),()=>da(e(x).decided_at)]),l(g,S)}),v(()=>i(_e,e(O))),l(C,ne)};h(Q,C=>{e(R).length===0?C(qe):C(Ne,-1)})}l(_,F)},$$slots:{default:!0}}),l(n,ze),At()}Ot(["change","input","focusout"]);function _i(n,t){const s=[];for(const c of[...ya(n),...ya(t)]){if(s.some(R=>R.path===c))continue;const m=$a(va(n,c)),k=$a(va(t,c));m!==k&&s.push({path:c,before:m,after:k})}return s}function va(n,t){let s=n;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}function ya(n,t=""){if(n===null||typeof n!="object"||Array.isArray(n))return t===""?[]:[t];const s=[];for(const[c,m]of Object.entries(n))s.push(...ya(m,t===""?c:`${t}.${c}`));return s}function $a(n){return n===void 0?"—":n===null?"vide":typeof n=="boolean"?n?"oui":"non":typeof n=="object"?JSON.stringify(n):String(n)}var bi=u('
                                                              Empreinte de la configuration en service
                                                              Version du binaire
                                                              Répertoire de données
                                                              Espace disque
                                                              ',1),wi=u('

                                                              '),Da=u(" "),yi=u(' '),xi=u("
                                                            • "),ki=u(`

                                                                Recopier reste possible : les valeurs entrent dans le brouillon, où elles se - corrigent champ par champ avant l’enregistrement.

                                                                `),qi=u(`

                                                                Ce fichier n’a été comparé à rien : la configuration en service n’a pas pu être - lue. Ni « identique », ni « n champs changent » — ce que ce fichier changerait - sur ce poste reste inconnu.

                                                                `),Si=u(`

                                                                Ce fichier décrit la même configuration que celle en service : il n’y a rien à - recopier. C’est ce qu’on veut lire à la fin d’un clonage. Deux champs ne sont pas - comparés : la date du dernier enregistrement, que chaque poste écrit lui-même, et - le mot de passe du catalogue, qu’aucun des deux ne porte en clair.

                                                                `),Li=u(' '),Ei=u('

                                                                '),zi=u(`

                                                                ChampEn serviceDans le fichier

                                                                Recopier n’applique rien : les valeurs entrent dans le brouillon, et c’est - « Enregistrer » qui les met en service.

                                                                `,1),ji=u('

                                                                ',1),Pi=u(`

                                                                L’export emporte encore l’empreinte du mot de passe : c’est la seule lecture que le - poste garde derrière la clé. L’import, lui, est lu PAR LE POSTE, qui écarte les deux - secrets et le numéro de poste avant de dire ce qui changerait.

                                                                `,1),Ti=u('

                                                                Lecture des versions enregistrées…

                                                                '),Ci=u(`

                                                                Les versions enregistrées n’ont pas pu être lues : cette liste ne dit donc rien de - ce que ce poste garde. Ce n’est pas « aucune version ».

                                                                `),Oi=u('

                                                                Aucune version enregistrée : ce poste n’a jamais été reconfiguré.

                                                                '),Ri=u('
                                                              • '),Ai=u(`

                                                                  Remettre une version en service remplace la configuration du poste sur-le-champ, et - ce qui n’a pas été enregistré est perdu : c’est le seul geste de cette page qui - change le poste tout de suite, et le seul qui garde ses 72 px.

                                                                  `,1),Ni=u('
                                                                  ');function Ii(n,t){Rt(t,!0);const s=vt(t,"admin",7),c=40,m=20,k=5,R=new Set(["modified_at","catalog.options.password"]),M=[{path:"station.name",name:"le nom du poste"},{path:"network.listen",name:"l’adresse d’écoute"},{path:"scale.options",name:"les réglages de la balance"},{path:"printer.options",name:"les réglages de l’imprimante"},{path:"catalog.options",name:"la source du catalogue, compte compris"}];let P=N(Lt([])),B=N("reading"),$=N(null),le=N(""),ge=N(null),xe=N(""),pe=N(Lt([])),L=N(""),D="";const ie=o(()=>s().busy||e(L)!==""),Z=o(()=>Y(e($),e(ge))),he=o(()=>e($)!==null&&e(ge)!==null),H=o(()=>ze(e($),e(ge))),ee=o(()=>e(Z).slice(0,c)),j=o(()=>e(pe).slice(0,m)),O=o(()=>e(P).slice(0,k)),K=o(()=>Xe(e(ee).length,e(Z).length,"champ qui change","champs qui changent")),T=o(()=>Xe(e(j).length,e(pe).length,"contrôle refuse une clé","contrôles refusent une clé")),b=o(()=>Xe(e(O).length,e(P).length,"version enregistrée","versions enregistrées")),I=o(()=>e(Z).length>1?`Recopier ces ${ve(e(Z).length)} champs dans le brouillon`:"Recopier ce champ dans le brouillon");We();function Y(d,q){return d===null||q===null?[]:_i(d,q).filter(y=>!R.has(y.path))}function ze(d,q){if(d===null||q===null)return[];const y=d,X=q;return M.filter(te=>Ae(X,te.path)&&!Ae(y,te.path))}function Ae(d,q){const y=va(d,q);return y==null||y===""?!0:typeof y!="object"||Array.isArray(y)?!1:Object.keys(y).length===0}function Ye(d){return d.length<2?d.join(""):`${d.slice(0,-1).join(", ")} et ${d[d.length-1]??""}`}ca(()=>{const d=t.health.config_fingerprint;d!==D&&(D=d,E())});async function We(){const d=await s().load(Rr);if(d===null){p(B,"unreadable");return}p(P,d,!0),p(B,"read")}async function E(){try{p($,(await qa()).config,!0),p(le,"")}catch(d){p($,null),p(le,d instanceof Error?d.message:"Le poste n’a pas répondu.",!0)}}async function re(d){p(L,d?"export-all":"export-clone",!0),s().notice="",s().actionError="";try{const q=await s().protect(()=>_(d));if(q===null)return;Q(q.name,q.blob),s().notice=`${q.name} est remis au navigateur : c’est lui qui l’enregistre, voyez ses téléchargements.`}finally{p(L,"")}}async function _(d){const y=await fetch(`/admin/api/config/export?hardware=${d?"1":"0"}`,{headers:{accept:"application/json"}});if(!y.ok)throw new $t(y.status,_e(await y.text(),"L’export"));return{name:V(d),blob:await y.blob()}}function V(d){const q=d?"":"-sans-materiel";return`config-poste${String(t.health.station)}${q}-${F()}.json`}function F(){const d=new Date,q=String(d.getMonth()+1).padStart(2,"0"),y=String(d.getDate()).padStart(2,"0");return`${String(d.getFullYear())}-${q}-${y}`}function Q(d,q){const y=URL.createObjectURL(q),X=document.createElement("a");X.href=y,X.download=d,document.body.appendChild(X),X.click(),X.remove(),setTimeout(()=>{URL.revokeObjectURL(y)},0)}async function qe(d){const q=await fetch("/admin/api/config/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(d)}),y=await q.text();if(!q.ok)throw new $t(q.status,_e(y,"L’import"));return JSON.parse(y)}async function Ne(d){if(d==null)return;s().notice="",s().actionError="";let q;try{q=JSON.parse(await d.text())}catch{s().actionError=`${d.name} n’est pas un fichier JSON lisible.`;return}p(L,"import");try{const y=await s().protect(()=>qe(q));if(y===null)return;p(ge,y.config,!0),p(xe,d.name,!0),p(pe,y.faults,!0),await E(),s().notice=C(d.name)}finally{p(L,"")}}function C(d){return e(he)?e(Z).length===0?`${d} décrit la même configuration que celle en service.`:`${d} est lu. Rien n’est appliqué : relisez le tableau.`:`${d} est lu, mais la configuration en service ne l’est pas : rien ne peut être comparé.`}async function ne(){if(e(ge)===null)return;const d=e(ge),q=[...e(Z)];for(const y of q)t.draft.set(y.path,va(d,y.path));s().actionError="",s().notice="Le fichier est recopié dans le brouillon. Rien n’est appliqué avant « Enregistrer ».",await E()}async function oe(d){p(L,`restore-${String(d)}`),s().notice="",s().actionError="";try{if(await s().protect(()=>Ar(d))===null)return;await t.draft.load(),await We(),await E(),await s().refresh(),s().notice=`La version ${String(d)} est remise en service.`}finally{p(L,"")}}function _e(d,q){try{const y=JSON.parse(d);if(typeof y?.message=="string"&&y.message!=="")return y.message}catch{}return`${q} a été refusé par le poste.`}function me(d){return t.draft.faults.find(q=>q.field===d)?.message??""}function He(d){return t.draft.faults.find(q=>q.field===d)?.allowed??[]}function Xe(d,q,y,X){const te=q>1?X:y;return d>=q?`${ve(q)} ${te}.`:`${ve(d)} lignes affichées sur ${ve(q)} ${te}.`}var g=Ni(),x=a(g);tt(x,{title:"Identité du poste",children:(d,q)=>{var y=bi(),X=ae(y);{let Pe=o(()=>t.draft.text("station.number")),Ie=o(()=>me("station.number")),nt=o(()=>He("station.number"));ht(X,{label:"Numéro du poste",path:"station.number",kind:"number",get value(){return e(Pe)},get fault(){return e(Ie)},get allowed(){return e(nt)},hint:"C’est de lui que dérive le nom du fichier de catalogue attendu, flv_.csv.",onchange:ut=>t.draft.set("station.number",Number(ut))})}var te=r(X,2);{let Pe=o(()=>t.draft.text("station.name")),Ie=o(()=>me("station.name")),nt=o(()=>He("station.name"));ht(te,{label:"Nom du poste",path:"station.name",get value(){return e(Pe)},get fault(){return e(Ie)},get allowed(){return e(nt)},hint:"Ce que lit un bénévole : « Poste 2 — fruits ».",onchange:ut=>t.draft.set("station.name",ut)})}var Te=r(te,2);{let Pe=o(()=>t.draft.text("station.coop")),Ie=o(()=>me("station.coop")),nt=o(()=>He("station.coop"));ht(Te,{label:"Coopérative",path:"station.coop",get value(){return e(Pe)},get fault(){return e(Ie)},get allowed(){return e(nt)},onchange:ut=>t.draft.set("station.coop",ut)})}var ye=r(Te,2),ce=r(a(ye),2),W=a(ce),Ue=r(ce,4),Fe=a(Ue),ke=r(Ue,4),Se=a(ke),Je=r(ke,4),J=a(Je);{var be=Pe=>{var Ie=qt("non mesuré");l(Pe,Ie)},fe=Pe=>{var Ie=qt();v((nt,ut,pt)=>i(Ie,`${nt??""} libres sur - ${ut??""} — seuil d’alerte - ${pt??""} Mo`),[()=>wa(t.health.disk.free_bytes),()=>wa(t.health.disk.total_bytes),()=>ve(t.health.disk.alert_mb)]),l(Pe,Ie)};h(J,Pe=>{t.health.disk===null?Pe(be):Pe(fe,-1)})}v(()=>{i(W,t.health.config_fingerprint),i(Fe,t.health.version),i(Se,t.health.disk===null?"non publié par ce poste":t.health.disk.path)}),l(d,y)},$$slots:{default:!0}});var S=r(x,2);tt(S,{title:"Exporter, importer",note:"Pour installer un autre poste : ce fichier emporte les tarifs, les garde-fous, l’étiquette et les catégories. Tout ce qui est propre à ce poste-ci reste ici — le mot de passe, le code de secours, le numéro et le nom du poste, les réglages de la balance, ceux de l’imprimante, la source du catalogue et le réseau.",children:(d,q)=>{var y=Pi(),X=ae(y),te=a(X);{let J=o(()=>e(L)==="export-all");rt(te,{label:"Exporter tout",protected:!0,get busy(){return e(J)},get disabled(){return e(ie)},onrun:()=>{re(!0)}})}var Te=r(te,2);{let J=o(()=>e(L)==="export-clone");rt(Te,{label:"Exporter sans le matériel",protected:!0,get busy(){return e(J)},get disabled(){return e(ie)},onrun:()=>{re(!1)}})}var ye=r(Te,2);let ce;var W=a(ye),Ue=r(W,3),Fe=r(X,4);{var ke=J=>{var be=wi(),fe=a(be);v(()=>i(fe,`La configuration en service n’a pas pu être lue : ${e(le)??""} La colonne - « En service » ne peut donc rien affirmer.`)),l(J,be)};h(Fe,J=>{e(le)!==""&&J(ke)})}var Se=r(Fe,2);{var Je=J=>{var be=ji(),fe=ae(be),Pe=a(fe),Ie=r(fe,2);{var nt=Qe=>{var _t=ki(),Tt=a(_t),Dt=a(Tt),w=r(Tt,2);at(w,21,()=>e(j),fa,(U,se)=>{var Ce=xi(),Me=a(Ce),Ke=a(Me),Ge=r(Me,2);{var A=we=>{var Re=Da(),Ee=a(Re);v(()=>i(Ee,e(se).field)),l(we,Re)};h(Ge,we=>{Wt.showTechnicalNames&&we(A)})}var G=r(Ge),de=r(G);{var Oe=we=>{var Re=yi(),Ee=a(Re);v($e=>i(Ee,`Valeurs acceptées : ${$e??""}.`),[()=>e(se).allowed.join(", ")]),l(we,Re)};h(de,we=>{e(se).allowed!==void 0&&e(se).allowed.length>0&&we(Oe)})}v(we=>{i(Ke,we),i(G,` ${e(se).message??""} `)},[()=>Ft(e(se).field)]),l(U,Ce)}),v(()=>i(Dt,`Ce fichier serait refusé en l’état : ${e(T)??""}`)),l(Qe,_t)};h(Ie,Qe=>{e(pe).length>0&&Qe(nt)})}var ut=r(Ie,2);{var pt=Qe=>{var _t=qi();l(Qe,_t)},Le=Qe=>{var _t=Si();l(Qe,_t)},st=Qe=>{var _t=zi(),Tt=ae(_t),Dt=a(Tt),w=r(Dt);{var U=de=>{var Oe=qt("Les autres sont dans le fichier, et « Recopier » les prend tous.");l(de,Oe)};h(w,de=>{e(Z).length>e(ee).length&&de(U)})}var se=r(Tt,2),Ce=a(se),Me=r(a(Ce));at(Me,21,()=>e(ee),de=>de.path,(de,Oe)=>{const we=o(()=>Ft(e(Oe).path));var Re=Li(),Ee=a(Re),$e=a(Ee),Be=r($e);{var ft=gt=>{var yt=Da(),Nt=a(yt);v(()=>i(Nt,e(Oe).path)),l(gt,yt)};h(Be,gt=>{Wt.showTechnicalNames&&e(we)!==e(Oe).path&>(ft)})}var ot=r(Ee),ct=a(ot),it=r(ot),dt=a(it);v(()=>{Ve(Re,"data-path",e(Oe).path),i($e,`${e(we)??""} `),i(ct,e(Oe).before),i(dt,e(Oe).after)}),l(de,Re)});var Ke=r(se,2);{var Ge=de=>{var Oe=Ei(),we=a(Oe);v(Re=>i(we,`Ce fichier ne porte pas ${Re??""} : - l’export sans le matériel les retire, et l’import ne remet que le numéro du - poste. Les lignes correspondantes sont VIDES ci-dessus, et - « Recopier » recopie ce vide dans le brouillon.`),[()=>Ye(e(H).map(Re=>Re.name))]),l(de,Oe)};h(Ke,de=>{e(H).length>0&&de(Ge)})}var A=r(Ke,2),G=a(A);rt(G,{kind:"write",get label(){return e(I)},get disabled(){return e(ie)},onrun:()=>{ne()}}),v(()=>i(Dt,`${e(K)??""} `)),l(Qe,_t)};h(ut,Qe=>{e(he)?e(Z).length===0?Qe(Le,1):Qe(st,-1):Qe(pt)})}v(()=>i(Pe,`Fichier lu : ${e(xe)??""}`)),l(J,be)};h(Se,J=>{e(ge)!==null&&J(Je)})}v(()=>{ce=Gt(ye,1,"choose svelte-18wbxwu",null,ce,{working:e(L)==="import",off:e(ie)}),i(W,`${e(L)==="import"?"Lecture du fichier…":"Importer un fichier"} `),Ue.disabled=e(ie)}),et("change",Ue,J=>{Ne(J.currentTarget.files?.item(0))}),l(d,y)},$$slots:{default:!0}});var ue=r(S,2);tt(ue,{title:"Cinq versions restaurables",note:"Chaque enregistrement fait tourner les versions : la plus récente est la 1.",children:(d,q)=>{var y=Ut(),X=ae(y);{var te=W=>{var Ue=Ti();l(W,Ue)},Te=W=>{var Ue=Ci();l(W,Ue)},ye=W=>{var Ue=Oi();l(W,Ue)},ce=W=>{var Ue=Ai(),Fe=ae(Ue),ke=a(Fe),Se=r(Fe,2),Je=a(Se);at(Je,21,()=>e(O),J=>J.version,(J,be)=>{var fe=Ri(),Pe=a(fe),Ie=a(Pe),nt=r(Pe,2),ut=a(nt),pt=r(nt,2),Le=a(pt),st=r(pt,2);{let Qe=o(()=>e(L)===`restore-${String(e(be).version)}`);rt(st,{kind:"destructive",label:"Remettre cette version en service",protected:!0,get busy(){return e(Qe)},get disabled(){return e(ie)},onrun:()=>{oe(e(be).version)}})}v((Qe,_t)=>{i(Ie,`version ${Qe??""}`),i(ut,_t),i(Le,e(be).config_fingerprint)},[()=>ve(e(be).version),()=>Yt(e(be).modified_at)]),l(J,fe)}),v(()=>i(ke,`${e(b)??""} Le poste n’en garde jamais plus de cinq.`)),l(W,Ue)};h(X,W=>{e(B)==="reading"?W(te):e(B)==="unreadable"?W(Te,1):e(P).length===0?W(ye,2):W(ce,-1)})}l(d,y)},$$slots:{default:!0}}),l(n,g),At()}Ot(["change"]);var $i=u('clé'),Di=u(' '),Ui=u('');function Jt(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"hint",3,""),m=vt(t,"disabled",3,!1),k=vt(t,"engaged",3,!1),R=vt(t,"busy",3,!1),M=vt(t,"protected",3,!1);var P=Ui();let B;var $=a(P),le=a($),ge=r(le);{var xe=D=>{var ie=$i();l(D,ie)};h(ge,D=>{M()&&D(xe)})}var pe=r($,2);{var L=D=>{var ie=Di(),Z=a(ie);v(()=>i(Z,R()?"En cours…":c())),l(D,ie)};h(pe,D=>{c()!==""&&D(L)})}v(()=>{B=Gt(P,1,`big touch-target ${s()??""}`,"svelte-15nlt6i",B,{engaged:k(),busy:R()}),Ve(P,"data-kind",s()),P.disabled=m()||R(),i(le,`${t.label??""} `)}),et("click",P,function(...D){t.onrun?.apply(this,D)}),l(n,P)}Ot(["click"]);const Fi="ERR-SCL-09";var Mi=u('

                                                                  '),Bi=u('

                                                                  '),Vi=u('

                                                                  Déposez ici le fichier clé

                                                                  '),Wi=u('

                                                                  ',1),Gi=u('');function Hi(n,t){Rt(t,!0);const s=vt(t,"admin",7);let c=N("");const m=o(()=>t.health.state.degraded?.code===Fi),k=o(()=>t.health.printing),R=o(()=>e(k)?.on_fallback===!0);let M=N(!1),P=N("");const B=o(()=>s().busy||e(P)!=="");async function $(E,re){p(c,""),p(P,E,!0);try{await s().run(re)}finally{p(P,"")}}async function le(E,re){p(c,""),p(P,E,!0);try{const _=await s().load(re);_!==null&&p(c,_.message,!0)}finally{p(P,"")}}async function ge(E,re){p(c,""),p(P,E,!0);try{const _=await s().protect(re);_!==null&&(s().notice=_.message,await s().refresh())}finally{p(P,"")}}async function xe(E){if(p(M,!1),E!=null){p(c,""),p(P,"import");try{const re=await s().protect(()=>Wa(E));if(re===null)return;p(c,re.result==="rejected"||re.result==="failed"?`${E.name} : REFUSÉ${re.reason===""?"":" — "+re.reason}. Le catalogue en service n’a pas changé.`:`${E.name} : ${String(re.rows_read_count)} lignes lues, ${String(re.weighable_count)} pesables. La veille l’appliquera dans la seconde.`,!0),await s().refresh()}finally{p(P,"")}}}var pe=Gi(),L=a(pe);{var D=E=>{var re=Mi(),_=a(re);v(()=>i(_,e(c))),l(E,re)};h(L,E=>{e(c)!==""&&E(D)})}var ie=r(L,2);{var Z=E=>{var re=Bi(),_=a(re);v(()=>i(_,e(k).banner)),l(E,re)};h(ie,E=>{e(k)!==null&&e(k).banner!==""&&E(Z)})}var he=r(ie,2),H=a(he);{let E=o(()=>e(P)==="scale");Jt(H,{label:"Tester la balance",hint:"Ce que le poste a déjà observé — le port n’est pas rouvert.",get busy(){return e(E)},get disabled(){return e(B)},onrun:()=>{le("scale",Sr)}})}var ee=r(H,2);{let E=o(()=>e(P)==="printer");Jt(ee,{label:"Tester l’imprimante",hint:"Ce que le superviseur a vu il y a moins d’une seconde.",get busy(){return e(E)},get disabled(){return e(B)},onrun:()=>{le("printer",Lr)}})}var j=r(ee,2);{let E=o(()=>e(P)==="label");Jt(j,{label:"Imprimer une étiquette de test",hint:"Une étiquette de démonstration sort de l’imprimante du poste.",get busy(){return e(E)},get disabled(){return e(B)},onrun:()=>{$("label",Er)}})}var O=r(j,2);{let E=o(()=>e(P)==="reprint");Jt(O,{label:"Réimprimer la dernière",hint:"La dernière étiquette imprimée sort une seconde fois.",get busy(){return e(E)},get disabled(){return e(B)},onrun:()=>{$("reprint",wr)}})}var K=r(O,2);{let E=o(()=>e(P)==="reload");Jt(K,{label:"Recharger le catalogue",kind:"write",hint:"La veille refait tout de suite le contrôle qu’elle fait toutes les cinq secondes.",get busy(){return e(E)},get disabled(){return e(B)},onrun:()=>{$("reload",yr)}})}var T=r(K,2);{let E=o(()=>e(m)?"Revenir à la balance":"Basculer en saisie manuelle"),re=o(()=>e(m)?"Le poids sera de nouveau lu sur la balance.":"Le poids se tape à la main : le poste continue de servir sans balance."),_=o(()=>e(P)==="manual");Jt(T,{get label(){return e(E)},kind:"destructive",get hint(){return e(re)},get engaged(){return e(m)},protected:!0,get busy(){return e(_)},get disabled(){return e(B)},onrun:()=>{ge("manual",()=>xr(!e(m)))}})}var b=r(T,2);{let E=o(()=>e(P)==="roll");Jt(b,{label:"J’ai changé le rouleau",kind:"write",hint:"Le compteur d’étiquettes repart à zéro. C’est le seul geste qui dise quelque chose de vrai du papier.",get busy(){return e(E)},get disabled(){return e(B)},onrun:()=>{$("roll",kr)}})}var I=r(b,2);{var Y=E=>{{let re=o(()=>e(R)?"Revenir à l’imprimante du poste":"Imprimer sur l’imprimante du poste voisin"),_=o(()=>e(R)?"Les étiquettes repartiront sur l’imprimante de ce poste.":"Les étiquettes sortiront sur l’imprimante voisine, pour cette session seulement."),V=o(()=>e(P)==="fallback");Jt(E,{get label(){return e(re)},kind:"write",get hint(){return e(_)},get engaged(){return e(R)},get busy(){return e(V)},get disabled(){return e(B)},onrun:()=>{$("fallback",()=>qr(!e(R)))}})}};h(I,E=>{e(k)!==null&&e(k).fallback_available&&E(Y)})}var ze=r(I,2),Ae=r(he,2);tt(Ae,{title:"Importer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(E,re)=>{var _=Vi();let V;var F=a(_),Q=r(a(F)),qe=a(Q),Ne=r(F,2),C=r(a(Ne));v(()=>{V=Gt(_,1,"drop svelte-1yd0nzg",null,V,{dropping:e(M)}),i(qe,`flv_${t.health.station??""}.csv`)}),Xt("dragover",_,ne=>{ne.preventDefault(),p(M,!0)}),Xt("dragleave",_,()=>p(M,!1)),Xt("drop",_,ne=>{ne.preventDefault(),xe(ne.dataTransfer?.files.item(0))}),et("change",C,ne=>{xe(ne.currentTarget.files?.item(0))}),l(E,_)},$$slots:{default:!0}});var Ye=r(Ae,2);{var We=E=>{tt(E,{title:"Le dernier fichier a été refusé",children:(re,_)=>{var V=Wi(),F=ae(V),Q=a(F),qe=r(F,2),Ne=a(qe);v(C=>{i(Q,`${(t.health.catalog.reason===""?"Aucun motif enregistré.":t.health.catalog.reason)??""} - Le catalogue en service n’a pas changé.`),i(Ne,`Dernier essai : ${C??""}.`)},[()=>Yt(t.health.catalog.occurred_at)]),l(re,V)},$$slots:{default:!0}})};h(Ye,E=>{t.health.catalog!==null&&t.health.catalog.result==="rejected"&&E(We)})}v(()=>Ve(ze,"href",zr)),l(n,pe),At()}Ot(["change"]);var Ji=u(''),Ki=u('

                                                                  ',1),Xi=u('

                                                                  ',1),Yi=u(''),Qi=u(''),Zi=u(''),eo=u(''),to=u(' '),ao=u(''),ro=u('

                                                                  Lecture de l’état du poste…

                                                                  '),no=u('

                                                                  '),so=u(" "),lo=u("
                                                                • "),io=u('
                                                                    '),oo=u('
                                                                    '),uo=u('

                                                                    ');function co(n,t){Rt(t,!0);const s=new un,c=new Xr(s),m=[{title:"Au quotidien",pages:[{id:"dashboard",label:"Tableau de bord"},{id:"troubleshooting",label:"Dépannage"}]},{title:"Réglages",pages:[{id:"hardware",label:"Matériel"},{id:"label",label:"Étiquette"},{id:"rules",label:"Règles"},{id:"catalog",label:"Catalogue"},{id:"journal",label:"Journal"},{id:"station",label:"Poste"}]}],k=o(()=>m.flatMap(g=>g.pages).find(g=>g.id===s.page)?.label??""),R=o(()=>s.health===null||s.health.station_name===""?`Poste ${String(s.health?.station??"")}`:s.health.station_name),M=o(()=>(c.pending?.changed_blocks??[]).map(g=>en(g)).join(", ")),P=o(()=>(c.pending?.changed_blocks??[]).join(", "));ka(()=>(s.start(),()=>s.stop()));async function B(g){s.open(g),Ca(g)&&c.config===null&&await c.load()}async function $(){await s.protect(()=>c.save())===!0&&(s.notice="La configuration est enregistrée et appliquée.")}var le=uo(),ge=a(le),xe=r(a(ge),2);at(xe,17,()=>m,g=>g.title,(g,x)=>{var S=Ki(),ue=ae(S),d=a(ue),q=r(ue,2);at(q,17,()=>e(x).pages,y=>y.id,(y,X)=>{var te=Ji();let Te;var ye=a(te);v(()=>{Te=Gt(te,1,"entry svelte-9b2mjq",null,Te,{current:s.page===e(X).id}),Ve(te,"aria-current",s.page===e(X).id?"page":void 0),i(ye,e(X).label)}),et("click",te,()=>{B(e(X).id)}),l(y,te)}),v(()=>i(d,e(x).title)),l(g,S)});var pe=r(xe,2),L=a(pe);{var D=g=>{var x=Xi(),S=ae(x),ue=a(S),d=r(S,2),q=a(d),y=r(d,2),X=a(y);v(()=>{i(ue,e(R)),i(q,`${s.health.coop??""} · version ${s.health.version??""}`),i(X,`configuration ${s.health.config_fingerprint??""}`)}),l(g,x)};h(L,g=>{s.health!==null&&g(D)})}var ie=r(L,2),Z=a(ie),he=r(ie,2);{var H=g=>{var x=Yi();et("click",x,function(...S){t.onclose?.apply(this,S)}),l(g,x)};h(he,g=>{t.onclose!==void 0&&g(H)})}var ee=r(ge,2),j=a(ee);{var O=g=>{var x=Qi(),S=a(x);v(()=>i(S,s.notice)),l(g,x)};h(j,g=>{s.notice!==""&&g(O)})}var K=r(j,2);{var T=g=>{var x=Zi(),S=a(x);v(()=>i(S,s.linkError)),l(g,x)};h(K,g=>{s.linkError!==""&&g(T)})}var b=r(K,2);{var I=g=>{var x=eo(),S=a(x);v(()=>i(S,s.actionError)),l(g,x)};h(b,g=>{s.actionError!==""&&g(I)})}var Y=r(b,2);{var ze=g=>{var x=ao(),S=a(x),ue=a(S),d=r(ue);{var q=te=>{var Te=to(),ye=a(Te);v(()=>i(ye,e(P))),l(te,Te)};h(d,te=>{Wt.showTechnicalNames&&te(q)})}var y=r(d),X=r(S,2);rt(X,{kind:"write",label:"Tout fonctionne : confirmer",onrun:()=>{s.protect(()=>c.confirm())}}),v(()=>{i(ue,`Configuration appliquée mais NON CONFIRMÉE. Ce qui a changé : ${e(M)??""}. `),i(y,` Le poste reviendra tout seul à la version précédente dans - ${c.pending.seconds_left??""} secondes si personne ne confirme.`)}),l(g,x)};h(Y,g=>{c.pending!==null&&g(ze)})}var Ae=r(Y,2),Ye=a(Ae),We=a(Ye),E=r(Ye,2);{var re=g=>{var x=ro();l(g,x)},_=g=>{al(g,{get health(){return s.health},onshowrows:()=>{B("catalog")}})},V=g=>{Hi(g,{get admin(){return s},get health(){return s.health}})},F=g=>{fl(g,{get admin(){return s},get draft(){return c},get health(){return s.health}})},Q=g=>{Ml(g,{get admin(){return s},get draft(){return c}})},qe=g=>{hi(g,{get draft(){return c},get health(){return s.health}})},Ne=g=>{hs(g,{get admin(){return s},get draft(){return c},get health(){return s.health}})},C=g=>{Ol(g,{get admin(){return s}})},ne=g=>{Ii(g,{get admin(){return s},get draft(){return c},get health(){return s.health}})};h(E,g=>{s.health===null?g(re):s.page==="dashboard"?g(_,1):s.page==="troubleshooting"?g(V,2):s.page==="hardware"?g(F,3):s.page==="label"?g(Q,4):s.page==="rules"?g(qe,5):s.page==="catalog"?g(Ne,6):s.page==="journal"?g(C,7):s.page==="station"&&g(ne,8)})}var oe=r(Ae,2);{var _e=g=>{var x=oo(),S=a(x);{var ue=X=>{var te=no(),Te=a(te),ye=r(Te);at(ye,16,()=>c.retired,ce=>ce,(ce,W)=>{{let Ue=o(()=>`retirer ${W}`);rt(ce,{kind:"write",get label(){return e(Ue)},onrun:()=>c.dropRetired(W)})}}),v(ce=>i(Te,`Ce fichier porte des réglages que cette version du poste ne connaît plus : - ${ce??""}. `),[()=>c.retired.map(ce=>Ft(ce)).join(", ")]),l(X,te)};h(S,X=>{c.retired.length>0&&X(ue)})}var d=r(S,2);{var q=X=>{var te=io();at(te,21,()=>c.faults,Te=>Te.field,(Te,ye)=>{var ce=lo(),W=a(ce),Ue=a(W),Fe=r(W,2);{var ke=be=>{var fe=so(),Pe=a(fe);v(()=>i(Pe,e(ye).field)),l(be,fe)};h(Fe,be=>{Wt.showTechnicalNames&&be(ke)})}var Se=r(Fe),Je=r(Se);{var J=be=>{var fe=qt();v(Pe=>i(fe,`— valeurs acceptées : ${Pe??""}`),[()=>e(ye).allowed.join(", ")]),l(be,fe)};h(Je,be=>{e(ye).allowed!==void 0&&e(ye).allowed.length>0&&be(J)})}v(be=>{i(Ue,be),i(Se,` ${e(ye).message??""} `)},[()=>Ft(e(ye).field)]),l(Te,ce)}),l(X,te)};h(d,X=>{c.faults.length>0&&X(q)})}var y=r(d,2);{let X=o(()=>c.dirty?"Enregistrer la configuration":"Aucune modification à enregistrer"),te=o(()=>!c.dirty||s.busy);rt(y,{kind:"write",get label(){return e(X)},get disabled(){return e(te)},onrun:()=>{$()}})}l(g,x)},me=o(()=>Ca(s.page)&&c.config!==null);h(oe,g=>{e(me)&&g(_e)})}var He=r(ee,2);{var Xe=g=>{mn(g,{get admin(){return s}})};h(He,g=>{s.pending!==null&&g(Xe)})}v(()=>{oa(Z,Wt.showTechnicalNames),i(We,e(k))}),et("change",Z,()=>Wt.toggleTechnicalNames()),l(n,le),At()}Ot(["click","change"]);let ia=null;function fo(n){if(ia!==null||n.querySelector("[data-admin]")!==null)return;const t=document.createElement("div");n.appendChild(t),ia={component:pr(co,{target:t,props:{onclose:vo}}),host:t}}function vo(){if(ia===null)return;const{component:n,host:t}=ia;ia=null,fr(n),t.remove()}export{vo as closeAdmin,fo as mountAdmin}; diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html index 7ab563e..036103f 100644 --- a/internal/web/dist/index.html +++ b/internal/web/dist/index.html @@ -6,7 +6,7 @@ Pesée - + diff --git a/web/src/admin/App.svelte b/web/src/admin/App.svelte index 1f6499f..d124ddd 100644 --- a/web/src/admin/App.svelte +++ b/web/src/admin/App.svelte @@ -14,6 +14,7 @@ import Rules from './pages/Rules.svelte' import Station from './pages/Station.svelte' import Troubleshooting from './pages/Troubleshooting.svelte' + import Update from './pages/Update.svelte' /** * L'écran d'administration, et ses DEUX NIVEAUX explicites (§14.4). @@ -63,6 +64,7 @@ { id: 'catalog', label: 'Catalogue' }, { id: 'journal', label: 'Journal' }, { id: 'station', label: 'Poste' }, + { id: 'update', label: 'Mise à jour' }, ], }, ] @@ -229,6 +231,8 @@ {:else if admin.page === 'station'} + {:else if admin.page === 'update'} + {/if} diff --git a/web/src/admin/lib/api.ts b/web/src/admin/lib/api.ts index 0b51d30..3410c32 100644 --- a/web/src/admin/lib/api.ts +++ b/web/src/admin/lib/api.ts @@ -12,6 +12,7 @@ import type { ProblemDTO, SessionDTO, TechnicalLineDTO, + UpdateDTO, WeighingDTO, } from './dto' @@ -371,3 +372,34 @@ function parseProblem(raw: string): ProblemDTO | null { return null } } + +// --- La page Mise à jour ---------------------------------------------------- + +/** Lit l'état des mises à jour. Libre : les pages de réglage s'ouvrent en lecture. */ +export function fetchUpdate(): Promise { + return getJSON('/admin/api/update') +} + +/** + * « Vérifier maintenant », sans attendre le tour quotidien. + * + * Rend l'état FRAIS et non un accusé : la page n'a rien à relire derrière. + */ +export function checkForUpdate(): Promise { + return postJSON('/admin/api/update/check', {}) +} + +/** + * Installe la version NOMMÉE. + * + * Le numéro voyage dans le corps parce que c'est celui que l'écran montrait : entre le + * dessin de la page et l'appui, une autre version a pu paraître, et l'installer sans rien + * dire serait installer ce que personne n'a lu. Le service répond alors 409. + * + * La réponse est un 202 : la bascule a commencé, et le poste qui l'a acceptée est sur le + * point d'être arrêté par elle. Il n'y aura pas de seconde réponse sur cette connexion — + * c'est l'écran qui va sonder jusqu'au retour. + */ +export function applyUpdate(version: string): Promise<{ version: string }> { + return postJSON<{ version: string }>('/admin/api/update/apply', { version }) +} diff --git a/web/src/admin/lib/dto.ts b/web/src/admin/lib/dto.ts index 8fb5ab7..78fba66 100644 --- a/web/src/admin/lib/dto.ts +++ b/web/src/admin/lib/dto.ts @@ -255,3 +255,46 @@ export interface SessionDTO { */ warning?: string } + +/** + * Les quatre issues d'une bascule, telles qu'`update.ps1` les écrit. + * + * Quatre et non deux : « annulée, le poste fonctionne » n'appelle personne, « le poste ne + * répond pas » demande quelqu'un tout de suite, et « rien n'a été remplacé » veut dire + * qu'on peut recommencer. + */ +export type UpdateStatus = + | 'succeeded' + | 'rolled-back' + | 'rolled-back-unhealthy' + | 'not-started' + +/** La dernière bascule tentée par ce poste. */ +export interface UpdateOutcomeDTO { + status: UpdateStatus + from: string + to: string + reason: string + finished_at: string +} + +/** Ce que la page Mise à jour dessine. */ +export interface UpdateDTO { + /** La version qui tourne en ce moment. */ + running: string + /** Le dépôt suivi, sous la forme propriétaire/projet. */ + repository: string + /** Faux là où la bascule n'existe pas : la page le dit au lieu d'un bouton mort. */ + supported: boolean + /** Vrai quand la version publiée est plus récente que celle qui tourne. */ + available: boolean + latest: string + published_at: string + html_url: string + checked_at: string + /** + * `null` et non un objet vide : « aucune bascule n'a jamais été tentée » et « une + * bascule a été tentée et n'a rien fait » sont deux phrases différentes à l'écran. + */ + outcome: UpdateOutcomeDTO | null +} diff --git a/web/src/admin/lib/session.svelte.ts b/web/src/admin/lib/session.svelte.ts index f822207..d43af61 100644 --- a/web/src/admin/lib/session.svelte.ts +++ b/web/src/admin/lib/session.svelte.ts @@ -5,7 +5,7 @@ import type { FaultDTO, HealthDTO } from './dto' /** À quelle cadence le tableau de bord se relit, en millisecondes. */ const REFRESH_PERIOD_MS = 3000 -/** Les pages de l'administration : deux bénévoles, six expertes (§14.4). */ +/** Les pages de l'administration : deux bénévoles, sept expertes (§14.4). */ export type PageID = | 'dashboard' | 'troubleshooting' @@ -15,6 +15,7 @@ export type PageID = | 'catalog' | 'journal' | 'station' + | 'update' /** Les deux pages ouvertes par défaut, SANS mot de passe (ADR-018, important-10). */ export const VOLUNTEER_PAGES: PageID[] = ['dashboard', 'troubleshooting'] @@ -28,7 +29,15 @@ export const VOLUNTEER_PAGES: PageID[] = ['dashboard', 'troubleshooting'] * débrancher l'imprimante — un mot de passe n'ajouterait là aucune sécurité et * supprimerait tout le dépannage. */ -export const EXPERT_PAGES: PageID[] = ['hardware', 'label', 'rules', 'catalog', 'journal', 'station'] +export const EXPERT_PAGES: PageID[] = [ + 'hardware', + 'label', + 'rules', + 'catalog', + 'journal', + 'station', + 'update', +] /** Vrai quand la page nommée demande une session ouverte. */ export function needsPassword(page: PageID): boolean { diff --git a/web/src/admin/pages/Update.svelte b/web/src/admin/pages/Update.svelte new file mode 100644 index 0000000..a27fd42 --- /dev/null +++ b/web/src/admin/pages/Update.svelte @@ -0,0 +1,262 @@ + + + + + + {#if state === null} +

                                                                    {readFailure === '' ? 'Lecture…' : readFailure}

                                                                    + {:else} + {#if readFailure !== ''} +

                                                                    {readFailure}

                                                                    + {/if} +
                                                                    +
                                                                    Version installée
                                                                    +
                                                                    {state.running}
                                                                    +
                                                                    Dépôt suivi
                                                                    +
                                                                    {state.repository}
                                                                    + {#if state.checked_at !== ''} +
                                                                    Dernière vérification
                                                                    +
                                                                    {frenchDateTime(state.checked_at)}
                                                                    + {/if} +
                                                                    + + {#if !state.supported} +

                                                                    + La mise à jour depuis cet écran n’existe que sur les postes Windows. Sur les + autres, elle se fait à la main — voir la notice d’installation. +

                                                                    + {:else if state.available} +

                                                                    + Version disponible : {state.latest} + {#if state.published_at !== ''}, publiée le {frenchDateTime(state.published_at)}{/if}. + {#if state.html_url !== ''} + + Voir les nouveautés + + {/if} +

                                                                    + {:else} +

                                                                    Ce poste est à jour.

                                                                    + {/if} + {/if} +
                                                                    + +{#if state !== null && state.supported} + +
                                                                    + void check()} + /> + {#if canInstall} + void install(state.latest)} + /> + {/if} +
                                                                    + + {#if canInstall} +

                                                                    + Le poste va s’arrêter environ une minute. L’écran client s’éteindra puis reviendra + tout seul. Si la nouvelle version ne démarre pas, la précédente sera remise + automatiquement — mais les données enregistrées, elles, ne reviendront pas en + arrière. +

                                                                    + {/if} + + {#if switching} +

                                                                    Mise à jour en cours. Le poste redémarre, ne le débranchez pas.

                                                                    + {/if} + {#if switchReport !== ''} +

                                                                    {switchReport}

                                                                    + {/if} +
                                                                    +{/if} + +{#if outcome !== null} + +

                                                                    {OUTCOMES[outcome.status]}

                                                                    +
                                                                    +
                                                                    Depuis
                                                                    +
                                                                    {outcome.from === '' ? '—' : outcome.from}
                                                                    +
                                                                    Vers
                                                                    +
                                                                    {outcome.to === '' ? '—' : outcome.to}
                                                                    + {#if outcome.finished_at !== ''} +
                                                                    Terminée le
                                                                    +
                                                                    {frenchDateTime(outcome.finished_at)}
                                                                    + {/if} + {#if outcome.reason !== ''} +
                                                                    Raison
                                                                    +
                                                                    {outcome.reason}
                                                                    + {/if} +
                                                                    +
                                                                    +{/if} + + diff --git a/web/test/admin-update.test.ts b/web/test/admin-update.test.ts new file mode 100644 index 0000000..fcace01 --- /dev/null +++ b/web/test/admin-update.test.ts @@ -0,0 +1,157 @@ +import { flushSync, mount, unmount } from 'svelte' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import Update from '../src/admin/pages/Update.svelte' +import type { UpdateDTO } from '../src/admin/lib/dto' +import { Admin } from '../src/admin/lib/session.svelte' +import * as api from '../src/admin/lib/api' + +/** + * La page Mise à jour, et les cinq choses qu'elle doit tenir. + * + * 1. le bouton NOMME la version qu'il installera — c'est ce que le service exige dans le + * corps de la demande, et le refus 409 existe pour que le bénévole valide ce qu'il a + * lu et non ce qui est arrivé depuis ; + * 2. l'acte est IRRÉVERSIBLE au sens d'ADR-037 : le retour arrière est automatique sur le + * binaire, il ne l'est pas sur la base ; + * 3. les quatre issues d'update.ps1 se disent en quatre phrases différentes — « annulée, + * le poste marche » et « le poste ne répond pas » ne demandent pas la même chose ; + * 4. un poste où la mise à jour n'existe pas le DIT, au lieu de montrer un bouton mort ; + * 5. aucun renvoi §X.Y ni ADR-0XX n'est visible. + */ + +/** Un poste sur 2.0.3 dont le dépôt publie 2.1.0. */ +const NOMINAL: UpdateDTO = { + running: '2.0.3', + repository: 'lostmind84/OpenScale', + supported: true, + available: true, + latest: '2.1.0', + published_at: '2026-07-28T09:14:22Z', + html_url: 'https://github.com/lostmind84/OpenScale/releases/tag/2.1.0', + checked_at: '2026-07-29T08:12:00Z', + outcome: null, +} + +let host: HTMLElement + +beforeEach(() => { + host = document.createElement('div') + document.body.appendChild(host) +}) + +afterEach(() => { + host.remove() + vi.restoreAllMocks() +}) + +/** Monte la page et attend qu'elle ait lu son état. */ +async function show(state: UpdateDTO): Promise<() => void> { + vi.spyOn(api, 'fetchUpdate').mockResolvedValue(state) + const admin = new Admin() + const page = mount(Update, { target: host, props: { admin } }) + await vi.waitFor(() => expect(host.textContent).toContain(state.running)) + flushSync() + return () => unmount(page) +} + +describe('la page Mise à jour', () => { + it('nomme la version que le bouton installera', async () => { + const close = await show(NOMINAL) + const button = host.querySelector('[data-act="apply"]') + expect(button).not.toBeNull() + expect(button?.textContent).toContain('2.1.0') + close() + }) + + it('traite l’installation comme un acte qui ne se défait pas', async () => { + const close = await show(NOMINAL) + const button = host.querySelector('[data-act="apply"]') + // Le retour arrière est automatique sur le binaire, jamais sur la base : les + // migrations s’appliquent au démarrage et y revenir est un geste humain. + expect(button?.dataset.kind).toBe('destructive') + close() + }) + + it('affiche la version installée et le dépôt suivi', async () => { + const close = await show(NOMINAL) + expect(host.textContent).toContain('2.0.3') + expect(host.textContent).toContain('lostmind84/OpenScale') + close() + }) + + it('ne propose rien à un poste déjà à jour', async () => { + const close = await show({ ...NOMINAL, available: false, latest: '2.0.3' }) + expect(host.querySelector('[data-act="apply"]')).toBeNull() + expect(host.textContent).toContain('2.0.3') + close() + }) + + it('dit qu’elle n’existe pas plutôt que de montrer un bouton mort', async () => { + const close = await show({ ...NOMINAL, supported: false, available: false }) + expect(host.querySelector('[data-act="apply"]')).toBeNull() + // La version installée reste lisible : c’est la première chose qu’on demande au + // téléphone, et elle ne dépend pas de la mise à jour. + expect(host.textContent).toContain('2.0.3') + close() + }) + + it('dit les quatre issues en quatre phrases différentes', async () => { + const said = new Set() + for (const status of [ + 'succeeded', + 'rolled-back', + 'rolled-back-unhealthy', + 'not-started', + ] as const) { + const close = await show({ + ...NOMINAL, + outcome: { + status, + from: '2.0.3', + to: '2.1.0', + reason: 'le poste ne répond pas', + finished_at: '2026-07-29T10:16:04Z', + }, + }) + const report = host.querySelector('[data-outcome]')?.textContent ?? '' + expect(report).not.toBe('') + said.add(report) + close() + host.replaceChildren() + } + expect(said.size).toBe(4) + }) + + it('n’annonce aucune tentative sur un poste qui n’a jamais basculé', async () => { + const close = await show(NOMINAL) + expect(host.querySelector('[data-outcome]')).toBeNull() + close() + }) + + it('ne montre aucun renvoi de dossier', async () => { + const close = await show({ + ...NOMINAL, + outcome: { + status: 'rolled-back', + from: '2.0.3', + to: '2.1.0', + reason: 'le poste ne répond pas', + finished_at: '2026-07-29T10:16:04Z', + }, + }) + expect(host.textContent).not.toMatch(/§\s*\d/) + expect(host.textContent).not.toMatch(/ADR-\d/) + close() + }) + + it('survit à une lecture qui échoue, sans page blanche', async () => { + vi.spyOn(api, 'fetchUpdate').mockRejectedValue(new Error('service injoignable')) + const admin = new Admin() + const page = mount(Update, { target: host, props: { admin } }) + // Attendre « du texte » ne prouve rien : « Lecture… » en est, et il est là avant + // que la promesse rejetée ne soit arrivée. C'est la PHRASE qu'on attend. + await vi.waitFor(() => expect(host.textContent).toContain('injoignable')) + flushSync() + unmount(page) + }) +}) From 9807a80144b3763c23845ba362a4c23f3199d2ce Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 12:48:12 +0200 Subject: [PATCH 16/19] feat(admin): le tableau de bord signale une version disponible Du TEXTE et non un feu. Les six feux restent ceux des peripheriques et des ressources : un poste parfaitement sain qui s'allumerait en orange parce qu'un correctif est sorti apprendrait aux benevoles a ignorer l'orange. Deux tests de garde-fou existants ont impose la conception, et ils avaient raison tous les deux. admin-two-levels tient la page benevole a UNE SEULE route : la disponibilite voyage donc dans /admin/api/health, avec tout le reste de ce que cette page lit, plutot que dans un second appel qui aurait elargi pour une courtoisie ce que fait un ecran ouvert sans mot de passe. Et tokens n'inventorie que --ink et --ink-muted comme texte : le lien est SOULIGNE et non colore, les deux fonds pleins n'ayant ete mesures que comme fonds. Et une panique, trouvee par la suite : le piege du NIL TYPE. newUpdateService rend nil sur un binaire "dev", et l'affecter a une interface produit une interface QUI N'EST PAS NULLE -- la garde `s.updater == nil` repond faux, la methode est appelee sur un recepteur nul, et la premiere lecture de champ panique. Trois secondes plus tard le sondage du tableau de bord recommence. C'etait le poste de chaque developpeur, et c'aurait ete celui de tout le monde jusqu'a la premiere version taguee. updaterFor rend une interface nulle, et un test de non-regression interdit le retour du piege sur les deux interfaces concernees. 686 tests front, suite Go complete verte en -race. Budget client inchange a 70,3 % : cette page est dans le paquet d'administration. --- cmd/openscale/serve.go | 2 +- cmd/openscale/update.go | 20 ++++ cmd/openscale/update_test.go | 51 ++++++++++ internal/web/dist/admin.html | 6 +- internal/web/dist/assets/admin-BoDRoAHx.js | 1 - internal/web/dist/assets/admin-BwNEGBuC.js | 1 + .../{index-Bn8FyGo6.js => index-CJPg-VaQ.js} | 4 +- internal/web/dist/assets/mount-2bHHy8nk.js | 93 ------------------- internal/web/dist/assets/mount-BCv4PVId.css | 1 - internal/web/dist/assets/mount-BdIa8cb_.js | 93 +++++++++++++++++++ internal/web/dist/assets/mount-C1tpIbdA.css | 1 + internal/web/dist/index.html | 2 +- internal/web/health.go | 12 +++ internal/web/update.go | 20 ++++ web/src/admin/App.svelte | 6 +- web/src/admin/lib/dto.ts | 9 ++ web/src/admin/pages/Dashboard.svelte | 47 +++++++++- web/test/admin-dashboard.test.ts | 24 +++++ web/test/fixtures/health.ts | 2 + 19 files changed, 291 insertions(+), 104 deletions(-) create mode 100644 cmd/openscale/update_test.go delete mode 100644 internal/web/dist/assets/admin-BoDRoAHx.js create mode 100644 internal/web/dist/assets/admin-BwNEGBuC.js rename internal/web/dist/assets/{index-Bn8FyGo6.js => index-CJPg-VaQ.js} (99%) delete mode 100644 internal/web/dist/assets/mount-2bHHy8nk.js delete mode 100644 internal/web/dist/assets/mount-BCv4PVId.css create mode 100644 internal/web/dist/assets/mount-BdIa8cb_.js create mode 100644 internal/web/dist/assets/mount-C1tpIbdA.css diff --git a/cmd/openscale/serve.go b/cmd/openscale/serve.go index ca2639e..8068cd7 100644 --- a/cmd/openscale/serve.go +++ b/cmd/openscale/serve.go @@ -459,7 +459,7 @@ func serve(ctx context.Context, o serveOptions, out io.Writer) error { printer: live, catalog: liveSource, machine: diag.NewMachine(clock), dataDir: o.dataDir, }, - Update: updateService, + Update: updaterFor(updateService), }) if err != nil { _ = binder.Close() diff --git a/cmd/openscale/update.go b/cmd/openscale/update.go index 2048ec4..e626e8a 100644 --- a/cmd/openscale/update.go +++ b/cmd/openscale/update.go @@ -10,6 +10,7 @@ import ( "openscale/internal/station" "openscale/internal/station/ports" "openscale/internal/update" + "openscale/internal/web" ) // newUpdateService wires the update service over the running station. @@ -68,6 +69,25 @@ func newUpdateService(clock ports.Clock, guard update.Guard, dataDir string) *up // releasePlatform is the suffix release.yml gives the archives. func releasePlatform() string { return runtime.GOOS + "-" + runtime.GOARCH } +// updaterFor returns what the HTTP layer should be given for the update routes. +// +// ★ IT RETURNS A NIL INTERFACE, NEVER A TYPED NIL, and that distinction is the +// whole reason this function exists rather than a direct assignment. Putting a nil +// *update.Service into an interface produces an interface that IS NOT nil: the +// `s.updater == nil` guard of every handler answers false, the method is then +// called on a nil receiver, and the first field it reads panics. +// +// Measured, not imagined: a binary whose version is « dev » builds no service, and +// every three-second poll of the dashboard took the whole HTTP connection down +// with it. That is every developer's station, and it would have been every +// station's until the first tagged build. +func updaterFor(service *update.Service) web.Updater { + if service == nil { + return nil + } + return service +} + // updatePoller adapts an update.Service to what the station's daily worker asks. // // It exists so that internal/station names no type of internal/update: the station diff --git a/cmd/openscale/update_test.go b/cmd/openscale/update_test.go new file mode 100644 index 0000000..6899ba4 --- /dev/null +++ b/cmd/openscale/update_test.go @@ -0,0 +1,51 @@ +package main + +import ( + "testing" + + "openscale/internal/station" + "openscale/internal/update" +) + +// TestANilServiceBecomesANilInterfaceAndNotATypedNil is a non-regression test for +// a panic that took the whole HTTP connection down on every station running a +// development build. +// +// Putting a nil *update.Service into an interface produces an interface that IS +// NOT nil. The `s.updater == nil` guard of every handler then answers false, the +// method is called on a nil receiver, and the first field it reads panics -- three +// seconds later the dashboard poll does it again. +// +// The comparison below looks tautological and is not: `web.Updater(service)` with +// a nil service would pass a `!= nil` test, which is exactly the trap. +func TestANilServiceBecomesANilInterfaceAndNotATypedNil(t *testing.T) { + if updater := updaterFor(nil); updater != nil { + t.Fatalf("un service absent devient une interface non nulle (%T) : "+ + "la garde des gestionnaires ne le verra pas et ils paniqueront", updater) + } + if poller := newUpdatePoller(nil); poller != nil { + t.Fatalf("un service absent devient un Poller non nul (%T) : "+ + "la station lancerait une goroutine qui panique une fois par jour", poller) + } +} + +// TestAServiceThatExistsIsHandedOverIntact. +func TestAServiceThatExistsIsHandedOverIntact(t *testing.T) { + service := &update.Service{} + if updater := updaterFor(service); updater == nil { + t.Fatal("un service présent n'atteint pas la couche HTTP") + } + var poller station.Poller = newUpdatePoller(service) + if poller == nil { + t.Fatal("un service présent ne donne aucun sondage quotidien") + } +} + +// TestADevelopmentBuildBuildsNoService: « dev » is not a version number, and +// comparing it to a release would tell a station it is out of date by an +// arithmetic nobody can defend. +func TestADevelopmentBuildBuildsNoService(t *testing.T) { + if _, err := update.ParseVersion(version); err == nil && version == "dev" { + t.Fatal("« dev » est lu comme un numéro de version") + } +} diff --git a/internal/web/dist/admin.html b/internal/web/dist/admin.html index 8239cdc..dc74262 100644 --- a/internal/web/dist/admin.html +++ b/internal/web/dist/admin.html @@ -5,11 +5,11 @@ Administration - + - + - +
                                                                    diff --git a/internal/web/dist/assets/admin-BoDRoAHx.js b/internal/web/dist/assets/admin-BoDRoAHx.js deleted file mode 100644 index e4776bc..0000000 --- a/internal/web/dist/assets/admin-BoDRoAHx.js +++ /dev/null @@ -1 +0,0 @@ -import"./app-Dn-BjSsJ.js";import{mountAdmin as m}from"./mount-2bHHy8nk.js";m(document.getElementById("app")); diff --git a/internal/web/dist/assets/admin-BwNEGBuC.js b/internal/web/dist/assets/admin-BwNEGBuC.js new file mode 100644 index 0000000..6661017 --- /dev/null +++ b/internal/web/dist/assets/admin-BwNEGBuC.js @@ -0,0 +1 @@ +import"./app-Dn-BjSsJ.js";import{mountAdmin as m}from"./mount-BdIa8cb_.js";m(document.getElementById("app")); diff --git a/internal/web/dist/assets/index-Bn8FyGo6.js b/internal/web/dist/assets/index-CJPg-VaQ.js similarity index 99% rename from internal/web/dist/assets/index-Bn8FyGo6.js rename to internal/web/dist/assets/index-CJPg-VaQ.js index 8b09389..024a32b 100644 --- a/internal/web/dist/assets/index-Bn8FyGo6.js +++ b/internal/web/dist/assets/index-CJPg-VaQ.js @@ -1,2 +1,2 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mount-2bHHy8nk.js","assets/app-Dn-BjSsJ.js","assets/app-DWjtdCrW.css","assets/mount-BCv4PVId.css"])))=>i.map(i=>d[i]); -import{e as Te,u as Ne,c as Re,r as Oe,a as We,D as Be,S as He,d as le,p as ne,b as T,i as B,I as ie,t as N,f as H,g as m,h as ae,j as y,k as t,s as p,l as oe,m as ce,n as te,o as z,q as h,v as u,w as ze,x as ve,y as Fe,z as x,A as D,B as ue,C as Ue,E as Ge,F as Me,G as Ve,H as Xe,J as Ke,K as Ze,L as Je,M as Ye,N as Qe,O as $e,P as et,Q as tt}from"./app-Dn-BjSsJ.js";class be{#e=new WeakMap;#t;#n;static entries=new WeakMap;constructor(e){this.#n=e}observe(e,a){var r=this.#e.get(e)||new Set;return r.add(a),this.#e.set(e,r),this.#r().observe(e,this.#n),()=>{var i=this.#e.get(e);i.delete(a),i.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??(this.#t=new ResizeObserver(e=>{for(var a of e){be.entries.set(a.target,a);for(var r of this.#e.get(a.target)||[])r(a)}}))}}var nt=new be({box:"border-box"});function we(n,e,a){var r=nt.observe(n,()=>a(n[e]));Te(()=>(Ne(()=>a(n[e])),r))}function he(n,e){return n===e||n?.[He]===e}function Ce(n={},e,a,r){var i=Re.r,l=We;return Te(()=>{var d,s;return Oe(()=>{d=s,s=[],Ne(()=>{he(a(...s),n)||(e(n,...s),d&&he(a(...d),n)&&e(null,...d))})}),()=>{let f=l;for(;f!==i&&f.parent!==null&&f.parent.f&Be;)f=f.parent;const v=()=>{s&&he(a(...s),n)&&e(null,...s)},c=f.teardown;f.teardown=()=>{v(),c?.()}}}),n}const at="modulepreload",rt=function(n){return"/"+n},Ee={},st=function(e,a,r){let i=Promise.resolve();if(a&&a.length>0){let f=function(v){return Promise.all(v.map(c=>Promise.resolve(c).then(E=>({status:"fulfilled",value:E}),E=>({status:"rejected",reason:E}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),s=d?.nonce||d?.getAttribute("nonce");i=f(a.map(v=>{if(v=rt(v),v in Ee)return;Ee[v]=!0;const c=v.endsWith(".css"),E=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${v}"]${E}`))return;const A=document.createElement("link");if(A.rel=c?"stylesheet":at,c||(A.as="script"),A.crossOrigin="",A.href=v,s&&A.setAttribute("nonce",s),document.head.appendChild(A),c)return new Promise((w,C)=>{A.addEventListener("load",w),A.addEventListener("error",()=>C(new Error(`Unable to preload CSS for ${v}`)))})}))}function l(d){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=d,window.dispatchEvent(s),!s.defaultPrevented)throw d}return i.then(d=>{for(const s of d||[])s.status==="rejected"&&l(s.reason);return e().catch(l)})};var it=y('

                                                                    kg

                                                                    ',1),ot=y('

                                                                    —kg

                                                                    Poids indisponible

                                                                    ',1),lt=y('');function ct(n,e){ne(e,!0);const a=T(e,"taring",3,!1),r=h(()=>{if(e.linkBanner!=="")return e.linkBanner;const I=e.snapshot?.message??null;if(I!==null)return I.text;switch(e.snapshot?.state){case"product_armed":return"Posez votre produit";case"awaiting_stability":return"Pesée en cours…";case"printing":return"Étiquette en cours…";case"entering_tare":return"Tapez la tare en grammes";case"scale_lost":return"Le poids n’est plus disponible.";default:return"Touchez votre produit, l’étiquette sort"}}),i=h(()=>e.snapshot===null?"var(--waiting)":e.snapshot.state==="faulted"||e.snapshot.state==="scale_lost"?"var(--fault)":e.snapshot.state==="rejected"?"var(--warning)":e.snapshot.state==="succeeded"||e.snapshot.weight.latched?"var(--ready)":"var(--waiting)"),l=h(()=>e.showWeight&&e.snapshot!==null&&e.snapshot.weight.available&&!e.snapshot.weight.expired),d=h(()=>{const I=e.snapshot?.message;return I!=null?I.level:e.snapshot?.state==="faulted"||e.snapshot?.state==="scale_lost"?"error":e.snapshot?.state==="rejected"?"warn":"info"});var s=lt(),f=u(s);let v;var c=u(f);{var E=I=>{var W=it(),F=ze(W),K=u(F),G=p(F,2),Q=u(G);N(()=>{z(K,e.snapshot.weight.net_text),z(Q,`tare ${e.snapshot.weight.tare_g??""} g`)}),m(I,W)},A=I=>{var W=ot();m(I,W)};B(c,I=>{t(l)&&e.snapshot?I(E):I(A,-1)})}var w=p(f,2);let C;var g=u(w),k=p(w,2);let O;var q=u(k);ie(q,{name:"tare",size:"1.4em"});var X=p(k,2);let U;N(()=>{oe(s,"data-state",e.snapshot?.state??"initializing"),v=ce(f,"",v,{"border-left-color":t(i)}),C=te(w,1,"instruction svelte-4gizvm",null,C,{warn:t(d)==="warn",error:t(d)==="error"}),z(g,t(r)),O=te(k,1,"tare-key touch-target svelte-4gizvm",null,O,{active:a()}),U=ce(X,"",U,{background:t(i)})}),H("click",k,function(...I){e.ontare?.apply(this,I)}),m(n,s),ae()}le(["click"]);const ut=4.5,dt=.1,vt={r:138,g:134,b:124};function qe(n){const e=/^#([\da-f])([\da-f])([\da-f])$/iu.exec(n);if(e!==null){const[,d,s,f]=e;return{r:Number.parseInt(d+d,16),g:Number.parseInt(s+s,16),b:Number.parseInt(f+f,16)}}const a=/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/iu.exec(n);if(a===null)return vt;const[,r,i,l]=a;return{r:Number.parseInt(r,16),g:Number.parseInt(i,16),b:Number.parseInt(l,16)}}function pe(n){const e=n/255;return e<=.03928?e/12.92:((e+.055)/1.055)**2.4}function ft({r:n,g:e,b:a}){return .2126*pe(n)+.7152*pe(e)+.0722*pe(a)}function gt(n){return 1.05/(ft(n)+.05)}function je(n){const{r:e,g:a,b:r}=qe(n),i=l=>Math.round(255-dt*(255-l));return`rgb(${i(e)}, ${i(a)}, ${i(r)})`}function De(n){let e=qe(n);for(let a=0;a<16&>(e) '),pt=y('');function _t(n,e){ne(e,!0);var a=pt(),r=u(a);ve(r,21,()=>e.chips,s=>s.code,(s,f)=>{var v=ht();let c,E;var A=p(u(v),2),w=u(A),C=p(A,2),g=u(C);N(k=>{c=te(v,1,"chip touch-target svelte-atgty1",null,c,{active:t(f).code===e.active}),oe(v,"aria-pressed",t(f).code===e.active),E=ce(v,"",E,k),z(w,t(f).label),z(g,t(f).count)},[()=>({"--chip-ink":De(t(f).color),"--chip-wash":je(t(f).color)})]),H("click",v,()=>e.onselect(t(f).code)),m(s,v)});var i=p(r,2);let l;var d=u(i);ie(d,{name:"search",size:"1.75rem"}),N(()=>{l=te(i,1,"chip touch-target search-key svelte-atgty1",null,l,{active:e.searchFieldOpen}),oe(i,"aria-pressed",e.searchFieldOpen)}),H("click",i,function(...s){e.onopensearch?.apply(this,s)}),m(n,a),ae()}le(["click"]);var bt=y(''),mt=y('

                                                                    '),yt=y('

                                                                    ');function ke(n,e){ne(e,!0);const a=T(e,"action",3,null);var r=yt(),i=u(r),l=u(i),d=u(l);ie(d,{name:"alert",size:"3rem"});var s=p(l,2),f=u(s),v=p(s,2),c=u(v),E=p(v,2);{var A=g=>{var k=bt(),O=u(k);N(()=>z(O,a().label)),H("click",k,function(...q){a().run?.apply(this,q)}),m(g,k)};B(E,g=>{a()!==null&&g(A)})}var w=p(E,2);{var C=g=>{var k=mt(),O=u(k);N(()=>z(O,e.code)),m(g,k)};B(w,g=>{e.code!==""&&g(C)})}N(()=>{z(f,e.title),z(c,e.detail)}),m(n,r),ae()}le(["click"]);const fe=34,xe=18,wt=.5,Et=.005,kt=1.15,Le=90,me=100;function xt(n,e){return Math.max(1,Math.floor(e/(n*kt)))}function At(n,e,a,r=Le){const i=St(n,a);if(i.length===0||e<=0)return fe;const l=Pt(a),d=e*(1-Et);for(let s=fe;s>xe;s-=wt){const f=s/me;if(Tt(i,l,f,d)<=xt(s,r))return s}return xe}const It=/(?<=[-‐–—])(?!\d)/u;function St(n,e){const a=[];for(const r of n.split(/\s+/u).filter(i=>i.length>0)){const i=r.split(It).filter(l=>l.length>0);for(const[l,d]of i.entries())a.push({width:e(d),length:[...d].length,spaced:l===0})}return a}function Pt(n){return Math.max(0,n("a a")-n("aa"))}function Tt(n,e,a,r){let i=1,l=0;for(const d of n){const s=d.width*a,f=d.spaced?e*a:0;if(l>0&&l+f+s<=r){l+=f+s;continue}if(l>0&&(i++,l=0),s<=r){l=s;continue}i+=Nt(s,d.length,r)-1,l=r}return i}function Nt(n,e,a){const r=Math.max(1,Math.floor(a*e/n));return Math.ceil(e/r)}function zt(n,e){const r=document.createElement("canvas").getContext("2d");if(r===null||typeof r.measureText!="function")return null;r.font=`${e} ${me}px ${n}`;const i=new Map;return l=>{const d=i.get(l);if(d!==void 0)return d;const s=r.measureText(l).width;return i.set(l,s),s}}var Mt=y(''),Ct=y(''),qt=y(' '),jt=y(''),Dt=y('');function Lt(n,e){ne(e,!0);const a=T(e,"nameSizePx",3,fe),r=T(e,"categoryColor",3,"#8a867c"),i=T(e,"primaryCode",3,""),l=T(e,"tierAbbrev",19,()=>({})),d=T(e,"selected",3,!1),s=T(e,"rejected",3,!1),f=T(e,"busy",3,!1),v=T(e,"showPrice",3,!0);let c=D("");const E=h(()=>e.product.image_url!==""&&e.product.image_url!==t(c)),A=h(()=>g(e.product.name)),w=h(()=>je(r())),C=h(()=>De(r()));function g(S){const M=S.match(/\p{L}/u);return M===null?"·":M[0].toUpperCase()}var k=Dt();let O;var q=u(k);let X;var U=u(q);{var I=S=>{var M=Mt();N(()=>oe(M,"src",e.product.image_url)),Fe("error",M,()=>x(c,e.product.image_url,!0)),m(S,M)},W=S=>{var M=Ct();let Z;var J=u(M);N(()=>{Z=ce(M,"",Z,{color:t(C)}),z(J,t(A))}),m(S,M)};B(U,S=>{t(E)?S(I):S(W,-1)})}var F=p(q,2),K=u(F);let G;var Q=u(K),_=p(F,2);{var P=S=>{var M=jt();ve(M,21,()=>e.product.prices,Z=>Z.code,(Z,J)=>{var b=qt();let L;var j=u(b);let R;var ee=u(j),Y=p(j,2),$=u(Y),re=p(Y,2),se=u(re);N(de=>{L=te(b,1,"price svelte-1ctu6qg",null,L,{secondary:t(J).code!==i()}),R=te(j,1,"abbrev svelte-1ctu6qg",null,R,{hollow:t(J).code!==i()}),z(ee,l()[t(J).code]??""),z($,t(J).text),z(se,de)},[()=>e.product.price_suffix.trim()]),m(Z,b)}),m(S,M)};B(_,S=>{v()&&S(P)})}N(()=>{O=te(k,1,"tile touch-target svelte-1ctu6qg",null,O,{selected:d(),rejected:s()}),oe(k,"data-product-id",e.product.id),k.disabled=f(),X=ce(q,"",X,{background:t(w)}),G=ce(K,"",G,{"font-size":`${a()??""}px`}),z(Q,e.product.name)}),H("pointerdown",k,()=>e.onpick(e.product)),m(n,k),ae()}le(["pointerdown"]);var Rt=y('
                                                                    '),Ot=y('

                                                                    Chargement du catalogue…

                                                                    ',1),Wt=y('

                                                                    '),Bt=y('

                                                                    '),Ht=y('
                                                                    '),Ft=y('
                                                                    '),Ut=y('
                                                                    ');function Gt(n,e){ne(e,!0);const a=T(e,"colors",19,()=>({})),r=T(e,"primaryCode",3,""),i=T(e,"tierAbbrev",19,()=>({})),l=T(e,"selectedID",3,null),d=T(e,"rejectedID",3,null),s=T(e,"busyID",3,null),f=T(e,"showPrices",3,!0),v=T(e,"loading",3,!1),c=T(e,"emptyMessage",3,"Aucun produit ne correspond."),E=T(e,"emptyHint",3,""),A=230,w=8,C=26,g="'Inter Variable', system-ui, sans-serif",k="'Inter Variable'",O=16;let q=D(0),X=D(!1);ue(()=>{const b=document.fonts;b!==void 0&&b.load(`700 ${me}px ${k}`).catch(()=>[]).then(()=>{x(X,!0)})});const U=h(()=>(t(X),zt(g,700)));let I=D(null),W=D(0),F=D(0);const K=h(()=>{if(t(q)<=0)return 0;const b=Math.max(1,Math.floor((t(q)+w)/(A+w)));return(t(q)-(b-1)*w)/b-C}),G=h(()=>t(W)>0?t(W):t(K));ue(()=>{t(q),e.products.length;const b=t(I)?.querySelector(".name-box");x(W,b?.clientWidth??0,!0)});const Q=h(()=>{const b=new Map;if(t(U)===null||t(G)<=0)return b;const L=t(F)>0?t(F):Le;for(const j of e.products)b.set(j.id,At(j.name,t(G),t(U),L));return b});var _=Ut(),P=u(_),S=p(P,2);{var M=b=>{var L=Ot(),j=ze(L);ve(j,21,()=>({length:O}),Ue,(R,ee)=>{var Y=Rt();m(R,Y)}),m(b,L)},Z=b=>{var L=Bt(),j=u(L),R=u(j),ee=p(j,2);{var Y=$=>{var re=Wt(),se=u(re);N(()=>z(se,E())),m($,re)};B(ee,$=>{E()!==""&&$(Y)})}N(()=>z(R,c())),m(b,L)},J=b=>{var L=Ft();ve(L,21,()=>e.products,j=>j.id,(j,R)=>{var ee=Ht(),Y=u(ee);{let $=h(()=>t(Q).get(t(R).id)??fe),re=h(()=>t(R).id===l()),se=h(()=>t(R).id===d()),de=h(()=>t(R).id===s());Lt(Y,{get product(){return t(R)},get nameSizePx(){return t($)},get categoryColor(){return a()[t(R).category_code]},get primaryCode(){return r()},get tierAbbrev(){return i()},get selected(){return t(re)},get rejected(){return t(se)},get busy(){return t(de)},get showPrice(){return f()},get onpick(){return e.onpick}})}m(j,ee)}),N(()=>oe(L,"data-tile-count",e.products.length)),m(b,L)};B(S,b=>{v()?b(M):e.products.length===0?b(Z,1):b(J,-1)})}Ce(_,b=>x(I,b),()=>t(I)),we(P,"clientHeight",b=>x(F,b)),we(_,"clientWidth",b=>x(q,b)),m(n,_),ae()}var Vt=y(''),Xt=y('
                                                                    ');function Kt(n,e){ne(e,!0);let a=D(null);ue(()=>{t(a)?.focus()});var r=Xt(),i=u(r);ie(i,{name:"search",size:"1.75rem"});var l=p(i,2);Ce(l,c=>x(a,c),()=>t(a));var d=p(l,2);{var s=c=>{var E=Vt();H("click",E,function(...A){e.onclose?.apply(this,A)}),m(c,E)};B(d,c=>{e.query!==""&&c(s)})}var f=p(d,2),v=u(f);ie(v,{name:"close",size:"1.5rem"}),N(()=>Ge(l,e.query)),H("input",l,c=>e.onquery(c.currentTarget.value)),H("keydown",l,c=>{c.key==="Escape"?(c.preventDefault(),e.onclose()):c.key==="Enter"&&(c.preventDefault(),e.onenter())}),H("click",f,function(...c){e.onclose?.apply(this,c)}),m(n,r),ae()}le(["input","keydown","click"]);function Zt(n){if(n==="")return"";const e=new Date(n);if(Number.isNaN(e.getTime()))return"";const a=i=>String(i).padStart(2,"0");return`${`${a(e.getDate())}/${a(e.getMonth()+1)}/${e.getFullYear()}`} ${a(e.getHours())}:${a(e.getMinutes())}:${a(e.getSeconds())}`}function Jt(n){return n.mode==="by_unit"?`${n.product_name} ${n.quantity} ${n.quantity>1?"unités":"unité"}`:`${n.product_name} ${n.net_text} kg`}var Yt=y('Catalogue du '),Qt=y('Catalogue en attente'),$t=y(' '),en=y('aucune pour le moment'),tn=y(' '),nn=y(''),an=y('
                                                                    Dernière étiquette
                                                                    ');function rn(n,e){ne(e,!0);const a=T(e,"catalogAt",3,""),r=T(e,"productCount",3,0),i=T(e,"appVersion",3,""),l=h(()=>[r()>0?`${r()} produits pesables`:"",i()!==""?`application ${i()}`:""].filter(_=>_!=="").join(" · "));var d=an();let s;var f=u(d),v=u(f);{var c=_=>{var P=Yt(),S=p(u(P)),M=u(S);N(()=>z(M,a())),m(_,P)},E=_=>{var P=Qt();m(_,P)};B(v,_=>{a()!==""?_(c):_(E,-1)})}var A=p(v,2);{var w=_=>{var P=$t(),S=u(P);N(()=>z(S,t(l))),m(_,P)};B(A,_=>{t(l)!==""&&_(w)})}var C=p(f,4),g=p(u(C),2);{var k=_=>{var P=en();m(_,P)},O=_=>{var P=tn(),S=u(P);N(M=>z(S,M),[()=>Jt(e.label)]),m(_,P)};B(g,_=>{e.label===null?_(k):_(O,-1)})}var q=p(C,2);{var X=_=>{var P=nn();N(()=>P.disabled=!e.available),H("click",P,function(...S){e.onreprint?.apply(this,S)}),m(_,P)};B(q,_=>{e.label!==null&&_(X)})}var U=p(q,2),I=u(U);let W;var F=p(I,2),K=u(F),G=p(U,2),Q=u(G);ie(Q,{name:"settings",size:"1.75rem"}),N(()=>{s=te(d,1,"bar svelte-161y12f",null,s,{live:e.label!==null}),W=te(I,1,"dot svelte-161y12f",null,W,{fault:!e.healthy}),oe(I,"aria-label",e.healthy?"Matériel disponible":"Matériel indisponible"),z(K,e.healthy?"Balance et imprimante disponibles":"Matériel indisponible")}),H("click",G,function(..._){e.onadmin?.apply(this,_)}),m(n,d),ae()}le(["click"]);var sn=y(''),on=y('

                                                                    Tare g

                                                                    ');function ln(n,e){ne(e,!0);const a=["1","2","3","4","5","6","7","8","9","0"];var r=on(),i=u(r),l=p(u(i),2),d=u(l),s=p(i,2);ve(s,20,()=>a,w=>w,(w,C)=>{var g=sn(),k=u(g);N(()=>z(k,C)),H("click",g,()=>e.ondigit(C)),m(w,g)});var f=p(s,2),v=u(f),c=p(v,2),E=p(c,2),A=u(E);ie(A,{name:"check",size:"1.5rem"}),N(()=>z(d,e.grams===""?"0":e.grams)),H("click",v,function(...w){e.onclear?.apply(this,w)}),H("click",c,function(...w){e.oncancel?.apply(this,w)}),H("click",E,function(...w){e.onconfirm?.apply(this,w)}),m(n,r),ae()}le(["click"]);function cn(n){return n===null||n.state==="initializing"?!1:n.scale.connected&&n.printer.health!=="faulted"}const Ae=1500,un=2e3,Ie=5e3;function dn(n,e){return e?n>=Ie?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!0}:n>=un?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!1}:n>=Ae?{showWeight:!1,banner:"",mustReconnect:!1}:{showWeight:!0,banner:"",mustReconnect:!1}:{showWeight:n=Ie}}const vn=250,fn={show_grid_prices:!0,idle_timeout_s:45,reprint_window_s:60,sound:!0};class gn{#e=D(null);get state(){return t(this.#e)}set state(e){x(this.#e,e,!0)}#t=D(null);get catalog(){return t(this.#t)}set catalog(e){x(this.#t,e,!0)}#n=D(Me({showWeight:!0,banner:"",mustReconnect:!1}));get link(){return t(this.#n)}set link(e){x(this.#n,e,!0)}#r=D("");get catalogError(){return t(this.#r)}set catalogError(e){x(this.#r,e,!0)}#s=null;#i=null;#o=Date.now();#a=!1;#l=!1;get presentation(){return this.catalog?.presentation??fn}start(){this.#u(),this.#c(),this.#i=setInterval(()=>this.#v(),vn)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null,this.#s?.close(),this.#s=null,this.#a=!1}#c(){const e=new EventSource("/api/v1/stream");this.#s=e,e.onopen=()=>{this.#a=!0,this.#o=Date.now()},e.addEventListener("state",a=>this.#d(a)),e.onerror=()=>{this.#a=!1}}#d(e){this.#o=Date.now(),this.#a=!0;const a=JSON.parse(e.data);this.state=a,this.catalog!==null&&a.catalog_count!==this.catalog.product_count&&this.#u()}#v(){const e=dn(Date.now()-this.#o,this.#a);this.link=e,e.mustReconnect&&(this.#s?.close(),this.#o=Date.now(),this.#c())}async#u(){if(!this.#l){this.#l=!0;try{this.catalog=await Ve(),this.catalogError=""}catch{this.catalog===null&&(this.catalogError="Catalogue indisponible. Prévenez un responsable.")}finally{this.#l=!1}}}}const Se="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Pe(){let n=Date.now();const e=new Array(26);for(let r=9;r>=0;r--)e[r]=Se[n%32],n=Math.floor(n/32);const a=new Uint8Array(16);crypto.getRandomValues(a);for(let r=0;r<16;r++)e[10+r]=Se[a[r]%32];return e.join("")}var hn=y('
                                                                    ');function pn(n,e){ne(e,!0);const a=new gn;let r=D(Me(Xe)),i=D(""),l=D(!1);const d=h(()=>t(i)!==""||t(l));let s=D(null),f=D(-1),v=D(null);const c=h(()=>a.catalog),E=h(()=>t(c)===null?[]:Qe(t(c))),A=h(()=>t(c)===null?[]:Je(t(c))),w=h(()=>Ze(t(E),t(r),t(i))),C=h(()=>{const o={};for(const V of t(c)?.categories??[])o[V.code]=V.color;return o}),g=h(()=>a.state),k=h(()=>a.presentation),O=h(()=>cn(t(g))),q=h(()=>{if(a.catalogError!=="")return{message:a.catalogError,hint:"Le poste réessaie tout seul."};if(t(E).length>0)return{message:"Aucun produit ne correspond.",hint:"Effacez des lettres ou changez de rayon."};const o=t(g)?.station??0;return{message:o>0?`Catalogue vide. En attente du fichier flv_${o}.csv.`:"Catalogue vide. En attente du fichier du poste.",hint:"Prévenez un responsable : aucun produit ne peut être pesé."}}),X=h(()=>t(g)?.state==="rejected"?t(g).product?.id??null:null),U=h(()=>t(g)?.state==="faulted"||t(g)?.state==="out_of_service"),I=["product_armed","weight_present","weight_stable","awaiting_stability","validating","printing"],W=h(()=>t(g)!==null&&I.includes(t(g).state)?t(g).product?.id??null:null),F=h(()=>Zt(t(c)?.updated_at??""));ue(()=>(a.start(),()=>a.stop())),ue(()=>{const o=a.state?.revision??-1;t(s)!==null&&o>t(f)&&(x(s,null),x(f,-1))});async function K(o){if(t(s)===null){x(s,o.id,!0),x(f,a.state?.revision??-1,!0);try{await Ke({product_id:o.id,tare_g:a.state?.weight.tare_g??0,units:1,manual_weight_g:0,seen_weight_g:a.state?.weight.gross_g??0,measurement_seq:a.state?.weight.seq??0,key:Pe()})}catch{x(s,null)}}}async function G(){const o=a.state?.reprint.job_id;o===void 0||o===""||await Ye(o,Pe())}function Q(){x(i,""),x(l,!1)}function _(){x(l,!0)}function P(){const o=t(w).length===1?t(w)[0]:void 0;o!==void 0&&K(o)}function S(o){if(!(o.metaKey||o.ctrlKey||o.altKey)&&!(o.target instanceof HTMLElement&&o.target.tagName==="INPUT")&&!(t(v)!==null||t(U))){if(o.key==="Escape"){o.preventDefault(),Q();return}if(o.key==="Backspace"){o.preventDefault(),x(i,t(i).slice(0,-1),!0);return}if(o.key==="Enter"){P();return}o.key.length===1&&/[a-zA-Z0-9 ]/.test(o.key)&&(o.key===" "&&t(i)===""||(o.preventDefault(),x(i,t(i)+o.key)))}}ue(()=>(window.addEventListener("keydown",S),()=>window.removeEventListener("keydown",S)));async function M(){(await st(()=>import("./mount-2bHHy8nk.js"),__vite__mapDeps([0,1,2,3]))).mountAdmin(document.body)}var Z=hn(),J=u(Z);{let o=h(()=>t(v)!==null);ct(J,{get snapshot(){return t(g)},get showWeight(){return a.link.showWeight},get linkBanner(){return a.link.banner},get taring(){return t(o)},ontare:()=>x(v,"")})}var b=p(J,2);{var L=o=>{ln(o,{get grams(){return t(v)},ondigit:V=>x(v,(t(v)??"")+V),onclear:()=>x(v,""),oncancel:()=>x(v,null),onconfirm:()=>x(v,null)})};B(b,o=>{t(v)!==null&&o(L)})}var j=p(b,2);{let o=h(()=>t(c)?.pricing.primary_code??""),V=h(()=>Object.fromEntries((t(c)?.pricing.tiers??[]).map(ye=>[ye.code,ye.abbrev]))),ge=h(()=>t(c)===null&&a.catalogError==="");Gt(j,{get products(){return t(w)},get colors(){return t(C)},get primaryCode(){return t(o)},get tierAbbrev(){return t(V)},get selectedID(){return t(W)},get rejectedID(){return t(X)},get busyID(){return t(s)},get showPrices(){return t(k).show_grid_prices},get loading(){return t(ge)},get emptyMessage(){return t(q).message},get emptyHint(){return t(q).hint},onpick:K})}var R=p(j,2);{var ee=o=>{Kt(o,{get query(){return t(i)},onquery:V=>x(i,V,!0),onclose:Q,onenter:P})};B(R,o=>{t(d)&&o(ee)})}var Y=p(R,2);_t(Y,{get chips(){return t(A)},get active(){return t(r)},get searchFieldOpen(){return t(d)},onselect:o=>x(r,o,!0),onopensearch:_});var $=p(Y,2);{let o=h(()=>t(g)?.last_label??null),V=h(()=>t(g)?.reprint.available??!1),ge=h(()=>t(c)?.app_version??"");rn($,{get label(){return t(o)},get available(){return t(V)},get catalogAt(){return t(F)},get productCount(){return t(E).length},get appVersion(){return t(ge)},get healthy(){return t(O)},onreprint:G,onadmin:M})}var re=p($,2);{var se=o=>{{let V=h(()=>t(g).message?.text??"Prévenez un responsable.");ke(o,{title:"Poste indisponible",get detail(){return t(V)},get code(){return t(g).fault_code},action:{label:"J’ai compris",run:()=>{$e()}}})}},de=o=>{ke(o,{title:"Poste hors service",detail:"Ce poste ne peut pas peser. Prévenez un responsable.",get code(){return t(g).fault_code}})};B(re,o=>{t(g)?.state==="faulted"?o(se):t(g)?.state==="out_of_service"&&o(de,1)})}m(n,Z),ae()}const _n=5;mn();document.addEventListener("contextmenu",n=>{bn(n.target)||n.preventDefault()});document.addEventListener("dragstart",n=>n.preventDefault());et(pn,{target:document.getElementById("app")});function bn(n){return n instanceof Element&&n.closest("[data-admin]")!==null}function mn(){let n=!1;const e=(a,r)=>{n||(n=!0,tt(a,r),yn(),setTimeout(()=>location.reload(),_n*1e3))};window.addEventListener("error",a=>e(a.message,a.error?.stack??"")),window.addEventListener("unhandledrejection",a=>e(String(a.reason),a.reason?.stack??""))}function yn(){const n=document.createElement("div");n.className="fatal",n.append(_e("h1","Une erreur est survenue"),_e("p","L’écran va se recharger tout seul."),_e("p","ERR-UI-01","code")),document.body.appendChild(n)}function _e(n,e,a=""){const r=document.createElement(n);return r.textContent=e,a!==""&&(r.className=a),r} +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mount-BdIa8cb_.js","assets/app-Dn-BjSsJ.js","assets/app-DWjtdCrW.css","assets/mount-C1tpIbdA.css"])))=>i.map(i=>d[i]); +import{e as Te,u as Ne,c as Re,r as Oe,a as We,D as Be,S as He,d as le,p as ne,b as T,i as B,I as ie,t as N,f as H,g as m,h as ae,j as y,k as t,s as p,l as oe,m as ce,n as te,o as z,q as h,v as u,w as ze,x as ve,y as Fe,z as x,A as D,B as ue,C as Ue,E as Ge,F as Me,G as Ve,H as Xe,J as Ke,K as Ze,L as Je,M as Ye,N as Qe,O as $e,P as et,Q as tt}from"./app-Dn-BjSsJ.js";class be{#e=new WeakMap;#t;#n;static entries=new WeakMap;constructor(e){this.#n=e}observe(e,a){var r=this.#e.get(e)||new Set;return r.add(a),this.#e.set(e,r),this.#r().observe(e,this.#n),()=>{var i=this.#e.get(e);i.delete(a),i.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??(this.#t=new ResizeObserver(e=>{for(var a of e){be.entries.set(a.target,a);for(var r of this.#e.get(a.target)||[])r(a)}}))}}var nt=new be({box:"border-box"});function we(n,e,a){var r=nt.observe(n,()=>a(n[e]));Te(()=>(Ne(()=>a(n[e])),r))}function he(n,e){return n===e||n?.[He]===e}function Ce(n={},e,a,r){var i=Re.r,l=We;return Te(()=>{var d,s;return Oe(()=>{d=s,s=[],Ne(()=>{he(a(...s),n)||(e(n,...s),d&&he(a(...d),n)&&e(null,...d))})}),()=>{let f=l;for(;f!==i&&f.parent!==null&&f.parent.f&Be;)f=f.parent;const v=()=>{s&&he(a(...s),n)&&e(null,...s)},c=f.teardown;f.teardown=()=>{v(),c?.()}}}),n}const at="modulepreload",rt=function(n){return"/"+n},Ee={},st=function(e,a,r){let i=Promise.resolve();if(a&&a.length>0){let f=function(v){return Promise.all(v.map(c=>Promise.resolve(c).then(E=>({status:"fulfilled",value:E}),E=>({status:"rejected",reason:E}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),s=d?.nonce||d?.getAttribute("nonce");i=f(a.map(v=>{if(v=rt(v),v in Ee)return;Ee[v]=!0;const c=v.endsWith(".css"),E=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${v}"]${E}`))return;const A=document.createElement("link");if(A.rel=c?"stylesheet":at,c||(A.as="script"),A.crossOrigin="",A.href=v,s&&A.setAttribute("nonce",s),document.head.appendChild(A),c)return new Promise((w,C)=>{A.addEventListener("load",w),A.addEventListener("error",()=>C(new Error(`Unable to preload CSS for ${v}`)))})}))}function l(d){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=d,window.dispatchEvent(s),!s.defaultPrevented)throw d}return i.then(d=>{for(const s of d||[])s.status==="rejected"&&l(s.reason);return e().catch(l)})};var it=y('

                                                                    kg

                                                                    ',1),ot=y('

                                                                    —kg

                                                                    Poids indisponible

                                                                    ',1),lt=y('');function ct(n,e){ne(e,!0);const a=T(e,"taring",3,!1),r=h(()=>{if(e.linkBanner!=="")return e.linkBanner;const I=e.snapshot?.message??null;if(I!==null)return I.text;switch(e.snapshot?.state){case"product_armed":return"Posez votre produit";case"awaiting_stability":return"Pesée en cours…";case"printing":return"Étiquette en cours…";case"entering_tare":return"Tapez la tare en grammes";case"scale_lost":return"Le poids n’est plus disponible.";default:return"Touchez votre produit, l’étiquette sort"}}),i=h(()=>e.snapshot===null?"var(--waiting)":e.snapshot.state==="faulted"||e.snapshot.state==="scale_lost"?"var(--fault)":e.snapshot.state==="rejected"?"var(--warning)":e.snapshot.state==="succeeded"||e.snapshot.weight.latched?"var(--ready)":"var(--waiting)"),l=h(()=>e.showWeight&&e.snapshot!==null&&e.snapshot.weight.available&&!e.snapshot.weight.expired),d=h(()=>{const I=e.snapshot?.message;return I!=null?I.level:e.snapshot?.state==="faulted"||e.snapshot?.state==="scale_lost"?"error":e.snapshot?.state==="rejected"?"warn":"info"});var s=lt(),f=u(s);let v;var c=u(f);{var E=I=>{var W=it(),F=ze(W),K=u(F),G=p(F,2),Q=u(G);N(()=>{z(K,e.snapshot.weight.net_text),z(Q,`tare ${e.snapshot.weight.tare_g??""} g`)}),m(I,W)},A=I=>{var W=ot();m(I,W)};B(c,I=>{t(l)&&e.snapshot?I(E):I(A,-1)})}var w=p(f,2);let C;var g=u(w),k=p(w,2);let O;var q=u(k);ie(q,{name:"tare",size:"1.4em"});var X=p(k,2);let U;N(()=>{oe(s,"data-state",e.snapshot?.state??"initializing"),v=ce(f,"",v,{"border-left-color":t(i)}),C=te(w,1,"instruction svelte-4gizvm",null,C,{warn:t(d)==="warn",error:t(d)==="error"}),z(g,t(r)),O=te(k,1,"tare-key touch-target svelte-4gizvm",null,O,{active:a()}),U=ce(X,"",U,{background:t(i)})}),H("click",k,function(...I){e.ontare?.apply(this,I)}),m(n,s),ae()}le(["click"]);const ut=4.5,dt=.1,vt={r:138,g:134,b:124};function qe(n){const e=/^#([\da-f])([\da-f])([\da-f])$/iu.exec(n);if(e!==null){const[,d,s,f]=e;return{r:Number.parseInt(d+d,16),g:Number.parseInt(s+s,16),b:Number.parseInt(f+f,16)}}const a=/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/iu.exec(n);if(a===null)return vt;const[,r,i,l]=a;return{r:Number.parseInt(r,16),g:Number.parseInt(i,16),b:Number.parseInt(l,16)}}function pe(n){const e=n/255;return e<=.03928?e/12.92:((e+.055)/1.055)**2.4}function ft({r:n,g:e,b:a}){return .2126*pe(n)+.7152*pe(e)+.0722*pe(a)}function gt(n){return 1.05/(ft(n)+.05)}function je(n){const{r:e,g:a,b:r}=qe(n),i=l=>Math.round(255-dt*(255-l));return`rgb(${i(e)}, ${i(a)}, ${i(r)})`}function De(n){let e=qe(n);for(let a=0;a<16&>(e) '),pt=y('');function _t(n,e){ne(e,!0);var a=pt(),r=u(a);ve(r,21,()=>e.chips,s=>s.code,(s,f)=>{var v=ht();let c,E;var A=p(u(v),2),w=u(A),C=p(A,2),g=u(C);N(k=>{c=te(v,1,"chip touch-target svelte-atgty1",null,c,{active:t(f).code===e.active}),oe(v,"aria-pressed",t(f).code===e.active),E=ce(v,"",E,k),z(w,t(f).label),z(g,t(f).count)},[()=>({"--chip-ink":De(t(f).color),"--chip-wash":je(t(f).color)})]),H("click",v,()=>e.onselect(t(f).code)),m(s,v)});var i=p(r,2);let l;var d=u(i);ie(d,{name:"search",size:"1.75rem"}),N(()=>{l=te(i,1,"chip touch-target search-key svelte-atgty1",null,l,{active:e.searchFieldOpen}),oe(i,"aria-pressed",e.searchFieldOpen)}),H("click",i,function(...s){e.onopensearch?.apply(this,s)}),m(n,a),ae()}le(["click"]);var bt=y(''),mt=y('

                                                                    '),yt=y('

                                                                    ');function ke(n,e){ne(e,!0);const a=T(e,"action",3,null);var r=yt(),i=u(r),l=u(i),d=u(l);ie(d,{name:"alert",size:"3rem"});var s=p(l,2),f=u(s),v=p(s,2),c=u(v),E=p(v,2);{var A=g=>{var k=bt(),O=u(k);N(()=>z(O,a().label)),H("click",k,function(...q){a().run?.apply(this,q)}),m(g,k)};B(E,g=>{a()!==null&&g(A)})}var w=p(E,2);{var C=g=>{var k=mt(),O=u(k);N(()=>z(O,e.code)),m(g,k)};B(w,g=>{e.code!==""&&g(C)})}N(()=>{z(f,e.title),z(c,e.detail)}),m(n,r),ae()}le(["click"]);const fe=34,xe=18,wt=.5,Et=.005,kt=1.15,Le=90,me=100;function xt(n,e){return Math.max(1,Math.floor(e/(n*kt)))}function At(n,e,a,r=Le){const i=St(n,a);if(i.length===0||e<=0)return fe;const l=Pt(a),d=e*(1-Et);for(let s=fe;s>xe;s-=wt){const f=s/me;if(Tt(i,l,f,d)<=xt(s,r))return s}return xe}const It=/(?<=[-‐–—])(?!\d)/u;function St(n,e){const a=[];for(const r of n.split(/\s+/u).filter(i=>i.length>0)){const i=r.split(It).filter(l=>l.length>0);for(const[l,d]of i.entries())a.push({width:e(d),length:[...d].length,spaced:l===0})}return a}function Pt(n){return Math.max(0,n("a a")-n("aa"))}function Tt(n,e,a,r){let i=1,l=0;for(const d of n){const s=d.width*a,f=d.spaced?e*a:0;if(l>0&&l+f+s<=r){l+=f+s;continue}if(l>0&&(i++,l=0),s<=r){l=s;continue}i+=Nt(s,d.length,r)-1,l=r}return i}function Nt(n,e,a){const r=Math.max(1,Math.floor(a*e/n));return Math.ceil(e/r)}function zt(n,e){const r=document.createElement("canvas").getContext("2d");if(r===null||typeof r.measureText!="function")return null;r.font=`${e} ${me}px ${n}`;const i=new Map;return l=>{const d=i.get(l);if(d!==void 0)return d;const s=r.measureText(l).width;return i.set(l,s),s}}var Mt=y(''),Ct=y(''),qt=y(' '),jt=y(''),Dt=y('');function Lt(n,e){ne(e,!0);const a=T(e,"nameSizePx",3,fe),r=T(e,"categoryColor",3,"#8a867c"),i=T(e,"primaryCode",3,""),l=T(e,"tierAbbrev",19,()=>({})),d=T(e,"selected",3,!1),s=T(e,"rejected",3,!1),f=T(e,"busy",3,!1),v=T(e,"showPrice",3,!0);let c=D("");const E=h(()=>e.product.image_url!==""&&e.product.image_url!==t(c)),A=h(()=>g(e.product.name)),w=h(()=>je(r())),C=h(()=>De(r()));function g(S){const M=S.match(/\p{L}/u);return M===null?"·":M[0].toUpperCase()}var k=Dt();let O;var q=u(k);let X;var U=u(q);{var I=S=>{var M=Mt();N(()=>oe(M,"src",e.product.image_url)),Fe("error",M,()=>x(c,e.product.image_url,!0)),m(S,M)},W=S=>{var M=Ct();let Z;var J=u(M);N(()=>{Z=ce(M,"",Z,{color:t(C)}),z(J,t(A))}),m(S,M)};B(U,S=>{t(E)?S(I):S(W,-1)})}var F=p(q,2),K=u(F);let G;var Q=u(K),_=p(F,2);{var P=S=>{var M=jt();ve(M,21,()=>e.product.prices,Z=>Z.code,(Z,J)=>{var b=qt();let L;var j=u(b);let R;var ee=u(j),Y=p(j,2),$=u(Y),re=p(Y,2),se=u(re);N(de=>{L=te(b,1,"price svelte-1ctu6qg",null,L,{secondary:t(J).code!==i()}),R=te(j,1,"abbrev svelte-1ctu6qg",null,R,{hollow:t(J).code!==i()}),z(ee,l()[t(J).code]??""),z($,t(J).text),z(se,de)},[()=>e.product.price_suffix.trim()]),m(Z,b)}),m(S,M)};B(_,S=>{v()&&S(P)})}N(()=>{O=te(k,1,"tile touch-target svelte-1ctu6qg",null,O,{selected:d(),rejected:s()}),oe(k,"data-product-id",e.product.id),k.disabled=f(),X=ce(q,"",X,{background:t(w)}),G=ce(K,"",G,{"font-size":`${a()??""}px`}),z(Q,e.product.name)}),H("pointerdown",k,()=>e.onpick(e.product)),m(n,k),ae()}le(["pointerdown"]);var Rt=y('
                                                                    '),Ot=y('

                                                                    Chargement du catalogue…

                                                                    ',1),Wt=y('

                                                                    '),Bt=y('

                                                                    '),Ht=y('
                                                                    '),Ft=y('
                                                                    '),Ut=y('
                                                                    ');function Gt(n,e){ne(e,!0);const a=T(e,"colors",19,()=>({})),r=T(e,"primaryCode",3,""),i=T(e,"tierAbbrev",19,()=>({})),l=T(e,"selectedID",3,null),d=T(e,"rejectedID",3,null),s=T(e,"busyID",3,null),f=T(e,"showPrices",3,!0),v=T(e,"loading",3,!1),c=T(e,"emptyMessage",3,"Aucun produit ne correspond."),E=T(e,"emptyHint",3,""),A=230,w=8,C=26,g="'Inter Variable', system-ui, sans-serif",k="'Inter Variable'",O=16;let q=D(0),X=D(!1);ue(()=>{const b=document.fonts;b!==void 0&&b.load(`700 ${me}px ${k}`).catch(()=>[]).then(()=>{x(X,!0)})});const U=h(()=>(t(X),zt(g,700)));let I=D(null),W=D(0),F=D(0);const K=h(()=>{if(t(q)<=0)return 0;const b=Math.max(1,Math.floor((t(q)+w)/(A+w)));return(t(q)-(b-1)*w)/b-C}),G=h(()=>t(W)>0?t(W):t(K));ue(()=>{t(q),e.products.length;const b=t(I)?.querySelector(".name-box");x(W,b?.clientWidth??0,!0)});const Q=h(()=>{const b=new Map;if(t(U)===null||t(G)<=0)return b;const L=t(F)>0?t(F):Le;for(const j of e.products)b.set(j.id,At(j.name,t(G),t(U),L));return b});var _=Ut(),P=u(_),S=p(P,2);{var M=b=>{var L=Ot(),j=ze(L);ve(j,21,()=>({length:O}),Ue,(R,ee)=>{var Y=Rt();m(R,Y)}),m(b,L)},Z=b=>{var L=Bt(),j=u(L),R=u(j),ee=p(j,2);{var Y=$=>{var re=Wt(),se=u(re);N(()=>z(se,E())),m($,re)};B(ee,$=>{E()!==""&&$(Y)})}N(()=>z(R,c())),m(b,L)},J=b=>{var L=Ft();ve(L,21,()=>e.products,j=>j.id,(j,R)=>{var ee=Ht(),Y=u(ee);{let $=h(()=>t(Q).get(t(R).id)??fe),re=h(()=>t(R).id===l()),se=h(()=>t(R).id===d()),de=h(()=>t(R).id===s());Lt(Y,{get product(){return t(R)},get nameSizePx(){return t($)},get categoryColor(){return a()[t(R).category_code]},get primaryCode(){return r()},get tierAbbrev(){return i()},get selected(){return t(re)},get rejected(){return t(se)},get busy(){return t(de)},get showPrice(){return f()},get onpick(){return e.onpick}})}m(j,ee)}),N(()=>oe(L,"data-tile-count",e.products.length)),m(b,L)};B(S,b=>{v()?b(M):e.products.length===0?b(Z,1):b(J,-1)})}Ce(_,b=>x(I,b),()=>t(I)),we(P,"clientHeight",b=>x(F,b)),we(_,"clientWidth",b=>x(q,b)),m(n,_),ae()}var Vt=y(''),Xt=y('
                                                                    ');function Kt(n,e){ne(e,!0);let a=D(null);ue(()=>{t(a)?.focus()});var r=Xt(),i=u(r);ie(i,{name:"search",size:"1.75rem"});var l=p(i,2);Ce(l,c=>x(a,c),()=>t(a));var d=p(l,2);{var s=c=>{var E=Vt();H("click",E,function(...A){e.onclose?.apply(this,A)}),m(c,E)};B(d,c=>{e.query!==""&&c(s)})}var f=p(d,2),v=u(f);ie(v,{name:"close",size:"1.5rem"}),N(()=>Ge(l,e.query)),H("input",l,c=>e.onquery(c.currentTarget.value)),H("keydown",l,c=>{c.key==="Escape"?(c.preventDefault(),e.onclose()):c.key==="Enter"&&(c.preventDefault(),e.onenter())}),H("click",f,function(...c){e.onclose?.apply(this,c)}),m(n,r),ae()}le(["input","keydown","click"]);function Zt(n){if(n==="")return"";const e=new Date(n);if(Number.isNaN(e.getTime()))return"";const a=i=>String(i).padStart(2,"0");return`${`${a(e.getDate())}/${a(e.getMonth()+1)}/${e.getFullYear()}`} ${a(e.getHours())}:${a(e.getMinutes())}:${a(e.getSeconds())}`}function Jt(n){return n.mode==="by_unit"?`${n.product_name} ${n.quantity} ${n.quantity>1?"unités":"unité"}`:`${n.product_name} ${n.net_text} kg`}var Yt=y('Catalogue du '),Qt=y('Catalogue en attente'),$t=y(' '),en=y('aucune pour le moment'),tn=y(' '),nn=y(''),an=y('
                                                                    Dernière étiquette
                                                                    ');function rn(n,e){ne(e,!0);const a=T(e,"catalogAt",3,""),r=T(e,"productCount",3,0),i=T(e,"appVersion",3,""),l=h(()=>[r()>0?`${r()} produits pesables`:"",i()!==""?`application ${i()}`:""].filter(_=>_!=="").join(" · "));var d=an();let s;var f=u(d),v=u(f);{var c=_=>{var P=Yt(),S=p(u(P)),M=u(S);N(()=>z(M,a())),m(_,P)},E=_=>{var P=Qt();m(_,P)};B(v,_=>{a()!==""?_(c):_(E,-1)})}var A=p(v,2);{var w=_=>{var P=$t(),S=u(P);N(()=>z(S,t(l))),m(_,P)};B(A,_=>{t(l)!==""&&_(w)})}var C=p(f,4),g=p(u(C),2);{var k=_=>{var P=en();m(_,P)},O=_=>{var P=tn(),S=u(P);N(M=>z(S,M),[()=>Jt(e.label)]),m(_,P)};B(g,_=>{e.label===null?_(k):_(O,-1)})}var q=p(C,2);{var X=_=>{var P=nn();N(()=>P.disabled=!e.available),H("click",P,function(...S){e.onreprint?.apply(this,S)}),m(_,P)};B(q,_=>{e.label!==null&&_(X)})}var U=p(q,2),I=u(U);let W;var F=p(I,2),K=u(F),G=p(U,2),Q=u(G);ie(Q,{name:"settings",size:"1.75rem"}),N(()=>{s=te(d,1,"bar svelte-161y12f",null,s,{live:e.label!==null}),W=te(I,1,"dot svelte-161y12f",null,W,{fault:!e.healthy}),oe(I,"aria-label",e.healthy?"Matériel disponible":"Matériel indisponible"),z(K,e.healthy?"Balance et imprimante disponibles":"Matériel indisponible")}),H("click",G,function(..._){e.onadmin?.apply(this,_)}),m(n,d),ae()}le(["click"]);var sn=y(''),on=y('

                                                                    Tare g

                                                                    ');function ln(n,e){ne(e,!0);const a=["1","2","3","4","5","6","7","8","9","0"];var r=on(),i=u(r),l=p(u(i),2),d=u(l),s=p(i,2);ve(s,20,()=>a,w=>w,(w,C)=>{var g=sn(),k=u(g);N(()=>z(k,C)),H("click",g,()=>e.ondigit(C)),m(w,g)});var f=p(s,2),v=u(f),c=p(v,2),E=p(c,2),A=u(E);ie(A,{name:"check",size:"1.5rem"}),N(()=>z(d,e.grams===""?"0":e.grams)),H("click",v,function(...w){e.onclear?.apply(this,w)}),H("click",c,function(...w){e.oncancel?.apply(this,w)}),H("click",E,function(...w){e.onconfirm?.apply(this,w)}),m(n,r),ae()}le(["click"]);function cn(n){return n===null||n.state==="initializing"?!1:n.scale.connected&&n.printer.health!=="faulted"}const Ae=1500,un=2e3,Ie=5e3;function dn(n,e){return e?n>=Ie?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!0}:n>=un?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!1}:n>=Ae?{showWeight:!1,banner:"",mustReconnect:!1}:{showWeight:!0,banner:"",mustReconnect:!1}:{showWeight:n=Ie}}const vn=250,fn={show_grid_prices:!0,idle_timeout_s:45,reprint_window_s:60,sound:!0};class gn{#e=D(null);get state(){return t(this.#e)}set state(e){x(this.#e,e,!0)}#t=D(null);get catalog(){return t(this.#t)}set catalog(e){x(this.#t,e,!0)}#n=D(Me({showWeight:!0,banner:"",mustReconnect:!1}));get link(){return t(this.#n)}set link(e){x(this.#n,e,!0)}#r=D("");get catalogError(){return t(this.#r)}set catalogError(e){x(this.#r,e,!0)}#s=null;#i=null;#o=Date.now();#a=!1;#l=!1;get presentation(){return this.catalog?.presentation??fn}start(){this.#u(),this.#c(),this.#i=setInterval(()=>this.#v(),vn)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null,this.#s?.close(),this.#s=null,this.#a=!1}#c(){const e=new EventSource("/api/v1/stream");this.#s=e,e.onopen=()=>{this.#a=!0,this.#o=Date.now()},e.addEventListener("state",a=>this.#d(a)),e.onerror=()=>{this.#a=!1}}#d(e){this.#o=Date.now(),this.#a=!0;const a=JSON.parse(e.data);this.state=a,this.catalog!==null&&a.catalog_count!==this.catalog.product_count&&this.#u()}#v(){const e=dn(Date.now()-this.#o,this.#a);this.link=e,e.mustReconnect&&(this.#s?.close(),this.#o=Date.now(),this.#c())}async#u(){if(!this.#l){this.#l=!0;try{this.catalog=await Ve(),this.catalogError=""}catch{this.catalog===null&&(this.catalogError="Catalogue indisponible. Prévenez un responsable.")}finally{this.#l=!1}}}}const Se="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Pe(){let n=Date.now();const e=new Array(26);for(let r=9;r>=0;r--)e[r]=Se[n%32],n=Math.floor(n/32);const a=new Uint8Array(16);crypto.getRandomValues(a);for(let r=0;r<16;r++)e[10+r]=Se[a[r]%32];return e.join("")}var hn=y('
                                                                    ');function pn(n,e){ne(e,!0);const a=new gn;let r=D(Me(Xe)),i=D(""),l=D(!1);const d=h(()=>t(i)!==""||t(l));let s=D(null),f=D(-1),v=D(null);const c=h(()=>a.catalog),E=h(()=>t(c)===null?[]:Qe(t(c))),A=h(()=>t(c)===null?[]:Je(t(c))),w=h(()=>Ze(t(E),t(r),t(i))),C=h(()=>{const o={};for(const V of t(c)?.categories??[])o[V.code]=V.color;return o}),g=h(()=>a.state),k=h(()=>a.presentation),O=h(()=>cn(t(g))),q=h(()=>{if(a.catalogError!=="")return{message:a.catalogError,hint:"Le poste réessaie tout seul."};if(t(E).length>0)return{message:"Aucun produit ne correspond.",hint:"Effacez des lettres ou changez de rayon."};const o=t(g)?.station??0;return{message:o>0?`Catalogue vide. En attente du fichier flv_${o}.csv.`:"Catalogue vide. En attente du fichier du poste.",hint:"Prévenez un responsable : aucun produit ne peut être pesé."}}),X=h(()=>t(g)?.state==="rejected"?t(g).product?.id??null:null),U=h(()=>t(g)?.state==="faulted"||t(g)?.state==="out_of_service"),I=["product_armed","weight_present","weight_stable","awaiting_stability","validating","printing"],W=h(()=>t(g)!==null&&I.includes(t(g).state)?t(g).product?.id??null:null),F=h(()=>Zt(t(c)?.updated_at??""));ue(()=>(a.start(),()=>a.stop())),ue(()=>{const o=a.state?.revision??-1;t(s)!==null&&o>t(f)&&(x(s,null),x(f,-1))});async function K(o){if(t(s)===null){x(s,o.id,!0),x(f,a.state?.revision??-1,!0);try{await Ke({product_id:o.id,tare_g:a.state?.weight.tare_g??0,units:1,manual_weight_g:0,seen_weight_g:a.state?.weight.gross_g??0,measurement_seq:a.state?.weight.seq??0,key:Pe()})}catch{x(s,null)}}}async function G(){const o=a.state?.reprint.job_id;o===void 0||o===""||await Ye(o,Pe())}function Q(){x(i,""),x(l,!1)}function _(){x(l,!0)}function P(){const o=t(w).length===1?t(w)[0]:void 0;o!==void 0&&K(o)}function S(o){if(!(o.metaKey||o.ctrlKey||o.altKey)&&!(o.target instanceof HTMLElement&&o.target.tagName==="INPUT")&&!(t(v)!==null||t(U))){if(o.key==="Escape"){o.preventDefault(),Q();return}if(o.key==="Backspace"){o.preventDefault(),x(i,t(i).slice(0,-1),!0);return}if(o.key==="Enter"){P();return}o.key.length===1&&/[a-zA-Z0-9 ]/.test(o.key)&&(o.key===" "&&t(i)===""||(o.preventDefault(),x(i,t(i)+o.key)))}}ue(()=>(window.addEventListener("keydown",S),()=>window.removeEventListener("keydown",S)));async function M(){(await st(()=>import("./mount-BdIa8cb_.js"),__vite__mapDeps([0,1,2,3]))).mountAdmin(document.body)}var Z=hn(),J=u(Z);{let o=h(()=>t(v)!==null);ct(J,{get snapshot(){return t(g)},get showWeight(){return a.link.showWeight},get linkBanner(){return a.link.banner},get taring(){return t(o)},ontare:()=>x(v,"")})}var b=p(J,2);{var L=o=>{ln(o,{get grams(){return t(v)},ondigit:V=>x(v,(t(v)??"")+V),onclear:()=>x(v,""),oncancel:()=>x(v,null),onconfirm:()=>x(v,null)})};B(b,o=>{t(v)!==null&&o(L)})}var j=p(b,2);{let o=h(()=>t(c)?.pricing.primary_code??""),V=h(()=>Object.fromEntries((t(c)?.pricing.tiers??[]).map(ye=>[ye.code,ye.abbrev]))),ge=h(()=>t(c)===null&&a.catalogError==="");Gt(j,{get products(){return t(w)},get colors(){return t(C)},get primaryCode(){return t(o)},get tierAbbrev(){return t(V)},get selectedID(){return t(W)},get rejectedID(){return t(X)},get busyID(){return t(s)},get showPrices(){return t(k).show_grid_prices},get loading(){return t(ge)},get emptyMessage(){return t(q).message},get emptyHint(){return t(q).hint},onpick:K})}var R=p(j,2);{var ee=o=>{Kt(o,{get query(){return t(i)},onquery:V=>x(i,V,!0),onclose:Q,onenter:P})};B(R,o=>{t(d)&&o(ee)})}var Y=p(R,2);_t(Y,{get chips(){return t(A)},get active(){return t(r)},get searchFieldOpen(){return t(d)},onselect:o=>x(r,o,!0),onopensearch:_});var $=p(Y,2);{let o=h(()=>t(g)?.last_label??null),V=h(()=>t(g)?.reprint.available??!1),ge=h(()=>t(c)?.app_version??"");rn($,{get label(){return t(o)},get available(){return t(V)},get catalogAt(){return t(F)},get productCount(){return t(E).length},get appVersion(){return t(ge)},get healthy(){return t(O)},onreprint:G,onadmin:M})}var re=p($,2);{var se=o=>{{let V=h(()=>t(g).message?.text??"Prévenez un responsable.");ke(o,{title:"Poste indisponible",get detail(){return t(V)},get code(){return t(g).fault_code},action:{label:"J’ai compris",run:()=>{$e()}}})}},de=o=>{ke(o,{title:"Poste hors service",detail:"Ce poste ne peut pas peser. Prévenez un responsable.",get code(){return t(g).fault_code}})};B(re,o=>{t(g)?.state==="faulted"?o(se):t(g)?.state==="out_of_service"&&o(de,1)})}m(n,Z),ae()}const _n=5;mn();document.addEventListener("contextmenu",n=>{bn(n.target)||n.preventDefault()});document.addEventListener("dragstart",n=>n.preventDefault());et(pn,{target:document.getElementById("app")});function bn(n){return n instanceof Element&&n.closest("[data-admin]")!==null}function mn(){let n=!1;const e=(a,r)=>{n||(n=!0,tt(a,r),yn(),setTimeout(()=>location.reload(),_n*1e3))};window.addEventListener("error",a=>e(a.message,a.error?.stack??"")),window.addEventListener("unhandledrejection",a=>e(String(a.reason),a.reason?.stack??""))}function yn(){const n=document.createElement("div");n.className="fatal",n.append(_e("h1","Une erreur est survenue"),_e("p","L’écran va se recharger tout seul."),_e("p","ERR-UI-01","code")),document.body.appendChild(n)}function _e(n,e,a=""){const r=document.createElement(n);return r.textContent=e,a!==""&&(r.className=a),r} diff --git a/internal/web/dist/assets/mount-2bHHy8nk.js b/internal/web/dist/assets/mount-2bHHy8nk.js deleted file mode 100644 index 6b6da3d..0000000 --- a/internal/web/dist/assets/mount-2bHHy8nk.js +++ /dev/null @@ -1,93 +0,0 @@ -import{R as ar,T as rr,U as nr,V as xa,W as Yt,e as sr,X as lr,Y as ir,Z as or,_ as ur,u as pa,r as Fa,$ as cr,c as Ma,B as aa,A as $,k as e,z as v,F as Lt,d as At,b as vt,s as r,i as m,t as p,n as Ht,l as Be,o as i,f as tt,g as l,v as a,j as u,p as Tt,I as dr,h as Ct,q as o,w as X,a0 as Ea,E as na,x as rt,G as Ba,a1 as ua,y as Qt,a2 as Dt,a3 as yt,a4 as sa,C as fa,K as vr,H as pr,P as fr,a5 as gr}from"./app-Dn-BjSsJ.js";function Va(n){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function mr(n,t,...s){var c=new nr(n);ar(()=>{const h=t()??null;c.ensure(h,h&&(x=>h(x,...s)))},rr)}function Wa(n,t,s=!1){if(n.multiple){if(t==null)return;if(!lr(t))return ir();for(var c of n.options)c.selected=t.includes(la(c));return}for(c of n.options){var h=la(c);if(or(h,t)){c.selected=!0;return}}(!s||t!==void 0)&&(n.selectedIndex=-1)}function hr(n){var t=new MutationObserver(()=>{"__value"in n&&Wa(n,n.__value)});t.observe(n,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),ur(()=>{t.disconnect()})}function _r(n,t,s=t){var c=new WeakSet,h=!0;xa(n,"change",x=>{var O=x?"[selected]":":checked",A;if(n.multiple)A=[].map.call(n.querySelectorAll(O),la);else{var T=n.querySelector(O)??n.querySelector("option:not([disabled])");A=T&&la(T)}s(A),n.__value=A,Yt!==null&&c.add(Yt)}),sr(()=>{var x=t();if(n===document.activeElement){var O=Yt;if(c.has(O))return}if(Wa(n,x,h),h&&x===void 0){var A=n.querySelector(":checked");A!==null&&(x=la(A),s(x))}n.__value=x,h=!1}),hr(n)}function la(n){return"__value"in n?n.__value:n.value}function ia(n,t,s=t){var c=new WeakSet;xa(n,"input",async h=>{var x=h?n.defaultValue:n.value;if(x=ma(n)?ha(x):x,s(x),Yt!==null&&c.add(Yt),await cr(),x!==(x=t())){var O=n.selectionStart,A=n.selectionEnd,T=n.value.length;if(n.value=x??"",A!==null){var H=n.value.length;O===A&&A===T&&H>T?(n.selectionStart=H,n.selectionEnd=H):(n.selectionStart=O,n.selectionEnd=Math.min(A,H))}}}),pa(t)==null&&n.value&&(s(ma(n)?ha(n.value):n.value),Yt!==null&&c.add(Yt)),Fa(()=>{var h=t();if(n===document.activeElement){var x=Yt;if(c.has(x))return}ma(n)&&h===ha(n.value)||n.type==="date"&&!h&&!n.value||h!==n.value&&(n.value=h??"")})}function za(n,t,s=t){xa(n,"change",c=>{var h=c?n.defaultChecked:n.checked;s(h)}),pa(t)==null&&s(n.checked),Fa(()=>{var c=t();n.checked=!!c})}function ma(n){var t=n.type;return t==="number"||t==="range"}function ha(n){return n===""?null:+n}function ka(n){Ma===null&&Va(),aa(()=>{const t=pa(n);if(typeof t=="function")return t})}function br(n){Ma===null&&Va(),ka(()=>()=>pa(n))}class It extends Error{constructor(t,s,c="",h=[]){super(s),this.status=t,this.code=c,this.faults=h,this.name="AdminError"}status;code;faults;get needsPassword(){return this.status===401}}function wr(){return Jt("/admin/api/health")}function yr(){return ht("/admin/api/troubleshooting/reprint",{})}function xr(){return ht("/admin/api/troubleshooting/reload-catalog",{})}function kr(n){return ht("/admin/api/troubleshooting/manual-entry",{on:n})}function qr(){return ht("/admin/api/troubleshooting/roll-changed",{})}function Sr(n){return ht("/admin/api/troubleshooting/fallback-printer",{on:n})}function Lr(){return ht("/admin/api/troubleshooting/test-scale",{})}function Er(){return ht("/admin/api/troubleshooting/test-printer",{})}function zr(){return ht("/admin/api/troubleshooting/test-label",{})}async function Ga(n){const t=new FormData;t.append("file",n,n.name);const s=await fetch("/admin/api/catalog/import",{method:"POST",body:t});return Sa(s,"POST /admin/api/catalog/import")}const jr="/admin/api/diagnostic.zip";function Pr(n){return ht("/admin/api/session",{password:n})}async function Tr(){await fetch("/admin/api/session",{method:"DELETE"})}function Cr(n,t){return ht("/admin/api/session/recovery",{code:n,password:t})}function qa(){return Jt("/admin/api/config")}function Or(n){return Ja("PUT","/admin/api/config",n)}function Rr(){return ht("/admin/api/config/confirm",{})}async function Ar(){return(await Jt("/admin/api/config/versions")).versions}function Nr(n){return ht("/admin/api/config/restore",{version:n})}async function $r(){return(await Jt("/admin/api/ports")).ports}async function Dr(){return(await Jt("/admin/api/printers")).printers}async function Ir(){return(await ht("/admin/api/printers/discover",{})).printers}function Ur(n){return ht("/admin/api/scale/detect",{port:n})}async function ja(n,t){return(await ht("/admin/api/scale/capture",{port:n,seconds:t})).frames}function Ha(n){return ht("/admin/api/printer/test?what="+n,{})}function Fr(n,t,s,c){const h=new URLSearchParams({template:n,demo:t?"1":"0",dual:s?"1":"0"});return h.set("t",String(c)),"/admin/api/label/preview.png?"+h.toString()}async function Mr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/journal?"+t)).weighings}function Br(n){return"/admin/api/journal/export.csv?"+new URLSearchParams(n).toString()}async function Vr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/technical?"+t)).entries}function Wr(n){const t=n===void 0?"":"?id="+String(n);return Jt("/admin/api/imports"+t)}function Gr(){return ht("/admin/api/catalog/reload",{})}function Hr(){return ht("/admin/api/catalog/forget-quarantine",{})}function Pa(n,t){return ht("/admin/api/products/"+encodeURIComponent(n)+"/decision",t)}function Jr(n){return ht("/admin/api/replay",{frame:n})}async function Jt(n){const t=await fetch(n,{headers:{accept:"application/json"}});return Sa(t,"GET "+n)}function ht(n,t){return Ja("POST",n,t)}async function Ja(n,t,s){const c=await fetch(t,{method:n,headers:{"content-type":"application/json"},body:JSON.stringify(s)});return Sa(c,n+" "+t)}async function Sa(n,t){const s=await n.text();if(!n.ok){const c=Ka(s);throw new It(n.status,Xr(s,t,n.status)+Kr(n),c?.code??"",c?.faults??[])}if(s!=="")return JSON.parse(s)}function Kr(n){if(n.status!==429)return"";const t=Number(n.headers.get("Retry-After")??"");if(!Number.isFinite(t)||t<=0)return"";const s=Math.ceil(t/60);return s>1?` Réessayez dans ${String(s)} minutes.`:" Réessayez dans une minute."}function Xr(n,t,s){const c=Ka(n);return c?.message!==void 0&&c.message!==""?c.message:`${t} a répondu ${String(s)}.`}function Ka(n){try{return JSON.parse(n)}catch{return null}}function Yr(){return Jt("/admin/api/update")}function Qr(){return ht("/admin/api/update/check",{})}function Zr(n){return ht("/admin/api/update/apply",{version:n})}class en{constructor(t){this.admin=t}admin;#e=$(null);get config(){return e(this.#e)}set config(t){v(this.#e,t,!0)}#a=$("");get fingerprint(){return e(this.#a)}set fingerprint(t){v(this.#a,t,!0)}#r=$(Lt([]));get retired(){return e(this.#r)}set retired(t){v(this.#r,t,!0)}#n=$(null);get pending(){return e(this.#n)}set pending(t){v(this.#n,t,!0)}#s=$(Lt([]));get faults(){return e(this.#s)}set faults(t){v(this.#s,t,!0)}#l=$(!1);get dirty(){return e(this.#l)}set dirty(t){v(this.#l,t,!0)}async load(){const t=await this.admin.load(()=>qa());t!==null&&(this.config=t.config,this.fingerprint=t.config_fingerprint,this.retired=t.retired_keys??[],this.pending=t.pending_confirmation,this.faults=[],this.dirty=!1)}value(t){let s=this.config;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}text(t){const s=this.value(t);return s==null?"":String(s)}number(t){const s=this.value(t);return typeof s=="number"?s:Number(s??0)}flag(t){return this.value(t)===!0}set(t,s){if(this.config===null)return;const c=t.split("."),h=c.pop();if(h===void 0)return;let x=this.config;for(const O of c){const A=x[O];(A===null||typeof A!="object")&&(x[O]={}),x=x[O]}x[h]=s,this.dirty=!0}unset(t){const s=t.split("."),c=s.pop();if(c===void 0||this.config===null)return!1;let h=this.config;for(const x of s){if(h===null||typeof h!="object")return!1;h=h[x]}return h===null||typeof h!="object"?!1:(delete h[c],this.dirty=!0,!0)}async save(){if(this.config===null)return!1;this.faults=[];const t=this.config;let s;try{s=await Or(t)}catch(c){if(Ta(c))throw c;return this.admin.report(c),this.faults=tn(this.admin),!1}return this.config=s.config,this.fingerprint=s.config_fingerprint,this.retired=s.retired_keys??[],this.pending=s.pending_confirmation,this.dirty=!1,!0}async confirm(){try{await Rr()}catch(t){if(Ta(t))throw t;this.admin.report(t);return}this.admin.notice="La configuration est confirmée.",this.pending=null,await this.admin.refresh()}dropRetired(t){if(t.includes("[")){this.admin.actionError=`« ${t} » est logée dans un tableau : cet écran ne sait pas la retirer de là. Modifiez le fichier de configuration lui-même.`;return}this.unset(t)&&(this.retired=this.retired.filter(s=>s!==t))}}function tn(n){return n.lastFaults}function Ta(n){return n instanceof It&&(n.status===401||n.status===409)}const an={"station.number":"Numéro du poste","station.name":"Nom du poste","station.coop":"Nom de la coopérative","network.listen":"Adresse d’écoute","network.admin_on_lan":"Administration accessible depuis le réseau","ui.language":"Langue","ui.sound":"Son","ui.idle_timeout_s":"Retour à l’accueil après (secondes)","ui.reprint_window_s":"Réimpression possible pendant (secondes)","ui.show_grid_prices":"Afficher les prix sur les tuiles","scale.type":"Protocole de la balance","scale.present":"Ce poste a une balance","scale.manual_entry_allowed":"Saisie manuelle autorisée","scale.degrade_after_s":"Passer en mode dégradé après (secondes)","scale.options.port":"Port série","scale.options.baud":"Vitesse (bauds)","scale.options.bits":"Bits de données","scale.options.parity":"Parité","scale.options.stop":"Bits d’arrêt","scale.options.backoff_min_ms":"Attente minimale avant réessai (ms)","scale.options.backoff_max_ms":"Attente maximale avant réessai (ms)","printer.type":"Pilote d’impression","printer.template":"Gabarit d’étiquette","printer.options.transport":"Transport","printer.options.queue":"File d’impression","printer.options.path":"Fichier de périphérique","printer.options.address":"Adresse réseau","printer.options.darkness":"Noircissement","printer.options.speed":"Vitesse d’impression","printer.options.offset_x":"Décalage horizontal (dots)","printer.options.offset_y":"Décalage vertical (dots)","printer.options.invert_bits":"Inverser les points","printer.options.copies":"Exemplaires","printer.options.roll_capacity":"Étiquettes par rouleau","pricing.amount_rounding":"Arrondi du montant","pricing.unit_price_rounding":"Arrondi du prix au kilo","pricing.primary_code":"Tarif principal","pricing.reference_code":"Tarif de référence","barcode.verify_reference_check_digit":"Vérifier la clé de contrôle","limits.empty_max_g":"Plateau considéré vide en dessous de (g)","limits.basket_check_enabled":"Vérifier la présence du panier","limits.basket_min_g":"Poids du panier, borne basse (g)","limits.basket_max_g":"Poids du panier, borne haute (g)","limits.min_weight_g":"Poids minimum accepté","limits.max_weight_g":"Poids maximum accepté","limits.max_tare_g":"Tare maximale","limits.min_units":"Unités minimum","limits.max_units":"Unités maximum","limits.max_amount_cents":"Montant maximum (centimes)","stability.mode":"Exigence de stabilité","stability.min_duration_ms":"Durée de stabilité (ms)","stability.tolerance_g":"Tolérance de stabilité (g)","stability.timeout_ms":"Délai d’attente de stabilité (ms)","stability.on_timeout":"Au bout du délai","stability.min_latch_rate":"Taux d’accroche minimal","stability.latch_rate_window_ms":"Fenêtre de mesure du taux (ms)","stability.expiry_floor_ms":"Péremption, plancher (ms)","stability.expiry_ceiling_ms":"Péremption, plafond (ms)","stability.expiry_factor":"Péremption, facteur","catalog.type":"Où le poste va chercher le catalogue","catalog.options.directory":"Répertoire surveillé","catalog.options.url":"Adresse du serveur","catalog.options.username":"Compte","catalog.options.password":"Mot de passe","catalog.options.separator":"Séparateur du CSV","catalog.options.poll_interval_s":"Vérifier toutes les (secondes)","catalog.options.stable_polls":"Vérifications avant lecture","catalog.options.max_file_size_mb":"Taille maximale du fichier (Mo)","catalog.options.max_image_size_kb":"Taille maximale d’une image (Ko)","catalog.options.min_readable_ratio":"Part minimale de lignes lisibles","catalog.options.max_weighable_drop":"Baisse maximale des produits pesables","catalog.options.max_archives":"Archives conservées","catalog.options.archive_days":"Archives conservées (jours)","catalog.options.failures_before_reject":"Échecs avant mise en quarantaine","catalog.images.source":"Origine des photos","catalog.images.path":"Répertoire des photos","catalog.fallback_category":"Rayon par défaut","journal.max_rows":"Pesées conservées","journal.max_days":"Pesées conservées (jours)","journal.max_technical":"Événements techniques conservés","admin.session_minutes":"Durée d’une session (minutes)","admin.attempts_per_minute":"Tentatives par minute","maintenance.weekly_integrity_check":"Contrôle d’intégrité hebdomadaire","maintenance.disk_alert_mb":"Alerte disque en dessous de (Mo)"};function Ft(n){return an[n]??n}const rn={station:"l’identité du poste",network:"le réseau",ui:"l’écran client",scale:"la balance",printer:"l’imprimante",pricing:"les tarifs",barcode:"le code-barres",limits:"les garde-fous",stability:"la stabilité",catalog:"le catalogue",journal:"le journal",maintenance:"la maintenance"};function nn(n){return rn[n]??n}const sn={scale:"balance",printer:"imprimante",catalog:"catalogue",ui:"écran",config:"configuration",http:"réseau",system:"système"};function Xa(n){return sn[n]??"origine inconnue"}const ba="openscale.admin.preferences",Ya="technical";class ln{#e=$(Lt(on()));get showTechnicalNames(){return e(this.#e)}set showTechnicalNames(t){v(this.#e,t,!0)}toggleTechnicalNames(){this.showTechnicalNames=!this.showTechnicalNames,un(this.showTechnicalNames)}}function on(){try{return globalThis.localStorage?.getItem(ba)===Ya}catch{return!1}}function un(n){try{n?globalThis.localStorage?.setItem(ba,Ya):globalThis.localStorage?.removeItem(ba)}catch{}}const Gt=new ln,cn=3e3,dn=["hardware","label","rules","catalog","journal","station","update"];function Ca(n){return dn.includes(n)}function vn(n){return n instanceof It&&(n.status===401||n.status===409)}class pn{constructor(t=cn){this.periodMs=t}periodMs;#e=$(null);get health(){return e(this.#e)}set health(t){v(this.#e,t,!0)}#a=$("dashboard");get page(){return e(this.#a)}set page(t){v(this.#a,t,!0)}#r=$(!1);get expert(){return e(this.#r)}set expert(t){v(this.#r,t,!0)}#n=$("");get linkError(){return e(this.#n)}set linkError(t){v(this.#n,t,!0)}#s=$("");get actionError(){return e(this.#s)}set actionError(t){v(this.#s,t,!0)}#l=$(!1);get needsFirstPassword(){return e(this.#l)}set needsFirstPassword(t){v(this.#l,t,!0)}#d=$("");get notice(){return e(this.#d)}set notice(t){v(this.#d,t,!0)}#v=$(!1);get busy(){return e(this.#v)}set busy(t){v(this.#v,t,!0)}#p=$(Lt([]));get lastFaults(){return e(this.#p)}set lastFaults(t){v(this.#p,t,!0)}#f=$(null);get pending(){return e(this.#f)}set pending(t){v(this.#f,t,!0)}#i=null;#o=null;start(){this.refresh(),this.#i=setInterval(()=>{this.refresh()},this.periodMs)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null}async refresh(){try{this.health=await wr(),this.linkError=""}catch(t){this.linkError=Oa(t)}}#u(){this.busy=!0,this.notice="",this.actionError="",this.needsFirstPassword=!1}#t(t){this.actionError=Oa(t),this.needsFirstPassword=t instanceof It&&t.status===409,this.#m(t)}async run(t){this.#u();try{const s=await t();this.notice=s.message}catch(s){this.#t(s)}finally{this.busy=!1}await this.refresh()}async login(t){this.#u();try{return await Pr(t),this.expert=!0,this.notice="Session d’administration ouverte.",!0}catch(s){return this.expert=!1,this.#t(s),!1}finally{this.busy=!1}}async recover(t,s){this.#u();try{const c=await Cr(t,s);return this.expert=!0,this.notice=c.warning??"Le mot de passe est remplacé et la session est ouverte.",!0}catch(c){return this.#t(c),!1}finally{this.busy=!1}}async protect(t){try{return await t()}catch(s){if(!vn(s))return this.#t(s),null;if(!await this.#g(s))return null;try{return await t()}catch(c){return this.#t(c),null}}}#g(t){return this.pending={kind:t.status===409?"first-password":"password",message:t.message},new Promise(s=>{this.#o=s})}async answerPassword(t){await this.login(t)&&this.#c(!0)}async answerRecovery(t,s){await this.recover(t,s)&&this.#c(!0)}cancelPassword(){this.#c(!1)}#c(t){this.pending=null,this.notice="";const s=this.#o;this.#o=null,s?.(t)}async logout(){await Tr(),this.expert=!1,this.page="dashboard",this.notice="Session d’administration fermée."}open(t){this.actionError="",this.notice="",this.needsFirstPassword=!1,this.page=t}async load(t){try{const s=await t();return this.actionError="",this.lastFaults=[],s}catch(s){return this.lastFaults=s instanceof It?s.faults:[],this.#t(s),null}}report(t){this.lastFaults=t instanceof It?t.faults:[],this.#t(t)}#m(t){t instanceof It&&t.needsPassword&&(this.expert=!1)}}function Oa(n){return n instanceof It?n.message:n instanceof Error?"Le poste n’a pas répondu : "+n.message:"Le poste n’a pas répondu."}var fn=u('clé'),gn=u('');function at(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"busy",3,!1),h=vt(t,"disabled",3,!1),x=vt(t,"protected",3,!1),O=vt(t,"act",3,void 0);var A=gn();let T;var H=a(A),B=r(H);{var se=_e=>{var ye=fn();l(_e,ye)};m(B,_e=>{x()&&_e(se)})}p(()=>{T=Ht(A,1,`act ${s()??""}`,"svelte-5tpq0o",T,{"touch-target":s()==="destructive",busy:c()}),Be(A,"data-kind",s()),Be(A,"data-act",O()),A.disabled=h()||c(),i(H,`${(c()?"En cours…":t.label)??""} `)}),tt("click",A,function(..._e){t.onrun?.apply(this,_e)}),l(n,A)}At(["click"]);var mn=u(`

                                                                    Le code de secours a été tiré à l’installation de ce poste et imprimé - sur sa fiche, rangée dans le classeur du magasin. Il n’existe nulle part ailleurs.

                                                                    `,1),hn=u(`

                                                                    Oublié ? Le code de secours de la fiche d’installation en repose un. - Un responsable peut aussi le faire en ligne de commande, avec openscale config password.

                                                                    `,1),_n=u('

                                                                    '),bn=u('');function wn(n,t){Tt(t,!0);const s=o(()=>t.admin.pending?.kind==="first-password");let c=$(""),h=$("");const x=8,O=o(()=>e(s)?e(h).trim().length>0&&e(c).length>=x:e(c).length>0);async function A(){!e(O)||t.admin.busy||(e(s)?await t.admin.answerRecovery(e(h),e(c)):await t.admin.answerPassword(e(c)),v(c,""),v(h,""))}var T=bn(),H=a(T),B=a(H),se=a(B),_e=a(se);dr(_e,{name:"settings",size:"1.5rem"});var ye=r(se,2),ce=a(ye),j=r(B,2),V=a(j),le=r(j,2);{var re=E=>{var b=mn(),R=r(X(b),2),J=r(a(R),2);Ea(J);var be=r(R,2),xe=a(be);xe.textContent="Nouveau mot de passe — 8 caractères au moins";var Ve=r(xe,2);tt("keydown",J,Ae=>Ae.key==="Enter"&&void A()),ia(J,()=>e(h),Ae=>v(h,Ae)),tt("keydown",Ve,Ae=>Ae.key==="Enter"&&void A()),ia(Ve,()=>e(c),Ae=>v(c,Ae)),l(E,b)},W=E=>{var b=hn(),R=X(b),J=r(a(R),2);Ea(J),tt("keydown",J,be=>be.key==="Enter"&&void A()),ia(J,()=>e(c),be=>v(c,be)),l(E,b)};m(le,E=>{e(s)?E(re):E(W,-1)})}var D=r(le,2);{var Y=E=>{var b=_n(),R=a(b);p(()=>i(R,t.admin.actionError)),l(E,b)};m(D,E=>{t.admin.actionError!==""&&E(Y)})}var z=r(D,2),P=a(z);at(P,{label:"Annuler",onrun:()=>t.admin.cancelPassword()});var G=r(P,2);{let E=o(()=>e(s)?"Poser ce mot de passe":"Continuer"),b=o(()=>!e(O)||t.admin.busy);at(G,{kind:"write",get label(){return e(E)},get disabled(){return e(b)},onrun:()=>{A()}})}p(()=>{i(ce,e(s)?"Ce poste n’a pas encore de mot de passe":"Mot de passe d’administration"),i(V,t.admin.pending?.message??"")}),l(n,T),Ct()}At(["keydown"]);var yn=u(' '),xn=u(""),kn=u(""),qn=u('

                                                                    '),Sn=u(" "),Ln=u('

                                                                    '),En=u('
                                                                    ');function _t(n,t){Tt(t,!0);const s=vt(t,"hint",3,""),c=vt(t,"kind",3,"text"),h=vt(t,"disabled",3,!1),x=vt(t,"fault",3,""),O=vt(t,"allowed",19,()=>[]),A=o(()=>`field-${t.path.replace(/\./gu,"-")}`);var T=En();let H;var B=a(T),se=a(B),_e=a(se),ye=r(se,2);{var ce=z=>{var P=yn(),G=a(P);p(()=>i(G,t.path)),l(z,P)};m(ye,z=>{Gt.showTechnicalNames&&z(ce)})}var j=r(B,2),V=r(j,2);{var le=z=>{var P=kn();rt(P,20,O,G=>G,(G,E)=>{var b=xn(),R={};p(()=>{R!==(R=E)&&(b.value=(b.__value=E)??"")}),l(G,b)}),p(()=>Be(P,"id",e(A)+"-allowed")),l(z,P)};m(V,z=>{O().length>0&&z(le)})}var re=r(V,2);{var W=z=>{var P=qn(),G=a(P);p(()=>i(G,s())),l(z,P)};m(re,z=>{s()!==""&&z(W)})}var D=r(re,2);{var Y=z=>{var P=Ln(),G=a(P),E=r(G);{var b=R=>{var J=Sn(),be=a(J);p(xe=>i(be,`Valeurs acceptées : ${xe??""}.`),[()=>O().join(", ")]),l(R,J)};m(E,R=>{O().length>0&&R(b)})}p(()=>i(G,`${x()??""} `)),l(z,P)};m(D,z=>{x()!==""&&z(Y)})}p(()=>{H=Ht(T,1,"field svelte-1oilv5s",null,H,{refused:x()!==""}),Be(B,"for",e(A)),i(_e,t.label),Be(j,"id",e(A)),Be(j,"type",c()),na(j,t.value),j.disabled=h(),Be(j,"list",O().length>0?e(A)+"-allowed":void 0)}),tt("input",j,z=>t.onchange(z.currentTarget.value)),l(n,T),Ct()}At(["input"]);const Ra=1e3;function da(n){const t=La(n);return t===null?"":[ta(t.getDate()),ta(t.getMonth()+1),String(t.getFullYear())].join("/")}function Mt(n){const t=La(n);return t===null?"":`${da(n)} à ${ta(t.getHours())}:${ta(t.getMinutes())}`}function zn(n){const t=La(n);return t===null?"":[ta(t.getHours()),ta(t.getMinutes()),ta(t.getSeconds())].join(":")}function wa(n){const t=n/1e6;return t<1e3?`${Math.round(t)} Mo`:`${Qa(t/1e3)} Go`}function me(n){const t=String(Math.abs(Math.trunc(n))),s=[];for(let c=t.length;c>0;c-=3)s.unshift(t.slice(Math.max(0,c-3),c));return(n<0?"-":"")+s.join(" ")}function ca(n){return n0&&x.push({count:Pt(n.anomalies_count),label:"anomalies",note:"à corriger dans Odoo",link:Aa(n.anomalies_count)}),n.unit_mismatches_count>0){const O=n.unit_mismatches_count;x.push({count:"+ "+Pt(O),label:O===1?"unité divergente":"unités divergentes",note:"pesable, unité à corriger",link:Aa(O)})}return{headline:`Catalogue du ${da(n.occurred_at)} — ${Pt(s)} produits reçus`,lines:x,oneLine:Pn(n)}}function Pn(n){const t=[`${Pt(n.rows_read_count)} reçus`,`${Pt(n.weighable_count)} pesables`,`${Pt(n.not_weighable_count)} non pesables`,`${Pt(n.anomalies_count)} anomalies`],s=n.unit_mismatches_count;return s>0&&t.push(`${Pt(s)} ${s===1?"unité divergente":"unités divergentes"}`),t.join(" · ")}function Aa(n){return n===1?"voir la ligne":`voir les ${Pt(n)} lignes`}function Tn(n){return n.map(t=>`${Cn(t)} (${Pt(t.count)})`).join(", ")}function Cn(n){switch(n.code){case"PREPACKAGED_PRODUCT":return"préemballés";case"INTERNAL_CODE_NOT_WEIGHABLE":return n.value===""?"code interne":"code interne "+n.value;case"NO_BARCODE":return"sans code-barres";default:return n.code}}function Pt(n){return String(n)}var On=u(' '),Rn=u(''),An=u('
                                                                    '),Nn=u('

                                                                    ');function Za(n,t){Tt(t,!0);const s=o(()=>jn(t.record,t.motives));var c=Nn(),h=a(c),x=a(h),O=r(h,2);rt(O,21,()=>e(s).lines,H=>H.label,(H,B)=>{var se=An(),_e=a(se),ye=a(_e),ce=r(_e,2),j=a(ce),V=a(j),le=r(j,2);{var re=Y=>{var z=On(),P=a(z);p(()=>i(P,e(B).note)),l(Y,z)};m(le,Y=>{e(B).note!==""&&Y(re)})}var W=r(le,2);{var D=Y=>{var z=Rn(),P=a(z);p(()=>i(P,e(B).link)),tt("click",z,()=>t.onshowrows(e(B).label==="anomalies"?"anomalies":"units")),l(Y,z)};m(W,Y=>{e(B).link!==""&&t.onshowrows!==void 0&&Y(D)})}p(()=>{Be(se,"data-row",e(B).label),i(ye,e(B).count),i(V,e(B).label)}),l(H,se)});var A=r(O,2),T=a(A);p(()=>{i(x,e(s).headline),i(T,e(s).oneLine)}),l(n,c),Ct()}At(["click"]);var $n=u('

                                                                    '),Dn=u('

                                                                    ');function Qe(n,t){const s=vt(t,"note",3,"");var c=Dn(),h=a(c),x=a(h),O=r(h,2);{var A=H=>{var B=$n(),se=a(B);p(()=>i(se,s())),l(H,B)};m(O,H=>{s()!==""&&H(A)})}var T=r(O,2);mr(T,()=>t.children),p(()=>i(x,t.title)),l(n,c)}const _a=(n,t=sa,s=sa)=>{var c=Un(),h=a(c);rt(h,21,t,fa,(x,O)=>{var A=In(),T=a(A),H=a(T),B=r(T,2),se=a(B),_e=r(B,2),ye=a(_e),ce=r(_e,2),j=a(ce);p(V=>{i(H,`ligne ${V??""}`),i(se,e(O).product_id),i(ye,e(O).value),i(j,e(O).message)},[()=>me(e(O).csv_line)]),l(x,A)}),p(()=>Be(c,"data-rows",s())),l(n,c)};var In=u('
                                                                  • '),Un=u('
                                                                      '),Fn=u('

                                                                      Lecture des réglages du poste…

                                                                      '),Mn=u(`

                                                                      Le poste y cherche le fichier , et le supprime - une fois lu : c’est ce qui dit au producteur que la livraison est prise.

                                                                      `,1),Bn=u(`

                                                                      Sur un serveur WebDAV, le dépôt d’un fichier CSV depuis cet écran n’est plus - possible : le poste n’a plus de répertoire local où l’écrire. C’est le seul recours - du jour de la mise en service.

                                                                      `,1),Vn=u(`

                                                                      Ce poste ne déclare aucune source : choisissez-en une ci-dessus, sinon il n’ira - chercher aucun catalogue.

                                                                      `),Wn=u('
                                                                      ',1),Gn=u('

                                                                      Aucun import enregistré sur ce poste.

                                                                      '),Hn=u(`

                                                                      « Oublier la quarantaine » fait relire un fichier que le poste avait écarté : c’est le - seul geste de cette page qui puisse remettre en service un catalogue refusé.

                                                                      `,1),Jn=u('

                                                                      '),Kn=u(`

                                                                      Déposez ici le fichier clé

                                                                      Ce dépôt remplace toute la grille par le fichier apporté : il change ce que le poste - vend, et le mot de passe est donc demandé au moment du dépôt.

                                                                      `,1),Xn=u('

                                                                      '),Yn=u('

                                                                      Aucune anomalie sur le dernier import.

                                                                      '),Qn=u('

                                                                      ',1),Zn=u('

                                                                      '),es=u('

                                                                      Aucune unité divergente sur le dernier import.

                                                                      '),ts=u('

                                                                      ',1),as=u('

                                                                      '),rs=u('

                                                                      Aucun produit non pesable sur le dernier import.

                                                                      '),ns=u('

                                                                      ',1),ss=u('

                                                                      Aucun import enregistré : rien n’a encore pu être retiré.

                                                                      '),ls=u('

                                                                      Aucun produit retiré par le dernier import.

                                                                      '),is=u(`

                                                                      Ils restent enregistrés avec leur historique : une étiquette déjà collée reste - lisible en caisse, et un produit qui revient dans un prochain fichier retrouve sa - tuile. Ce poste n’en publie encore que le nombre — leurs noms se lisent dans Odoo, - en comparant avec l’export précédent.

                                                                      `,1),os=u('
                                                                    • '),us=u(`

                                                                        Un produit retiré de la grille ne se trouve plus ici : il se reprend dans - « Décisions en vigueur », plus bas.

                                                                        `,1),cs=u(`

                                                                        Sans motif, le poste refuse la décision. Le seul acte qui s’en passe est celui qui - EFFACE la décision en vigueur : proposer de nouveau un produit qui ne porte aucune - dérogation.

                                                                        `),ds=u(`

                                                                        Produit choisi :

                                                                        Ce produit est-il proposé dans la grille ?

                                                                        La dérogation s’enregistre seule : elle ne remet pas dans la grille un produit - qui en a été retiré, et retirer un produit n’efface pas sa dérogation.

                                                                        `),vs=u(' ',1),ps=u('

                                                                        Aucune décision locale : la grille est celle du fichier.

                                                                        '),fs=u('
                                                                      • '),gs=u('

                                                                          ',1),ms=u('

                                                                          '),hs=u('

                                                                          Aucun import dans l’historique.

                                                                          '),_s=u(' '),bs=u('

                                                                          QuandFichierSourceRésultatMotifLuesPesablesNon pesablesAnomaliesRetirés
                                                                          ',1),ws=u('
                                                                          ');function ys(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=20,h=50,x=20,O=["NO_BARCODE","PREPACKAGED_PRODUCT","INTERNAL_CODE_NOT_WEIGHABLE"],A={applied:"appliqué",unchanged:"identique au précédent",rejected:"refusé",failed:"échec"},T={local_drop:"dépôt local",webdav:"WebDAV",manual:"déposé sur l’écran"},H={local_drop:["catalog.options.directory"],webdav:["catalog.options.url","catalog.options.username","catalog.options.password"]},B=o(()=>t.draft.text("catalog.type"));let se=$(Lt([])),_e=$(Lt([])),ye=$(Lt([])),ce=$(""),j=$(""),V=$(""),le=$(""),re=$(!1),W=$(""),D=$(""),Y=$("loading"),z=$("loading");const P=o(()=>s().busy||e(D)!==""),G=o(()=>e(_e).filter(k=>k.issue==="anomaly")),E=o(()=>e(_e).filter(k=>O.includes(k.code))),b=o(()=>e(_e).filter(k=>k.code==="UNIT_MISMATCH")),R=o(()=>new Map(e(ye).map(k=>[k.id,k.name]))),J=o(()=>e(ce)===""?[]:vr(e(ye),pr,e(ce))),be=o(()=>e(J).slice(0,c)),xe=o(()=>e(G).slice(0,h)),Ve=o(()=>e(b).slice(0,h)),Ae=o(()=>e(E).slice(0,h)),w=o(()=>[...t.health.decisions].sort((k,K)=>K.decided_at.localeCompare(k.decided_at))),N=o(()=>e(w).slice(0,x)),_=o(()=>e(j)===""?null:e(w).find(k=>k.product_id===e(j))??null),F=o(()=>e(_)===null?!0:e(_).offered),I=o(()=>e(_)?.min_weight_g??null),Q=o(()=>e(le).trim()===""?null:Number(e(le))),qe=o(()=>e(Q)!==null&&Number.isFinite(e(Q))&&e(Q)>0),oe=o(()=>e(V).trim()),S=o(()=>e(oe)!==""||!Le(!1,e(I))),Z=o(()=>e(oe)!==""||!Le(!0,e(I))),ie=o(()=>e(qe)&&(e(oe)!==""||!Le(e(F),e(Q)))),pe=o(()=>e(oe)!==""||!Le(e(F),null)),we=o(()=>t.health.catalog?.products_withdrawn_count??0),He=o(()=>e(se).find(k=>t.health.catalog!==null&&k.id`${me(e(we))} ${e(we)>1?"produits retirés":"produit retiré"}`+(e(He)===null?".":` depuis l’import du ${Mt(e(He).occurred_at)}.`)),fe=o(()=>e(J).length>e(be).length?`${me(e(be).length)} produits affichés sur ${me(e(J).length)} trouvés — précisez votre recherche.`:`${me(e(J).length)} ${e(J).length>1?"produits trouvés":"produit trouvé"}.`),d=o(()=>`${me(e(se).length)} ${e(se).length>1?"imports affichés":"import affiché"} : le poste n’en publie jamais plus de vingt.`),y=o(()=>e(Y)==="loading"?"Lecture des signalements du dernier import…":"Les signalements du dernier import n’ont pas pu être lus : cet écran ne sait pas ce qu’ils disent."),ee=o(()=>e(Y)==="loading"?"Lecture de l’historique des imports…":"L’historique des imports n’a pas pu être lu : cet écran ne sait pas ce qu’il contient.");let f;aa(()=>{const k=t.health.catalog?.id??null;k!==f&&(f=k,q())});async function q(){const k=await s().load(()=>Wr(t.health.catalog?.id));k===null?v(Y,"unread"):(v(se,k.imports,!0),v(_e,k.findings,!0),v(Y,"read"));const K=await s().load(()=>Ba());if(K===null){v(z,"unread");return}v(ye,K.products,!0),v(z,"read")}async function L(k,K){v(D,k,!0),s().actionError="",s().notice="";try{const ue=await s().protect(K);return ue===null?!1:(s().notice=ue.message,await s().refresh(),!0)}finally{v(D,"")}}function he(k){t.draft.set("catalog.type",k);for(const[K,ue]of Object.entries(H))if(K!==k)for(const Re of ue)t.draft.unset(Re)}function ne(k){return t.draft.faults.find(K=>K.field===k)?.message??""}function Se(k){v(j,k,!0),v(V,"");const K=e(w).find(ue=>ue.product_id===k)?.min_weight_g??null;v(le,K===null?"":String(K),!0)}function Le(k,K){return!(k&&K===null)}async function de(k){if(e(j)==="")return;const K=e(j),ue=e(I),Re=e(oe);await L("offered",()=>Pa(K,{offered:k,min_weight_g:ue,reason:Re}))&&v(V,"")}async function M(k){if(e(j)==="")return;const K=e(j),ue=e(F),Re=e(oe);await L(k===null?"waiver-off":"waiver",()=>Pa(K,{offered:ue,min_weight_g:k,reason:Re}))&&v(V,"")}async function Ie(k){if(v(re,!1),k!=null){v(W,""),s().actionError="",s().notice="",v(D,"import");try{const K=await s().protect(()=>Ga(k));if(K===null)return;v(W,`${k.name} : ${me(K.rows_read_count)} lignes lues, ${me(K.weighable_count)} pesables. `+(K.reason===""?"Le fichier est déposé ; son résultat s’inscrira dans l’historique des imports.":K.reason)),await s().refresh()}finally{v(D,"")}}}function Me(k){k.preventDefault(),v(re,!1);const K=k.dataTransfer?.files.item(0);if(e(P)){v(W,`${K?.name??"Ce fichier"} n’a pas été déposé : un acte est déjà en cours sur cette page. Réessayez quand il aura répondu.`);return}Ie(K)}function Ee(k){const K=k.files?.item(0);k.value="",Ie(K)}function Te(k){const K=e(R).get(k);return K!==void 0?K:e(z)==="loading"?"Lecture du nom…":e(z)==="unread"?"Nom inconnu : le catalogue n’a pas pu être lu":"Produit absent du catalogue en service"}function Je(k,K,ue,Re){const We=K>1?Re:ue;return k>=K?`${me(K)} ${We}.`:`${me(k)} lignes affichées sur ${me(K)} ${We}.`}function ae(k){const K=[];return k.offered||K.push("retiré de la grille"),k.min_weight_g!==null&&K.push(`peut peser à partir de ${me(k.min_weight_g)} g`),K.length===0?"aucune restriction":K.join(" · ")}function De(k){const K=A[k.result]??"résultat inconnu";return k.code===""?K:`${K} (${k.code})`}function ve(k){return T[k.source]??"source inconnue"}var ze=ws(),Oe=a(ze);Qe(Oe,{title:"Où le poste va chercher le catalogue",children:(k,K)=>{var ue=Wn(),Re=X(ue),We=a(Re),Ye=a(We),Ke=r(We,2),U=a(Ke),te=r(Re,2);{var ge=Pe=>{var Ue=Fn();l(Pe,Ue)},Ne=Pe=>{var Ue=Mn(),Ge=X(Ue);{let it=o(()=>Ft("catalog.options.directory")),dt=o(()=>t.draft.text("catalog.options.directory")),gt=o(()=>ne("catalog.options.directory"));_t(Ge,{get label(){return e(it)},path:"catalog.options.directory",get value(){return e(dt)},hint:"Laissez vide pour le répertoire du poste, celui que le service crée lui-même. Un répertoire nommé ici doit exister : le poste ne le crée pas.",get fault(){return e(gt)},onchange:xt=>t.draft.set("catalog.options.directory",xt)})}var ft=r(Ge,2),ot=r(a(ft)),ct=a(ot);p(()=>i(ct,`flv_${t.health.station??""}.csv`)),l(Pe,Ue)},ke=Pe=>{var Ue=Bn(),Ge=X(Ue);{let ct=o(()=>Ft("catalog.options.url")),it=o(()=>t.draft.text("catalog.options.url")),dt=o(()=>ne("catalog.options.url"));_t(Ge,{get label(){return e(ct)},path:"catalog.options.url",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.url",gt)})}var ft=r(Ge,2);{let ct=o(()=>Ft("catalog.options.username")),it=o(()=>t.draft.text("catalog.options.username")),dt=o(()=>ne("catalog.options.username"));_t(ft,{get label(){return e(ct)},path:"catalog.options.username",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.username",gt)})}var ot=r(ft,2);{let ct=o(()=>Ft("catalog.options.password")),it=o(()=>ne("catalog.options.password"));_t(ot,{get label(){return e(ct)},path:"catalog.options.password",kind:"password",value:"",hint:"Laissez vide : le mot de passe actuel est conservé.",get fault(){return e(it)},onchange:dt=>t.draft.set("catalog.options.password",dt)})}l(Pe,Ue)},$e=Pe=>{var Ue=Vn();l(Pe,Ue)};m(te,Pe=>{t.draft.config===null?Pe(ge):e(B)==="local_drop"?Pe(Ne,1):e(B)==="webdav"?Pe(ke,2):Pe($e,-1)})}p(()=>{ua(Ye,e(B)==="local_drop"),ua(U,e(B)==="webdav")}),tt("change",Ye,()=>he("local_drop")),tt("change",U,()=>he("webdav")),l(k,ue)},$$slots:{default:!0}});var nt=r(Oe,2);Qe(nt,{title:"Dernier import",children:(k,K)=>{var ue=Hn(),Re=X(ue);{var We=ke=>{var $e=Gn();l(ke,$e)},Ye=ke=>{Za(ke,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives}})};m(Re,ke=>{t.health.catalog===null?ke(We):ke(Ye,-1)})}var Ke=r(Re,2),U=a(Ke),te=r(Ke,2),ge=a(te);{let ke=o(()=>e(D)==="reload");at(ge,{act:"reload",kind:"write",label:"Recharger le catalogue",protected:!0,get busy(){return e(ke)},get disabled(){return e(P)},onrun:()=>{L("reload",Gr)}})}var Ne=r(ge,2);{let ke=o(()=>e(D)==="quarantine");at(Ne,{act:"quarantine",kind:"destructive",label:"Oublier la quarantaine",protected:!0,get busy(){return e(ke)},get disabled(){return e(P)},onrun:()=>{L("quarantine",Hr)}})}p(()=>i(U,t.health.catalog_source===null?"Aucune source de catalogue publiée par ce poste.":"Source : "+t.health.catalog_source.label)),l(k,ue)},$$slots:{default:!0}});var ut=r(nt,2);Qe(ut,{title:"Déposer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(k,K)=>{var ue=Kn(),Re=X(ue);{var We=Pe=>{var Ue=Jn(),Ge=a(Ue);p(()=>i(Ge,e(W))),l(Pe,Ue)};m(Re,Pe=>{e(W)!==""&&Pe(We)})}var Ye=r(Re,2);let Ke;var U=a(Ye),te=r(a(U)),ge=a(te),Ne=r(U,2),ke=a(Ne),$e=r(ke);p(()=>{Ke=Ht(Ye,1,"drop svelte-3gk7u7",null,Ke,{dropping:e(re),working:e(D)==="import"}),i(ge,`flv_${t.health.station??""}.csv`),i(ke,`${e(D)==="import"?"Import en cours…":"Choisir un fichier"} `),$e.disabled=e(P)}),Qt("dragover",Ye,Pe=>{Pe.preventDefault(),!e(P)&&v(re,!0)}),Qt("dragleave",Ye,()=>v(re,!1)),Qt("drop",Ye,Me),tt("change",$e,Pe=>Ee(Pe.currentTarget)),l(k,ue)},$$slots:{default:!0}});var pt=r(ut,2);Qe(pt,{title:"Anomalies à corriger dans Odoo",note:"Chaque ligne porte son numéro dans le CSV, son motif et la valeur fautive.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=Xn(),ge=a(te);p(()=>i(ge,e(y))),l(U,te)},Ye=U=>{var te=Yn();l(U,te)},Ke=U=>{var te=Qn(),ge=X(te),Ne=a(ge),ke=r(Ne);{var $e=Ue=>{var Ge=yt("Corrigez celles-ci dans Odoo : l’import suivant ne signalera que ce qui reste.");l(Ue,Ge)};m(ke,Ue=>{e(G).length>e(xe).length&&Ue($e)})}var Pe=r(ge,2);_a(Pe,()=>e(xe),()=>"anomalies"),p(Ue=>i(Ne,`${Ue??""} `),[()=>Je(e(xe).length,e(G).length,"anomalie","anomalies")]),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(G).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var je=r(pt,2);Qe(je,{title:"Unités divergentes",note:"Le produit reste proposé : le code-barres fait foi, seul le libellé du prix est faux.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=Zn(),ge=a(te);p(()=>i(ge,e(y))),l(U,te)},Ye=U=>{var te=es();l(U,te)},Ke=U=>{var te=ts(),ge=X(te),Ne=a(ge),ke=r(ge,2);_a(ke,()=>e(Ve),()=>"mismatches"),p($e=>i(Ne,$e),[()=>Je(e(Ve).length,e(b).length,"unité divergente","unités divergentes")]),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(b).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var st=r(je,2);Qe(st,{title:"Produits non pesables",note:"Un inventaire, pas une liste d’erreurs : ces produits portent déjà leur code-barres et n’ont aucune raison d’être pesés.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=as(),ge=a(te);p(()=>i(ge,e(y))),l(U,te)},Ye=U=>{var te=rs();l(U,te)},Ke=U=>{var te=ns(),ge=X(te),Ne=a(ge),ke=r(ge,2);_a(ke,()=>e(Ae),()=>"not-weighable"),p($e=>i(Ne,$e),[()=>Je(e(Ae).length,e(E).length,"produit non pesable","produits non pesables")]),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(E).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var Ze=r(st,2);Qe(Ze,{title:"Produits retirés depuis l’import précédent",note:"Un produit absent du nouveau fichier est marqué retiré à sa date, jamais supprimé.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=ss();l(U,te)},Ye=U=>{var te=ls();l(U,te)},Ke=U=>{var te=is(),ge=X(te),Ne=a(ge);p(()=>i(Ne,e(Xe))),l(U,te)};m(Re,U=>{t.health.catalog===null?U(We):e(we)===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}});var bt=r(Ze,2);Qe(bt,{title:"Décider d’un produit",note:"Retirer un produit et l’autoriser à peser moins sont deux décisions séparées : l’une n’efface pas l’autre.",children:(k,K)=>{var ue=vs(),Re=r(X(ue),2),We=r(Re,2);{var Ye=te=>{var ge=us(),Ne=X(ge),ke=a(Ne),$e=r(Ne,2),Pe=a($e);rt(Pe,21,()=>e(be),Ue=>Ue.id,(Ue,Ge)=>{var ft=os(),ot=a(ft),ct=a(ot),it=r(ot,2),dt=a(it),gt=r(it,2),xt=a(gt);p(()=>{i(ct,e(Ge).name),i(dt,e(Ge).id),i(xt,`${e(Ge).unit_price_text??""}${e(Ge).price_suffix??""}`)}),tt("click",ot,()=>Se(e(Ge).id)),l(Ue,ft)}),p(()=>i(ke,e(fe))),l(te,ge)};m(We,te=>{e(ce)!==""&&te(Ye)})}var Ke=r(We,2);{var U=te=>{var ge=ds(),Ne=a(ge),ke=r(a(Ne)),$e=a(ke),Pe=r(ke),Ue=r(Ne,2),Ge=a(Ue),ft=r(Ue,4),ot=r(ft,2);{var ct=et=>{var qt=cs();l(et,qt)};m(ot,et=>{e(oe)===""&&et(ct)})}var it=r(ot,2),dt=r(a(it),2),gt=a(dt);{var xt=et=>{{let qt=o(()=>e(D)==="offered"),$t=o(()=>e(P)||!e(S));at(et,{act:"offered",kind:"destructive",label:"Ne plus proposer ce produit",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{de(!1)}})}},Nt=et=>{{let qt=o(()=>e(D)==="offered"),$t=o(()=>e(P)||!e(Z));at(et,{act:"offered",kind:"write",label:"Le proposer de nouveau",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{de(!0)}})}};m(gt,et=>{e(F)?et(xt):et(Nt,-1)})}var Bt=r(it,2),Rt=r(a(Bt),2),Vt=r(Rt,2),kt=a(Vt);{let et=o(()=>e(D)==="waiver"),qt=o(()=>e(P)||!e(ie));at(kt,{act:"waiver",kind:"write",label:"Enregistrer la dérogation",protected:!0,get busy(){return e(et)},get disabled(){return e(qt)},onrun:()=>{M(e(Q))}})}var Et=r(kt,2);{var Wt=et=>{{let qt=o(()=>e(D)==="waiver-off"),$t=o(()=>e(P)||!e(pe));at(et,{act:"waiver-off",kind:"write",label:"Retirer la dérogation",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{M(null)}})}};m(Et,et=>{e(I)!==null&&et(Wt)})}p((et,qt)=>{Be(ge,"data-decision",e(j)),i($e,et),i(Pe,` (${e(j)??""})`),i(Ge,`En vigueur : ${qt??""}`),na(Rt,e(le))},[()=>Te(e(j)),()=>e(_)===null?"aucune décision — ce produit suit les règles générales":ae(e(_))]),ia(ft,()=>e(V),et=>v(V,et)),tt("input",Rt,et=>v(le,et.currentTarget.value,!0)),l(te,ge)};m(Ke,te=>{e(j)!==""&&te(U)})}ia(Re,()=>e(ce),te=>v(ce,te)),l(k,ue)},$$slots:{default:!0}});var Ot=r(bt,2);Qe(Ot,{title:"Décisions en vigueur",note:"Ce qu’un humain a décidé, produit par produit. C’est ici que se reprend un produit retiré de la grille.",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=Ke=>{var U=ps();l(Ke,U)},Ye=Ke=>{var U=gs(),te=X(U),ge=a(te),Ne=r(te,2),ke=a(Ne);rt(ke,21,()=>e(N),$e=>$e.product_id,($e,Pe)=>{var Ue=fs(),Ge=a(Ue),ft=r(Ge,2),ot=a(ft),ct=r(ft,2),it=a(ct),dt=r(ct,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),Rt=a(Bt);p((Vt,kt,Et)=>{i(ot,Vt),i(it,e(Pe).product_id),i(gt,kt),i(Nt,e(Pe).reason),i(Rt,Et)},[()=>Te(e(Pe).product_id),()=>ae(e(Pe)),()=>da(e(Pe).decided_at)]),tt("click",Ge,()=>Se(e(Pe).product_id)),l($e,Ue)}),p($e=>i(ge,$e),[()=>Je(e(N).length,e(w).length,"décision en vigueur","décisions en vigueur")]),l(Ke,U)};m(Re,Ke=>{e(w).length===0?Ke(We):Ke(Ye,-1)})}l(k,ue)},$$slots:{default:!0}});var Ut=r(Ot,2);Qe(Ut,{title:"Vingt derniers imports",children:(k,K)=>{var ue=Dt(),Re=X(ue);{var We=U=>{var te=ms(),ge=a(te);p(()=>i(ge,e(ee))),l(U,te)},Ye=U=>{var te=hs();l(U,te)},Ke=U=>{var te=bs(),ge=X(te),Ne=a(ge),ke=r(ge,2),$e=a(ke),Pe=r(a($e));rt(Pe,21,()=>e(se),Ue=>Ue.id,(Ue,Ge)=>{var ft=_s(),ot=a(ft),ct=a(ot),it=r(ot),dt=a(it),gt=r(it),xt=a(gt),Nt=r(gt),Bt=a(Nt),Rt=r(Nt),Vt=a(Rt),kt=r(Rt),Et=a(kt),Wt=r(kt),et=a(Wt),qt=r(Wt),$t=a(qt),Zt=r(qt),ra=a(Zt),g=r(Zt),C=a(g);p((Ce,Fe,lt,mt,wt,zt,jt,St)=>{i(ct,Ce),i(dt,e(Ge).file_name),i(xt,Fe),i(Bt,lt),i(Vt,e(Ge).reason),i(Et,mt),i(et,wt),i($t,zt),i(ra,jt),i(C,St)},[()=>Mt(e(Ge).occurred_at),()=>ve(e(Ge)),()=>De(e(Ge)),()=>me(e(Ge).rows_read_count),()=>me(e(Ge).weighable_count),()=>me(e(Ge).not_weighable_count),()=>me(e(Ge).anomalies_count),()=>me(e(Ge).products_withdrawn_count)]),l(Ue,ft)}),p(()=>i(Ne,e(d))),l(U,te)};m(Re,U=>{e(Y)!=="read"?U(We):e(se).length===0?U(Ye,1):U(Ke,-1)})}l(k,ue)},$$slots:{default:!0}}),l(n,ze),Ct()}At(["change","click","input"]);function xs(n){switch(n){case"ok":return"OK";case"warn":return"À SURVEILLER";case"fault":return"EN PANNE";case"off":return"SANS OBJET";default:return"INCONNU"}}var ks=u('

                                                                          '),qs=u('

                                                                          ');function Ss(n,t){Tt(t,!0);var s=qs(),c=a(s),h=a(c),x=r(h,2),O=a(x),A=r(x,2),T=a(A),H=r(c,2),B=a(H),se=r(H,2);{var _e=ye=>{var ce=ks(),j=a(ce);p(()=>i(j,t.light.remedy)),l(ye,ce)};m(se,ye=>{t.light.remedy!==""&&ye(_e)})}p(ye=>{Be(s,"data-light",t.light.id),Be(s,"data-level",t.light.level),Be(h,"data-level",t.light.level),i(O,t.light.label),i(T,ye),i(B,t.light.value)},[()=>xs(t.light.level)]),l(n,s),Ct()}function Ls(n){return[Es(n),zs(n),Ps(n),Ts(n),Os(n),Rs(n)]}function Es(n){const t=n.state.scale,s=ca(t.median_ms);return n.scale_present?t.connected?t.too_slow?{id:"scale",label:"Balance",level:"warn",value:`une mesure toutes les ${s}, plus lent que la péremption`,remedy:"À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante. Vérifiez le câble et l’adaptateur USB, puis la cadence sur la page Matériel."}:{id:"scale",label:"Balance",level:"ok",value:As(t.provisional,`une mesure toutes les ${s}`),remedy:""}:{id:"scale",label:"Balance",level:"fault",value:"elle ne répond plus",remedy:"Vérifiez le câble et l’alimentation de la balance, puis touchez « Tester la balance ». En attendant, « Basculer en saisie manuelle » permet de continuer à servir."}:{id:"scale",label:"Balance",level:"off",value:"ce poste est déclaré sans balance",remedy:""}}function zs(n){const t=n.state.printer;switch(t.health){case"faulted":return{id:"printer",label:"Imprimante",level:"fault",value:t.detail===""?"elle ne peut pas imprimer":t.detail,remedy:js(n)};case"consumable":return{id:"printer",label:"Imprimante",level:"warn",value:"elle imprime, mais le rouleau arrive en fin de vie",remedy:"Changez le rouleau quand vous passez derrière le comptoir, puis touchez « J’ai changé le rouleau ». Le poste continue de servir en attendant."};case"ready":return{id:"printer",label:"Imprimante",level:"ok",value:"elle répond et n’a rien à signaler",remedy:""};default:return{id:"printer",label:"Imprimante",level:"unknown",value:"elle prend les étiquettes et ne dit rien en retour",remedy:"C’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne. Pour savoir si elle imprime, touchez « Imprimer une étiquette de test »."}}}function js(n){const t="Regardez le capot, le rouleau et le câble, puis touchez « Tester l’imprimante ».";return n.printing?.fallback_available===!0?t+" Le poste peut servir en attendant : « Imprimer sur l’imprimante du poste voisin ».":t}function Ps(n){const t=n.roll;return t===null?{id:"roll",label:"Rouleau",level:"unknown",value:"aucun compteur d’étiquettes sur ce poste",remedy:`Sans imprimante construite, il n’y a pas de rouleau à compter : vérifiez le « ${Ft("printer.type")} » et ses réglages sur la page Matériel.`}:t.known?t.level==="warn"?{id:"roll",label:"Rouleau",level:"warn",value:t.message,remedy:"Changez le rouleau, puis touchez « J’ai changé le rouleau »."}:{id:"roll",label:"Rouleau",level:"ok",value:t.message,remedy:""}:{id:"roll",label:"Rouleau",level:"unknown",value:t.message,remedy:"Touchez « J’ai changé le rouleau » en mettant un rouleau neuf : c’est le seul geste qui dise quelque chose de vrai du papier."}}function Ts(n){const t=n.catalog_source?.label??"";if(n.state.catalog_count===0)return{id:"catalog",label:"Catalogue",level:"fault",value:"aucun produit dans la grille",remedy:Cs(t)};const s=n.catalog;return s!==null&&s.result!=="applied"&&s.result!=="unchanged"?{id:"catalog",label:"Catalogue",level:"warn",value:`dernier fichier refusé : ${s.reason===""?s.result:s.reason}`,remedy:"La grille tourne toujours sur le catalogue précédent. Corrigez le fichier dans Odoo, ou touchez « Oublier la quarantaine » puis redéposez-le."}:{id:"catalog",label:"Catalogue",level:"ok",value:`${me(n.state.catalog_count)} produits dans la grille`,remedy:""}}function Cs(n){return"Déposez le fichier du catalogue là où le poste le guette, ou utilisez « Importer un catalogue » ci-contre pour le glisser depuis une clé USB."+(n===""?"":` Ce poste surveille : ${n}.`)}function Os(n){const t=n.disk;if(t===null)return{id:"disk",label:"Disque",level:"unknown",value:"la place libre n’a pas pu être mesurée",remedy:"Téléchargez le fichier de diagnostic : il porte ce que le poste a pu lire du volume, et c’est la pièce qu’un support demandera."};const s=wa(t.free_bytes),c=`seuil ${me(t.alert_mb)} Mo`;return t.free_bytes===0?{id:"disk",label:"Disque",level:"fault",value:`plus un octet libre sur ${t.path}`,remedy:"Le journal ne peut plus rien écrire, alors que les étiquettes continuent de sortir. Faites de la place sur le disque, puis rechargez cette page."}:t.alert_mb>0&&t.free_bytes0?{id:"journal",label:"Journal",level:"fault",value:`${me(t)} pesées imprimées mais non enregistrées`,remedy:"Les étiquettes sortent, les ventes sont bonnes, c’est la trace qui manque. Téléchargez le fichier de diagnostic et prévenez le support : ne redémarrez rien."}:n.counters.journal_rows_count<0?{id:"journal",label:"Journal",level:"unknown",value:"ce poste n’a pas de journal ouvert",remedy:"Le poste pèse et imprime quand même. Téléchargez le fichier de diagnostic : il dit pourquoi la base n’a pas pu être ouverte."}:{id:"journal",label:"Journal",level:"ok",value:`${me(n.counters.journal_rows_count)} pesées enregistrées`,remedy:""}}function As(n,t){return n?t+" (cadence encore provisoire)":t}function Ns(n){return n===null||!n.known?"unknown":n.configured?"ok":"missing"}function $s(n){switch(n){case"applied":return"appliqué";case"unchanged":return"inchangé, déjà appliqué";case"rejected":return"refusé";case"failed":return"échec";default:return n}}var Ds=u("Une mesure toutes les ",1),Is=u('

                                                                          '),Us=u("Source : ",1),Fs=u(`

                                                                          Aucun import enregistré sur ce poste : le catalogue n’est jamais arrivé, ou le - journal ne le porte pas.

                                                                          `),Ms=u('

                                                                          ',1),Bs=u('

                                                                          ',1),Vs=u('

                                                                          Aucune décision locale : le catalogue est proposé tel qu’il arrive.

                                                                          '),Ws=u('

                                                                          '),Gs=u(' '),Hs=u('
                                                                        • '),Js=u('
                                                                            ',1),Ks=u(`INCONNU — la question n’a pas pu être posée à ce système. Ce - n’est pas « non configuré » : personne ne sait encore.`,1),Xs=u('OK ',1),Ys=u('NON CONFIGURÉ ',1),Qs=u('

                                                                            '),Zs=u('

                                                                            ',1),el=u('

                                                                            Rien à signaler depuis le démarrage du poste.

                                                                            '),tl=u(' '),al=u('
                                                                          • '),rl=u('
                                                                              '),nl=u('
                                                                              Numéro de poste
                                                                              Nom
                                                                              Coopérative
                                                                              Version
                                                                              Empreinte de configuration
                                                                              '),sl=u('
                                                                              ');function ll(n,t){Tt(t,!0);const s=o(()=>Ls(t.health)),c=o(()=>t.health.state.scale),h=o(()=>t.health.unattended_restart),x=o(()=>t.health.decisions),O=5,A=o(()=>e(x).slice(0,O));var T=sl(),H=a(T);rt(H,21,()=>e(s),V=>V.id,(V,le)=>{Ss(V,{get light(){return e(le)}})});var B=r(H,2);Qe(B,{title:"Cadence de la balance",children:(V,le)=>{var re=Is(),W=a(re);{var D=G=>{var E=yt("Ce poste est déclaré sans balance : le poids est saisi à la main.");l(G,E)},Y=G=>{var E=yt(`La balance ne répond pas : aucune cadence n’est mesurable tant qu’aucune trame - n’arrive.`);l(G,E)},z=G=>{var E=yt(`Aucun intervalle n’a encore été mesuré : la cadence apparaîtra dès les premières - trames.`);l(G,E)},P=G=>{var E=Ds(),b=r(X(E)),R=a(b),J=r(b),be=r(J);{var xe=w=>{var N=yt("C’est encore une valeur d’attente : moins de huit intervalles ont été observés.");l(w,N)};m(be,w=>{e(c).provisional&&w(xe)})}var Ve=r(be,2);{var Ae=w=>{var N=yt("À cette cadence, un poids serait périmé avant l’arrivée de la mesure suivante.");l(w,N)};m(Ve,w=>{e(c).too_slow&&w(Ae)})}p((w,N)=>{i(R,w),i(J,`, médiane - observée sur ${N??""} intervalles. `)},[()=>ca(e(c).median_ms),()=>me(e(c).observations_count)]),l(G,E)};m(W,G=>{t.health.scale_present?e(c).connected?e(c).observations_count===0?G(z,2):G(P,-1):G(Y,1):G(D)})}l(V,re)},$$slots:{default:!0}});var se=r(B,2);Qe(se,{title:"Catalogue",children:(V,le)=>{var re=Bs(),W=X(re),D=a(W);{var Y=b=>{var R=yt("Aucune source de catalogue n’est publiée par ce poste.");l(b,R)},z=b=>{var R=Us(),J=r(X(R)),be=a(J);p(()=>i(be,t.health.catalog_source.label)),l(b,R)};m(D,b=>{t.health.catalog_source===null?b(Y):b(z,-1)})}var P=r(W,2);{var G=b=>{var R=Fs();l(b,R)},E=b=>{var R=Ms(),J=X(R),be=a(J),xe=r(J,2);Za(xe,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives},get onshowrows(){return t.onshowrows}}),p((Ve,Ae)=>i(be,`Dernier essai : ${Ve??""} — - ${Ae??""}${t.health.catalog.file_name===""?"":" ("+t.health.catalog.file_name+")"}${t.health.catalog.reason===""?"":" : "+t.health.catalog.reason}`),[()=>Mt(t.health.catalog.occurred_at),()=>$s(t.health.catalog.result)]),l(b,R)};m(P,b=>{t.health.catalog===null?b(G):b(E,-1)})}l(V,re)},$$slots:{default:!0}});var _e=r(se,2);Qe(_e,{title:"Décisions locales en vigueur",note:"Ce qu’un humain a décidé de ce catalogue, avec son motif et sa date.",children:(V,le)=>{var re=Dt(),W=X(re);{var D=z=>{var P=Vs();l(z,P)},Y=z=>{var P=Js(),G=X(P);{var E=R=>{var J=Ws(),be=a(J);p(xe=>i(be,`${xe??""} décisions en vigueur — les - 5 plus récentes ci-dessous. La liste entière est sur la page - Catalogue.`),[()=>me(e(x).length)]),l(R,J)};m(G,R=>{e(x).length>O&&R(E)})}var b=r(G,2);rt(b,21,()=>e(A),R=>R.product_id,(R,J)=>{var be=Hs(),xe=a(be),Ve=a(xe),Ae=r(xe,2);{var w=Q=>{var qe=Gs(),oe=a(qe);p(S=>i(oe,`peut peser à partir de ${S??""} g`),[()=>me(e(J).min_weight_g)]),l(Q,qe)};m(Ae,Q=>{e(J).min_weight_g!==null&&Q(w)})}var N=r(Ae,2),_=a(N),F=r(N,2),I=a(F);p(Q=>{i(Ve,`${e(J).offered?"Dérogation de poids":"Produit retiré"} — ${e(J).product_id??""}`),i(_,e(J).reason),i(I,`${Q??""}, ${e(J).decided_by??""}`)},[()=>da(e(J).decided_at)]),l(R,be)}),l(z,P)};m(W,z=>{e(x).length===0?z(D):z(Y,-1)})}l(V,re)},$$slots:{default:!0}});var ye=r(_e,2);Qe(ye,{title:"Redémarrage sans intervention",children:(V,le)=>{var re=Zs(),W=X(re),D=a(W);{var Y=b=>{var R=Ks();l(b,R)},z=b=>{var R=Xs(),J=r(X(R));p(()=>i(J,` — après une coupure de courant, ce poste revient seul sur - l’écran client. ${e(h).detail??""}`)),l(b,R)},P=b=>{var R=Ys(),J=r(X(R));p(()=>i(J,` — ${e(h).detail??""}`)),l(b,R)};m(D,b=>{e(h)===null||!e(h).known?b(Y):e(h).configured?b(z,1):b(P,-1)})}var G=r(W,2);{var E=b=>{var R=Qs(),J=a(R);p(()=>i(J,e(h).remedy)),l(b,R)};m(G,b=>{e(h)!==null&&e(h).known&&!e(h).configured&&e(h).remedy!==""&&b(E)})}p(b=>Be(W,"data-verdict",b),[()=>Ns(e(h))]),l(V,re)},$$slots:{default:!0}});var ce=r(ye,2);Qe(ce,{title:"Dix derniers événements",children:(V,le)=>{var re=Dt(),W=X(re);{var D=z=>{var P=el();l(z,P)},Y=z=>{var P=rl();rt(P,21,()=>t.health.events,G=>G.id,(G,E)=>{var b=al(),R=a(b),J=a(R),be=r(R,2),xe=a(be),Ve=r(be,2);{var Ae=_=>{var F=tl(),I=a(F);p(()=>i(I,e(E).code)),l(_,F)};m(Ve,_=>{e(E).code!==""&&Gt.showTechnicalNames&&_(Ae)})}var w=r(Ve,2),N=a(w);p((_,F)=>{Be(b,"data-level",e(E).level),i(J,_),i(xe,F),i(N,e(E).message)},[()=>zn(e(E).occurred_at),()=>Xa(e(E).source)]),l(G,b)}),l(z,P)};m(W,z=>{t.health.events.length===0?z(D):z(Y,-1)})}l(V,re)},$$slots:{default:!0}});var j=r(ce,2);Qe(j,{title:"Ce poste",children:(V,le)=>{var re=nl(),W=r(a(re),2),D=a(W),Y=r(W,4),z=a(Y),P=r(Y,4),G=a(P),E=r(P,4),b=a(E),R=r(E,4),J=a(R);p(()=>{i(D,t.health.station),i(z,t.health.station_name===""?"non renseigné":t.health.station_name),i(G,t.health.coop),i(b,t.health.version),i(J,t.health.config_fingerprint)}),l(V,re)},$$slots:{default:!0}}),l(n,T),Ct()}var il=u(''),ol=u(`

                                                                              Lecture de la configuration en cours… tant qu’elle n’est pas arrivée, cette page ne - déclare rien de ce poste.

                                                                              `),Na=u('
                                                                            • '),ul=u('
                                                                                '),cl=u('

                                                                                ',1),dl=u('
                                                                              • '),vl=u('

                                                                                  ',1),pl=u('

                                                                                  '),fl=u('
                                                                                • '),gl=u('
                                                                                    '),ml=u('

                                                                                      ',1),hl=u('

                                                                                      Balance

                                                                                      L’état et la cadence viennent de ce que le poste observe vraiment, jamais d’un réglage.

                                                                                      Réglages série de la balance

                                                                                      Imprimante

                                                                                      Réglages de l’imprimante
                                                                                      ');function _l(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=20,h=3,x=250,O=50,A=h*1e3+x+1e3,T=12;let H=$(Lt([])),B=$(Lt([])),se=$(Lt([])),_e=$(Lt([])),ye=$(""),ce=$(!1),j=$(""),V=$(0),le=$(0),re=$(!1),W=$(null),D=!1;const Y=o(()=>t.health.state.scale),z=o(()=>t.health.state.printer),P=o(()=>t.draft.config!==null),G=o(()=>e(P)&&!t.draft.flag("scale.present")),E=o(()=>t.draft.text("scale.options.port")),b=o(()=>e(W)!==null&&e(W).port===e(E)?e(W).reason:""),R=o(()=>e(ye)===e(E)?e(_e):[]),J=o(d),be=o(ee),xe=o(L),Ve=o(Se),Ae=o(()=>Ee("scale.type")!==""||Ee("scale.options.port")!==""),w=o(()=>Ee("printer.type")!==""||Ee("printer.options.transport")!==""||Ee("printer.options.queue")!=="");br(()=>{D=!0}),ka(()=>{S("ports",Z)}),aa(()=>{e(re)||!N(e(E))||F(e(E))});function N(g){return!D&&e(P)&&!e(G)&&g!==""&&g===e(E)&&_(g)&&e(b)===""&&e(j)===""}function _(g){return e(H).some(C=>C.name===g)}async function F(g){v(re,!0);try{for(;N(g);){try{Q(g,await ja(g,h))}catch(C){v(W,{port:g,reason:Je(C)},!0);return}await qe()}}finally{v(re,!1)}}async function I(){const g=e(E),C=await s().protect(()=>ja(g,h));C!==null&&(Q(g,C),v(W,null))}function Q(g,C){if(D||C.length===0)return;const Ce=g===e(ye)?e(_e):[];v(ye,g,!0),v(_e,[...Ce,...C].slice(-c),!0)}function qe(g=x){return new Promise(C=>setTimeout(C,g))}async function oe(){const g=Date.now()+A;for(;e(re)&&Date.now()Ha(g));C!==null&&(s().notice=C.message,await s().refresh())}async function He(){v(V,0),v(le,0),e(ce)||await Z();const g=e(H);if(g.length===0){v(se,[],!0);return}await s().protect(()=>Xe(g))}async function Xe(g){v(se,[],!0),v(le,g.length,!0);for(const[C,Ce]of g.entries()){v(V,C+1);try{const Fe=await Ur(Ce.name);v(se,[...e(se),{port:Ce.name,message:Fe.message,refused:!1}],!0)}catch(Fe){if(ae(Fe))throw Fe;v(se,[...e(se),{port:Ce.name,message:Je(Fe),refused:!0}],!0)}}}function fe(){return e(j)!=="detect"?"Détecter automatiquement":e(re)?"Détection : le port se libère…":e(le)===0?"Détection : énumération des ports…":`Détection : port ${me(e(V))} sur ${me(e(le))}…`}function d(){return t.health.scale_present?e(Y).connected?e(Y).too_slow?{level:"warn",word:"Trop lente",detail:y()+" À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante."}:{level:"ok",word:"Connectée",detail:"Elle répond. "+y()}:{level:"fault",word:"Sans réponse",detail:"Elle ne répond plus. Vérifiez le câble et l’alimentation, puis « Tester la balance » sur la page Dépannage."}:{level:"off",word:"Sans balance",detail:"Ce poste est déclaré sans balance : le feu est éteint et le poids se saisit à la main."}}function y(){return e(Y).observations_count===0?"Aucun intervalle n’a encore été mesuré : la cadence sera connue dès les premières trames.":`Une mesure toutes les ${ca(e(Y).median_ms)} sur ${me(e(Y).observations_count)} intervalles`+(e(Y).provisional?", cadence encore provisoire.":".")}function ee(){const g=f[e(z).health]??{level:"unknown",word:"État inconnu",detail:"Le poste a répondu un état que cet écran ne sait pas nommer."};return{...g,detail:e(z).detail===""?g.detail:e(z).detail}}const f={ready:{level:"ok",word:"Prête",detail:"Elle répond et n’a rien à signaler."},consumable:{level:"warn",word:"Rouleau en fin de vie",detail:"Elle imprime, mais le rouleau arrive en fin de vie."},faulted:{level:"fault",word:"En panne",detail:"Elle ne peut pas imprimer."},unknown:{level:"unknown",word:"Silencieuse",detail:"Elle prend les étiquettes et ne dit rien en retour : c’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne."}};function q(){const g=e(z).observed_at===""?"Jamais observée depuis le démarrage":`Observée le ${Mt(e(z).observed_at)}`,C=e(z).pending_jobs_count;return`${g}, ${me(C)} ${C>1?"travaux":"travail"} en attente.`}function L(){const g=ne();return g===""?he():`${he()} ${g}`}function he(){return e(P)?e(G)?"Ce poste est déclaré sans balance : aucun port n’est écouté.":e(E)===""?"Aucun port n’est indiqué : choisissez-en un dans la liste ci-dessus pour écouter les trames.":e(ce)?_(e(E))?e(b)!==""?`L’écoute de ${e(E)} est arrêtée.`:e(j)!==""?`L’écoute de ${e(E)} est suspendue le temps de l’acte en cours.`:`Écoute de ${e(E)}.`:`${e(E)} n’est pas visible depuis ce poste : rien n’est écouté en continu.`:e(j)==="ports"?`Énumération des ports en cours : l’écoute de ${e(E)} démarre dès qu’il est vu.`:`Les ports de ce poste n’ont pas été énumérés : « Lister les ports » dira si ${e(E)} existe.`:"Lecture de la configuration en cours : le port à écouter n’est pas encore connu."}function ne(){return e(R).length===0?"Aucune trame reçue pour l’instant.":e(R).length===1?`Une seule trame reçue — ${me(c)} au plus sont gardées.`:`Les ${me(e(R).length)} dernières trames — ${me(c)} au plus, la plus récente en bas.`}function Se(){return!e(P)||e(G)||e(E)===""?"":e(b)!==""?"Reprendre l’écoute":e(ce)&&!_(e(E))?"Écouter ce port une fois":""}function Le(g,C,Ce){const Fe=`${me(Ce)} ${Ce>1?C:g}`;return Ce<=T?Fe+".":`${Fe} — seules les ${me(T)} premières lignes sont affichées.`}function de(g,C){const Ce=Te(g);return Ce.length>0?Ce:C.slice(0,T)}function M(g){return[...new TextEncoder().encode(g)].map(C=>C.toString(16).toUpperCase().padStart(2,"0")).join(" ")}const Ie={0:"NUL",2:"STX",3:"ETX",4:"EOT",5:"ENQ",6:"ACK",9:"TAB",10:"LF",13:"CR",21:"NAK",27:"ESC",127:"DEL"};function Me(g){let C="";for(const Ce of g){const Fe=Ce.codePointAt(0)??0;if(Fe>=32&&Fe!==127){C+=Ce;continue}C+=`⟨${Ie[Fe]??Fe.toString(16).toUpperCase().padStart(2,"0")}⟩`}return C}function Ee(g){return t.draft.faults.find(C=>C.field===g)?.message??""}function Te(g){return t.draft.faults.find(C=>C.field===g)?.allowed??[]}function Je(g){return g instanceof It?g.message:g instanceof Error?"Le poste n’a pas répondu : "+g.message:"Le poste n’a pas répondu."}function ae(g){return g instanceof It&&(g.status===401||g.status===409)}const De=[{what:"label",name:"étiquette"},{what:"alignment",name:"alignement"},{what:"ruler",name:"réglette"}];var ve=hl(),ze=a(ve),Oe=a(ze),nt=r(a(Oe),2),ut=r(a(nt),1,!0),pt=r(Oe,2),je=a(pt),st=r(pt,4);{var Ze=g=>{var C=il(),Ce=a(C);p(()=>ua(Ce,e(G))),tt("change",Ce,Fe=>t.draft.set("scale.present",!Fe.currentTarget.checked)),l(g,C)},bt=g=>{var C=ol();l(g,C)};m(st,g=>{e(P)?g(Ze):g(bt,-1)})}var Ot=r(st,2),Ut=a(Ot);{let g=o(()=>e(j)==="ports"),C=o(()=>e(j)!=="");at(Ut,{label:"Lister les ports",get busy(){return e(g)},get disabled(){return e(C)},onrun:()=>{S("ports",Z)}})}var k=r(Ut,2);{let g=o(fe),C=o(()=>e(j)!=="");at(k,{act:"detect",get label(){return e(g)},protected:!0,get disabled(){return e(C)},onrun:()=>{S("detect",He)}})}var K=r(Ot,2);{var ue=g=>{var C=cl(),Ce=X(C),Fe=a(Ce),lt=r(Ce,2);{var mt=wt=>{var zt=ul();rt(zt,21,()=>e(H).slice(0,T),jt=>jt.name,(jt,St)=>{var Kt=Na(),ea=a(Kt),ga=a(ea),er=r(ea,2),tr=a(er);p(()=>{i(ga,e(St).name),i(tr,`${(e(St).description===""?"aucune description USB":e(St).description)??""} - ${e(St).vid===""?"":` — VID ${e(St).vid} PID ${e(St).pid}`}`)}),tt("click",ea,()=>t.draft.set("scale.options.port",e(St).name)),l(jt,Kt)}),l(wt,zt)};m(lt,wt=>{e(H).length>0&&wt(mt)})}p(wt=>i(Fe,wt),[()=>e(H).length===0?"Aucun port série n’est visible depuis ce poste.":Le("port détecté","ports détectés",e(H).length)]),l(g,C)};m(K,g=>{e(ce)&&g(ue)})}var Re=r(K,2);{var We=g=>{var C=vl(),Ce=X(C),Fe=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(se).slice(0,T),mt=>mt.port,(mt,wt)=>{var zt=dl();let jt;var St=a(zt),Kt=a(St),ea=r(St,2),ga=a(ea);p(()=>{jt=Ht(zt,1,"svelte-1p4i0xm",null,jt,{refused:e(wt).refused}),i(Kt,e(wt).port),i(ga,e(wt).message)}),l(mt,zt)}),p(mt=>i(Fe,mt),[()=>Le("port interrogé","ports interrogés",e(se).length)]),l(g,C)};m(Re,g=>{e(se).length>0&&g(We)})}var Ye=r(Re,2),Ke=r(a(Ye),2),U=a(Ke);{let g=o(()=>t.draft.text("scale.type")),C=o(()=>Ee("scale.type")),Ce=o(()=>Te("scale.type")),Fe=o(()=>!e(P));_t(U,{label:"Protocole",path:"scale.type",get value(){return e(g)},hint:"Les valeurs acceptées apparaissent ici si l’enregistrement est refusé.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("scale.type",lt)})}var te=r(U,2);{let g=o(()=>t.draft.text("scale.options.port")),C=o(()=>Ee("scale.options.port")),Ce=o(()=>de("scale.options.port",e(H).map(lt=>lt.name))),Fe=o(()=>!e(P));_t(te,{label:"Port série",path:"scale.options.port",get value(){return e(g)},hint:"Choisissez-le dans la liste détectée ci-dessus plutôt que de le taper : l’écoute permanente ne suit que des ports détectés.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("scale.options.port",lt)})}var ge=r(Ye,2),Ne=a(ge),ke=a(Ne),$e=r(Ne,2);{var Pe=g=>{var C=pl(),Ce=a(C),Fe=r(Ce);{var lt=mt=>{{let wt=o(()=>e(j)==="listen"),zt=o(()=>e(j)!=="");at(mt,{act:"listen",get label(){return e(Ve)},protected:!0,get busy(){return e(wt)},get disabled(){return e(zt)},onrun:()=>{S("listen",I)}})}};m(Fe,mt=>{e(Ve)!==""&&mt(lt)})}p(()=>i(Ce,`${e(b)??""} `)),l(g,C)};m($e,g=>{(e(b)!==""||e(Ve)!=="")&&g(Pe)})}var Ue=r($e,2);{var Ge=g=>{var C=gl();rt(C,21,()=>e(R),fa,(Ce,Fe)=>{var lt=fl(),mt=a(lt),wt=a(mt),zt=r(mt,2),jt=a(zt);p((St,Kt)=>{i(wt,St),i(jt,Kt)},[()=>M(e(Fe)),()=>Me(e(Fe))]),l(Ce,lt)}),l(g,C)};m(Ue,g=>{e(R).length>0&&g(Ge)})}var ft=r(ze,2),ot=a(ft),ct=r(a(ot),2),it=r(a(ct),1,!0),dt=r(ot,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),Rt=a(Bt);{let g=o(()=>e(j)==="printers"),C=o(()=>e(j)!=="");at(Rt,{label:"Lister les files",get busy(){return e(g)},get disabled(){return e(C)},onrun:()=>{S("printers",ie)}})}var Vt=r(Rt,2);{let g=o(()=>e(j)==="discover"),C=o(()=>e(j)!=="");at(Vt,{label:"Rechercher l’imprimante",protected:!0,get busy(){return e(g)},get disabled(){return e(C)},onrun:()=>{S("discover",pe)}})}var kt=r(Vt,2);rt(kt,17,()=>De,g=>g.what,(g,C)=>{{let Ce=o(()=>`Auto-test : ${e(C).name}${e(j)===e(C).what?" — en cours…":""}`),Fe=o(()=>e(j)!=="");at(g,{get act(){return e(C).what},get label(){return e(Ce)},protected:!0,get disabled(){return e(Fe)},onrun:()=>{S(e(C).what,()=>we(e(C).what))}})}});var Et=r(Bt,2);{var Wt=g=>{var C=ml(),Ce=X(C),Fe=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(B).slice(0,T),mt=>mt.name,(mt,wt)=>{var zt=Na(),jt=a(zt),St=a(jt),Kt=r(jt,2),ea=a(Kt);p(()=>{i(St,e(wt).name),i(ea,`${e(wt).detail??""}${e(wt).default?" — file par défaut du système":""}`)}),tt("click",jt,()=>t.draft.set("printer.options.queue",e(wt).name)),l(mt,zt)}),p(mt=>i(Fe,mt),[()=>Le("file d’impression","files d’impression",e(B).length)]),l(g,C)};m(Et,g=>{e(B).length>0&&g(Wt)})}var et=r(Et,2),qt=r(a(et),2),$t=a(qt);{let g=o(()=>t.draft.text("printer.type")),C=o(()=>Ee("printer.type")),Ce=o(()=>Te("printer.type")),Fe=o(()=>!e(P));_t($t,{label:"Driver",path:"printer.type",get value(){return e(g)},hint:"Gardez le driver raster : c’est celui que les postes en service utilisent.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("printer.type",lt)})}var Zt=r($t,2);{let g=o(()=>t.draft.text("printer.options.transport")),C=o(()=>Ee("printer.options.transport")),Ce=o(()=>Te("printer.options.transport")),Fe=o(()=>!e(P));_t(Zt,{label:"Transport",path:"printer.options.transport",get value(){return e(g)},hint:"Local par défaut : une file Windows ou un fichier de périphérique de ce poste.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("printer.options.transport",lt)})}var ra=r(Zt,2);{let g=o(()=>t.draft.text("printer.options.queue")),C=o(()=>Ee("printer.options.queue")),Ce=o(()=>de("printer.options.queue",e(B).map(lt=>lt.name))),Fe=o(()=>!e(P));_t(ra,{label:"File ou chemin",path:"printer.options.queue",get value(){return e(g)},hint:"Choisissez-la dans la liste ci-dessus : une file mal orthographiée ne s’imprime pas.",get fault(){return e(C)},get allowed(){return e(Ce)},get disabled(){return e(Fe)},onchange:lt=>t.draft.set("printer.options.queue",lt)})}p(g=>{Be(nt,"data-level",e(J).level),i(ut,e(J).word),i(je,e(J).detail),Ye.open=e(Ae),i(ke,e(xe)),Be(ct,"data-level",e(be).level),i(it,e(be).word),i(gt,e(be).detail),i(Nt,g),et.open=e(w)},[()=>q()]),l(n,ve),Ct()}At(["change","click"]);var bl=u(""),wl=u('L’export sera proposé quand le journal aura répondu.'),yl=u('L’export n’est pas proposé : ce poste n’a pas répondu à la lecture du journal.'),xl=u('Exporter en CSV'),kl=u('

                                                                                      '),ql=u('

                                                                                      '),Sl=u(`

                                                                                      Aucune trame brute n’a été enregistrée pour cette pesée : il n’y a - rien à rejouer.

                                                                                      `),Ll=u(`

                                                                                      La trame repart dans le décodeur du poste EN SERVICE : le poids - affiché au client change, et rien ne le remet comme il était. C’est - ce qui fait d’un refus inexpliqué un test permanent, sans - déplacement au magasin et sans balance.

                                                                                      `,1),El=u('

                                                                                      Produit
                                                                                      Référence
                                                                                      Vente
                                                                                      Brut / tare / net
                                                                                      Stabilité
                                                                                      Origine du poids
                                                                                      Résultat
                                                                                      Trame brute
                                                                                      '),zl=u(' ',1),jl=u('

                                                                                      QuandProduitNetCode-barresRésultatDuréeDétail
                                                                                      ',1),Pl=u('
                                                                                      ',1),Tl=u('

                                                                                      '),Cl=u(' '),Ol=u(' '),Rl=u('
                                                                                    • '),Al=u('

                                                                                        ',1),Nl=u('
                                                                                        ');function $l(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=200,h=5e3,x=50,O=500,A=7,T={sent:"envoyée à l’imprimante",rejected:"refusée",failed:"en échec",reprint:"réimpression"},H=[{value:"",label:"toutes"},{value:"sent",label:"envoyées à l’imprimante"},{value:"rejected",label:"refusées"},{value:"failed",label:"en échec"},{value:"reprint",label:"réimpressions"}],B={scale:"balance",manual:"saisie manuelle",replay:"trame rejouée"},se={stable:"stable",unstable:"instable",unknown:"non déclarée par la balance",not_applicable:"sans objet — saisie manuelle"},_e={by_weight:"au poids",by_unit:"à l’unité"},ye={debug:"mise au point",info:"information",warn:"avertissement",error:"erreur",critical:"critique"};let ce=$(Lt([])),j=$(Lt([])),V=$(""),le=$(null),re=$(!1),W=$(!1),D=$("loading"),Y=$("loading"),z=$(""),P=$("");const G=o(()=>({limit:String(c),...e(V)===""?{}:{result:e(V)}})),E=o(()=>({...e(G),limit:String(h)})),b=o(()=>e(ce).find(I=>I.id===e(le))??null),R=o(()=>H.find(I=>I.value===e(V))?.label??""),J=o(()=>Ae(e(ce).length,c,"pesée","pesées",`L’export CSV en emporte jusqu’à ${me(h)}.`)),be=o(()=>Ae(e(j).length,x,"ligne","lignes",`Le fichier de diagnostic emporte les ${me(O)} dernières.`));xe();async function xe(){v(le,null),v(W,!0),v(D,"loading"),v(Y,"loading"),v(ce,[],!0),v(j,[],!0),v(z,""),v(P,"");try{const I=await s().load(()=>Mr(e(G)));v(D,I===null?"unread":"read",!0),v(z,I===null?s().actionError:"",!0),v(ce,I??[],!0);const Q=await s().load(()=>Vr({limit:String(x)}));v(Y,Q===null?"unread":"read",!0),v(P,Q===null?s().actionError:"",!0),v(j,Q??[],!0)}finally{v(W,!1),s().actionError=""}}async function Ve(I){s().notice="",s().actionError="",v(re,!0);try{const Q=await s().protect(()=>Jr(I));Q!==null&&(s().notice=Q.message)}finally{v(re,!1)}}function Ae(I,Q,qe,oe,S){const Z=I>1?oe:qe;return I{var qe=Pl(),oe=X(qe),S=r(a(oe),2);rt(S,21,()=>H,f=>f.value,(f,q)=>{var L=bl(),he=a(L),ne={};p(()=>{i(he,e(q).label),ne!==(ne=e(q).value)&&(L.value=(L.__value=e(q).value)??"")}),l(f,L)});var Z=r(S,2);at(Z,{label:"Rafraîchir",get busy(){return e(W)},onrun:()=>{xe()}});var ie=r(Z,2);{var pe=f=>{var q=wl();l(f,q)},we=f=>{var q=yl();l(f,q)},He=f=>{var q=xl();p(L=>Be(q,"href",L),[()=>Br(e(E))]),l(f,q)};m(ie,f=>{e(D)==="loading"?f(pe):e(D)==="unread"?f(we,1):f(He,-1)})}var Xe=r(oe,2);{var fe=f=>{var q=kl(),L=a(q);p(he=>i(L,`L’export emporte le même filtre que le tableau, mais pas son plafond : il descend - jusqu’à ${he??""} pesées, en point-virgule et en UTF-8 — il s’ouvre - tel quel dans le tableur d’un Windows français. Il ne demande aucun mot de passe : la - lecture du journal n’en demande pas non plus, et le fichier de diagnostic emporte déjà - les deux cents dernières pesées.`),[()=>me(h)]),l(f,q)};m(Xe,f=>{e(D)==="read"&&f(fe)})}var d=r(Xe,2);{var y=f=>{var q=ql(),L=a(q);{var he=de=>{var M=yt("Lecture du journal…");l(de,M)},ne=de=>{var M=yt();p(()=>i(M,`Le journal n’a pas pu être lu : ${e(z)??""} Ce n’est pas « aucune pesée ».`)),l(de,M)},Se=de=>{var M=yt("Le journal ne contient aucune pesée.");l(de,M)},Le=de=>{var M=yt();p(()=>i(M,`Aucune pesée ne correspond au filtre « ${e(R)??""} ».`)),l(de,M)};m(L,de=>{e(D)==="loading"?de(he):e(D)==="unread"?de(ne,1):e(V)===""?de(Se,2):de(Le,-1)})}l(f,q)},ee=f=>{var q=jl(),L=X(q),he=a(L),ne=r(L,2),Se=a(ne),Le=r(a(Se));rt(Le,21,()=>e(ce),de=>de.id,(de,M)=>{var Ie=zl(),Me=X(Ie);let Ee;var Te=a(Me),Je=a(Te),ae=r(Te),De=a(ae),ve=r(ae),ze=a(ve),Oe=r(ve),nt=a(Oe),ut=r(Oe),pt=a(ut),je=r(ut),st=a(je),Ze=r(je),bt=a(Ze),Ot=a(bt),Ut=r(Me,2);{var k=K=>{var ue=El(),Re=a(ue);Be(Re,"colspan",A);var We=a(Re),Ye=a(We),Ke=r(We,2),U=r(a(Ke),2),te=a(U),ge=r(U,4),Ne=a(ge),ke=r(ge,4),$e=a(ke),Pe=r(ke,4),Ue=a(Pe),Ge=r(Pe,4),ft=a(Ge),ot=r(Ge,4),ct=a(ot),it=r(ot,4),dt=a(it),gt=r(it,4),xt=a(gt),Nt=a(xt),Bt=r(Ke,2);{var Rt=kt=>{var Et=Sl();l(kt,Et)},Vt=kt=>{var Et=Ll(),Wt=X(Et),et=a(Wt);at(et,{kind:"destructive",label:"Rejouer cette trame",protected:!0,get busy(){return e(re)},onrun:()=>{Ve(e(b).frame)}}),l(kt,Et)};m(Bt,kt=>{e(b).frame===""?kt(Rt):kt(Vt,-1)})}p((kt,Et,Wt,et,qt,$t,Zt,ra,g)=>{Be(ue,"data-detail",e(M).id),i(Ye,`Pesée ${e(b).id??""}`),i(te,`${e(b).product_name??""} (${e(b).product_id??""})`),i(Ne,e(b).reference),i($e,`${kt??""} — - ${Et??""} - ${e(b).quantity>1?"unités":"unité"}`),i(Ue,`${Wt??""} g / ${et??""} g / - ${qt??""} g`),i(ft,`${$t??""} — cadence - médiane ${Zt??""}`),i(ct,ra),i(dt,`${g??""}${e(b).detail===""?"":` — ${e(b).detail}`}`),i(Nt,e(b).frame===""?"aucune trame enregistrée":e(b).frame)},[()=>w(_e,e(b).mode,"mode de vente inconnu"),()=>me(e(b).quantity),()=>me(e(b).gross_g),()=>me(e(b).tare_g),()=>me(e(b).net_g),()=>w(se,e(b).stability,"stabilité inconnue"),()=>ca(e(b).rate_ms),()=>w(B,e(b).source,"origine inconnue"),()=>w(T,e(b).result,"résultat inconnu")]),l(K,ue)};m(Ut,K=>{e(le)===e(M).id&&e(b)!==null&&K(k)})}p((K,ue,Re,We)=>{Ee=Ht(Me,1,"svelte-86o8oz",null,Ee,{open:e(le)===e(M).id}),i(Je,K),i(De,e(M).product_name),i(ze,`${ue??""} g`),i(nt,e(M).barcode),i(pt,Re),i(st,We),Be(bt,"aria-expanded",e(le)===e(M).id),i(Ot,e(le)===e(M).id?"fermer":"détail")},[()=>Mt(e(M).occurred_at),()=>me(e(M).net_g),()=>w(T,e(M).result,"résultat inconnu"),()=>ca(e(M).duration_ms)]),tt("click",bt,()=>v(le,e(le)===e(M).id?null:e(M).id,!0)),l(de,Ie)}),p(()=>i(he,e(J))),l(f,q)};m(d,f=>{e(ce).length===0?f(y):f(ee,-1)})}tt("change",S,()=>{xe()}),_r(S,()=>e(V),f=>v(V,f)),l(I,qe)},$$slots:{default:!0}});var F=r(_,2);Qe(F,{title:"Journal technique",children:(I,Q)=>{var qe=Dt(),oe=X(qe);{var S=ie=>{var pe=Tl(),we=a(pe);{var He=d=>{var y=yt("Lecture du journal technique…");l(d,y)},Xe=d=>{var y=yt();p(()=>i(y,`Le journal technique n’a pas pu être lu : ${e(P)??""} Ce n’est pas « aucune - ligne ».`)),l(d,y)},fe=d=>{var y=yt("Aucune ligne technique.");l(d,y)};m(we,d=>{e(Y)==="loading"?d(He):e(Y)==="unread"?d(Xe,1):d(fe,-1)})}l(ie,pe)},Z=ie=>{var pe=Al(),we=X(pe),He=a(we),Xe=r(we,2),fe=a(Xe);rt(fe,21,()=>e(j),d=>d.id,(d,y)=>{var ee=Rl(),f=a(ee),q=a(f),L=r(f,2),he=a(L),ne=r(L,2),Se=a(ne),Le=r(ne,2);{var de=Te=>{var Je=Cl(),ae=a(Je);p(()=>i(ae,e(y).code)),l(Te,Je)};m(Le,Te=>{e(y).code!==""&&Gt.showTechnicalNames&&Te(de)})}var M=r(Le,2),Ie=a(M),Me=r(M,2);{var Ee=Te=>{var Je=Ol(),ae=a(Je);p(()=>i(ae,e(y).detail)),l(Te,Je)};m(Me,Te=>{e(y).detail!==""&&Te(Ee)})}p((Te,Je,ae)=>{Be(ee,"data-level",e(y).level),i(q,Te),i(he,Je),i(Se,ae),i(Ie,e(y).message)},[()=>Mt(e(y).occurred_at),()=>w(ye,e(y).level,"niveau inconnu"),()=>Xa(e(y).source)]),l(d,ee)}),p(()=>i(He,e(be))),l(ie,pe)};m(oe,ie=>{e(j).length===0?ie(S):ie(Z,-1)})}l(I,qe)},$$slots:{default:!0}}),l(n,N),Ct()}At(["change","click"]);var Dl=u('

                                                                                        '),Il=u('

                                                                                        '),Ul=u('

                                                                                        '),Fl=u('

                                                                                        '),Ml=u('

                                                                                        Lecture de la configuration en cours… les flèches attendent qu’elle soit arrivée.

                                                                                        '),Bl=u(`
                                                                                        Aperçu de l’étiquette telle qu’elle serait imprimée

                                                                                        Décalage horizontal

                                                                                        Décalage vertical

                                                                                        Le décalage ne descend pas sous zéro : le poste refuse un décalage négatif quel que - soit le gabarit. Le maximum, lui, dépend de la géométrie du gabarit ; il est annoncé - ici si l’enregistrement le dépasse.

                                                                                        Le symbole code-barres est volontairement tronqué : un symbole conforme n’entre pas - sur 40 × 25 mm avec les cinq champs texte. Ce n’est pas un défaut de rendu et il n’y - a rien à corriger.

                                                                                        Le détail chiffré du symbole — largeur de module, modules rendus, modules attendus — - n’est pas servi par ce poste. Cet écran n’affiche pas un chiffre qu’il aurait deviné.

                                                                                        `,1),Vl=u(`

                                                                                        Chaque appui sort une étiquette pour de bon : le mot de passe est demandé au moment - d’imprimer, et l’impression repart d’elle-même une fois la session ouverte.

                                                                                        `,1),Wl=u('
                                                                                        ');function Gl(n,t){Tt(t,!0);const s=vt(t,"admin",7),c="printer.template",h="printer.options.offset_x",x="printer.options.offset_y",O=0,A="L’aperçu n’a pas pu être rendu par le poste.",T="Le poste a rendu l’aperçu, mais le navigateur ne l’a pas affiché.",H=[{what:"alignment",label:"Imprimer la mire d’alignement"},{what:"ruler",label:"Imprimer la réglette"}];let B=$(1),se=$(!0),_e=$(!1),ye=$(""),ce=$(null),j=$(""),V=null,le=!1;const re=o(()=>t.draft.text(c)),W=o(()=>Fr(e(re),e(se),e(_e),e(B))),D=o(()=>t.draft.config!==null),Y=o(()=>t.draft.number(h)),z=o(()=>t.draft.number(x)),P=o(()=>F(h)),G=o(()=>F(x)),E=o(w),b=o(()=>s().busy||e(j)!=="");aa(()=>{if(t.draft.config===null)return;if(t.draft.dirty){V===null&&R();return}const S={x:t.draft.number(h),y:t.draft.number(x)};if(V!==null&&V.x===S.x&&V.y===S.y)return;const Z=V===null;V=S,v(ce,S,!0),Z||xe()});async function R(){if(!le){le=!0;try{const S=await qa(),Z={x:J(S.config,h),y:J(S.config,x)};V=Z,v(ce,Z,!0)}catch{}finally{le=!1}}}function J(S,Z){let ie=S;for(const pe of Z.split(".")){if(ie===null||typeof ie!="object")return 0;ie=ie[pe]}return typeof ie=="number"?ie:0}function be(S,Z){const ie=t.draft.number(S)+Z;ie=2?"s":""}`}function _(S,Z){const ie=Number(Z);Z.trim()===""||Number.isNaN(ie)||t.draft.set(S,ie)}function F(S){return t.draft.faults.find(Z=>Z.field===S)?.message??""}async function I(S){if(!e(b)){v(j,S,!0),s().actionError="",s().notice="";try{const Z=await s().protect(()=>Ha(S));if(Z===null)return;s().notice=Z.message,await s().refresh()}finally{v(j,"")}}}var Q=Wl(),qe=a(Q);Qe(qe,{title:"Aperçu de l’étiquette",note:"Le même moteur que l’impression (A2) : le décalage se voit parce qu’il est cuit dans le bitmap. L’image porte le gabarit en cours d’édition, mais le décalage ENREGISTRÉ — jamais celui que les flèches sont en train de régler.",children:(S,Z)=>{var ie=Bl(),pe=X(ie);{var we=je=>{var st=Dl(),Ze=a(st);p(()=>i(Ze,e(E))),l(je,st)};m(pe,je=>{e(E)!==""&&je(we)})}var He=r(pe,2),Xe=a(He),fe=a(Xe),d=r(fe,2);{var y=je=>{var st=Il(),Ze=a(st);p(()=>i(Ze,e(ye))),l(je,st)};m(d,je=>{e(ye)!==""&&je(y)})}var ee=r(Xe,2),f=a(ee),q=r(a(f),2),L=a(q),he=r(f,2),ne=a(he);{let je=o(()=>!e(D)||e(Y)<=O);at(ne,{label:"← 1 dot",get disabled(){return e(je)},onrun:()=>be(h,-1)})}var Se=r(ne,2);{let je=o(()=>!e(D));at(Se,{label:"1 dot →",get disabled(){return e(je)},onrun:()=>be(h,1)})}var Le=r(he,2);{var de=je=>{var st=Ul(),Ze=a(st);p(()=>i(Ze,e(P))),l(je,st)};m(Le,je=>{e(P)!==""&&je(de)})}var M=r(Le,2),Ie=r(a(M),2),Me=a(Ie),Ee=r(M,2),Te=a(Ee);{let je=o(()=>!e(D)||e(z)<=O);at(Te,{label:"↑ 1 dot",get disabled(){return e(je)},onrun:()=>be(x,-1)})}var Je=r(Te,2);{let je=o(()=>!e(D));at(Je,{label:"1 dot ↓",get disabled(){return e(je)},onrun:()=>be(x,1)})}var ae=r(Ee,2);{var De=je=>{var st=Fl(),Ze=a(st);p(()=>i(Ze,e(G))),l(je,st)};m(ae,je=>{e(G)!==""&&je(De)})}var ve=r(ae,4),ze=a(ve),Oe=r(ve,2),nt=a(Oe),ut=r(Oe,2);{var pt=je=>{var st=Ml();l(je,st)};m(ut,je=>{e(D)||je(pt)})}p((je,st)=>{Be(fe,"src",e(W)),i(L,je),i(Me,st)},[()=>N(e(Y)),()=>N(e(z))]),Qt("load",fe,()=>v(ye,"")),Qt("error",fe,()=>{Ve(e(W))}),tt("change",ze,()=>xe()),za(ze,()=>e(se),je=>v(se,je)),tt("change",nt,()=>xe()),za(nt,()=>e(_e),je=>v(_e,je)),l(S,ie)},$$slots:{default:!0}});var oe=r(qe,2);Qe(oe,{title:"Gabarit et impression",children:(S,Z)=>{var ie=Vl(),pe=X(ie);{let d=o(()=>F(c)),y=o(()=>!e(D));_t(pe,{label:"Gabarit",path:c,get value(){return e(re)},hint:"Le gabarit reproduit à l’identique s’appelle weighing_identical (A1).",get fault(){return e(d)},get disabled(){return e(y)},onchange:ee=>{t.draft.set(c,ee),xe()}})}var we=r(pe,2);{let d=o(()=>t.draft.text("printer.options.darkness")),y=o(()=>F("printer.options.darkness")),ee=o(()=>!e(D));_t(we,{label:"Noircissement",path:"printer.options.darkness",kind:"number",get value(){return e(d)},hint:"Trop bas, l’étiquette pâlit au soleil ; trop haut, elle bave et le scanner refuse.",get fault(){return e(y)},get disabled(){return e(ee)},onchange:f=>_("printer.options.darkness",f)})}var He=r(we,2);{let d=o(()=>t.draft.text("printer.options.speed")),y=o(()=>F("printer.options.speed")),ee=o(()=>!e(D));_t(He,{label:"Vitesse",path:"printer.options.speed",kind:"number",get value(){return e(d)},get fault(){return e(y)},get disabled(){return e(ee)},onchange:f=>_("printer.options.speed",f)})}var Xe=r(He,2);{let d=o(()=>t.draft.text("printer.options.copies")),y=o(()=>F("printer.options.copies")),ee=o(()=>!e(D));_t(Xe,{label:"Exemplaires",path:"printer.options.copies",kind:"number",get value(){return e(d)},hint:"Un client repart avec une étiquette : deux exemplaires se justifient, pas se devinent.",get fault(){return e(y)},get disabled(){return e(ee)},onchange:f=>_("printer.options.copies",f)})}var fe=r(Xe,2);rt(fe,21,()=>H,d=>d.what,(d,y)=>{{let ee=o(()=>e(j)===e(y).what);at(d,{get act(){return e(y).what},get label(){return e(y).label},protected:!0,get busy(){return e(ee)},get disabled(){return e(b)},onrun:()=>{I(e(y).what)}})}}),l(S,ie)},$$slots:{default:!0}}),l(n,Q),Ct()}At(["change"]);var Hl=u(" "),Jl=u(' '),Kl=u(''),Xl=u('

                                                                                        Aucun tarif déclaré dans la configuration lue.

                                                                                        '),Yl=u('Prix du catalogue Odoo — pas de remise'),$a=u(' '),Ql=u(' % ',1),Zl=u(' '),ei=u(`

                                                                                        CodeLibelléAbrégéRemiseOrdre

                                                                                        Un champ vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la - retrouve dès qu’on quitte le champ. Une remise effacée serait le plein tarif pour - tous les adhérents.

                                                                                        `,1),ti=u(" ",1),ai=u(`

                                                                                        Les deux arrondis sont distincts parce qu’ils tombent à des endroits différents du - calcul : arrondir le prix au kilo puis le multiplier, ou multiplier puis arrondir, ne - donne pas le même centime sur une étiquette. Le poste applique l’arrondi commercial, - et l’écart se voit au centime près sur la même pesée.

                                                                                        `,1),ri=u('

                                                                                        Rien ne s’affiche au client : c’est une information.

                                                                                        '),ni=u('

                                                                                        '),si=u('
                                                                                        '),li=u('

                                                                                        '),ii=u('
                                                                                      • '),oi=u(`

                                                                                        Aucun verdict en ce moment : ces lignes portent sur la pesée EN COURS, et le poste - est au repos. Elles apparaissent le temps d’un cycle, quand un client pose son sac - et touche une tuile.

                                                                                        `),ui=u(' '),ci=u(' ',1),di=u('
                                                                                      • '),vi=u('

                                                                                          ',1),pi=u(`

                                                                                          L’ordre compte : le premier verdict bloquant décide de ce que le client lit. Les - garde-fous 1 à 7 portent sur l’état de la balance, c’est-à-dire le poids brut ; les - garde-fous 8 à 14 portent sur la vente, c’est-à-dire le net. Le code en gris est - celui du journal, celui qu’on lit au téléphone.

                                                                                            Un seuil vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la retrouve - dès qu’on quitte le champ. Pour changer un seuil, on tape l’autre valeur.

                                                                                            Ce que les garde-fous disent de la pesée en cours

                                                                                            `,1),fi=u(`

                                                                                            Le plan de numérotation n’est PAS ici, et c’est tout l’intérêt : préfixes, largeur de - la référence, largeur de la charge utile, décimales et mode de vente sont une - constante du binaire, indexée par préfixe et vérifiée au démarrage. - Un champ qui change le SENS du code lu par la caisse n’est pas un réglage, c’est un - contrat externe : il change avec une version du binaire, relue et testée, jamais - depuis l’écran d’un poste.

                                                                                            `,1),gi=u('

                                                                                            Aucune dérogation : la limite générale s’applique à tous les produits.

                                                                                            '),mi=u(`

                                                                                            Les noms de produits n’ont pas pu être lus : le catalogue en service n’a pas - répondu. Les identifiants Odoo restent affichés.

                                                                                            `),hi=u(`Produit retiré : le garde-fou 14 refuse le produit avant que cette - dérogation ait un sens.`),_i=u('
                                                                                          1. '),bi=u('

                                                                                              ',1),wi=u('
                                                                                              ');function yi(n,t){Tt(t,!0);const s=(_,F=sa,I=sa,Q=sa)=>{var qe=Kl(),oe=a(qe),S=r(oe,2),Z=a(S),ie=a(Z),pe=r(Z,2);{var we=fe=>{var d=Hl(),y=a(d);p(()=>i(y,F())),l(fe,d)};m(pe,fe=>{Gt.showTechnicalNames&&fe(we)})}var He=r(pe,2);{var Xe=fe=>{var d=Jl(),y=a(d);p(()=>i(y,Q())),l(fe,d)};m(He,fe=>{Q()!==""&&fe(Xe)})}p((fe,d)=>{Be(qe,"data-flag",F()),Be(qe,"data-on",fe),ua(oe,d),i(ie,I())},[()=>String(t.draft.flag(F())),()=>t.draft.flag(F())]),tt("change",oe,fe=>t.draft.set(F(),fe.currentTarget.checked)),l(_,qe)},c=20,h=o(()=>se(t.draft)),x=o(()=>t.health.state.diagnostics),O=o(()=>t.health.decisions.filter(_=>_.min_weight_g!==null)),A=o(()=>e(O).filter(_=>!_.offered)),T=o(()=>e(O).slice(0,c));let H=$(Lt({})),B=$("loading");W();function se(_){const F=_.value("pricing.tiers");return Array.isArray(F)?F.map(I=>{const Q=I??{},qe=Q.discount_percent;return{code:String(Q.code??""),label:String(Q.label??""),abbrev:String(Q.abbrev??""),written:qe===void 0?null:String(qe),discount:_e(qe)?qe:null,rank:Number(Q.rank??0)}}):[]}function _e(_){return typeof _!="number"||!Number.isFinite(_)||_<0||_>100?!1:Math.abs(_*10-Math.round(_*10))<1e-9}const ye=o(()=>String(t.draft.value("pricing.reference_code")??""));function ce(_){return _===null?"":String(_).replace(".",",")}function j(_){const F=1e3-Math.round(_*10);return`${String(Math.trunc(F/100))},${String(F%100).padStart(2,"0")}`}function V(_,F){const I=Number(F);F.trim()===""||Number.isNaN(I)||t.draft.set(_,I)}function le(_,F){const I=F.trim().replace(",",".");if(!/^\d{1,3}(\.\d)?$/u.test(I))return;const Q=Number(I);Q>100||t.draft.set(_,Q)}function re(_,F){!(_ instanceof HTMLInputElement)||_.value===F||(_.value=F)}async function W(){try{const _=await Ba();v(H,Object.fromEntries(_.products.map(F=>[F.id,F.name])),!0),v(B,"read")}catch{v(B,"unread")}}function D(_){const F=e(H)[_];return F!==void 0?F:e(B)==="loading"?"Lecture du nom…":e(B)==="read"?"Produit absent du catalogue en service":"Nom non lu"}function Y(_){return _<=1?`${String(_)} tarif déclaré`:`${String(_)} tarifs déclarés`}const z=o(()=>`${me(e(x).length)} ${e(x).length>1?"verdicts":"verdict"} sur les quatorze garde-fous.`),P=o(()=>`${me(e(T).length)} ${e(T).length>1?"lignes affichées":"ligne affichée"} sur ${me(e(O).length)} `+(e(A).length===0?`${e(O).length>1?"dérogations en vigueur":"dérogation en vigueur"}.`:`${e(O).length>1?"dérogations enregistrées":"dérogation enregistrée"}, dont ${me(e(A).length)} sur un produit retiré, sans effet : le garde-fou 14 refuse le produit avant que le 8 ait un sens.`)+(e(O).length>e(T).length?" Les autres se lisent produit par produit depuis l’onglet Catalogue.":""));function G(_){return`plancher ${me(_)} g : refusé à ${me(_)} g et en dessous`}const E=[{rank:1,code:"OVERLOAD",label:"Surcharge",when:"La balance annonce elle-même OL, ou le poids brut dépasse la capacité.",severity:"Bloquant",blocking:!0,message:"La balance est en surcharge. Retirez votre article.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la capacité, réglée au garde-fou 9 sous « ${Ft("limits.max_weight_g")} ».`},{rank:2,code:"MEASUREMENT_EXPIRED",label:"Poids périmé",when:"La mesure est plus vieille que la péremption, dans les deux modes de stabilité.",severity:"Bloquant",blocking:!0,message:"Poids indisponible. Patientez ou appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil à régler : le poste calcule lui-même à partir du rythme de la balance."},{rank:3,code:"BASKET_MISSING",label:"Panier absent",when:"Le poids brut tombe dans la fenêtre négative du panier : il a été soulevé.",severity:"Bloquant",blocking:!0,message:"Le panier n'est pas sur la balance. Reposez-le.",thresholds:[{path:"limits.basket_min_g",label:"Bas de la fenêtre du panier",hint:"En grammes, NÉGATIF : c’est le poids du panier que la balance a perdu."},{path:"limits.basket_max_g",label:"Haut de la fenêtre du panier",hint:"Négatif lui aussi, et plus proche de zéro que le bas."}],switchPath:"limits.basket_check_enabled",switchLabel:"Ce poste travaille avec un panier taré",note:"La règle s’active ou non, en bloc : il n’y a pas de demi-mesure à régler."},{rank:4,code:"SCALE_EMPTY",label:"Plateau vide",when:"Le poids brut ne sort pas de la bande « il n’y a rien sur le plateau ».",severity:"Bloquant — un filet, hors parcours nominal",blocking:!0,message:"Posez votre produit.",thresholds:[{path:"limits.empty_max_g",label:"Plateau considéré vide",hint:"En dessous, le poste considère qu’il n’y a rien sur le plateau."}],switchPath:"",switchLabel:"",note:"Toucher une tuile sur un plateau vide ARME la sélection au lieu d’être refusé : la règle reste évaluée pour la saisie manuelle et les chemins dérivés."},{rank:5,code:"TARE_REQUIRED",label:"Remise à zéro nécessaire",when:"Le brut est sous la bande du plateau vide, et hors de la fenêtre du panier.",severity:"Bloquant",blocking:!0,message:"La balance doit être remise à zéro.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la valeur négative de celui du garde-fou 4, « ${Ft("limits.empty_max_g")} ».`},{rank:6,code:"WEIGHT_UNSTABLE",label:"Pesée instable",when:"La trame déclare la mesure instable.",severity:"Information par défaut (A3)",blocking:!1,message:"Pesée en cours…",thresholds:[],switchPath:"",switchLabel:"",note:"La sévérité suit l’exigence de stabilité : elle passe à Bloquant quand celle-ci est réglée sur « blocking ». L’impression n’est jamais bloquée par défaut."},{rank:7,code:"TARE_INVALID",label:"Emballage incohérent",when:"Une tare a été saisie, et elle atteint la pesée ou dépasse le maximum.",severity:"Bloquant",blocking:!0,message:"Le poids de l'emballage est supérieur ou égal à la pesée.",thresholds:[{path:"limits.max_tare_g",label:"Tare maximum",hint:"Une tare plus lourde que le maximum est une faute de frappe."}],switchPath:"",switchLabel:"",note:""},{rank:8,code:"WEIGHT_TOO_LOW",label:"Poids trop faible",when:"Vente au poids : le NET est strictement positif et ne dépasse pas le plancher.",severity:"Bloquant",blocking:!0,message:"La balance doit être retarée, ou l'emballage est trop lourd.",thresholds:[{path:"limits.min_weight_g",label:"Poids minimum",hint:"Une dérogation par produit existe, dans l’onglet Catalogue."}],switchPath:"",switchLabel:"",note:""},{rank:9,code:"WEIGHT_TOO_HIGH",label:"Poids trop élevé",when:"Le NET dépasse la capacité — strictement, pour que la capacité reste atteignable.",severity:"Bloquant",blocking:!0,message:"{{.Weight}} kg, ça paraît un peu lourd !",thresholds:[{path:"limits.max_weight_g",label:"Poids maximum",hint:"C’est la capacité du champ NNDDD du code-barres, pas un seuil de vraisemblance."}],switchPath:"",switchLabel:"",note:""},{rank:10,code:"UNITS_OUT_OF_RANGE",label:"Nombre d’unités hors plage",when:"Vente à l’unité : la quantité sort de la plage.",severity:"Bloquant",blocking:!0,message:"{{.Quantity}} unités, ça paraît un peu beaucoup !",thresholds:[{path:"limits.min_units",label:"Unités minimum",hint:""},{path:"limits.max_units",label:"Unités maximum",hint:""}],switchPath:"",switchLabel:"",note:""},{rank:11,code:"AMOUNT_OUT_OF_CAPACITY",label:"Montant hors capacité du code-barres",when:"La charge utile encode un PRIX, et il dépasse ce que le champ peut porter.",severity:"Bloquant",blocking:!0,message:"Prix trop élevé pour le code-barres.",thresholds:[{path:"limits.max_amount_cents",label:"Montant maximum",hint:"En centimes. Aucun préfixe du plan livré n’encode un prix : la règle est éprouvée sans qu’aucun produit puisse l’atteindre."}],switchPath:"",switchLabel:"",note:""},{rank:12,code:"ZERO_PRICE",label:"Prix nul",when:"Le montant du tarif imprimé en grand vaut zéro.",severity:"Bloquant",blocking:!0,message:"Prix nul. Appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : un produit à 0 € est une anomalie sans nuance."},{rank:13,code:"LIGHT_PRODUCT_ALLOWED",label:"Produit léger autorisé",when:"Le garde-fou 8 n’a pas déclenché grâce à la dérogation du produit.",severity:"Information",blocking:!1,message:"",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil général : c’est la dérogation par produit, listée plus bas et posée depuis l’onglet Catalogue. Rien ne s’affiche au client ; l’id du produit est journalisé."},{rank:14,code:"PRODUCT_WITHDRAWN",label:"Produit retiré",when:"Quelqu’un a décidé de ne plus proposer ce produit.",severity:"Bloquant",blocking:!0,message:"Ce produit n'est pas disponible.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : c’est une décision humaine, prise depuis l’onglet Catalogue. Aucune règle d’import ne peut la déduire."}];function b(_){return E.find(F=>F.code===_)?.label??"Garde-fou inconnu de cet écran"}const R=["{{.Weight}}","{{.Quantity}}"];function J(_){return _==="blocking"?"Bloquant":_==="info"?"Information":"Sévérité inconnue de cet écran"}var be=wi(),xe=a(be);Qe(xe,{title:"Grille de tarifs",note:"Un second tarif n’est pas une case à cocher : c’est une ligne de plus dans cette grille.",children:(_,F)=>{var I=ti(),Q=X(I);{var qe=ie=>{var pe=Xl();l(ie,pe)},oe=ie=>{var pe=ei(),we=X(pe),He=a(we),Xe=r(we,2),fe=a(Xe),d=r(a(fe));rt(d,21,()=>e(h),fa,(y,ee,f)=>{var q=Zl(),L=a(q),he=a(L),ne=r(L),Se=a(ne);Be(Se,"aria-label",`Libellé du tarif ${f+1}`);var Le=r(ne),de=a(Le);Be(de,"aria-label",`Abrégé du tarif ${f+1}`);var M=r(Le),Ie=a(M);{var Me=ve=>{var ze=Yl();l(ve,ze)},Ee=ve=>{var ze=$a(),Oe=a(ze);p(()=>i(Oe,`${e(ee).written??""} — le tarif de référence est le prix du catalogue : il - ne peut pas porter de remise, et l’enregistrement la refusera.`)),l(ve,ze)},Te=ve=>{var ze=$a(),Oe=a(ze);p(()=>i(Oe,`${e(ee).written??""} — une remise s’écrit au dixième de point ; celle-ci se - change dans le fichier de configuration.`)),l(ve,ze)},Je=ve=>{var ze=Ql(),Oe=X(ze);Be(Oe,"aria-label",`Remise du tarif ${f+1}`);var nt=r(Oe,2),ut=a(nt);p((pt,je)=>{na(Oe,pt),i(ut,`un produit à 10,00 €/kg s’affiche ${je??""} €/kg`)},[()=>ce(e(ee).discount??0),()=>j(e(ee).discount??0)]),tt("input",Oe,pt=>le(`pricing.tiers.${String(f)}.discount_percent`,pt.currentTarget.value)),tt("focusout",Oe,pt=>re(pt.currentTarget,ce(e(ee).discount??0))),l(ve,ze)};m(Ie,ve=>{e(ee).code===e(ye)&&e(ee).written===null?ve(Me):e(ee).code===e(ye)?ve(Ee,1):e(ee).written!==null&&e(ee).discount===null?ve(Te,2):ve(Je,-1)})}var ae=r(M),De=a(ae);p(()=>{i(he,e(ee).code),na(Se,e(ee).label),na(de,e(ee).abbrev),i(De,e(ee).rank)}),tt("input",Se,ve=>t.draft.set(`pricing.tiers.${String(f)}.label`,ve.currentTarget.value)),tt("input",de,ve=>t.draft.set(`pricing.tiers.${String(f)}.abbrev`,ve.currentTarget.value)),l(y,q)}),p(y=>i(He,`${y??""}.`),[()=>Y(e(h).length)]),l(ie,pe)};m(Q,ie=>{e(h).length===0?ie(qe):ie(oe,-1)})}var S=r(Q,2);{let ie=o(()=>t.draft.text("pricing.primary_code"));_t(S,{label:"Tarif imprimé en grand",path:"pricing.primary_code",get value(){return e(ie)},hint:"Le prix que le client lit sur l’étiquette (A7).",onchange:pe=>t.draft.set("pricing.primary_code",pe)})}var Z=r(S,2);{let ie=o(()=>t.draft.text("pricing.reference_code"));_t(Z,{label:"Tarif qui serait encodé si le code-barres portait un prix",path:"pricing.reference_code",get value(){return e(ie)},hint:"Le plan livré ne porte pas de prix, mais un poids ou un nombre d’unités : la caisse retrouve le prix par la référence, dans Odoo, et ne doit jamais sous-facturer.",onchange:pe=>t.draft.set("pricing.reference_code",pe)})}l(_,I)},$$slots:{default:!0}});var Ve=r(xe,2);Qe(Ve,{title:"Les deux arrondis",children:(_,F)=>{var I=ai(),Q=X(I);{let oe=o(()=>t.draft.text("pricing.unit_price_rounding"));_t(Q,{label:"Arrondi du prix unitaire dérivé",path:"pricing.unit_price_rounding",get value(){return e(oe)},hint:"Il s’applique au prix au kilo calculé par la remise.",onchange:S=>t.draft.set("pricing.unit_price_rounding",S)})}var qe=r(Q,2);{let oe=o(()=>t.draft.text("pricing.amount_rounding"));_t(qe,{label:"Arrondi du montant",path:"pricing.amount_rounding",get value(){return e(oe)},hint:"Il s’applique au montant de l’étiquette.",onchange:S=>t.draft.set("pricing.amount_rounding",S)})}l(_,I)},$$slots:{default:!0}});var Ae=r(Ve,2);Qe(Ae,{title:"Les quatorze garde-fous, dans l’ordre d’évaluation",note:"Le seuil se modifie ici. La sévérité ne se règle pas : elle dit si le poste refuse ou avertit, et cela ne dépend pas du magasin. Le message affiché au client n’est pas encore modifiable depuis cet écran.",children:(_,F)=>{var I=pi(),Q=r(X(I),2);rt(Q,21,()=>E,pe=>pe.code,(pe,we)=>{var He=ii(),Xe=a(He),fe=a(Xe),d=a(fe),y=r(fe,2),ee=a(y),f=r(y,2),q=a(f),L=r(f,2),he=a(L),ne=r(Xe,2),Se=a(ne),Le=r(ne,2);{var de=ae=>{var De=ri();l(ae,De)},M=ae=>{var De=ni(),ve=a(De);p(()=>i(ve,`« ${e(we).message??""} »`)),l(ae,De)};m(Le,ae=>{e(we).message===""?ae(de):ae(M,-1)})}var Ie=r(Le,2);{var Me=ae=>{s(ae,()=>e(we).switchPath,()=>e(we).switchLabel,()=>"")};m(Ie,ae=>{e(we).switchPath!==""&&ae(Me)})}var Ee=r(Ie,2);rt(Ee,17,()=>e(we).thresholds,ae=>ae.path,(ae,De)=>{var ve=si(),ze=a(ve);{let Oe=o(()=>t.draft.text(e(De).path));_t(ze,{get label(){return e(De).label},get path(){return e(De).path},kind:"number",get value(){return e(Oe)},get hint(){return e(De).hint},onchange:nt=>V(e(De).path,nt)})}tt("focusout",ve,Oe=>re(Oe.target,t.draft.text(e(De).path))),l(ae,ve)});var Te=r(Ee,2);{var Je=ae=>{var De=li(),ve=a(De);p(()=>i(ve,e(we).note)),l(ae,De)};m(Te,ae=>{e(we).note!==""&&ae(Je)})}p(ae=>{Be(He,"data-code",e(we).code),i(d,e(we).rank),i(ee,e(we).label),i(q,e(we).code),Be(L,"data-blocking",ae),i(he,e(we).severity),i(Se,e(we).when)},[()=>String(e(we).blocking)]),l(pe,He)});var qe=r(Q,4),oe=a(qe),S=r(qe,4);{var Z=pe=>{var we=oi();l(pe,we)},ie=pe=>{var we=vi(),He=X(we),Xe=a(He),fe=r(He,2);rt(fe,21,()=>e(x),d=>d.code,(d,y)=>{var ee=di(),f=a(ee),q=a(f),L=r(f,2),he=a(L),ne=r(L,2),Se=a(ne),Le=r(ne,2);{var de=Me=>{var Ee=ui(),Te=a(Ee);p(()=>i(Te,`« ${e(y).message??""} »`)),l(Me,Ee)};m(Le,Me=>{e(y).message!==""&&Me(de)})}var M=r(Le,2);{var Ie=Me=>{var Ee=ci(),Te=X(Ee),Je=a(Te),ae=r(Te,2),De=a(ae);p(ve=>{i(Je,ve),i(De,e(y).product_id)},[()=>D(e(y).product_id)]),l(Me,Ee)};m(M,Me=>{e(y).product_id!==""&&Me(Ie)})}p((Me,Ee,Te)=>{Be(ee,"data-blocking",Me),i(q,Ee),i(he,e(y).code),i(Se,Te)},[()=>String(e(y).blocking),()=>b(e(y).code),()=>J(e(y).severity)]),l(d,ee)}),p(d=>{Be(He,"data-verdicts",d),i(Xe,e(z))},[()=>String(e(x).length)]),l(pe,we)};m(S,pe=>{e(x).length===0?pe(Z):pe(ie,-1)})}p(pe=>i(oe,`Les marqueurs ${pe??""} sont remplacés par les valeurs de la pesée au - moment où le message s’affiche.`),[()=>R.join(" et ")]),l(_,I)},$$slots:{default:!0}});var w=r(Ae,2);Qe(w,{title:"Code-barres",note:"Un seul réglage, et c’est voulu : le reste du plan de numérotation n’est pas de la configuration.",children:(_,F)=>{var I=fi(),Q=X(I);s(Q,()=>"barcode.verify_reference_check_digit",()=>"Refuser une référence dont la clé de contrôle est fausse",()=>"Décoché, le poste recalcule une clé juste sur une référence fausse, en silence — et la caisse encaisse un autre article."),l(_,I)},$$slots:{default:!0}});var N=r(w,2);Qe(N,{title:"Dérogations de poids minimum",note:"En lecture ici ; elles se modifient depuis l’onglet Catalogue, là où se trouve le produit.",children:(_,F)=>{var I=Dt(),Q=X(I);{var qe=S=>{var Z=gi();l(S,Z)},oe=S=>{var Z=bi(),ie=X(Z),pe=a(ie),we=r(ie,2);{var He=fe=>{var d=mi();l(fe,d)};m(we,fe=>{e(B)==="unread"&&fe(He)})}var Xe=r(we,2);rt(Xe,21,()=>e(T),fe=>fe.product_id,(fe,d)=>{var y=_i(),ee=a(y),f=a(ee),q=r(ee,2),L=a(q),he=r(q,2),ne=a(he),Se=r(he,2),Le=a(Se),de=r(Se,2),M=a(de),Ie=r(de,2);{var Me=Ee=>{var Te=hi();l(Ee,Te)};m(Ie,Ee=>{e(d).offered||Ee(Me)})}p((Ee,Te,Je,ae)=>{Be(y,"data-withdrawn",Ee),i(f,Te),i(L,e(d).product_id),i(ne,Je),i(Le,e(d).reason),i(M,`${ae??""}, ${e(d).decided_by??""}`)},[()=>String(!e(d).offered),()=>D(e(d).product_id),()=>G(e(d).min_weight_g??0),()=>da(e(d).decided_at)]),l(fe,y)}),p(()=>i(pe,e(P))),l(S,Z)};m(Q,S=>{e(O).length===0?S(qe):S(oe,-1)})}l(_,I)},$$slots:{default:!0}}),l(n,be),Ct()}At(["change","input","focusout"]);function xi(n,t){const s=[];for(const c of[...ya(n),...ya(t)]){if(s.some(O=>O.path===c))continue;const h=Da(va(n,c)),x=Da(va(t,c));h!==x&&s.push({path:c,before:h,after:x})}return s}function va(n,t){let s=n;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}function ya(n,t=""){if(n===null||typeof n!="object"||Array.isArray(n))return t===""?[]:[t];const s=[];for(const[c,h]of Object.entries(n))s.push(...ya(h,t===""?c:`${t}.${c}`));return s}function Da(n){return n===void 0?"—":n===null?"vide":typeof n=="boolean"?n?"oui":"non":typeof n=="object"?JSON.stringify(n):String(n)}var ki=u('
                                                                                              Empreinte de la configuration en service
                                                                                              Version du binaire
                                                                                              Répertoire de données
                                                                                              Espace disque
                                                                                              ',1),qi=u('

                                                                                              '),Ia=u(" "),Si=u(' '),Li=u("
                                                                                            • "),Ei=u(`

                                                                                                Recopier reste possible : les valeurs entrent dans le brouillon, où elles se - corrigent champ par champ avant l’enregistrement.

                                                                                                `),zi=u(`

                                                                                                Ce fichier n’a été comparé à rien : la configuration en service n’a pas pu être - lue. Ni « identique », ni « n champs changent » — ce que ce fichier changerait - sur ce poste reste inconnu.

                                                                                                `),ji=u(`

                                                                                                Ce fichier décrit la même configuration que celle en service : il n’y a rien à - recopier. C’est ce qu’on veut lire à la fin d’un clonage. Deux champs ne sont pas - comparés : la date du dernier enregistrement, que chaque poste écrit lui-même, et - le mot de passe du catalogue, qu’aucun des deux ne porte en clair.

                                                                                                `),Pi=u(' '),Ti=u('

                                                                                                '),Ci=u(`

                                                                                                ChampEn serviceDans le fichier

                                                                                                Recopier n’applique rien : les valeurs entrent dans le brouillon, et c’est - « Enregistrer » qui les met en service.

                                                                                                `,1),Oi=u('

                                                                                                ',1),Ri=u(`

                                                                                                L’export emporte encore l’empreinte du mot de passe : c’est la seule lecture que le - poste garde derrière la clé. L’import, lui, est lu PAR LE POSTE, qui écarte les deux - secrets et le numéro de poste avant de dire ce qui changerait.

                                                                                                `,1),Ai=u('

                                                                                                Lecture des versions enregistrées…

                                                                                                '),Ni=u(`

                                                                                                Les versions enregistrées n’ont pas pu être lues : cette liste ne dit donc rien de - ce que ce poste garde. Ce n’est pas « aucune version ».

                                                                                                `),$i=u('

                                                                                                Aucune version enregistrée : ce poste n’a jamais été reconfiguré.

                                                                                                '),Di=u('
                                                                                              • '),Ii=u(`

                                                                                                  Remettre une version en service remplace la configuration du poste sur-le-champ, et - ce qui n’a pas été enregistré est perdu : c’est le seul geste de cette page qui - change le poste tout de suite, et le seul qui garde ses 72 px.

                                                                                                  `,1),Ui=u('
                                                                                                  ');function Fi(n,t){Tt(t,!0);const s=vt(t,"admin",7),c=40,h=20,x=5,O=new Set(["modified_at","catalog.options.password"]),A=[{path:"station.name",name:"le nom du poste"},{path:"network.listen",name:"l’adresse d’écoute"},{path:"scale.options",name:"les réglages de la balance"},{path:"printer.options",name:"les réglages de l’imprimante"},{path:"catalog.options",name:"la source du catalogue, compte compris"}];let T=$(Lt([])),H=$("reading"),B=$(null),se=$(""),_e=$(null),ye=$(""),ce=$(Lt([])),j=$(""),V="";const le=o(()=>s().busy||e(j)!==""),re=o(()=>J(e(B),e(_e))),W=o(()=>e(B)!==null&&e(_e)!==null),D=o(()=>be(e(B),e(_e))),Y=o(()=>e(re).slice(0,c)),z=o(()=>e(ce).slice(0,h)),P=o(()=>e(T).slice(0,x)),G=o(()=>Xe(e(Y).length,e(re).length,"champ qui change","champs qui changent")),E=o(()=>Xe(e(z).length,e(ce).length,"contrôle refuse une clé","contrôles refusent une clé")),b=o(()=>Xe(e(P).length,e(T).length,"version enregistrée","versions enregistrées")),R=o(()=>e(re).length>1?`Recopier ces ${me(e(re).length)} champs dans le brouillon`:"Recopier ce champ dans le brouillon");Ae();function J(f,q){return f===null||q===null?[]:xi(f,q).filter(L=>!O.has(L.path))}function be(f,q){if(f===null||q===null)return[];const L=f,he=q;return A.filter(ne=>xe(he,ne.path)&&!xe(L,ne.path))}function xe(f,q){const L=va(f,q);return L==null||L===""?!0:typeof L!="object"||Array.isArray(L)?!1:Object.keys(L).length===0}function Ve(f){return f.length<2?f.join(""):`${f.slice(0,-1).join(", ")} et ${f[f.length-1]??""}`}aa(()=>{const f=t.health.config_fingerprint;f!==V&&(V=f,w())});async function Ae(){const f=await s().load(Ar);if(f===null){v(H,"unreadable");return}v(T,f,!0),v(H,"read")}async function w(){try{v(B,(await qa()).config,!0),v(se,"")}catch(f){v(B,null),v(se,f instanceof Error?f.message:"Le poste n’a pas répondu.",!0)}}async function N(f){v(j,f?"export-all":"export-clone",!0),s().notice="",s().actionError="";try{const q=await s().protect(()=>_(f));if(q===null)return;Q(q.name,q.blob),s().notice=`${q.name} est remis au navigateur : c’est lui qui l’enregistre, voyez ses téléchargements.`}finally{v(j,"")}}async function _(f){const L=await fetch(`/admin/api/config/export?hardware=${f?"1":"0"}`,{headers:{accept:"application/json"}});if(!L.ok)throw new It(L.status,pe(await L.text(),"L’export"));return{name:F(f),blob:await L.blob()}}function F(f){const q=f?"":"-sans-materiel";return`config-poste${String(t.health.station)}${q}-${I()}.json`}function I(){const f=new Date,q=String(f.getMonth()+1).padStart(2,"0"),L=String(f.getDate()).padStart(2,"0");return`${String(f.getFullYear())}-${q}-${L}`}function Q(f,q){const L=URL.createObjectURL(q),he=document.createElement("a");he.href=L,he.download=f,document.body.appendChild(he),he.click(),he.remove(),setTimeout(()=>{URL.revokeObjectURL(L)},0)}async function qe(f){const q=await fetch("/admin/api/config/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)}),L=await q.text();if(!q.ok)throw new It(q.status,pe(L,"L’import"));return JSON.parse(L)}async function oe(f){if(f==null)return;s().notice="",s().actionError="";let q;try{q=JSON.parse(await f.text())}catch{s().actionError=`${f.name} n’est pas un fichier JSON lisible.`;return}v(j,"import");try{const L=await s().protect(()=>qe(q));if(L===null)return;v(_e,L.config,!0),v(ye,f.name,!0),v(ce,L.faults,!0),await w(),s().notice=S(f.name)}finally{v(j,"")}}function S(f){return e(W)?e(re).length===0?`${f} décrit la même configuration que celle en service.`:`${f} est lu. Rien n’est appliqué : relisez le tableau.`:`${f} est lu, mais la configuration en service ne l’est pas : rien ne peut être comparé.`}async function Z(){if(e(_e)===null)return;const f=e(_e),q=[...e(re)];for(const L of q)t.draft.set(L.path,va(f,L.path));s().actionError="",s().notice="Le fichier est recopié dans le brouillon. Rien n’est appliqué avant « Enregistrer ».",await w()}async function ie(f){v(j,`restore-${String(f)}`),s().notice="",s().actionError="";try{if(await s().protect(()=>Nr(f))===null)return;await t.draft.load(),await Ae(),await w(),await s().refresh(),s().notice=`La version ${String(f)} est remise en service.`}finally{v(j,"")}}function pe(f,q){try{const L=JSON.parse(f);if(typeof L?.message=="string"&&L.message!=="")return L.message}catch{}return`${q} a été refusé par le poste.`}function we(f){return t.draft.faults.find(q=>q.field===f)?.message??""}function He(f){return t.draft.faults.find(q=>q.field===f)?.allowed??[]}function Xe(f,q,L,he){const ne=q>1?he:L;return f>=q?`${me(q)} ${ne}.`:`${me(f)} lignes affichées sur ${me(q)} ${ne}.`}var fe=Ui(),d=a(fe);Qe(d,{title:"Identité du poste",children:(f,q)=>{var L=ki(),he=X(L);{let ze=o(()=>t.draft.text("station.number")),Oe=o(()=>we("station.number")),nt=o(()=>He("station.number"));_t(he,{label:"Numéro du poste",path:"station.number",kind:"number",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},hint:"C’est de lui que dérive le nom du fichier de catalogue attendu, flv_.csv.",onchange:ut=>t.draft.set("station.number",Number(ut))})}var ne=r(he,2);{let ze=o(()=>t.draft.text("station.name")),Oe=o(()=>we("station.name")),nt=o(()=>He("station.name"));_t(ne,{label:"Nom du poste",path:"station.name",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},hint:"Ce que lit un bénévole : « Poste 2 — fruits ».",onchange:ut=>t.draft.set("station.name",ut)})}var Se=r(ne,2);{let ze=o(()=>t.draft.text("station.coop")),Oe=o(()=>we("station.coop")),nt=o(()=>He("station.coop"));_t(Se,{label:"Coopérative",path:"station.coop",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},onchange:ut=>t.draft.set("station.coop",ut)})}var Le=r(Se,2),de=r(a(Le),2),M=a(de),Ie=r(de,4),Me=a(Ie),Ee=r(Ie,4),Te=a(Ee),Je=r(Ee,4),ae=a(Je);{var De=ze=>{var Oe=yt("non mesuré");l(ze,Oe)},ve=ze=>{var Oe=yt();p((nt,ut,pt)=>i(Oe,`${nt??""} libres sur - ${ut??""} — seuil d’alerte - ${pt??""} Mo`),[()=>wa(t.health.disk.free_bytes),()=>wa(t.health.disk.total_bytes),()=>me(t.health.disk.alert_mb)]),l(ze,Oe)};m(ae,ze=>{t.health.disk===null?ze(De):ze(ve,-1)})}p(()=>{i(M,t.health.config_fingerprint),i(Me,t.health.version),i(Te,t.health.disk===null?"non publié par ce poste":t.health.disk.path)}),l(f,L)},$$slots:{default:!0}});var y=r(d,2);Qe(y,{title:"Exporter, importer",note:"Pour installer un autre poste : ce fichier emporte les tarifs, les garde-fous, l’étiquette et les catégories. Tout ce qui est propre à ce poste-ci reste ici — le mot de passe, le code de secours, le numéro et le nom du poste, les réglages de la balance, ceux de l’imprimante, la source du catalogue et le réseau.",children:(f,q)=>{var L=Ri(),he=X(L),ne=a(he);{let ae=o(()=>e(j)==="export-all");at(ne,{label:"Exporter tout",protected:!0,get busy(){return e(ae)},get disabled(){return e(le)},onrun:()=>{N(!0)}})}var Se=r(ne,2);{let ae=o(()=>e(j)==="export-clone");at(Se,{label:"Exporter sans le matériel",protected:!0,get busy(){return e(ae)},get disabled(){return e(le)},onrun:()=>{N(!1)}})}var Le=r(Se,2);let de;var M=a(Le),Ie=r(M,3),Me=r(he,4);{var Ee=ae=>{var De=qi(),ve=a(De);p(()=>i(ve,`La configuration en service n’a pas pu être lue : ${e(se)??""} La colonne - « En service » ne peut donc rien affirmer.`)),l(ae,De)};m(Me,ae=>{e(se)!==""&&ae(Ee)})}var Te=r(Me,2);{var Je=ae=>{var De=Oi(),ve=X(De),ze=a(ve),Oe=r(ve,2);{var nt=Ze=>{var bt=Ei(),Ot=a(bt),Ut=a(Ot),k=r(Ot,2);rt(k,21,()=>e(z),fa,(K,ue)=>{var Re=Li(),We=a(Re),Ye=a(We),Ke=r(We,2);{var U=ke=>{var $e=Ia(),Pe=a($e);p(()=>i(Pe,e(ue).field)),l(ke,$e)};m(Ke,ke=>{Gt.showTechnicalNames&&ke(U)})}var te=r(Ke),ge=r(te);{var Ne=ke=>{var $e=Si(),Pe=a($e);p(Ue=>i(Pe,`Valeurs acceptées : ${Ue??""}.`),[()=>e(ue).allowed.join(", ")]),l(ke,$e)};m(ge,ke=>{e(ue).allowed!==void 0&&e(ue).allowed.length>0&&ke(Ne)})}p(ke=>{i(Ye,ke),i(te,` ${e(ue).message??""} `)},[()=>Ft(e(ue).field)]),l(K,Re)}),p(()=>i(Ut,`Ce fichier serait refusé en l’état : ${e(E)??""}`)),l(Ze,bt)};m(Oe,Ze=>{e(ce).length>0&&Ze(nt)})}var ut=r(Oe,2);{var pt=Ze=>{var bt=zi();l(Ze,bt)},je=Ze=>{var bt=ji();l(Ze,bt)},st=Ze=>{var bt=Ci(),Ot=X(bt),Ut=a(Ot),k=r(Ut);{var K=ge=>{var Ne=yt("Les autres sont dans le fichier, et « Recopier » les prend tous.");l(ge,Ne)};m(k,ge=>{e(re).length>e(Y).length&&ge(K)})}var ue=r(Ot,2),Re=a(ue),We=r(a(Re));rt(We,21,()=>e(Y),ge=>ge.path,(ge,Ne)=>{const ke=o(()=>Ft(e(Ne).path));var $e=Pi(),Pe=a($e),Ue=a(Pe),Ge=r(Ue);{var ft=gt=>{var xt=Ia(),Nt=a(xt);p(()=>i(Nt,e(Ne).path)),l(gt,xt)};m(Ge,gt=>{Gt.showTechnicalNames&&e(ke)!==e(Ne).path&>(ft)})}var ot=r(Pe),ct=a(ot),it=r(ot),dt=a(it);p(()=>{Be($e,"data-path",e(Ne).path),i(Ue,`${e(ke)??""} `),i(ct,e(Ne).before),i(dt,e(Ne).after)}),l(ge,$e)});var Ye=r(ue,2);{var Ke=ge=>{var Ne=Ti(),ke=a(Ne);p($e=>i(ke,`Ce fichier ne porte pas ${$e??""} : - l’export sans le matériel les retire, et l’import ne remet que le numéro du - poste. Les lignes correspondantes sont VIDES ci-dessus, et - « Recopier » recopie ce vide dans le brouillon.`),[()=>Ve(e(D).map($e=>$e.name))]),l(ge,Ne)};m(Ye,ge=>{e(D).length>0&&ge(Ke)})}var U=r(Ye,2),te=a(U);at(te,{kind:"write",get label(){return e(R)},get disabled(){return e(le)},onrun:()=>{Z()}}),p(()=>i(Ut,`${e(G)??""} `)),l(Ze,bt)};m(ut,Ze=>{e(W)?e(re).length===0?Ze(je,1):Ze(st,-1):Ze(pt)})}p(()=>i(ze,`Fichier lu : ${e(ye)??""}`)),l(ae,De)};m(Te,ae=>{e(_e)!==null&&ae(Je)})}p(()=>{de=Ht(Le,1,"choose svelte-18wbxwu",null,de,{working:e(j)==="import",off:e(le)}),i(M,`${e(j)==="import"?"Lecture du fichier…":"Importer un fichier"} `),Ie.disabled=e(le)}),tt("change",Ie,ae=>{oe(ae.currentTarget.files?.item(0))}),l(f,L)},$$slots:{default:!0}});var ee=r(y,2);Qe(ee,{title:"Cinq versions restaurables",note:"Chaque enregistrement fait tourner les versions : la plus récente est la 1.",children:(f,q)=>{var L=Dt(),he=X(L);{var ne=M=>{var Ie=Ai();l(M,Ie)},Se=M=>{var Ie=Ni();l(M,Ie)},Le=M=>{var Ie=$i();l(M,Ie)},de=M=>{var Ie=Ii(),Me=X(Ie),Ee=a(Me),Te=r(Me,2),Je=a(Te);rt(Je,21,()=>e(P),ae=>ae.version,(ae,De)=>{var ve=Di(),ze=a(ve),Oe=a(ze),nt=r(ze,2),ut=a(nt),pt=r(nt,2),je=a(pt),st=r(pt,2);{let Ze=o(()=>e(j)===`restore-${String(e(De).version)}`);at(st,{kind:"destructive",label:"Remettre cette version en service",protected:!0,get busy(){return e(Ze)},get disabled(){return e(le)},onrun:()=>{ie(e(De).version)}})}p((Ze,bt)=>{i(Oe,`version ${Ze??""}`),i(ut,bt),i(je,e(De).config_fingerprint)},[()=>me(e(De).version),()=>Mt(e(De).modified_at)]),l(ae,ve)}),p(()=>i(Ee,`${e(b)??""} Le poste n’en garde jamais plus de cinq.`)),l(M,Ie)};m(he,M=>{e(H)==="reading"?M(ne):e(H)==="unreadable"?M(Se,1):e(T).length===0?M(Le,2):M(de,-1)})}l(f,L)},$$slots:{default:!0}}),l(n,fe),Ct()}At(["change"]);var Mi=u('clé'),Bi=u(' '),Vi=u('');function Xt(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"hint",3,""),h=vt(t,"disabled",3,!1),x=vt(t,"engaged",3,!1),O=vt(t,"busy",3,!1),A=vt(t,"protected",3,!1);var T=Vi();let H;var B=a(T),se=a(B),_e=r(se);{var ye=V=>{var le=Mi();l(V,le)};m(_e,V=>{A()&&V(ye)})}var ce=r(B,2);{var j=V=>{var le=Bi(),re=a(le);p(()=>i(re,O()?"En cours…":c())),l(V,le)};m(ce,V=>{c()!==""&&V(j)})}p(()=>{H=Ht(T,1,`big touch-target ${s()??""}`,"svelte-15nlt6i",H,{engaged:x(),busy:O()}),Be(T,"data-kind",s()),T.disabled=h()||O(),i(se,`${t.label??""} `)}),tt("click",T,function(...V){t.onrun?.apply(this,V)}),l(n,T)}At(["click"]);const Wi="ERR-SCL-09";var Gi=u('

                                                                                                  '),Hi=u('

                                                                                                  '),Ji=u('

                                                                                                  Déposez ici le fichier clé

                                                                                                  '),Ki=u('

                                                                                                  ',1),Xi=u('');function Yi(n,t){Tt(t,!0);const s=vt(t,"admin",7);let c=$("");const h=o(()=>t.health.state.degraded?.code===Wi),x=o(()=>t.health.printing),O=o(()=>e(x)?.on_fallback===!0);let A=$(!1),T=$("");const H=o(()=>s().busy||e(T)!=="");async function B(w,N){v(c,""),v(T,w,!0);try{await s().run(N)}finally{v(T,"")}}async function se(w,N){v(c,""),v(T,w,!0);try{const _=await s().load(N);_!==null&&v(c,_.message,!0)}finally{v(T,"")}}async function _e(w,N){v(c,""),v(T,w,!0);try{const _=await s().protect(N);_!==null&&(s().notice=_.message,await s().refresh())}finally{v(T,"")}}async function ye(w){if(v(A,!1),w!=null){v(c,""),v(T,"import");try{const N=await s().protect(()=>Ga(w));if(N===null)return;v(c,N.result==="rejected"||N.result==="failed"?`${w.name} : REFUSÉ${N.reason===""?"":" — "+N.reason}. Le catalogue en service n’a pas changé.`:`${w.name} : ${String(N.rows_read_count)} lignes lues, ${String(N.weighable_count)} pesables. La veille l’appliquera dans la seconde.`,!0),await s().refresh()}finally{v(T,"")}}}var ce=Xi(),j=a(ce);{var V=w=>{var N=Gi(),_=a(N);p(()=>i(_,e(c))),l(w,N)};m(j,w=>{e(c)!==""&&w(V)})}var le=r(j,2);{var re=w=>{var N=Hi(),_=a(N);p(()=>i(_,e(x).banner)),l(w,N)};m(le,w=>{e(x)!==null&&e(x).banner!==""&&w(re)})}var W=r(le,2),D=a(W);{let w=o(()=>e(T)==="scale");Xt(D,{label:"Tester la balance",hint:"Ce que le poste a déjà observé — le port n’est pas rouvert.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{se("scale",Lr)}})}var Y=r(D,2);{let w=o(()=>e(T)==="printer");Xt(Y,{label:"Tester l’imprimante",hint:"Ce que le superviseur a vu il y a moins d’une seconde.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{se("printer",Er)}})}var z=r(Y,2);{let w=o(()=>e(T)==="label");Xt(z,{label:"Imprimer une étiquette de test",hint:"Une étiquette de démonstration sort de l’imprimante du poste.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("label",zr)}})}var P=r(z,2);{let w=o(()=>e(T)==="reprint");Xt(P,{label:"Réimprimer la dernière",hint:"La dernière étiquette imprimée sort une seconde fois.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("reprint",yr)}})}var G=r(P,2);{let w=o(()=>e(T)==="reload");Xt(G,{label:"Recharger le catalogue",kind:"write",hint:"La veille refait tout de suite le contrôle qu’elle fait toutes les cinq secondes.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("reload",xr)}})}var E=r(G,2);{let w=o(()=>e(h)?"Revenir à la balance":"Basculer en saisie manuelle"),N=o(()=>e(h)?"Le poids sera de nouveau lu sur la balance.":"Le poids se tape à la main : le poste continue de servir sans balance."),_=o(()=>e(T)==="manual");Xt(E,{get label(){return e(w)},kind:"destructive",get hint(){return e(N)},get engaged(){return e(h)},protected:!0,get busy(){return e(_)},get disabled(){return e(H)},onrun:()=>{_e("manual",()=>kr(!e(h)))}})}var b=r(E,2);{let w=o(()=>e(T)==="roll");Xt(b,{label:"J’ai changé le rouleau",kind:"write",hint:"Le compteur d’étiquettes repart à zéro. C’est le seul geste qui dise quelque chose de vrai du papier.",get busy(){return e(w)},get disabled(){return e(H)},onrun:()=>{B("roll",qr)}})}var R=r(b,2);{var J=w=>{{let N=o(()=>e(O)?"Revenir à l’imprimante du poste":"Imprimer sur l’imprimante du poste voisin"),_=o(()=>e(O)?"Les étiquettes repartiront sur l’imprimante de ce poste.":"Les étiquettes sortiront sur l’imprimante voisine, pour cette session seulement."),F=o(()=>e(T)==="fallback");Xt(w,{get label(){return e(N)},kind:"write",get hint(){return e(_)},get engaged(){return e(O)},get busy(){return e(F)},get disabled(){return e(H)},onrun:()=>{B("fallback",()=>Sr(!e(O)))}})}};m(R,w=>{e(x)!==null&&e(x).fallback_available&&w(J)})}var be=r(R,2),xe=r(W,2);Qe(xe,{title:"Importer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(w,N)=>{var _=Ji();let F;var I=a(_),Q=r(a(I)),qe=a(Q),oe=r(I,2),S=r(a(oe));p(()=>{F=Ht(_,1,"drop svelte-1yd0nzg",null,F,{dropping:e(A)}),i(qe,`flv_${t.health.station??""}.csv`)}),Qt("dragover",_,Z=>{Z.preventDefault(),v(A,!0)}),Qt("dragleave",_,()=>v(A,!1)),Qt("drop",_,Z=>{Z.preventDefault(),ye(Z.dataTransfer?.files.item(0))}),tt("change",S,Z=>{ye(Z.currentTarget.files?.item(0))}),l(w,_)},$$slots:{default:!0}});var Ve=r(xe,2);{var Ae=w=>{Qe(w,{title:"Le dernier fichier a été refusé",children:(N,_)=>{var F=Ki(),I=X(F),Q=a(I),qe=r(I,2),oe=a(qe);p(S=>{i(Q,`${(t.health.catalog.reason===""?"Aucun motif enregistré.":t.health.catalog.reason)??""} - Le catalogue en service n’a pas changé.`),i(oe,`Dernier essai : ${S??""}.`)},[()=>Mt(t.health.catalog.occurred_at)]),l(N,F)},$$slots:{default:!0}})};m(Ve,w=>{t.health.catalog!==null&&t.health.catalog.result==="rejected"&&w(Ae)})}p(()=>Be(be,"href",jr)),l(n,ce),Ct()}At(["change"]);const Qi={succeeded:"La dernière mise à jour a réussi.","rolled-back":"La dernière mise à jour a échoué. La version précédente a été remise et le poste fonctionne.","rolled-back-unhealthy":"La dernière mise à jour a échoué et le poste n’a pas redémarré. Appelez le support.","not-started":"La dernière mise à jour n’a pas démarré : rien n’a été remplacé. Vous pouvez réessayer."},Zi=2e3,eo=300*1e3;var to=u('

                                                                                                  '),Ua=u('

                                                                                                  '),ao=u("
                                                                                                  Dernière vérification
                                                                                                  ",1),ro=u(`

                                                                                                  La mise à jour depuis cet écran n’existe que sur les postes Windows. Sur les - autres, elle se fait à la main — voir la notice d’installation.

                                                                                                  `),no=u('Voir les nouveautés'),so=u('

                                                                                                  Version disponible : .

                                                                                                  '),lo=u('

                                                                                                  Ce poste est à jour.

                                                                                                  '),io=u("
                                                                                                  Version installée
                                                                                                  Dépôt suivi
                                                                                                  ",1),oo=u(`

                                                                                                  Le poste va s’arrêter environ une minute. L’écran client s’éteindra puis reviendra - tout seul. Si la nouvelle version ne démarre pas, la précédente sera remise - automatiquement — mais les données enregistrées, elles, ne reviendront pas en - arrière.

                                                                                                  `),uo=u('

                                                                                                  Mise à jour en cours. Le poste redémarre, ne le débranchez pas.

                                                                                                  '),co=u('
                                                                                                  ',1),vo=u("
                                                                                                  Terminée le
                                                                                                  ",1),po=u("
                                                                                                  Raison
                                                                                                  ",1),fo=u('

                                                                                                  Depuis
                                                                                                  Vers
                                                                                                  ',1),go=u(" ",1);function mo(n,t){Tt(t,!0);let s=$(null),c=$(""),h=$(""),x=$(!1),O=$("");const A=o(()=>e(s)?.outcome??null),T=o(()=>e(s)?.supported===!0&&e(s).available);aa(()=>{H()});async function H(){try{v(s,await Yr(),!0),v(c,"")}catch(W){v(c,W instanceof Error?W.message:"Lecture impossible.",!0)}}async function B(){v(h,"check");try{const W=await t.admin.protect(()=>Qr());W!==null&&(v(s,W,!0),v(c,""))}finally{v(h,"")}}async function se(W){v(h,"apply"),v(O,"");try{if(await t.admin.protect(()=>Zr(W))===null)return;v(x,!0),v(O,await _e()?"":"Le poste n’a pas répondu dans les cinq minutes. Allez le voir.",!0),await H()}finally{v(h,""),v(x,!1)}}async function _e(){const W=Date.now()+eo;for(;Date.now()setTimeout(D,Zi));try{if((await fetch("/healthz",{cache:"no-store"})).ok)return!0}catch{}}return!1}var ye=go(),ce=X(ye);Qe(ce,{title:"Version de ce poste",children:(W,D)=>{var Y=Dt(),z=X(Y);{var P=E=>{var b=to(),R=a(b);p(()=>i(R,e(c)===""?"Lecture…":e(c))),l(E,b)},G=E=>{var b=io(),R=X(b);{var J=oe=>{var S=Ua(),Z=a(S);p(()=>i(Z,e(c))),l(oe,S)};m(R,oe=>{e(c)!==""&&oe(J)})}var be=r(R,2),xe=r(a(be),2),Ve=a(xe),Ae=r(xe,4),w=a(Ae),N=r(Ae,2);{var _=oe=>{var S=ao(),Z=r(X(S),2),ie=a(Z);p(pe=>i(ie,pe),[()=>Mt(e(s).checked_at)]),l(oe,S)};m(N,oe=>{e(s).checked_at!==""&&oe(_)})}var F=r(be,2);{var I=oe=>{var S=ro();l(oe,S)},Q=oe=>{var S=so(),Z=r(a(S)),ie=a(Z),pe=r(Z,2);{var we=fe=>{var d=yt();p(y=>i(d,`, publiée le ${y??""}`),[()=>Mt(e(s).published_at)]),l(fe,d)};m(pe,fe=>{e(s).published_at!==""&&fe(we)})}var He=r(pe,2);{var Xe=fe=>{var d=no();p(()=>Be(d,"href",e(s).html_url)),l(fe,d)};m(He,fe=>{e(s).html_url!==""&&fe(Xe)})}p(()=>i(ie,e(s).latest)),l(oe,S)},qe=oe=>{var S=lo();l(oe,S)};m(F,oe=>{e(s).supported?e(s).available?oe(Q,1):oe(qe,-1):oe(I)})}p(()=>{i(Ve,e(s).running),i(w,e(s).repository)}),l(E,b)};m(z,E=>{e(s)===null?E(P):E(G,-1)})}l(W,Y)},$$slots:{default:!0}});var j=r(ce,2);{var V=W=>{Qe(W,{title:"Installer",children:(D,Y)=>{var z=co(),P=X(z),G=a(P);{let w=o(()=>e(h)==="check");at(G,{kind:"read",label:"Vérifier maintenant",protected:!0,act:"check",get busy(){return e(w)},get disabled(){return e(x)},onrun:()=>{B()}})}var E=r(G,2);{var b=w=>{{let N=o(()=>`Installer la version ${e(s).latest}`),_=o(()=>e(h)==="apply");at(w,{kind:"destructive",get label(){return e(N)},protected:!0,act:"apply",get busy(){return e(_)},onrun:()=>{se(e(s).latest)}})}};m(E,w=>{e(T)&&w(b)})}var R=r(P,2);{var J=w=>{var N=oo();l(w,N)};m(R,w=>{e(T)&&w(J)})}var be=r(R,2);{var xe=w=>{var N=uo();l(w,N)};m(be,w=>{e(x)&&w(xe)})}var Ve=r(be,2);{var Ae=w=>{var N=Ua(),_=a(N);p(()=>i(_,e(O))),l(w,N)};m(Ve,w=>{e(O)!==""&&w(Ae)})}l(D,z)},$$slots:{default:!0}})};m(j,W=>{e(s)!==null&&e(s).supported&&W(V)})}var le=r(j,2);{var re=W=>{Qe(W,{title:"Dernière tentative",children:(D,Y)=>{var z=fo(),P=X(z),G=a(P),E=r(P,2),b=r(a(E),2),R=a(b),J=r(b,4),be=a(J),xe=r(J,2);{var Ve=N=>{var _=vo(),F=r(X(_),2),I=a(F);p(Q=>i(I,Q),[()=>Mt(e(A).finished_at)]),l(N,_)};m(xe,N=>{e(A).finished_at!==""&&N(Ve)})}var Ae=r(xe,2);{var w=N=>{var _=po(),F=r(X(_),2),I=a(F);p(()=>i(I,e(A).reason)),l(N,_)};m(Ae,N=>{e(A).reason!==""&&N(w)})}p(()=>{Be(P,"data-outcome",e(A).status),i(G,Qi[e(A).status]),i(R,e(A).from===""?"—":e(A).from),i(be,e(A).to===""?"—":e(A).to)}),l(D,z)},$$slots:{default:!0}})};m(le,W=>{e(A)!==null&&W(re)})}l(n,ye),Ct()}var ho=u(''),_o=u('

                                                                                                  ',1),bo=u('

                                                                                                  ',1),wo=u(''),yo=u(''),xo=u(''),ko=u(''),qo=u(' '),So=u(''),Lo=u('

                                                                                                  Lecture de l’état du poste…

                                                                                                  '),Eo=u('

                                                                                                  '),zo=u(" "),jo=u("
                                                                                                • "),Po=u('
                                                                                                    '),To=u('
                                                                                                    '),Co=u('

                                                                                                    ');function Oo(n,t){Tt(t,!0);const s=new pn,c=new en(s),h=[{title:"Au quotidien",pages:[{id:"dashboard",label:"Tableau de bord"},{id:"troubleshooting",label:"Dépannage"}]},{title:"Réglages",pages:[{id:"hardware",label:"Matériel"},{id:"label",label:"Étiquette"},{id:"rules",label:"Règles"},{id:"catalog",label:"Catalogue"},{id:"journal",label:"Journal"},{id:"station",label:"Poste"},{id:"update",label:"Mise à jour"}]}],x=o(()=>h.flatMap(d=>d.pages).find(d=>d.id===s.page)?.label??""),O=o(()=>s.health===null||s.health.station_name===""?`Poste ${String(s.health?.station??"")}`:s.health.station_name),A=o(()=>(c.pending?.changed_blocks??[]).map(d=>nn(d)).join(", ")),T=o(()=>(c.pending?.changed_blocks??[]).join(", "));ka(()=>(s.start(),()=>s.stop()));async function H(d){s.open(d),Ca(d)&&c.config===null&&await c.load()}async function B(){await s.protect(()=>c.save())===!0&&(s.notice="La configuration est enregistrée et appliquée.")}var se=Co(),_e=a(se),ye=r(a(_e),2);rt(ye,17,()=>h,d=>d.title,(d,y)=>{var ee=_o(),f=X(ee),q=a(f),L=r(f,2);rt(L,17,()=>e(y).pages,he=>he.id,(he,ne)=>{var Se=ho();let Le;var de=a(Se);p(()=>{Le=Ht(Se,1,"entry svelte-9b2mjq",null,Le,{current:s.page===e(ne).id}),Be(Se,"aria-current",s.page===e(ne).id?"page":void 0),i(de,e(ne).label)}),tt("click",Se,()=>{H(e(ne).id)}),l(he,Se)}),p(()=>i(q,e(y).title)),l(d,ee)});var ce=r(ye,2),j=a(ce);{var V=d=>{var y=bo(),ee=X(y),f=a(ee),q=r(ee,2),L=a(q),he=r(q,2),ne=a(he);p(()=>{i(f,e(O)),i(L,`${s.health.coop??""} · version ${s.health.version??""}`),i(ne,`configuration ${s.health.config_fingerprint??""}`)}),l(d,y)};m(j,d=>{s.health!==null&&d(V)})}var le=r(j,2),re=a(le),W=r(le,2);{var D=d=>{var y=wo();tt("click",y,function(...ee){t.onclose?.apply(this,ee)}),l(d,y)};m(W,d=>{t.onclose!==void 0&&d(D)})}var Y=r(_e,2),z=a(Y);{var P=d=>{var y=yo(),ee=a(y);p(()=>i(ee,s.notice)),l(d,y)};m(z,d=>{s.notice!==""&&d(P)})}var G=r(z,2);{var E=d=>{var y=xo(),ee=a(y);p(()=>i(ee,s.linkError)),l(d,y)};m(G,d=>{s.linkError!==""&&d(E)})}var b=r(G,2);{var R=d=>{var y=ko(),ee=a(y);p(()=>i(ee,s.actionError)),l(d,y)};m(b,d=>{s.actionError!==""&&d(R)})}var J=r(b,2);{var be=d=>{var y=So(),ee=a(y),f=a(ee),q=r(f);{var L=Se=>{var Le=qo(),de=a(Le);p(()=>i(de,e(T))),l(Se,Le)};m(q,Se=>{Gt.showTechnicalNames&&Se(L)})}var he=r(q),ne=r(ee,2);at(ne,{kind:"write",label:"Tout fonctionne : confirmer",onrun:()=>{s.protect(()=>c.confirm())}}),p(()=>{i(f,`Configuration appliquée mais NON CONFIRMÉE. Ce qui a changé : ${e(A)??""}. `),i(he,` Le poste reviendra tout seul à la version précédente dans - ${c.pending.seconds_left??""} secondes si personne ne confirme.`)}),l(d,y)};m(J,d=>{c.pending!==null&&d(be)})}var xe=r(J,2),Ve=a(xe),Ae=a(Ve),w=r(Ve,2);{var N=d=>{var y=Lo();l(d,y)},_=d=>{ll(d,{get health(){return s.health},onshowrows:()=>{H("catalog")}})},F=d=>{Yi(d,{get admin(){return s},get health(){return s.health}})},I=d=>{_l(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},Q=d=>{Gl(d,{get admin(){return s},get draft(){return c}})},qe=d=>{yi(d,{get draft(){return c},get health(){return s.health}})},oe=d=>{ys(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},S=d=>{$l(d,{get admin(){return s}})},Z=d=>{Fi(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},ie=d=>{mo(d,{get admin(){return s}})};m(w,d=>{s.health===null?d(N):s.page==="dashboard"?d(_,1):s.page==="troubleshooting"?d(F,2):s.page==="hardware"?d(I,3):s.page==="label"?d(Q,4):s.page==="rules"?d(qe,5):s.page==="catalog"?d(oe,6):s.page==="journal"?d(S,7):s.page==="station"?d(Z,8):s.page==="update"&&d(ie,9)})}var pe=r(xe,2);{var we=d=>{var y=To(),ee=a(y);{var f=ne=>{var Se=Eo(),Le=a(Se),de=r(Le);rt(de,16,()=>c.retired,M=>M,(M,Ie)=>{{let Me=o(()=>`retirer ${Ie}`);at(M,{kind:"write",get label(){return e(Me)},onrun:()=>c.dropRetired(Ie)})}}),p(M=>i(Le,`Ce fichier porte des réglages que cette version du poste ne connaît plus : - ${M??""}. `),[()=>c.retired.map(M=>Ft(M)).join(", ")]),l(ne,Se)};m(ee,ne=>{c.retired.length>0&&ne(f)})}var q=r(ee,2);{var L=ne=>{var Se=Po();rt(Se,21,()=>c.faults,Le=>Le.field,(Le,de)=>{var M=jo(),Ie=a(M),Me=a(Ie),Ee=r(Ie,2);{var Te=ve=>{var ze=zo(),Oe=a(ze);p(()=>i(Oe,e(de).field)),l(ve,ze)};m(Ee,ve=>{Gt.showTechnicalNames&&ve(Te)})}var Je=r(Ee),ae=r(Je);{var De=ve=>{var ze=yt();p(Oe=>i(ze,`— valeurs acceptées : ${Oe??""}`),[()=>e(de).allowed.join(", ")]),l(ve,ze)};m(ae,ve=>{e(de).allowed!==void 0&&e(de).allowed.length>0&&ve(De)})}p(ve=>{i(Me,ve),i(Je,` ${e(de).message??""} `)},[()=>Ft(e(de).field)]),l(Le,M)}),l(ne,Se)};m(q,ne=>{c.faults.length>0&&ne(L)})}var he=r(q,2);{let ne=o(()=>c.dirty?"Enregistrer la configuration":"Aucune modification à enregistrer"),Se=o(()=>!c.dirty||s.busy);at(he,{kind:"write",get label(){return e(ne)},get disabled(){return e(Se)},onrun:()=>{B()}})}l(d,y)},He=o(()=>Ca(s.page)&&c.config!==null);m(pe,d=>{e(He)&&d(we)})}var Xe=r(Y,2);{var fe=d=>{wn(d,{get admin(){return s}})};m(Xe,d=>{s.pending!==null&&d(fe)})}p(()=>{ua(re,Gt.showTechnicalNames),i(Ae,e(x))}),tt("change",re,()=>Gt.toggleTechnicalNames()),l(n,se),Ct()}At(["click","change"]);let oa=null;function No(n){if(oa!==null||n.querySelector("[data-admin]")!==null)return;const t=document.createElement("div");n.appendChild(t),oa={component:fr(Oo,{target:t,props:{onclose:Ro}}),host:t}}function Ro(){if(oa===null)return;const{component:n,host:t}=oa;oa=null,gr(n),t.remove()}export{Ro as closeAdmin,No as mountAdmin}; diff --git a/internal/web/dist/assets/mount-BCv4PVId.css b/internal/web/dist/assets/mount-BCv4PVId.css deleted file mode 100644 index dfa5431..0000000 --- a/internal/web/dist/assets/mount-BCv4PVId.css +++ /dev/null @@ -1 +0,0 @@ -.act.svelte-5tpq0o{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.read.svelte-5tpq0o{color:var(--ink);background:var(--surface);border:1px solid var(--border)}.write.svelte-5tpq0o{color:var(--surface);background:var(--action);border:1px solid var(--action)}.destructive.svelte-5tpq0o{min-height:var(--touch-min);color:var(--surface);background:var(--danger);border:1px solid var(--danger)}@media(hover:hover){.read.svelte-5tpq0o:hover:not(:disabled){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}.write.svelte-5tpq0o:hover:not(:disabled){border-color:var(--action)}.destructive.svelte-5tpq0o:hover:not(:disabled){border-color:var(--danger)}.write.svelte-5tpq0o:hover:not(:disabled),.destructive.svelte-5tpq0o:hover:not(:disabled){box-shadow:var(--shadow-2);filter:brightness(.92)}}.act.svelte-5tpq0o:disabled{opacity:.5;box-shadow:none;cursor:default}.act.busy.svelte-5tpq0o:disabled{opacity:1}.key.svelte-5tpq0o{padding:.0625rem .375rem;border-radius:var(--radius-pill);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;background:var(--bg);color:var(--ink-muted)}.write.svelte-5tpq0o .key:where(.svelte-5tpq0o),.destructive.svelte-5tpq0o .key:where(.svelte-5tpq0o){background:var(--surface);color:var(--ink)}.scrim.svelte-zyka3t{position:fixed;inset:0;z-index:95;display:flex;align-items:center;justify-content:center;padding:2rem;background:#1c1b1973}.panel.svelte-zyka3t{display:flex;flex-direction:column;gap:.75rem;width:min(34rem,100%);padding:1.75rem;background:var(--surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-2)}header.svelte-zyka3t{display:flex;align-items:center;gap:.75rem}.glyph.svelte-zyka3t{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;flex:none;border-radius:var(--radius-sm);background:var(--bg);color:var(--ink-muted)}h2.svelte-zyka3t{margin:0;font-size:1.375rem;line-height:1.2}.why.svelte-zyka3t,.how.svelte-zyka3t{margin:0;color:var(--ink-muted);font-size:1rem;line-height:1.4}.field.svelte-zyka3t{display:flex;flex-direction:column;gap:.375rem;font-size:1rem;color:var(--ink-muted)}input.svelte-zyka3t{height:2.75rem;padding:0 .75rem;font:inherit;font-size:1.125rem;color:var(--ink);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm)}input.svelte-zyka3t:focus-visible{outline:3px solid var(--focus);outline-offset:1px}.refusal.svelte-zyka3t{margin:0;padding:.625rem .75rem;border-left:.25rem solid var(--fault);background:var(--fault-wash);font-size:1rem}footer.svelte-zyka3t{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.25rem}.field.svelte-1oilv5s{display:flex;flex-direction:column;gap:.25rem;padding:.5rem 0}label.svelte-1oilv5s{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}.name.svelte-1oilv5s{font-size:1.125rem;font-weight:700}code.svelte-1oilv5s{font-size:.9375rem;color:var(--ink-muted)}input.svelte-1oilv5s{min-height:3rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.refused.svelte-1oilv5s input:where(.svelte-1oilv5s){border-left:.5rem solid var(--fault)}.hint.svelte-1oilv5s,.fault.svelte-1oilv5s{margin:0;font-size:1rem;color:var(--ink-muted)}.fault.svelte-1oilv5s{padding-left:.5rem;border-left:.25rem solid var(--fault)}.inventory.svelte-tuh4jc{display:flex;flex-direction:column;gap:.5rem}.headline.svelte-tuh4jc{margin:0;font-size:1.375rem;font-weight:700}dl.svelte-tuh4jc{margin:0;display:flex;flex-direction:column;gap:.25rem}.row.svelte-tuh4jc{display:flex;align-items:baseline;gap:.75rem}dt.svelte-tuh4jc{flex:none;width:4.5rem;text-align:right;font-size:1.375rem;font-weight:700}dd.svelte-tuh4jc{margin:0;display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem;font-size:1.125rem}.label.svelte-tuh4jc{font-weight:700}.note.svelte-tuh4jc{color:var(--ink-muted)}.rows.svelte-tuh4jc{padding:0 .5rem;min-height:var(--touch-min);text-decoration:underline;color:var(--ink-muted)}.oneline.svelte-tuh4jc{margin:0;font-size:1rem;color:var(--ink-muted)}.panel.svelte-ww3z5u{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}h2.svelte-ww3z5u{margin:0 0 .5rem;font-size:1.5rem;font-weight:700}.note.svelte-ww3z5u{margin:0 0 .75rem;font-size:1rem;color:var(--ink-muted)}.pages.svelte-3gk7u7{display:flex;flex-direction:column;gap:1rem}.fact.svelte-3gk7u7{margin:.5rem 0;font-size:1.125rem}.muted.svelte-3gk7u7{color:var(--ink-muted);font-size:1rem}.actions.svelte-3gk7u7{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.key.svelte-3gk7u7{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.scroll.svelte-3gk7u7{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--bg)}.rows.svelte-3gk7u7{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7):first-child{border-top:none}.line.svelte-3gk7u7,.id.svelte-3gk7u7,.value.svelte-3gk7u7{color:var(--ink-muted)}.line.svelte-3gk7u7{flex:none;width:7rem}.what.svelte-3gk7u7{font-weight:700}.message.svelte-3gk7u7{flex:1 1 20rem}.pick.svelte-3gk7u7{height:2.75rem;padding:0 .5rem;font-weight:700;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-3gk7u7:hover:not(:disabled){background:var(--surface)}}.search.svelte-3gk7u7,label.svelte-3gk7u7{display:block;margin-top:.5rem;font-size:1.0625rem;font-weight:700}.choice.svelte-3gk7u7{display:flex;flex-direction:column}.choice.svelte-3gk7u7 label:where(.svelte-3gk7u7){display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin:0;font-weight:400}.choice.svelte-3gk7u7 input:where(.svelte-3gk7u7){width:1.5rem;height:1.5rem;min-height:0;flex:0 0 auto;padding:0;background:none;border:none;border-radius:0}input.svelte-3gk7u7{min-height:2.75rem;width:100%;max-width:34rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.decision.svelte-3gk7u7{margin-top:.75rem;padding-top:.75rem;border-top:1px solid var(--border)}.act-block.svelte-3gk7u7{margin-top:1rem;padding:.75rem 1rem 1rem;background:var(--bg);border-radius:var(--radius)}.act-block.svelte-3gk7u7 .what:where(.svelte-3gk7u7){margin:0;font-size:1.0625rem;font-weight:700}.drop.svelte-3gk7u7{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius);transition:border-color var(--tap) var(--ease),background-color var(--tap) var(--ease)}.drop.dropping.svelte-3gk7u7{border-color:var(--focus);background:var(--waiting-wash)}.drop.working.svelte-3gk7u7{border-color:var(--waiting);background:var(--waiting-wash)}.drop.svelte-3gk7u7 p:where(.svelte-3gk7u7){margin:0;font-size:1.125rem}.choose.svelte-3gk7u7{display:inline-flex;align-items:center;margin:0;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease)}.choose.svelte-3gk7u7:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){.choose.svelte-3gk7u7:active{transform:none}}@media(hover:hover){.choose.svelte-3gk7u7:hover{filter:brightness(.92)}}.choose.svelte-3gk7u7 input:where(.svelte-3gk7u7){display:none}table.svelte-3gk7u7{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-3gk7u7,td.svelte-3gk7u7{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}.why.svelte-3gk7u7{white-space:normal;min-width:16rem;color:var(--ink-muted)}th.svelte-3gk7u7{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.light.svelte-1421wa8{display:flex;flex-direction:column;gap:.375rem;padding:.875rem 1rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--waiting);border-radius:var(--radius)}.light[data-level=ok].svelte-1421wa8{border-left-color:var(--ready)}.light[data-level=warn].svelte-1421wa8{border-left-color:var(--warning)}.light[data-level=fault].svelte-1421wa8{border-left-color:var(--fault)}header.svelte-1421wa8{display:flex;align-items:center;gap:.5rem}h3.svelte-1421wa8{margin:0;font-size:1.25rem;font-weight:700}.dot.svelte-1421wa8{width:1rem;height:1rem;border-radius:50%;background:var(--waiting);flex:none}.dot[data-level=ok].svelte-1421wa8{background:var(--ready)}.dot[data-level=warn].svelte-1421wa8{background:var(--warning)}.dot[data-level=fault].svelte-1421wa8{background:var(--fault)}.dot[data-level=unknown].svelte-1421wa8,.dot[data-level=off].svelte-1421wa8{background:transparent;box-shadow:inset 0 0 0 3px var(--waiting)}.verdict.svelte-1421wa8{margin-left:auto;font-size:1rem;font-weight:700;color:var(--ink-muted);letter-spacing:.03em}.value.svelte-1421wa8{margin:0;font-size:1.125rem}.remedy.svelte-1421wa8{margin:0;font-size:1rem;color:var(--ink-muted)}.dashboard.svelte-1w44m0y{display:flex;flex-direction:column;gap:1rem}.lights.svelte-1w44m0y{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));grid-auto-rows:1fr;gap:var(--touch-gap)}.fact.svelte-1w44m0y{margin:0 0 .5rem;font-size:1.125rem;overflow-wrap:break-word}.remedy.svelte-1w44m0y,.count.svelte-1w44m0y{color:var(--ink-muted);font-size:1rem}.restart.svelte-1w44m0y strong:where(.svelte-1w44m0y){letter-spacing:.03em}.restart[data-verdict=missing].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--warning);background:var(--warning-wash)}.restart[data-verdict=unknown].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--waiting);background:var(--waiting-wash)}.decisions.svelte-1w44m0y,.events.svelte-1w44m0y{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.375rem}.decisions.svelte-1w44m0y li:where(.svelte-1w44m0y),.events.svelte-1w44m0y li:where(.svelte-1w44m0y){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.what.svelte-1w44m0y,.message.svelte-1w44m0y{font-weight:700}.detail.svelte-1w44m0y,.time.svelte-1w44m0y,.source.svelte-1w44m0y,.code.svelte-1w44m0y{color:var(--ink-muted)}.events.svelte-1w44m0y li[data-level=error]:where(.svelte-1w44m0y){border-left:.25rem solid var(--fault);padding-left:.5rem}.events.svelte-1w44m0y li[data-level=warn]:where(.svelte-1w44m0y){border-left:.25rem solid var(--warning);padding-left:.5rem}.identity.svelte-1w44m0y{margin:0;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.125rem}.identity.svelte-1w44m0y dt:where(.svelte-1w44m0y){color:var(--ink-muted)}.identity.svelte-1w44m0y dd:where(.svelte-1w44m0y){margin:0;font-weight:700}.pages.svelte-1p4i0xm{display:flex;flex-direction:column;gap:1rem}.panel.svelte-1p4i0xm{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}.head.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;justify-content:space-between}h2.svelte-1p4i0xm{margin:0;font-size:1.5rem;font-weight:700}.standing.svelte-1p4i0xm{display:inline-flex;align-items:center;gap:.5rem;height:2rem;padding:0 .75rem;border-radius:var(--radius-pill);background:var(--waiting-wash);font-size:1rem;font-weight:700}.standing[data-level=ok].svelte-1p4i0xm{background:var(--ready-wash)}.standing[data-level=warn].svelte-1p4i0xm{background:var(--warning-wash)}.standing[data-level=fault].svelte-1p4i0xm{background:var(--fault-wash)}.dot.svelte-1p4i0xm{width:.625rem;height:.625rem;border-radius:var(--radius-pill);background:var(--waiting)}.standing[data-level=ok].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--ready)}.standing[data-level=warn].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--warning)}.standing[data-level=fault].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--fault)}.fact.svelte-1p4i0xm{margin:.75rem 0 0;font-size:1.125rem}.note.svelte-1p4i0xm,.count.svelte-1p4i0xm,.waiting.svelte-1p4i0xm{margin:.25rem 0 0;font-size:1rem;color:var(--ink-muted)}.check.svelte-1p4i0xm{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin-top:.5rem;font-size:1.0625rem}.check.svelte-1p4i0xm input:where(.svelte-1p4i0xm){width:1.5rem;height:1.5rem;flex:0 0 auto}.check.svelte-1p4i0xm small:where(.svelte-1p4i0xm){display:block;font-size:1rem;color:var(--ink-muted)}.actions.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.5rem;margin:.75rem 0 0}@media(hover:hover){.pick.svelte-1p4i0xm:hover,summary.svelte-1p4i0xm:hover{background:var(--bg)}}.list.svelte-1p4i0xm{margin:.25rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column}.list.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.125rem 0;border-top:1px solid var(--border-soft);font-size:1.0625rem}.list.svelte-1p4i0xm li.refused:where(.svelte-1p4i0xm){padding-left:.5rem;border-left:.25rem solid var(--fault);background:var(--fault-wash)}.pick.svelte-1p4i0xm{height:2.75rem;padding:0 .75rem;font-weight:700;text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}.what.svelte-1p4i0xm{font-weight:700}.detail.svelte-1p4i0xm{color:var(--ink-muted)}.folded.svelte-1p4i0xm{margin-top:1rem;border:1px solid var(--border);border-radius:var(--radius)}summary.svelte-1p4i0xm{display:flex;align-items:center;height:2.75rem;padding:0 .75rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius);cursor:pointer;transition:background-color var(--tap) var(--ease),transform var(--tap) var(--ease)}summary.svelte-1p4i0xm:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){summary.svelte-1p4i0xm:active{transform:none}}.frames.svelte-1p4i0xm{margin-top:1rem;max-height:18rem;overflow-y:auto;background:var(--waiting-wash);border:1px solid var(--border);border-radius:var(--radius)}.frames-head.svelte-1p4i0xm{position:sticky;top:0;margin:0;padding:.5rem .75rem;background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;box-shadow:var(--shadow-2);font-size:1rem;color:var(--ink-muted)}.interrupted.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;margin:0;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);font-size:1rem}.frame-rows.svelte-1p4i0xm{margin:0;padding:.5rem .75rem;list-style:none;display:flex;flex-direction:column;gap:.25rem}.frame-rows.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);gap:.75rem;font-size:.9375rem}.hex.svelte-1p4i0xm,.decoded.svelte-1p4i0xm{overflow-x:auto;white-space:pre}.decoded.svelte-1p4i0xm{color:var(--ink-muted)}div.journal.svelte-86o8oz{display:flex;flex-direction:column;gap:1rem;max-width:none;--reading-column: 68rem}.filters.svelte-86o8oz{display:flex;flex-wrap:wrap;gap:var(--touch-gap);align-items:center;margin-bottom:.75rem;font-size:1.0625rem}.filters.svelte-86o8oz label:where(.svelte-86o8oz){font-weight:700}select.svelte-86o8oz{min-height:2.75rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.export.svelte-86o8oz{display:inline-flex;align-items:center;gap:.5rem;height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.export.svelte-86o8oz:active{transform:scale(.975)}@media(hover:hover){.export.svelte-86o8oz:hover{border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.replay.svelte-86o8oz{margin-top:.5rem}.fact.svelte-86o8oz{margin:.5rem 0;max-width:var(--reading-column);font-size:1.125rem}.muted.svelte-86o8oz{color:var(--ink-muted);font-size:1rem}.table-box.svelte-86o8oz,.lines-box.svelte-86o8oz{overflow:auto;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.table-box.svelte-86o8oz{max-height:34rem}.lines-box.svelte-86o8oz{max-height:28rem}table.svelte-86o8oz{border-collapse:collapse;min-width:100%;font-size:1.0625rem}th.svelte-86o8oz,td.svelte-86o8oz{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}th.svelte-86o8oz{position:sticky;top:0;z-index:1;color:var(--ink-muted);font-size:1rem;background:var(--bg)}tbody.svelte-86o8oz tr:where(.svelte-86o8oz){transition:background-color var(--tap) var(--ease)}@media(hover:hover){tbody.svelte-86o8oz tr:where(.svelte-86o8oz):hover{background:var(--surface)}}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz){background:var(--surface);font-weight:700}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz)>td:where(.svelte-86o8oz):first-child{box-shadow:inset .1875rem 0 0 0 var(--focus)}.pick.svelte-86o8oz{height:2.75rem;padding:0 .5rem;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-86o8oz:hover:not(:disabled){background:var(--surface)}}.detail-row.svelte-86o8oz{background:var(--surface)}.detail-cell.svelte-86o8oz{padding:.75rem 1rem 1rem;white-space:normal;box-shadow:inset .1875rem 0 0 0 var(--focus)}.detail-cell.svelte-86o8oz h3:where(.svelte-86o8oz){margin:0 0 .5rem;font-size:1.25rem}.detail-cell.svelte-86o8oz dl:where(.svelte-86o8oz){display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;max-width:var(--reading-column);margin:0;font-size:1.0625rem;font-weight:400}.detail-cell.svelte-86o8oz dt:where(.svelte-86o8oz){color:var(--ink-muted)}.detail-cell.svelte-86o8oz dd:where(.svelte-86o8oz){margin:0}.detail-cell.svelte-86o8oz code:where(.svelte-86o8oz){overflow-wrap:anywhere}.lines.svelte-86o8oz{margin:0;padding:0 .75rem;list-style:none}.lines.svelte-86o8oz li:where(.svelte-86o8oz){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.lines.svelte-86o8oz li:where(.svelte-86o8oz):first-child{border-top:none}.lines.svelte-86o8oz li[data-level=error]:where(.svelte-86o8oz),.lines.svelte-86o8oz li[data-level=critical]:where(.svelte-86o8oz){border-left:.25rem solid var(--fault);padding-left:.5rem}.lines.svelte-86o8oz li[data-level=warn]:where(.svelte-86o8oz){border-left:.25rem solid var(--warning);padding-left:.5rem}.when.svelte-86o8oz,.level.svelte-86o8oz,.from.svelte-86o8oz,.code.svelte-86o8oz,.detail-text.svelte-86o8oz{color:var(--ink-muted)}.level.svelte-86o8oz{flex:none;width:8rem}.message.svelte-86o8oz{flex:1 1 20rem;font-weight:700}.sr-only.svelte-86o8oz{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.pages.svelte-llv1r8{display:flex;flex-direction:column;gap:1rem}.stale.svelte-llv1r8{margin:0 0 .75rem;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);border-radius:var(--radius-sm);font-size:1.0625rem}.preview.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:1.5rem;align-items:flex-start}.sheet.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0;padding:.75rem;background:var(--waiting-wash);border-radius:var(--radius-lg)}img.svelte-llv1r8{width:40rem;max-width:100%;image-rendering:pixelated;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-1)}.refused.svelte-llv1r8{margin:0;padding:.5rem .75rem;background:var(--fault-wash);border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);font-size:1.0625rem}.nudges.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0}.axis.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0;font-size:1.125rem}.pair.svelte-llv1r8{display:flex;gap:var(--touch-gap)}.fault.svelte-llv1r8{margin:0;padding-left:.5rem;border-left:.25rem solid var(--fault);font-size:1rem;color:var(--ink-muted)}.check.svelte-llv1r8{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;font-size:1.0625rem}.check.svelte-llv1r8 input:where(.svelte-llv1r8){width:1.5rem;height:1.5rem;flex:0 0 auto}.truncation.svelte-llv1r8,.absent.svelte-llv1r8,.waiting.svelte-llv1r8,.bound.svelte-llv1r8,.cost.svelte-llv1r8{margin:1rem 0 0;font-size:1rem;color:var(--ink-muted)}.absent.svelte-llv1r8{margin-top:.5rem}.bound.svelte-llv1r8{margin-top:.25rem}.cost.svelte-llv1r8{margin-top:.5rem}.actions.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin-top:.75rem}.pages.svelte-hzfkjn{display:flex;flex-direction:column;gap:1rem}.fact.svelte-hzfkjn{margin:.5rem 0;font-size:1.125rem}.muted.svelte-hzfkjn,.locked.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}h3.svelte-hzfkjn{margin:1.5rem 0 .5rem;font-size:1.25rem}.scroll.svelte-hzfkjn{overflow-x:auto}table.svelte-hzfkjn{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-hzfkjn,td.svelte-hzfkjn{padding:.375rem .5rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}input.svelte-hzfkjn{min-height:2.75rem;width:100%;min-width:6rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.rules.svelte-hzfkjn{margin:.75rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.75rem}.rule.svelte-hzfkjn{padding:.75rem 1rem 1rem;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.rule-head.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0}.rank.svelte-hzfkjn{flex:none;min-width:1.75rem;color:var(--ink-muted);font-variant-numeric:tabular-nums;font-weight:700}.rule-label.svelte-hzfkjn{font-size:1.125rem;font-weight:700}.token.svelte-hzfkjn{color:var(--ink-muted);font-size:.9375rem}.severity.svelte-hzfkjn{margin-left:auto;padding:.125rem .625rem;font-size:.9375rem;border-radius:var(--radius-pill);background:var(--waiting-wash)}.severity[data-blocking=true].svelte-hzfkjn{background:var(--warning-wash)}.when.svelte-hzfkjn{margin:.375rem 0 0;font-size:1rem}.quote.svelte-hzfkjn{margin:.5rem 0 0;padding:.5rem .75rem;font-size:1.0625rem;background:var(--surface);border-radius:var(--radius-sm);box-shadow:var(--shadow-1)}.quote.silent.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem;box-shadow:none;background:var(--waiting-wash)}.note.svelte-hzfkjn{margin:.5rem 0 0;font-size:1rem;color:var(--ink-muted)}.toggle.svelte-hzfkjn{display:flex;align-items:center;gap:.75rem;min-height:2.75rem;margin:.5rem 0 0;padding:.375rem .75rem;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--waiting-wash);cursor:pointer;transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease)}.toggle[data-on=true].svelte-hzfkjn{background:var(--ready-wash)}@media(hover:hover){.toggle.svelte-hzfkjn:hover{border-color:var(--ink-muted)}}.toggle.svelte-hzfkjn input:where(.svelte-hzfkjn){flex:none;width:1.5rem;height:1.5rem;min-height:0;min-width:0;padding:0;accent-color:var(--focus)}.toggle-text.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline}.toggle-label.svelte-hzfkjn{font-size:1.0625rem;font-weight:700}.hint.svelte-hzfkjn{flex:1 1 20rem;color:var(--ink-muted);font-size:1rem}.box.svelte-hzfkjn{display:contents}.verdicts.svelte-hzfkjn{margin:0;padding:0;list-style:none}.waivers.svelte-hzfkjn{max-height:24rem;overflow-y:auto}.verdicts.svelte-hzfkjn li:where(.svelte-hzfkjn){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.verdicts.svelte-hzfkjn li[data-blocking=true]:where(.svelte-hzfkjn){border-left:.25rem solid var(--warning);background:var(--warning-wash);padding-left:.5rem}.what.svelte-hzfkjn,.message.svelte-hzfkjn{font-weight:700}.detail.svelte-hzfkjn{color:var(--ink-muted)}.dead.svelte-hzfkjn{flex:1 1 20rem;padding:.125rem .625rem;font-size:1rem;background:var(--warning-wash);border-radius:var(--radius-sm)}.unread.svelte-hzfkjn{margin:.5rem 0;padding:.5rem .75rem;font-size:1rem;background:var(--fault-wash);border-left:.25rem solid var(--fault);border-radius:var(--radius-sm)}.pages.svelte-18wbxwu{display:flex;flex-direction:column;gap:1rem}.fact.svelte-18wbxwu{margin:.5rem 0;font-size:1.125rem}.muted.svelte-18wbxwu{color:var(--ink-muted);font-size:1rem}.filename.svelte-18wbxwu{margin-top:1rem;font-weight:700}.identity.svelte-18wbxwu{margin:1rem 0 0;padding:.75rem 1rem;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.0625rem;background:var(--bg);border-radius:var(--radius)}.identity.svelte-18wbxwu dt:where(.svelte-18wbxwu){color:var(--ink-muted)}.identity.svelte-18wbxwu dd:where(.svelte-18wbxwu){margin:0;font-weight:700}.actions.svelte-18wbxwu{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.choose.svelte-18wbxwu{display:inline-flex;align-items:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.choose.svelte-18wbxwu:active:not(.off){transform:scale(.975)}@media(hover:hover){.choose.svelte-18wbxwu:hover:not(.off){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.choose.off.svelte-18wbxwu{opacity:.5;box-shadow:none;cursor:default}.choose.working.svelte-18wbxwu{opacity:1;border-color:var(--waiting)}.choose.svelte-18wbxwu input:where(.svelte-18wbxwu){display:none}.key.svelte-18wbxwu{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.same.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--ready);border-radius:var(--radius-sm);background:var(--ready-wash)}.warned.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);background:var(--fault-wash)}.faults.svelte-18wbxwu{margin-top:.75rem;padding:.25rem 1rem .5rem;border-left:.375rem solid var(--warning);border-radius:var(--radius);background:var(--warning-wash)}.faults.svelte-18wbxwu ul:where(.svelte-18wbxwu){margin:0;padding-left:1.25rem;font-size:1.0625rem}.allowed.svelte-18wbxwu{display:block;color:var(--ink-muted);font-size:1rem}.scroll.svelte-18wbxwu{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-lg);background:var(--bg)}table.svelte-18wbxwu{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-18wbxwu,td.svelte-18wbxwu{padding:.375rem .75rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-18wbxwu{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.rows.svelte-18wbxwu{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.5rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu):first-child{border-top:none}.what.svelte-18wbxwu{font-weight:700}.detail.svelte-18wbxwu{color:var(--ink-muted)}.big.svelte-15nlt6i{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.write.svelte-15nlt6i{color:var(--surface);background:var(--action);border-color:var(--action)}.destructive.svelte-15nlt6i{color:var(--surface);background:var(--danger);border-color:var(--danger)}.write.svelte-15nlt6i .hint:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .hint:where(.svelte-15nlt6i){color:var(--surface);opacity:.85}.write.svelte-15nlt6i .guarded:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .guarded:where(.svelte-15nlt6i){background:var(--surface);color:var(--ink)}.big.svelte-15nlt6i:disabled{opacity:.5}.big.busy.svelte-15nlt6i{opacity:1;border-color:var(--waiting);background:var(--waiting-wash)}.big.busy.write.svelte-15nlt6i{background:var(--action)}.big.busy.destructive.svelte-15nlt6i{background:var(--danger)}.guarded.svelte-15nlt6i{margin-left:.5rem;padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;vertical-align:middle}.big.engaged.svelte-15nlt6i{border-left:.5rem solid var(--warning)}.label.svelte-15nlt6i{font-size:1.375rem;font-weight:700}.hint.svelte-15nlt6i{font-size:1rem;color:var(--ink-muted)}.troubleshooting.svelte-1yd0nzg{display:flex;flex-direction:column;gap:1rem}.buttons.svelte-1yd0nzg{display:grid;grid-template-columns:repeat(auto-fit,minmax(22rem,1fr));gap:var(--touch-gap)}.big.svelte-1yd0nzg{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.label.svelte-1yd0nzg{font-size:1.375rem;font-weight:700}.hint.svelte-1yd0nzg{font-size:1rem;color:var(--ink-muted)}.report.svelte-1yd0nzg{margin:0;padding:.75rem 1rem;font-size:1.125rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--ready);border-radius:var(--radius)}.drop.svelte-1yd0nzg{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius)}.drop.dropping.svelte-1yd0nzg{border-color:var(--focus)}.drop.svelte-1yd0nzg p:where(.svelte-1yd0nzg){margin:0;font-size:1.125rem}.key.svelte-1yd0nzg{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.choose.svelte-1yd0nzg{display:inline-flex;align-items:center;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius);cursor:pointer}@media(hover:hover){.choose.svelte-1yd0nzg:hover{filter:brightness(.92)}}.choose.svelte-1yd0nzg input:where(.svelte-1yd0nzg){display:none}.fact.svelte-1yd0nzg{margin:0 0 .5rem;font-size:1.125rem}.muted.svelte-1yd0nzg{color:var(--ink-muted)}.row.svelte-o9s8nz{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:.75rem}.admin.svelte-9b2mjq{position:fixed;inset:0;z-index:90;display:grid;grid-template-columns:16rem 1fr;background:var(--bg);color:var(--ink);overflow:hidden}.rail.svelte-9b2mjq{display:flex;flex-direction:column;gap:.125rem;padding:1rem .75rem;overflow-y:auto;background:var(--surface);border-right:1px solid var(--border)}h1.svelte-9b2mjq{margin:0 .5rem 1rem;font-size:1.25rem;letter-spacing:-.01em}.group.svelte-9b2mjq{margin:1rem .5rem .375rem;color:var(--ink-muted);font-size:.8125rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.entry.svelte-9b2mjq{padding:0 .75rem;height:2.5rem;display:flex;align-items:center;text-align:left;font-size:1.0625rem;border-radius:var(--radius-sm);color:var(--ink-muted);transition:background-color var(--tap) var(--ease),color var(--tap) var(--ease)}@media(hover:hover){.entry.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.entry.current.svelte-9b2mjq{background:var(--bg);color:var(--ink);font-weight:700;box-shadow:inset .1875rem 0 0 0 var(--focus)}.foot.svelte-9b2mjq{margin-top:auto;padding:1rem .5rem 0;border-top:1px solid var(--border)}.station.svelte-9b2mjq{margin:0;font-size:1rem;font-weight:600}.build.svelte-9b2mjq{margin:.125rem 0 0;font-size:.8125rem;color:var(--ink-muted)}.technical.svelte-9b2mjq{display:flex;align-items:center;gap:.5rem;min-height:2.75rem;margin-top:.75rem;color:var(--ink-muted);font-size:.9375rem}.technical.svelte-9b2mjq input:where(.svelte-9b2mjq){width:1.5rem;height:1.5rem;flex:0 0 auto}.back.svelte-9b2mjq{margin-top:.75rem;padding:0 .5rem;height:2.25rem;font-size:.9375rem;color:var(--ink-muted);border-radius:var(--radius-sm)}@media(hover:hover){.back.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.body.svelte-9b2mjq{display:flex;flex-direction:column;min-width:0;overflow:hidden}.page.svelte-9b2mjq{flex:1;min-height:0;overflow-y:auto;padding:1.5rem 2rem 3rem}.page.svelte-9b2mjq>*{max-width:68rem}.page-title.svelte-9b2mjq{margin:0 0 1.25rem;font-size:1.75rem;letter-spacing:-.01em}.waiting.svelte-9b2mjq{margin:0;font-size:1.125rem;color:var(--ink-muted)}.banner.svelte-9b2mjq{flex:none;margin:0;padding:.75rem 2rem;font-size:1.0625rem;background:var(--surface);border-bottom:1px solid var(--border)}.notice.svelte-9b2mjq{border-left:.375rem solid var(--ready);background:var(--ready-wash)}.failure.svelte-9b2mjq{border-left:.375rem solid var(--fault);background:var(--fault-wash)}.pending.svelte-9b2mjq{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;border-left:.375rem solid var(--warning);background:var(--warning-wash)}.pending.svelte-9b2mjq p:where(.svelte-9b2mjq){margin:0;flex:1 1 24rem}.save-bar.svelte-9b2mjq{flex:none;padding:.75rem 2rem;background:var(--surface);border-top:1px solid var(--border);box-shadow:var(--shadow-1)}.retired.svelte-9b2mjq{margin:0 0 .5rem;font-size:.9375rem;color:var(--ink-muted)}.faults.svelte-9b2mjq{margin:0 0 .5rem;padding-left:1.25rem;font-size:1rem} diff --git a/internal/web/dist/assets/mount-BdIa8cb_.js b/internal/web/dist/assets/mount-BdIa8cb_.js new file mode 100644 index 0000000..4fcc6eb --- /dev/null +++ b/internal/web/dist/assets/mount-BdIa8cb_.js @@ -0,0 +1,93 @@ +import{R as ar,T as rr,U as nr,V as xa,W as Yt,e as sr,X as lr,Y as ir,Z as or,_ as ur,u as pa,r as Fa,$ as cr,c as Ma,B as aa,A as F,k as e,z as v,F as Lt,d as Tt,b as vt,s as r,i as m,t as p,n as Ht,l as Be,o as i,f as et,g as l,v as a,j as o,p as Ct,I as dr,h as Ot,q as u,w as H,a0 as Ea,E as na,x as rt,G as Ba,a1 as ua,y as Qt,a2 as Dt,a3 as yt,a4 as sa,C as fa,K as vr,H as pr,P as fr,a5 as gr}from"./app-Dn-BjSsJ.js";function Va(n){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function mr(n,t,...s){var c=new nr(n);ar(()=>{const _=t()??null;c.ensure(_,_&&(w=>_(w,...s)))},rr)}function Wa(n,t,s=!1){if(n.multiple){if(t==null)return;if(!lr(t))return ir();for(var c of n.options)c.selected=t.includes(la(c));return}for(c of n.options){var _=la(c);if(or(_,t)){c.selected=!0;return}}(!s||t!==void 0)&&(n.selectedIndex=-1)}function hr(n){var t=new MutationObserver(()=>{"__value"in n&&Wa(n,n.__value)});t.observe(n,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),ur(()=>{t.disconnect()})}function _r(n,t,s=t){var c=new WeakSet,_=!0;xa(n,"change",w=>{var C=w?"[selected]":":checked",R;if(n.multiple)R=[].map.call(n.querySelectorAll(C),la);else{var A=n.querySelector(C)??n.querySelector("option:not([disabled])");R=A&&la(A)}s(R),n.__value=R,Yt!==null&&c.add(Yt)}),sr(()=>{var w=t();if(n===document.activeElement){var C=Yt;if(c.has(C))return}if(Wa(n,w,_),_&&w===void 0){var R=n.querySelector(":checked");R!==null&&(w=la(R),s(w))}n.__value=w,_=!1}),hr(n)}function la(n){return"__value"in n?n.__value:n.value}function ia(n,t,s=t){var c=new WeakSet;xa(n,"input",async _=>{var w=_?n.defaultValue:n.value;if(w=ma(n)?ha(w):w,s(w),Yt!==null&&c.add(Yt),await cr(),w!==(w=t())){var C=n.selectionStart,R=n.selectionEnd,A=n.value.length;if(n.value=w??"",R!==null){var W=n.value.length;C===R&&R===A&&W>A?(n.selectionStart=W,n.selectionEnd=W):(n.selectionStart=C,n.selectionEnd=Math.min(R,W))}}}),pa(t)==null&&n.value&&(s(ma(n)?ha(n.value):n.value),Yt!==null&&c.add(Yt)),Fa(()=>{var _=t();if(n===document.activeElement){var w=Yt;if(c.has(w))return}ma(n)&&_===ha(n.value)||n.type==="date"&&!_&&!n.value||_!==n.value&&(n.value=_??"")})}function za(n,t,s=t){xa(n,"change",c=>{var _=c?n.defaultChecked:n.checked;s(_)}),pa(t)==null&&s(n.checked),Fa(()=>{var c=t();n.checked=!!c})}function ma(n){var t=n.type;return t==="number"||t==="range"}function ha(n){return n===""?null:+n}function ka(n){Ma===null&&Va(),aa(()=>{const t=pa(n);if(typeof t=="function")return t})}function br(n){Ma===null&&Va(),ka(()=>()=>pa(n))}class It extends Error{constructor(t,s,c="",_=[]){super(s),this.status=t,this.code=c,this.faults=_,this.name="AdminError"}status;code;faults;get needsPassword(){return this.status===401}}function wr(){return Jt("/admin/api/health")}function yr(){return ht("/admin/api/troubleshooting/reprint",{})}function xr(){return ht("/admin/api/troubleshooting/reload-catalog",{})}function kr(n){return ht("/admin/api/troubleshooting/manual-entry",{on:n})}function qr(){return ht("/admin/api/troubleshooting/roll-changed",{})}function Sr(n){return ht("/admin/api/troubleshooting/fallback-printer",{on:n})}function Lr(){return ht("/admin/api/troubleshooting/test-scale",{})}function Er(){return ht("/admin/api/troubleshooting/test-printer",{})}function zr(){return ht("/admin/api/troubleshooting/test-label",{})}async function Ga(n){const t=new FormData;t.append("file",n,n.name);const s=await fetch("/admin/api/catalog/import",{method:"POST",body:t});return Sa(s,"POST /admin/api/catalog/import")}const jr="/admin/api/diagnostic.zip";function Pr(n){return ht("/admin/api/session",{password:n})}async function Tr(){await fetch("/admin/api/session",{method:"DELETE"})}function Cr(n,t){return ht("/admin/api/session/recovery",{code:n,password:t})}function qa(){return Jt("/admin/api/config")}function Or(n){return Ja("PUT","/admin/api/config",n)}function Rr(){return ht("/admin/api/config/confirm",{})}async function Ar(){return(await Jt("/admin/api/config/versions")).versions}function Nr(n){return ht("/admin/api/config/restore",{version:n})}async function $r(){return(await Jt("/admin/api/ports")).ports}async function Dr(){return(await Jt("/admin/api/printers")).printers}async function Ir(){return(await ht("/admin/api/printers/discover",{})).printers}function Ur(n){return ht("/admin/api/scale/detect",{port:n})}async function ja(n,t){return(await ht("/admin/api/scale/capture",{port:n,seconds:t})).frames}function Ha(n){return ht("/admin/api/printer/test?what="+n,{})}function Fr(n,t,s,c){const _=new URLSearchParams({template:n,demo:t?"1":"0",dual:s?"1":"0"});return _.set("t",String(c)),"/admin/api/label/preview.png?"+_.toString()}async function Mr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/journal?"+t)).weighings}function Br(n){return"/admin/api/journal/export.csv?"+new URLSearchParams(n).toString()}async function Vr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/technical?"+t)).entries}function Wr(n){const t=n===void 0?"":"?id="+String(n);return Jt("/admin/api/imports"+t)}function Gr(){return ht("/admin/api/catalog/reload",{})}function Hr(){return ht("/admin/api/catalog/forget-quarantine",{})}function Pa(n,t){return ht("/admin/api/products/"+encodeURIComponent(n)+"/decision",t)}function Jr(n){return ht("/admin/api/replay",{frame:n})}async function Jt(n){const t=await fetch(n,{headers:{accept:"application/json"}});return Sa(t,"GET "+n)}function ht(n,t){return Ja("POST",n,t)}async function Ja(n,t,s){const c=await fetch(t,{method:n,headers:{"content-type":"application/json"},body:JSON.stringify(s)});return Sa(c,n+" "+t)}async function Sa(n,t){const s=await n.text();if(!n.ok){const c=Ka(s);throw new It(n.status,Xr(s,t,n.status)+Kr(n),c?.code??"",c?.faults??[])}if(s!=="")return JSON.parse(s)}function Kr(n){if(n.status!==429)return"";const t=Number(n.headers.get("Retry-After")??"");if(!Number.isFinite(t)||t<=0)return"";const s=Math.ceil(t/60);return s>1?` Réessayez dans ${String(s)} minutes.`:" Réessayez dans une minute."}function Xr(n,t,s){const c=Ka(n);return c?.message!==void 0&&c.message!==""?c.message:`${t} a répondu ${String(s)}.`}function Ka(n){try{return JSON.parse(n)}catch{return null}}function Yr(){return Jt("/admin/api/update")}function Qr(){return ht("/admin/api/update/check",{})}function Zr(n){return ht("/admin/api/update/apply",{version:n})}class en{constructor(t){this.admin=t}admin;#e=F(null);get config(){return e(this.#e)}set config(t){v(this.#e,t,!0)}#a=F("");get fingerprint(){return e(this.#a)}set fingerprint(t){v(this.#a,t,!0)}#r=F(Lt([]));get retired(){return e(this.#r)}set retired(t){v(this.#r,t,!0)}#n=F(null);get pending(){return e(this.#n)}set pending(t){v(this.#n,t,!0)}#s=F(Lt([]));get faults(){return e(this.#s)}set faults(t){v(this.#s,t,!0)}#l=F(!1);get dirty(){return e(this.#l)}set dirty(t){v(this.#l,t,!0)}async load(){const t=await this.admin.load(()=>qa());t!==null&&(this.config=t.config,this.fingerprint=t.config_fingerprint,this.retired=t.retired_keys??[],this.pending=t.pending_confirmation,this.faults=[],this.dirty=!1)}value(t){let s=this.config;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}text(t){const s=this.value(t);return s==null?"":String(s)}number(t){const s=this.value(t);return typeof s=="number"?s:Number(s??0)}flag(t){return this.value(t)===!0}set(t,s){if(this.config===null)return;const c=t.split("."),_=c.pop();if(_===void 0)return;let w=this.config;for(const C of c){const R=w[C];(R===null||typeof R!="object")&&(w[C]={}),w=w[C]}w[_]=s,this.dirty=!0}unset(t){const s=t.split("."),c=s.pop();if(c===void 0||this.config===null)return!1;let _=this.config;for(const w of s){if(_===null||typeof _!="object")return!1;_=_[w]}return _===null||typeof _!="object"?!1:(delete _[c],this.dirty=!0,!0)}async save(){if(this.config===null)return!1;this.faults=[];const t=this.config;let s;try{s=await Or(t)}catch(c){if(Ta(c))throw c;return this.admin.report(c),this.faults=tn(this.admin),!1}return this.config=s.config,this.fingerprint=s.config_fingerprint,this.retired=s.retired_keys??[],this.pending=s.pending_confirmation,this.dirty=!1,!0}async confirm(){try{await Rr()}catch(t){if(Ta(t))throw t;this.admin.report(t);return}this.admin.notice="La configuration est confirmée.",this.pending=null,await this.admin.refresh()}dropRetired(t){if(t.includes("[")){this.admin.actionError=`« ${t} » est logée dans un tableau : cet écran ne sait pas la retirer de là. Modifiez le fichier de configuration lui-même.`;return}this.unset(t)&&(this.retired=this.retired.filter(s=>s!==t))}}function tn(n){return n.lastFaults}function Ta(n){return n instanceof It&&(n.status===401||n.status===409)}const an={"station.number":"Numéro du poste","station.name":"Nom du poste","station.coop":"Nom de la coopérative","network.listen":"Adresse d’écoute","network.admin_on_lan":"Administration accessible depuis le réseau","ui.language":"Langue","ui.sound":"Son","ui.idle_timeout_s":"Retour à l’accueil après (secondes)","ui.reprint_window_s":"Réimpression possible pendant (secondes)","ui.show_grid_prices":"Afficher les prix sur les tuiles","scale.type":"Protocole de la balance","scale.present":"Ce poste a une balance","scale.manual_entry_allowed":"Saisie manuelle autorisée","scale.degrade_after_s":"Passer en mode dégradé après (secondes)","scale.options.port":"Port série","scale.options.baud":"Vitesse (bauds)","scale.options.bits":"Bits de données","scale.options.parity":"Parité","scale.options.stop":"Bits d’arrêt","scale.options.backoff_min_ms":"Attente minimale avant réessai (ms)","scale.options.backoff_max_ms":"Attente maximale avant réessai (ms)","printer.type":"Pilote d’impression","printer.template":"Gabarit d’étiquette","printer.options.transport":"Transport","printer.options.queue":"File d’impression","printer.options.path":"Fichier de périphérique","printer.options.address":"Adresse réseau","printer.options.darkness":"Noircissement","printer.options.speed":"Vitesse d’impression","printer.options.offset_x":"Décalage horizontal (dots)","printer.options.offset_y":"Décalage vertical (dots)","printer.options.invert_bits":"Inverser les points","printer.options.copies":"Exemplaires","printer.options.roll_capacity":"Étiquettes par rouleau","pricing.amount_rounding":"Arrondi du montant","pricing.unit_price_rounding":"Arrondi du prix au kilo","pricing.primary_code":"Tarif principal","pricing.reference_code":"Tarif de référence","barcode.verify_reference_check_digit":"Vérifier la clé de contrôle","limits.empty_max_g":"Plateau considéré vide en dessous de (g)","limits.basket_check_enabled":"Vérifier la présence du panier","limits.basket_min_g":"Poids du panier, borne basse (g)","limits.basket_max_g":"Poids du panier, borne haute (g)","limits.min_weight_g":"Poids minimum accepté","limits.max_weight_g":"Poids maximum accepté","limits.max_tare_g":"Tare maximale","limits.min_units":"Unités minimum","limits.max_units":"Unités maximum","limits.max_amount_cents":"Montant maximum (centimes)","stability.mode":"Exigence de stabilité","stability.min_duration_ms":"Durée de stabilité (ms)","stability.tolerance_g":"Tolérance de stabilité (g)","stability.timeout_ms":"Délai d’attente de stabilité (ms)","stability.on_timeout":"Au bout du délai","stability.min_latch_rate":"Taux d’accroche minimal","stability.latch_rate_window_ms":"Fenêtre de mesure du taux (ms)","stability.expiry_floor_ms":"Péremption, plancher (ms)","stability.expiry_ceiling_ms":"Péremption, plafond (ms)","stability.expiry_factor":"Péremption, facteur","catalog.type":"Où le poste va chercher le catalogue","catalog.options.directory":"Répertoire surveillé","catalog.options.url":"Adresse du serveur","catalog.options.username":"Compte","catalog.options.password":"Mot de passe","catalog.options.separator":"Séparateur du CSV","catalog.options.poll_interval_s":"Vérifier toutes les (secondes)","catalog.options.stable_polls":"Vérifications avant lecture","catalog.options.max_file_size_mb":"Taille maximale du fichier (Mo)","catalog.options.max_image_size_kb":"Taille maximale d’une image (Ko)","catalog.options.min_readable_ratio":"Part minimale de lignes lisibles","catalog.options.max_weighable_drop":"Baisse maximale des produits pesables","catalog.options.max_archives":"Archives conservées","catalog.options.archive_days":"Archives conservées (jours)","catalog.options.failures_before_reject":"Échecs avant mise en quarantaine","catalog.images.source":"Origine des photos","catalog.images.path":"Répertoire des photos","catalog.fallback_category":"Rayon par défaut","journal.max_rows":"Pesées conservées","journal.max_days":"Pesées conservées (jours)","journal.max_technical":"Événements techniques conservés","admin.session_minutes":"Durée d’une session (minutes)","admin.attempts_per_minute":"Tentatives par minute","maintenance.weekly_integrity_check":"Contrôle d’intégrité hebdomadaire","maintenance.disk_alert_mb":"Alerte disque en dessous de (Mo)"};function Ft(n){return an[n]??n}const rn={station:"l’identité du poste",network:"le réseau",ui:"l’écran client",scale:"la balance",printer:"l’imprimante",pricing:"les tarifs",barcode:"le code-barres",limits:"les garde-fous",stability:"la stabilité",catalog:"le catalogue",journal:"le journal",maintenance:"la maintenance"};function nn(n){return rn[n]??n}const sn={scale:"balance",printer:"imprimante",catalog:"catalogue",ui:"écran",config:"configuration",http:"réseau",system:"système"};function Xa(n){return sn[n]??"origine inconnue"}const ba="openscale.admin.preferences",Ya="technical";class ln{#e=F(Lt(on()));get showTechnicalNames(){return e(this.#e)}set showTechnicalNames(t){v(this.#e,t,!0)}toggleTechnicalNames(){this.showTechnicalNames=!this.showTechnicalNames,un(this.showTechnicalNames)}}function on(){try{return globalThis.localStorage?.getItem(ba)===Ya}catch{return!1}}function un(n){try{n?globalThis.localStorage?.setItem(ba,Ya):globalThis.localStorage?.removeItem(ba)}catch{}}const Gt=new ln,cn=3e3,dn=["hardware","label","rules","catalog","journal","station","update"];function Ca(n){return dn.includes(n)}function vn(n){return n instanceof It&&(n.status===401||n.status===409)}class pn{constructor(t=cn){this.periodMs=t}periodMs;#e=F(null);get health(){return e(this.#e)}set health(t){v(this.#e,t,!0)}#a=F("dashboard");get page(){return e(this.#a)}set page(t){v(this.#a,t,!0)}#r=F(!1);get expert(){return e(this.#r)}set expert(t){v(this.#r,t,!0)}#n=F("");get linkError(){return e(this.#n)}set linkError(t){v(this.#n,t,!0)}#s=F("");get actionError(){return e(this.#s)}set actionError(t){v(this.#s,t,!0)}#l=F(!1);get needsFirstPassword(){return e(this.#l)}set needsFirstPassword(t){v(this.#l,t,!0)}#d=F("");get notice(){return e(this.#d)}set notice(t){v(this.#d,t,!0)}#v=F(!1);get busy(){return e(this.#v)}set busy(t){v(this.#v,t,!0)}#p=F(Lt([]));get lastFaults(){return e(this.#p)}set lastFaults(t){v(this.#p,t,!0)}#f=F(null);get pending(){return e(this.#f)}set pending(t){v(this.#f,t,!0)}#i=null;#o=null;start(){this.refresh(),this.#i=setInterval(()=>{this.refresh()},this.periodMs)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null}async refresh(){try{this.health=await wr(),this.linkError=""}catch(t){this.linkError=Oa(t)}}#u(){this.busy=!0,this.notice="",this.actionError="",this.needsFirstPassword=!1}#t(t){this.actionError=Oa(t),this.needsFirstPassword=t instanceof It&&t.status===409,this.#m(t)}async run(t){this.#u();try{const s=await t();this.notice=s.message}catch(s){this.#t(s)}finally{this.busy=!1}await this.refresh()}async login(t){this.#u();try{return await Pr(t),this.expert=!0,this.notice="Session d’administration ouverte.",!0}catch(s){return this.expert=!1,this.#t(s),!1}finally{this.busy=!1}}async recover(t,s){this.#u();try{const c=await Cr(t,s);return this.expert=!0,this.notice=c.warning??"Le mot de passe est remplacé et la session est ouverte.",!0}catch(c){return this.#t(c),!1}finally{this.busy=!1}}async protect(t){try{return await t()}catch(s){if(!vn(s))return this.#t(s),null;if(!await this.#g(s))return null;try{return await t()}catch(c){return this.#t(c),null}}}#g(t){return this.pending={kind:t.status===409?"first-password":"password",message:t.message},new Promise(s=>{this.#o=s})}async answerPassword(t){await this.login(t)&&this.#c(!0)}async answerRecovery(t,s){await this.recover(t,s)&&this.#c(!0)}cancelPassword(){this.#c(!1)}#c(t){this.pending=null,this.notice="";const s=this.#o;this.#o=null,s?.(t)}async logout(){await Tr(),this.expert=!1,this.page="dashboard",this.notice="Session d’administration fermée."}open(t){this.actionError="",this.notice="",this.needsFirstPassword=!1,this.page=t}async load(t){try{const s=await t();return this.actionError="",this.lastFaults=[],s}catch(s){return this.lastFaults=s instanceof It?s.faults:[],this.#t(s),null}}report(t){this.lastFaults=t instanceof It?t.faults:[],this.#t(t)}#m(t){t instanceof It&&t.needsPassword&&(this.expert=!1)}}function Oa(n){return n instanceof It?n.message:n instanceof Error?"Le poste n’a pas répondu : "+n.message:"Le poste n’a pas répondu."}var fn=o('clé'),gn=o('');function at(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"busy",3,!1),_=vt(t,"disabled",3,!1),w=vt(t,"protected",3,!1),C=vt(t,"act",3,void 0);var R=gn();let A;var W=a(R),G=r(W);{var le=we=>{var ke=fn();l(we,ke)};m(G,we=>{w()&&we(le)})}p(()=>{A=Ht(R,1,`act ${s()??""}`,"svelte-5tpq0o",A,{"touch-target":s()==="destructive",busy:c()}),Be(R,"data-kind",s()),Be(R,"data-act",C()),R.disabled=_()||c(),i(W,`${(c()?"En cours…":t.label)??""} `)}),et("click",R,function(...we){t.onrun?.apply(this,we)}),l(n,R)}Tt(["click"]);var mn=o(`

                                                                                                    Le code de secours a été tiré à l’installation de ce poste et imprimé + sur sa fiche, rangée dans le classeur du magasin. Il n’existe nulle part ailleurs.

                                                                                                    `,1),hn=o(`

                                                                                                    Oublié ? Le code de secours de la fiche d’installation en repose un. + Un responsable peut aussi le faire en ligne de commande, avec openscale config password.

                                                                                                    `,1),_n=o('

                                                                                                    '),bn=o('');function wn(n,t){Ct(t,!0);const s=u(()=>t.admin.pending?.kind==="first-password");let c=F(""),_=F("");const w=8,C=u(()=>e(s)?e(_).trim().length>0&&e(c).length>=w:e(c).length>0);async function R(){!e(C)||t.admin.busy||(e(s)?await t.admin.answerRecovery(e(_),e(c)):await t.admin.answerPassword(e(c)),v(c,""),v(_,""))}var A=bn(),W=a(A),G=a(W),le=a(G),we=a(le);dr(we,{name:"settings",size:"1.5rem"});var ke=r(le,2),ue=a(ke),j=r(G,2),de=a(j),J=r(j,2);{var ye=S=>{var y=mn(),P=r(H(y),2),I=r(a(P),2);Ea(I);var Q=r(P,2),pe=a(Q);pe.textContent="Nouveau mot de passe — 8 caractères au moins";var De=r(pe,2);et("keydown",I,Oe=>Oe.key==="Enter"&&void R()),ia(I,()=>e(_),Oe=>v(_,Oe)),et("keydown",De,Oe=>Oe.key==="Enter"&&void R()),ia(De,()=>e(c),Oe=>v(c,Oe)),l(S,y)},B=S=>{var y=hn(),P=H(y),I=r(a(P),2);Ea(I),et("keydown",I,Q=>Q.key==="Enter"&&void R()),ia(I,()=>e(c),Q=>v(c,Q)),l(S,y)};m(J,S=>{e(s)?S(ye):S(B,-1)})}var $=r(J,2);{var Z=S=>{var y=_n(),P=a(y);p(()=>i(P,t.admin.actionError)),l(S,y)};m($,S=>{t.admin.actionError!==""&&S(Z)})}var D=r($,2),z=a(D);at(z,{label:"Annuler",onrun:()=>t.admin.cancelPassword()});var re=r(z,2);{let S=u(()=>e(s)?"Poser ce mot de passe":"Continuer"),y=u(()=>!e(C)||t.admin.busy);at(re,{kind:"write",get label(){return e(S)},get disabled(){return e(y)},onrun:()=>{R()}})}p(()=>{i(ue,e(s)?"Ce poste n’a pas encore de mot de passe":"Mot de passe d’administration"),i(de,t.admin.pending?.message??"")}),l(n,A),Ot()}Tt(["keydown"]);var yn=o(' '),xn=o(""),kn=o(""),qn=o('

                                                                                                    '),Sn=o(" "),Ln=o('

                                                                                                    '),En=o('
                                                                                                    ');function _t(n,t){Ct(t,!0);const s=vt(t,"hint",3,""),c=vt(t,"kind",3,"text"),_=vt(t,"disabled",3,!1),w=vt(t,"fault",3,""),C=vt(t,"allowed",19,()=>[]),R=u(()=>`field-${t.path.replace(/\./gu,"-")}`);var A=En();let W;var G=a(A),le=a(G),we=a(le),ke=r(le,2);{var ue=D=>{var z=yn(),re=a(z);p(()=>i(re,t.path)),l(D,z)};m(ke,D=>{Gt.showTechnicalNames&&D(ue)})}var j=r(G,2),de=r(j,2);{var J=D=>{var z=kn();rt(z,20,C,re=>re,(re,S)=>{var y=xn(),P={};p(()=>{P!==(P=S)&&(y.value=(y.__value=S)??"")}),l(re,y)}),p(()=>Be(z,"id",e(R)+"-allowed")),l(D,z)};m(de,D=>{C().length>0&&D(J)})}var ye=r(de,2);{var B=D=>{var z=qn(),re=a(z);p(()=>i(re,s())),l(D,z)};m(ye,D=>{s()!==""&&D(B)})}var $=r(ye,2);{var Z=D=>{var z=Ln(),re=a(z),S=r(re);{var y=P=>{var I=Sn(),Q=a(I);p(pe=>i(Q,`Valeurs acceptées : ${pe??""}.`),[()=>C().join(", ")]),l(P,I)};m(S,P=>{C().length>0&&P(y)})}p(()=>i(re,`${w()??""} `)),l(D,z)};m($,D=>{w()!==""&&D(Z)})}p(()=>{W=Ht(A,1,"field svelte-1oilv5s",null,W,{refused:w()!==""}),Be(G,"for",e(R)),i(we,t.label),Be(j,"id",e(R)),Be(j,"type",c()),na(j,t.value),j.disabled=_(),Be(j,"list",C().length>0?e(R)+"-allowed":void 0)}),et("input",j,D=>t.onchange(D.currentTarget.value)),l(n,A),Ot()}Tt(["input"]);const Ra=1e3;function da(n){const t=La(n);return t===null?"":[ta(t.getDate()),ta(t.getMonth()+1),String(t.getFullYear())].join("/")}function Mt(n){const t=La(n);return t===null?"":`${da(n)} à ${ta(t.getHours())}:${ta(t.getMinutes())}`}function zn(n){const t=La(n);return t===null?"":[ta(t.getHours()),ta(t.getMinutes()),ta(t.getSeconds())].join(":")}function wa(n){const t=n/1e6;return t<1e3?`${Math.round(t)} Mo`:`${Qa(t/1e3)} Go`}function he(n){const t=String(Math.abs(Math.trunc(n))),s=[];for(let c=t.length;c>0;c-=3)s.unshift(t.slice(Math.max(0,c-3),c));return(n<0?"-":"")+s.join(" ")}function ca(n){return n0&&w.push({count:Pt(n.anomalies_count),label:"anomalies",note:"à corriger dans Odoo",link:Aa(n.anomalies_count)}),n.unit_mismatches_count>0){const C=n.unit_mismatches_count;w.push({count:"+ "+Pt(C),label:C===1?"unité divergente":"unités divergentes",note:"pesable, unité à corriger",link:Aa(C)})}return{headline:`Catalogue du ${da(n.occurred_at)} — ${Pt(s)} produits reçus`,lines:w,oneLine:Pn(n)}}function Pn(n){const t=[`${Pt(n.rows_read_count)} reçus`,`${Pt(n.weighable_count)} pesables`,`${Pt(n.not_weighable_count)} non pesables`,`${Pt(n.anomalies_count)} anomalies`],s=n.unit_mismatches_count;return s>0&&t.push(`${Pt(s)} ${s===1?"unité divergente":"unités divergentes"}`),t.join(" · ")}function Aa(n){return n===1?"voir la ligne":`voir les ${Pt(n)} lignes`}function Tn(n){return n.map(t=>`${Cn(t)} (${Pt(t.count)})`).join(", ")}function Cn(n){switch(n.code){case"PREPACKAGED_PRODUCT":return"préemballés";case"INTERNAL_CODE_NOT_WEIGHABLE":return n.value===""?"code interne":"code interne "+n.value;case"NO_BARCODE":return"sans code-barres";default:return n.code}}function Pt(n){return String(n)}var On=o(' '),Rn=o(''),An=o('
                                                                                                    '),Nn=o('

                                                                                                    ');function Za(n,t){Ct(t,!0);const s=u(()=>jn(t.record,t.motives));var c=Nn(),_=a(c),w=a(_),C=r(_,2);rt(C,21,()=>e(s).lines,W=>W.label,(W,G)=>{var le=An(),we=a(le),ke=a(we),ue=r(we,2),j=a(ue),de=a(j),J=r(j,2);{var ye=Z=>{var D=On(),z=a(D);p(()=>i(z,e(G).note)),l(Z,D)};m(J,Z=>{e(G).note!==""&&Z(ye)})}var B=r(J,2);{var $=Z=>{var D=Rn(),z=a(D);p(()=>i(z,e(G).link)),et("click",D,()=>t.onshowrows(e(G).label==="anomalies"?"anomalies":"units")),l(Z,D)};m(B,Z=>{e(G).link!==""&&t.onshowrows!==void 0&&Z($)})}p(()=>{Be(le,"data-row",e(G).label),i(ke,e(G).count),i(de,e(G).label)}),l(W,le)});var R=r(C,2),A=a(R);p(()=>{i(w,e(s).headline),i(A,e(s).oneLine)}),l(n,c),Ot()}Tt(["click"]);var $n=o('

                                                                                                    '),Dn=o('

                                                                                                    ');function Qe(n,t){const s=vt(t,"note",3,"");var c=Dn(),_=a(c),w=a(_),C=r(_,2);{var R=W=>{var G=$n(),le=a(G);p(()=>i(le,s())),l(W,G)};m(C,W=>{s()!==""&&W(R)})}var A=r(C,2);mr(A,()=>t.children),p(()=>i(w,t.title)),l(n,c)}const _a=(n,t=sa,s=sa)=>{var c=Un(),_=a(c);rt(_,21,t,fa,(w,C)=>{var R=In(),A=a(R),W=a(A),G=r(A,2),le=a(G),we=r(G,2),ke=a(we),ue=r(we,2),j=a(ue);p(de=>{i(W,`ligne ${de??""}`),i(le,e(C).product_id),i(ke,e(C).value),i(j,e(C).message)},[()=>he(e(C).csv_line)]),l(w,R)}),p(()=>Be(c,"data-rows",s())),l(n,c)};var In=o('
                                                                                                  • '),Un=o('
                                                                                                      '),Fn=o('

                                                                                                      Lecture des réglages du poste…

                                                                                                      '),Mn=o(`

                                                                                                      Le poste y cherche le fichier , et le supprime + une fois lu : c’est ce qui dit au producteur que la livraison est prise.

                                                                                                      `,1),Bn=o(`

                                                                                                      Sur un serveur WebDAV, le dépôt d’un fichier CSV depuis cet écran n’est plus + possible : le poste n’a plus de répertoire local où l’écrire. C’est le seul recours + du jour de la mise en service.

                                                                                                      `,1),Vn=o(`

                                                                                                      Ce poste ne déclare aucune source : choisissez-en une ci-dessus, sinon il n’ira + chercher aucun catalogue.

                                                                                                      `),Wn=o('
                                                                                                      ',1),Gn=o('

                                                                                                      Aucun import enregistré sur ce poste.

                                                                                                      '),Hn=o(`

                                                                                                      « Oublier la quarantaine » fait relire un fichier que le poste avait écarté : c’est le + seul geste de cette page qui puisse remettre en service un catalogue refusé.

                                                                                                      `,1),Jn=o('

                                                                                                      '),Kn=o(`

                                                                                                      Déposez ici le fichier clé

                                                                                                      Ce dépôt remplace toute la grille par le fichier apporté : il change ce que le poste + vend, et le mot de passe est donc demandé au moment du dépôt.

                                                                                                      `,1),Xn=o('

                                                                                                      '),Yn=o('

                                                                                                      Aucune anomalie sur le dernier import.

                                                                                                      '),Qn=o('

                                                                                                      ',1),Zn=o('

                                                                                                      '),es=o('

                                                                                                      Aucune unité divergente sur le dernier import.

                                                                                                      '),ts=o('

                                                                                                      ',1),as=o('

                                                                                                      '),rs=o('

                                                                                                      Aucun produit non pesable sur le dernier import.

                                                                                                      '),ns=o('

                                                                                                      ',1),ss=o('

                                                                                                      Aucun import enregistré : rien n’a encore pu être retiré.

                                                                                                      '),ls=o('

                                                                                                      Aucun produit retiré par le dernier import.

                                                                                                      '),is=o(`

                                                                                                      Ils restent enregistrés avec leur historique : une étiquette déjà collée reste + lisible en caisse, et un produit qui revient dans un prochain fichier retrouve sa + tuile. Ce poste n’en publie encore que le nombre — leurs noms se lisent dans Odoo, + en comparant avec l’export précédent.

                                                                                                      `,1),os=o('
                                                                                                    • '),us=o(`

                                                                                                        Un produit retiré de la grille ne se trouve plus ici : il se reprend dans + « Décisions en vigueur », plus bas.

                                                                                                        `,1),cs=o(`

                                                                                                        Sans motif, le poste refuse la décision. Le seul acte qui s’en passe est celui qui + EFFACE la décision en vigueur : proposer de nouveau un produit qui ne porte aucune + dérogation.

                                                                                                        `),ds=o(`

                                                                                                        Produit choisi :

                                                                                                        Ce produit est-il proposé dans la grille ?

                                                                                                        La dérogation s’enregistre seule : elle ne remet pas dans la grille un produit + qui en a été retiré, et retirer un produit n’efface pas sa dérogation.

                                                                                                        `),vs=o(' ',1),ps=o('

                                                                                                        Aucune décision locale : la grille est celle du fichier.

                                                                                                        '),fs=o('
                                                                                                      • '),gs=o('

                                                                                                          ',1),ms=o('

                                                                                                          '),hs=o('

                                                                                                          Aucun import dans l’historique.

                                                                                                          '),_s=o(' '),bs=o('

                                                                                                          QuandFichierSourceRésultatMotifLuesPesablesNon pesablesAnomaliesRetirés
                                                                                                          ',1),ws=o('
                                                                                                          ');function ys(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=20,_=50,w=20,C=["NO_BARCODE","PREPACKAGED_PRODUCT","INTERNAL_CODE_NOT_WEIGHABLE"],R={applied:"appliqué",unchanged:"identique au précédent",rejected:"refusé",failed:"échec"},A={local_drop:"dépôt local",webdav:"WebDAV",manual:"déposé sur l’écran"},W={local_drop:["catalog.options.directory"],webdav:["catalog.options.url","catalog.options.username","catalog.options.password"]},G=u(()=>t.draft.text("catalog.type"));let le=F(Lt([])),we=F(Lt([])),ke=F(Lt([])),ue=F(""),j=F(""),de=F(""),J=F(""),ye=F(!1),B=F(""),$=F(""),Z=F("loading"),D=F("loading");const z=u(()=>s().busy||e($)!==""),re=u(()=>e(we).filter(k=>k.issue==="anomaly")),S=u(()=>e(we).filter(k=>C.includes(k.code))),y=u(()=>e(we).filter(k=>k.code==="UNIT_MISMATCH")),P=u(()=>new Map(e(ke).map(k=>[k.id,k.name]))),I=u(()=>e(ue)===""?[]:vr(e(ke),pr,e(ue))),Q=u(()=>e(I).slice(0,c)),pe=u(()=>e(re).slice(0,_)),De=u(()=>e(y).slice(0,_)),Oe=u(()=>e(S).slice(0,_)),b=u(()=>[...t.health.decisions].sort((k,Y)=>Y.decided_at.localeCompare(k.decided_at))),T=u(()=>e(b).slice(0,w)),g=u(()=>e(j)===""?null:e(b).find(k=>k.product_id===e(j))??null),U=u(()=>e(g)===null?!0:e(g).offered),N=u(()=>e(g)?.min_weight_g??null),K=u(()=>e(J).trim()===""?null:Number(e(J))),_e=u(()=>e(K)!==null&&Number.isFinite(e(K))&&e(K)>0),ne=u(()=>e(de).trim()),L=u(()=>e(ne)!==""||!Le(!1,e(N))),X=u(()=>e(ne)!==""||!Le(!0,e(N))),ie=u(()=>e(_e)&&(e(ne)!==""||!Le(e(U),e(K)))),fe=u(()=>e(ne)!==""||!Le(e(U),null)),xe=u(()=>t.health.catalog?.products_withdrawn_count??0),He=u(()=>e(le).find(k=>t.health.catalog!==null&&k.id`${he(e(xe))} ${e(xe)>1?"produits retirés":"produit retiré"}`+(e(He)===null?".":` depuis l’import du ${Mt(e(He).occurred_at)}.`)),ge=u(()=>e(I).length>e(Q).length?`${he(e(Q).length)} produits affichés sur ${he(e(I).length)} trouvés — précisez votre recherche.`:`${he(e(I).length)} ${e(I).length>1?"produits trouvés":"produit trouvé"}.`),d=u(()=>`${he(e(le).length)} ${e(le).length>1?"imports affichés":"import affiché"} : le poste n’en publie jamais plus de vingt.`),x=u(()=>e(Z)==="loading"?"Lecture des signalements du dernier import…":"Les signalements du dernier import n’ont pas pu être lus : cet écran ne sait pas ce qu’ils disent."),ee=u(()=>e(Z)==="loading"?"Lecture de l’historique des imports…":"L’historique des imports n’a pas pu être lu : cet écran ne sait pas ce qu’il contient.");let f;aa(()=>{const k=t.health.catalog?.id??null;k!==f&&(f=k,q())});async function q(){const k=await s().load(()=>Wr(t.health.catalog?.id));k===null?v(Z,"unread"):(v(le,k.imports,!0),v(we,k.findings,!0),v(Z,"read"));const Y=await s().load(()=>Ba());if(Y===null){v(D,"unread");return}v(ke,Y.products,!0),v(D,"read")}async function E(k,Y){v($,k,!0),s().actionError="",s().notice="";try{const oe=await s().protect(Y);return oe===null?!1:(s().notice=oe.message,await s().refresh(),!0)}finally{v($,"")}}function be(k){t.draft.set("catalog.type",k);for(const[Y,oe]of Object.entries(W))if(Y!==k)for(const Ae of oe)t.draft.unset(Ae)}function se(k){return t.draft.faults.find(Y=>Y.field===k)?.message??""}function Se(k){v(j,k,!0),v(de,"");const Y=e(b).find(oe=>oe.product_id===k)?.min_weight_g??null;v(J,Y===null?"":String(Y),!0)}function Le(k,Y){return!(k&&Y===null)}async function ce(k){if(e(j)==="")return;const Y=e(j),oe=e(N),Ae=e(ne);await E("offered",()=>Pa(Y,{offered:k,min_weight_g:oe,reason:Ae}))&&v(de,"")}async function V(k){if(e(j)==="")return;const Y=e(j),oe=e(U),Ae=e(ne);await E(k===null?"waiver-off":"waiver",()=>Pa(Y,{offered:oe,min_weight_g:k,reason:Ae}))&&v(de,"")}async function Ue(k){if(v(ye,!1),k!=null){v(B,""),s().actionError="",s().notice="",v($,"import");try{const Y=await s().protect(()=>Ga(k));if(Y===null)return;v(B,`${k.name} : ${he(Y.rows_read_count)} lignes lues, ${he(Y.weighable_count)} pesables. `+(Y.reason===""?"Le fichier est déposé ; son résultat s’inscrira dans l’historique des imports.":Y.reason)),await s().refresh()}finally{v($,"")}}}function Ve(k){k.preventDefault(),v(ye,!1);const Y=k.dataTransfer?.files.item(0);if(e(z)){v(B,`${Y?.name??"Ce fichier"} n’a pas été déposé : un acte est déjà en cours sur cette page. Réessayez quand il aura répondu.`);return}Ue(Y)}function Ee(k){const Y=k.files?.item(0);k.value="",Ue(Y)}function Te(k){const Y=e(P).get(k);return Y!==void 0?Y:e(D)==="loading"?"Lecture du nom…":e(D)==="unread"?"Nom inconnu : le catalogue n’a pas pu être lu":"Produit absent du catalogue en service"}function Je(k,Y,oe,Ae){const We=Y>1?Ae:oe;return k>=Y?`${he(Y)} ${We}.`:`${he(k)} lignes affichées sur ${he(Y)} ${We}.`}function ae(k){const Y=[];return k.offered||Y.push("retiré de la grille"),k.min_weight_g!==null&&Y.push(`peut peser à partir de ${he(k.min_weight_g)} g`),Y.length===0?"aucune restriction":Y.join(" · ")}function Ie(k){const Y=R[k.result]??"résultat inconnu";return k.code===""?Y:`${Y} (${k.code})`}function ve(k){return A[k.source]??"source inconnue"}var ze=ws(),Re=a(ze);Qe(Re,{title:"Où le poste va chercher le catalogue",children:(k,Y)=>{var oe=Wn(),Ae=H(oe),We=a(Ae),Ye=a(We),Ke=r(We,2),M=a(Ke),te=r(Ae,2);{var me=Pe=>{var Fe=Fn();l(Pe,Fe)},Ne=Pe=>{var Fe=Mn(),Ge=H(Fe);{let it=u(()=>Ft("catalog.options.directory")),dt=u(()=>t.draft.text("catalog.options.directory")),gt=u(()=>se("catalog.options.directory"));_t(Ge,{get label(){return e(it)},path:"catalog.options.directory",get value(){return e(dt)},hint:"Laissez vide pour le répertoire du poste, celui que le service crée lui-même. Un répertoire nommé ici doit exister : le poste ne le crée pas.",get fault(){return e(gt)},onchange:xt=>t.draft.set("catalog.options.directory",xt)})}var ft=r(Ge,2),ot=r(a(ft)),ct=a(ot);p(()=>i(ct,`flv_${t.health.station??""}.csv`)),l(Pe,Fe)},qe=Pe=>{var Fe=Bn(),Ge=H(Fe);{let ct=u(()=>Ft("catalog.options.url")),it=u(()=>t.draft.text("catalog.options.url")),dt=u(()=>se("catalog.options.url"));_t(Ge,{get label(){return e(ct)},path:"catalog.options.url",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.url",gt)})}var ft=r(Ge,2);{let ct=u(()=>Ft("catalog.options.username")),it=u(()=>t.draft.text("catalog.options.username")),dt=u(()=>se("catalog.options.username"));_t(ft,{get label(){return e(ct)},path:"catalog.options.username",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.username",gt)})}var ot=r(ft,2);{let ct=u(()=>Ft("catalog.options.password")),it=u(()=>se("catalog.options.password"));_t(ot,{get label(){return e(ct)},path:"catalog.options.password",kind:"password",value:"",hint:"Laissez vide : le mot de passe actuel est conservé.",get fault(){return e(it)},onchange:dt=>t.draft.set("catalog.options.password",dt)})}l(Pe,Fe)},$e=Pe=>{var Fe=Vn();l(Pe,Fe)};m(te,Pe=>{t.draft.config===null?Pe(me):e(G)==="local_drop"?Pe(Ne,1):e(G)==="webdav"?Pe(qe,2):Pe($e,-1)})}p(()=>{ua(Ye,e(G)==="local_drop"),ua(M,e(G)==="webdav")}),et("change",Ye,()=>be("local_drop")),et("change",M,()=>be("webdav")),l(k,oe)},$$slots:{default:!0}});var nt=r(Re,2);Qe(nt,{title:"Dernier import",children:(k,Y)=>{var oe=Hn(),Ae=H(oe);{var We=qe=>{var $e=Gn();l(qe,$e)},Ye=qe=>{Za(qe,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives}})};m(Ae,qe=>{t.health.catalog===null?qe(We):qe(Ye,-1)})}var Ke=r(Ae,2),M=a(Ke),te=r(Ke,2),me=a(te);{let qe=u(()=>e($)==="reload");at(me,{act:"reload",kind:"write",label:"Recharger le catalogue",protected:!0,get busy(){return e(qe)},get disabled(){return e(z)},onrun:()=>{E("reload",Gr)}})}var Ne=r(me,2);{let qe=u(()=>e($)==="quarantine");at(Ne,{act:"quarantine",kind:"destructive",label:"Oublier la quarantaine",protected:!0,get busy(){return e(qe)},get disabled(){return e(z)},onrun:()=>{E("quarantine",Hr)}})}p(()=>i(M,t.health.catalog_source===null?"Aucune source de catalogue publiée par ce poste.":"Source : "+t.health.catalog_source.label)),l(k,oe)},$$slots:{default:!0}});var ut=r(nt,2);Qe(ut,{title:"Déposer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(k,Y)=>{var oe=Kn(),Ae=H(oe);{var We=Pe=>{var Fe=Jn(),Ge=a(Fe);p(()=>i(Ge,e(B))),l(Pe,Fe)};m(Ae,Pe=>{e(B)!==""&&Pe(We)})}var Ye=r(Ae,2);let Ke;var M=a(Ye),te=r(a(M)),me=a(te),Ne=r(M,2),qe=a(Ne),$e=r(qe);p(()=>{Ke=Ht(Ye,1,"drop svelte-3gk7u7",null,Ke,{dropping:e(ye),working:e($)==="import"}),i(me,`flv_${t.health.station??""}.csv`),i(qe,`${e($)==="import"?"Import en cours…":"Choisir un fichier"} `),$e.disabled=e(z)}),Qt("dragover",Ye,Pe=>{Pe.preventDefault(),!e(z)&&v(ye,!0)}),Qt("dragleave",Ye,()=>v(ye,!1)),Qt("drop",Ye,Ve),et("change",$e,Pe=>Ee(Pe.currentTarget)),l(k,oe)},$$slots:{default:!0}});var pt=r(ut,2);Qe(pt,{title:"Anomalies à corriger dans Odoo",note:"Chaque ligne porte son numéro dans le CSV, son motif et la valeur fautive.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=Xn(),me=a(te);p(()=>i(me,e(x))),l(M,te)},Ye=M=>{var te=Yn();l(M,te)},Ke=M=>{var te=Qn(),me=H(te),Ne=a(me),qe=r(Ne);{var $e=Fe=>{var Ge=yt("Corrigez celles-ci dans Odoo : l’import suivant ne signalera que ce qui reste.");l(Fe,Ge)};m(qe,Fe=>{e(re).length>e(pe).length&&Fe($e)})}var Pe=r(me,2);_a(Pe,()=>e(pe),()=>"anomalies"),p(Fe=>i(Ne,`${Fe??""} `),[()=>Je(e(pe).length,e(re).length,"anomalie","anomalies")]),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(re).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var je=r(pt,2);Qe(je,{title:"Unités divergentes",note:"Le produit reste proposé : le code-barres fait foi, seul le libellé du prix est faux.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=Zn(),me=a(te);p(()=>i(me,e(x))),l(M,te)},Ye=M=>{var te=es();l(M,te)},Ke=M=>{var te=ts(),me=H(te),Ne=a(me),qe=r(me,2);_a(qe,()=>e(De),()=>"mismatches"),p($e=>i(Ne,$e),[()=>Je(e(De).length,e(y).length,"unité divergente","unités divergentes")]),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(y).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var st=r(je,2);Qe(st,{title:"Produits non pesables",note:"Un inventaire, pas une liste d’erreurs : ces produits portent déjà leur code-barres et n’ont aucune raison d’être pesés.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=as(),me=a(te);p(()=>i(me,e(x))),l(M,te)},Ye=M=>{var te=rs();l(M,te)},Ke=M=>{var te=ns(),me=H(te),Ne=a(me),qe=r(me,2);_a(qe,()=>e(Oe),()=>"not-weighable"),p($e=>i(Ne,$e),[()=>Je(e(Oe).length,e(S).length,"produit non pesable","produits non pesables")]),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(S).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var Ze=r(st,2);Qe(Ze,{title:"Produits retirés depuis l’import précédent",note:"Un produit absent du nouveau fichier est marqué retiré à sa date, jamais supprimé.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=ss();l(M,te)},Ye=M=>{var te=ls();l(M,te)},Ke=M=>{var te=is(),me=H(te),Ne=a(me);p(()=>i(Ne,e(Xe))),l(M,te)};m(Ae,M=>{t.health.catalog===null?M(We):e(xe)===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var bt=r(Ze,2);Qe(bt,{title:"Décider d’un produit",note:"Retirer un produit et l’autoriser à peser moins sont deux décisions séparées : l’une n’efface pas l’autre.",children:(k,Y)=>{var oe=vs(),Ae=r(H(oe),2),We=r(Ae,2);{var Ye=te=>{var me=us(),Ne=H(me),qe=a(Ne),$e=r(Ne,2),Pe=a($e);rt(Pe,21,()=>e(Q),Fe=>Fe.id,(Fe,Ge)=>{var ft=os(),ot=a(ft),ct=a(ot),it=r(ot,2),dt=a(it),gt=r(it,2),xt=a(gt);p(()=>{i(ct,e(Ge).name),i(dt,e(Ge).id),i(xt,`${e(Ge).unit_price_text??""}${e(Ge).price_suffix??""}`)}),et("click",ot,()=>Se(e(Ge).id)),l(Fe,ft)}),p(()=>i(qe,e(ge))),l(te,me)};m(We,te=>{e(ue)!==""&&te(Ye)})}var Ke=r(We,2);{var M=te=>{var me=ds(),Ne=a(me),qe=r(a(Ne)),$e=a(qe),Pe=r(qe),Fe=r(Ne,2),Ge=a(Fe),ft=r(Fe,4),ot=r(ft,2);{var ct=tt=>{var qt=cs();l(tt,qt)};m(ot,tt=>{e(ne)===""&&tt(ct)})}var it=r(ot,2),dt=r(a(it),2),gt=a(dt);{var xt=tt=>{{let qt=u(()=>e($)==="offered"),$t=u(()=>e(z)||!e(L));at(tt,{act:"offered",kind:"destructive",label:"Ne plus proposer ce produit",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{ce(!1)}})}},Nt=tt=>{{let qt=u(()=>e($)==="offered"),$t=u(()=>e(z)||!e(X));at(tt,{act:"offered",kind:"write",label:"Le proposer de nouveau",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{ce(!0)}})}};m(gt,tt=>{e(U)?tt(xt):tt(Nt,-1)})}var Bt=r(it,2),At=r(a(Bt),2),Vt=r(At,2),kt=a(Vt);{let tt=u(()=>e($)==="waiver"),qt=u(()=>e(z)||!e(ie));at(kt,{act:"waiver",kind:"write",label:"Enregistrer la dérogation",protected:!0,get busy(){return e(tt)},get disabled(){return e(qt)},onrun:()=>{V(e(K))}})}var Et=r(kt,2);{var Wt=tt=>{{let qt=u(()=>e($)==="waiver-off"),$t=u(()=>e(z)||!e(fe));at(tt,{act:"waiver-off",kind:"write",label:"Retirer la dérogation",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{V(null)}})}};m(Et,tt=>{e(N)!==null&&tt(Wt)})}p((tt,qt)=>{Be(me,"data-decision",e(j)),i($e,tt),i(Pe,` (${e(j)??""})`),i(Ge,`En vigueur : ${qt??""}`),na(At,e(J))},[()=>Te(e(j)),()=>e(g)===null?"aucune décision — ce produit suit les règles générales":ae(e(g))]),ia(ft,()=>e(de),tt=>v(de,tt)),et("input",At,tt=>v(J,tt.currentTarget.value,!0)),l(te,me)};m(Ke,te=>{e(j)!==""&&te(M)})}ia(Ae,()=>e(ue),te=>v(ue,te)),l(k,oe)},$$slots:{default:!0}});var Rt=r(bt,2);Qe(Rt,{title:"Décisions en vigueur",note:"Ce qu’un humain a décidé, produit par produit. C’est ici que se reprend un produit retiré de la grille.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=Ke=>{var M=ps();l(Ke,M)},Ye=Ke=>{var M=gs(),te=H(M),me=a(te),Ne=r(te,2),qe=a(Ne);rt(qe,21,()=>e(T),$e=>$e.product_id,($e,Pe)=>{var Fe=fs(),Ge=a(Fe),ft=r(Ge,2),ot=a(ft),ct=r(ft,2),it=a(ct),dt=r(ct,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),At=a(Bt);p((Vt,kt,Et)=>{i(ot,Vt),i(it,e(Pe).product_id),i(gt,kt),i(Nt,e(Pe).reason),i(At,Et)},[()=>Te(e(Pe).product_id),()=>ae(e(Pe)),()=>da(e(Pe).decided_at)]),et("click",Ge,()=>Se(e(Pe).product_id)),l($e,Fe)}),p($e=>i(me,$e),[()=>Je(e(T).length,e(b).length,"décision en vigueur","décisions en vigueur")]),l(Ke,M)};m(Ae,Ke=>{e(b).length===0?Ke(We):Ke(Ye,-1)})}l(k,oe)},$$slots:{default:!0}});var Ut=r(Rt,2);Qe(Ut,{title:"Vingt derniers imports",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=ms(),me=a(te);p(()=>i(me,e(ee))),l(M,te)},Ye=M=>{var te=hs();l(M,te)},Ke=M=>{var te=bs(),me=H(te),Ne=a(me),qe=r(me,2),$e=a(qe),Pe=r(a($e));rt(Pe,21,()=>e(le),Fe=>Fe.id,(Fe,Ge)=>{var ft=_s(),ot=a(ft),ct=a(ot),it=r(ot),dt=a(it),gt=r(it),xt=a(gt),Nt=r(gt),Bt=a(Nt),At=r(Nt),Vt=a(At),kt=r(At),Et=a(kt),Wt=r(kt),tt=a(Wt),qt=r(Wt),$t=a(qt),Zt=r(qt),ra=a(Zt),h=r(Zt),O=a(h);p((Ce,Me,lt,mt,wt,zt,jt,St)=>{i(ct,Ce),i(dt,e(Ge).file_name),i(xt,Me),i(Bt,lt),i(Vt,e(Ge).reason),i(Et,mt),i(tt,wt),i($t,zt),i(ra,jt),i(O,St)},[()=>Mt(e(Ge).occurred_at),()=>ve(e(Ge)),()=>Ie(e(Ge)),()=>he(e(Ge).rows_read_count),()=>he(e(Ge).weighable_count),()=>he(e(Ge).not_weighable_count),()=>he(e(Ge).anomalies_count),()=>he(e(Ge).products_withdrawn_count)]),l(Fe,ft)}),p(()=>i(Ne,e(d))),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(le).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}}),l(n,ze),Ot()}Tt(["change","click","input"]);function xs(n){switch(n){case"ok":return"OK";case"warn":return"À SURVEILLER";case"fault":return"EN PANNE";case"off":return"SANS OBJET";default:return"INCONNU"}}var ks=o('

                                                                                                          '),qs=o('

                                                                                                          ');function Ss(n,t){Ct(t,!0);var s=qs(),c=a(s),_=a(c),w=r(_,2),C=a(w),R=r(w,2),A=a(R),W=r(c,2),G=a(W),le=r(W,2);{var we=ke=>{var ue=ks(),j=a(ue);p(()=>i(j,t.light.remedy)),l(ke,ue)};m(le,ke=>{t.light.remedy!==""&&ke(we)})}p(ke=>{Be(s,"data-light",t.light.id),Be(s,"data-level",t.light.level),Be(_,"data-level",t.light.level),i(C,t.light.label),i(A,ke),i(G,t.light.value)},[()=>xs(t.light.level)]),l(n,s),Ot()}function Ls(n){return[Es(n),zs(n),Ps(n),Ts(n),Os(n),Rs(n)]}function Es(n){const t=n.state.scale,s=ca(t.median_ms);return n.scale_present?t.connected?t.too_slow?{id:"scale",label:"Balance",level:"warn",value:`une mesure toutes les ${s}, plus lent que la péremption`,remedy:"À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante. Vérifiez le câble et l’adaptateur USB, puis la cadence sur la page Matériel."}:{id:"scale",label:"Balance",level:"ok",value:As(t.provisional,`une mesure toutes les ${s}`),remedy:""}:{id:"scale",label:"Balance",level:"fault",value:"elle ne répond plus",remedy:"Vérifiez le câble et l’alimentation de la balance, puis touchez « Tester la balance ». En attendant, « Basculer en saisie manuelle » permet de continuer à servir."}:{id:"scale",label:"Balance",level:"off",value:"ce poste est déclaré sans balance",remedy:""}}function zs(n){const t=n.state.printer;switch(t.health){case"faulted":return{id:"printer",label:"Imprimante",level:"fault",value:t.detail===""?"elle ne peut pas imprimer":t.detail,remedy:js(n)};case"consumable":return{id:"printer",label:"Imprimante",level:"warn",value:"elle imprime, mais le rouleau arrive en fin de vie",remedy:"Changez le rouleau quand vous passez derrière le comptoir, puis touchez « J’ai changé le rouleau ». Le poste continue de servir en attendant."};case"ready":return{id:"printer",label:"Imprimante",level:"ok",value:"elle répond et n’a rien à signaler",remedy:""};default:return{id:"printer",label:"Imprimante",level:"unknown",value:"elle prend les étiquettes et ne dit rien en retour",remedy:"C’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne. Pour savoir si elle imprime, touchez « Imprimer une étiquette de test »."}}}function js(n){const t="Regardez le capot, le rouleau et le câble, puis touchez « Tester l’imprimante ».";return n.printing?.fallback_available===!0?t+" Le poste peut servir en attendant : « Imprimer sur l’imprimante du poste voisin ».":t}function Ps(n){const t=n.roll;return t===null?{id:"roll",label:"Rouleau",level:"unknown",value:"aucun compteur d’étiquettes sur ce poste",remedy:`Sans imprimante construite, il n’y a pas de rouleau à compter : vérifiez le « ${Ft("printer.type")} » et ses réglages sur la page Matériel.`}:t.known?t.level==="warn"?{id:"roll",label:"Rouleau",level:"warn",value:t.message,remedy:"Changez le rouleau, puis touchez « J’ai changé le rouleau »."}:{id:"roll",label:"Rouleau",level:"ok",value:t.message,remedy:""}:{id:"roll",label:"Rouleau",level:"unknown",value:t.message,remedy:"Touchez « J’ai changé le rouleau » en mettant un rouleau neuf : c’est le seul geste qui dise quelque chose de vrai du papier."}}function Ts(n){const t=n.catalog_source?.label??"";if(n.state.catalog_count===0)return{id:"catalog",label:"Catalogue",level:"fault",value:"aucun produit dans la grille",remedy:Cs(t)};const s=n.catalog;return s!==null&&s.result!=="applied"&&s.result!=="unchanged"?{id:"catalog",label:"Catalogue",level:"warn",value:`dernier fichier refusé : ${s.reason===""?s.result:s.reason}`,remedy:"La grille tourne toujours sur le catalogue précédent. Corrigez le fichier dans Odoo, ou touchez « Oublier la quarantaine » puis redéposez-le."}:{id:"catalog",label:"Catalogue",level:"ok",value:`${he(n.state.catalog_count)} produits dans la grille`,remedy:""}}function Cs(n){return"Déposez le fichier du catalogue là où le poste le guette, ou utilisez « Importer un catalogue » ci-contre pour le glisser depuis une clé USB."+(n===""?"":` Ce poste surveille : ${n}.`)}function Os(n){const t=n.disk;if(t===null)return{id:"disk",label:"Disque",level:"unknown",value:"la place libre n’a pas pu être mesurée",remedy:"Téléchargez le fichier de diagnostic : il porte ce que le poste a pu lire du volume, et c’est la pièce qu’un support demandera."};const s=wa(t.free_bytes),c=`seuil ${he(t.alert_mb)} Mo`;return t.free_bytes===0?{id:"disk",label:"Disque",level:"fault",value:`plus un octet libre sur ${t.path}`,remedy:"Le journal ne peut plus rien écrire, alors que les étiquettes continuent de sortir. Faites de la place sur le disque, puis rechargez cette page."}:t.alert_mb>0&&t.free_bytes0?{id:"journal",label:"Journal",level:"fault",value:`${he(t)} pesées imprimées mais non enregistrées`,remedy:"Les étiquettes sortent, les ventes sont bonnes, c’est la trace qui manque. Téléchargez le fichier de diagnostic et prévenez le support : ne redémarrez rien."}:n.counters.journal_rows_count<0?{id:"journal",label:"Journal",level:"unknown",value:"ce poste n’a pas de journal ouvert",remedy:"Le poste pèse et imprime quand même. Téléchargez le fichier de diagnostic : il dit pourquoi la base n’a pas pu être ouverte."}:{id:"journal",label:"Journal",level:"ok",value:`${he(n.counters.journal_rows_count)} pesées enregistrées`,remedy:""}}function As(n,t){return n?t+" (cadence encore provisoire)":t}function Ns(n){return n===null||!n.known?"unknown":n.configured?"ok":"missing"}function $s(n){switch(n){case"applied":return"appliqué";case"unchanged":return"inchangé, déjà appliqué";case"rejected":return"refusé";case"failed":return"échec";default:return n}}var Ds=o("Une mesure toutes les ",1),Is=o('

                                                                                                          '),Us=o("Source : ",1),Fs=o(`

                                                                                                          Aucun import enregistré sur ce poste : le catalogue n’est jamais arrivé, ou le + journal ne le porte pas.

                                                                                                          `),Ms=o('

                                                                                                          ',1),Bs=o('

                                                                                                          ',1),Vs=o('

                                                                                                          Aucune décision locale : le catalogue est proposé tel qu’il arrive.

                                                                                                          '),Ws=o('

                                                                                                          '),Gs=o(' '),Hs=o('
                                                                                                        • '),Js=o('
                                                                                                            ',1),Ks=o(`INCONNU — la question n’a pas pu être posée à ce système. Ce + n’est pas « non configuré » : personne ne sait encore.`,1),Xs=o('OK ',1),Ys=o('NON CONFIGURÉ ',1),Qs=o('

                                                                                                            '),Zs=o('

                                                                                                            ',1),el=o('

                                                                                                            Rien à signaler depuis le démarrage du poste.

                                                                                                            '),tl=o(' '),al=o('
                                                                                                          • '),rl=o('
                                                                                                              '),nl=o(''),sl=o('

                                                                                                              '),ll=o('
                                                                                                              Numéro de poste
                                                                                                              Nom
                                                                                                              Coopérative
                                                                                                              Version
                                                                                                              Empreinte de configuration
                                                                                                              ',1),il=o('
                                                                                                              ');function ol(n,t){Ct(t,!0);const s=u(()=>t.health.new_version),c=u(()=>Ls(t.health)),_=u(()=>t.health.state.scale),w=u(()=>t.health.unattended_restart),C=u(()=>t.health.decisions),R=5,A=u(()=>e(C).slice(0,R));var W=il(),G=a(W);rt(G,21,()=>e(c),J=>J.id,(J,ye)=>{Ss(J,{get light(){return e(ye)}})});var le=r(G,2);Qe(le,{title:"Cadence de la balance",children:(J,ye)=>{var B=Is(),$=a(B);{var Z=S=>{var y=yt("Ce poste est déclaré sans balance : le poids est saisi à la main.");l(S,y)},D=S=>{var y=yt(`La balance ne répond pas : aucune cadence n’est mesurable tant qu’aucune trame + n’arrive.`);l(S,y)},z=S=>{var y=yt(`Aucun intervalle n’a encore été mesuré : la cadence apparaîtra dès les premières + trames.`);l(S,y)},re=S=>{var y=Ds(),P=r(H(y)),I=a(P),Q=r(P),pe=r(Q);{var De=T=>{var g=yt("C’est encore une valeur d’attente : moins de huit intervalles ont été observés.");l(T,g)};m(pe,T=>{e(_).provisional&&T(De)})}var Oe=r(pe,2);{var b=T=>{var g=yt("À cette cadence, un poids serait périmé avant l’arrivée de la mesure suivante.");l(T,g)};m(Oe,T=>{e(_).too_slow&&T(b)})}p((T,g)=>{i(I,T),i(Q,`, médiane + observée sur ${g??""} intervalles. `)},[()=>ca(e(_).median_ms),()=>he(e(_).observations_count)]),l(S,y)};m($,S=>{t.health.scale_present?e(_).connected?e(_).observations_count===0?S(z,2):S(re,-1):S(D,1):S(Z)})}l(J,B)},$$slots:{default:!0}});var we=r(le,2);Qe(we,{title:"Catalogue",children:(J,ye)=>{var B=Bs(),$=H(B),Z=a($);{var D=P=>{var I=yt("Aucune source de catalogue n’est publiée par ce poste.");l(P,I)},z=P=>{var I=Us(),Q=r(H(I)),pe=a(Q);p(()=>i(pe,t.health.catalog_source.label)),l(P,I)};m(Z,P=>{t.health.catalog_source===null?P(D):P(z,-1)})}var re=r($,2);{var S=P=>{var I=Fs();l(P,I)},y=P=>{var I=Ms(),Q=H(I),pe=a(Q),De=r(Q,2);Za(De,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives},get onshowrows(){return t.onshowrows}}),p((Oe,b)=>i(pe,`Dernier essai : ${Oe??""} — + ${b??""}${t.health.catalog.file_name===""?"":" ("+t.health.catalog.file_name+")"}${t.health.catalog.reason===""?"":" : "+t.health.catalog.reason}`),[()=>Mt(t.health.catalog.occurred_at),()=>$s(t.health.catalog.result)]),l(P,I)};m(re,P=>{t.health.catalog===null?P(S):P(y,-1)})}l(J,B)},$$slots:{default:!0}});var ke=r(we,2);Qe(ke,{title:"Décisions locales en vigueur",note:"Ce qu’un humain a décidé de ce catalogue, avec son motif et sa date.",children:(J,ye)=>{var B=Dt(),$=H(B);{var Z=z=>{var re=Vs();l(z,re)},D=z=>{var re=Js(),S=H(re);{var y=I=>{var Q=Ws(),pe=a(Q);p(De=>i(pe,`${De??""} décisions en vigueur — les + 5 plus récentes ci-dessous. La liste entière est sur la page + Catalogue.`),[()=>he(e(C).length)]),l(I,Q)};m(S,I=>{e(C).length>R&&I(y)})}var P=r(S,2);rt(P,21,()=>e(A),I=>I.product_id,(I,Q)=>{var pe=Hs(),De=a(pe),Oe=a(De),b=r(De,2);{var T=_e=>{var ne=Gs(),L=a(ne);p(X=>i(L,`peut peser à partir de ${X??""} g`),[()=>he(e(Q).min_weight_g)]),l(_e,ne)};m(b,_e=>{e(Q).min_weight_g!==null&&_e(T)})}var g=r(b,2),U=a(g),N=r(g,2),K=a(N);p(_e=>{i(Oe,`${e(Q).offered?"Dérogation de poids":"Produit retiré"} — ${e(Q).product_id??""}`),i(U,e(Q).reason),i(K,`${_e??""}, ${e(Q).decided_by??""}`)},[()=>da(e(Q).decided_at)]),l(I,pe)}),l(z,re)};m($,z=>{e(C).length===0?z(Z):z(D,-1)})}l(J,B)},$$slots:{default:!0}});var ue=r(ke,2);Qe(ue,{title:"Redémarrage sans intervention",children:(J,ye)=>{var B=Zs(),$=H(B),Z=a($);{var D=P=>{var I=Ks();l(P,I)},z=P=>{var I=Xs(),Q=r(H(I));p(()=>i(Q,` — après une coupure de courant, ce poste revient seul sur + l’écran client. ${e(w).detail??""}`)),l(P,I)},re=P=>{var I=Ys(),Q=r(H(I));p(()=>i(Q,` — ${e(w).detail??""}`)),l(P,I)};m(Z,P=>{e(w)===null||!e(w).known?P(D):e(w).configured?P(z,1):P(re,-1)})}var S=r($,2);{var y=P=>{var I=Qs(),Q=a(I);p(()=>i(Q,e(w).remedy)),l(P,I)};m(S,P=>{e(w)!==null&&e(w).known&&!e(w).configured&&e(w).remedy!==""&&P(y)})}p(P=>Be($,"data-verdict",P),[()=>Ns(e(w))]),l(J,B)},$$slots:{default:!0}});var j=r(ue,2);Qe(j,{title:"Dix derniers événements",children:(J,ye)=>{var B=Dt(),$=H(B);{var Z=z=>{var re=el();l(z,re)},D=z=>{var re=rl();rt(re,21,()=>t.health.events,S=>S.id,(S,y)=>{var P=al(),I=a(P),Q=a(I),pe=r(I,2),De=a(pe),Oe=r(pe,2);{var b=U=>{var N=tl(),K=a(N);p(()=>i(K,e(y).code)),l(U,N)};m(Oe,U=>{e(y).code!==""&&Gt.showTechnicalNames&&U(b)})}var T=r(Oe,2),g=a(T);p((U,N)=>{Be(P,"data-level",e(y).level),i(Q,U),i(De,N),i(g,e(y).message)},[()=>zn(e(y).occurred_at),()=>Xa(e(y).source)]),l(S,P)}),l(z,re)};m($,z=>{t.health.events.length===0?z(Z):z(D,-1)})}l(J,B)},$$slots:{default:!0}});var de=r(j,2);Qe(de,{title:"Ce poste",children:(J,ye)=>{var B=ll(),$=H(B),Z=r(a($),2),D=a(Z),z=r(Z,4),re=a(z),S=r(z,4),y=a(S),P=r(S,4),I=a(P),Q=r(P,4),pe=a(Q),De=r($,2);{var Oe=b=>{var T=sl(),g=a(T),U=r(g);{var N=K=>{var _e=nl();et("click",_e,function(...ne){t.onshowupdate?.apply(this,ne)}),l(K,_e)};m(U,K=>{t.onshowupdate!==void 0&&K(N)})}p(()=>{Be(T,"data-update-available",e(s)),i(g,`Version ${e(s)??""} disponible. `)}),l(b,T)};m(De,b=>{e(s)!==""&&b(Oe)})}p(()=>{i(D,t.health.station),i(re,t.health.station_name===""?"non renseigné":t.health.station_name),i(y,t.health.coop),i(I,t.health.version),i(pe,t.health.config_fingerprint)}),l(J,B)},$$slots:{default:!0}}),l(n,W),Ot()}Tt(["click"]);var ul=o(''),cl=o(`

                                                                                                              Lecture de la configuration en cours… tant qu’elle n’est pas arrivée, cette page ne + déclare rien de ce poste.

                                                                                                              `),Na=o('
                                                                                                            • '),dl=o('
                                                                                                                '),vl=o('

                                                                                                                ',1),pl=o('
                                                                                                              • '),fl=o('

                                                                                                                  ',1),gl=o('

                                                                                                                  '),ml=o('
                                                                                                                • '),hl=o('
                                                                                                                    '),_l=o('

                                                                                                                      ',1),bl=o('

                                                                                                                      Balance

                                                                                                                      L’état et la cadence viennent de ce que le poste observe vraiment, jamais d’un réglage.

                                                                                                                      Réglages série de la balance

                                                                                                                      Imprimante

                                                                                                                      Réglages de l’imprimante
                                                                                                                      ');function wl(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=20,_=3,w=250,C=50,R=_*1e3+w+1e3,A=12;let W=F(Lt([])),G=F(Lt([])),le=F(Lt([])),we=F(Lt([])),ke=F(""),ue=F(!1),j=F(""),de=F(0),J=F(0),ye=F(!1),B=F(null),$=!1;const Z=u(()=>t.health.state.scale),D=u(()=>t.health.state.printer),z=u(()=>t.draft.config!==null),re=u(()=>e(z)&&!t.draft.flag("scale.present")),S=u(()=>t.draft.text("scale.options.port")),y=u(()=>e(B)!==null&&e(B).port===e(S)?e(B).reason:""),P=u(()=>e(ke)===e(S)?e(we):[]),I=u(d),Q=u(ee),pe=u(E),De=u(Se),Oe=u(()=>Ee("scale.type")!==""||Ee("scale.options.port")!==""),b=u(()=>Ee("printer.type")!==""||Ee("printer.options.transport")!==""||Ee("printer.options.queue")!=="");br(()=>{$=!0}),ka(()=>{L("ports",X)}),aa(()=>{e(ye)||!T(e(S))||U(e(S))});function T(h){return!$&&e(z)&&!e(re)&&h!==""&&h===e(S)&&g(h)&&e(y)===""&&e(j)===""}function g(h){return e(W).some(O=>O.name===h)}async function U(h){v(ye,!0);try{for(;T(h);){try{K(h,await ja(h,_))}catch(O){v(B,{port:h,reason:Je(O)},!0);return}await _e()}}finally{v(ye,!1)}}async function N(){const h=e(S),O=await s().protect(()=>ja(h,_));O!==null&&(K(h,O),v(B,null))}function K(h,O){if($||O.length===0)return;const Ce=h===e(ke)?e(we):[];v(ke,h,!0),v(we,[...Ce,...O].slice(-c),!0)}function _e(h=w){return new Promise(O=>setTimeout(O,h))}async function ne(){const h=Date.now()+R;for(;e(ye)&&Date.now()Ha(h));O!==null&&(s().notice=O.message,await s().refresh())}async function He(){v(de,0),v(J,0),e(ue)||await X();const h=e(W);if(h.length===0){v(le,[],!0);return}await s().protect(()=>Xe(h))}async function Xe(h){v(le,[],!0),v(J,h.length,!0);for(const[O,Ce]of h.entries()){v(de,O+1);try{const Me=await Ur(Ce.name);v(le,[...e(le),{port:Ce.name,message:Me.message,refused:!1}],!0)}catch(Me){if(ae(Me))throw Me;v(le,[...e(le),{port:Ce.name,message:Je(Me),refused:!0}],!0)}}}function ge(){return e(j)!=="detect"?"Détecter automatiquement":e(ye)?"Détection : le port se libère…":e(J)===0?"Détection : énumération des ports…":`Détection : port ${he(e(de))} sur ${he(e(J))}…`}function d(){return t.health.scale_present?e(Z).connected?e(Z).too_slow?{level:"warn",word:"Trop lente",detail:x()+" À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante."}:{level:"ok",word:"Connectée",detail:"Elle répond. "+x()}:{level:"fault",word:"Sans réponse",detail:"Elle ne répond plus. Vérifiez le câble et l’alimentation, puis « Tester la balance » sur la page Dépannage."}:{level:"off",word:"Sans balance",detail:"Ce poste est déclaré sans balance : le feu est éteint et le poids se saisit à la main."}}function x(){return e(Z).observations_count===0?"Aucun intervalle n’a encore été mesuré : la cadence sera connue dès les premières trames.":`Une mesure toutes les ${ca(e(Z).median_ms)} sur ${he(e(Z).observations_count)} intervalles`+(e(Z).provisional?", cadence encore provisoire.":".")}function ee(){const h=f[e(D).health]??{level:"unknown",word:"État inconnu",detail:"Le poste a répondu un état que cet écran ne sait pas nommer."};return{...h,detail:e(D).detail===""?h.detail:e(D).detail}}const f={ready:{level:"ok",word:"Prête",detail:"Elle répond et n’a rien à signaler."},consumable:{level:"warn",word:"Rouleau en fin de vie",detail:"Elle imprime, mais le rouleau arrive en fin de vie."},faulted:{level:"fault",word:"En panne",detail:"Elle ne peut pas imprimer."},unknown:{level:"unknown",word:"Silencieuse",detail:"Elle prend les étiquettes et ne dit rien en retour : c’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne."}};function q(){const h=e(D).observed_at===""?"Jamais observée depuis le démarrage":`Observée le ${Mt(e(D).observed_at)}`,O=e(D).pending_jobs_count;return`${h}, ${he(O)} ${O>1?"travaux":"travail"} en attente.`}function E(){const h=se();return h===""?be():`${be()} ${h}`}function be(){return e(z)?e(re)?"Ce poste est déclaré sans balance : aucun port n’est écouté.":e(S)===""?"Aucun port n’est indiqué : choisissez-en un dans la liste ci-dessus pour écouter les trames.":e(ue)?g(e(S))?e(y)!==""?`L’écoute de ${e(S)} est arrêtée.`:e(j)!==""?`L’écoute de ${e(S)} est suspendue le temps de l’acte en cours.`:`Écoute de ${e(S)}.`:`${e(S)} n’est pas visible depuis ce poste : rien n’est écouté en continu.`:e(j)==="ports"?`Énumération des ports en cours : l’écoute de ${e(S)} démarre dès qu’il est vu.`:`Les ports de ce poste n’ont pas été énumérés : « Lister les ports » dira si ${e(S)} existe.`:"Lecture de la configuration en cours : le port à écouter n’est pas encore connu."}function se(){return e(P).length===0?"Aucune trame reçue pour l’instant.":e(P).length===1?`Une seule trame reçue — ${he(c)} au plus sont gardées.`:`Les ${he(e(P).length)} dernières trames — ${he(c)} au plus, la plus récente en bas.`}function Se(){return!e(z)||e(re)||e(S)===""?"":e(y)!==""?"Reprendre l’écoute":e(ue)&&!g(e(S))?"Écouter ce port une fois":""}function Le(h,O,Ce){const Me=`${he(Ce)} ${Ce>1?O:h}`;return Ce<=A?Me+".":`${Me} — seules les ${he(A)} premières lignes sont affichées.`}function ce(h,O){const Ce=Te(h);return Ce.length>0?Ce:O.slice(0,A)}function V(h){return[...new TextEncoder().encode(h)].map(O=>O.toString(16).toUpperCase().padStart(2,"0")).join(" ")}const Ue={0:"NUL",2:"STX",3:"ETX",4:"EOT",5:"ENQ",6:"ACK",9:"TAB",10:"LF",13:"CR",21:"NAK",27:"ESC",127:"DEL"};function Ve(h){let O="";for(const Ce of h){const Me=Ce.codePointAt(0)??0;if(Me>=32&&Me!==127){O+=Ce;continue}O+=`⟨${Ue[Me]??Me.toString(16).toUpperCase().padStart(2,"0")}⟩`}return O}function Ee(h){return t.draft.faults.find(O=>O.field===h)?.message??""}function Te(h){return t.draft.faults.find(O=>O.field===h)?.allowed??[]}function Je(h){return h instanceof It?h.message:h instanceof Error?"Le poste n’a pas répondu : "+h.message:"Le poste n’a pas répondu."}function ae(h){return h instanceof It&&(h.status===401||h.status===409)}const Ie=[{what:"label",name:"étiquette"},{what:"alignment",name:"alignement"},{what:"ruler",name:"réglette"}];var ve=bl(),ze=a(ve),Re=a(ze),nt=r(a(Re),2),ut=r(a(nt),1,!0),pt=r(Re,2),je=a(pt),st=r(pt,4);{var Ze=h=>{var O=ul(),Ce=a(O);p(()=>ua(Ce,e(re))),et("change",Ce,Me=>t.draft.set("scale.present",!Me.currentTarget.checked)),l(h,O)},bt=h=>{var O=cl();l(h,O)};m(st,h=>{e(z)?h(Ze):h(bt,-1)})}var Rt=r(st,2),Ut=a(Rt);{let h=u(()=>e(j)==="ports"),O=u(()=>e(j)!=="");at(Ut,{label:"Lister les ports",get busy(){return e(h)},get disabled(){return e(O)},onrun:()=>{L("ports",X)}})}var k=r(Ut,2);{let h=u(ge),O=u(()=>e(j)!=="");at(k,{act:"detect",get label(){return e(h)},protected:!0,get disabled(){return e(O)},onrun:()=>{L("detect",He)}})}var Y=r(Rt,2);{var oe=h=>{var O=vl(),Ce=H(O),Me=a(Ce),lt=r(Ce,2);{var mt=wt=>{var zt=dl();rt(zt,21,()=>e(W).slice(0,A),jt=>jt.name,(jt,St)=>{var Kt=Na(),ea=a(Kt),ga=a(ea),er=r(ea,2),tr=a(er);p(()=>{i(ga,e(St).name),i(tr,`${(e(St).description===""?"aucune description USB":e(St).description)??""} + ${e(St).vid===""?"":` — VID ${e(St).vid} PID ${e(St).pid}`}`)}),et("click",ea,()=>t.draft.set("scale.options.port",e(St).name)),l(jt,Kt)}),l(wt,zt)};m(lt,wt=>{e(W).length>0&&wt(mt)})}p(wt=>i(Me,wt),[()=>e(W).length===0?"Aucun port série n’est visible depuis ce poste.":Le("port détecté","ports détectés",e(W).length)]),l(h,O)};m(Y,h=>{e(ue)&&h(oe)})}var Ae=r(Y,2);{var We=h=>{var O=fl(),Ce=H(O),Me=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(le).slice(0,A),mt=>mt.port,(mt,wt)=>{var zt=pl();let jt;var St=a(zt),Kt=a(St),ea=r(St,2),ga=a(ea);p(()=>{jt=Ht(zt,1,"svelte-1p4i0xm",null,jt,{refused:e(wt).refused}),i(Kt,e(wt).port),i(ga,e(wt).message)}),l(mt,zt)}),p(mt=>i(Me,mt),[()=>Le("port interrogé","ports interrogés",e(le).length)]),l(h,O)};m(Ae,h=>{e(le).length>0&&h(We)})}var Ye=r(Ae,2),Ke=r(a(Ye),2),M=a(Ke);{let h=u(()=>t.draft.text("scale.type")),O=u(()=>Ee("scale.type")),Ce=u(()=>Te("scale.type")),Me=u(()=>!e(z));_t(M,{label:"Protocole",path:"scale.type",get value(){return e(h)},hint:"Les valeurs acceptées apparaissent ici si l’enregistrement est refusé.",get fault(){return e(O)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("scale.type",lt)})}var te=r(M,2);{let h=u(()=>t.draft.text("scale.options.port")),O=u(()=>Ee("scale.options.port")),Ce=u(()=>ce("scale.options.port",e(W).map(lt=>lt.name))),Me=u(()=>!e(z));_t(te,{label:"Port série",path:"scale.options.port",get value(){return e(h)},hint:"Choisissez-le dans la liste détectée ci-dessus plutôt que de le taper : l’écoute permanente ne suit que des ports détectés.",get fault(){return e(O)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("scale.options.port",lt)})}var me=r(Ye,2),Ne=a(me),qe=a(Ne),$e=r(Ne,2);{var Pe=h=>{var O=gl(),Ce=a(O),Me=r(Ce);{var lt=mt=>{{let wt=u(()=>e(j)==="listen"),zt=u(()=>e(j)!=="");at(mt,{act:"listen",get label(){return e(De)},protected:!0,get busy(){return e(wt)},get disabled(){return e(zt)},onrun:()=>{L("listen",N)}})}};m(Me,mt=>{e(De)!==""&&mt(lt)})}p(()=>i(Ce,`${e(y)??""} `)),l(h,O)};m($e,h=>{(e(y)!==""||e(De)!=="")&&h(Pe)})}var Fe=r($e,2);{var Ge=h=>{var O=hl();rt(O,21,()=>e(P),fa,(Ce,Me)=>{var lt=ml(),mt=a(lt),wt=a(mt),zt=r(mt,2),jt=a(zt);p((St,Kt)=>{i(wt,St),i(jt,Kt)},[()=>V(e(Me)),()=>Ve(e(Me))]),l(Ce,lt)}),l(h,O)};m(Fe,h=>{e(P).length>0&&h(Ge)})}var ft=r(ze,2),ot=a(ft),ct=r(a(ot),2),it=r(a(ct),1,!0),dt=r(ot,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),At=a(Bt);{let h=u(()=>e(j)==="printers"),O=u(()=>e(j)!=="");at(At,{label:"Lister les files",get busy(){return e(h)},get disabled(){return e(O)},onrun:()=>{L("printers",ie)}})}var Vt=r(At,2);{let h=u(()=>e(j)==="discover"),O=u(()=>e(j)!=="");at(Vt,{label:"Rechercher l’imprimante",protected:!0,get busy(){return e(h)},get disabled(){return e(O)},onrun:()=>{L("discover",fe)}})}var kt=r(Vt,2);rt(kt,17,()=>Ie,h=>h.what,(h,O)=>{{let Ce=u(()=>`Auto-test : ${e(O).name}${e(j)===e(O).what?" — en cours…":""}`),Me=u(()=>e(j)!=="");at(h,{get act(){return e(O).what},get label(){return e(Ce)},protected:!0,get disabled(){return e(Me)},onrun:()=>{L(e(O).what,()=>xe(e(O).what))}})}});var Et=r(Bt,2);{var Wt=h=>{var O=_l(),Ce=H(O),Me=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(G).slice(0,A),mt=>mt.name,(mt,wt)=>{var zt=Na(),jt=a(zt),St=a(jt),Kt=r(jt,2),ea=a(Kt);p(()=>{i(St,e(wt).name),i(ea,`${e(wt).detail??""}${e(wt).default?" — file par défaut du système":""}`)}),et("click",jt,()=>t.draft.set("printer.options.queue",e(wt).name)),l(mt,zt)}),p(mt=>i(Me,mt),[()=>Le("file d’impression","files d’impression",e(G).length)]),l(h,O)};m(Et,h=>{e(G).length>0&&h(Wt)})}var tt=r(Et,2),qt=r(a(tt),2),$t=a(qt);{let h=u(()=>t.draft.text("printer.type")),O=u(()=>Ee("printer.type")),Ce=u(()=>Te("printer.type")),Me=u(()=>!e(z));_t($t,{label:"Driver",path:"printer.type",get value(){return e(h)},hint:"Gardez le driver raster : c’est celui que les postes en service utilisent.",get fault(){return e(O)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.type",lt)})}var Zt=r($t,2);{let h=u(()=>t.draft.text("printer.options.transport")),O=u(()=>Ee("printer.options.transport")),Ce=u(()=>Te("printer.options.transport")),Me=u(()=>!e(z));_t(Zt,{label:"Transport",path:"printer.options.transport",get value(){return e(h)},hint:"Local par défaut : une file Windows ou un fichier de périphérique de ce poste.",get fault(){return e(O)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.options.transport",lt)})}var ra=r(Zt,2);{let h=u(()=>t.draft.text("printer.options.queue")),O=u(()=>Ee("printer.options.queue")),Ce=u(()=>ce("printer.options.queue",e(G).map(lt=>lt.name))),Me=u(()=>!e(z));_t(ra,{label:"File ou chemin",path:"printer.options.queue",get value(){return e(h)},hint:"Choisissez-la dans la liste ci-dessus : une file mal orthographiée ne s’imprime pas.",get fault(){return e(O)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.options.queue",lt)})}p(h=>{Be(nt,"data-level",e(I).level),i(ut,e(I).word),i(je,e(I).detail),Ye.open=e(Oe),i(qe,e(pe)),Be(ct,"data-level",e(Q).level),i(it,e(Q).word),i(gt,e(Q).detail),i(Nt,h),tt.open=e(b)},[()=>q()]),l(n,ve),Ot()}Tt(["change","click"]);var yl=o(""),xl=o('L’export sera proposé quand le journal aura répondu.'),kl=o('L’export n’est pas proposé : ce poste n’a pas répondu à la lecture du journal.'),ql=o('Exporter en CSV'),Sl=o('

                                                                                                                      '),Ll=o('

                                                                                                                      '),El=o(`

                                                                                                                      Aucune trame brute n’a été enregistrée pour cette pesée : il n’y a + rien à rejouer.

                                                                                                                      `),zl=o(`

                                                                                                                      La trame repart dans le décodeur du poste EN SERVICE : le poids + affiché au client change, et rien ne le remet comme il était. C’est + ce qui fait d’un refus inexpliqué un test permanent, sans + déplacement au magasin et sans balance.

                                                                                                                      `,1),jl=o('

                                                                                                                      Produit
                                                                                                                      Référence
                                                                                                                      Vente
                                                                                                                      Brut / tare / net
                                                                                                                      Stabilité
                                                                                                                      Origine du poids
                                                                                                                      Résultat
                                                                                                                      Trame brute
                                                                                                                      '),Pl=o(' ',1),Tl=o('

                                                                                                                      QuandProduitNetCode-barresRésultatDuréeDétail
                                                                                                                      ',1),Cl=o('
                                                                                                                      ',1),Ol=o('

                                                                                                                      '),Rl=o(' '),Al=o(' '),Nl=o('
                                                                                                                    • '),$l=o('

                                                                                                                        ',1),Dl=o('
                                                                                                                        ');function Il(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=200,_=5e3,w=50,C=500,R=7,A={sent:"envoyée à l’imprimante",rejected:"refusée",failed:"en échec",reprint:"réimpression"},W=[{value:"",label:"toutes"},{value:"sent",label:"envoyées à l’imprimante"},{value:"rejected",label:"refusées"},{value:"failed",label:"en échec"},{value:"reprint",label:"réimpressions"}],G={scale:"balance",manual:"saisie manuelle",replay:"trame rejouée"},le={stable:"stable",unstable:"instable",unknown:"non déclarée par la balance",not_applicable:"sans objet — saisie manuelle"},we={by_weight:"au poids",by_unit:"à l’unité"},ke={debug:"mise au point",info:"information",warn:"avertissement",error:"erreur",critical:"critique"};let ue=F(Lt([])),j=F(Lt([])),de=F(""),J=F(null),ye=F(!1),B=F(!1),$=F("loading"),Z=F("loading"),D=F(""),z=F("");const re=u(()=>({limit:String(c),...e(de)===""?{}:{result:e(de)}})),S=u(()=>({...e(re),limit:String(_)})),y=u(()=>e(ue).find(N=>N.id===e(J))??null),P=u(()=>W.find(N=>N.value===e(de))?.label??""),I=u(()=>Oe(e(ue).length,c,"pesée","pesées",`L’export CSV en emporte jusqu’à ${he(_)}.`)),Q=u(()=>Oe(e(j).length,w,"ligne","lignes",`Le fichier de diagnostic emporte les ${he(C)} dernières.`));pe();async function pe(){v(J,null),v(B,!0),v($,"loading"),v(Z,"loading"),v(ue,[],!0),v(j,[],!0),v(D,""),v(z,"");try{const N=await s().load(()=>Mr(e(re)));v($,N===null?"unread":"read",!0),v(D,N===null?s().actionError:"",!0),v(ue,N??[],!0);const K=await s().load(()=>Vr({limit:String(w)}));v(Z,K===null?"unread":"read",!0),v(z,K===null?s().actionError:"",!0),v(j,K??[],!0)}finally{v(B,!1),s().actionError=""}}async function De(N){s().notice="",s().actionError="",v(ye,!0);try{const K=await s().protect(()=>Jr(N));K!==null&&(s().notice=K.message)}finally{v(ye,!1)}}function Oe(N,K,_e,ne,L){const X=N>1?ne:_e;return N{var _e=Cl(),ne=H(_e),L=r(a(ne),2);rt(L,21,()=>W,f=>f.value,(f,q)=>{var E=yl(),be=a(E),se={};p(()=>{i(be,e(q).label),se!==(se=e(q).value)&&(E.value=(E.__value=e(q).value)??"")}),l(f,E)});var X=r(L,2);at(X,{label:"Rafraîchir",get busy(){return e(B)},onrun:()=>{pe()}});var ie=r(X,2);{var fe=f=>{var q=xl();l(f,q)},xe=f=>{var q=kl();l(f,q)},He=f=>{var q=ql();p(E=>Be(q,"href",E),[()=>Br(e(S))]),l(f,q)};m(ie,f=>{e($)==="loading"?f(fe):e($)==="unread"?f(xe,1):f(He,-1)})}var Xe=r(ne,2);{var ge=f=>{var q=Sl(),E=a(q);p(be=>i(E,`L’export emporte le même filtre que le tableau, mais pas son plafond : il descend + jusqu’à ${be??""} pesées, en point-virgule et en UTF-8 — il s’ouvre + tel quel dans le tableur d’un Windows français. Il ne demande aucun mot de passe : la + lecture du journal n’en demande pas non plus, et le fichier de diagnostic emporte déjà + les deux cents dernières pesées.`),[()=>he(_)]),l(f,q)};m(Xe,f=>{e($)==="read"&&f(ge)})}var d=r(Xe,2);{var x=f=>{var q=Ll(),E=a(q);{var be=ce=>{var V=yt("Lecture du journal…");l(ce,V)},se=ce=>{var V=yt();p(()=>i(V,`Le journal n’a pas pu être lu : ${e(D)??""} Ce n’est pas « aucune pesée ».`)),l(ce,V)},Se=ce=>{var V=yt("Le journal ne contient aucune pesée.");l(ce,V)},Le=ce=>{var V=yt();p(()=>i(V,`Aucune pesée ne correspond au filtre « ${e(P)??""} ».`)),l(ce,V)};m(E,ce=>{e($)==="loading"?ce(be):e($)==="unread"?ce(se,1):e(de)===""?ce(Se,2):ce(Le,-1)})}l(f,q)},ee=f=>{var q=Tl(),E=H(q),be=a(E),se=r(E,2),Se=a(se),Le=r(a(Se));rt(Le,21,()=>e(ue),ce=>ce.id,(ce,V)=>{var Ue=Pl(),Ve=H(Ue);let Ee;var Te=a(Ve),Je=a(Te),ae=r(Te),Ie=a(ae),ve=r(ae),ze=a(ve),Re=r(ve),nt=a(Re),ut=r(Re),pt=a(ut),je=r(ut),st=a(je),Ze=r(je),bt=a(Ze),Rt=a(bt),Ut=r(Ve,2);{var k=Y=>{var oe=jl(),Ae=a(oe);Be(Ae,"colspan",R);var We=a(Ae),Ye=a(We),Ke=r(We,2),M=r(a(Ke),2),te=a(M),me=r(M,4),Ne=a(me),qe=r(me,4),$e=a(qe),Pe=r(qe,4),Fe=a(Pe),Ge=r(Pe,4),ft=a(Ge),ot=r(Ge,4),ct=a(ot),it=r(ot,4),dt=a(it),gt=r(it,4),xt=a(gt),Nt=a(xt),Bt=r(Ke,2);{var At=kt=>{var Et=El();l(kt,Et)},Vt=kt=>{var Et=zl(),Wt=H(Et),tt=a(Wt);at(tt,{kind:"destructive",label:"Rejouer cette trame",protected:!0,get busy(){return e(ye)},onrun:()=>{De(e(y).frame)}}),l(kt,Et)};m(Bt,kt=>{e(y).frame===""?kt(At):kt(Vt,-1)})}p((kt,Et,Wt,tt,qt,$t,Zt,ra,h)=>{Be(oe,"data-detail",e(V).id),i(Ye,`Pesée ${e(y).id??""}`),i(te,`${e(y).product_name??""} (${e(y).product_id??""})`),i(Ne,e(y).reference),i($e,`${kt??""} — + ${Et??""} + ${e(y).quantity>1?"unités":"unité"}`),i(Fe,`${Wt??""} g / ${tt??""} g / + ${qt??""} g`),i(ft,`${$t??""} — cadence + médiane ${Zt??""}`),i(ct,ra),i(dt,`${h??""}${e(y).detail===""?"":` — ${e(y).detail}`}`),i(Nt,e(y).frame===""?"aucune trame enregistrée":e(y).frame)},[()=>b(we,e(y).mode,"mode de vente inconnu"),()=>he(e(y).quantity),()=>he(e(y).gross_g),()=>he(e(y).tare_g),()=>he(e(y).net_g),()=>b(le,e(y).stability,"stabilité inconnue"),()=>ca(e(y).rate_ms),()=>b(G,e(y).source,"origine inconnue"),()=>b(A,e(y).result,"résultat inconnu")]),l(Y,oe)};m(Ut,Y=>{e(J)===e(V).id&&e(y)!==null&&Y(k)})}p((Y,oe,Ae,We)=>{Ee=Ht(Ve,1,"svelte-86o8oz",null,Ee,{open:e(J)===e(V).id}),i(Je,Y),i(Ie,e(V).product_name),i(ze,`${oe??""} g`),i(nt,e(V).barcode),i(pt,Ae),i(st,We),Be(bt,"aria-expanded",e(J)===e(V).id),i(Rt,e(J)===e(V).id?"fermer":"détail")},[()=>Mt(e(V).occurred_at),()=>he(e(V).net_g),()=>b(A,e(V).result,"résultat inconnu"),()=>ca(e(V).duration_ms)]),et("click",bt,()=>v(J,e(J)===e(V).id?null:e(V).id,!0)),l(ce,Ue)}),p(()=>i(be,e(I))),l(f,q)};m(d,f=>{e(ue).length===0?f(x):f(ee,-1)})}et("change",L,()=>{pe()}),_r(L,()=>e(de),f=>v(de,f)),l(N,_e)},$$slots:{default:!0}});var U=r(g,2);Qe(U,{title:"Journal technique",children:(N,K)=>{var _e=Dt(),ne=H(_e);{var L=ie=>{var fe=Ol(),xe=a(fe);{var He=d=>{var x=yt("Lecture du journal technique…");l(d,x)},Xe=d=>{var x=yt();p(()=>i(x,`Le journal technique n’a pas pu être lu : ${e(z)??""} Ce n’est pas « aucune + ligne ».`)),l(d,x)},ge=d=>{var x=yt("Aucune ligne technique.");l(d,x)};m(xe,d=>{e(Z)==="loading"?d(He):e(Z)==="unread"?d(Xe,1):d(ge,-1)})}l(ie,fe)},X=ie=>{var fe=$l(),xe=H(fe),He=a(xe),Xe=r(xe,2),ge=a(Xe);rt(ge,21,()=>e(j),d=>d.id,(d,x)=>{var ee=Nl(),f=a(ee),q=a(f),E=r(f,2),be=a(E),se=r(E,2),Se=a(se),Le=r(se,2);{var ce=Te=>{var Je=Rl(),ae=a(Je);p(()=>i(ae,e(x).code)),l(Te,Je)};m(Le,Te=>{e(x).code!==""&&Gt.showTechnicalNames&&Te(ce)})}var V=r(Le,2),Ue=a(V),Ve=r(V,2);{var Ee=Te=>{var Je=Al(),ae=a(Je);p(()=>i(ae,e(x).detail)),l(Te,Je)};m(Ve,Te=>{e(x).detail!==""&&Te(Ee)})}p((Te,Je,ae)=>{Be(ee,"data-level",e(x).level),i(q,Te),i(be,Je),i(Se,ae),i(Ue,e(x).message)},[()=>Mt(e(x).occurred_at),()=>b(ke,e(x).level,"niveau inconnu"),()=>Xa(e(x).source)]),l(d,ee)}),p(()=>i(He,e(Q))),l(ie,fe)};m(ne,ie=>{e(j).length===0?ie(L):ie(X,-1)})}l(N,_e)},$$slots:{default:!0}}),l(n,T),Ot()}Tt(["change","click"]);var Ul=o('

                                                                                                                        '),Fl=o('

                                                                                                                        '),Ml=o('

                                                                                                                        '),Bl=o('

                                                                                                                        '),Vl=o('

                                                                                                                        Lecture de la configuration en cours… les flèches attendent qu’elle soit arrivée.

                                                                                                                        '),Wl=o(`
                                                                                                                        Aperçu de l’étiquette telle qu’elle serait imprimée

                                                                                                                        Décalage horizontal

                                                                                                                        Décalage vertical

                                                                                                                        Le décalage ne descend pas sous zéro : le poste refuse un décalage négatif quel que + soit le gabarit. Le maximum, lui, dépend de la géométrie du gabarit ; il est annoncé + ici si l’enregistrement le dépasse.

                                                                                                                        Le symbole code-barres est volontairement tronqué : un symbole conforme n’entre pas + sur 40 × 25 mm avec les cinq champs texte. Ce n’est pas un défaut de rendu et il n’y + a rien à corriger.

                                                                                                                        Le détail chiffré du symbole — largeur de module, modules rendus, modules attendus — + n’est pas servi par ce poste. Cet écran n’affiche pas un chiffre qu’il aurait deviné.

                                                                                                                        `,1),Gl=o(`

                                                                                                                        Chaque appui sort une étiquette pour de bon : le mot de passe est demandé au moment + d’imprimer, et l’impression repart d’elle-même une fois la session ouverte.

                                                                                                                        `,1),Hl=o('
                                                                                                                        ');function Jl(n,t){Ct(t,!0);const s=vt(t,"admin",7),c="printer.template",_="printer.options.offset_x",w="printer.options.offset_y",C=0,R="L’aperçu n’a pas pu être rendu par le poste.",A="Le poste a rendu l’aperçu, mais le navigateur ne l’a pas affiché.",W=[{what:"alignment",label:"Imprimer la mire d’alignement"},{what:"ruler",label:"Imprimer la réglette"}];let G=F(1),le=F(!0),we=F(!1),ke=F(""),ue=F(null),j=F(""),de=null,J=!1;const ye=u(()=>t.draft.text(c)),B=u(()=>Fr(e(ye),e(le),e(we),e(G))),$=u(()=>t.draft.config!==null),Z=u(()=>t.draft.number(_)),D=u(()=>t.draft.number(w)),z=u(()=>U(_)),re=u(()=>U(w)),S=u(b),y=u(()=>s().busy||e(j)!=="");aa(()=>{if(t.draft.config===null)return;if(t.draft.dirty){de===null&&P();return}const L={x:t.draft.number(_),y:t.draft.number(w)};if(de!==null&&de.x===L.x&&de.y===L.y)return;const X=de===null;de=L,v(ue,L,!0),X||pe()});async function P(){if(!J){J=!0;try{const L=await qa(),X={x:I(L.config,_),y:I(L.config,w)};de=X,v(ue,X,!0)}catch{}finally{J=!1}}}function I(L,X){let ie=L;for(const fe of X.split(".")){if(ie===null||typeof ie!="object")return 0;ie=ie[fe]}return typeof ie=="number"?ie:0}function Q(L,X){const ie=t.draft.number(L)+X;ie=2?"s":""}`}function g(L,X){const ie=Number(X);X.trim()===""||Number.isNaN(ie)||t.draft.set(L,ie)}function U(L){return t.draft.faults.find(X=>X.field===L)?.message??""}async function N(L){if(!e(y)){v(j,L,!0),s().actionError="",s().notice="";try{const X=await s().protect(()=>Ha(L));if(X===null)return;s().notice=X.message,await s().refresh()}finally{v(j,"")}}}var K=Hl(),_e=a(K);Qe(_e,{title:"Aperçu de l’étiquette",note:"Le même moteur que l’impression (A2) : le décalage se voit parce qu’il est cuit dans le bitmap. L’image porte le gabarit en cours d’édition, mais le décalage ENREGISTRÉ — jamais celui que les flèches sont en train de régler.",children:(L,X)=>{var ie=Wl(),fe=H(ie);{var xe=je=>{var st=Ul(),Ze=a(st);p(()=>i(Ze,e(S))),l(je,st)};m(fe,je=>{e(S)!==""&&je(xe)})}var He=r(fe,2),Xe=a(He),ge=a(Xe),d=r(ge,2);{var x=je=>{var st=Fl(),Ze=a(st);p(()=>i(Ze,e(ke))),l(je,st)};m(d,je=>{e(ke)!==""&&je(x)})}var ee=r(Xe,2),f=a(ee),q=r(a(f),2),E=a(q),be=r(f,2),se=a(be);{let je=u(()=>!e($)||e(Z)<=C);at(se,{label:"← 1 dot",get disabled(){return e(je)},onrun:()=>Q(_,-1)})}var Se=r(se,2);{let je=u(()=>!e($));at(Se,{label:"1 dot →",get disabled(){return e(je)},onrun:()=>Q(_,1)})}var Le=r(be,2);{var ce=je=>{var st=Ml(),Ze=a(st);p(()=>i(Ze,e(z))),l(je,st)};m(Le,je=>{e(z)!==""&&je(ce)})}var V=r(Le,2),Ue=r(a(V),2),Ve=a(Ue),Ee=r(V,2),Te=a(Ee);{let je=u(()=>!e($)||e(D)<=C);at(Te,{label:"↑ 1 dot",get disabled(){return e(je)},onrun:()=>Q(w,-1)})}var Je=r(Te,2);{let je=u(()=>!e($));at(Je,{label:"1 dot ↓",get disabled(){return e(je)},onrun:()=>Q(w,1)})}var ae=r(Ee,2);{var Ie=je=>{var st=Bl(),Ze=a(st);p(()=>i(Ze,e(re))),l(je,st)};m(ae,je=>{e(re)!==""&&je(Ie)})}var ve=r(ae,4),ze=a(ve),Re=r(ve,2),nt=a(Re),ut=r(Re,2);{var pt=je=>{var st=Vl();l(je,st)};m(ut,je=>{e($)||je(pt)})}p((je,st)=>{Be(ge,"src",e(B)),i(E,je),i(Ve,st)},[()=>T(e(Z)),()=>T(e(D))]),Qt("load",ge,()=>v(ke,"")),Qt("error",ge,()=>{De(e(B))}),et("change",ze,()=>pe()),za(ze,()=>e(le),je=>v(le,je)),et("change",nt,()=>pe()),za(nt,()=>e(we),je=>v(we,je)),l(L,ie)},$$slots:{default:!0}});var ne=r(_e,2);Qe(ne,{title:"Gabarit et impression",children:(L,X)=>{var ie=Gl(),fe=H(ie);{let d=u(()=>U(c)),x=u(()=>!e($));_t(fe,{label:"Gabarit",path:c,get value(){return e(ye)},hint:"Le gabarit reproduit à l’identique s’appelle weighing_identical (A1).",get fault(){return e(d)},get disabled(){return e(x)},onchange:ee=>{t.draft.set(c,ee),pe()}})}var xe=r(fe,2);{let d=u(()=>t.draft.text("printer.options.darkness")),x=u(()=>U("printer.options.darkness")),ee=u(()=>!e($));_t(xe,{label:"Noircissement",path:"printer.options.darkness",kind:"number",get value(){return e(d)},hint:"Trop bas, l’étiquette pâlit au soleil ; trop haut, elle bave et le scanner refuse.",get fault(){return e(x)},get disabled(){return e(ee)},onchange:f=>g("printer.options.darkness",f)})}var He=r(xe,2);{let d=u(()=>t.draft.text("printer.options.speed")),x=u(()=>U("printer.options.speed")),ee=u(()=>!e($));_t(He,{label:"Vitesse",path:"printer.options.speed",kind:"number",get value(){return e(d)},get fault(){return e(x)},get disabled(){return e(ee)},onchange:f=>g("printer.options.speed",f)})}var Xe=r(He,2);{let d=u(()=>t.draft.text("printer.options.copies")),x=u(()=>U("printer.options.copies")),ee=u(()=>!e($));_t(Xe,{label:"Exemplaires",path:"printer.options.copies",kind:"number",get value(){return e(d)},hint:"Un client repart avec une étiquette : deux exemplaires se justifient, pas se devinent.",get fault(){return e(x)},get disabled(){return e(ee)},onchange:f=>g("printer.options.copies",f)})}var ge=r(Xe,2);rt(ge,21,()=>W,d=>d.what,(d,x)=>{{let ee=u(()=>e(j)===e(x).what);at(d,{get act(){return e(x).what},get label(){return e(x).label},protected:!0,get busy(){return e(ee)},get disabled(){return e(y)},onrun:()=>{N(e(x).what)}})}}),l(L,ie)},$$slots:{default:!0}}),l(n,K),Ot()}Tt(["change"]);var Kl=o(" "),Xl=o(' '),Yl=o(''),Ql=o('

                                                                                                                        Aucun tarif déclaré dans la configuration lue.

                                                                                                                        '),Zl=o('Prix du catalogue Odoo — pas de remise'),$a=o(' '),ei=o(' % ',1),ti=o(' '),ai=o(`

                                                                                                                        CodeLibelléAbrégéRemiseOrdre

                                                                                                                        Un champ vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la + retrouve dès qu’on quitte le champ. Une remise effacée serait le plein tarif pour + tous les adhérents.

                                                                                                                        `,1),ri=o(" ",1),ni=o(`

                                                                                                                        Les deux arrondis sont distincts parce qu’ils tombent à des endroits différents du + calcul : arrondir le prix au kilo puis le multiplier, ou multiplier puis arrondir, ne + donne pas le même centime sur une étiquette. Le poste applique l’arrondi commercial, + et l’écart se voit au centime près sur la même pesée.

                                                                                                                        `,1),si=o('

                                                                                                                        Rien ne s’affiche au client : c’est une information.

                                                                                                                        '),li=o('

                                                                                                                        '),ii=o('
                                                                                                                        '),oi=o('

                                                                                                                        '),ui=o('
                                                                                                                      • '),ci=o(`

                                                                                                                        Aucun verdict en ce moment : ces lignes portent sur la pesée EN COURS, et le poste + est au repos. Elles apparaissent le temps d’un cycle, quand un client pose son sac + et touche une tuile.

                                                                                                                        `),di=o(' '),vi=o(' ',1),pi=o('
                                                                                                                      • '),fi=o('

                                                                                                                          ',1),gi=o(`

                                                                                                                          L’ordre compte : le premier verdict bloquant décide de ce que le client lit. Les + garde-fous 1 à 7 portent sur l’état de la balance, c’est-à-dire le poids brut ; les + garde-fous 8 à 14 portent sur la vente, c’est-à-dire le net. Le code en gris est + celui du journal, celui qu’on lit au téléphone.

                                                                                                                            Un seuil vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la retrouve + dès qu’on quitte le champ. Pour changer un seuil, on tape l’autre valeur.

                                                                                                                            Ce que les garde-fous disent de la pesée en cours

                                                                                                                            `,1),mi=o(`

                                                                                                                            Le plan de numérotation n’est PAS ici, et c’est tout l’intérêt : préfixes, largeur de + la référence, largeur de la charge utile, décimales et mode de vente sont une + constante du binaire, indexée par préfixe et vérifiée au démarrage. + Un champ qui change le SENS du code lu par la caisse n’est pas un réglage, c’est un + contrat externe : il change avec une version du binaire, relue et testée, jamais + depuis l’écran d’un poste.

                                                                                                                            `,1),hi=o('

                                                                                                                            Aucune dérogation : la limite générale s’applique à tous les produits.

                                                                                                                            '),_i=o(`

                                                                                                                            Les noms de produits n’ont pas pu être lus : le catalogue en service n’a pas + répondu. Les identifiants Odoo restent affichés.

                                                                                                                            `),bi=o(`Produit retiré : le garde-fou 14 refuse le produit avant que cette + dérogation ait un sens.`),wi=o('
                                                                                                                          1. '),yi=o('

                                                                                                                              ',1),xi=o('
                                                                                                                              ');function ki(n,t){Ct(t,!0);const s=(g,U=sa,N=sa,K=sa)=>{var _e=Yl(),ne=a(_e),L=r(ne,2),X=a(L),ie=a(X),fe=r(X,2);{var xe=ge=>{var d=Kl(),x=a(d);p(()=>i(x,U())),l(ge,d)};m(fe,ge=>{Gt.showTechnicalNames&&ge(xe)})}var He=r(fe,2);{var Xe=ge=>{var d=Xl(),x=a(d);p(()=>i(x,K())),l(ge,d)};m(He,ge=>{K()!==""&&ge(Xe)})}p((ge,d)=>{Be(_e,"data-flag",U()),Be(_e,"data-on",ge),ua(ne,d),i(ie,N())},[()=>String(t.draft.flag(U())),()=>t.draft.flag(U())]),et("change",ne,ge=>t.draft.set(U(),ge.currentTarget.checked)),l(g,_e)},c=20,_=u(()=>le(t.draft)),w=u(()=>t.health.state.diagnostics),C=u(()=>t.health.decisions.filter(g=>g.min_weight_g!==null)),R=u(()=>e(C).filter(g=>!g.offered)),A=u(()=>e(C).slice(0,c));let W=F(Lt({})),G=F("loading");B();function le(g){const U=g.value("pricing.tiers");return Array.isArray(U)?U.map(N=>{const K=N??{},_e=K.discount_percent;return{code:String(K.code??""),label:String(K.label??""),abbrev:String(K.abbrev??""),written:_e===void 0?null:String(_e),discount:we(_e)?_e:null,rank:Number(K.rank??0)}}):[]}function we(g){return typeof g!="number"||!Number.isFinite(g)||g<0||g>100?!1:Math.abs(g*10-Math.round(g*10))<1e-9}const ke=u(()=>String(t.draft.value("pricing.reference_code")??""));function ue(g){return g===null?"":String(g).replace(".",",")}function j(g){const U=1e3-Math.round(g*10);return`${String(Math.trunc(U/100))},${String(U%100).padStart(2,"0")}`}function de(g,U){const N=Number(U);U.trim()===""||Number.isNaN(N)||t.draft.set(g,N)}function J(g,U){const N=U.trim().replace(",",".");if(!/^\d{1,3}(\.\d)?$/u.test(N))return;const K=Number(N);K>100||t.draft.set(g,K)}function ye(g,U){!(g instanceof HTMLInputElement)||g.value===U||(g.value=U)}async function B(){try{const g=await Ba();v(W,Object.fromEntries(g.products.map(U=>[U.id,U.name])),!0),v(G,"read")}catch{v(G,"unread")}}function $(g){const U=e(W)[g];return U!==void 0?U:e(G)==="loading"?"Lecture du nom…":e(G)==="read"?"Produit absent du catalogue en service":"Nom non lu"}function Z(g){return g<=1?`${String(g)} tarif déclaré`:`${String(g)} tarifs déclarés`}const D=u(()=>`${he(e(w).length)} ${e(w).length>1?"verdicts":"verdict"} sur les quatorze garde-fous.`),z=u(()=>`${he(e(A).length)} ${e(A).length>1?"lignes affichées":"ligne affichée"} sur ${he(e(C).length)} `+(e(R).length===0?`${e(C).length>1?"dérogations en vigueur":"dérogation en vigueur"}.`:`${e(C).length>1?"dérogations enregistrées":"dérogation enregistrée"}, dont ${he(e(R).length)} sur un produit retiré, sans effet : le garde-fou 14 refuse le produit avant que le 8 ait un sens.`)+(e(C).length>e(A).length?" Les autres se lisent produit par produit depuis l’onglet Catalogue.":""));function re(g){return`plancher ${he(g)} g : refusé à ${he(g)} g et en dessous`}const S=[{rank:1,code:"OVERLOAD",label:"Surcharge",when:"La balance annonce elle-même OL, ou le poids brut dépasse la capacité.",severity:"Bloquant",blocking:!0,message:"La balance est en surcharge. Retirez votre article.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la capacité, réglée au garde-fou 9 sous « ${Ft("limits.max_weight_g")} ».`},{rank:2,code:"MEASUREMENT_EXPIRED",label:"Poids périmé",when:"La mesure est plus vieille que la péremption, dans les deux modes de stabilité.",severity:"Bloquant",blocking:!0,message:"Poids indisponible. Patientez ou appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil à régler : le poste calcule lui-même à partir du rythme de la balance."},{rank:3,code:"BASKET_MISSING",label:"Panier absent",when:"Le poids brut tombe dans la fenêtre négative du panier : il a été soulevé.",severity:"Bloquant",blocking:!0,message:"Le panier n'est pas sur la balance. Reposez-le.",thresholds:[{path:"limits.basket_min_g",label:"Bas de la fenêtre du panier",hint:"En grammes, NÉGATIF : c’est le poids du panier que la balance a perdu."},{path:"limits.basket_max_g",label:"Haut de la fenêtre du panier",hint:"Négatif lui aussi, et plus proche de zéro que le bas."}],switchPath:"limits.basket_check_enabled",switchLabel:"Ce poste travaille avec un panier taré",note:"La règle s’active ou non, en bloc : il n’y a pas de demi-mesure à régler."},{rank:4,code:"SCALE_EMPTY",label:"Plateau vide",when:"Le poids brut ne sort pas de la bande « il n’y a rien sur le plateau ».",severity:"Bloquant — un filet, hors parcours nominal",blocking:!0,message:"Posez votre produit.",thresholds:[{path:"limits.empty_max_g",label:"Plateau considéré vide",hint:"En dessous, le poste considère qu’il n’y a rien sur le plateau."}],switchPath:"",switchLabel:"",note:"Toucher une tuile sur un plateau vide ARME la sélection au lieu d’être refusé : la règle reste évaluée pour la saisie manuelle et les chemins dérivés."},{rank:5,code:"TARE_REQUIRED",label:"Remise à zéro nécessaire",when:"Le brut est sous la bande du plateau vide, et hors de la fenêtre du panier.",severity:"Bloquant",blocking:!0,message:"La balance doit être remise à zéro.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la valeur négative de celui du garde-fou 4, « ${Ft("limits.empty_max_g")} ».`},{rank:6,code:"WEIGHT_UNSTABLE",label:"Pesée instable",when:"La trame déclare la mesure instable.",severity:"Information par défaut (A3)",blocking:!1,message:"Pesée en cours…",thresholds:[],switchPath:"",switchLabel:"",note:"La sévérité suit l’exigence de stabilité : elle passe à Bloquant quand celle-ci est réglée sur « blocking ». L’impression n’est jamais bloquée par défaut."},{rank:7,code:"TARE_INVALID",label:"Emballage incohérent",when:"Une tare a été saisie, et elle atteint la pesée ou dépasse le maximum.",severity:"Bloquant",blocking:!0,message:"Le poids de l'emballage est supérieur ou égal à la pesée.",thresholds:[{path:"limits.max_tare_g",label:"Tare maximum",hint:"Une tare plus lourde que le maximum est une faute de frappe."}],switchPath:"",switchLabel:"",note:""},{rank:8,code:"WEIGHT_TOO_LOW",label:"Poids trop faible",when:"Vente au poids : le NET est strictement positif et ne dépasse pas le plancher.",severity:"Bloquant",blocking:!0,message:"La balance doit être retarée, ou l'emballage est trop lourd.",thresholds:[{path:"limits.min_weight_g",label:"Poids minimum",hint:"Une dérogation par produit existe, dans l’onglet Catalogue."}],switchPath:"",switchLabel:"",note:""},{rank:9,code:"WEIGHT_TOO_HIGH",label:"Poids trop élevé",when:"Le NET dépasse la capacité — strictement, pour que la capacité reste atteignable.",severity:"Bloquant",blocking:!0,message:"{{.Weight}} kg, ça paraît un peu lourd !",thresholds:[{path:"limits.max_weight_g",label:"Poids maximum",hint:"C’est la capacité du champ NNDDD du code-barres, pas un seuil de vraisemblance."}],switchPath:"",switchLabel:"",note:""},{rank:10,code:"UNITS_OUT_OF_RANGE",label:"Nombre d’unités hors plage",when:"Vente à l’unité : la quantité sort de la plage.",severity:"Bloquant",blocking:!0,message:"{{.Quantity}} unités, ça paraît un peu beaucoup !",thresholds:[{path:"limits.min_units",label:"Unités minimum",hint:""},{path:"limits.max_units",label:"Unités maximum",hint:""}],switchPath:"",switchLabel:"",note:""},{rank:11,code:"AMOUNT_OUT_OF_CAPACITY",label:"Montant hors capacité du code-barres",when:"La charge utile encode un PRIX, et il dépasse ce que le champ peut porter.",severity:"Bloquant",blocking:!0,message:"Prix trop élevé pour le code-barres.",thresholds:[{path:"limits.max_amount_cents",label:"Montant maximum",hint:"En centimes. Aucun préfixe du plan livré n’encode un prix : la règle est éprouvée sans qu’aucun produit puisse l’atteindre."}],switchPath:"",switchLabel:"",note:""},{rank:12,code:"ZERO_PRICE",label:"Prix nul",when:"Le montant du tarif imprimé en grand vaut zéro.",severity:"Bloquant",blocking:!0,message:"Prix nul. Appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : un produit à 0 € est une anomalie sans nuance."},{rank:13,code:"LIGHT_PRODUCT_ALLOWED",label:"Produit léger autorisé",when:"Le garde-fou 8 n’a pas déclenché grâce à la dérogation du produit.",severity:"Information",blocking:!1,message:"",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil général : c’est la dérogation par produit, listée plus bas et posée depuis l’onglet Catalogue. Rien ne s’affiche au client ; l’id du produit est journalisé."},{rank:14,code:"PRODUCT_WITHDRAWN",label:"Produit retiré",when:"Quelqu’un a décidé de ne plus proposer ce produit.",severity:"Bloquant",blocking:!0,message:"Ce produit n'est pas disponible.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : c’est une décision humaine, prise depuis l’onglet Catalogue. Aucune règle d’import ne peut la déduire."}];function y(g){return S.find(U=>U.code===g)?.label??"Garde-fou inconnu de cet écran"}const P=["{{.Weight}}","{{.Quantity}}"];function I(g){return g==="blocking"?"Bloquant":g==="info"?"Information":"Sévérité inconnue de cet écran"}var Q=xi(),pe=a(Q);Qe(pe,{title:"Grille de tarifs",note:"Un second tarif n’est pas une case à cocher : c’est une ligne de plus dans cette grille.",children:(g,U)=>{var N=ri(),K=H(N);{var _e=ie=>{var fe=Ql();l(ie,fe)},ne=ie=>{var fe=ai(),xe=H(fe),He=a(xe),Xe=r(xe,2),ge=a(Xe),d=r(a(ge));rt(d,21,()=>e(_),fa,(x,ee,f)=>{var q=ti(),E=a(q),be=a(E),se=r(E),Se=a(se);Be(Se,"aria-label",`Libellé du tarif ${f+1}`);var Le=r(se),ce=a(Le);Be(ce,"aria-label",`Abrégé du tarif ${f+1}`);var V=r(Le),Ue=a(V);{var Ve=ve=>{var ze=Zl();l(ve,ze)},Ee=ve=>{var ze=$a(),Re=a(ze);p(()=>i(Re,`${e(ee).written??""} — le tarif de référence est le prix du catalogue : il + ne peut pas porter de remise, et l’enregistrement la refusera.`)),l(ve,ze)},Te=ve=>{var ze=$a(),Re=a(ze);p(()=>i(Re,`${e(ee).written??""} — une remise s’écrit au dixième de point ; celle-ci se + change dans le fichier de configuration.`)),l(ve,ze)},Je=ve=>{var ze=ei(),Re=H(ze);Be(Re,"aria-label",`Remise du tarif ${f+1}`);var nt=r(Re,2),ut=a(nt);p((pt,je)=>{na(Re,pt),i(ut,`un produit à 10,00 €/kg s’affiche ${je??""} €/kg`)},[()=>ue(e(ee).discount??0),()=>j(e(ee).discount??0)]),et("input",Re,pt=>J(`pricing.tiers.${String(f)}.discount_percent`,pt.currentTarget.value)),et("focusout",Re,pt=>ye(pt.currentTarget,ue(e(ee).discount??0))),l(ve,ze)};m(Ue,ve=>{e(ee).code===e(ke)&&e(ee).written===null?ve(Ve):e(ee).code===e(ke)?ve(Ee,1):e(ee).written!==null&&e(ee).discount===null?ve(Te,2):ve(Je,-1)})}var ae=r(V),Ie=a(ae);p(()=>{i(be,e(ee).code),na(Se,e(ee).label),na(ce,e(ee).abbrev),i(Ie,e(ee).rank)}),et("input",Se,ve=>t.draft.set(`pricing.tiers.${String(f)}.label`,ve.currentTarget.value)),et("input",ce,ve=>t.draft.set(`pricing.tiers.${String(f)}.abbrev`,ve.currentTarget.value)),l(x,q)}),p(x=>i(He,`${x??""}.`),[()=>Z(e(_).length)]),l(ie,fe)};m(K,ie=>{e(_).length===0?ie(_e):ie(ne,-1)})}var L=r(K,2);{let ie=u(()=>t.draft.text("pricing.primary_code"));_t(L,{label:"Tarif imprimé en grand",path:"pricing.primary_code",get value(){return e(ie)},hint:"Le prix que le client lit sur l’étiquette (A7).",onchange:fe=>t.draft.set("pricing.primary_code",fe)})}var X=r(L,2);{let ie=u(()=>t.draft.text("pricing.reference_code"));_t(X,{label:"Tarif qui serait encodé si le code-barres portait un prix",path:"pricing.reference_code",get value(){return e(ie)},hint:"Le plan livré ne porte pas de prix, mais un poids ou un nombre d’unités : la caisse retrouve le prix par la référence, dans Odoo, et ne doit jamais sous-facturer.",onchange:fe=>t.draft.set("pricing.reference_code",fe)})}l(g,N)},$$slots:{default:!0}});var De=r(pe,2);Qe(De,{title:"Les deux arrondis",children:(g,U)=>{var N=ni(),K=H(N);{let ne=u(()=>t.draft.text("pricing.unit_price_rounding"));_t(K,{label:"Arrondi du prix unitaire dérivé",path:"pricing.unit_price_rounding",get value(){return e(ne)},hint:"Il s’applique au prix au kilo calculé par la remise.",onchange:L=>t.draft.set("pricing.unit_price_rounding",L)})}var _e=r(K,2);{let ne=u(()=>t.draft.text("pricing.amount_rounding"));_t(_e,{label:"Arrondi du montant",path:"pricing.amount_rounding",get value(){return e(ne)},hint:"Il s’applique au montant de l’étiquette.",onchange:L=>t.draft.set("pricing.amount_rounding",L)})}l(g,N)},$$slots:{default:!0}});var Oe=r(De,2);Qe(Oe,{title:"Les quatorze garde-fous, dans l’ordre d’évaluation",note:"Le seuil se modifie ici. La sévérité ne se règle pas : elle dit si le poste refuse ou avertit, et cela ne dépend pas du magasin. Le message affiché au client n’est pas encore modifiable depuis cet écran.",children:(g,U)=>{var N=gi(),K=r(H(N),2);rt(K,21,()=>S,fe=>fe.code,(fe,xe)=>{var He=ui(),Xe=a(He),ge=a(Xe),d=a(ge),x=r(ge,2),ee=a(x),f=r(x,2),q=a(f),E=r(f,2),be=a(E),se=r(Xe,2),Se=a(se),Le=r(se,2);{var ce=ae=>{var Ie=si();l(ae,Ie)},V=ae=>{var Ie=li(),ve=a(Ie);p(()=>i(ve,`« ${e(xe).message??""} »`)),l(ae,Ie)};m(Le,ae=>{e(xe).message===""?ae(ce):ae(V,-1)})}var Ue=r(Le,2);{var Ve=ae=>{s(ae,()=>e(xe).switchPath,()=>e(xe).switchLabel,()=>"")};m(Ue,ae=>{e(xe).switchPath!==""&&ae(Ve)})}var Ee=r(Ue,2);rt(Ee,17,()=>e(xe).thresholds,ae=>ae.path,(ae,Ie)=>{var ve=ii(),ze=a(ve);{let Re=u(()=>t.draft.text(e(Ie).path));_t(ze,{get label(){return e(Ie).label},get path(){return e(Ie).path},kind:"number",get value(){return e(Re)},get hint(){return e(Ie).hint},onchange:nt=>de(e(Ie).path,nt)})}et("focusout",ve,Re=>ye(Re.target,t.draft.text(e(Ie).path))),l(ae,ve)});var Te=r(Ee,2);{var Je=ae=>{var Ie=oi(),ve=a(Ie);p(()=>i(ve,e(xe).note)),l(ae,Ie)};m(Te,ae=>{e(xe).note!==""&&ae(Je)})}p(ae=>{Be(He,"data-code",e(xe).code),i(d,e(xe).rank),i(ee,e(xe).label),i(q,e(xe).code),Be(E,"data-blocking",ae),i(be,e(xe).severity),i(Se,e(xe).when)},[()=>String(e(xe).blocking)]),l(fe,He)});var _e=r(K,4),ne=a(_e),L=r(_e,4);{var X=fe=>{var xe=ci();l(fe,xe)},ie=fe=>{var xe=fi(),He=H(xe),Xe=a(He),ge=r(He,2);rt(ge,21,()=>e(w),d=>d.code,(d,x)=>{var ee=pi(),f=a(ee),q=a(f),E=r(f,2),be=a(E),se=r(E,2),Se=a(se),Le=r(se,2);{var ce=Ve=>{var Ee=di(),Te=a(Ee);p(()=>i(Te,`« ${e(x).message??""} »`)),l(Ve,Ee)};m(Le,Ve=>{e(x).message!==""&&Ve(ce)})}var V=r(Le,2);{var Ue=Ve=>{var Ee=vi(),Te=H(Ee),Je=a(Te),ae=r(Te,2),Ie=a(ae);p(ve=>{i(Je,ve),i(Ie,e(x).product_id)},[()=>$(e(x).product_id)]),l(Ve,Ee)};m(V,Ve=>{e(x).product_id!==""&&Ve(Ue)})}p((Ve,Ee,Te)=>{Be(ee,"data-blocking",Ve),i(q,Ee),i(be,e(x).code),i(Se,Te)},[()=>String(e(x).blocking),()=>y(e(x).code),()=>I(e(x).severity)]),l(d,ee)}),p(d=>{Be(He,"data-verdicts",d),i(Xe,e(D))},[()=>String(e(w).length)]),l(fe,xe)};m(L,fe=>{e(w).length===0?fe(X):fe(ie,-1)})}p(fe=>i(ne,`Les marqueurs ${fe??""} sont remplacés par les valeurs de la pesée au + moment où le message s’affiche.`),[()=>P.join(" et ")]),l(g,N)},$$slots:{default:!0}});var b=r(Oe,2);Qe(b,{title:"Code-barres",note:"Un seul réglage, et c’est voulu : le reste du plan de numérotation n’est pas de la configuration.",children:(g,U)=>{var N=mi(),K=H(N);s(K,()=>"barcode.verify_reference_check_digit",()=>"Refuser une référence dont la clé de contrôle est fausse",()=>"Décoché, le poste recalcule une clé juste sur une référence fausse, en silence — et la caisse encaisse un autre article."),l(g,N)},$$slots:{default:!0}});var T=r(b,2);Qe(T,{title:"Dérogations de poids minimum",note:"En lecture ici ; elles se modifient depuis l’onglet Catalogue, là où se trouve le produit.",children:(g,U)=>{var N=Dt(),K=H(N);{var _e=L=>{var X=hi();l(L,X)},ne=L=>{var X=yi(),ie=H(X),fe=a(ie),xe=r(ie,2);{var He=ge=>{var d=_i();l(ge,d)};m(xe,ge=>{e(G)==="unread"&&ge(He)})}var Xe=r(xe,2);rt(Xe,21,()=>e(A),ge=>ge.product_id,(ge,d)=>{var x=wi(),ee=a(x),f=a(ee),q=r(ee,2),E=a(q),be=r(q,2),se=a(be),Se=r(be,2),Le=a(Se),ce=r(Se,2),V=a(ce),Ue=r(ce,2);{var Ve=Ee=>{var Te=bi();l(Ee,Te)};m(Ue,Ee=>{e(d).offered||Ee(Ve)})}p((Ee,Te,Je,ae)=>{Be(x,"data-withdrawn",Ee),i(f,Te),i(E,e(d).product_id),i(se,Je),i(Le,e(d).reason),i(V,`${ae??""}, ${e(d).decided_by??""}`)},[()=>String(!e(d).offered),()=>$(e(d).product_id),()=>re(e(d).min_weight_g??0),()=>da(e(d).decided_at)]),l(ge,x)}),p(()=>i(fe,e(z))),l(L,X)};m(K,L=>{e(C).length===0?L(_e):L(ne,-1)})}l(g,N)},$$slots:{default:!0}}),l(n,Q),Ot()}Tt(["change","input","focusout"]);function qi(n,t){const s=[];for(const c of[...ya(n),...ya(t)]){if(s.some(C=>C.path===c))continue;const _=Da(va(n,c)),w=Da(va(t,c));_!==w&&s.push({path:c,before:_,after:w})}return s}function va(n,t){let s=n;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}function ya(n,t=""){if(n===null||typeof n!="object"||Array.isArray(n))return t===""?[]:[t];const s=[];for(const[c,_]of Object.entries(n))s.push(...ya(_,t===""?c:`${t}.${c}`));return s}function Da(n){return n===void 0?"—":n===null?"vide":typeof n=="boolean"?n?"oui":"non":typeof n=="object"?JSON.stringify(n):String(n)}var Si=o('
                                                                                                                              Empreinte de la configuration en service
                                                                                                                              Version du binaire
                                                                                                                              Répertoire de données
                                                                                                                              Espace disque
                                                                                                                              ',1),Li=o('

                                                                                                                              '),Ia=o(" "),Ei=o(' '),zi=o("
                                                                                                                            • "),ji=o(`

                                                                                                                                Recopier reste possible : les valeurs entrent dans le brouillon, où elles se + corrigent champ par champ avant l’enregistrement.

                                                                                                                                `),Pi=o(`

                                                                                                                                Ce fichier n’a été comparé à rien : la configuration en service n’a pas pu être + lue. Ni « identique », ni « n champs changent » — ce que ce fichier changerait + sur ce poste reste inconnu.

                                                                                                                                `),Ti=o(`

                                                                                                                                Ce fichier décrit la même configuration que celle en service : il n’y a rien à + recopier. C’est ce qu’on veut lire à la fin d’un clonage. Deux champs ne sont pas + comparés : la date du dernier enregistrement, que chaque poste écrit lui-même, et + le mot de passe du catalogue, qu’aucun des deux ne porte en clair.

                                                                                                                                `),Ci=o(' '),Oi=o('

                                                                                                                                '),Ri=o(`

                                                                                                                                ChampEn serviceDans le fichier

                                                                                                                                Recopier n’applique rien : les valeurs entrent dans le brouillon, et c’est + « Enregistrer » qui les met en service.

                                                                                                                                `,1),Ai=o('

                                                                                                                                ',1),Ni=o(`

                                                                                                                                L’export emporte encore l’empreinte du mot de passe : c’est la seule lecture que le + poste garde derrière la clé. L’import, lui, est lu PAR LE POSTE, qui écarte les deux + secrets et le numéro de poste avant de dire ce qui changerait.

                                                                                                                                `,1),$i=o('

                                                                                                                                Lecture des versions enregistrées…

                                                                                                                                '),Di=o(`

                                                                                                                                Les versions enregistrées n’ont pas pu être lues : cette liste ne dit donc rien de + ce que ce poste garde. Ce n’est pas « aucune version ».

                                                                                                                                `),Ii=o('

                                                                                                                                Aucune version enregistrée : ce poste n’a jamais été reconfiguré.

                                                                                                                                '),Ui=o('
                                                                                                                              • '),Fi=o(`

                                                                                                                                  Remettre une version en service remplace la configuration du poste sur-le-champ, et + ce qui n’a pas été enregistré est perdu : c’est le seul geste de cette page qui + change le poste tout de suite, et le seul qui garde ses 72 px.

                                                                                                                                  `,1),Mi=o('
                                                                                                                                  ');function Bi(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=40,_=20,w=5,C=new Set(["modified_at","catalog.options.password"]),R=[{path:"station.name",name:"le nom du poste"},{path:"network.listen",name:"l’adresse d’écoute"},{path:"scale.options",name:"les réglages de la balance"},{path:"printer.options",name:"les réglages de l’imprimante"},{path:"catalog.options",name:"la source du catalogue, compte compris"}];let A=F(Lt([])),W=F("reading"),G=F(null),le=F(""),we=F(null),ke=F(""),ue=F(Lt([])),j=F(""),de="";const J=u(()=>s().busy||e(j)!==""),ye=u(()=>I(e(G),e(we))),B=u(()=>e(G)!==null&&e(we)!==null),$=u(()=>Q(e(G),e(we))),Z=u(()=>e(ye).slice(0,c)),D=u(()=>e(ue).slice(0,_)),z=u(()=>e(A).slice(0,w)),re=u(()=>Xe(e(Z).length,e(ye).length,"champ qui change","champs qui changent")),S=u(()=>Xe(e(D).length,e(ue).length,"contrôle refuse une clé","contrôles refusent une clé")),y=u(()=>Xe(e(z).length,e(A).length,"version enregistrée","versions enregistrées")),P=u(()=>e(ye).length>1?`Recopier ces ${he(e(ye).length)} champs dans le brouillon`:"Recopier ce champ dans le brouillon");Oe();function I(f,q){return f===null||q===null?[]:qi(f,q).filter(E=>!C.has(E.path))}function Q(f,q){if(f===null||q===null)return[];const E=f,be=q;return R.filter(se=>pe(be,se.path)&&!pe(E,se.path))}function pe(f,q){const E=va(f,q);return E==null||E===""?!0:typeof E!="object"||Array.isArray(E)?!1:Object.keys(E).length===0}function De(f){return f.length<2?f.join(""):`${f.slice(0,-1).join(", ")} et ${f[f.length-1]??""}`}aa(()=>{const f=t.health.config_fingerprint;f!==de&&(de=f,b())});async function Oe(){const f=await s().load(Ar);if(f===null){v(W,"unreadable");return}v(A,f,!0),v(W,"read")}async function b(){try{v(G,(await qa()).config,!0),v(le,"")}catch(f){v(G,null),v(le,f instanceof Error?f.message:"Le poste n’a pas répondu.",!0)}}async function T(f){v(j,f?"export-all":"export-clone",!0),s().notice="",s().actionError="";try{const q=await s().protect(()=>g(f));if(q===null)return;K(q.name,q.blob),s().notice=`${q.name} est remis au navigateur : c’est lui qui l’enregistre, voyez ses téléchargements.`}finally{v(j,"")}}async function g(f){const E=await fetch(`/admin/api/config/export?hardware=${f?"1":"0"}`,{headers:{accept:"application/json"}});if(!E.ok)throw new It(E.status,fe(await E.text(),"L’export"));return{name:U(f),blob:await E.blob()}}function U(f){const q=f?"":"-sans-materiel";return`config-poste${String(t.health.station)}${q}-${N()}.json`}function N(){const f=new Date,q=String(f.getMonth()+1).padStart(2,"0"),E=String(f.getDate()).padStart(2,"0");return`${String(f.getFullYear())}-${q}-${E}`}function K(f,q){const E=URL.createObjectURL(q),be=document.createElement("a");be.href=E,be.download=f,document.body.appendChild(be),be.click(),be.remove(),setTimeout(()=>{URL.revokeObjectURL(E)},0)}async function _e(f){const q=await fetch("/admin/api/config/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)}),E=await q.text();if(!q.ok)throw new It(q.status,fe(E,"L’import"));return JSON.parse(E)}async function ne(f){if(f==null)return;s().notice="",s().actionError="";let q;try{q=JSON.parse(await f.text())}catch{s().actionError=`${f.name} n’est pas un fichier JSON lisible.`;return}v(j,"import");try{const E=await s().protect(()=>_e(q));if(E===null)return;v(we,E.config,!0),v(ke,f.name,!0),v(ue,E.faults,!0),await b(),s().notice=L(f.name)}finally{v(j,"")}}function L(f){return e(B)?e(ye).length===0?`${f} décrit la même configuration que celle en service.`:`${f} est lu. Rien n’est appliqué : relisez le tableau.`:`${f} est lu, mais la configuration en service ne l’est pas : rien ne peut être comparé.`}async function X(){if(e(we)===null)return;const f=e(we),q=[...e(ye)];for(const E of q)t.draft.set(E.path,va(f,E.path));s().actionError="",s().notice="Le fichier est recopié dans le brouillon. Rien n’est appliqué avant « Enregistrer ».",await b()}async function ie(f){v(j,`restore-${String(f)}`),s().notice="",s().actionError="";try{if(await s().protect(()=>Nr(f))===null)return;await t.draft.load(),await Oe(),await b(),await s().refresh(),s().notice=`La version ${String(f)} est remise en service.`}finally{v(j,"")}}function fe(f,q){try{const E=JSON.parse(f);if(typeof E?.message=="string"&&E.message!=="")return E.message}catch{}return`${q} a été refusé par le poste.`}function xe(f){return t.draft.faults.find(q=>q.field===f)?.message??""}function He(f){return t.draft.faults.find(q=>q.field===f)?.allowed??[]}function Xe(f,q,E,be){const se=q>1?be:E;return f>=q?`${he(q)} ${se}.`:`${he(f)} lignes affichées sur ${he(q)} ${se}.`}var ge=Mi(),d=a(ge);Qe(d,{title:"Identité du poste",children:(f,q)=>{var E=Si(),be=H(E);{let ze=u(()=>t.draft.text("station.number")),Re=u(()=>xe("station.number")),nt=u(()=>He("station.number"));_t(be,{label:"Numéro du poste",path:"station.number",kind:"number",get value(){return e(ze)},get fault(){return e(Re)},get allowed(){return e(nt)},hint:"C’est de lui que dérive le nom du fichier de catalogue attendu, flv_.csv.",onchange:ut=>t.draft.set("station.number",Number(ut))})}var se=r(be,2);{let ze=u(()=>t.draft.text("station.name")),Re=u(()=>xe("station.name")),nt=u(()=>He("station.name"));_t(se,{label:"Nom du poste",path:"station.name",get value(){return e(ze)},get fault(){return e(Re)},get allowed(){return e(nt)},hint:"Ce que lit un bénévole : « Poste 2 — fruits ».",onchange:ut=>t.draft.set("station.name",ut)})}var Se=r(se,2);{let ze=u(()=>t.draft.text("station.coop")),Re=u(()=>xe("station.coop")),nt=u(()=>He("station.coop"));_t(Se,{label:"Coopérative",path:"station.coop",get value(){return e(ze)},get fault(){return e(Re)},get allowed(){return e(nt)},onchange:ut=>t.draft.set("station.coop",ut)})}var Le=r(Se,2),ce=r(a(Le),2),V=a(ce),Ue=r(ce,4),Ve=a(Ue),Ee=r(Ue,4),Te=a(Ee),Je=r(Ee,4),ae=a(Je);{var Ie=ze=>{var Re=yt("non mesuré");l(ze,Re)},ve=ze=>{var Re=yt();p((nt,ut,pt)=>i(Re,`${nt??""} libres sur + ${ut??""} — seuil d’alerte + ${pt??""} Mo`),[()=>wa(t.health.disk.free_bytes),()=>wa(t.health.disk.total_bytes),()=>he(t.health.disk.alert_mb)]),l(ze,Re)};m(ae,ze=>{t.health.disk===null?ze(Ie):ze(ve,-1)})}p(()=>{i(V,t.health.config_fingerprint),i(Ve,t.health.version),i(Te,t.health.disk===null?"non publié par ce poste":t.health.disk.path)}),l(f,E)},$$slots:{default:!0}});var x=r(d,2);Qe(x,{title:"Exporter, importer",note:"Pour installer un autre poste : ce fichier emporte les tarifs, les garde-fous, l’étiquette et les catégories. Tout ce qui est propre à ce poste-ci reste ici — le mot de passe, le code de secours, le numéro et le nom du poste, les réglages de la balance, ceux de l’imprimante, la source du catalogue et le réseau.",children:(f,q)=>{var E=Ni(),be=H(E),se=a(be);{let ae=u(()=>e(j)==="export-all");at(se,{label:"Exporter tout",protected:!0,get busy(){return e(ae)},get disabled(){return e(J)},onrun:()=>{T(!0)}})}var Se=r(se,2);{let ae=u(()=>e(j)==="export-clone");at(Se,{label:"Exporter sans le matériel",protected:!0,get busy(){return e(ae)},get disabled(){return e(J)},onrun:()=>{T(!1)}})}var Le=r(Se,2);let ce;var V=a(Le),Ue=r(V,3),Ve=r(be,4);{var Ee=ae=>{var Ie=Li(),ve=a(Ie);p(()=>i(ve,`La configuration en service n’a pas pu être lue : ${e(le)??""} La colonne + « En service » ne peut donc rien affirmer.`)),l(ae,Ie)};m(Ve,ae=>{e(le)!==""&&ae(Ee)})}var Te=r(Ve,2);{var Je=ae=>{var Ie=Ai(),ve=H(Ie),ze=a(ve),Re=r(ve,2);{var nt=Ze=>{var bt=ji(),Rt=a(bt),Ut=a(Rt),k=r(Rt,2);rt(k,21,()=>e(D),fa,(Y,oe)=>{var Ae=zi(),We=a(Ae),Ye=a(We),Ke=r(We,2);{var M=qe=>{var $e=Ia(),Pe=a($e);p(()=>i(Pe,e(oe).field)),l(qe,$e)};m(Ke,qe=>{Gt.showTechnicalNames&&qe(M)})}var te=r(Ke),me=r(te);{var Ne=qe=>{var $e=Ei(),Pe=a($e);p(Fe=>i(Pe,`Valeurs acceptées : ${Fe??""}.`),[()=>e(oe).allowed.join(", ")]),l(qe,$e)};m(me,qe=>{e(oe).allowed!==void 0&&e(oe).allowed.length>0&&qe(Ne)})}p(qe=>{i(Ye,qe),i(te,` ${e(oe).message??""} `)},[()=>Ft(e(oe).field)]),l(Y,Ae)}),p(()=>i(Ut,`Ce fichier serait refusé en l’état : ${e(S)??""}`)),l(Ze,bt)};m(Re,Ze=>{e(ue).length>0&&Ze(nt)})}var ut=r(Re,2);{var pt=Ze=>{var bt=Pi();l(Ze,bt)},je=Ze=>{var bt=Ti();l(Ze,bt)},st=Ze=>{var bt=Ri(),Rt=H(bt),Ut=a(Rt),k=r(Ut);{var Y=me=>{var Ne=yt("Les autres sont dans le fichier, et « Recopier » les prend tous.");l(me,Ne)};m(k,me=>{e(ye).length>e(Z).length&&me(Y)})}var oe=r(Rt,2),Ae=a(oe),We=r(a(Ae));rt(We,21,()=>e(Z),me=>me.path,(me,Ne)=>{const qe=u(()=>Ft(e(Ne).path));var $e=Ci(),Pe=a($e),Fe=a(Pe),Ge=r(Fe);{var ft=gt=>{var xt=Ia(),Nt=a(xt);p(()=>i(Nt,e(Ne).path)),l(gt,xt)};m(Ge,gt=>{Gt.showTechnicalNames&&e(qe)!==e(Ne).path&>(ft)})}var ot=r(Pe),ct=a(ot),it=r(ot),dt=a(it);p(()=>{Be($e,"data-path",e(Ne).path),i(Fe,`${e(qe)??""} `),i(ct,e(Ne).before),i(dt,e(Ne).after)}),l(me,$e)});var Ye=r(oe,2);{var Ke=me=>{var Ne=Oi(),qe=a(Ne);p($e=>i(qe,`Ce fichier ne porte pas ${$e??""} : + l’export sans le matériel les retire, et l’import ne remet que le numéro du + poste. Les lignes correspondantes sont VIDES ci-dessus, et + « Recopier » recopie ce vide dans le brouillon.`),[()=>De(e($).map($e=>$e.name))]),l(me,Ne)};m(Ye,me=>{e($).length>0&&me(Ke)})}var M=r(Ye,2),te=a(M);at(te,{kind:"write",get label(){return e(P)},get disabled(){return e(J)},onrun:()=>{X()}}),p(()=>i(Ut,`${e(re)??""} `)),l(Ze,bt)};m(ut,Ze=>{e(B)?e(ye).length===0?Ze(je,1):Ze(st,-1):Ze(pt)})}p(()=>i(ze,`Fichier lu : ${e(ke)??""}`)),l(ae,Ie)};m(Te,ae=>{e(we)!==null&&ae(Je)})}p(()=>{ce=Ht(Le,1,"choose svelte-18wbxwu",null,ce,{working:e(j)==="import",off:e(J)}),i(V,`${e(j)==="import"?"Lecture du fichier…":"Importer un fichier"} `),Ue.disabled=e(J)}),et("change",Ue,ae=>{ne(ae.currentTarget.files?.item(0))}),l(f,E)},$$slots:{default:!0}});var ee=r(x,2);Qe(ee,{title:"Cinq versions restaurables",note:"Chaque enregistrement fait tourner les versions : la plus récente est la 1.",children:(f,q)=>{var E=Dt(),be=H(E);{var se=V=>{var Ue=$i();l(V,Ue)},Se=V=>{var Ue=Di();l(V,Ue)},Le=V=>{var Ue=Ii();l(V,Ue)},ce=V=>{var Ue=Fi(),Ve=H(Ue),Ee=a(Ve),Te=r(Ve,2),Je=a(Te);rt(Je,21,()=>e(z),ae=>ae.version,(ae,Ie)=>{var ve=Ui(),ze=a(ve),Re=a(ze),nt=r(ze,2),ut=a(nt),pt=r(nt,2),je=a(pt),st=r(pt,2);{let Ze=u(()=>e(j)===`restore-${String(e(Ie).version)}`);at(st,{kind:"destructive",label:"Remettre cette version en service",protected:!0,get busy(){return e(Ze)},get disabled(){return e(J)},onrun:()=>{ie(e(Ie).version)}})}p((Ze,bt)=>{i(Re,`version ${Ze??""}`),i(ut,bt),i(je,e(Ie).config_fingerprint)},[()=>he(e(Ie).version),()=>Mt(e(Ie).modified_at)]),l(ae,ve)}),p(()=>i(Ee,`${e(y)??""} Le poste n’en garde jamais plus de cinq.`)),l(V,Ue)};m(be,V=>{e(W)==="reading"?V(se):e(W)==="unreadable"?V(Se,1):e(A).length===0?V(Le,2):V(ce,-1)})}l(f,E)},$$slots:{default:!0}}),l(n,ge),Ot()}Tt(["change"]);var Vi=o('clé'),Wi=o(' '),Gi=o('');function Xt(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"hint",3,""),_=vt(t,"disabled",3,!1),w=vt(t,"engaged",3,!1),C=vt(t,"busy",3,!1),R=vt(t,"protected",3,!1);var A=Gi();let W;var G=a(A),le=a(G),we=r(le);{var ke=de=>{var J=Vi();l(de,J)};m(we,de=>{R()&&de(ke)})}var ue=r(G,2);{var j=de=>{var J=Wi(),ye=a(J);p(()=>i(ye,C()?"En cours…":c())),l(de,J)};m(ue,de=>{c()!==""&&de(j)})}p(()=>{W=Ht(A,1,`big touch-target ${s()??""}`,"svelte-15nlt6i",W,{engaged:w(),busy:C()}),Be(A,"data-kind",s()),A.disabled=_()||C(),i(le,`${t.label??""} `)}),et("click",A,function(...de){t.onrun?.apply(this,de)}),l(n,A)}Tt(["click"]);const Hi="ERR-SCL-09";var Ji=o('

                                                                                                                                  '),Ki=o('

                                                                                                                                  '),Xi=o('

                                                                                                                                  Déposez ici le fichier clé

                                                                                                                                  '),Yi=o('

                                                                                                                                  ',1),Qi=o('');function Zi(n,t){Ct(t,!0);const s=vt(t,"admin",7);let c=F("");const _=u(()=>t.health.state.degraded?.code===Hi),w=u(()=>t.health.printing),C=u(()=>e(w)?.on_fallback===!0);let R=F(!1),A=F("");const W=u(()=>s().busy||e(A)!=="");async function G(b,T){v(c,""),v(A,b,!0);try{await s().run(T)}finally{v(A,"")}}async function le(b,T){v(c,""),v(A,b,!0);try{const g=await s().load(T);g!==null&&v(c,g.message,!0)}finally{v(A,"")}}async function we(b,T){v(c,""),v(A,b,!0);try{const g=await s().protect(T);g!==null&&(s().notice=g.message,await s().refresh())}finally{v(A,"")}}async function ke(b){if(v(R,!1),b!=null){v(c,""),v(A,"import");try{const T=await s().protect(()=>Ga(b));if(T===null)return;v(c,T.result==="rejected"||T.result==="failed"?`${b.name} : REFUSÉ${T.reason===""?"":" — "+T.reason}. Le catalogue en service n’a pas changé.`:`${b.name} : ${String(T.rows_read_count)} lignes lues, ${String(T.weighable_count)} pesables. La veille l’appliquera dans la seconde.`,!0),await s().refresh()}finally{v(A,"")}}}var ue=Qi(),j=a(ue);{var de=b=>{var T=Ji(),g=a(T);p(()=>i(g,e(c))),l(b,T)};m(j,b=>{e(c)!==""&&b(de)})}var J=r(j,2);{var ye=b=>{var T=Ki(),g=a(T);p(()=>i(g,e(w).banner)),l(b,T)};m(J,b=>{e(w)!==null&&e(w).banner!==""&&b(ye)})}var B=r(J,2),$=a(B);{let b=u(()=>e(A)==="scale");Xt($,{label:"Tester la balance",hint:"Ce que le poste a déjà observé — le port n’est pas rouvert.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{le("scale",Lr)}})}var Z=r($,2);{let b=u(()=>e(A)==="printer");Xt(Z,{label:"Tester l’imprimante",hint:"Ce que le superviseur a vu il y a moins d’une seconde.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{le("printer",Er)}})}var D=r(Z,2);{let b=u(()=>e(A)==="label");Xt(D,{label:"Imprimer une étiquette de test",hint:"Une étiquette de démonstration sort de l’imprimante du poste.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("label",zr)}})}var z=r(D,2);{let b=u(()=>e(A)==="reprint");Xt(z,{label:"Réimprimer la dernière",hint:"La dernière étiquette imprimée sort une seconde fois.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("reprint",yr)}})}var re=r(z,2);{let b=u(()=>e(A)==="reload");Xt(re,{label:"Recharger le catalogue",kind:"write",hint:"La veille refait tout de suite le contrôle qu’elle fait toutes les cinq secondes.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("reload",xr)}})}var S=r(re,2);{let b=u(()=>e(_)?"Revenir à la balance":"Basculer en saisie manuelle"),T=u(()=>e(_)?"Le poids sera de nouveau lu sur la balance.":"Le poids se tape à la main : le poste continue de servir sans balance."),g=u(()=>e(A)==="manual");Xt(S,{get label(){return e(b)},kind:"destructive",get hint(){return e(T)},get engaged(){return e(_)},protected:!0,get busy(){return e(g)},get disabled(){return e(W)},onrun:()=>{we("manual",()=>kr(!e(_)))}})}var y=r(S,2);{let b=u(()=>e(A)==="roll");Xt(y,{label:"J’ai changé le rouleau",kind:"write",hint:"Le compteur d’étiquettes repart à zéro. C’est le seul geste qui dise quelque chose de vrai du papier.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("roll",qr)}})}var P=r(y,2);{var I=b=>{{let T=u(()=>e(C)?"Revenir à l’imprimante du poste":"Imprimer sur l’imprimante du poste voisin"),g=u(()=>e(C)?"Les étiquettes repartiront sur l’imprimante de ce poste.":"Les étiquettes sortiront sur l’imprimante voisine, pour cette session seulement."),U=u(()=>e(A)==="fallback");Xt(b,{get label(){return e(T)},kind:"write",get hint(){return e(g)},get engaged(){return e(C)},get busy(){return e(U)},get disabled(){return e(W)},onrun:()=>{G("fallback",()=>Sr(!e(C)))}})}};m(P,b=>{e(w)!==null&&e(w).fallback_available&&b(I)})}var Q=r(P,2),pe=r(B,2);Qe(pe,{title:"Importer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(b,T)=>{var g=Xi();let U;var N=a(g),K=r(a(N)),_e=a(K),ne=r(N,2),L=r(a(ne));p(()=>{U=Ht(g,1,"drop svelte-1yd0nzg",null,U,{dropping:e(R)}),i(_e,`flv_${t.health.station??""}.csv`)}),Qt("dragover",g,X=>{X.preventDefault(),v(R,!0)}),Qt("dragleave",g,()=>v(R,!1)),Qt("drop",g,X=>{X.preventDefault(),ke(X.dataTransfer?.files.item(0))}),et("change",L,X=>{ke(X.currentTarget.files?.item(0))}),l(b,g)},$$slots:{default:!0}});var De=r(pe,2);{var Oe=b=>{Qe(b,{title:"Le dernier fichier a été refusé",children:(T,g)=>{var U=Yi(),N=H(U),K=a(N),_e=r(N,2),ne=a(_e);p(L=>{i(K,`${(t.health.catalog.reason===""?"Aucun motif enregistré.":t.health.catalog.reason)??""} + Le catalogue en service n’a pas changé.`),i(ne,`Dernier essai : ${L??""}.`)},[()=>Mt(t.health.catalog.occurred_at)]),l(T,U)},$$slots:{default:!0}})};m(De,b=>{t.health.catalog!==null&&t.health.catalog.result==="rejected"&&b(Oe)})}p(()=>Be(Q,"href",jr)),l(n,ue),Ot()}Tt(["change"]);const eo={succeeded:"La dernière mise à jour a réussi.","rolled-back":"La dernière mise à jour a échoué. La version précédente a été remise et le poste fonctionne.","rolled-back-unhealthy":"La dernière mise à jour a échoué et le poste n’a pas redémarré. Appelez le support.","not-started":"La dernière mise à jour n’a pas démarré : rien n’a été remplacé. Vous pouvez réessayer."},to=2e3,ao=300*1e3;var ro=o('

                                                                                                                                  '),Ua=o('

                                                                                                                                  '),no=o("
                                                                                                                                  Dernière vérification
                                                                                                                                  ",1),so=o(`

                                                                                                                                  La mise à jour depuis cet écran n’existe que sur les postes Windows. Sur les + autres, elle se fait à la main — voir la notice d’installation.

                                                                                                                                  `),lo=o('Voir les nouveautés'),io=o('

                                                                                                                                  Version disponible : .

                                                                                                                                  '),oo=o('

                                                                                                                                  Ce poste est à jour.

                                                                                                                                  '),uo=o("
                                                                                                                                  Version installée
                                                                                                                                  Dépôt suivi
                                                                                                                                  ",1),co=o(`

                                                                                                                                  Le poste va s’arrêter environ une minute. L’écran client s’éteindra puis reviendra + tout seul. Si la nouvelle version ne démarre pas, la précédente sera remise + automatiquement — mais les données enregistrées, elles, ne reviendront pas en + arrière.

                                                                                                                                  `),vo=o('

                                                                                                                                  Mise à jour en cours. Le poste redémarre, ne le débranchez pas.

                                                                                                                                  '),po=o('
                                                                                                                                  ',1),fo=o("
                                                                                                                                  Terminée le
                                                                                                                                  ",1),go=o("
                                                                                                                                  Raison
                                                                                                                                  ",1),mo=o('

                                                                                                                                  Depuis
                                                                                                                                  Vers
                                                                                                                                  ',1),ho=o(" ",1);function _o(n,t){Ct(t,!0);let s=F(null),c=F(""),_=F(""),w=F(!1),C=F("");const R=u(()=>e(s)?.outcome??null),A=u(()=>e(s)?.supported===!0&&e(s).available);aa(()=>{W()});async function W(){try{v(s,await Yr(),!0),v(c,"")}catch(B){v(c,B instanceof Error?B.message:"Lecture impossible.",!0)}}async function G(){v(_,"check");try{const B=await t.admin.protect(()=>Qr());B!==null&&(v(s,B,!0),v(c,""))}finally{v(_,"")}}async function le(B){v(_,"apply"),v(C,"");try{if(await t.admin.protect(()=>Zr(B))===null)return;v(w,!0),v(C,await we()?"":"Le poste n’a pas répondu dans les cinq minutes. Allez le voir.",!0),await W()}finally{v(_,""),v(w,!1)}}async function we(){const B=Date.now()+ao;for(;Date.now()setTimeout($,to));try{if((await fetch("/healthz",{cache:"no-store"})).ok)return!0}catch{}}return!1}var ke=ho(),ue=H(ke);Qe(ue,{title:"Version de ce poste",children:(B,$)=>{var Z=Dt(),D=H(Z);{var z=S=>{var y=ro(),P=a(y);p(()=>i(P,e(c)===""?"Lecture…":e(c))),l(S,y)},re=S=>{var y=uo(),P=H(y);{var I=ne=>{var L=Ua(),X=a(L);p(()=>i(X,e(c))),l(ne,L)};m(P,ne=>{e(c)!==""&&ne(I)})}var Q=r(P,2),pe=r(a(Q),2),De=a(pe),Oe=r(pe,4),b=a(Oe),T=r(Oe,2);{var g=ne=>{var L=no(),X=r(H(L),2),ie=a(X);p(fe=>i(ie,fe),[()=>Mt(e(s).checked_at)]),l(ne,L)};m(T,ne=>{e(s).checked_at!==""&&ne(g)})}var U=r(Q,2);{var N=ne=>{var L=so();l(ne,L)},K=ne=>{var L=io(),X=r(a(L)),ie=a(X),fe=r(X,2);{var xe=ge=>{var d=yt();p(x=>i(d,`, publiée le ${x??""}`),[()=>Mt(e(s).published_at)]),l(ge,d)};m(fe,ge=>{e(s).published_at!==""&&ge(xe)})}var He=r(fe,2);{var Xe=ge=>{var d=lo();p(()=>Be(d,"href",e(s).html_url)),l(ge,d)};m(He,ge=>{e(s).html_url!==""&&ge(Xe)})}p(()=>i(ie,e(s).latest)),l(ne,L)},_e=ne=>{var L=oo();l(ne,L)};m(U,ne=>{e(s).supported?e(s).available?ne(K,1):ne(_e,-1):ne(N)})}p(()=>{i(De,e(s).running),i(b,e(s).repository)}),l(S,y)};m(D,S=>{e(s)===null?S(z):S(re,-1)})}l(B,Z)},$$slots:{default:!0}});var j=r(ue,2);{var de=B=>{Qe(B,{title:"Installer",children:($,Z)=>{var D=po(),z=H(D),re=a(z);{let b=u(()=>e(_)==="check");at(re,{kind:"read",label:"Vérifier maintenant",protected:!0,act:"check",get busy(){return e(b)},get disabled(){return e(w)},onrun:()=>{G()}})}var S=r(re,2);{var y=b=>{{let T=u(()=>`Installer la version ${e(s).latest}`),g=u(()=>e(_)==="apply");at(b,{kind:"destructive",get label(){return e(T)},protected:!0,act:"apply",get busy(){return e(g)},onrun:()=>{le(e(s).latest)}})}};m(S,b=>{e(A)&&b(y)})}var P=r(z,2);{var I=b=>{var T=co();l(b,T)};m(P,b=>{e(A)&&b(I)})}var Q=r(P,2);{var pe=b=>{var T=vo();l(b,T)};m(Q,b=>{e(w)&&b(pe)})}var De=r(Q,2);{var Oe=b=>{var T=Ua(),g=a(T);p(()=>i(g,e(C))),l(b,T)};m(De,b=>{e(C)!==""&&b(Oe)})}l($,D)},$$slots:{default:!0}})};m(j,B=>{e(s)!==null&&e(s).supported&&B(de)})}var J=r(j,2);{var ye=B=>{Qe(B,{title:"Dernière tentative",children:($,Z)=>{var D=mo(),z=H(D),re=a(z),S=r(z,2),y=r(a(S),2),P=a(y),I=r(y,4),Q=a(I),pe=r(I,2);{var De=T=>{var g=fo(),U=r(H(g),2),N=a(U);p(K=>i(N,K),[()=>Mt(e(R).finished_at)]),l(T,g)};m(pe,T=>{e(R).finished_at!==""&&T(De)})}var Oe=r(pe,2);{var b=T=>{var g=go(),U=r(H(g),2),N=a(U);p(()=>i(N,e(R).reason)),l(T,g)};m(Oe,T=>{e(R).reason!==""&&T(b)})}p(()=>{Be(z,"data-outcome",e(R).status),i(re,eo[e(R).status]),i(P,e(R).from===""?"—":e(R).from),i(Q,e(R).to===""?"—":e(R).to)}),l($,D)},$$slots:{default:!0}})};m(J,B=>{e(R)!==null&&B(ye)})}l(n,ke),Ot()}var bo=o(''),wo=o('

                                                                                                                                  ',1),yo=o('

                                                                                                                                  ',1),xo=o(''),ko=o(''),qo=o(''),So=o(''),Lo=o(' '),Eo=o(''),zo=o('

                                                                                                                                  Lecture de l’état du poste…

                                                                                                                                  '),jo=o('

                                                                                                                                  '),Po=o(" "),To=o("
                                                                                                                                • "),Co=o('
                                                                                                                                    '),Oo=o('
                                                                                                                                    '),Ro=o('

                                                                                                                                    ');function Ao(n,t){Ct(t,!0);const s=new pn,c=new en(s),_=[{title:"Au quotidien",pages:[{id:"dashboard",label:"Tableau de bord"},{id:"troubleshooting",label:"Dépannage"}]},{title:"Réglages",pages:[{id:"hardware",label:"Matériel"},{id:"label",label:"Étiquette"},{id:"rules",label:"Règles"},{id:"catalog",label:"Catalogue"},{id:"journal",label:"Journal"},{id:"station",label:"Poste"},{id:"update",label:"Mise à jour"}]}],w=u(()=>_.flatMap(d=>d.pages).find(d=>d.id===s.page)?.label??""),C=u(()=>s.health===null||s.health.station_name===""?`Poste ${String(s.health?.station??"")}`:s.health.station_name),R=u(()=>(c.pending?.changed_blocks??[]).map(d=>nn(d)).join(", ")),A=u(()=>(c.pending?.changed_blocks??[]).join(", "));ka(()=>(s.start(),()=>s.stop()));async function W(d){s.open(d),Ca(d)&&c.config===null&&await c.load()}async function G(){await s.protect(()=>c.save())===!0&&(s.notice="La configuration est enregistrée et appliquée.")}var le=Ro(),we=a(le),ke=r(a(we),2);rt(ke,17,()=>_,d=>d.title,(d,x)=>{var ee=wo(),f=H(ee),q=a(f),E=r(f,2);rt(E,17,()=>e(x).pages,be=>be.id,(be,se)=>{var Se=bo();let Le;var ce=a(Se);p(()=>{Le=Ht(Se,1,"entry svelte-9b2mjq",null,Le,{current:s.page===e(se).id}),Be(Se,"aria-current",s.page===e(se).id?"page":void 0),i(ce,e(se).label)}),et("click",Se,()=>{W(e(se).id)}),l(be,Se)}),p(()=>i(q,e(x).title)),l(d,ee)});var ue=r(ke,2),j=a(ue);{var de=d=>{var x=yo(),ee=H(x),f=a(ee),q=r(ee,2),E=a(q),be=r(q,2),se=a(be);p(()=>{i(f,e(C)),i(E,`${s.health.coop??""} · version ${s.health.version??""}`),i(se,`configuration ${s.health.config_fingerprint??""}`)}),l(d,x)};m(j,d=>{s.health!==null&&d(de)})}var J=r(j,2),ye=a(J),B=r(J,2);{var $=d=>{var x=xo();et("click",x,function(...ee){t.onclose?.apply(this,ee)}),l(d,x)};m(B,d=>{t.onclose!==void 0&&d($)})}var Z=r(we,2),D=a(Z);{var z=d=>{var x=ko(),ee=a(x);p(()=>i(ee,s.notice)),l(d,x)};m(D,d=>{s.notice!==""&&d(z)})}var re=r(D,2);{var S=d=>{var x=qo(),ee=a(x);p(()=>i(ee,s.linkError)),l(d,x)};m(re,d=>{s.linkError!==""&&d(S)})}var y=r(re,2);{var P=d=>{var x=So(),ee=a(x);p(()=>i(ee,s.actionError)),l(d,x)};m(y,d=>{s.actionError!==""&&d(P)})}var I=r(y,2);{var Q=d=>{var x=Eo(),ee=a(x),f=a(ee),q=r(f);{var E=Se=>{var Le=Lo(),ce=a(Le);p(()=>i(ce,e(A))),l(Se,Le)};m(q,Se=>{Gt.showTechnicalNames&&Se(E)})}var be=r(q),se=r(ee,2);at(se,{kind:"write",label:"Tout fonctionne : confirmer",onrun:()=>{s.protect(()=>c.confirm())}}),p(()=>{i(f,`Configuration appliquée mais NON CONFIRMÉE. Ce qui a changé : ${e(R)??""}. `),i(be,` Le poste reviendra tout seul à la version précédente dans + ${c.pending.seconds_left??""} secondes si personne ne confirme.`)}),l(d,x)};m(I,d=>{c.pending!==null&&d(Q)})}var pe=r(I,2),De=a(pe),Oe=a(De),b=r(De,2);{var T=d=>{var x=zo();l(d,x)},g=d=>{ol(d,{get health(){return s.health},onshowrows:()=>{W("catalog")},onshowupdate:()=>{W("update")}})},U=d=>{Zi(d,{get admin(){return s},get health(){return s.health}})},N=d=>{wl(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},K=d=>{Jl(d,{get admin(){return s},get draft(){return c}})},_e=d=>{ki(d,{get draft(){return c},get health(){return s.health}})},ne=d=>{ys(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},L=d=>{Il(d,{get admin(){return s}})},X=d=>{Bi(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},ie=d=>{_o(d,{get admin(){return s}})};m(b,d=>{s.health===null?d(T):s.page==="dashboard"?d(g,1):s.page==="troubleshooting"?d(U,2):s.page==="hardware"?d(N,3):s.page==="label"?d(K,4):s.page==="rules"?d(_e,5):s.page==="catalog"?d(ne,6):s.page==="journal"?d(L,7):s.page==="station"?d(X,8):s.page==="update"&&d(ie,9)})}var fe=r(pe,2);{var xe=d=>{var x=Oo(),ee=a(x);{var f=se=>{var Se=jo(),Le=a(Se),ce=r(Le);rt(ce,16,()=>c.retired,V=>V,(V,Ue)=>{{let Ve=u(()=>`retirer ${Ue}`);at(V,{kind:"write",get label(){return e(Ve)},onrun:()=>c.dropRetired(Ue)})}}),p(V=>i(Le,`Ce fichier porte des réglages que cette version du poste ne connaît plus : + ${V??""}. `),[()=>c.retired.map(V=>Ft(V)).join(", ")]),l(se,Se)};m(ee,se=>{c.retired.length>0&&se(f)})}var q=r(ee,2);{var E=se=>{var Se=Co();rt(Se,21,()=>c.faults,Le=>Le.field,(Le,ce)=>{var V=To(),Ue=a(V),Ve=a(Ue),Ee=r(Ue,2);{var Te=ve=>{var ze=Po(),Re=a(ze);p(()=>i(Re,e(ce).field)),l(ve,ze)};m(Ee,ve=>{Gt.showTechnicalNames&&ve(Te)})}var Je=r(Ee),ae=r(Je);{var Ie=ve=>{var ze=yt();p(Re=>i(ze,`— valeurs acceptées : ${Re??""}`),[()=>e(ce).allowed.join(", ")]),l(ve,ze)};m(ae,ve=>{e(ce).allowed!==void 0&&e(ce).allowed.length>0&&ve(Ie)})}p(ve=>{i(Ve,ve),i(Je,` ${e(ce).message??""} `)},[()=>Ft(e(ce).field)]),l(Le,V)}),l(se,Se)};m(q,se=>{c.faults.length>0&&se(E)})}var be=r(q,2);{let se=u(()=>c.dirty?"Enregistrer la configuration":"Aucune modification à enregistrer"),Se=u(()=>!c.dirty||s.busy);at(be,{kind:"write",get label(){return e(se)},get disabled(){return e(Se)},onrun:()=>{G()}})}l(d,x)},He=u(()=>Ca(s.page)&&c.config!==null);m(fe,d=>{e(He)&&d(xe)})}var Xe=r(Z,2);{var ge=d=>{wn(d,{get admin(){return s}})};m(Xe,d=>{s.pending!==null&&d(ge)})}p(()=>{ua(ye,Gt.showTechnicalNames),i(Oe,e(w))}),et("change",ye,()=>Gt.toggleTechnicalNames()),l(n,le),Ot()}Tt(["click","change"]);let oa=null;function Do(n){if(oa!==null||n.querySelector("[data-admin]")!==null)return;const t=document.createElement("div");n.appendChild(t),oa={component:fr(Ao,{target:t,props:{onclose:No}}),host:t}}function No(){if(oa===null)return;const{component:n,host:t}=oa;oa=null,gr(n),t.remove()}export{No as closeAdmin,Do as mountAdmin}; diff --git a/internal/web/dist/assets/mount-C1tpIbdA.css b/internal/web/dist/assets/mount-C1tpIbdA.css new file mode 100644 index 0000000..a4a6c96 --- /dev/null +++ b/internal/web/dist/assets/mount-C1tpIbdA.css @@ -0,0 +1 @@ +.act.svelte-5tpq0o{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.read.svelte-5tpq0o{color:var(--ink);background:var(--surface);border:1px solid var(--border)}.write.svelte-5tpq0o{color:var(--surface);background:var(--action);border:1px solid var(--action)}.destructive.svelte-5tpq0o{min-height:var(--touch-min);color:var(--surface);background:var(--danger);border:1px solid var(--danger)}@media(hover:hover){.read.svelte-5tpq0o:hover:not(:disabled){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}.write.svelte-5tpq0o:hover:not(:disabled){border-color:var(--action)}.destructive.svelte-5tpq0o:hover:not(:disabled){border-color:var(--danger)}.write.svelte-5tpq0o:hover:not(:disabled),.destructive.svelte-5tpq0o:hover:not(:disabled){box-shadow:var(--shadow-2);filter:brightness(.92)}}.act.svelte-5tpq0o:disabled{opacity:.5;box-shadow:none;cursor:default}.act.busy.svelte-5tpq0o:disabled{opacity:1}.key.svelte-5tpq0o{padding:.0625rem .375rem;border-radius:var(--radius-pill);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;background:var(--bg);color:var(--ink-muted)}.write.svelte-5tpq0o .key:where(.svelte-5tpq0o),.destructive.svelte-5tpq0o .key:where(.svelte-5tpq0o){background:var(--surface);color:var(--ink)}.scrim.svelte-zyka3t{position:fixed;inset:0;z-index:95;display:flex;align-items:center;justify-content:center;padding:2rem;background:#1c1b1973}.panel.svelte-zyka3t{display:flex;flex-direction:column;gap:.75rem;width:min(34rem,100%);padding:1.75rem;background:var(--surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-2)}header.svelte-zyka3t{display:flex;align-items:center;gap:.75rem}.glyph.svelte-zyka3t{display:flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;flex:none;border-radius:var(--radius-sm);background:var(--bg);color:var(--ink-muted)}h2.svelte-zyka3t{margin:0;font-size:1.375rem;line-height:1.2}.why.svelte-zyka3t,.how.svelte-zyka3t{margin:0;color:var(--ink-muted);font-size:1rem;line-height:1.4}.field.svelte-zyka3t{display:flex;flex-direction:column;gap:.375rem;font-size:1rem;color:var(--ink-muted)}input.svelte-zyka3t{height:2.75rem;padding:0 .75rem;font:inherit;font-size:1.125rem;color:var(--ink);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm)}input.svelte-zyka3t:focus-visible{outline:3px solid var(--focus);outline-offset:1px}.refusal.svelte-zyka3t{margin:0;padding:.625rem .75rem;border-left:.25rem solid var(--fault);background:var(--fault-wash);font-size:1rem}footer.svelte-zyka3t{display:flex;justify-content:flex-end;gap:.5rem;margin-top:.25rem}.field.svelte-1oilv5s{display:flex;flex-direction:column;gap:.25rem;padding:.5rem 0}label.svelte-1oilv5s{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}.name.svelte-1oilv5s{font-size:1.125rem;font-weight:700}code.svelte-1oilv5s{font-size:.9375rem;color:var(--ink-muted)}input.svelte-1oilv5s{min-height:3rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.refused.svelte-1oilv5s input:where(.svelte-1oilv5s){border-left:.5rem solid var(--fault)}.hint.svelte-1oilv5s,.fault.svelte-1oilv5s{margin:0;font-size:1rem;color:var(--ink-muted)}.fault.svelte-1oilv5s{padding-left:.5rem;border-left:.25rem solid var(--fault)}.inventory.svelte-tuh4jc{display:flex;flex-direction:column;gap:.5rem}.headline.svelte-tuh4jc{margin:0;font-size:1.375rem;font-weight:700}dl.svelte-tuh4jc{margin:0;display:flex;flex-direction:column;gap:.25rem}.row.svelte-tuh4jc{display:flex;align-items:baseline;gap:.75rem}dt.svelte-tuh4jc{flex:none;width:4.5rem;text-align:right;font-size:1.375rem;font-weight:700}dd.svelte-tuh4jc{margin:0;display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem;font-size:1.125rem}.label.svelte-tuh4jc{font-weight:700}.note.svelte-tuh4jc{color:var(--ink-muted)}.rows.svelte-tuh4jc{padding:0 .5rem;min-height:var(--touch-min);text-decoration:underline;color:var(--ink-muted)}.oneline.svelte-tuh4jc{margin:0;font-size:1rem;color:var(--ink-muted)}.panel.svelte-ww3z5u{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}h2.svelte-ww3z5u{margin:0 0 .5rem;font-size:1.5rem;font-weight:700}.note.svelte-ww3z5u{margin:0 0 .75rem;font-size:1rem;color:var(--ink-muted)}.pages.svelte-3gk7u7{display:flex;flex-direction:column;gap:1rem}.fact.svelte-3gk7u7{margin:.5rem 0;font-size:1.125rem}.muted.svelte-3gk7u7{color:var(--ink-muted);font-size:1rem}.actions.svelte-3gk7u7{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.key.svelte-3gk7u7{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.scroll.svelte-3gk7u7{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-sm);background:var(--bg)}.rows.svelte-3gk7u7{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-3gk7u7 li:where(.svelte-3gk7u7):first-child{border-top:none}.line.svelte-3gk7u7,.id.svelte-3gk7u7,.value.svelte-3gk7u7{color:var(--ink-muted)}.line.svelte-3gk7u7{flex:none;width:7rem}.what.svelte-3gk7u7{font-weight:700}.message.svelte-3gk7u7{flex:1 1 20rem}.pick.svelte-3gk7u7{height:2.75rem;padding:0 .5rem;font-weight:700;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-3gk7u7:hover:not(:disabled){background:var(--surface)}}.search.svelte-3gk7u7,label.svelte-3gk7u7{display:block;margin-top:.5rem;font-size:1.0625rem;font-weight:700}.choice.svelte-3gk7u7{display:flex;flex-direction:column}.choice.svelte-3gk7u7 label:where(.svelte-3gk7u7){display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin:0;font-weight:400}.choice.svelte-3gk7u7 input:where(.svelte-3gk7u7){width:1.5rem;height:1.5rem;min-height:0;flex:0 0 auto;padding:0;background:none;border:none;border-radius:0}input.svelte-3gk7u7{min-height:2.75rem;width:100%;max-width:34rem;padding:0 .75rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.decision.svelte-3gk7u7{margin-top:.75rem;padding-top:.75rem;border-top:1px solid var(--border)}.act-block.svelte-3gk7u7{margin-top:1rem;padding:.75rem 1rem 1rem;background:var(--bg);border-radius:var(--radius)}.act-block.svelte-3gk7u7 .what:where(.svelte-3gk7u7){margin:0;font-size:1.0625rem;font-weight:700}.drop.svelte-3gk7u7{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius);transition:border-color var(--tap) var(--ease),background-color var(--tap) var(--ease)}.drop.dropping.svelte-3gk7u7{border-color:var(--focus);background:var(--waiting-wash)}.drop.working.svelte-3gk7u7{border-color:var(--waiting);background:var(--waiting-wash)}.drop.svelte-3gk7u7 p:where(.svelte-3gk7u7){margin:0;font-size:1.125rem}.choose.svelte-3gk7u7{display:inline-flex;align-items:center;margin:0;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease)}.choose.svelte-3gk7u7:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){.choose.svelte-3gk7u7:active{transform:none}}@media(hover:hover){.choose.svelte-3gk7u7:hover{filter:brightness(.92)}}.choose.svelte-3gk7u7 input:where(.svelte-3gk7u7){display:none}table.svelte-3gk7u7{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-3gk7u7,td.svelte-3gk7u7{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}.why.svelte-3gk7u7{white-space:normal;min-width:16rem;color:var(--ink-muted)}th.svelte-3gk7u7{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.light.svelte-1421wa8{display:flex;flex-direction:column;gap:.375rem;padding:.875rem 1rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--waiting);border-radius:var(--radius)}.light[data-level=ok].svelte-1421wa8{border-left-color:var(--ready)}.light[data-level=warn].svelte-1421wa8{border-left-color:var(--warning)}.light[data-level=fault].svelte-1421wa8{border-left-color:var(--fault)}header.svelte-1421wa8{display:flex;align-items:center;gap:.5rem}h3.svelte-1421wa8{margin:0;font-size:1.25rem;font-weight:700}.dot.svelte-1421wa8{width:1rem;height:1rem;border-radius:50%;background:var(--waiting);flex:none}.dot[data-level=ok].svelte-1421wa8{background:var(--ready)}.dot[data-level=warn].svelte-1421wa8{background:var(--warning)}.dot[data-level=fault].svelte-1421wa8{background:var(--fault)}.dot[data-level=unknown].svelte-1421wa8,.dot[data-level=off].svelte-1421wa8{background:transparent;box-shadow:inset 0 0 0 3px var(--waiting)}.verdict.svelte-1421wa8{margin-left:auto;font-size:1rem;font-weight:700;color:var(--ink-muted);letter-spacing:.03em}.value.svelte-1421wa8{margin:0;font-size:1.125rem}.remedy.svelte-1421wa8{margin:0;font-size:1rem;color:var(--ink-muted)}.dashboard.svelte-1w44m0y{display:flex;flex-direction:column;gap:1rem}.link.svelte-1w44m0y{padding:0;font:inherit;color:inherit;text-decoration:underline;background:none;border:none;cursor:pointer}.lights.svelte-1w44m0y{display:grid;grid-template-columns:repeat(auto-fit,minmax(20rem,1fr));grid-auto-rows:1fr;gap:var(--touch-gap)}.fact.svelte-1w44m0y{margin:0 0 .5rem;font-size:1.125rem;overflow-wrap:break-word}.remedy.svelte-1w44m0y,.count.svelte-1w44m0y{color:var(--ink-muted);font-size:1rem}.restart.svelte-1w44m0y strong:where(.svelte-1w44m0y){letter-spacing:.03em}.restart[data-verdict=missing].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--warning);background:var(--warning-wash)}.restart[data-verdict=unknown].svelte-1w44m0y{margin-bottom:.25rem;padding:.5rem .75rem;border-left:.25rem solid var(--waiting);background:var(--waiting-wash)}.decisions.svelte-1w44m0y,.events.svelte-1w44m0y{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.375rem}.decisions.svelte-1w44m0y li:where(.svelte-1w44m0y),.events.svelte-1w44m0y li:where(.svelte-1w44m0y){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.what.svelte-1w44m0y,.message.svelte-1w44m0y{font-weight:700}.detail.svelte-1w44m0y,.time.svelte-1w44m0y,.source.svelte-1w44m0y,.code.svelte-1w44m0y{color:var(--ink-muted)}.events.svelte-1w44m0y li[data-level=error]:where(.svelte-1w44m0y){border-left:.25rem solid var(--fault);padding-left:.5rem}.events.svelte-1w44m0y li[data-level=warn]:where(.svelte-1w44m0y){border-left:.25rem solid var(--warning);padding-left:.5rem}.identity.svelte-1w44m0y{margin:0;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.125rem}.identity.svelte-1w44m0y dt:where(.svelte-1w44m0y){color:var(--ink-muted)}.identity.svelte-1w44m0y dd:where(.svelte-1w44m0y){margin:0;font-weight:700}.pages.svelte-1p4i0xm{display:flex;flex-direction:column;gap:1rem}.panel.svelte-1p4i0xm{padding:1rem 1.25rem 1.25rem;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}.head.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;justify-content:space-between}h2.svelte-1p4i0xm{margin:0;font-size:1.5rem;font-weight:700}.standing.svelte-1p4i0xm{display:inline-flex;align-items:center;gap:.5rem;height:2rem;padding:0 .75rem;border-radius:var(--radius-pill);background:var(--waiting-wash);font-size:1rem;font-weight:700}.standing[data-level=ok].svelte-1p4i0xm{background:var(--ready-wash)}.standing[data-level=warn].svelte-1p4i0xm{background:var(--warning-wash)}.standing[data-level=fault].svelte-1p4i0xm{background:var(--fault-wash)}.dot.svelte-1p4i0xm{width:.625rem;height:.625rem;border-radius:var(--radius-pill);background:var(--waiting)}.standing[data-level=ok].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--ready)}.standing[data-level=warn].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--warning)}.standing[data-level=fault].svelte-1p4i0xm .dot:where(.svelte-1p4i0xm){background:var(--fault)}.fact.svelte-1p4i0xm{margin:.75rem 0 0;font-size:1.125rem}.note.svelte-1p4i0xm,.count.svelte-1p4i0xm,.waiting.svelte-1p4i0xm{margin:.25rem 0 0;font-size:1rem;color:var(--ink-muted)}.check.svelte-1p4i0xm{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;margin-top:.5rem;font-size:1.0625rem}.check.svelte-1p4i0xm input:where(.svelte-1p4i0xm){width:1.5rem;height:1.5rem;flex:0 0 auto}.check.svelte-1p4i0xm small:where(.svelte-1p4i0xm){display:block;font-size:1rem;color:var(--ink-muted)}.actions.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.5rem;margin:.75rem 0 0}@media(hover:hover){.pick.svelte-1p4i0xm:hover,summary.svelte-1p4i0xm:hover{background:var(--bg)}}.list.svelte-1p4i0xm{margin:.25rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column}.list.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.125rem 0;border-top:1px solid var(--border-soft);font-size:1.0625rem}.list.svelte-1p4i0xm li.refused:where(.svelte-1p4i0xm){padding-left:.5rem;border-left:.25rem solid var(--fault);background:var(--fault-wash)}.pick.svelte-1p4i0xm{height:2.75rem;padding:0 .75rem;font-weight:700;text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}.what.svelte-1p4i0xm{font-weight:700}.detail.svelte-1p4i0xm{color:var(--ink-muted)}.folded.svelte-1p4i0xm{margin-top:1rem;border:1px solid var(--border);border-radius:var(--radius)}summary.svelte-1p4i0xm{display:flex;align-items:center;height:2.75rem;padding:0 .75rem;font-size:1.0625rem;font-weight:700;border-radius:var(--radius);cursor:pointer;transition:background-color var(--tap) var(--ease),transform var(--tap) var(--ease)}summary.svelte-1p4i0xm:active{transform:scale(.975)}@media(prefers-reduced-motion:reduce){summary.svelte-1p4i0xm:active{transform:none}}.frames.svelte-1p4i0xm{margin-top:1rem;max-height:18rem;overflow-y:auto;background:var(--waiting-wash);border:1px solid var(--border);border-radius:var(--radius)}.frames-head.svelte-1p4i0xm{position:sticky;top:0;margin:0;padding:.5rem .75rem;background:var(--surface);border-radius:var(--radius) var(--radius) 0 0;box-shadow:var(--shadow-2);font-size:1rem;color:var(--ink-muted)}.interrupted.svelte-1p4i0xm{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;margin:0;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);font-size:1rem}.frame-rows.svelte-1p4i0xm{margin:0;padding:.5rem .75rem;list-style:none;display:flex;flex-direction:column;gap:.25rem}.frame-rows.svelte-1p4i0xm li:where(.svelte-1p4i0xm){display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);gap:.75rem;font-size:.9375rem}.hex.svelte-1p4i0xm,.decoded.svelte-1p4i0xm{overflow-x:auto;white-space:pre}.decoded.svelte-1p4i0xm{color:var(--ink-muted)}div.journal.svelte-86o8oz{display:flex;flex-direction:column;gap:1rem;max-width:none;--reading-column: 68rem}.filters.svelte-86o8oz{display:flex;flex-wrap:wrap;gap:var(--touch-gap);align-items:center;margin-bottom:.75rem;font-size:1.0625rem}.filters.svelte-86o8oz label:where(.svelte-86o8oz){font-weight:700}select.svelte-86o8oz{min-height:2.75rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.export.svelte-86o8oz{display:inline-flex;align-items:center;gap:.5rem;height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);transition:transform var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.export.svelte-86o8oz:active{transform:scale(.975)}@media(hover:hover){.export.svelte-86o8oz:hover{border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.replay.svelte-86o8oz{margin-top:.5rem}.fact.svelte-86o8oz{margin:.5rem 0;max-width:var(--reading-column);font-size:1.125rem}.muted.svelte-86o8oz{color:var(--ink-muted);font-size:1rem}.table-box.svelte-86o8oz,.lines-box.svelte-86o8oz{overflow:auto;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.table-box.svelte-86o8oz{max-height:34rem}.lines-box.svelte-86o8oz{max-height:28rem}table.svelte-86o8oz{border-collapse:collapse;min-width:100%;font-size:1.0625rem}th.svelte-86o8oz,td.svelte-86o8oz{padding:.375rem .5rem;text-align:left;white-space:nowrap;border-bottom:1px solid var(--border)}th.svelte-86o8oz{position:sticky;top:0;z-index:1;color:var(--ink-muted);font-size:1rem;background:var(--bg)}tbody.svelte-86o8oz tr:where(.svelte-86o8oz){transition:background-color var(--tap) var(--ease)}@media(hover:hover){tbody.svelte-86o8oz tr:where(.svelte-86o8oz):hover{background:var(--surface)}}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz){background:var(--surface);font-weight:700}tbody.svelte-86o8oz tr.open:where(.svelte-86o8oz)>td:where(.svelte-86o8oz):first-child{box-shadow:inset .1875rem 0 0 0 var(--focus)}.pick.svelte-86o8oz{height:2.75rem;padding:0 .5rem;color:var(--ink);text-decoration:underline;border-radius:var(--radius-sm);transition:background-color var(--tap) var(--ease)}@media(hover:hover){.pick.svelte-86o8oz:hover:not(:disabled){background:var(--surface)}}.detail-row.svelte-86o8oz{background:var(--surface)}.detail-cell.svelte-86o8oz{padding:.75rem 1rem 1rem;white-space:normal;box-shadow:inset .1875rem 0 0 0 var(--focus)}.detail-cell.svelte-86o8oz h3:where(.svelte-86o8oz){margin:0 0 .5rem;font-size:1.25rem}.detail-cell.svelte-86o8oz dl:where(.svelte-86o8oz){display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;max-width:var(--reading-column);margin:0;font-size:1.0625rem;font-weight:400}.detail-cell.svelte-86o8oz dt:where(.svelte-86o8oz){color:var(--ink-muted)}.detail-cell.svelte-86o8oz dd:where(.svelte-86o8oz){margin:0}.detail-cell.svelte-86o8oz code:where(.svelte-86o8oz){overflow-wrap:anywhere}.lines.svelte-86o8oz{margin:0;padding:0 .75rem;list-style:none}.lines.svelte-86o8oz li:where(.svelte-86o8oz){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.lines.svelte-86o8oz li:where(.svelte-86o8oz):first-child{border-top:none}.lines.svelte-86o8oz li[data-level=error]:where(.svelte-86o8oz),.lines.svelte-86o8oz li[data-level=critical]:where(.svelte-86o8oz){border-left:.25rem solid var(--fault);padding-left:.5rem}.lines.svelte-86o8oz li[data-level=warn]:where(.svelte-86o8oz){border-left:.25rem solid var(--warning);padding-left:.5rem}.when.svelte-86o8oz,.level.svelte-86o8oz,.from.svelte-86o8oz,.code.svelte-86o8oz,.detail-text.svelte-86o8oz{color:var(--ink-muted)}.level.svelte-86o8oz{flex:none;width:8rem}.message.svelte-86o8oz{flex:1 1 20rem;font-weight:700}.sr-only.svelte-86o8oz{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.pages.svelte-llv1r8{display:flex;flex-direction:column;gap:1rem}.stale.svelte-llv1r8{margin:0 0 .75rem;padding:.5rem .75rem;background:var(--warning-wash);border-left:.375rem solid var(--warning);border-radius:var(--radius-sm);font-size:1.0625rem}.preview.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:1.5rem;align-items:flex-start}.sheet.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0;padding:.75rem;background:var(--waiting-wash);border-radius:var(--radius-lg)}img.svelte-llv1r8{width:40rem;max-width:100%;image-rendering:pixelated;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-1)}.refused.svelte-llv1r8{margin:0;padding:.5rem .75rem;background:var(--fault-wash);border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);font-size:1.0625rem}.nudges.svelte-llv1r8{display:flex;flex-direction:column;gap:.5rem;min-width:0}.axis.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0;font-size:1.125rem}.pair.svelte-llv1r8{display:flex;gap:var(--touch-gap)}.fault.svelte-llv1r8{margin:0;padding-left:.5rem;border-left:.25rem solid var(--fault);font-size:1rem;color:var(--ink-muted)}.check.svelte-llv1r8{display:flex;gap:.75rem;align-items:center;min-height:2.75rem;font-size:1.0625rem}.check.svelte-llv1r8 input:where(.svelte-llv1r8){width:1.5rem;height:1.5rem;flex:0 0 auto}.truncation.svelte-llv1r8,.absent.svelte-llv1r8,.waiting.svelte-llv1r8,.bound.svelte-llv1r8,.cost.svelte-llv1r8{margin:1rem 0 0;font-size:1rem;color:var(--ink-muted)}.absent.svelte-llv1r8{margin-top:.5rem}.bound.svelte-llv1r8{margin-top:.25rem}.cost.svelte-llv1r8{margin-top:.5rem}.actions.svelte-llv1r8{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin-top:.75rem}.pages.svelte-hzfkjn{display:flex;flex-direction:column;gap:1rem}.fact.svelte-hzfkjn{margin:.5rem 0;font-size:1.125rem}.muted.svelte-hzfkjn,.locked.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}h3.svelte-hzfkjn{margin:1.5rem 0 .5rem;font-size:1.25rem}.scroll.svelte-hzfkjn{overflow-x:auto}table.svelte-hzfkjn{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-hzfkjn,td.svelte-hzfkjn{padding:.375rem .5rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem}input.svelte-hzfkjn{min-height:2.75rem;width:100%;min-width:6rem;padding:0 .5rem;font:inherit;font-variant-numeric:inherit;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm)}.rules.svelte-hzfkjn{margin:.75rem 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:.75rem}.rule.svelte-hzfkjn{padding:.75rem 1rem 1rem;background:var(--bg);border:1px solid var(--border-soft);border-radius:var(--radius-sm)}.rule-head.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin:0}.rank.svelte-hzfkjn{flex:none;min-width:1.75rem;color:var(--ink-muted);font-variant-numeric:tabular-nums;font-weight:700}.rule-label.svelte-hzfkjn{font-size:1.125rem;font-weight:700}.token.svelte-hzfkjn{color:var(--ink-muted);font-size:.9375rem}.severity.svelte-hzfkjn{margin-left:auto;padding:.125rem .625rem;font-size:.9375rem;border-radius:var(--radius-pill);background:var(--waiting-wash)}.severity[data-blocking=true].svelte-hzfkjn{background:var(--warning-wash)}.when.svelte-hzfkjn{margin:.375rem 0 0;font-size:1rem}.quote.svelte-hzfkjn{margin:.5rem 0 0;padding:.5rem .75rem;font-size:1.0625rem;background:var(--surface);border-radius:var(--radius-sm);box-shadow:var(--shadow-1)}.quote.silent.svelte-hzfkjn{color:var(--ink-muted);font-size:1rem;box-shadow:none;background:var(--waiting-wash)}.note.svelte-hzfkjn{margin:.5rem 0 0;font-size:1rem;color:var(--ink-muted)}.toggle.svelte-hzfkjn{display:flex;align-items:center;gap:.75rem;min-height:2.75rem;margin:.5rem 0 0;padding:.375rem .75rem;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--waiting-wash);cursor:pointer;transition:background-color var(--tap) var(--ease),border-color var(--tap) var(--ease)}.toggle[data-on=true].svelte-hzfkjn{background:var(--ready-wash)}@media(hover:hover){.toggle.svelte-hzfkjn:hover{border-color:var(--ink-muted)}}.toggle.svelte-hzfkjn input:where(.svelte-hzfkjn){flex:none;width:1.5rem;height:1.5rem;min-height:0;min-width:0;padding:0;accent-color:var(--focus)}.toggle-text.svelte-hzfkjn{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline}.toggle-label.svelte-hzfkjn{font-size:1.0625rem;font-weight:700}.hint.svelte-hzfkjn{flex:1 1 20rem;color:var(--ink-muted);font-size:1rem}.box.svelte-hzfkjn{display:contents}.verdicts.svelte-hzfkjn{margin:0;padding:0;list-style:none}.waivers.svelte-hzfkjn{max-height:24rem;overflow-y:auto}.verdicts.svelte-hzfkjn li:where(.svelte-hzfkjn){display:flex;flex-wrap:wrap;gap:.75rem;align-items:baseline;padding:.375rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.verdicts.svelte-hzfkjn li[data-blocking=true]:where(.svelte-hzfkjn){border-left:.25rem solid var(--warning);background:var(--warning-wash);padding-left:.5rem}.what.svelte-hzfkjn,.message.svelte-hzfkjn{font-weight:700}.detail.svelte-hzfkjn{color:var(--ink-muted)}.dead.svelte-hzfkjn{flex:1 1 20rem;padding:.125rem .625rem;font-size:1rem;background:var(--warning-wash);border-radius:var(--radius-sm)}.unread.svelte-hzfkjn{margin:.5rem 0;padding:.5rem .75rem;font-size:1rem;background:var(--fault-wash);border-left:.25rem solid var(--fault);border-radius:var(--radius-sm)}.pages.svelte-18wbxwu{display:flex;flex-direction:column;gap:1rem}.fact.svelte-18wbxwu{margin:.5rem 0;font-size:1.125rem}.muted.svelte-18wbxwu{color:var(--ink-muted);font-size:1rem}.filename.svelte-18wbxwu{margin-top:1rem;font-weight:700}.identity.svelte-18wbxwu{margin:1rem 0 0;padding:.75rem 1rem;display:grid;grid-template-columns:auto 1fr;gap:.25rem 1rem;font-size:1.0625rem;background:var(--bg);border-radius:var(--radius)}.identity.svelte-18wbxwu dt:where(.svelte-18wbxwu){color:var(--ink-muted)}.identity.svelte-18wbxwu dd:where(.svelte-18wbxwu){margin:0;font-weight:700}.actions.svelte-18wbxwu{display:flex;flex-wrap:wrap;gap:var(--touch-gap);margin:.75rem 0 0}.choose.svelte-18wbxwu{display:inline-flex;align-items:center;gap:.5rem;min-height:2.75rem;padding:0 1rem;font-size:1.0625rem;font-weight:700;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:var(--shadow-1);cursor:pointer;transition:transform var(--tap) var(--ease),background-color var(--tap) var(--ease),border-color var(--tap) var(--ease),box-shadow var(--slide) var(--ease)}.choose.svelte-18wbxwu:active:not(.off){transform:scale(.975)}@media(hover:hover){.choose.svelte-18wbxwu:hover:not(.off){border-color:var(--ink-muted);box-shadow:var(--shadow-2)}}.choose.off.svelte-18wbxwu{opacity:.5;box-shadow:none;cursor:default}.choose.working.svelte-18wbxwu{opacity:1;border-color:var(--waiting)}.choose.svelte-18wbxwu input:where(.svelte-18wbxwu){display:none}.key.svelte-18wbxwu{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.same.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--ready);border-radius:var(--radius-sm);background:var(--ready-wash)}.warned.svelte-18wbxwu{padding:.5rem .75rem;border-left:.375rem solid var(--fault);border-radius:var(--radius-sm);background:var(--fault-wash)}.faults.svelte-18wbxwu{margin-top:.75rem;padding:.25rem 1rem .5rem;border-left:.375rem solid var(--warning);border-radius:var(--radius);background:var(--warning-wash)}.faults.svelte-18wbxwu ul:where(.svelte-18wbxwu){margin:0;padding-left:1.25rem;font-size:1.0625rem}.allowed.svelte-18wbxwu{display:block;color:var(--ink-muted);font-size:1rem}.scroll.svelte-18wbxwu{max-height:24rem;overflow:auto;border:1px solid var(--border-soft);border-radius:var(--radius-lg);background:var(--bg)}table.svelte-18wbxwu{border-collapse:collapse;width:100%;font-size:1.0625rem}th.svelte-18wbxwu,td.svelte-18wbxwu{padding:.375rem .75rem;text-align:left;border-bottom:1px solid var(--border)}th.svelte-18wbxwu{position:sticky;top:0;color:var(--ink-muted);font-size:1rem;background:var(--bg)}.rows.svelte-18wbxwu{margin:0;padding:0 .75rem;list-style:none}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu){display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;padding:.5rem 0;border-top:1px solid var(--border);font-size:1.0625rem}.rows.svelte-18wbxwu li:where(.svelte-18wbxwu):first-child{border-top:none}.what.svelte-18wbxwu{font-weight:700}.detail.svelte-18wbxwu{color:var(--ink-muted)}.big.svelte-15nlt6i{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.write.svelte-15nlt6i{color:var(--surface);background:var(--action);border-color:var(--action)}.destructive.svelte-15nlt6i{color:var(--surface);background:var(--danger);border-color:var(--danger)}.write.svelte-15nlt6i .hint:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .hint:where(.svelte-15nlt6i){color:var(--surface);opacity:.85}.write.svelte-15nlt6i .guarded:where(.svelte-15nlt6i),.destructive.svelte-15nlt6i .guarded:where(.svelte-15nlt6i){background:var(--surface);color:var(--ink)}.big.svelte-15nlt6i:disabled{opacity:.5}.big.busy.svelte-15nlt6i{opacity:1;border-color:var(--waiting);background:var(--waiting-wash)}.big.busy.write.svelte-15nlt6i{background:var(--action)}.big.busy.destructive.svelte-15nlt6i{background:var(--danger)}.guarded.svelte-15nlt6i{margin-left:.5rem;padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;vertical-align:middle}.big.engaged.svelte-15nlt6i{border-left:.5rem solid var(--warning)}.label.svelte-15nlt6i{font-size:1.375rem;font-weight:700}.hint.svelte-15nlt6i{font-size:1rem;color:var(--ink-muted)}.troubleshooting.svelte-1yd0nzg{display:flex;flex-direction:column;gap:1rem}.buttons.svelte-1yd0nzg{display:grid;grid-template-columns:repeat(auto-fit,minmax(22rem,1fr));gap:var(--touch-gap)}.big.svelte-1yd0nzg{display:flex;flex-direction:column;justify-content:center;gap:.25rem;min-height:6rem;padding:1rem 1.25rem;text-align:left;text-decoration:none;color:var(--ink);background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.label.svelte-1yd0nzg{font-size:1.375rem;font-weight:700}.hint.svelte-1yd0nzg{font-size:1rem;color:var(--ink-muted)}.report.svelte-1yd0nzg{margin:0;padding:.75rem 1rem;font-size:1.125rem;background:var(--surface);border:1px solid var(--border);border-left:.5rem solid var(--ready);border-radius:var(--radius)}.drop.svelte-1yd0nzg{display:flex;flex-wrap:wrap;align-items:center;gap:1rem;padding:1rem;border:2px dashed var(--border);border-radius:var(--radius)}.drop.dropping.svelte-1yd0nzg{border-color:var(--focus)}.drop.svelte-1yd0nzg p:where(.svelte-1yd0nzg){margin:0;font-size:1.125rem}.key.svelte-1yd0nzg{padding:.0625rem .375rem;border-radius:var(--radius-pill);background:var(--bg);color:var(--ink-muted);font-size:.75rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase}.choose.svelte-1yd0nzg{display:inline-flex;align-items:center;padding:0 1rem;font-size:1.125rem;font-weight:700;color:var(--surface);background:var(--danger);border:1px solid var(--danger);border-radius:var(--radius);cursor:pointer}@media(hover:hover){.choose.svelte-1yd0nzg:hover{filter:brightness(.92)}}.choose.svelte-1yd0nzg input:where(.svelte-1yd0nzg){display:none}.fact.svelte-1yd0nzg{margin:0 0 .5rem;font-size:1.125rem}.muted.svelte-1yd0nzg{color:var(--ink-muted)}.row.svelte-o9s8nz{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:.75rem}.admin.svelte-9b2mjq{position:fixed;inset:0;z-index:90;display:grid;grid-template-columns:16rem 1fr;background:var(--bg);color:var(--ink);overflow:hidden}.rail.svelte-9b2mjq{display:flex;flex-direction:column;gap:.125rem;padding:1rem .75rem;overflow-y:auto;background:var(--surface);border-right:1px solid var(--border)}h1.svelte-9b2mjq{margin:0 .5rem 1rem;font-size:1.25rem;letter-spacing:-.01em}.group.svelte-9b2mjq{margin:1rem .5rem .375rem;color:var(--ink-muted);font-size:.8125rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase}.entry.svelte-9b2mjq{padding:0 .75rem;height:2.5rem;display:flex;align-items:center;text-align:left;font-size:1.0625rem;border-radius:var(--radius-sm);color:var(--ink-muted);transition:background-color var(--tap) var(--ease),color var(--tap) var(--ease)}@media(hover:hover){.entry.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.entry.current.svelte-9b2mjq{background:var(--bg);color:var(--ink);font-weight:700;box-shadow:inset .1875rem 0 0 0 var(--focus)}.foot.svelte-9b2mjq{margin-top:auto;padding:1rem .5rem 0;border-top:1px solid var(--border)}.station.svelte-9b2mjq{margin:0;font-size:1rem;font-weight:600}.build.svelte-9b2mjq{margin:.125rem 0 0;font-size:.8125rem;color:var(--ink-muted)}.technical.svelte-9b2mjq{display:flex;align-items:center;gap:.5rem;min-height:2.75rem;margin-top:.75rem;color:var(--ink-muted);font-size:.9375rem}.technical.svelte-9b2mjq input:where(.svelte-9b2mjq){width:1.5rem;height:1.5rem;flex:0 0 auto}.back.svelte-9b2mjq{margin-top:.75rem;padding:0 .5rem;height:2.25rem;font-size:.9375rem;color:var(--ink-muted);border-radius:var(--radius-sm)}@media(hover:hover){.back.svelte-9b2mjq:hover{background:var(--bg);color:var(--ink)}}.body.svelte-9b2mjq{display:flex;flex-direction:column;min-width:0;overflow:hidden}.page.svelte-9b2mjq{flex:1;min-height:0;overflow-y:auto;padding:1.5rem 2rem 3rem}.page.svelte-9b2mjq>*{max-width:68rem}.page-title.svelte-9b2mjq{margin:0 0 1.25rem;font-size:1.75rem;letter-spacing:-.01em}.waiting.svelte-9b2mjq{margin:0;font-size:1.125rem;color:var(--ink-muted)}.banner.svelte-9b2mjq{flex:none;margin:0;padding:.75rem 2rem;font-size:1.0625rem;background:var(--surface);border-bottom:1px solid var(--border)}.notice.svelte-9b2mjq{border-left:.375rem solid var(--ready);background:var(--ready-wash)}.failure.svelte-9b2mjq{border-left:.375rem solid var(--fault);background:var(--fault-wash)}.pending.svelte-9b2mjq{display:flex;flex-wrap:wrap;gap:1rem;align-items:center;border-left:.375rem solid var(--warning);background:var(--warning-wash)}.pending.svelte-9b2mjq p:where(.svelte-9b2mjq){margin:0;flex:1 1 24rem}.save-bar.svelte-9b2mjq{flex:none;padding:.75rem 2rem;background:var(--surface);border-top:1px solid var(--border);box-shadow:var(--shadow-1)}.retired.svelte-9b2mjq{margin:0 0 .5rem;font-size:.9375rem;color:var(--ink-muted)}.faults.svelte-9b2mjq{margin:0 0 .5rem;padding-left:1.25rem;font-size:1rem} diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html index 036103f..c330a07 100644 --- a/internal/web/dist/index.html +++ b/internal/web/dist/index.html @@ -6,7 +6,7 @@ Pesée - + diff --git a/internal/web/health.go b/internal/web/health.go index ccf9de0..9b08cba 100644 --- a/internal/web/health.go +++ b/internal/web/health.go @@ -186,6 +186,17 @@ type adminHealthDTO struct { // all: that is what decides whether the troubleshooting page offers « Imprimer sur // l'imprimante du poste N » (§14.4, §8.4). Routing *routingDTO `json:"printing"` + // NewVersion is the version published that is newer than the one running, or the + // empty string. + // + // It travels HERE, in the payload the dashboard already reads, and not on a + // route of its own: the volunteer page opens without a password and calls + // exactly one route, which is a property a test holds. A second call from that + // page would have widened, for a courtesy, what an unauthenticated screen does. + // + // It is read from the last poll left on disk, never by asking the repository: + // this handler answers every three seconds. + NewVersion string `json:"new_version"` } // routingDTO is which printer is in service. @@ -286,6 +297,7 @@ func (s *Server) adminHealth(w http.ResponseWriter, r *http.Request) { Events: []technicalLineDTO{}, CatalogMotives: []motiveDTO{}, Decisions: []decisionDTO{}, + NewVersion: s.newVersion(cfg.Update.Repository), } s.fillHealthFromStore(r.Context(), &body) s.fillHealthFromPlatform(r.Context(), &body, cfg) diff --git a/internal/web/update.go b/internal/web/update.go index 2639303..66ba904 100644 --- a/internal/web/update.go +++ b/internal/web/update.go @@ -189,3 +189,23 @@ func instantOrEmpty(at time.Time) string { } return at.Format(time.RFC3339) } + +// newVersion reports the published version worth installing, or the empty string. +// +// It reads what the daily poll left on disk and never asks the repository: this +// answers GET /admin/api/health, which every open administration screen calls +// every three seconds. +// +// A FAILURE IS SILENCE. The badge is a courtesy; its absence teaches nothing +// false, whereas an error on the dashboard would send somebody looking for a +// breakdown that does not exist. +func (s *Server) newVersion(repository string) string { + if s.updater == nil { + return "" + } + status, err := s.updater.Status(repository) + if err != nil || !status.Available { + return "" + } + return status.Check.Version +} diff --git a/web/src/admin/App.svelte b/web/src/admin/App.svelte index d124ddd..0e47eb8 100644 --- a/web/src/admin/App.svelte +++ b/web/src/admin/App.svelte @@ -216,7 +216,11 @@ {#if admin.health === null}

                                                                                                                                    Lecture de l’état du poste…

                                                                                                                                    {:else if admin.page === 'dashboard'} - void open('catalog')} /> + void open('catalog')} + onshowupdate={() => void open('update')} + /> {:else if admin.page === 'troubleshooting'} {:else if admin.page === 'hardware'} diff --git a/web/src/admin/lib/dto.ts b/web/src/admin/lib/dto.ts index 78fba66..87b6b98 100644 --- a/web/src/admin/lib/dto.ts +++ b/web/src/admin/lib/dto.ts @@ -151,6 +151,15 @@ export interface HealthDTO { disk: DiskDTO | null unattended_restart: RestartDTO | null printing: RoutingDTO | null + /** + * La version publiée plus récente que celle qui tourne, ou une chaîne vide. + * + * Elle voyage ici, dans la charge utile que le tableau de bord lit déjà, et non sur + * une route à elle : la page bénévole s'ouvre sans mot de passe et n'appelle qu'une + * route, ce qu'un test tient. Vide aussi quand le service n'a pas pu lire — la + * pastille est une courtoisie, pas un diagnostic. + */ + new_version: string } /** Une ligne de tarif d'une pesée journalisée. */ diff --git a/web/src/admin/pages/Dashboard.svelte b/web/src/admin/pages/Dashboard.svelte index 760888f..061be1f 100644 --- a/web/src/admin/pages/Dashboard.svelte +++ b/web/src/admin/pages/Dashboard.svelte @@ -30,9 +30,24 @@ * de passe ; les lignes à corriger, elles, sont un travail d'expert (§14.4). */ onshowrows?: () => void + /** Ce qui se passe quand un bénévole touche la pastille de version. */ + onshowupdate?: () => void } - const { health, onshowrows }: Props = $props() + const { health, onshowrows, onshowupdate }: Props = $props() + + /** + * La version publiée, quand il en existe une plus récente que celle qui tourne. + * + * Elle vient du MÊME appel que tout le reste de cette page. Un second appel, même + * libre, aurait élargi pour une courtoisie ce que fait un écran ouvert sans mot de + * passe — et un test tient cette page à une seule route. + * + * Le service rend une chaîne vide quand il n'y a rien à dire, y compris quand il n'a + * pas pu lire : la pastille est une courtoisie, son absence n'apprend rien de faux, + * alors qu'une erreur sur le tableau de bord enverrait chercher une panne inexistante. + */ + const newVersion = $derived(health.new_version) const lights = $derived(lightsOf(health)) const scale = $derived(health.state.scale) @@ -212,6 +227,20 @@
                                                                                                                                    Empreinte de configuration
                                                                                                                                    {health.config_fingerprint}
                                                                                                                                    + + {#if newVersion !== ''} +

                                                                                                                                    + Version {newVersion} disponible. + {#if onshowupdate !== undefined} + + {/if} +

                                                                                                                                    + {/if} @@ -257,6 +286,22 @@ gap: 1rem; } + /* La pastille de version est un LIEN et non un acte : elle ne fait rien au poste, elle + ouvre une page. Elle n'emprunte donc ni la couleur ni la cible de 44 px des boutons + d'action, qui disent la nature d'un acte (ADR-037). */ + /* Le SOULIGNÉ et non la couleur. §14.2 n'inventorie que --ink et --ink-muted comme + texte, et les deux fonds pleins n'ont été mesurés que comme fonds : peindre du texte + avec --action poserait un contraste que personne n'a vérifié. */ + .link { + padding: 0; + font: inherit; + color: inherit; + text-decoration: underline; + background: none; + border: none; + cursor: pointer; + } + /* `1fr` sur les rangées : les six feux ont la MÊME hauteur, comme les tuiles de la grille client (ADR-030). Ragés, ils se lisaient comme six objets différents. */ .lights { diff --git a/web/test/admin-dashboard.test.ts b/web/test/admin-dashboard.test.ts index 1b9c9b9..a7bed03 100644 --- a/web/test/admin-dashboard.test.ts +++ b/web/test/admin-dashboard.test.ts @@ -2,6 +2,7 @@ import { flushSync, mount, unmount } from 'svelte' import { afterEach, beforeEach, describe, expect, it } from 'vitest' import Dashboard from '../src/admin/pages/Dashboard.svelte' import type { HealthDTO } from '../src/admin/lib/dto' +import { lightsOf } from '../src/admin/lib/lights' import { preferences } from '../src/admin/lib/preferences.svelte' import { nominalHealth } from './fixtures/health' @@ -179,3 +180,26 @@ describe('les décisions locales', () => { expect(host.textContent).toContain('12 décisions en vigueur') }) }) + +describe('la pastille de version', () => { + /** + * Une version disponible N'EST PAS UNE PANNE. + * + * `lightsOf` réserve les couleurs aux périphériques et aux ressources ; un poste + * parfaitement sain qui s'allumerait en orange parce qu'un correctif est sorti + * apprendrait aux bénévoles à ignorer l'orange. La pastille est du texte, et un lien. + */ + it('annonce une version disponible sans allumer de feu', () => { + show({ ...nominal(), new_version: '2.1.0' }) + + const badge = host.querySelector('[data-update-available]') + expect(badge).not.toBeNull() + expect(badge?.textContent).toContain('2.1.0') + expect(host.querySelectorAll('[data-light]')).toHaveLength(lightsOf(nominal()).length) + }) + + it('ne dit rien quand le poste est à jour', () => { + show({ ...nominal(), new_version: '' }) + expect(host.querySelector('[data-update-available]')).toBeNull() + }) +}) diff --git a/web/test/fixtures/health.ts b/web/test/fixtures/health.ts index e33d069..6530b8e 100644 --- a/web/test/fixtures/health.ts +++ b/web/test/fixtures/health.ts @@ -152,6 +152,8 @@ export function nominalHealth(overrides: Partial = {}): HealthDTO { name: 'Étiqueteuse', banner: '', }, + // Un poste à jour : la pastille de version ne s'affiche que si un test la demande. + new_version: '', ...overrides, } } From 63132b644557d866baf176b6f84db927d66da673 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 13:11:23 +0200 Subject: [PATCH 17/19] docs: la mise a jour depuis l'ecran, et les trois defauts qu'elle a trouves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ADR-040 et §15.5 reecrite : le bouton, les quatre issues d'update.ps1, le depot suivi en couple owner/repo, et le modele de menace ECRIT parce qu'il est assume -- l'empreinte vient de la meme publication que l'archive, elle prouve que le telechargement n'a pas ete abime, pas que la publication est legitime. Ce qui change n'est pas le risque, deja pris par tout benevole qui suit la notice, mais le DELAI : une journee au lieu de plusieurs mois. Le glossaire prend les identifiants du paquet et les neuf codes ERR-UPD. Et "depot" y passe de deux sens a trois : le troisieme n'etait plus "en prose seulement" depuis qu'il porte une cle de configuration. Les trois se distinguent au singulier -- repositories est la couche SQLite, repository est le depot Git, local_drop est un repertoire. TROUBLESHOOTING donne une ligne par issue et dit ce qu'un benevole fait de chacune : "annulee, le poste fonctionne" n'appelle personne, et c'est l'information qui manquait le plus. INSTALLATION cesse de decrire une procedure de console, sans la supprimer -- elle reste le seul chemin sous Linux et quand l'ecran est inaccessible. La spec porte les quatre points ou l'implementation l'a corrigee, chacun ne d'un test ou d'une mesure. Et SUIVI consigne une derive documentaire de plus, relevee en ecrivant §15.5 qui la portait aussi : trois occurrences disent encore ProgramData\Balance et balance.db. --- INSTALLATION.md | 31 ++++++++++-- SUIVI.md | 44 +++++++++++++++++ TROUBLESHOOTING.md | 44 ++++++++++++++--- docs/02-architecture.md | 41 +++++++++++++++- docs/03-glossaire.md | 49 +++++++++++++++++-- ...6-07-29-mise-a-jour-depuis-admin-design.md | 39 +++++++++++++-- 6 files changed, 230 insertions(+), 18 deletions(-) diff --git a/INSTALLATION.md b/INSTALLATION.md index 88c6061..632da6f 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -313,6 +313,21 @@ et les adresses de partage en sont retirés. ## Mettre à jour un poste +**Depuis l'écran, en un geste.** Bouton « Réglages » sur l'écran client → page +**« Mise à jour »**. Le poste vérifie une fois par jour s'il existe une version plus +récente, et le tableau de bord l'annonce quand c'est le cas. Le bouton la nomme. + +Le poste s'arrête environ une minute, l'écran client s'éteint puis **revient tout seul**. +Ne débranchez rien pendant ce temps. Si la nouvelle version ne démarre pas, l'ancienne est +remise automatiquement et l'écran dit ce qui s'est passé — les quatre issues possibles sont +décrites dans `TROUBLESHOOTING.md`. + +Le bouton refuse pendant une pesée, et tant qu'un catalogue qui vient d'arriver n'est pas +entré en service. + +**À la main**, si l'écran d'administration est inaccessible, ou sur un poste Linux où le +bouton n'existe pas : + 1. Copiez l'archive de la nouvelle version sur le poste et décompressez-la. 2. PowerShell **en administrateur**, dans le dossier de la nouvelle version : @@ -320,13 +335,23 @@ et les adresses de partage en sont retirés. .\update.ps1 ``` -Le script arrête le service proprement, **sauvegarde l'ancienne version sous un nom -horodaté**, installe la nouvelle, redémarre, et **vérifie que le poste répond**. Si ça -échoue, il **remet l'ancienne version tout seul** et vous dit pourquoi. +C'est le même script que celui du bouton. Il arrête le service proprement, **sauvegarde +l'ancienne version sous un nom horodaté**, installe la nouvelle, redémarre, **vérifie que +le poste répond**, et **relance l'écran client**. Si ça échoue, il **remet l'ancienne +version tout seul** et vous dit pourquoi. La configuration, le catalogue et le journal des pesées **ne sont pas touchés** : ils ne vivent pas à côté du programme. +### Suivre un autre dépôt + +Le code est libre. Une coopérative qui fait tourner sa propre version peut la faire suivre +à ses postes : page **« Mise à jour »**, champ *Dépôt suivi*, sous la forme +`propriétaire/projet` — jamais une adresse web. C'est un réglage protégé par le mot de +passe, et il doit être **le même sur les quatre postes** : l'empreinte de configuration +affichée au tableau de bord en tient compte, et deux postes qui suivent deux dépôts +différents n'affichent pas la même. + ## Désinstaller un poste ```powershell diff --git a/SUIVI.md b/SUIVI.md index e8f2f12..5d93f81 100644 --- a/SUIVI.md +++ b/SUIVI.md @@ -3,6 +3,46 @@ > Tableau de bord. À mettre à jour au fil de l'eau — c'est le premier fichier à lire > pour savoir où on en est. +**La mise à jour se déclenche depuis l'écran, et elle est livrée (29/07/2026).** ADR-040 : +le poste sonde une fois par jour l'API des publications du dépôt suivi, porte une pastille +au tableau de bord, télécharge l'archive au clic, **vérifie son empreinte SHA-256**, +l'extrait, écrit `pending.json` puis lance `update.ps1` détaché. Le service tourne en +`LocalSystem` : **il n'y avait aucune élévation à obtenir**, ce qui a rendu tout le reste +possible. Treize tâches, 13 commits, suite Go complète verte en `-race`, **686 tests front**, +budget de l'écran client inchangé à 70,3 % — cette page est dans le paquet +d'administration, que la grille ne charge jamais. + +**Trois défauts existants ont été payés par ce chantier.** (1) **L'écran client restait +noir** après toute mise à jour : `Stop-OpenScaleBinaryHolders` termine la tâche du kiosque, +`openscale-kiosk.xml` n'a qu'un `LogonTrigger`, et personne ne la relançait — ni +`install.ps1`, ni `update.ps1`. Le défaut a survécu parce qu'un humain qui met à jour finit +par redémarrer le poste ; un bénévole qui touche un bouton, non. (2) `update.ps1` +**confondait ses échecs** sous un seul `exit 1`, alors que « restauré, le poste marche » et +« restauré, le poste est mort » ne demandent pas la même chose. (3) Le **nil typé** : sur un +binaire `dev`, aucun service de mise à jour n'est construit, et affecter ce `nil` à une +interface produit une interface **qui n'est pas nulle** — la garde répond faux, la méthode +est appelée sur un récepteur nul, et le sondage du tableau de bord faisait tomber la +connexion HTTP toutes les trois secondes. C'était le poste de chaque développeur. + +**Quatre décisions ont été prises par des tests, contre le plan.** La table de `tokens.test` +n'inventorie que `--ink` et `--ink-muted` comme texte : la pastille est **soulignée** et non +colorée, les deux fonds pleins n'ayant été mesurés que comme fonds. `admin-two-levels` tient +la page bénévole à **une seule route** : la disponibilité voyage donc dans +`/admin/api/health` plutôt que dans un second appel qui aurait élargi, pour une courtoisie, +ce que fait un écran ouvert sans mot de passe. Le jugement des chemins d'archive se fait en +séparateurs `/` **avant** toute conversion — `filepath.Clean` transforme sur Windows +`/etc/cron.d/evil` en `\etc\cron.d\evil`, que `filepath.IsAbs` déclare **relatif**, et la +première écriture du contrôle laissait donc passer un chemin absolu à la Unix. Et le worker +de sondage enregistre ses minuteries **dans la goroutine appelante**, comme le Hub et le +superviseur : les enregistrer dans la sienne faisait dépendre le premier sondage de +l'ordonnanceur. Trente jours passent maintenant en 0,28 s. + +**Deux gardes contre le murage d'un poste.** Une bascule dont le budget de quinze minutes +est dépassé est **effacée** au lieu d'opposer `ErrAlreadyRunning` pour toujours — c'est la +conséquence directe de ce que le banc a mesuré, un `Start()` qui rend `nil` sans rien +lancer. Et un lancement qui **échoue** efface son `pending.json` : le processus est encore +vivant pour le faire. + **Le banc a tranché la mise à jour depuis l'écran, et corrigé son plan (29/07/2026).** La tâche 0 du plan `2026-07-29-mise-a-jour-depuis-admin` posait une question qui conditionnait tout le reste : une PowerShell lancée **détachée** par le service survit-elle à l'arrêt de @@ -434,6 +474,7 @@ comptage A/B au scanner de caisse est ce qui tranchera le tracé géométrique d | **L7** | Catalogue — sources, import CSV, images | 2,5 sem. | ✅ **26/07/2026** | | **L8** | Admin et exploitation — écrans, diagnostic, installeurs | 4 sem. | ✅ **26/07/2026** | | **L9** | Recette et mise en service — poste pilote 2 semaines | 3 sem. | ⬜ | +| **hors lot** | Mise à jour depuis l'écran (ADR-040) — paquet `internal/update`, page « Mise à jour », contrat `update.ps1` | 1 j·h | ✅ **29/07/2026** | **Ce qui reste, et il n'y a que ça.** L0 approvisionne le banc (SATO WS408, GRAM XFOC, rouleau, lecteur) ; L9 est la recette sur site. **Aucun des deux ne demande d'écrire du @@ -595,6 +636,7 @@ son périmètre. Aucune ne bloque : ce sont des endroits où la documentation d |---|---|---| | 1 | §16.4, l'extrait du `Makefile` | Montre `bin/balance`, `./tools/boundary/check.sh` et `test: front` là où le `Makefile` réel dit `bin/openscale`, `go run ./tools/boundary` et `test: vet` | | 2 | §16.4, l'énumération du pipeline CI | Nomme `staticcheck`, qu'aucune étape de `ci.yml` ne lance ; et place `make boundary` / `make deps` **avant** `go test -race`, alors que la CI les lance après | +| 3 | §11.1 et §13.2, les chemins de `ProgramData` | Trois occurrences disent encore `C:\ProgramData\Balance` et `balance.db` là où le poste écrit `C:\ProgramData\OpenScale` et `openscale.db`. Relevé en écrivant §15.5, qui portait la même faute et a été corrigé ; les trois autres sont hors du périmètre d'ADR-040 | C'est exactement la classe de défaut qu'ADR-039 et `make deps` suppriment pour les **dépendances**. Ces deux-là portent sur les **outils**, et rien ne les vérifie encore. @@ -646,6 +688,8 @@ de référence produit, pas une correction cosmétique. | Date | Événement | |---|---| | 29/07/2026 | **Tâche 0 du plan de mise à jour depuis l'écran mesurée sur le banc** : le processus détaché survit à l'arrêt du service (113 lignes après), mais `DETACHED_PROCESS` empêche `powershell.exe` de démarrer — le plan passe à `CREATE_NO_WINDOW`. Deux trouvailles incidentes : `-InstallDir`/`-DataRoot` morts sur `install.ps1`, et le `--listen` ignoré de L8 repayé une seconde fois | +| 29/07/2026 | **Mise à jour depuis l'écran livrée** (ADR-040) : sondage quotidien, empreinte SHA-256 vérifiée, `update.ps1` devenu un contrat à quatre issues, page « Mise à jour », contrôle 48 sur le dépôt suivi. Trois défauts existants payés au passage — l'écran client qui restait noir, les échecs indistincts du script, et le nil typé qui faisait paniquer le tableau de bord de tout binaire `dev` | +| 29/07/2026 | **Banc L0 — tâche 0 :** `DETACHED_PROCESS` fait sortir `powershell.exe` en 100 ms avec le code 0 **sans lire son script**. `CREATE_NO_WINDOW` à la place ; l'enfant survit à l'arrêt du service, 113 des 120 lignes du témoin écrites après | | 28/07/2026 | Écran client repris en « Grand Format » (ADR-035, ADR-036) : grille continue — `ui.tile_size` retiré, ce qui **annule le réglage à trois valeurs livré la veille** —, double tarif affiché par tuile, recherche au clavier physique (le poste n'est pas tactile), CategoryBar/StatusBar remplacent FilterBar/ReprintBar. **438 tests front** (23 fichiers), tous verts, mesurés sur ce poste | | 24/07/2026 | Analyse du legacy : 16 rapports, 240 000 lignes de VBA lues | | 24/07/2026 | Conception : 4 architectures en concurrence, 12 jugements, 32 critiques | diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 7ea93a8..adee7d7 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -283,17 +283,41 @@ un accès administrateur au PC pour réinitialiser la configuration : Le poste redemande alors le parcours « premier accès » et **impose** un nouveau mot de passe. **Refaites une fiche d'installation** dans la foulée. +## Mettre le poste à jour + +**Réglages → Mise à jour.** Quand une version plus récente existe, le tableau de bord +l'annonce et cette page porte un bouton rouge qui la nomme. Le poste s'arrête environ une +minute, l'écran client s'éteint puis revient tout seul. **Ne débranchez rien pendant ce +temps.** + +Le bouton refuse pendant une pesée, et tant qu'un catalogue qui vient d'arriver n'est pas +entré en service : réessayez dans un instant. Il accepte, en revanche, sur un poste en +configuration d'usine — c'est justement le cas où une version neuve peut aider. + +--- + ## Une mise à jour a échoué -`update.ps1` **remet l'ancienne version tout seul** et affiche pourquoi. Le poste doit -donc fonctionner comme avant. Vérifiez l'écran client, puis prévenez un responsable avec -le message affiché. +**Le poste remet l'ancienne version tout seul**, et la page Mise à jour dit ce qui s'est +passé. Quatre phrases, et elles ne demandent pas la même chose : + +| Ce que dit l'écran | Ce que vous faites | +|---|---| +| « La dernière mise à jour a réussi. » | Rien. Vérifiez l'écran client. | +| « …a échoué. La version précédente a été remise et le poste fonctionne. » | **Personne à appeler.** Signalez-le à un responsable quand vous en aurez l'occasion. | +| « …a échoué et le poste n'a pas redémarré. Appelez le support. » | Tout de suite. Envoyez le fichier de diagnostic (voir plus bas). | +| « …n'a pas démarré : rien n'a été remplacé. » | **Rien n'a bougé.** Vous pouvez réessayer. | **Si le message parle du schéma de la base** (`ERR-DB-02`, « base créée par une version plus récente »), le retour arrière est en **trois** gestes et le troisième vous appartient : -le script vous **nomme le fichier de sauvegarde de la base** à remettre en place. -Attention, les pesées enregistrées depuis la mise à jour seront perdues : exportez le -journal depuis l'écran d'administration avant de le faire. +le journal de la mise à jour vous **nomme le fichier de sauvegarde de la base** à remettre +en place. Attention, les pesées enregistrées depuis la mise à jour seront perdues : +exportez le journal depuis l'écran d'administration avant de le faire. + +**Si l'écran d'administration est inaccessible**, la procédure à la main existe toujours : +décompressez l'archive de la version, puis, dans une console **en administrateur**, +`powershell -ExecutionPolicy Bypass -File .\update.ps1`. C'est le seul chemin sur un poste +Linux, où le bouton n'existe pas et où l'écran le dit. --- @@ -333,3 +357,11 @@ Ils ne servent **pas** à chercher : ils servent à confirmer qu'on parle de la | `ERR-SYS-08` | **le redémarrage sans intervention n'est pas configuré** | | `ERR-KSK-02` | l'affichage n'arrive pas à rester ouvert | | `ERR-UI-01` | l'écran client a rencontré une erreur d'affichage | +| `ERR-UPD-01` | le serveur des versions est injoignable — la connexion du magasin, le plus souvent | +| `ERR-UPD-02` | le fichier téléchargé est abîmé ; **rien n'a été installé** | +| `ERR-UPD-03` | le poste est occupé : une pesée, ou un catalogue qui entre en service | +| `ERR-UPD-04` | une mise à jour est déjà en cours | +| `ERR-UPD-05` | la mise à jour depuis l'écran n'existe pas sur ce poste (Linux) | +| `ERR-UPD-06` · `ERR-UPD-07` | la bascule a échoué, version précédente remise · **et le poste ne répond pas** | +| `ERR-UPD-08` | cette version ne contient pas de fichier pour ce poste | +| `ERR-UPD-09` | une autre version est parue depuis l'affichage : rechargez la page | diff --git a/docs/02-architecture.md b/docs/02-architecture.md index 05263e3..5138d3f 100644 --- a/docs/02-architecture.md +++ b/docs/02-architecture.md @@ -4080,9 +4080,22 @@ Le kiosque : unité séparée, `ExecStart=/usr/bin/cage -d -- /usr/local/bin/bal **Installer un poste — 15 minutes** : décompresser · `install.ps1` en administrateur · **redémarrer et cocher « le poste est revenu seul sur l'écran client »** (recette obligatoire, bloquant-7) · appui long 3 s dans le coin bas-droit de l'écran client (§14.3) → *Réglages avancés* → **le code de secours de la fiche** pose le mot de passe d'administration *(l'assistant en 5 étapes de §14.4 n'est pas écrit ; le chemin existe, il n'est pas encore guidé)* · Balance → *Détecter automatiquement* · Imprimante → *Imprimer une étiquette de test* → **superposer avec une étiquette actuelle** → régler le décalage ±1 dot · Catalogue → **source et numéro de poste** *(pas de « chemin » à saisir : `local_drop` est créé par le service, et le nom du fichier dérive du numéro de poste — §11.2)* → *Importer maintenant* (ou glisser-déposer un CSV). Postes 2 à 4 : *Poste → Importer* la configuration du poste 1 **sans le bloc matériel**, puis les deux étapes matériel. **Vérifier que l'empreinte affichée est identique.** -**Mettre à jour** : `update.ps1` arrête le service **avec contrôle d'erreur**, sauvegarde le binaire sous un nom horodaté, copie, redémarre, **vérifie `/healthz` (jamais `/readyz`)**, et **restaure automatiquement** la version précédente en cas d'échec. La configuration et la base ne sont pas touchées : elles vivent dans `ProgramData`, pas à côté du binaire. Les migrations s'appliquent au démarrage, **précédées d'un `VACUUM INTO` horodaté** ; le retour arrière restaure `.previous` **et** la copie `balance.db.before-vN-…` si le schéma a bougé — la procédure le dit, c'est trois commandes, pas deux. +**Mettre à jour — un bouton, page « Mise à jour »** (ADR-040). Le poste sonde une fois par jour l'API des publications du dépôt suivi (`update.repository`, contrôle 48) et porte une pastille au tableau de bord dès qu'une version plus récente existe. Le bouton télécharge l'archive de la plateforme, **vérifie son empreinte SHA-256** contre `SHA256SUMS-archives.txt`, l'extrait sous `/updates//`, écrit `pending.json` **avant** de rendre la main, et lance `update.ps1` détaché. Le service tourne en `LocalSystem` : il n'y a aucune élévation à obtenir. Le poste **refuse** de tomber pendant une pesée ou tant qu'un catalogue lu n'est pas entré en service — sa suppression valant acquittement, l'arrêter là le perdrait. Un poste **hors service**, lui, passe : c'est la sortie de secours d'une version cassée. -**Désinstaller** (important-15) : `uninstall.ps1` **restaure** ce que `install.ps1` a sauvegardé dans `restore.json` — plan d'alimentation, stratégies Windows Update, ouverture de session automatique, suspension USB — supprime la tâche kiosque, arrête et retire le service, et **laisse `C:\ProgramData\Balance` intact** (données et configuration) sauf `--purge` explicite. Sans cela, la bascule est irréversible et le retour à l'application Access impossible. +`update.ps1` fait le travail, et il est le même que celui de la notice : il arrête tout ce qui tient le binaire — le service **et la tâche du kiosque** —, sauvegarde le binaire sous un nom horodaté, copie, redémarre, **vérifie `/healthz` (jamais `/readyz`)**, **restaure automatiquement** en cas d'échec, et **relance l'écran client** sur les quatre chemins. Il écrit `outcome.json` sur les quatre aussi : au moment où il se termine, le processus qui aurait pu lire son code de retour est mort depuis une minute, puisque c'est lui qui l'a arrêté. Le binaire qui démarre ensuite — le neuf **ou l'ancien restauré** — relit ce fichier, le verse au journal technique et le sert à l'écran, si bien qu'un navigateur fermé au mauvais moment ne perd rien. + +| Code | `status` | Ce que l'écran en dit | +|---|---|---| +| `0` | `succeeded` | La nouvelle version tourne | +| `10` | `rolled-back` | Échec, version précédente remise, **le poste fonctionne** — personne à appeler | +| `11` | `rolled-back-unhealthy` | Échec **et le poste ne répond pas** — `openscale doctor`, et le chemin de la sauvegarde | +| `12` | `not-started` | **Rien n'a été remplacé.** On peut recliquer | + +La configuration et la base ne sont pas touchées : elles vivent dans `ProgramData`, pas à côté du binaire. Les migrations s'appliquent au démarrage, **précédées d'un `VACUUM INTO` horodaté** ; le retour arrière restaure le binaire sauvegardé **et** la copie `openscale.db.before-vN-…` si le schéma a bougé — le script nomme cette copie, et **ne la remet pas lui-même** : perdre les pesées enregistrées depuis la mise à jour est une décision qui appartient à un humain. + +Le geste manuel reste documenté et reste valable : `update.ps1` lancé à la main depuis l'archive décompressée, sans `-OutcomePath`, écrit son compte rendu sur la console. C'est le seul chemin sous Linux, où l'écran répond honnêtement que la mise à jour n'y existe pas. + +**Désinstaller** (important-15) : `uninstall.ps1` **restaure** ce que `install.ps1` a sauvegardé dans `restore.json` — plan d'alimentation, stratégies Windows Update, ouverture de session automatique, suspension USB — supprime la tâche kiosque, arrête et retire le service, et **laisse `C:\ProgramData\OpenScale` intact** (données et configuration) sauf `--purge` explicite. Sans cela, la bascule est irréversible et le retour à l'application Access impossible. --- @@ -4728,6 +4741,30 @@ Les opportunités de coupure ont été mesurées de la même façon plutôt que **Conséquence.** L'argument de revue et l'argument des dix ans sont le même argument : `net/http` et `database/sql` sont couverts par la **promesse de compatibilité de Go** — du code qui compile aujourd'hui compilera contre les versions 1.x à venir. Un framework tiers ne l'est pas ; la version épinglée en 2026 demandera des montées de version pour suivre le Go de 2034, et chaque montée est une migration que personne ne fera dans une épicerie coopérative sans développeur. La règle cesse par ailleurs d'être une convention : `tools/deps` compare `go.mod` aux deux tables de l'inventaire, dans les deux sens, et la CI l'exécute. C'est ce contrôle qui manquait — son absence est la raison pour laquelle §17.1 a annoncé dix modules pendant que le binaire en portait six. +### ADR-040 — La mise à jour se déclenche depuis l'écran, `update.ps1` l'exécute + +**Statut** : accepté · **Date** : 29/07/2026 · **Portée** : `internal/update`, `internal/platform/update_*.go`, `internal/web/update.go`, `internal/station/workers.go`, `deploy/windows/`, §11.2 (bloc `update`), §11.3 (contrôle 48), §14.4 (page Mise à jour), §15.5 · **Complète** : ADR-033, ADR-037 + +**Contexte.** Mettre à jour un poste demandait qu'un humain télécharge une archive, la décompresse, ouvre une console **en administrateur** et lance `update.ps1`. Aucun bénévole de la coopérative ne fera ça. La conséquence est mesurable et elle est déjà arrivée à ce projet : un correctif publié reste sur GitHub, et le poste tourne sur la version qu'il avait le jour de son installation. + +**Décision.** Un bouton de l'écran d'administration interroge l'API des publications du dépôt suivi, télécharge l'archive de la plateforme, **vérifie son empreinte SHA-256** contre `SHA256SUMS-archives.txt`, l'extrait sous `/updates//`, puis lance **détaché** le `update.ps1` de cette archive. Le service tournant en `LocalSystem` (`internal/platform/service_windows.go` n'écrit aucun `ServiceStartName`), il n'y a **aucune élévation à obtenir** : le processus qui sert `/admin/api` peut déjà écrire dans `Program Files` et piloter le gestionnaire de services. + +**Pourquoi `update.ps1` et non du Go.** Le script fait déjà l'arrêt des détenteurs du binaire, la sauvegarde horodatée, la copie, le redémarrage, le contrôle de `/healthz` et le **retour arrière automatique**, et `deploy/deploy_test.go` le couvre. Une seconde implémentation de cette procédure divergerait — c'est ce que `station.ShutdownBudget()` a supprimé ailleurs. Le bouton fait donc littéralement ce que la notice demandait à un humain de faire. + +**Conséquence — `update.ps1` devient un contrat.** Six paramètres, quatre codes de sortie (`0`, `10` restauré et sain, `11` restauré et mort, `12` rien n'a bougé) et un `outcome.json` écrit **sur les quatre**. C'est le seul objet qui traverse : au moment où le script se termine, le processus qui aurait pu lire son code de retour est mort depuis une minute, puisque c'est ce script qui l'a arrêté. Le binaire qui démarre ensuite — le neuf **ou l'ancien restauré** — le relit, le verse au journal et le sert à l'écran. + +**Conséquence — deux défauts existants, payés par ce chantier.** La tâche du kiosque n'était relancée par personne après `schtasks /end`, et `openscale-kiosk.xml` n'a qu'un `LogonTrigger` : l'écran client restait **noir** après toute mise à jour, et après tout installeur rejoué sur un poste qui marche — le geste que `TROUBLESHOOTING.md` recommande. Le défaut a survécu parce qu'un humain qui met à jour finit par redémarrer le poste. Et `update.ps1` confondait « restauré, le poste marche » avec « restauré, le poste est mort », alors que le premier n'appelle personne. + +**Conséquence — le dépôt devient un réglage, en `owner/repo` et jamais en URL.** Le code est sous AGPL : une coopérative qui fait tourner son fork doit pouvoir le suivre. Mais c'est le seul champ du fichier qui désigne **d'où viendra du code privilégié**, et un champ acceptant une adresse entière ferait de « enregistrer la configuration » un « télécharger du code de n'importe où, et l'exécuter en `LocalSystem` ». L'hôte est compilé dans le binaire ; le contrôle 48 refuse tout ce qui n'est pas un couple. Le bloc entre dans `Export(false)`, donc dans l'empreinte : un poste qui suit un autre dépôt que ses voisins se voit sur les huit caractères du tableau de bord. + +**Contrepartie assumée — le modèle de menace.** L'empreinte vient de la même publication que l'archive : elle prouve que le téléchargement n'a pas été abîmé, **elle ne prouve pas que la publication est légitime**. Quiconque obtient le droit de publier sur le dépôt configuré obtient l'exécution de code privilégié sur les quatre postes. Le risque n'est pas neuf — un bénévole qui suit `INSTALLATION.md` télécharge et exécute la même archive sans vérifier davantage — mais le **délai** change : le bouton propage en une journée ce qu'un geste manuel propageait en plusieurs mois. Ce qui le contient : le contrôle 48, le mot de passe à l'enregistrement, la journalisation en `warn` de tout changement de dépôt, et la présence du dépôt dans `diagnostic.zip`. Une signature détachée — clé publique compilée, clé privée hors GitHub — reste possible plus tard sans rien casser : ce serait une vérification de plus dans `Stager`. + +**Contrepartie assumée — Windows seulement.** Les quatre postes de la coopérative sont sous Windows. `platform.ApplyUpdate` répond `ErrUpdateUnsupported` ailleurs, l'écran le **dit** au lieu de montrer un bouton mort, et `update.sh` reste manuel sur un Raspberry Pi. Écrire un chemin systemd que rien n'exécuterait ne prouverait rien et demanderait quand même d'être maintenu. + +**Le drapeau qui a été mesuré et non choisi.** `platform.ApplyUpdate` lance la PowerShell avec `CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP`, et **jamais `DETACHED_PROCESS`**. Les deux cachent la fenêtre ; un seul exécute le script. Mesuré au banc le 29/07/2026 sur quatre jeux de drapeaux : avec `DETACHED_PROCESS`, `powershell.exe` sort en 100 ms avec le code **0** sans lire son fichier — c'est une application console, et son hôte abandonne quand il n'a aucune console à attacher. Un échec silencieux avec un code de retour de réussite, sur le chemin dont personne ne regarde la sortie. Que l'enfant survive ensuite à l'arrêt du service par le gestionnaire de services a été mesuré le même jour : 113 des 120 lignes du témoin ont été écrites après l'arrêt. + +--- + --- ## 21. Inconnues à lever sur site diff --git a/docs/03-glossaire.md b/docs/03-glossaire.md index 034afe9..6d2454f 100644 --- a/docs/03-glossaire.md +++ b/docs/03-glossaire.md @@ -73,6 +73,7 @@ mot français recouvre plusieurs concepts (ou l'inverse) ; la section | `internal/plateforme/` | `internal/platform/` | Direct. | | `internal/plateforme/horloge.go` | `internal/platform/clock.go` | Seule implémentation réelle de `Clock`. | | `internal/diag/` | `internal/diag/` | Abréviation identique dans les deux langues. | +| — | `internal/update/` | Paquet neuf (ADR-040) : lire les versions publiées, télécharger, vérifier, préparer la bascule. Ne s'appelle pas `updater` — c'est le paquet qui met à jour, pas l'objet. | | `internal/obs/` | `internal/obs/` | Observabilité / observability : identique. | | `internal/assets/` | `internal/assets/` | Déjà anglais. | | `internal/web/dist` | `internal/web/dist` | Sortie Vite committée, inchangée. | @@ -861,6 +862,31 @@ sentinelles, familles de constantes préfixées par leur type, unités portées | `Font` · `Carlito` · `DejaVuSansCondensed` | La famille avec laquelle le moteur sait rendre — la valeur de la clé `font` d'un gabarit. `Carlito` est le clone métrique de Calibri retenu par ADR-020 ; `DejaVuSansCondensed` sert les gabarits neutres et le repli. | | `ErrUnknownFont` | Un gabarit qui nomme une police que le binaire ne porte pas. Erreur **typée** et non sentinelle, parce qu'elle doit dire LAQUELLE : `ErrUnknownFont.Font`. | +### `internal/update` + +Le paquet d'ADR-040. Tout y est calculable ; la seule fonction privilégiée vit dans +`internal/platform`. + +| Identifiant | Ce qu'il désigne | +| --- | --- | +| `Version` · `ParseVersion` · `(Version).Compare` · `IsPrerelease` · `String` | Un numéro de publication, ordonné. `Patch` est normalisé — `v0.1` et `0.1.0` nomment la même version, et la première publiée de ce dépôt s'appelait `v0.1`. Une préversion se classe SOUS sa propre version. | +| `ErrNotAVersion` | Un tag qui n'est pas un numéro : `banc-de-test`, `avant-migration`, et `dev` que porte un binaire compilé hors tag. | +| `Release` · `Asset` · `(Release).Asset` | Une publication et ses fichiers joints. `Tag` est le tag **tel que publié** — c'est de lui que dérive le nom des archives, jamais de `Version.String()`. `Asset.URL` est `browser_download_url` et jamais le champ `url`, qui rendrait du JSON décrivant l'archive. | +| `Source` · `GitHubSource` · `(GitHubSource).Latest` · `DefaultBaseURL` | L'interface déclarée côté consommateur, et son implémentation sur `/repos/{owner}/{repo}/releases/latest` — ce point d'entrée exclut déjà brouillons et préversions, c'est son contrat. `DefaultBaseURL` est **compilé** : ce qu'un fichier peut nommer est le dépôt, jamais l'hôte. | +| `ErrNoRelease` · `ErrUnreachable` | « ce dépôt n'a rien publié de stable » (404, **pas une panne**) contre « le serveur n'a pas pu être lu » (réseau, proxy, limite de débit). Les confondre annoncerait à un poste qu'il est à jour alors que personne n'en sait rien. | +| `Stager` · `Staged` · `(Stager).Stage` | Télécharge, compare à l'empreinte publiée, extrait. Rien n'est gardé sur un refus. L'empreinte est calculée **en écrivant**, jamais sur un tampon. | +| `ErrAssetMissing` · `ErrChecksumMismatch` · `ErrUnsafeArchive` | Pas d'archive pour cette plateforme (ou une archive qui ne porte pas ce que la bascule attend) ; l'archive ne correspond pas à son empreinte ; l'archive écrit hors de son répertoire. Le troisième n'est pas théorique : l'archive vient du réseau et un processus `LocalSystem` l'extrait. | +| `State` · `Check` · `Pending` · `Outcome` | L'état sur disque, sous `/updates/`. Des fichiers et non la base : rien ici ne vaut une migration, et un poste qui ne démarre pas doit pouvoir dire ce qui lui est arrivé — c'est le moment où une base est ce sur quoi on ne peut pas compter. | +| `(State).TakeOutcome` · `LastOutcome` · `ClearPending` | Consommer le compte rendu **une seule fois** (le renommage rend l'opération idempotente : un poste redémarré trois fois ne journalise pas trois fois la même bascule), servir le dernier à l'écran, abandonner une bascule qui ne rendra jamais compte. | +| `SwapBudget` · `(Pending).Stale` | Quinze minutes, au-delà desquelles une bascule est réputée n'avoir jamais démarré. Sans cette borne, un `Start()` qui rend `nil` sans rien lancer murait le poste : `pending.json` écrit, aucun `outcome.json` à venir, et `ErrAlreadyRunning` opposé à toute mise à jour ultérieure. Un instant de départ **nul** compte comme périmé. | +| `StatusSucceeded` · `StatusRolledBack` · `StatusRolledBackUnhealthy` · `StatusNotStarted` | Les quatre issues qu'`update.ps1` écrit, et que l'écran dit en quatre phrases différentes. | +| `Service` · `Paths` · `Status` · `(Service).Check` · `Status` · `Apply` | L'orchestration. `Apply` écrit `pending.json` **avant** de rendre la main : le script arrête le service, donc rien d'écrit après ne le serait jamais. `Status` lit le disque et ne sonde pas — la page doit se dessiner tout de suite. | +| `Guard` | « le poste peut-il tomber ? », posée au Hub. Déclarée ici, côté consommateur ; `*station.Hub` la satisfait. | +| `ErrVersionMoved` · `ErrBusy` · `ErrAlreadyRunning` · `BusyError` | Trois refus qui ne se confondent pas. `BusyError` est un **type** et non un message formaté, parce que la phrase remonte telle quelle jusqu'à l'écran : la retrouver en coupant un préfixe casserait à la première reformulation. | +| `platform.UpdateSpec` · `platform.ApplyUpdate` · `platform.ErrUpdateUnsupported` | La seule fonction privilégiée. Les chemins sont **absolus** et viennent du processus qui tourne, jamais des défauts du script — qui pointent `Program Files`. | +| `station.Poller` · `(Station).runUpdateWorker` | Le sondage quotidien, sur l'horloge injectée. `Poller` rend une **chaîne** et ne nomme aucun type d'`internal/update` : la station n'a pas à savoir ce qu'est une publication. | +| `domain.UpdateConfig` · `DefaultUpdateRepository` | Le bloc `update` du fichier. Un couple `owner/repo` et jamais une URL — c'est le seul champ qui désigne d'où viendra du code privilégié. L'absence de la clé est **légale** et vaut le défaut. | + ### Codes `ERR-xxx-nn` réellement alloués par le code La table de correspondance `BAL→SCL`, `IMP→PRN`, `BDD→DB`, `IHM→UI`, `KIO→KSK` reste celle @@ -882,6 +908,16 @@ français. | `ERR-DB-02` | `store.ErrSchemaFromNewerVersion` | « base créée par une version plus récente (schéma N, ce binaire connaît M). Mettez l'application à jour. » | | `ERR-KSK-02` | `kiosk.CodeCrashLoop` — page de secours du superviseur | « Le poste rencontre un problème » + « l'affichage n'arrive pas à rester ouvert (N arrêts en moins de 10 secondes dans la dernière heure) ». C'est le seul code que §15.2 attribue au kiosque, et il est réservé à la BOUCLE DE PLANTAGE : la page d'attente (« Le poste redémarre… ») n'en porte aucun, parce que §15.4 n'en donne aucun à cette ligne et qu'un code inventé serait répété au téléphone comme si un binaire l'émettait. | +| `ERR-UPD-01` | `web.codeUpdateUnreachable` | « Impossible de joindre le serveur des versions. » Le réseau, un proxy qui rend du HTML, une limite de débit épuisée. **Distinct de « aucune version publiée »**, qui n'est pas une panne et répond 200 avec une phrase : les confondre annoncerait à un poste qu'il est à jour alors que personne n'en sait rien. | +| `ERR-UPD-02` | `web.codeUpdateChecksum` | « Le fichier téléchargé est abîmé. Rien n'a été installé. » L'archive ne correspond pas à l'empreinte publiée ; le staging est effacé. | +| `ERR-UPD-03` | `web.codeUpdateBusy` | Le poste est occupé. **Le message est celui du garde-fou, rendu tel quel** — le Hub sait s'il s'agit d'une pesée ou d'un catalogue en attente de mise en service, la couche HTTP ne le sait pas, et paraphraser perdrait la seule information sur laquelle on peut agir. | +| `ERR-UPD-04` | `web.codeUpdateInFlight` | « Une mise à jour est déjà en cours. » Une bascule dont le budget de quinze minutes est dépassé n'est plus « en cours » : elle est effacée, et le poste réessaie. | +| `ERR-UPD-05` | `web.codeUpdateUnsupported` | « La mise à jour depuis l'écran n'existe que sous Windows. » La route existe et refuse, plutôt que d'être absente : un 404 enverrait chercher une faute de frappe. | +| `ERR-UPD-06` | *libellé de `outcome.json`* | La bascule a échoué et la version précédente a été remise ; **le poste fonctionne**. N'appelle personne. Le code sort d'`update.ps1` (`exit 10`), non d'une constante Go. | +| `ERR-UPD-07` | *libellé de `outcome.json`* | La bascule a échoué **et le poste ne répond pas** (`exit 11`). Demande quelqu'un tout de suite : `openscale doctor`, et le chemin de la sauvegarde. | +| `ERR-UPD-08` | `web.codeUpdateNoArchive` | « Cette version ne contient pas de fichier pour ce poste. » Un fork qui a renommé ses archives, ou une publication sans `SHA256SUMS-archives.txt`. | +| `ERR-UPD-09` | `web.codeUpdateMoved` | « Une autre version est parue depuis l'affichage de cette page. Rechargez-la. » **Deux codes et non un avec `-03`** : « attendez un instant » et « rechargez la page » ne sont pas la même instruction. | + > **Codes cités mais pas encore alloués.** `ERR-SCL-08` (poignée déjà relâchée, journalisée > par le Hub), `ERR-CAT-03` et `ERR-CAT-05` (échec de contenu, fichier lu mais non > supprimé) apparaissent dans des commentaires et des tests de L2/L3, portés par la couche @@ -1153,12 +1189,17 @@ Commandes SBPL ``, ``, ``, `<#E>`, ``, `<%>`, ``, ``, `` dans l'aide de `openscale capture`. Le mot `balance` y désigne l'APPAREIL, et c'est le seul nom d'objet système où il subsiste. -### « dépôt » : deux sens +### « dépôt » : trois sens Le répertoire de dépôt (`depot_local`) → `local_drop` / paquet `localdrop` / -`/catalog/incoming/` ; et les « 6 dépôts » de la couche SQLite (design pattern) → -`repositories`, dans le paquet `internal/store`. Ne pas confondre avec « dépôt Git » → -`repository` en prose seulement. +`/catalog/incoming/` ; les « 6 dépôts » de la couche SQLite (design pattern) → +`repositories`, dans le paquet `internal/store` ; et le **dépôt Git** → `repository`. + +Ce troisième sens n'était plus « en prose seulement » depuis ADR-040 : il porte la clé +`update.repository`, le champ `update.GitHubSource.Repository` et le paramètre `repository` +des trois méthodes de `update.Service`. C'est le SEUL des trois qui soit au singulier dans +le code, et c'est ce qui les distingue à la lecture : `repositories` est la couche SQLite, +`repository` est le dépôt Git, `local_drop` est un répertoire. ### « scrutation » → `poll` diff --git a/docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md b/docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md index 0dc3ea9..52374f0 100644 --- a/docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md +++ b/docs/superpowers/specs/2026-07-29-mise-a-jour-depuis-admin-design.md @@ -1,7 +1,9 @@ # Mettre à jour un poste depuis l'écran d'administration -> Conception validée le 29/07/2026. Elle décrit ce qui doit être construit, pas ce qui -> existe. L'implémentation n'a pas commencé. +> Conception validée le 29/07/2026, **implémentée le même jour**. Ce document décrit ce +> qui a été construit ; les quatre points où la construction l'a corrigé sont réunis au +> § « Ce que l'implémentation a corrigé dans cette spécification », et reportés là où ils +> portent. La référence tenue à jour reste `docs/02-architecture.md` (ADR-040, §15.5). ## Le problème @@ -369,7 +371,12 @@ brouillon et le panneau de mot de passe des autres pages de réglage. ## Erreurs et journal -Huit codes, préfixe `ERR-UPD`, chacun avec un message français complet. +Neuf codes, préfixe `ERR-UPD`, chacun avec un message français complet. + +> **Neuf et non huit.** Cette spécification en prévoyait huit et faisait porter par +> `ERR-UPD-03` — le garde-fou — le cas « la version a changé depuis l'affichage ». Ce sont +> deux refus qui ne demandent pas la même chose à un bénévole : l'un dit « attendez un +> instant », l'autre « rechargez la page ». L'implémentation les a séparés. | Code | Quand | Message | |---|---|---| @@ -381,6 +388,7 @@ Huit codes, préfixe `ERR-UPD`, chacun avec un message français complet. | `ERR-UPD-06` | `outcome.json` dit `rolled-back` | « La mise à jour a échoué, la version précédente a été remise. Le poste fonctionne. » | | `ERR-UPD-07` | `outcome.json` dit `rolled-back-unhealthy` | « La mise à jour a échoué et le poste ne répond pas. Lancez `openscale doctor`. » | | `ERR-UPD-08` | Archive absente de la Release | « Cette version ne contient pas de fichier pour ce poste. » | +| `ERR-UPD-09` | La version affichée n'est plus celle qui serait installée | « Une autre version est parue depuis l'affichage de cette page. Rechargez-la. » | Journal technique : `info` pour une vérification et une version trouvée ; `warn` pour un sondage échoué et pour un **changement de dépôt** ; `error` pour `-02`, `-06` et `-07`. Le @@ -431,6 +439,31 @@ Dit ici pour ne pas être redemandé : - pas de mise à jour des quatre postes depuis un seul écran ; - pas d'hébergeur autre que GitHub. +## Ce que l'implémentation a corrigé dans cette spécification + +Quatre écarts, tous assumés et tous nés d'un test ou d'une mesure. + +1. **`ERR-UPD-09`**, un neuvième code — voir l'encadré ci-dessus. +2. **`Status.Supported` est un champ câblé** (`runtime.GOOS == "windows"`) et non une + déduction faite en appelant `ApplyUpdate` : la déduction lancerait une PowerShell pour + répondre à une question. +3. **La pastille du tableau de bord voyage dans `/admin/api/health`**, et non sur la route + de la page Mise à jour. `web/test/admin-two-levels.test.ts` tient la page bénévole à + **une seule route** : un second appel, même libre, aurait élargi pour une courtoisie ce + que fait un écran ouvert sans mot de passe. D'où le champ `new_version`, vide quand il + n'y a rien à dire — y compris quand le service n'a pas pu lire. +4. **`CREATE_NO_WINDOW` et non `DETACHED_PROCESS`.** Mesuré au banc le 29/07/2026 : + `powershell.exe` est une application console, et sans console à attacher son hôte + abandonne — sortie en 100 ms, code **0**, script jamais lu. Le drapeau dont le nom dit + « détaché » est celui qui ne lance rien. Compte rendu : + `docs/superpowers/plans/2026-07-29-banc-detached-process.md`. + +Et une conséquence que le banc a mise au jour, tranchée à l'implémentation : un `Start()` +qui rend `nil` sans rien lancer laissait un `pending.json` éternel, et `ErrAlreadyRunning` +aurait refusé **toute** mise à jour ultérieure — un poste muré par un échec qui n'a rien +écrit nulle part. `SwapBudget` (quinze minutes) et `Pending.Stale` ferment cette porte, +un instant de départ nul comptant comme périmé. + ## Documentation touchée - `docs/02-architecture.md` — une section, et **ADR-040 : la mise à jour se déclenche depuis From 1d36494a53b47e6506951f4ffbc7fbf29d221022 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 17:07:30 +0200 Subject: [PATCH 18/19] fix(admin): la page ne compilait pas, et seul svelte-check le disait Deux erreurs de type que ni vitest ni go test ne voient, et que la CI a attrapees. `let state = $state(null)` : une variable nommee `state` rend `$state` AMBIGU -- Svelte lit le prefixe `$` comme l'abonnement au store du meme nom, si bien que la rune elle-meme devient "variable utilisee avant sa declaration". Le fichier se compilait assez pour que les tests passent, et pas assez pour `npm run check`. La variable s'appelle `current`, et le commentaire dit pourquoi. Et le rappel du bouton lisait `current.latest` : il s'execute plus tard, et TypeScript ne peut pas savoir que `current` sera encore non nul a ce moment-la. La version est desormais capturee dans un `$derived`, ce qui est de toute facon plus juste -- c'est la chaine AFFICHEE que le service exige dans le corps de la demande. Ma verification omettait `npm run check`, que la CI lance entre `npm ci` et `vitest`. Elle ne l'omet plus. --- internal/web/dist/admin.html | 4 +- internal/web/dist/assets/admin-DNV6wY58.js | 1 + internal/web/dist/assets/admin-UjwWpqPm.js | 1 - .../{index-DzH6V9Hl.js => index-87_aneK-.js} | 4 +- internal/web/dist/assets/mount-BjqIypvA.js | 93 +++++++++++++++++++ internal/web/dist/assets/mount-D4FDGy22.js | 93 ------------------- internal/web/dist/index.html | 2 +- web/src/admin/pages/Update.svelte | 54 +++++++---- 8 files changed, 133 insertions(+), 119 deletions(-) create mode 100644 internal/web/dist/assets/admin-DNV6wY58.js delete mode 100644 internal/web/dist/assets/admin-UjwWpqPm.js rename internal/web/dist/assets/{index-DzH6V9Hl.js => index-87_aneK-.js} (99%) create mode 100644 internal/web/dist/assets/mount-BjqIypvA.js delete mode 100644 internal/web/dist/assets/mount-D4FDGy22.js diff --git a/internal/web/dist/admin.html b/internal/web/dist/admin.html index 20f92d7..e254f8a 100644 --- a/internal/web/dist/admin.html +++ b/internal/web/dist/admin.html @@ -5,9 +5,9 @@ Administration - + - + diff --git a/internal/web/dist/assets/admin-DNV6wY58.js b/internal/web/dist/assets/admin-DNV6wY58.js new file mode 100644 index 0000000..e7f5f31 --- /dev/null +++ b/internal/web/dist/assets/admin-DNV6wY58.js @@ -0,0 +1 @@ +import"./app-Dn-BjSsJ.js";import{mountAdmin as m}from"./mount-BjqIypvA.js";m(document.getElementById("app")); diff --git a/internal/web/dist/assets/admin-UjwWpqPm.js b/internal/web/dist/assets/admin-UjwWpqPm.js deleted file mode 100644 index 928e6d0..0000000 --- a/internal/web/dist/assets/admin-UjwWpqPm.js +++ /dev/null @@ -1 +0,0 @@ -import"./app-Dn-BjSsJ.js";import{mountAdmin as m}from"./mount-D4FDGy22.js";m(document.getElementById("app")); diff --git a/internal/web/dist/assets/index-DzH6V9Hl.js b/internal/web/dist/assets/index-87_aneK-.js similarity index 99% rename from internal/web/dist/assets/index-DzH6V9Hl.js rename to internal/web/dist/assets/index-87_aneK-.js index 406f232..56c4f70 100644 --- a/internal/web/dist/assets/index-DzH6V9Hl.js +++ b/internal/web/dist/assets/index-87_aneK-.js @@ -1,2 +1,2 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mount-D4FDGy22.js","assets/app-Dn-BjSsJ.js","assets/app-DWjtdCrW.css","assets/mount-C1tpIbdA.css"])))=>i.map(i=>d[i]); -import{e as Te,u as Ne,c as Re,r as Oe,a as We,D as Be,S as He,d as le,p as ne,b as T,i as B,I as ie,t as N,f as H,g as m,h as ae,j as y,k as t,s as p,l as oe,m as ce,n as te,o as z,q as h,v as u,w as ze,x as ve,y as Fe,z as x,A as D,B as ue,C as Ue,E as Ge,F as Me,G as Ve,H as Xe,J as Ke,K as Ze,L as Je,M as Ye,N as Qe,O as $e,P as et,Q as tt}from"./app-Dn-BjSsJ.js";class be{#e=new WeakMap;#t;#n;static entries=new WeakMap;constructor(e){this.#n=e}observe(e,a){var r=this.#e.get(e)||new Set;return r.add(a),this.#e.set(e,r),this.#r().observe(e,this.#n),()=>{var i=this.#e.get(e);i.delete(a),i.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??(this.#t=new ResizeObserver(e=>{for(var a of e){be.entries.set(a.target,a);for(var r of this.#e.get(a.target)||[])r(a)}}))}}var nt=new be({box:"border-box"});function we(n,e,a){var r=nt.observe(n,()=>a(n[e]));Te(()=>(Ne(()=>a(n[e])),r))}function he(n,e){return n===e||n?.[He]===e}function Ce(n={},e,a,r){var i=Re.r,l=We;return Te(()=>{var d,s;return Oe(()=>{d=s,s=[],Ne(()=>{he(a(...s),n)||(e(n,...s),d&&he(a(...d),n)&&e(null,...d))})}),()=>{let f=l;for(;f!==i&&f.parent!==null&&f.parent.f&Be;)f=f.parent;const v=()=>{s&&he(a(...s),n)&&e(null,...s)},c=f.teardown;f.teardown=()=>{v(),c?.()}}}),n}const at="modulepreload",rt=function(n){return"/"+n},Ee={},st=function(e,a,r){let i=Promise.resolve();if(a&&a.length>0){let f=function(v){return Promise.all(v.map(c=>Promise.resolve(c).then(E=>({status:"fulfilled",value:E}),E=>({status:"rejected",reason:E}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),s=d?.nonce||d?.getAttribute("nonce");i=f(a.map(v=>{if(v=rt(v),v in Ee)return;Ee[v]=!0;const c=v.endsWith(".css"),E=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${v}"]${E}`))return;const A=document.createElement("link");if(A.rel=c?"stylesheet":at,c||(A.as="script"),A.crossOrigin="",A.href=v,s&&A.setAttribute("nonce",s),document.head.appendChild(A),c)return new Promise((w,C)=>{A.addEventListener("load",w),A.addEventListener("error",()=>C(new Error(`Unable to preload CSS for ${v}`)))})}))}function l(d){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=d,window.dispatchEvent(s),!s.defaultPrevented)throw d}return i.then(d=>{for(const s of d||[])s.status==="rejected"&&l(s.reason);return e().catch(l)})};var it=y('

                                                                                                                                    kg

                                                                                                                                    ',1),ot=y('

                                                                                                                                    —kg

                                                                                                                                    Poids indisponible

                                                                                                                                    ',1),lt=y('');function ct(n,e){ne(e,!0);const a=T(e,"taring",3,!1),r=h(()=>{if(e.linkBanner!=="")return e.linkBanner;const I=e.snapshot?.message??null;if(I!==null)return I.text;switch(e.snapshot?.state){case"product_armed":return"Posez votre produit";case"awaiting_stability":return"Pesée en cours…";case"printing":return"Étiquette en cours…";case"entering_tare":return"Tapez la tare en grammes";case"scale_lost":return"Le poids n’est plus disponible.";default:return"Touchez votre produit, l’étiquette sort"}}),i=h(()=>e.snapshot===null?"var(--waiting)":e.snapshot.state==="faulted"||e.snapshot.state==="scale_lost"?"var(--fault)":e.snapshot.state==="rejected"?"var(--warning)":e.snapshot.state==="succeeded"||e.snapshot.weight.latched?"var(--ready)":"var(--waiting)"),l=h(()=>e.showWeight&&e.snapshot!==null&&e.snapshot.weight.available&&!e.snapshot.weight.expired),d=h(()=>{const I=e.snapshot?.message;return I!=null?I.level:e.snapshot?.state==="faulted"||e.snapshot?.state==="scale_lost"?"error":e.snapshot?.state==="rejected"?"warn":"info"});var s=lt(),f=u(s);let v;var c=u(f);{var E=I=>{var W=it(),F=ze(W),K=u(F),G=p(F,2),Q=u(G);N(()=>{z(K,e.snapshot.weight.net_text),z(Q,`tare ${e.snapshot.weight.tare_g??""} g`)}),m(I,W)},A=I=>{var W=ot();m(I,W)};B(c,I=>{t(l)&&e.snapshot?I(E):I(A,-1)})}var w=p(f,2);let C;var g=u(w),k=p(w,2);let O;var q=u(k);ie(q,{name:"tare",size:"1.4em"});var X=p(k,2);let U;N(()=>{oe(s,"data-state",e.snapshot?.state??"initializing"),v=ce(f,"",v,{"border-left-color":t(i)}),C=te(w,1,"instruction svelte-4gizvm",null,C,{warn:t(d)==="warn",error:t(d)==="error"}),z(g,t(r)),O=te(k,1,"tare-key touch-target svelte-4gizvm",null,O,{active:a()}),U=ce(X,"",U,{background:t(i)})}),H("click",k,function(...I){e.ontare?.apply(this,I)}),m(n,s),ae()}le(["click"]);const ut=4.5,dt=.1,vt={r:138,g:134,b:124};function qe(n){const e=/^#([\da-f])([\da-f])([\da-f])$/iu.exec(n);if(e!==null){const[,d,s,f]=e;return{r:Number.parseInt(d+d,16),g:Number.parseInt(s+s,16),b:Number.parseInt(f+f,16)}}const a=/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/iu.exec(n);if(a===null)return vt;const[,r,i,l]=a;return{r:Number.parseInt(r,16),g:Number.parseInt(i,16),b:Number.parseInt(l,16)}}function pe(n){const e=n/255;return e<=.03928?e/12.92:((e+.055)/1.055)**2.4}function ft({r:n,g:e,b:a}){return .2126*pe(n)+.7152*pe(e)+.0722*pe(a)}function gt(n){return 1.05/(ft(n)+.05)}function je(n){const{r:e,g:a,b:r}=qe(n),i=l=>Math.round(255-dt*(255-l));return`rgb(${i(e)}, ${i(a)}, ${i(r)})`}function De(n){let e=qe(n);for(let a=0;a<16&>(e) '),pt=y('');function _t(n,e){ne(e,!0);var a=pt(),r=u(a);ve(r,21,()=>e.chips,s=>s.code,(s,f)=>{var v=ht();let c,E;var A=p(u(v),2),w=u(A),C=p(A,2),g=u(C);N(k=>{c=te(v,1,"chip touch-target svelte-atgty1",null,c,{active:t(f).code===e.active}),oe(v,"aria-pressed",t(f).code===e.active),E=ce(v,"",E,k),z(w,t(f).label),z(g,t(f).count)},[()=>({"--chip-ink":De(t(f).color),"--chip-wash":je(t(f).color)})]),H("click",v,()=>e.onselect(t(f).code)),m(s,v)});var i=p(r,2);let l;var d=u(i);ie(d,{name:"search",size:"1.75rem"}),N(()=>{l=te(i,1,"chip touch-target search-key svelte-atgty1",null,l,{active:e.searchFieldOpen}),oe(i,"aria-pressed",e.searchFieldOpen)}),H("click",i,function(...s){e.onopensearch?.apply(this,s)}),m(n,a),ae()}le(["click"]);var bt=y(''),mt=y('

                                                                                                                                    '),yt=y('

                                                                                                                                    ');function ke(n,e){ne(e,!0);const a=T(e,"action",3,null);var r=yt(),i=u(r),l=u(i),d=u(l);ie(d,{name:"alert",size:"3rem"});var s=p(l,2),f=u(s),v=p(s,2),c=u(v),E=p(v,2);{var A=g=>{var k=bt(),O=u(k);N(()=>z(O,a().label)),H("click",k,function(...q){a().run?.apply(this,q)}),m(g,k)};B(E,g=>{a()!==null&&g(A)})}var w=p(E,2);{var C=g=>{var k=mt(),O=u(k);N(()=>z(O,e.code)),m(g,k)};B(w,g=>{e.code!==""&&g(C)})}N(()=>{z(f,e.title),z(c,e.detail)}),m(n,r),ae()}le(["click"]);const fe=34,xe=18,wt=.5,Et=.005,kt=1.15,Le=90,me=100;function xt(n,e){return Math.max(1,Math.floor(e/(n*kt)))}function At(n,e,a,r=Le){const i=St(n,a);if(i.length===0||e<=0)return fe;const l=Pt(a),d=e*(1-Et);for(let s=fe;s>xe;s-=wt){const f=s/me;if(Tt(i,l,f,d)<=xt(s,r))return s}return xe}const It=/(?<=[-‐–—])(?!\d)/u;function St(n,e){const a=[];for(const r of n.split(/\s+/u).filter(i=>i.length>0)){const i=r.split(It).filter(l=>l.length>0);for(const[l,d]of i.entries())a.push({width:e(d),length:[...d].length,spaced:l===0})}return a}function Pt(n){return Math.max(0,n("a a")-n("aa"))}function Tt(n,e,a,r){let i=1,l=0;for(const d of n){const s=d.width*a,f=d.spaced?e*a:0;if(l>0&&l+f+s<=r){l+=f+s;continue}if(l>0&&(i++,l=0),s<=r){l=s;continue}i+=Nt(s,d.length,r)-1,l=r}return i}function Nt(n,e,a){const r=Math.max(1,Math.floor(a*e/n));return Math.ceil(e/r)}function zt(n,e){const r=document.createElement("canvas").getContext("2d");if(r===null||typeof r.measureText!="function")return null;r.font=`${e} ${me}px ${n}`;const i=new Map;return l=>{const d=i.get(l);if(d!==void 0)return d;const s=r.measureText(l).width;return i.set(l,s),s}}var Mt=y(''),Ct=y(''),qt=y(' '),jt=y(''),Dt=y('');function Lt(n,e){ne(e,!0);const a=T(e,"nameSizePx",3,fe),r=T(e,"categoryColor",3,"#8a867c"),i=T(e,"primaryCode",3,""),l=T(e,"tierAbbrev",19,()=>({})),d=T(e,"selected",3,!1),s=T(e,"rejected",3,!1),f=T(e,"busy",3,!1),v=T(e,"showPrice",3,!0);let c=D("");const E=h(()=>e.product.image_url!==""&&e.product.image_url!==t(c)),A=h(()=>g(e.product.name)),w=h(()=>je(r())),C=h(()=>De(r()));function g(S){const M=S.match(/\p{L}/u);return M===null?"·":M[0].toUpperCase()}var k=Dt();let O;var q=u(k);let X;var U=u(q);{var I=S=>{var M=Mt();N(()=>oe(M,"src",e.product.image_url)),Fe("error",M,()=>x(c,e.product.image_url,!0)),m(S,M)},W=S=>{var M=Ct();let Z;var J=u(M);N(()=>{Z=ce(M,"",Z,{color:t(C)}),z(J,t(A))}),m(S,M)};B(U,S=>{t(E)?S(I):S(W,-1)})}var F=p(q,2),K=u(F);let G;var Q=u(K),_=p(F,2);{var P=S=>{var M=jt();ve(M,21,()=>e.product.prices,Z=>Z.code,(Z,J)=>{var b=qt();let L;var j=u(b);let R;var ee=u(j),Y=p(j,2),$=u(Y),re=p(Y,2),se=u(re);N(de=>{L=te(b,1,"price svelte-1ctu6qg",null,L,{secondary:t(J).code!==i()}),R=te(j,1,"abbrev svelte-1ctu6qg",null,R,{hollow:t(J).code!==i()}),z(ee,l()[t(J).code]??""),z($,t(J).text),z(se,de)},[()=>e.product.price_suffix.trim()]),m(Z,b)}),m(S,M)};B(_,S=>{v()&&S(P)})}N(()=>{O=te(k,1,"tile touch-target svelte-1ctu6qg",null,O,{selected:d(),rejected:s()}),oe(k,"data-product-id",e.product.id),k.disabled=f(),X=ce(q,"",X,{background:t(w)}),G=ce(K,"",G,{"font-size":`${a()??""}px`}),z(Q,e.product.name)}),H("pointerdown",k,()=>e.onpick(e.product)),m(n,k),ae()}le(["pointerdown"]);var Rt=y('
                                                                                                                                    '),Ot=y('

                                                                                                                                    Chargement du catalogue…

                                                                                                                                    ',1),Wt=y('

                                                                                                                                    '),Bt=y('

                                                                                                                                    '),Ht=y('
                                                                                                                                    '),Ft=y('
                                                                                                                                    '),Ut=y('
                                                                                                                                    ');function Gt(n,e){ne(e,!0);const a=T(e,"colors",19,()=>({})),r=T(e,"primaryCode",3,""),i=T(e,"tierAbbrev",19,()=>({})),l=T(e,"selectedID",3,null),d=T(e,"rejectedID",3,null),s=T(e,"busyID",3,null),f=T(e,"showPrices",3,!0),v=T(e,"loading",3,!1),c=T(e,"emptyMessage",3,"Aucun produit ne correspond."),E=T(e,"emptyHint",3,""),A=230,w=8,C=26,g="'Inter Variable', system-ui, sans-serif",k="'Inter Variable'",O=16;let q=D(0),X=D(!1);ue(()=>{const b=document.fonts;b!==void 0&&b.load(`700 ${me}px ${k}`).catch(()=>[]).then(()=>{x(X,!0)})});const U=h(()=>(t(X),zt(g,700)));let I=D(null),W=D(0),F=D(0);const K=h(()=>{if(t(q)<=0)return 0;const b=Math.max(1,Math.floor((t(q)+w)/(A+w)));return(t(q)-(b-1)*w)/b-C}),G=h(()=>t(W)>0?t(W):t(K));ue(()=>{t(q),e.products.length;const b=t(I)?.querySelector(".name-box");x(W,b?.clientWidth??0,!0)});const Q=h(()=>{const b=new Map;if(t(U)===null||t(G)<=0)return b;const L=t(F)>0?t(F):Le;for(const j of e.products)b.set(j.id,At(j.name,t(G),t(U),L));return b});var _=Ut(),P=u(_),S=p(P,2);{var M=b=>{var L=Ot(),j=ze(L);ve(j,21,()=>({length:O}),Ue,(R,ee)=>{var Y=Rt();m(R,Y)}),m(b,L)},Z=b=>{var L=Bt(),j=u(L),R=u(j),ee=p(j,2);{var Y=$=>{var re=Wt(),se=u(re);N(()=>z(se,E())),m($,re)};B(ee,$=>{E()!==""&&$(Y)})}N(()=>z(R,c())),m(b,L)},J=b=>{var L=Ft();ve(L,21,()=>e.products,j=>j.id,(j,R)=>{var ee=Ht(),Y=u(ee);{let $=h(()=>t(Q).get(t(R).id)??fe),re=h(()=>t(R).id===l()),se=h(()=>t(R).id===d()),de=h(()=>t(R).id===s());Lt(Y,{get product(){return t(R)},get nameSizePx(){return t($)},get categoryColor(){return a()[t(R).category_code]},get primaryCode(){return r()},get tierAbbrev(){return i()},get selected(){return t(re)},get rejected(){return t(se)},get busy(){return t(de)},get showPrice(){return f()},get onpick(){return e.onpick}})}m(j,ee)}),N(()=>oe(L,"data-tile-count",e.products.length)),m(b,L)};B(S,b=>{v()?b(M):e.products.length===0?b(Z,1):b(J,-1)})}Ce(_,b=>x(I,b),()=>t(I)),we(P,"clientHeight",b=>x(F,b)),we(_,"clientWidth",b=>x(q,b)),m(n,_),ae()}var Vt=y(''),Xt=y('
                                                                                                                                    ');function Kt(n,e){ne(e,!0);let a=D(null);ue(()=>{t(a)?.focus()});var r=Xt(),i=u(r);ie(i,{name:"search",size:"1.75rem"});var l=p(i,2);Ce(l,c=>x(a,c),()=>t(a));var d=p(l,2);{var s=c=>{var E=Vt();H("click",E,function(...A){e.onclose?.apply(this,A)}),m(c,E)};B(d,c=>{e.query!==""&&c(s)})}var f=p(d,2),v=u(f);ie(v,{name:"close",size:"1.5rem"}),N(()=>Ge(l,e.query)),H("input",l,c=>e.onquery(c.currentTarget.value)),H("keydown",l,c=>{c.key==="Escape"?(c.preventDefault(),e.onclose()):c.key==="Enter"&&(c.preventDefault(),e.onenter())}),H("click",f,function(...c){e.onclose?.apply(this,c)}),m(n,r),ae()}le(["input","keydown","click"]);function Zt(n){if(n==="")return"";const e=new Date(n);if(Number.isNaN(e.getTime()))return"";const a=i=>String(i).padStart(2,"0");return`${`${a(e.getDate())}/${a(e.getMonth()+1)}/${e.getFullYear()}`} ${a(e.getHours())}:${a(e.getMinutes())}:${a(e.getSeconds())}`}function Jt(n){return n.mode==="by_unit"?`${n.product_name} ${n.quantity} ${n.quantity>1?"unités":"unité"}`:`${n.product_name} ${n.net_text} kg`}var Yt=y('Catalogue du '),Qt=y('Catalogue en attente'),$t=y(' '),en=y('aucune pour le moment'),tn=y(' '),nn=y(''),an=y('
                                                                                                                                    Dernière étiquette
                                                                                                                                    ');function rn(n,e){ne(e,!0);const a=T(e,"catalogAt",3,""),r=T(e,"productCount",3,0),i=T(e,"appVersion",3,""),l=h(()=>[r()>0?`${r()} produits pesables`:"",i()!==""?`application ${i()}`:""].filter(_=>_!=="").join(" · "));var d=an();let s;var f=u(d),v=u(f);{var c=_=>{var P=Yt(),S=p(u(P)),M=u(S);N(()=>z(M,a())),m(_,P)},E=_=>{var P=Qt();m(_,P)};B(v,_=>{a()!==""?_(c):_(E,-1)})}var A=p(v,2);{var w=_=>{var P=$t(),S=u(P);N(()=>z(S,t(l))),m(_,P)};B(A,_=>{t(l)!==""&&_(w)})}var C=p(f,4),g=p(u(C),2);{var k=_=>{var P=en();m(_,P)},O=_=>{var P=tn(),S=u(P);N(M=>z(S,M),[()=>Jt(e.label)]),m(_,P)};B(g,_=>{e.label===null?_(k):_(O,-1)})}var q=p(C,2);{var X=_=>{var P=nn();N(()=>P.disabled=!e.available),H("click",P,function(...S){e.onreprint?.apply(this,S)}),m(_,P)};B(q,_=>{e.label!==null&&_(X)})}var U=p(q,2),I=u(U);let W;var F=p(I,2),K=u(F),G=p(U,2),Q=u(G);ie(Q,{name:"settings",size:"1.75rem"}),N(()=>{s=te(d,1,"bar svelte-161y12f",null,s,{live:e.label!==null}),W=te(I,1,"dot svelte-161y12f",null,W,{fault:!e.healthy}),oe(I,"aria-label",e.healthy?"Matériel disponible":"Matériel indisponible"),z(K,e.healthy?"Balance et imprimante disponibles":"Matériel indisponible")}),H("click",G,function(..._){e.onadmin?.apply(this,_)}),m(n,d),ae()}le(["click"]);var sn=y(''),on=y('

                                                                                                                                    Tare g

                                                                                                                                    ');function ln(n,e){ne(e,!0);const a=["1","2","3","4","5","6","7","8","9","0"];var r=on(),i=u(r),l=p(u(i),2),d=u(l),s=p(i,2);ve(s,20,()=>a,w=>w,(w,C)=>{var g=sn(),k=u(g);N(()=>z(k,C)),H("click",g,()=>e.ondigit(C)),m(w,g)});var f=p(s,2),v=u(f),c=p(v,2),E=p(c,2),A=u(E);ie(A,{name:"check",size:"1.5rem"}),N(()=>z(d,e.grams===""?"0":e.grams)),H("click",v,function(...w){e.onclear?.apply(this,w)}),H("click",c,function(...w){e.oncancel?.apply(this,w)}),H("click",E,function(...w){e.onconfirm?.apply(this,w)}),m(n,r),ae()}le(["click"]);function cn(n){return n===null||n.state==="initializing"?!1:n.scale.connected&&n.printer.health!=="faulted"}const Ae=1500,un=2e3,Ie=5e3;function dn(n,e){return e?n>=Ie?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!0}:n>=un?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!1}:n>=Ae?{showWeight:!1,banner:"",mustReconnect:!1}:{showWeight:!0,banner:"",mustReconnect:!1}:{showWeight:n=Ie}}const vn=250,fn={show_grid_prices:!0,idle_timeout_s:45,reprint_window_s:60,sound:!0};class gn{#e=D(null);get state(){return t(this.#e)}set state(e){x(this.#e,e,!0)}#t=D(null);get catalog(){return t(this.#t)}set catalog(e){x(this.#t,e,!0)}#n=D(Me({showWeight:!0,banner:"",mustReconnect:!1}));get link(){return t(this.#n)}set link(e){x(this.#n,e,!0)}#r=D("");get catalogError(){return t(this.#r)}set catalogError(e){x(this.#r,e,!0)}#s=null;#i=null;#o=Date.now();#a=!1;#l=!1;get presentation(){return this.catalog?.presentation??fn}start(){this.#u(),this.#c(),this.#i=setInterval(()=>this.#v(),vn)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null,this.#s?.close(),this.#s=null,this.#a=!1}#c(){const e=new EventSource("/api/v1/stream");this.#s=e,e.onopen=()=>{this.#a=!0,this.#o=Date.now()},e.addEventListener("state",a=>this.#d(a)),e.onerror=()=>{this.#a=!1}}#d(e){this.#o=Date.now(),this.#a=!0;const a=JSON.parse(e.data);this.state=a,this.catalog!==null&&a.catalog_count!==this.catalog.product_count&&this.#u()}#v(){const e=dn(Date.now()-this.#o,this.#a);this.link=e,e.mustReconnect&&(this.#s?.close(),this.#o=Date.now(),this.#c())}async#u(){if(!this.#l){this.#l=!0;try{this.catalog=await Ve(),this.catalogError=""}catch{this.catalog===null&&(this.catalogError="Catalogue indisponible. Prévenez un responsable.")}finally{this.#l=!1}}}}const Se="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Pe(){let n=Date.now();const e=new Array(26);for(let r=9;r>=0;r--)e[r]=Se[n%32],n=Math.floor(n/32);const a=new Uint8Array(16);crypto.getRandomValues(a);for(let r=0;r<16;r++)e[10+r]=Se[a[r]%32];return e.join("")}var hn=y('
                                                                                                                                    ');function pn(n,e){ne(e,!0);const a=new gn;let r=D(Me(Xe)),i=D(""),l=D(!1);const d=h(()=>t(i)!==""||t(l));let s=D(null),f=D(-1),v=D(null);const c=h(()=>a.catalog),E=h(()=>t(c)===null?[]:Qe(t(c))),A=h(()=>t(c)===null?[]:Je(t(c))),w=h(()=>Ze(t(E),t(r),t(i))),C=h(()=>{const o={};for(const V of t(c)?.categories??[])o[V.code]=V.color;return o}),g=h(()=>a.state),k=h(()=>a.presentation),O=h(()=>cn(t(g))),q=h(()=>{if(a.catalogError!=="")return{message:a.catalogError,hint:"Le poste réessaie tout seul."};if(t(E).length>0)return{message:"Aucun produit ne correspond.",hint:"Effacez des lettres ou changez de rayon."};const o=t(g)?.station??0;return{message:o>0?`Catalogue vide. En attente du fichier flv_${o}.csv.`:"Catalogue vide. En attente du fichier du poste.",hint:"Prévenez un responsable : aucun produit ne peut être pesé."}}),X=h(()=>t(g)?.state==="rejected"?t(g).product?.id??null:null),U=h(()=>t(g)?.state==="faulted"||t(g)?.state==="out_of_service"),I=["product_armed","weight_present","weight_stable","awaiting_stability","validating","printing"],W=h(()=>t(g)!==null&&I.includes(t(g).state)?t(g).product?.id??null:null),F=h(()=>Zt(t(c)?.updated_at??""));ue(()=>(a.start(),()=>a.stop())),ue(()=>{const o=a.state?.revision??-1;t(s)!==null&&o>t(f)&&(x(s,null),x(f,-1))});async function K(o){if(t(s)===null){x(s,o.id,!0),x(f,a.state?.revision??-1,!0);try{await Ke({product_id:o.id,tare_g:a.state?.weight.tare_g??0,units:1,manual_weight_g:0,seen_weight_g:a.state?.weight.gross_g??0,measurement_seq:a.state?.weight.seq??0,key:Pe()})}catch{x(s,null)}}}async function G(){const o=a.state?.reprint.job_id;o===void 0||o===""||await Ye(o,Pe())}function Q(){x(i,""),x(l,!1)}function _(){x(l,!0)}function P(){const o=t(w).length===1?t(w)[0]:void 0;o!==void 0&&K(o)}function S(o){if(!(o.metaKey||o.ctrlKey||o.altKey)&&!(o.target instanceof HTMLElement&&o.target.tagName==="INPUT")&&!(t(v)!==null||t(U))){if(o.key==="Escape"){o.preventDefault(),Q();return}if(o.key==="Backspace"){o.preventDefault(),x(i,t(i).slice(0,-1),!0);return}if(o.key==="Enter"){P();return}o.key.length===1&&/[a-zA-Z0-9 ]/.test(o.key)&&(o.key===" "&&t(i)===""||(o.preventDefault(),x(i,t(i)+o.key)))}}ue(()=>(window.addEventListener("keydown",S),()=>window.removeEventListener("keydown",S)));async function M(){(await st(()=>import("./mount-D4FDGy22.js"),__vite__mapDeps([0,1,2,3]))).mountAdmin(document.body)}var Z=hn(),J=u(Z);{let o=h(()=>t(v)!==null);ct(J,{get snapshot(){return t(g)},get showWeight(){return a.link.showWeight},get linkBanner(){return a.link.banner},get taring(){return t(o)},ontare:()=>x(v,"")})}var b=p(J,2);{var L=o=>{ln(o,{get grams(){return t(v)},ondigit:V=>x(v,(t(v)??"")+V),onclear:()=>x(v,""),oncancel:()=>x(v,null),onconfirm:()=>x(v,null)})};B(b,o=>{t(v)!==null&&o(L)})}var j=p(b,2);{let o=h(()=>t(c)?.pricing.primary_code??""),V=h(()=>Object.fromEntries((t(c)?.pricing.tiers??[]).map(ye=>[ye.code,ye.abbrev]))),ge=h(()=>t(c)===null&&a.catalogError==="");Gt(j,{get products(){return t(w)},get colors(){return t(C)},get primaryCode(){return t(o)},get tierAbbrev(){return t(V)},get selectedID(){return t(W)},get rejectedID(){return t(X)},get busyID(){return t(s)},get showPrices(){return t(k).show_grid_prices},get loading(){return t(ge)},get emptyMessage(){return t(q).message},get emptyHint(){return t(q).hint},onpick:K})}var R=p(j,2);{var ee=o=>{Kt(o,{get query(){return t(i)},onquery:V=>x(i,V,!0),onclose:Q,onenter:P})};B(R,o=>{t(d)&&o(ee)})}var Y=p(R,2);_t(Y,{get chips(){return t(A)},get active(){return t(r)},get searchFieldOpen(){return t(d)},onselect:o=>x(r,o,!0),onopensearch:_});var $=p(Y,2);{let o=h(()=>t(g)?.last_label??null),V=h(()=>t(g)?.reprint.available??!1),ge=h(()=>t(c)?.app_version??"");rn($,{get label(){return t(o)},get available(){return t(V)},get catalogAt(){return t(F)},get productCount(){return t(E).length},get appVersion(){return t(ge)},get healthy(){return t(O)},onreprint:G,onadmin:M})}var re=p($,2);{var se=o=>{{let V=h(()=>t(g).message?.text??"Prévenez un responsable.");ke(o,{title:"Poste indisponible",get detail(){return t(V)},get code(){return t(g).fault_code},action:{label:"J’ai compris",run:()=>{$e()}}})}},de=o=>{ke(o,{title:"Poste hors service",detail:"Ce poste ne peut pas peser. Prévenez un responsable.",get code(){return t(g).fault_code}})};B(re,o=>{t(g)?.state==="faulted"?o(se):t(g)?.state==="out_of_service"&&o(de,1)})}m(n,Z),ae()}const _n=5;mn();document.addEventListener("contextmenu",n=>{bn(n.target)||n.preventDefault()});document.addEventListener("dragstart",n=>n.preventDefault());et(pn,{target:document.getElementById("app")});function bn(n){return n instanceof Element&&n.closest("[data-admin]")!==null}function mn(){let n=!1;const e=(a,r)=>{n||(n=!0,tt(a,r),yn(),setTimeout(()=>location.reload(),_n*1e3))};window.addEventListener("error",a=>e(a.message,a.error?.stack??"")),window.addEventListener("unhandledrejection",a=>e(String(a.reason),a.reason?.stack??""))}function yn(){const n=document.createElement("div");n.className="fatal",n.append(_e("h1","Une erreur est survenue"),_e("p","L’écran va se recharger tout seul."),_e("p","ERR-UI-01","code")),document.body.appendChild(n)}function _e(n,e,a=""){const r=document.createElement(n);return r.textContent=e,a!==""&&(r.className=a),r} +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/mount-BjqIypvA.js","assets/app-Dn-BjSsJ.js","assets/app-DWjtdCrW.css","assets/mount-C1tpIbdA.css"])))=>i.map(i=>d[i]); +import{e as Te,u as Ne,c as Re,r as Oe,a as We,D as Be,S as He,d as le,p as ne,b as T,i as B,I as ie,t as N,f as H,g as m,h as ae,j as y,k as t,s as p,l as oe,m as ce,n as te,o as z,q as h,v as u,w as ze,x as ve,y as Fe,z as x,A as D,B as ue,C as Ue,E as Ge,F as Me,G as Ve,H as Xe,J as Ke,K as Ze,L as Je,M as Ye,N as Qe,O as $e,P as et,Q as tt}from"./app-Dn-BjSsJ.js";class be{#e=new WeakMap;#t;#n;static entries=new WeakMap;constructor(e){this.#n=e}observe(e,a){var r=this.#e.get(e)||new Set;return r.add(a),this.#e.set(e,r),this.#r().observe(e,this.#n),()=>{var i=this.#e.get(e);i.delete(a),i.size===0&&(this.#e.delete(e),this.#t.unobserve(e))}}#r(){return this.#t??(this.#t=new ResizeObserver(e=>{for(var a of e){be.entries.set(a.target,a);for(var r of this.#e.get(a.target)||[])r(a)}}))}}var nt=new be({box:"border-box"});function we(n,e,a){var r=nt.observe(n,()=>a(n[e]));Te(()=>(Ne(()=>a(n[e])),r))}function he(n,e){return n===e||n?.[He]===e}function Ce(n={},e,a,r){var i=Re.r,l=We;return Te(()=>{var d,s;return Oe(()=>{d=s,s=[],Ne(()=>{he(a(...s),n)||(e(n,...s),d&&he(a(...d),n)&&e(null,...d))})}),()=>{let f=l;for(;f!==i&&f.parent!==null&&f.parent.f&Be;)f=f.parent;const v=()=>{s&&he(a(...s),n)&&e(null,...s)},c=f.teardown;f.teardown=()=>{v(),c?.()}}}),n}const at="modulepreload",rt=function(n){return"/"+n},Ee={},st=function(e,a,r){let i=Promise.resolve();if(a&&a.length>0){let f=function(v){return Promise.all(v.map(c=>Promise.resolve(c).then(E=>({status:"fulfilled",value:E}),E=>({status:"rejected",reason:E}))))};document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),s=d?.nonce||d?.getAttribute("nonce");i=f(a.map(v=>{if(v=rt(v),v in Ee)return;Ee[v]=!0;const c=v.endsWith(".css"),E=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${v}"]${E}`))return;const A=document.createElement("link");if(A.rel=c?"stylesheet":at,c||(A.as="script"),A.crossOrigin="",A.href=v,s&&A.setAttribute("nonce",s),document.head.appendChild(A),c)return new Promise((w,C)=>{A.addEventListener("load",w),A.addEventListener("error",()=>C(new Error(`Unable to preload CSS for ${v}`)))})}))}function l(d){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=d,window.dispatchEvent(s),!s.defaultPrevented)throw d}return i.then(d=>{for(const s of d||[])s.status==="rejected"&&l(s.reason);return e().catch(l)})};var it=y('

                                                                                                                                    kg

                                                                                                                                    ',1),ot=y('

                                                                                                                                    —kg

                                                                                                                                    Poids indisponible

                                                                                                                                    ',1),lt=y('');function ct(n,e){ne(e,!0);const a=T(e,"taring",3,!1),r=h(()=>{if(e.linkBanner!=="")return e.linkBanner;const I=e.snapshot?.message??null;if(I!==null)return I.text;switch(e.snapshot?.state){case"product_armed":return"Posez votre produit";case"awaiting_stability":return"Pesée en cours…";case"printing":return"Étiquette en cours…";case"entering_tare":return"Tapez la tare en grammes";case"scale_lost":return"Le poids n’est plus disponible.";default:return"Touchez votre produit, l’étiquette sort"}}),i=h(()=>e.snapshot===null?"var(--waiting)":e.snapshot.state==="faulted"||e.snapshot.state==="scale_lost"?"var(--fault)":e.snapshot.state==="rejected"?"var(--warning)":e.snapshot.state==="succeeded"||e.snapshot.weight.latched?"var(--ready)":"var(--waiting)"),l=h(()=>e.showWeight&&e.snapshot!==null&&e.snapshot.weight.available&&!e.snapshot.weight.expired),d=h(()=>{const I=e.snapshot?.message;return I!=null?I.level:e.snapshot?.state==="faulted"||e.snapshot?.state==="scale_lost"?"error":e.snapshot?.state==="rejected"?"warn":"info"});var s=lt(),f=u(s);let v;var c=u(f);{var E=I=>{var W=it(),F=ze(W),K=u(F),G=p(F,2),Q=u(G);N(()=>{z(K,e.snapshot.weight.net_text),z(Q,`tare ${e.snapshot.weight.tare_g??""} g`)}),m(I,W)},A=I=>{var W=ot();m(I,W)};B(c,I=>{t(l)&&e.snapshot?I(E):I(A,-1)})}var w=p(f,2);let C;var g=u(w),k=p(w,2);let O;var q=u(k);ie(q,{name:"tare",size:"1.4em"});var X=p(k,2);let U;N(()=>{oe(s,"data-state",e.snapshot?.state??"initializing"),v=ce(f,"",v,{"border-left-color":t(i)}),C=te(w,1,"instruction svelte-4gizvm",null,C,{warn:t(d)==="warn",error:t(d)==="error"}),z(g,t(r)),O=te(k,1,"tare-key touch-target svelte-4gizvm",null,O,{active:a()}),U=ce(X,"",U,{background:t(i)})}),H("click",k,function(...I){e.ontare?.apply(this,I)}),m(n,s),ae()}le(["click"]);const ut=4.5,dt=.1,vt={r:138,g:134,b:124};function qe(n){const e=/^#([\da-f])([\da-f])([\da-f])$/iu.exec(n);if(e!==null){const[,d,s,f]=e;return{r:Number.parseInt(d+d,16),g:Number.parseInt(s+s,16),b:Number.parseInt(f+f,16)}}const a=/^#([\da-f]{2})([\da-f]{2})([\da-f]{2})$/iu.exec(n);if(a===null)return vt;const[,r,i,l]=a;return{r:Number.parseInt(r,16),g:Number.parseInt(i,16),b:Number.parseInt(l,16)}}function pe(n){const e=n/255;return e<=.03928?e/12.92:((e+.055)/1.055)**2.4}function ft({r:n,g:e,b:a}){return .2126*pe(n)+.7152*pe(e)+.0722*pe(a)}function gt(n){return 1.05/(ft(n)+.05)}function je(n){const{r:e,g:a,b:r}=qe(n),i=l=>Math.round(255-dt*(255-l));return`rgb(${i(e)}, ${i(a)}, ${i(r)})`}function De(n){let e=qe(n);for(let a=0;a<16&>(e) '),pt=y('');function _t(n,e){ne(e,!0);var a=pt(),r=u(a);ve(r,21,()=>e.chips,s=>s.code,(s,f)=>{var v=ht();let c,E;var A=p(u(v),2),w=u(A),C=p(A,2),g=u(C);N(k=>{c=te(v,1,"chip touch-target svelte-atgty1",null,c,{active:t(f).code===e.active}),oe(v,"aria-pressed",t(f).code===e.active),E=ce(v,"",E,k),z(w,t(f).label),z(g,t(f).count)},[()=>({"--chip-ink":De(t(f).color),"--chip-wash":je(t(f).color)})]),H("click",v,()=>e.onselect(t(f).code)),m(s,v)});var i=p(r,2);let l;var d=u(i);ie(d,{name:"search",size:"1.75rem"}),N(()=>{l=te(i,1,"chip touch-target search-key svelte-atgty1",null,l,{active:e.searchFieldOpen}),oe(i,"aria-pressed",e.searchFieldOpen)}),H("click",i,function(...s){e.onopensearch?.apply(this,s)}),m(n,a),ae()}le(["click"]);var bt=y(''),mt=y('

                                                                                                                                    '),yt=y('

                                                                                                                                    ');function ke(n,e){ne(e,!0);const a=T(e,"action",3,null);var r=yt(),i=u(r),l=u(i),d=u(l);ie(d,{name:"alert",size:"3rem"});var s=p(l,2),f=u(s),v=p(s,2),c=u(v),E=p(v,2);{var A=g=>{var k=bt(),O=u(k);N(()=>z(O,a().label)),H("click",k,function(...q){a().run?.apply(this,q)}),m(g,k)};B(E,g=>{a()!==null&&g(A)})}var w=p(E,2);{var C=g=>{var k=mt(),O=u(k);N(()=>z(O,e.code)),m(g,k)};B(w,g=>{e.code!==""&&g(C)})}N(()=>{z(f,e.title),z(c,e.detail)}),m(n,r),ae()}le(["click"]);const fe=34,xe=18,wt=.5,Et=.005,kt=1.15,Le=90,me=100;function xt(n,e){return Math.max(1,Math.floor(e/(n*kt)))}function At(n,e,a,r=Le){const i=St(n,a);if(i.length===0||e<=0)return fe;const l=Pt(a),d=e*(1-Et);for(let s=fe;s>xe;s-=wt){const f=s/me;if(Tt(i,l,f,d)<=xt(s,r))return s}return xe}const It=/(?<=[-‐–—])(?!\d)/u;function St(n,e){const a=[];for(const r of n.split(/\s+/u).filter(i=>i.length>0)){const i=r.split(It).filter(l=>l.length>0);for(const[l,d]of i.entries())a.push({width:e(d),length:[...d].length,spaced:l===0})}return a}function Pt(n){return Math.max(0,n("a a")-n("aa"))}function Tt(n,e,a,r){let i=1,l=0;for(const d of n){const s=d.width*a,f=d.spaced?e*a:0;if(l>0&&l+f+s<=r){l+=f+s;continue}if(l>0&&(i++,l=0),s<=r){l=s;continue}i+=Nt(s,d.length,r)-1,l=r}return i}function Nt(n,e,a){const r=Math.max(1,Math.floor(a*e/n));return Math.ceil(e/r)}function zt(n,e){const r=document.createElement("canvas").getContext("2d");if(r===null||typeof r.measureText!="function")return null;r.font=`${e} ${me}px ${n}`;const i=new Map;return l=>{const d=i.get(l);if(d!==void 0)return d;const s=r.measureText(l).width;return i.set(l,s),s}}var Mt=y(''),Ct=y(''),qt=y(' '),jt=y(''),Dt=y('');function Lt(n,e){ne(e,!0);const a=T(e,"nameSizePx",3,fe),r=T(e,"categoryColor",3,"#8a867c"),i=T(e,"primaryCode",3,""),l=T(e,"tierAbbrev",19,()=>({})),d=T(e,"selected",3,!1),s=T(e,"rejected",3,!1),f=T(e,"busy",3,!1),v=T(e,"showPrice",3,!0);let c=D("");const E=h(()=>e.product.image_url!==""&&e.product.image_url!==t(c)),A=h(()=>g(e.product.name)),w=h(()=>je(r())),C=h(()=>De(r()));function g(S){const M=S.match(/\p{L}/u);return M===null?"·":M[0].toUpperCase()}var k=Dt();let O;var q=u(k);let X;var U=u(q);{var I=S=>{var M=Mt();N(()=>oe(M,"src",e.product.image_url)),Fe("error",M,()=>x(c,e.product.image_url,!0)),m(S,M)},W=S=>{var M=Ct();let Z;var J=u(M);N(()=>{Z=ce(M,"",Z,{color:t(C)}),z(J,t(A))}),m(S,M)};B(U,S=>{t(E)?S(I):S(W,-1)})}var F=p(q,2),K=u(F);let G;var Q=u(K),_=p(F,2);{var P=S=>{var M=jt();ve(M,21,()=>e.product.prices,Z=>Z.code,(Z,J)=>{var b=qt();let L;var j=u(b);let R;var ee=u(j),Y=p(j,2),$=u(Y),re=p(Y,2),se=u(re);N(de=>{L=te(b,1,"price svelte-1ctu6qg",null,L,{secondary:t(J).code!==i()}),R=te(j,1,"abbrev svelte-1ctu6qg",null,R,{hollow:t(J).code!==i()}),z(ee,l()[t(J).code]??""),z($,t(J).text),z(se,de)},[()=>e.product.price_suffix.trim()]),m(Z,b)}),m(S,M)};B(_,S=>{v()&&S(P)})}N(()=>{O=te(k,1,"tile touch-target svelte-1ctu6qg",null,O,{selected:d(),rejected:s()}),oe(k,"data-product-id",e.product.id),k.disabled=f(),X=ce(q,"",X,{background:t(w)}),G=ce(K,"",G,{"font-size":`${a()??""}px`}),z(Q,e.product.name)}),H("pointerdown",k,()=>e.onpick(e.product)),m(n,k),ae()}le(["pointerdown"]);var Rt=y('
                                                                                                                                    '),Ot=y('

                                                                                                                                    Chargement du catalogue…

                                                                                                                                    ',1),Wt=y('

                                                                                                                                    '),Bt=y('

                                                                                                                                    '),Ht=y('
                                                                                                                                    '),Ft=y('
                                                                                                                                    '),Ut=y('
                                                                                                                                    ');function Gt(n,e){ne(e,!0);const a=T(e,"colors",19,()=>({})),r=T(e,"primaryCode",3,""),i=T(e,"tierAbbrev",19,()=>({})),l=T(e,"selectedID",3,null),d=T(e,"rejectedID",3,null),s=T(e,"busyID",3,null),f=T(e,"showPrices",3,!0),v=T(e,"loading",3,!1),c=T(e,"emptyMessage",3,"Aucun produit ne correspond."),E=T(e,"emptyHint",3,""),A=230,w=8,C=26,g="'Inter Variable', system-ui, sans-serif",k="'Inter Variable'",O=16;let q=D(0),X=D(!1);ue(()=>{const b=document.fonts;b!==void 0&&b.load(`700 ${me}px ${k}`).catch(()=>[]).then(()=>{x(X,!0)})});const U=h(()=>(t(X),zt(g,700)));let I=D(null),W=D(0),F=D(0);const K=h(()=>{if(t(q)<=0)return 0;const b=Math.max(1,Math.floor((t(q)+w)/(A+w)));return(t(q)-(b-1)*w)/b-C}),G=h(()=>t(W)>0?t(W):t(K));ue(()=>{t(q),e.products.length;const b=t(I)?.querySelector(".name-box");x(W,b?.clientWidth??0,!0)});const Q=h(()=>{const b=new Map;if(t(U)===null||t(G)<=0)return b;const L=t(F)>0?t(F):Le;for(const j of e.products)b.set(j.id,At(j.name,t(G),t(U),L));return b});var _=Ut(),P=u(_),S=p(P,2);{var M=b=>{var L=Ot(),j=ze(L);ve(j,21,()=>({length:O}),Ue,(R,ee)=>{var Y=Rt();m(R,Y)}),m(b,L)},Z=b=>{var L=Bt(),j=u(L),R=u(j),ee=p(j,2);{var Y=$=>{var re=Wt(),se=u(re);N(()=>z(se,E())),m($,re)};B(ee,$=>{E()!==""&&$(Y)})}N(()=>z(R,c())),m(b,L)},J=b=>{var L=Ft();ve(L,21,()=>e.products,j=>j.id,(j,R)=>{var ee=Ht(),Y=u(ee);{let $=h(()=>t(Q).get(t(R).id)??fe),re=h(()=>t(R).id===l()),se=h(()=>t(R).id===d()),de=h(()=>t(R).id===s());Lt(Y,{get product(){return t(R)},get nameSizePx(){return t($)},get categoryColor(){return a()[t(R).category_code]},get primaryCode(){return r()},get tierAbbrev(){return i()},get selected(){return t(re)},get rejected(){return t(se)},get busy(){return t(de)},get showPrice(){return f()},get onpick(){return e.onpick}})}m(j,ee)}),N(()=>oe(L,"data-tile-count",e.products.length)),m(b,L)};B(S,b=>{v()?b(M):e.products.length===0?b(Z,1):b(J,-1)})}Ce(_,b=>x(I,b),()=>t(I)),we(P,"clientHeight",b=>x(F,b)),we(_,"clientWidth",b=>x(q,b)),m(n,_),ae()}var Vt=y(''),Xt=y('
                                                                                                                                    ');function Kt(n,e){ne(e,!0);let a=D(null);ue(()=>{t(a)?.focus()});var r=Xt(),i=u(r);ie(i,{name:"search",size:"1.75rem"});var l=p(i,2);Ce(l,c=>x(a,c),()=>t(a));var d=p(l,2);{var s=c=>{var E=Vt();H("click",E,function(...A){e.onclose?.apply(this,A)}),m(c,E)};B(d,c=>{e.query!==""&&c(s)})}var f=p(d,2),v=u(f);ie(v,{name:"close",size:"1.5rem"}),N(()=>Ge(l,e.query)),H("input",l,c=>e.onquery(c.currentTarget.value)),H("keydown",l,c=>{c.key==="Escape"?(c.preventDefault(),e.onclose()):c.key==="Enter"&&(c.preventDefault(),e.onenter())}),H("click",f,function(...c){e.onclose?.apply(this,c)}),m(n,r),ae()}le(["input","keydown","click"]);function Zt(n){if(n==="")return"";const e=new Date(n);if(Number.isNaN(e.getTime()))return"";const a=i=>String(i).padStart(2,"0");return`${`${a(e.getDate())}/${a(e.getMonth()+1)}/${e.getFullYear()}`} ${a(e.getHours())}:${a(e.getMinutes())}:${a(e.getSeconds())}`}function Jt(n){return n.mode==="by_unit"?`${n.product_name} ${n.quantity} ${n.quantity>1?"unités":"unité"}`:`${n.product_name} ${n.net_text} kg`}var Yt=y('Catalogue du '),Qt=y('Catalogue en attente'),$t=y(' '),en=y('aucune pour le moment'),tn=y(' '),nn=y(''),an=y('
                                                                                                                                    Dernière étiquette
                                                                                                                                    ');function rn(n,e){ne(e,!0);const a=T(e,"catalogAt",3,""),r=T(e,"productCount",3,0),i=T(e,"appVersion",3,""),l=h(()=>[r()>0?`${r()} produits pesables`:"",i()!==""?`application ${i()}`:""].filter(_=>_!=="").join(" · "));var d=an();let s;var f=u(d),v=u(f);{var c=_=>{var P=Yt(),S=p(u(P)),M=u(S);N(()=>z(M,a())),m(_,P)},E=_=>{var P=Qt();m(_,P)};B(v,_=>{a()!==""?_(c):_(E,-1)})}var A=p(v,2);{var w=_=>{var P=$t(),S=u(P);N(()=>z(S,t(l))),m(_,P)};B(A,_=>{t(l)!==""&&_(w)})}var C=p(f,4),g=p(u(C),2);{var k=_=>{var P=en();m(_,P)},O=_=>{var P=tn(),S=u(P);N(M=>z(S,M),[()=>Jt(e.label)]),m(_,P)};B(g,_=>{e.label===null?_(k):_(O,-1)})}var q=p(C,2);{var X=_=>{var P=nn();N(()=>P.disabled=!e.available),H("click",P,function(...S){e.onreprint?.apply(this,S)}),m(_,P)};B(q,_=>{e.label!==null&&_(X)})}var U=p(q,2),I=u(U);let W;var F=p(I,2),K=u(F),G=p(U,2),Q=u(G);ie(Q,{name:"settings",size:"1.75rem"}),N(()=>{s=te(d,1,"bar svelte-161y12f",null,s,{live:e.label!==null}),W=te(I,1,"dot svelte-161y12f",null,W,{fault:!e.healthy}),oe(I,"aria-label",e.healthy?"Matériel disponible":"Matériel indisponible"),z(K,e.healthy?"Balance et imprimante disponibles":"Matériel indisponible")}),H("click",G,function(..._){e.onadmin?.apply(this,_)}),m(n,d),ae()}le(["click"]);var sn=y(''),on=y('

                                                                                                                                    Tare g

                                                                                                                                    ');function ln(n,e){ne(e,!0);const a=["1","2","3","4","5","6","7","8","9","0"];var r=on(),i=u(r),l=p(u(i),2),d=u(l),s=p(i,2);ve(s,20,()=>a,w=>w,(w,C)=>{var g=sn(),k=u(g);N(()=>z(k,C)),H("click",g,()=>e.ondigit(C)),m(w,g)});var f=p(s,2),v=u(f),c=p(v,2),E=p(c,2),A=u(E);ie(A,{name:"check",size:"1.5rem"}),N(()=>z(d,e.grams===""?"0":e.grams)),H("click",v,function(...w){e.onclear?.apply(this,w)}),H("click",c,function(...w){e.oncancel?.apply(this,w)}),H("click",E,function(...w){e.onconfirm?.apply(this,w)}),m(n,r),ae()}le(["click"]);function cn(n){return n===null||n.state==="initializing"?!1:n.scale.connected&&n.printer.health!=="faulted"}const Ae=1500,un=2e3,Ie=5e3;function dn(n,e){return e?n>=Ie?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!0}:n>=un?{showWeight:!1,banner:"Poids indisponible",mustReconnect:!1}:n>=Ae?{showWeight:!1,banner:"",mustReconnect:!1}:{showWeight:!0,banner:"",mustReconnect:!1}:{showWeight:n=Ie}}const vn=250,fn={show_grid_prices:!0,idle_timeout_s:45,reprint_window_s:60,sound:!0};class gn{#e=D(null);get state(){return t(this.#e)}set state(e){x(this.#e,e,!0)}#t=D(null);get catalog(){return t(this.#t)}set catalog(e){x(this.#t,e,!0)}#n=D(Me({showWeight:!0,banner:"",mustReconnect:!1}));get link(){return t(this.#n)}set link(e){x(this.#n,e,!0)}#r=D("");get catalogError(){return t(this.#r)}set catalogError(e){x(this.#r,e,!0)}#s=null;#i=null;#o=Date.now();#a=!1;#l=!1;get presentation(){return this.catalog?.presentation??fn}start(){this.#u(),this.#c(),this.#i=setInterval(()=>this.#v(),vn)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null,this.#s?.close(),this.#s=null,this.#a=!1}#c(){const e=new EventSource("/api/v1/stream");this.#s=e,e.onopen=()=>{this.#a=!0,this.#o=Date.now()},e.addEventListener("state",a=>this.#d(a)),e.onerror=()=>{this.#a=!1}}#d(e){this.#o=Date.now(),this.#a=!0;const a=JSON.parse(e.data);this.state=a,this.catalog!==null&&a.catalog_count!==this.catalog.product_count&&this.#u()}#v(){const e=dn(Date.now()-this.#o,this.#a);this.link=e,e.mustReconnect&&(this.#s?.close(),this.#o=Date.now(),this.#c())}async#u(){if(!this.#l){this.#l=!0;try{this.catalog=await Ve(),this.catalogError=""}catch{this.catalog===null&&(this.catalogError="Catalogue indisponible. Prévenez un responsable.")}finally{this.#l=!1}}}}const Se="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Pe(){let n=Date.now();const e=new Array(26);for(let r=9;r>=0;r--)e[r]=Se[n%32],n=Math.floor(n/32);const a=new Uint8Array(16);crypto.getRandomValues(a);for(let r=0;r<16;r++)e[10+r]=Se[a[r]%32];return e.join("")}var hn=y('
                                                                                                                                    ');function pn(n,e){ne(e,!0);const a=new gn;let r=D(Me(Xe)),i=D(""),l=D(!1);const d=h(()=>t(i)!==""||t(l));let s=D(null),f=D(-1),v=D(null);const c=h(()=>a.catalog),E=h(()=>t(c)===null?[]:Qe(t(c))),A=h(()=>t(c)===null?[]:Je(t(c))),w=h(()=>Ze(t(E),t(r),t(i))),C=h(()=>{const o={};for(const V of t(c)?.categories??[])o[V.code]=V.color;return o}),g=h(()=>a.state),k=h(()=>a.presentation),O=h(()=>cn(t(g))),q=h(()=>{if(a.catalogError!=="")return{message:a.catalogError,hint:"Le poste réessaie tout seul."};if(t(E).length>0)return{message:"Aucun produit ne correspond.",hint:"Effacez des lettres ou changez de rayon."};const o=t(g)?.station??0;return{message:o>0?`Catalogue vide. En attente du fichier flv_${o}.csv.`:"Catalogue vide. En attente du fichier du poste.",hint:"Prévenez un responsable : aucun produit ne peut être pesé."}}),X=h(()=>t(g)?.state==="rejected"?t(g).product?.id??null:null),U=h(()=>t(g)?.state==="faulted"||t(g)?.state==="out_of_service"),I=["product_armed","weight_present","weight_stable","awaiting_stability","validating","printing"],W=h(()=>t(g)!==null&&I.includes(t(g).state)?t(g).product?.id??null:null),F=h(()=>Zt(t(c)?.updated_at??""));ue(()=>(a.start(),()=>a.stop())),ue(()=>{const o=a.state?.revision??-1;t(s)!==null&&o>t(f)&&(x(s,null),x(f,-1))});async function K(o){if(t(s)===null){x(s,o.id,!0),x(f,a.state?.revision??-1,!0);try{await Ke({product_id:o.id,tare_g:a.state?.weight.tare_g??0,units:1,manual_weight_g:0,seen_weight_g:a.state?.weight.gross_g??0,measurement_seq:a.state?.weight.seq??0,key:Pe()})}catch{x(s,null)}}}async function G(){const o=a.state?.reprint.job_id;o===void 0||o===""||await Ye(o,Pe())}function Q(){x(i,""),x(l,!1)}function _(){x(l,!0)}function P(){const o=t(w).length===1?t(w)[0]:void 0;o!==void 0&&K(o)}function S(o){if(!(o.metaKey||o.ctrlKey||o.altKey)&&!(o.target instanceof HTMLElement&&o.target.tagName==="INPUT")&&!(t(v)!==null||t(U))){if(o.key==="Escape"){o.preventDefault(),Q();return}if(o.key==="Backspace"){o.preventDefault(),x(i,t(i).slice(0,-1),!0);return}if(o.key==="Enter"){P();return}o.key.length===1&&/[a-zA-Z0-9 ]/.test(o.key)&&(o.key===" "&&t(i)===""||(o.preventDefault(),x(i,t(i)+o.key)))}}ue(()=>(window.addEventListener("keydown",S),()=>window.removeEventListener("keydown",S)));async function M(){(await st(()=>import("./mount-BjqIypvA.js"),__vite__mapDeps([0,1,2,3]))).mountAdmin(document.body)}var Z=hn(),J=u(Z);{let o=h(()=>t(v)!==null);ct(J,{get snapshot(){return t(g)},get showWeight(){return a.link.showWeight},get linkBanner(){return a.link.banner},get taring(){return t(o)},ontare:()=>x(v,"")})}var b=p(J,2);{var L=o=>{ln(o,{get grams(){return t(v)},ondigit:V=>x(v,(t(v)??"")+V),onclear:()=>x(v,""),oncancel:()=>x(v,null),onconfirm:()=>x(v,null)})};B(b,o=>{t(v)!==null&&o(L)})}var j=p(b,2);{let o=h(()=>t(c)?.pricing.primary_code??""),V=h(()=>Object.fromEntries((t(c)?.pricing.tiers??[]).map(ye=>[ye.code,ye.abbrev]))),ge=h(()=>t(c)===null&&a.catalogError==="");Gt(j,{get products(){return t(w)},get colors(){return t(C)},get primaryCode(){return t(o)},get tierAbbrev(){return t(V)},get selectedID(){return t(W)},get rejectedID(){return t(X)},get busyID(){return t(s)},get showPrices(){return t(k).show_grid_prices},get loading(){return t(ge)},get emptyMessage(){return t(q).message},get emptyHint(){return t(q).hint},onpick:K})}var R=p(j,2);{var ee=o=>{Kt(o,{get query(){return t(i)},onquery:V=>x(i,V,!0),onclose:Q,onenter:P})};B(R,o=>{t(d)&&o(ee)})}var Y=p(R,2);_t(Y,{get chips(){return t(A)},get active(){return t(r)},get searchFieldOpen(){return t(d)},onselect:o=>x(r,o,!0),onopensearch:_});var $=p(Y,2);{let o=h(()=>t(g)?.last_label??null),V=h(()=>t(g)?.reprint.available??!1),ge=h(()=>t(c)?.app_version??"");rn($,{get label(){return t(o)},get available(){return t(V)},get catalogAt(){return t(F)},get productCount(){return t(E).length},get appVersion(){return t(ge)},get healthy(){return t(O)},onreprint:G,onadmin:M})}var re=p($,2);{var se=o=>{{let V=h(()=>t(g).message?.text??"Prévenez un responsable.");ke(o,{title:"Poste indisponible",get detail(){return t(V)},get code(){return t(g).fault_code},action:{label:"J’ai compris",run:()=>{$e()}}})}},de=o=>{ke(o,{title:"Poste hors service",detail:"Ce poste ne peut pas peser. Prévenez un responsable.",get code(){return t(g).fault_code}})};B(re,o=>{t(g)?.state==="faulted"?o(se):t(g)?.state==="out_of_service"&&o(de,1)})}m(n,Z),ae()}const _n=5;mn();document.addEventListener("contextmenu",n=>{bn(n.target)||n.preventDefault()});document.addEventListener("dragstart",n=>n.preventDefault());et(pn,{target:document.getElementById("app")});function bn(n){return n instanceof Element&&n.closest("[data-admin]")!==null}function mn(){let n=!1;const e=(a,r)=>{n||(n=!0,tt(a,r),yn(),setTimeout(()=>location.reload(),_n*1e3))};window.addEventListener("error",a=>e(a.message,a.error?.stack??"")),window.addEventListener("unhandledrejection",a=>e(String(a.reason),a.reason?.stack??""))}function yn(){const n=document.createElement("div");n.className="fatal",n.append(_e("h1","Une erreur est survenue"),_e("p","L’écran va se recharger tout seul."),_e("p","ERR-UI-01","code")),document.body.appendChild(n)}function _e(n,e,a=""){const r=document.createElement(n);return r.textContent=e,a!==""&&(r.className=a),r} diff --git a/internal/web/dist/assets/mount-BjqIypvA.js b/internal/web/dist/assets/mount-BjqIypvA.js new file mode 100644 index 0000000..8a1e0f1 --- /dev/null +++ b/internal/web/dist/assets/mount-BjqIypvA.js @@ -0,0 +1,93 @@ +import{R as ar,T as rr,U as nr,V as xa,W as Yt,e as sr,X as lr,Y as ir,Z as or,_ as ur,u as pa,r as Fa,$ as cr,c as Ma,B as aa,A as U,k as e,z as v,F as Lt,d as Tt,b as vt,s as r,i as m,t as p,n as Ht,l as Be,o as i,f as et,g as l,v as a,j as u,p as Ct,I as dr,h as Rt,q as o,w as K,a0 as Ea,E as na,x as rt,G as Ba,a1 as ua,y as Qt,a2 as Dt,a3 as yt,a4 as sa,C as fa,K as vr,H as pr,P as fr,a5 as gr}from"./app-Dn-BjSsJ.js";function Va(n){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function mr(n,t,...s){var c=new nr(n);ar(()=>{const _=t()??null;c.ensure(_,_&&(b=>_(b,...s)))},rr)}function Wa(n,t,s=!1){if(n.multiple){if(t==null)return;if(!lr(t))return ir();for(var c of n.options)c.selected=t.includes(la(c));return}for(c of n.options){var _=la(c);if(or(_,t)){c.selected=!0;return}}(!s||t!==void 0)&&(n.selectedIndex=-1)}function hr(n){var t=new MutationObserver(()=>{"__value"in n&&Wa(n,n.__value)});t.observe(n,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),ur(()=>{t.disconnect()})}function _r(n,t,s=t){var c=new WeakSet,_=!0;xa(n,"change",b=>{var R=b?"[selected]":":checked",N;if(n.multiple)N=[].map.call(n.querySelectorAll(R),la);else{var $=n.querySelector(R)??n.querySelector("option:not([disabled])");N=$&&la($)}s(N),n.__value=N,Yt!==null&&c.add(Yt)}),sr(()=>{var b=t();if(n===document.activeElement){var R=Yt;if(c.has(R))return}if(Wa(n,b,_),_&&b===void 0){var N=n.querySelector(":checked");N!==null&&(b=la(N),s(b))}n.__value=b,_=!1}),hr(n)}function la(n){return"__value"in n?n.__value:n.value}function ia(n,t,s=t){var c=new WeakSet;xa(n,"input",async _=>{var b=_?n.defaultValue:n.value;if(b=ma(n)?ha(b):b,s(b),Yt!==null&&c.add(Yt),await cr(),b!==(b=t())){var R=n.selectionStart,N=n.selectionEnd,$=n.value.length;if(n.value=b??"",N!==null){var G=n.value.length;R===N&&N===$&&G>$?(n.selectionStart=G,n.selectionEnd=G):(n.selectionStart=R,n.selectionEnd=Math.min(N,G))}}}),pa(t)==null&&n.value&&(s(ma(n)?ha(n.value):n.value),Yt!==null&&c.add(Yt)),Fa(()=>{var _=t();if(n===document.activeElement){var b=Yt;if(c.has(b))return}ma(n)&&_===ha(n.value)||n.type==="date"&&!_&&!n.value||_!==n.value&&(n.value=_??"")})}function za(n,t,s=t){xa(n,"change",c=>{var _=c?n.defaultChecked:n.checked;s(_)}),pa(t)==null&&s(n.checked),Fa(()=>{var c=t();n.checked=!!c})}function ma(n){var t=n.type;return t==="number"||t==="range"}function ha(n){return n===""?null:+n}function ka(n){Ma===null&&Va(),aa(()=>{const t=pa(n);if(typeof t=="function")return t})}function br(n){Ma===null&&Va(),ka(()=>()=>pa(n))}class It extends Error{constructor(t,s,c="",_=[]){super(s),this.status=t,this.code=c,this.faults=_,this.name="AdminError"}status;code;faults;get needsPassword(){return this.status===401}}function wr(){return Jt("/admin/api/health")}function yr(){return ht("/admin/api/troubleshooting/reprint",{})}function xr(){return ht("/admin/api/troubleshooting/reload-catalog",{})}function kr(n){return ht("/admin/api/troubleshooting/manual-entry",{on:n})}function qr(){return ht("/admin/api/troubleshooting/roll-changed",{})}function Sr(n){return ht("/admin/api/troubleshooting/fallback-printer",{on:n})}function Lr(){return ht("/admin/api/troubleshooting/test-scale",{})}function Er(){return ht("/admin/api/troubleshooting/test-printer",{})}function zr(){return ht("/admin/api/troubleshooting/test-label",{})}async function Ga(n){const t=new FormData;t.append("file",n,n.name);const s=await fetch("/admin/api/catalog/import",{method:"POST",body:t});return Sa(s,"POST /admin/api/catalog/import")}const jr="/admin/api/diagnostic.zip";function Pr(n){return ht("/admin/api/session",{password:n})}async function Tr(){await fetch("/admin/api/session",{method:"DELETE"})}function Cr(n,t){return ht("/admin/api/session/recovery",{code:n,password:t})}function qa(){return Jt("/admin/api/config")}function Rr(n){return Ja("PUT","/admin/api/config",n)}function Or(){return ht("/admin/api/config/confirm",{})}async function Ar(){return(await Jt("/admin/api/config/versions")).versions}function Nr(n){return ht("/admin/api/config/restore",{version:n})}async function $r(){return(await Jt("/admin/api/ports")).ports}async function Dr(){return(await Jt("/admin/api/printers")).printers}async function Ir(){return(await ht("/admin/api/printers/discover",{})).printers}function Ur(n){return ht("/admin/api/scale/detect",{port:n})}async function ja(n,t){return(await ht("/admin/api/scale/capture",{port:n,seconds:t})).frames}function Ha(n){return ht("/admin/api/printer/test?what="+n,{})}function Fr(n,t,s,c){const _=new URLSearchParams({template:n,demo:t?"1":"0",dual:s?"1":"0"});return _.set("t",String(c)),"/admin/api/label/preview.png?"+_.toString()}async function Mr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/journal?"+t)).weighings}function Br(n){return"/admin/api/journal/export.csv?"+new URLSearchParams(n).toString()}async function Vr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/technical?"+t)).entries}function Wr(n){const t=n===void 0?"":"?id="+String(n);return Jt("/admin/api/imports"+t)}function Gr(){return ht("/admin/api/catalog/reload",{})}function Hr(){return ht("/admin/api/catalog/forget-quarantine",{})}function Pa(n,t){return ht("/admin/api/products/"+encodeURIComponent(n)+"/decision",t)}function Jr(n){return ht("/admin/api/replay",{frame:n})}async function Jt(n){const t=await fetch(n,{headers:{accept:"application/json"}});return Sa(t,"GET "+n)}function ht(n,t){return Ja("POST",n,t)}async function Ja(n,t,s){const c=await fetch(t,{method:n,headers:{"content-type":"application/json"},body:JSON.stringify(s)});return Sa(c,n+" "+t)}async function Sa(n,t){const s=await n.text();if(!n.ok){const c=Ka(s);throw new It(n.status,Xr(s,t,n.status)+Kr(n),c?.code??"",c?.faults??[])}if(s!=="")return JSON.parse(s)}function Kr(n){if(n.status!==429)return"";const t=Number(n.headers.get("Retry-After")??"");if(!Number.isFinite(t)||t<=0)return"";const s=Math.ceil(t/60);return s>1?` Réessayez dans ${String(s)} minutes.`:" Réessayez dans une minute."}function Xr(n,t,s){const c=Ka(n);return c?.message!==void 0&&c.message!==""?c.message:`${t} a répondu ${String(s)}.`}function Ka(n){try{return JSON.parse(n)}catch{return null}}function Yr(){return Jt("/admin/api/update")}function Qr(){return ht("/admin/api/update/check",{})}function Zr(n){return ht("/admin/api/update/apply",{version:n})}class en{constructor(t){this.admin=t}admin;#e=U(null);get config(){return e(this.#e)}set config(t){v(this.#e,t,!0)}#a=U("");get fingerprint(){return e(this.#a)}set fingerprint(t){v(this.#a,t,!0)}#r=U(Lt([]));get retired(){return e(this.#r)}set retired(t){v(this.#r,t,!0)}#n=U(null);get pending(){return e(this.#n)}set pending(t){v(this.#n,t,!0)}#s=U(Lt([]));get faults(){return e(this.#s)}set faults(t){v(this.#s,t,!0)}#l=U(!1);get dirty(){return e(this.#l)}set dirty(t){v(this.#l,t,!0)}async load(){const t=await this.admin.load(()=>qa());t!==null&&(this.config=t.config,this.fingerprint=t.config_fingerprint,this.retired=t.retired_keys??[],this.pending=t.pending_confirmation,this.faults=[],this.dirty=!1)}value(t){let s=this.config;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}text(t){const s=this.value(t);return s==null?"":String(s)}number(t){const s=this.value(t);return typeof s=="number"?s:Number(s??0)}flag(t){return this.value(t)===!0}set(t,s){if(this.config===null)return;const c=t.split("."),_=c.pop();if(_===void 0)return;let b=this.config;for(const R of c){const N=b[R];(N===null||typeof N!="object")&&(b[R]={}),b=b[R]}b[_]=s,this.dirty=!0}unset(t){const s=t.split("."),c=s.pop();if(c===void 0||this.config===null)return!1;let _=this.config;for(const b of s){if(_===null||typeof _!="object")return!1;_=_[b]}return _===null||typeof _!="object"?!1:(delete _[c],this.dirty=!0,!0)}async save(){if(this.config===null)return!1;this.faults=[];const t=this.config;let s;try{s=await Rr(t)}catch(c){if(Ta(c))throw c;return this.admin.report(c),this.faults=tn(this.admin),!1}return this.config=s.config,this.fingerprint=s.config_fingerprint,this.retired=s.retired_keys??[],this.pending=s.pending_confirmation,this.dirty=!1,!0}async confirm(){try{await Or()}catch(t){if(Ta(t))throw t;this.admin.report(t);return}this.admin.notice="La configuration est confirmée.",this.pending=null,await this.admin.refresh()}dropRetired(t){if(t.includes("[")){this.admin.actionError=`« ${t} » est logée dans un tableau : cet écran ne sait pas la retirer de là. Modifiez le fichier de configuration lui-même.`;return}this.unset(t)&&(this.retired=this.retired.filter(s=>s!==t))}}function tn(n){return n.lastFaults}function Ta(n){return n instanceof It&&(n.status===401||n.status===409)}const an={"station.number":"Numéro du poste","station.name":"Nom du poste","station.coop":"Nom de la coopérative","network.listen":"Adresse d’écoute","network.admin_on_lan":"Administration accessible depuis le réseau","ui.language":"Langue","ui.sound":"Son","ui.idle_timeout_s":"Retour à l’accueil après (secondes)","ui.reprint_window_s":"Réimpression possible pendant (secondes)","ui.show_grid_prices":"Afficher les prix sur les tuiles","scale.type":"Protocole de la balance","scale.present":"Ce poste a une balance","scale.manual_entry_allowed":"Saisie manuelle autorisée","scale.degrade_after_s":"Passer en mode dégradé après (secondes)","scale.options.port":"Port série","scale.options.baud":"Vitesse (bauds)","scale.options.bits":"Bits de données","scale.options.parity":"Parité","scale.options.stop":"Bits d’arrêt","scale.options.backoff_min_ms":"Attente minimale avant réessai (ms)","scale.options.backoff_max_ms":"Attente maximale avant réessai (ms)","printer.type":"Pilote d’impression","printer.template":"Gabarit d’étiquette","printer.options.transport":"Transport","printer.options.queue":"File d’impression","printer.options.path":"Fichier de périphérique","printer.options.address":"Adresse réseau","printer.options.darkness":"Noircissement","printer.options.speed":"Vitesse d’impression","printer.options.offset_x":"Décalage horizontal (dots)","printer.options.offset_y":"Décalage vertical (dots)","printer.options.invert_bits":"Inverser les points","printer.options.copies":"Exemplaires","printer.options.roll_capacity":"Étiquettes par rouleau","pricing.amount_rounding":"Arrondi du montant","pricing.unit_price_rounding":"Arrondi du prix au kilo","pricing.primary_code":"Tarif principal","pricing.reference_code":"Tarif de référence","barcode.verify_reference_check_digit":"Vérifier la clé de contrôle","limits.empty_max_g":"Plateau considéré vide en dessous de (g)","limits.basket_check_enabled":"Vérifier la présence du panier","limits.basket_min_g":"Poids du panier, borne basse (g)","limits.basket_max_g":"Poids du panier, borne haute (g)","limits.min_weight_g":"Poids minimum accepté","limits.max_weight_g":"Poids maximum accepté","limits.max_tare_g":"Tare maximale","limits.min_units":"Unités minimum","limits.max_units":"Unités maximum","limits.max_amount_cents":"Montant maximum (centimes)","stability.mode":"Exigence de stabilité","stability.min_duration_ms":"Durée de stabilité (ms)","stability.tolerance_g":"Tolérance de stabilité (g)","stability.timeout_ms":"Délai d’attente de stabilité (ms)","stability.on_timeout":"Au bout du délai","stability.min_latch_rate":"Taux d’accroche minimal","stability.latch_rate_window_ms":"Fenêtre de mesure du taux (ms)","stability.expiry_floor_ms":"Péremption, plancher (ms)","stability.expiry_ceiling_ms":"Péremption, plafond (ms)","stability.expiry_factor":"Péremption, facteur","catalog.type":"Où le poste va chercher le catalogue","catalog.options.directory":"Répertoire surveillé","catalog.options.url":"Adresse du serveur","catalog.options.username":"Compte","catalog.options.password":"Mot de passe","catalog.options.separator":"Séparateur du CSV","catalog.options.poll_interval_s":"Vérifier toutes les (secondes)","catalog.options.stable_polls":"Vérifications avant lecture","catalog.options.max_file_size_mb":"Taille maximale du fichier (Mo)","catalog.options.max_image_size_kb":"Taille maximale d’une image (Ko)","catalog.options.min_readable_ratio":"Part minimale de lignes lisibles","catalog.options.max_weighable_drop":"Baisse maximale des produits pesables","catalog.options.max_archives":"Archives conservées","catalog.options.archive_days":"Archives conservées (jours)","catalog.options.failures_before_reject":"Échecs avant mise en quarantaine","catalog.images.source":"Origine des photos","catalog.images.path":"Répertoire des photos","catalog.fallback_category":"Rayon par défaut","journal.max_rows":"Pesées conservées","journal.max_days":"Pesées conservées (jours)","journal.max_technical":"Événements techniques conservés","admin.session_minutes":"Durée d’une session (minutes)","admin.attempts_per_minute":"Tentatives par minute","maintenance.weekly_integrity_check":"Contrôle d’intégrité hebdomadaire","maintenance.disk_alert_mb":"Alerte disque en dessous de (Mo)"};function Ft(n){return an[n]??n}const rn={station:"l’identité du poste",network:"le réseau",ui:"l’écran client",scale:"la balance",printer:"l’imprimante",pricing:"les tarifs",barcode:"le code-barres",limits:"les garde-fous",stability:"la stabilité",catalog:"le catalogue",journal:"le journal",maintenance:"la maintenance"};function nn(n){return rn[n]??n}const sn={scale:"balance",printer:"imprimante",catalog:"catalogue",ui:"écran",config:"configuration",http:"réseau",system:"système"};function Xa(n){return sn[n]??"origine inconnue"}const ba="openscale.admin.preferences",Ya="technical";class ln{#e=U(Lt(on()));get showTechnicalNames(){return e(this.#e)}set showTechnicalNames(t){v(this.#e,t,!0)}toggleTechnicalNames(){this.showTechnicalNames=!this.showTechnicalNames,un(this.showTechnicalNames)}}function on(){try{return globalThis.localStorage?.getItem(ba)===Ya}catch{return!1}}function un(n){try{n?globalThis.localStorage?.setItem(ba,Ya):globalThis.localStorage?.removeItem(ba)}catch{}}const Gt=new ln,cn=3e3,dn=["hardware","label","rules","catalog","journal","station","update"];function Ca(n){return dn.includes(n)}function vn(n){return n instanceof It&&(n.status===401||n.status===409)}class pn{constructor(t=cn){this.periodMs=t}periodMs;#e=U(null);get health(){return e(this.#e)}set health(t){v(this.#e,t,!0)}#a=U("dashboard");get page(){return e(this.#a)}set page(t){v(this.#a,t,!0)}#r=U(!1);get expert(){return e(this.#r)}set expert(t){v(this.#r,t,!0)}#n=U("");get linkError(){return e(this.#n)}set linkError(t){v(this.#n,t,!0)}#s=U("");get actionError(){return e(this.#s)}set actionError(t){v(this.#s,t,!0)}#l=U(!1);get needsFirstPassword(){return e(this.#l)}set needsFirstPassword(t){v(this.#l,t,!0)}#d=U("");get notice(){return e(this.#d)}set notice(t){v(this.#d,t,!0)}#v=U(!1);get busy(){return e(this.#v)}set busy(t){v(this.#v,t,!0)}#p=U(Lt([]));get lastFaults(){return e(this.#p)}set lastFaults(t){v(this.#p,t,!0)}#f=U(null);get pending(){return e(this.#f)}set pending(t){v(this.#f,t,!0)}#i=null;#o=null;start(){this.refresh(),this.#i=setInterval(()=>{this.refresh()},this.periodMs)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null}async refresh(){try{this.health=await wr(),this.linkError=""}catch(t){this.linkError=Ra(t)}}#u(){this.busy=!0,this.notice="",this.actionError="",this.needsFirstPassword=!1}#t(t){this.actionError=Ra(t),this.needsFirstPassword=t instanceof It&&t.status===409,this.#m(t)}async run(t){this.#u();try{const s=await t();this.notice=s.message}catch(s){this.#t(s)}finally{this.busy=!1}await this.refresh()}async login(t){this.#u();try{return await Pr(t),this.expert=!0,this.notice="Session d’administration ouverte.",!0}catch(s){return this.expert=!1,this.#t(s),!1}finally{this.busy=!1}}async recover(t,s){this.#u();try{const c=await Cr(t,s);return this.expert=!0,this.notice=c.warning??"Le mot de passe est remplacé et la session est ouverte.",!0}catch(c){return this.#t(c),!1}finally{this.busy=!1}}async protect(t){try{return await t()}catch(s){if(!vn(s))return this.#t(s),null;if(!await this.#g(s))return null;try{return await t()}catch(c){return this.#t(c),null}}}#g(t){return this.pending={kind:t.status===409?"first-password":"password",message:t.message},new Promise(s=>{this.#o=s})}async answerPassword(t){await this.login(t)&&this.#c(!0)}async answerRecovery(t,s){await this.recover(t,s)&&this.#c(!0)}cancelPassword(){this.#c(!1)}#c(t){this.pending=null,this.notice="";const s=this.#o;this.#o=null,s?.(t)}async logout(){await Tr(),this.expert=!1,this.page="dashboard",this.notice="Session d’administration fermée."}open(t){this.actionError="",this.notice="",this.needsFirstPassword=!1,this.page=t}async load(t){try{const s=await t();return this.actionError="",this.lastFaults=[],s}catch(s){return this.lastFaults=s instanceof It?s.faults:[],this.#t(s),null}}report(t){this.lastFaults=t instanceof It?t.faults:[],this.#t(t)}#m(t){t instanceof It&&t.needsPassword&&(this.expert=!1)}}function Ra(n){return n instanceof It?n.message:n instanceof Error?"Le poste n’a pas répondu : "+n.message:"Le poste n’a pas répondu."}var fn=u('clé'),gn=u('');function at(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"busy",3,!1),_=vt(t,"disabled",3,!1),b=vt(t,"protected",3,!1),R=vt(t,"act",3,void 0);var N=gn();let $;var G=a(N),V=r(G);{var se=be=>{var ye=fn();l(be,ye)};m(V,be=>{b()&&be(se)})}p(()=>{$=Ht(N,1,`act ${s()??""}`,"svelte-5tpq0o",$,{"touch-target":s()==="destructive",busy:c()}),Be(N,"data-kind",s()),Be(N,"data-act",R()),N.disabled=_()||c(),i(G,`${(c()?"En cours…":t.label)??""} `)}),et("click",N,function(...be){t.onrun?.apply(this,be)}),l(n,N)}Tt(["click"]);var mn=u(`

                                                                                                                                    Le code de secours a été tiré à l’installation de ce poste et imprimé + sur sa fiche, rangée dans le classeur du magasin. Il n’existe nulle part ailleurs.

                                                                                                                                    `,1),hn=u(`

                                                                                                                                    Oublié ? Le code de secours de la fiche d’installation en repose un. + Un responsable peut aussi le faire en ligne de commande, avec openscale config password.

                                                                                                                                    `,1),_n=u('

                                                                                                                                    '),bn=u('');function wn(n,t){Ct(t,!0);const s=o(()=>t.admin.pending?.kind==="first-password");let c=U(""),_=U("");const b=8,R=o(()=>e(s)?e(_).trim().length>0&&e(c).length>=b:e(c).length>0);async function N(){!e(R)||t.admin.busy||(e(s)?await t.admin.answerRecovery(e(_),e(c)):await t.admin.answerPassword(e(c)),v(c,""),v(_,""))}var $=bn(),G=a($),V=a(G),se=a(V),be=a(se);dr(be,{name:"settings",size:"1.5rem"});var ye=r(se,2),ie=a(ye),j=r(V,2),oe=a(j),X=r(j,2);{var we=T=>{var y=mn(),C=r(K(y),2),D=r(a(C),2);Ea(D);var Z=r(C,2),pe=a(Z);pe.textContent="Nouveau mot de passe — 8 caractères au moins";var Oe=r(pe,2);et("keydown",D,Ae=>Ae.key==="Enter"&&void N()),ia(D,()=>e(_),Ae=>v(_,Ae)),et("keydown",Oe,Ae=>Ae.key==="Enter"&&void N()),ia(Oe,()=>e(c),Ae=>v(c,Ae)),l(T,y)},me=T=>{var y=hn(),C=K(y),D=r(a(C),2);Ea(D),et("keydown",D,Z=>Z.key==="Enter"&&void N()),ia(D,()=>e(c),Z=>v(c,Z)),l(T,y)};m(X,T=>{e(s)?T(we):T(me,-1)})}var E=r(X,2);{var H=T=>{var y=_n(),C=a(y);p(()=>i(C,t.admin.actionError)),l(T,y)};m(E,T=>{t.admin.actionError!==""&&T(H)})}var F=r(E,2),P=a(F);at(P,{label:"Annuler",onrun:()=>t.admin.cancelPassword()});var te=r(P,2);{let T=o(()=>e(s)?"Poser ce mot de passe":"Continuer"),y=o(()=>!e(R)||t.admin.busy);at(te,{kind:"write",get label(){return e(T)},get disabled(){return e(y)},onrun:()=>{N()}})}p(()=>{i(ie,e(s)?"Ce poste n’a pas encore de mot de passe":"Mot de passe d’administration"),i(oe,t.admin.pending?.message??"")}),l(n,$),Rt()}Tt(["keydown"]);var yn=u(' '),xn=u(""),kn=u(""),qn=u('

                                                                                                                                    '),Sn=u(" "),Ln=u('

                                                                                                                                    '),En=u('
                                                                                                                                    ');function _t(n,t){Ct(t,!0);const s=vt(t,"hint",3,""),c=vt(t,"kind",3,"text"),_=vt(t,"disabled",3,!1),b=vt(t,"fault",3,""),R=vt(t,"allowed",19,()=>[]),N=o(()=>`field-${t.path.replace(/\./gu,"-")}`);var $=En();let G;var V=a($),se=a(V),be=a(se),ye=r(se,2);{var ie=F=>{var P=yn(),te=a(P);p(()=>i(te,t.path)),l(F,P)};m(ye,F=>{Gt.showTechnicalNames&&F(ie)})}var j=r(V,2),oe=r(j,2);{var X=F=>{var P=kn();rt(P,20,R,te=>te,(te,T)=>{var y=xn(),C={};p(()=>{C!==(C=T)&&(y.value=(y.__value=T)??"")}),l(te,y)}),p(()=>Be(P,"id",e(N)+"-allowed")),l(F,P)};m(oe,F=>{R().length>0&&F(X)})}var we=r(oe,2);{var me=F=>{var P=qn(),te=a(P);p(()=>i(te,s())),l(F,P)};m(we,F=>{s()!==""&&F(me)})}var E=r(we,2);{var H=F=>{var P=Ln(),te=a(P),T=r(te);{var y=C=>{var D=Sn(),Z=a(D);p(pe=>i(Z,`Valeurs acceptées : ${pe??""}.`),[()=>R().join(", ")]),l(C,D)};m(T,C=>{R().length>0&&C(y)})}p(()=>i(te,`${b()??""} `)),l(F,P)};m(E,F=>{b()!==""&&F(H)})}p(()=>{G=Ht($,1,"field svelte-1oilv5s",null,G,{refused:b()!==""}),Be(V,"for",e(N)),i(be,t.label),Be(j,"id",e(N)),Be(j,"type",c()),na(j,t.value),j.disabled=_(),Be(j,"list",R().length>0?e(N)+"-allowed":void 0)}),et("input",j,F=>t.onchange(F.currentTarget.value)),l(n,$),Rt()}Tt(["input"]);const Oa=1e3;function da(n){const t=La(n);return t===null?"":[ta(t.getDate()),ta(t.getMonth()+1),String(t.getFullYear())].join("/")}function Mt(n){const t=La(n);return t===null?"":`${da(n)} à ${ta(t.getHours())}:${ta(t.getMinutes())}`}function zn(n){const t=La(n);return t===null?"":[ta(t.getHours()),ta(t.getMinutes()),ta(t.getSeconds())].join(":")}function wa(n){const t=n/1e6;return t<1e3?`${Math.round(t)} Mo`:`${Qa(t/1e3)} Go`}function ge(n){const t=String(Math.abs(Math.trunc(n))),s=[];for(let c=t.length;c>0;c-=3)s.unshift(t.slice(Math.max(0,c-3),c));return(n<0?"-":"")+s.join(" ")}function ca(n){return n0&&b.push({count:Pt(n.anomalies_count),label:"anomalies",note:"à corriger dans Odoo",link:Aa(n.anomalies_count)}),n.unit_mismatches_count>0){const R=n.unit_mismatches_count;b.push({count:"+ "+Pt(R),label:R===1?"unité divergente":"unités divergentes",note:"pesable, unité à corriger",link:Aa(R)})}return{headline:`Catalogue du ${da(n.occurred_at)} — ${Pt(s)} produits reçus`,lines:b,oneLine:Pn(n)}}function Pn(n){const t=[`${Pt(n.rows_read_count)} reçus`,`${Pt(n.weighable_count)} pesables`,`${Pt(n.not_weighable_count)} non pesables`,`${Pt(n.anomalies_count)} anomalies`],s=n.unit_mismatches_count;return s>0&&t.push(`${Pt(s)} ${s===1?"unité divergente":"unités divergentes"}`),t.join(" · ")}function Aa(n){return n===1?"voir la ligne":`voir les ${Pt(n)} lignes`}function Tn(n){return n.map(t=>`${Cn(t)} (${Pt(t.count)})`).join(", ")}function Cn(n){switch(n.code){case"PREPACKAGED_PRODUCT":return"préemballés";case"INTERNAL_CODE_NOT_WEIGHABLE":return n.value===""?"code interne":"code interne "+n.value;case"NO_BARCODE":return"sans code-barres";default:return n.code}}function Pt(n){return String(n)}var Rn=u(' '),On=u(''),An=u('
                                                                                                                                    '),Nn=u('

                                                                                                                                    ');function Za(n,t){Ct(t,!0);const s=o(()=>jn(t.record,t.motives));var c=Nn(),_=a(c),b=a(_),R=r(_,2);rt(R,21,()=>e(s).lines,G=>G.label,(G,V)=>{var se=An(),be=a(se),ye=a(be),ie=r(be,2),j=a(ie),oe=a(j),X=r(j,2);{var we=H=>{var F=Rn(),P=a(F);p(()=>i(P,e(V).note)),l(H,F)};m(X,H=>{e(V).note!==""&&H(we)})}var me=r(X,2);{var E=H=>{var F=On(),P=a(F);p(()=>i(P,e(V).link)),et("click",F,()=>t.onshowrows(e(V).label==="anomalies"?"anomalies":"units")),l(H,F)};m(me,H=>{e(V).link!==""&&t.onshowrows!==void 0&&H(E)})}p(()=>{Be(se,"data-row",e(V).label),i(ye,e(V).count),i(oe,e(V).label)}),l(G,se)});var N=r(R,2),$=a(N);p(()=>{i(b,e(s).headline),i($,e(s).oneLine)}),l(n,c),Rt()}Tt(["click"]);var $n=u('

                                                                                                                                    '),Dn=u('

                                                                                                                                    ');function Qe(n,t){const s=vt(t,"note",3,"");var c=Dn(),_=a(c),b=a(_),R=r(_,2);{var N=G=>{var V=$n(),se=a(V);p(()=>i(se,s())),l(G,V)};m(R,G=>{s()!==""&&G(N)})}var $=r(R,2);mr($,()=>t.children),p(()=>i(b,t.title)),l(n,c)}const _a=(n,t=sa,s=sa)=>{var c=Un(),_=a(c);rt(_,21,t,fa,(b,R)=>{var N=In(),$=a(N),G=a($),V=r($,2),se=a(V),be=r(V,2),ye=a(be),ie=r(be,2),j=a(ie);p(oe=>{i(G,`ligne ${oe??""}`),i(se,e(R).product_id),i(ye,e(R).value),i(j,e(R).message)},[()=>ge(e(R).csv_line)]),l(b,N)}),p(()=>Be(c,"data-rows",s())),l(n,c)};var In=u('
                                                                                                                                  • '),Un=u('
                                                                                                                                      '),Fn=u('

                                                                                                                                      Lecture des réglages du poste…

                                                                                                                                      '),Mn=u(`

                                                                                                                                      Le poste y cherche le fichier , et le supprime + une fois lu : c’est ce qui dit au producteur que la livraison est prise.

                                                                                                                                      `,1),Bn=u(`

                                                                                                                                      Sur un serveur WebDAV, le dépôt d’un fichier CSV depuis cet écran n’est plus + possible : le poste n’a plus de répertoire local où l’écrire. C’est le seul recours + du jour de la mise en service.

                                                                                                                                      `,1),Vn=u(`

                                                                                                                                      Ce poste ne déclare aucune source : choisissez-en une ci-dessus, sinon il n’ira + chercher aucun catalogue.

                                                                                                                                      `),Wn=u('
                                                                                                                                      ',1),Gn=u('

                                                                                                                                      Aucun import enregistré sur ce poste.

                                                                                                                                      '),Hn=u(`

                                                                                                                                      « Oublier la quarantaine » fait relire un fichier que le poste avait écarté : c’est le + seul geste de cette page qui puisse remettre en service un catalogue refusé.

                                                                                                                                      `,1),Jn=u('

                                                                                                                                      '),Kn=u(`

                                                                                                                                      Déposez ici le fichier clé

                                                                                                                                      Ce dépôt remplace toute la grille par le fichier apporté : il change ce que le poste + vend, et le mot de passe est donc demandé au moment du dépôt.

                                                                                                                                      `,1),Xn=u('

                                                                                                                                      '),Yn=u('

                                                                                                                                      Aucune anomalie sur le dernier import.

                                                                                                                                      '),Qn=u('

                                                                                                                                      ',1),Zn=u('

                                                                                                                                      '),es=u('

                                                                                                                                      Aucune unité divergente sur le dernier import.

                                                                                                                                      '),ts=u('

                                                                                                                                      ',1),as=u('

                                                                                                                                      '),rs=u('

                                                                                                                                      Aucun produit non pesable sur le dernier import.

                                                                                                                                      '),ns=u('

                                                                                                                                      ',1),ss=u('

                                                                                                                                      Aucun import enregistré : rien n’a encore pu être retiré.

                                                                                                                                      '),ls=u('

                                                                                                                                      Aucun produit retiré par le dernier import.

                                                                                                                                      '),is=u(`

                                                                                                                                      Ils restent enregistrés avec leur historique : une étiquette déjà collée reste + lisible en caisse, et un produit qui revient dans un prochain fichier retrouve sa + tuile. Ce poste n’en publie encore que le nombre — leurs noms se lisent dans Odoo, + en comparant avec l’export précédent.

                                                                                                                                      `,1),os=u('
                                                                                                                                    • '),us=u(`

                                                                                                                                        Un produit retiré de la grille ne se trouve plus ici : il se reprend dans + « Décisions en vigueur », plus bas.

                                                                                                                                        `,1),cs=u(`

                                                                                                                                        Sans motif, le poste refuse la décision. Le seul acte qui s’en passe est celui qui + EFFACE la décision en vigueur : proposer de nouveau un produit qui ne porte aucune + dérogation.

                                                                                                                                        `),ds=u(`

                                                                                                                                        Produit choisi :

                                                                                                                                        Ce produit est-il proposé dans la grille ?

                                                                                                                                        La dérogation s’enregistre seule : elle ne remet pas dans la grille un produit + qui en a été retiré, et retirer un produit n’efface pas sa dérogation.

                                                                                                                                        `),vs=u(' ',1),ps=u('

                                                                                                                                        Aucune décision locale : la grille est celle du fichier.

                                                                                                                                        '),fs=u('
                                                                                                                                      • '),gs=u('

                                                                                                                                          ',1),ms=u('

                                                                                                                                          '),hs=u('

                                                                                                                                          Aucun import dans l’historique.

                                                                                                                                          '),_s=u(' '),bs=u('

                                                                                                                                          QuandFichierSourceRésultatMotifLuesPesablesNon pesablesAnomaliesRetirés
                                                                                                                                          ',1),ws=u('
                                                                                                                                          ');function ys(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=20,_=50,b=20,R=["NO_BARCODE","PREPACKAGED_PRODUCT","INTERNAL_CODE_NOT_WEIGHABLE"],N={applied:"appliqué",unchanged:"identique au précédent",rejected:"refusé",failed:"échec"},$={local_drop:"dépôt local",webdav:"WebDAV",manual:"déposé sur l’écran"},G={local_drop:["catalog.options.directory"],webdav:["catalog.options.url","catalog.options.username","catalog.options.password"]},V=o(()=>t.draft.text("catalog.type"));let se=U(Lt([])),be=U(Lt([])),ye=U(Lt([])),ie=U(""),j=U(""),oe=U(""),X=U(""),we=U(!1),me=U(""),E=U(""),H=U("loading"),F=U("loading");const P=o(()=>s().busy||e(E)!==""),te=o(()=>e(be).filter(k=>k.issue==="anomaly")),T=o(()=>e(be).filter(k=>R.includes(k.code))),y=o(()=>e(be).filter(k=>k.code==="UNIT_MISMATCH")),C=o(()=>new Map(e(ye).map(k=>[k.id,k.name]))),D=o(()=>e(ie)===""?[]:vr(e(ye),pr,e(ie))),Z=o(()=>e(D).slice(0,c)),pe=o(()=>e(te).slice(0,_)),Oe=o(()=>e(y).slice(0,_)),Ae=o(()=>e(T).slice(0,_)),S=o(()=>[...t.health.decisions].sort((k,Q)=>Q.decided_at.localeCompare(k.decided_at))),z=o(()=>e(S).slice(0,b)),g=o(()=>e(j)===""?null:e(S).find(k=>k.product_id===e(j))??null),O=o(()=>e(g)===null?!0:e(g).offered),I=o(()=>e(g)?.min_weight_g??null),J=o(()=>e(X).trim()===""?null:Number(e(X))),ce=o(()=>e(J)!==null&&Number.isFinite(e(J))&&e(J)>0),Ee=o(()=>e(oe).trim()),x=o(()=>e(Ee)!==""||!Se(!1,e(I))),M=o(()=>e(Ee)!==""||!Se(!0,e(I))),re=o(()=>e(ce)&&(e(Ee)!==""||!Se(e(O),e(J)))),he=o(()=>e(Ee)!==""||!Se(e(O),null)),de=o(()=>t.health.catalog?.products_withdrawn_count??0),He=o(()=>e(se).find(k=>t.health.catalog!==null&&k.id`${ge(e(de))} ${e(de)>1?"produits retirés":"produit retiré"}`+(e(He)===null?".":` depuis l’import du ${Mt(e(He).occurred_at)}.`)),xe=o(()=>e(D).length>e(Z).length?`${ge(e(Z).length)} produits affichés sur ${ge(e(D).length)} trouvés — précisez votre recherche.`:`${ge(e(D).length)} ${e(D).length>1?"produits trouvés":"produit trouvé"}.`),d=o(()=>`${ge(e(se).length)} ${e(se).length>1?"imports affichés":"import affiché"} : le poste n’en publie jamais plus de vingt.`),w=o(()=>e(H)==="loading"?"Lecture des signalements du dernier import…":"Les signalements du dernier import n’ont pas pu être lus : cet écran ne sait pas ce qu’ils disent."),Y=o(()=>e(H)==="loading"?"Lecture de l’historique des imports…":"L’historique des imports n’a pas pu être lu : cet écran ne sait pas ce qu’il contient.");let f;aa(()=>{const k=t.health.catalog?.id??null;k!==f&&(f=k,q())});async function q(){const k=await s().load(()=>Wr(t.health.catalog?.id));k===null?v(H,"unread"):(v(se,k.imports,!0),v(be,k.findings,!0),v(H,"read"));const Q=await s().load(()=>Ba());if(Q===null){v(F,"unread");return}v(ye,Q.products,!0),v(F,"read")}async function L(k,Q){v(E,k,!0),s().actionError="",s().notice="";try{const le=await s().protect(Q);return le===null?!1:(s().notice=le.message,await s().refresh(),!0)}finally{v(E,"")}}function _e(k){t.draft.set("catalog.type",k);for(const[Q,le]of Object.entries(G))if(Q!==k)for(const Ne of le)t.draft.unset(Ne)}function ne(k){return t.draft.faults.find(Q=>Q.field===k)?.message??""}function qe(k){v(j,k,!0),v(oe,"");const Q=e(S).find(le=>le.product_id===k)?.min_weight_g??null;v(X,Q===null?"":String(Q),!0)}function Se(k,Q){return!(k&&Q===null)}async function ue(k){if(e(j)==="")return;const Q=e(j),le=e(I),Ne=e(Ee);await L("offered",()=>Pa(Q,{offered:k,min_weight_g:le,reason:Ne}))&&v(oe,"")}async function W(k){if(e(j)==="")return;const Q=e(j),le=e(O),Ne=e(Ee);await L(k===null?"waiver-off":"waiver",()=>Pa(Q,{offered:le,min_weight_g:k,reason:Ne}))&&v(oe,"")}async function Ue(k){if(v(we,!1),k!=null){v(me,""),s().actionError="",s().notice="",v(E,"import");try{const Q=await s().protect(()=>Ga(k));if(Q===null)return;v(me,`${k.name} : ${ge(Q.rows_read_count)} lignes lues, ${ge(Q.weighable_count)} pesables. `+(Q.reason===""?"Le fichier est déposé ; son résultat s’inscrira dans l’historique des imports.":Q.reason)),await s().refresh()}finally{v(E,"")}}}function Ve(k){k.preventDefault(),v(we,!1);const Q=k.dataTransfer?.files.item(0);if(e(P)){v(me,`${Q?.name??"Ce fichier"} n’a pas été déposé : un acte est déjà en cours sur cette page. Réessayez quand il aura répondu.`);return}Ue(Q)}function Le(k){const Q=k.files?.item(0);k.value="",Ue(Q)}function Te(k){const Q=e(C).get(k);return Q!==void 0?Q:e(F)==="loading"?"Lecture du nom…":e(F)==="unread"?"Nom inconnu : le catalogue n’a pas pu être lu":"Produit absent du catalogue en service"}function Je(k,Q,le,Ne){const We=Q>1?Ne:le;return k>=Q?`${ge(Q)} ${We}.`:`${ge(k)} lignes affichées sur ${ge(Q)} ${We}.`}function ae(k){const Q=[];return k.offered||Q.push("retiré de la grille"),k.min_weight_g!==null&&Q.push(`peut peser à partir de ${ge(k.min_weight_g)} g`),Q.length===0?"aucune restriction":Q.join(" · ")}function Ie(k){const Q=N[k.result]??"résultat inconnu";return k.code===""?Q:`${Q} (${k.code})`}function ve(k){return $[k.source]??"source inconnue"}var ze=ws(),Re=a(ze);Qe(Re,{title:"Où le poste va chercher le catalogue",children:(k,Q)=>{var le=Wn(),Ne=K(le),We=a(Ne),Ye=a(We),Ke=r(We,2),B=a(Ke),ee=r(Ne,2);{var fe=Pe=>{var Fe=Fn();l(Pe,Fe)},$e=Pe=>{var Fe=Mn(),Ge=K(Fe);{let it=o(()=>Ft("catalog.options.directory")),dt=o(()=>t.draft.text("catalog.options.directory")),gt=o(()=>ne("catalog.options.directory"));_t(Ge,{get label(){return e(it)},path:"catalog.options.directory",get value(){return e(dt)},hint:"Laissez vide pour le répertoire du poste, celui que le service crée lui-même. Un répertoire nommé ici doit exister : le poste ne le crée pas.",get fault(){return e(gt)},onchange:xt=>t.draft.set("catalog.options.directory",xt)})}var ft=r(Ge,2),ot=r(a(ft)),ct=a(ot);p(()=>i(ct,`flv_${t.health.station??""}.csv`)),l(Pe,Fe)},ke=Pe=>{var Fe=Bn(),Ge=K(Fe);{let ct=o(()=>Ft("catalog.options.url")),it=o(()=>t.draft.text("catalog.options.url")),dt=o(()=>ne("catalog.options.url"));_t(Ge,{get label(){return e(ct)},path:"catalog.options.url",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.url",gt)})}var ft=r(Ge,2);{let ct=o(()=>Ft("catalog.options.username")),it=o(()=>t.draft.text("catalog.options.username")),dt=o(()=>ne("catalog.options.username"));_t(ft,{get label(){return e(ct)},path:"catalog.options.username",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.username",gt)})}var ot=r(ft,2);{let ct=o(()=>Ft("catalog.options.password")),it=o(()=>ne("catalog.options.password"));_t(ot,{get label(){return e(ct)},path:"catalog.options.password",kind:"password",value:"",hint:"Laissez vide : le mot de passe actuel est conservé.",get fault(){return e(it)},onchange:dt=>t.draft.set("catalog.options.password",dt)})}l(Pe,Fe)},De=Pe=>{var Fe=Vn();l(Pe,Fe)};m(ee,Pe=>{t.draft.config===null?Pe(fe):e(V)==="local_drop"?Pe($e,1):e(V)==="webdav"?Pe(ke,2):Pe(De,-1)})}p(()=>{ua(Ye,e(V)==="local_drop"),ua(B,e(V)==="webdav")}),et("change",Ye,()=>_e("local_drop")),et("change",B,()=>_e("webdav")),l(k,le)},$$slots:{default:!0}});var nt=r(Re,2);Qe(nt,{title:"Dernier import",children:(k,Q)=>{var le=Hn(),Ne=K(le);{var We=ke=>{var De=Gn();l(ke,De)},Ye=ke=>{Za(ke,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives}})};m(Ne,ke=>{t.health.catalog===null?ke(We):ke(Ye,-1)})}var Ke=r(Ne,2),B=a(Ke),ee=r(Ke,2),fe=a(ee);{let ke=o(()=>e(E)==="reload");at(fe,{act:"reload",kind:"write",label:"Recharger le catalogue",protected:!0,get busy(){return e(ke)},get disabled(){return e(P)},onrun:()=>{L("reload",Gr)}})}var $e=r(fe,2);{let ke=o(()=>e(E)==="quarantine");at($e,{act:"quarantine",kind:"destructive",label:"Oublier la quarantaine",protected:!0,get busy(){return e(ke)},get disabled(){return e(P)},onrun:()=>{L("quarantine",Hr)}})}p(()=>i(B,t.health.catalog_source===null?"Aucune source de catalogue publiée par ce poste.":"Source : "+t.health.catalog_source.label)),l(k,le)},$$slots:{default:!0}});var ut=r(nt,2);Qe(ut,{title:"Déposer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(k,Q)=>{var le=Kn(),Ne=K(le);{var We=Pe=>{var Fe=Jn(),Ge=a(Fe);p(()=>i(Ge,e(me))),l(Pe,Fe)};m(Ne,Pe=>{e(me)!==""&&Pe(We)})}var Ye=r(Ne,2);let Ke;var B=a(Ye),ee=r(a(B)),fe=a(ee),$e=r(B,2),ke=a($e),De=r(ke);p(()=>{Ke=Ht(Ye,1,"drop svelte-3gk7u7",null,Ke,{dropping:e(we),working:e(E)==="import"}),i(fe,`flv_${t.health.station??""}.csv`),i(ke,`${e(E)==="import"?"Import en cours…":"Choisir un fichier"} `),De.disabled=e(P)}),Qt("dragover",Ye,Pe=>{Pe.preventDefault(),!e(P)&&v(we,!0)}),Qt("dragleave",Ye,()=>v(we,!1)),Qt("drop",Ye,Ve),et("change",De,Pe=>Le(Pe.currentTarget)),l(k,le)},$$slots:{default:!0}});var pt=r(ut,2);Qe(pt,{title:"Anomalies à corriger dans Odoo",note:"Chaque ligne porte son numéro dans le CSV, son motif et la valeur fautive.",children:(k,Q)=>{var le=Dt(),Ne=K(le);{var We=B=>{var ee=Xn(),fe=a(ee);p(()=>i(fe,e(w))),l(B,ee)},Ye=B=>{var ee=Yn();l(B,ee)},Ke=B=>{var ee=Qn(),fe=K(ee),$e=a(fe),ke=r($e);{var De=Fe=>{var Ge=yt("Corrigez celles-ci dans Odoo : l’import suivant ne signalera que ce qui reste.");l(Fe,Ge)};m(ke,Fe=>{e(te).length>e(pe).length&&Fe(De)})}var Pe=r(fe,2);_a(Pe,()=>e(pe),()=>"anomalies"),p(Fe=>i($e,`${Fe??""} `),[()=>Je(e(pe).length,e(te).length,"anomalie","anomalies")]),l(B,ee)};m(Ne,B=>{e(H)!=="read"?B(We):e(te).length===0?B(Ye,1):B(Ke,-1)})}l(k,le)},$$slots:{default:!0}});var je=r(pt,2);Qe(je,{title:"Unités divergentes",note:"Le produit reste proposé : le code-barres fait foi, seul le libellé du prix est faux.",children:(k,Q)=>{var le=Dt(),Ne=K(le);{var We=B=>{var ee=Zn(),fe=a(ee);p(()=>i(fe,e(w))),l(B,ee)},Ye=B=>{var ee=es();l(B,ee)},Ke=B=>{var ee=ts(),fe=K(ee),$e=a(fe),ke=r(fe,2);_a(ke,()=>e(Oe),()=>"mismatches"),p(De=>i($e,De),[()=>Je(e(Oe).length,e(y).length,"unité divergente","unités divergentes")]),l(B,ee)};m(Ne,B=>{e(H)!=="read"?B(We):e(y).length===0?B(Ye,1):B(Ke,-1)})}l(k,le)},$$slots:{default:!0}});var st=r(je,2);Qe(st,{title:"Produits non pesables",note:"Un inventaire, pas une liste d’erreurs : ces produits portent déjà leur code-barres et n’ont aucune raison d’être pesés.",children:(k,Q)=>{var le=Dt(),Ne=K(le);{var We=B=>{var ee=as(),fe=a(ee);p(()=>i(fe,e(w))),l(B,ee)},Ye=B=>{var ee=rs();l(B,ee)},Ke=B=>{var ee=ns(),fe=K(ee),$e=a(fe),ke=r(fe,2);_a(ke,()=>e(Ae),()=>"not-weighable"),p(De=>i($e,De),[()=>Je(e(Ae).length,e(T).length,"produit non pesable","produits non pesables")]),l(B,ee)};m(Ne,B=>{e(H)!=="read"?B(We):e(T).length===0?B(Ye,1):B(Ke,-1)})}l(k,le)},$$slots:{default:!0}});var Ze=r(st,2);Qe(Ze,{title:"Produits retirés depuis l’import précédent",note:"Un produit absent du nouveau fichier est marqué retiré à sa date, jamais supprimé.",children:(k,Q)=>{var le=Dt(),Ne=K(le);{var We=B=>{var ee=ss();l(B,ee)},Ye=B=>{var ee=ls();l(B,ee)},Ke=B=>{var ee=is(),fe=K(ee),$e=a(fe);p(()=>i($e,e(Xe))),l(B,ee)};m(Ne,B=>{t.health.catalog===null?B(We):e(de)===0?B(Ye,1):B(Ke,-1)})}l(k,le)},$$slots:{default:!0}});var bt=r(Ze,2);Qe(bt,{title:"Décider d’un produit",note:"Retirer un produit et l’autoriser à peser moins sont deux décisions séparées : l’une n’efface pas l’autre.",children:(k,Q)=>{var le=vs(),Ne=r(K(le),2),We=r(Ne,2);{var Ye=ee=>{var fe=us(),$e=K(fe),ke=a($e),De=r($e,2),Pe=a(De);rt(Pe,21,()=>e(Z),Fe=>Fe.id,(Fe,Ge)=>{var ft=os(),ot=a(ft),ct=a(ot),it=r(ot,2),dt=a(it),gt=r(it,2),xt=a(gt);p(()=>{i(ct,e(Ge).name),i(dt,e(Ge).id),i(xt,`${e(Ge).unit_price_text??""}${e(Ge).price_suffix??""}`)}),et("click",ot,()=>qe(e(Ge).id)),l(Fe,ft)}),p(()=>i(ke,e(xe))),l(ee,fe)};m(We,ee=>{e(ie)!==""&&ee(Ye)})}var Ke=r(We,2);{var B=ee=>{var fe=ds(),$e=a(fe),ke=r(a($e)),De=a(ke),Pe=r(ke),Fe=r($e,2),Ge=a(Fe),ft=r(Fe,4),ot=r(ft,2);{var ct=tt=>{var qt=cs();l(tt,qt)};m(ot,tt=>{e(Ee)===""&&tt(ct)})}var it=r(ot,2),dt=r(a(it),2),gt=a(dt);{var xt=tt=>{{let qt=o(()=>e(E)==="offered"),$t=o(()=>e(P)||!e(x));at(tt,{act:"offered",kind:"destructive",label:"Ne plus proposer ce produit",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{ue(!1)}})}},Nt=tt=>{{let qt=o(()=>e(E)==="offered"),$t=o(()=>e(P)||!e(M));at(tt,{act:"offered",kind:"write",label:"Le proposer de nouveau",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{ue(!0)}})}};m(gt,tt=>{e(O)?tt(xt):tt(Nt,-1)})}var Bt=r(it,2),At=r(a(Bt),2),Vt=r(At,2),kt=a(Vt);{let tt=o(()=>e(E)==="waiver"),qt=o(()=>e(P)||!e(re));at(kt,{act:"waiver",kind:"write",label:"Enregistrer la dérogation",protected:!0,get busy(){return e(tt)},get disabled(){return e(qt)},onrun:()=>{W(e(J))}})}var Et=r(kt,2);{var Wt=tt=>{{let qt=o(()=>e(E)==="waiver-off"),$t=o(()=>e(P)||!e(he));at(tt,{act:"waiver-off",kind:"write",label:"Retirer la dérogation",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{W(null)}})}};m(Et,tt=>{e(I)!==null&&tt(Wt)})}p((tt,qt)=>{Be(fe,"data-decision",e(j)),i(De,tt),i(Pe,` (${e(j)??""})`),i(Ge,`En vigueur : ${qt??""}`),na(At,e(X))},[()=>Te(e(j)),()=>e(g)===null?"aucune décision — ce produit suit les règles générales":ae(e(g))]),ia(ft,()=>e(oe),tt=>v(oe,tt)),et("input",At,tt=>v(X,tt.currentTarget.value,!0)),l(ee,fe)};m(Ke,ee=>{e(j)!==""&&ee(B)})}ia(Ne,()=>e(ie),ee=>v(ie,ee)),l(k,le)},$$slots:{default:!0}});var Ot=r(bt,2);Qe(Ot,{title:"Décisions en vigueur",note:"Ce qu’un humain a décidé, produit par produit. C’est ici que se reprend un produit retiré de la grille.",children:(k,Q)=>{var le=Dt(),Ne=K(le);{var We=Ke=>{var B=ps();l(Ke,B)},Ye=Ke=>{var B=gs(),ee=K(B),fe=a(ee),$e=r(ee,2),ke=a($e);rt(ke,21,()=>e(z),De=>De.product_id,(De,Pe)=>{var Fe=fs(),Ge=a(Fe),ft=r(Ge,2),ot=a(ft),ct=r(ft,2),it=a(ct),dt=r(ct,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),At=a(Bt);p((Vt,kt,Et)=>{i(ot,Vt),i(it,e(Pe).product_id),i(gt,kt),i(Nt,e(Pe).reason),i(At,Et)},[()=>Te(e(Pe).product_id),()=>ae(e(Pe)),()=>da(e(Pe).decided_at)]),et("click",Ge,()=>qe(e(Pe).product_id)),l(De,Fe)}),p(De=>i(fe,De),[()=>Je(e(z).length,e(S).length,"décision en vigueur","décisions en vigueur")]),l(Ke,B)};m(Ne,Ke=>{e(S).length===0?Ke(We):Ke(Ye,-1)})}l(k,le)},$$slots:{default:!0}});var Ut=r(Ot,2);Qe(Ut,{title:"Vingt derniers imports",children:(k,Q)=>{var le=Dt(),Ne=K(le);{var We=B=>{var ee=ms(),fe=a(ee);p(()=>i(fe,e(Y))),l(B,ee)},Ye=B=>{var ee=hs();l(B,ee)},Ke=B=>{var ee=bs(),fe=K(ee),$e=a(fe),ke=r(fe,2),De=a(ke),Pe=r(a(De));rt(Pe,21,()=>e(se),Fe=>Fe.id,(Fe,Ge)=>{var ft=_s(),ot=a(ft),ct=a(ot),it=r(ot),dt=a(it),gt=r(it),xt=a(gt),Nt=r(gt),Bt=a(Nt),At=r(Nt),Vt=a(At),kt=r(At),Et=a(kt),Wt=r(kt),tt=a(Wt),qt=r(Wt),$t=a(qt),Zt=r(qt),ra=a(Zt),h=r(Zt),A=a(h);p((Ce,Me,lt,mt,wt,zt,jt,St)=>{i(ct,Ce),i(dt,e(Ge).file_name),i(xt,Me),i(Bt,lt),i(Vt,e(Ge).reason),i(Et,mt),i(tt,wt),i($t,zt),i(ra,jt),i(A,St)},[()=>Mt(e(Ge).occurred_at),()=>ve(e(Ge)),()=>Ie(e(Ge)),()=>ge(e(Ge).rows_read_count),()=>ge(e(Ge).weighable_count),()=>ge(e(Ge).not_weighable_count),()=>ge(e(Ge).anomalies_count),()=>ge(e(Ge).products_withdrawn_count)]),l(Fe,ft)}),p(()=>i($e,e(d))),l(B,ee)};m(Ne,B=>{e(H)!=="read"?B(We):e(se).length===0?B(Ye,1):B(Ke,-1)})}l(k,le)},$$slots:{default:!0}}),l(n,ze),Rt()}Tt(["change","click","input"]);function xs(n){switch(n){case"ok":return"OK";case"warn":return"À SURVEILLER";case"fault":return"EN PANNE";case"off":return"SANS OBJET";default:return"INCONNU"}}var ks=u('

                                                                                                                                          '),qs=u('

                                                                                                                                          ');function Ss(n,t){Ct(t,!0);var s=qs(),c=a(s),_=a(c),b=r(_,2),R=a(b),N=r(b,2),$=a(N),G=r(c,2),V=a(G),se=r(G,2);{var be=ye=>{var ie=ks(),j=a(ie);p(()=>i(j,t.light.remedy)),l(ye,ie)};m(se,ye=>{t.light.remedy!==""&&ye(be)})}p(ye=>{Be(s,"data-light",t.light.id),Be(s,"data-level",t.light.level),Be(_,"data-level",t.light.level),i(R,t.light.label),i($,ye),i(V,t.light.value)},[()=>xs(t.light.level)]),l(n,s),Rt()}function Ls(n){return[Es(n),zs(n),Ps(n),Ts(n),Rs(n),Os(n)]}function Es(n){const t=n.state.scale,s=ca(t.median_ms);return n.scale_present?t.connected?t.too_slow?{id:"scale",label:"Balance",level:"warn",value:`une mesure toutes les ${s}, plus lent que la péremption`,remedy:"À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante. Vérifiez le câble et l’adaptateur USB, puis la cadence sur la page Matériel."}:{id:"scale",label:"Balance",level:"ok",value:As(t.provisional,`une mesure toutes les ${s}`),remedy:""}:{id:"scale",label:"Balance",level:"fault",value:"elle ne répond plus",remedy:"Vérifiez le câble et l’alimentation de la balance, puis touchez « Tester la balance ». En attendant, « Basculer en saisie manuelle » permet de continuer à servir."}:{id:"scale",label:"Balance",level:"off",value:"ce poste est déclaré sans balance",remedy:""}}function zs(n){const t=n.state.printer;switch(t.health){case"faulted":return{id:"printer",label:"Imprimante",level:"fault",value:t.detail===""?"elle ne peut pas imprimer":t.detail,remedy:js(n)};case"consumable":return{id:"printer",label:"Imprimante",level:"warn",value:"elle imprime, mais le rouleau arrive en fin de vie",remedy:"Changez le rouleau quand vous passez derrière le comptoir, puis touchez « J’ai changé le rouleau ». Le poste continue de servir en attendant."};case"ready":return{id:"printer",label:"Imprimante",level:"ok",value:"elle répond et n’a rien à signaler",remedy:""};default:return{id:"printer",label:"Imprimante",level:"unknown",value:"elle prend les étiquettes et ne dit rien en retour",remedy:"C’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne. Pour savoir si elle imprime, touchez « Imprimer une étiquette de test »."}}}function js(n){const t="Regardez le capot, le rouleau et le câble, puis touchez « Tester l’imprimante ».";return n.printing?.fallback_available===!0?t+" Le poste peut servir en attendant : « Imprimer sur l’imprimante du poste voisin ».":t}function Ps(n){const t=n.roll;return t===null?{id:"roll",label:"Rouleau",level:"unknown",value:"aucun compteur d’étiquettes sur ce poste",remedy:`Sans imprimante construite, il n’y a pas de rouleau à compter : vérifiez le « ${Ft("printer.type")} » et ses réglages sur la page Matériel.`}:t.known?t.level==="warn"?{id:"roll",label:"Rouleau",level:"warn",value:t.message,remedy:"Changez le rouleau, puis touchez « J’ai changé le rouleau »."}:{id:"roll",label:"Rouleau",level:"ok",value:t.message,remedy:""}:{id:"roll",label:"Rouleau",level:"unknown",value:t.message,remedy:"Touchez « J’ai changé le rouleau » en mettant un rouleau neuf : c’est le seul geste qui dise quelque chose de vrai du papier."}}function Ts(n){const t=n.catalog_source?.label??"";if(n.state.catalog_count===0)return{id:"catalog",label:"Catalogue",level:"fault",value:"aucun produit dans la grille",remedy:Cs(t)};const s=n.catalog;return s!==null&&s.result!=="applied"&&s.result!=="unchanged"?{id:"catalog",label:"Catalogue",level:"warn",value:`dernier fichier refusé : ${s.reason===""?s.result:s.reason}`,remedy:"La grille tourne toujours sur le catalogue précédent. Corrigez le fichier dans Odoo, ou touchez « Oublier la quarantaine » puis redéposez-le."}:{id:"catalog",label:"Catalogue",level:"ok",value:`${ge(n.state.catalog_count)} produits dans la grille`,remedy:""}}function Cs(n){return"Déposez le fichier du catalogue là où le poste le guette, ou utilisez « Importer un catalogue » ci-contre pour le glisser depuis une clé USB."+(n===""?"":` Ce poste surveille : ${n}.`)}function Rs(n){const t=n.disk;if(t===null)return{id:"disk",label:"Disque",level:"unknown",value:"la place libre n’a pas pu être mesurée",remedy:"Téléchargez le fichier de diagnostic : il porte ce que le poste a pu lire du volume, et c’est la pièce qu’un support demandera."};const s=wa(t.free_bytes),c=`seuil ${ge(t.alert_mb)} Mo`;return t.free_bytes===0?{id:"disk",label:"Disque",level:"fault",value:`plus un octet libre sur ${t.path}`,remedy:"Le journal ne peut plus rien écrire, alors que les étiquettes continuent de sortir. Faites de la place sur le disque, puis rechargez cette page."}:t.alert_mb>0&&t.free_bytes0?{id:"journal",label:"Journal",level:"fault",value:`${ge(t)} pesées imprimées mais non enregistrées`,remedy:"Les étiquettes sortent, les ventes sont bonnes, c’est la trace qui manque. Téléchargez le fichier de diagnostic et prévenez le support : ne redémarrez rien."}:n.counters.journal_rows_count<0?{id:"journal",label:"Journal",level:"unknown",value:"ce poste n’a pas de journal ouvert",remedy:"Le poste pèse et imprime quand même. Téléchargez le fichier de diagnostic : il dit pourquoi la base n’a pas pu être ouverte."}:{id:"journal",label:"Journal",level:"ok",value:`${ge(n.counters.journal_rows_count)} pesées enregistrées`,remedy:""}}function As(n,t){return n?t+" (cadence encore provisoire)":t}function Ns(n){return n===null||!n.known?"unknown":n.configured?"ok":"missing"}function $s(n){switch(n){case"applied":return"appliqué";case"unchanged":return"inchangé, déjà appliqué";case"rejected":return"refusé";case"failed":return"échec";default:return n}}var Ds=u("Une mesure toutes les ",1),Is=u('

                                                                                                                                          '),Us=u("Source : ",1),Fs=u(`

                                                                                                                                          Aucun import enregistré sur ce poste : le catalogue n’est jamais arrivé, ou le + journal ne le porte pas.

                                                                                                                                          `),Ms=u('

                                                                                                                                          ',1),Bs=u('

                                                                                                                                          ',1),Vs=u('

                                                                                                                                          Aucune décision locale : le catalogue est proposé tel qu’il arrive.

                                                                                                                                          '),Ws=u('

                                                                                                                                          '),Gs=u(' '),Hs=u('
                                                                                                                                        • '),Js=u('
                                                                                                                                            ',1),Ks=u(`INCONNU — la question n’a pas pu être posée à ce système. Ce + n’est pas « non configuré » : personne ne sait encore.`,1),Xs=u('OK ',1),Ys=u('NON CONFIGURÉ ',1),Qs=u('

                                                                                                                                            '),Zs=u('

                                                                                                                                            ',1),el=u('

                                                                                                                                            Rien à signaler depuis le démarrage du poste.

                                                                                                                                            '),tl=u(' '),al=u('
                                                                                                                                          • '),rl=u('
                                                                                                                                              '),nl=u(''),sl=u('

                                                                                                                                              '),ll=u('
                                                                                                                                              Numéro de poste
                                                                                                                                              Nom
                                                                                                                                              Coopérative
                                                                                                                                              Version
                                                                                                                                              Empreinte de configuration
                                                                                                                                              ',1),il=u('
                                                                                                                                              ');function ol(n,t){Ct(t,!0);const s=o(()=>t.health.new_version),c=o(()=>Ls(t.health)),_=o(()=>t.health.state.scale),b=o(()=>t.health.unattended_restart),R=o(()=>t.health.decisions),N=5,$=o(()=>e(R).slice(0,N));var G=il(),V=a(G);rt(V,21,()=>e(c),X=>X.id,(X,we)=>{Ss(X,{get light(){return e(we)}})});var se=r(V,2);Qe(se,{title:"Cadence de la balance",children:(X,we)=>{var me=Is(),E=a(me);{var H=T=>{var y=yt("Ce poste est déclaré sans balance : le poids est saisi à la main.");l(T,y)},F=T=>{var y=yt(`La balance ne répond pas : aucune cadence n’est mesurable tant qu’aucune trame + n’arrive.`);l(T,y)},P=T=>{var y=yt(`Aucun intervalle n’a encore été mesuré : la cadence apparaîtra dès les premières + trames.`);l(T,y)},te=T=>{var y=Ds(),C=r(K(y)),D=a(C),Z=r(C),pe=r(Z);{var Oe=z=>{var g=yt("C’est encore une valeur d’attente : moins de huit intervalles ont été observés.");l(z,g)};m(pe,z=>{e(_).provisional&&z(Oe)})}var Ae=r(pe,2);{var S=z=>{var g=yt("À cette cadence, un poids serait périmé avant l’arrivée de la mesure suivante.");l(z,g)};m(Ae,z=>{e(_).too_slow&&z(S)})}p((z,g)=>{i(D,z),i(Z,`, médiane + observée sur ${g??""} intervalles. `)},[()=>ca(e(_).median_ms),()=>ge(e(_).observations_count)]),l(T,y)};m(E,T=>{t.health.scale_present?e(_).connected?e(_).observations_count===0?T(P,2):T(te,-1):T(F,1):T(H)})}l(X,me)},$$slots:{default:!0}});var be=r(se,2);Qe(be,{title:"Catalogue",children:(X,we)=>{var me=Bs(),E=K(me),H=a(E);{var F=C=>{var D=yt("Aucune source de catalogue n’est publiée par ce poste.");l(C,D)},P=C=>{var D=Us(),Z=r(K(D)),pe=a(Z);p(()=>i(pe,t.health.catalog_source.label)),l(C,D)};m(H,C=>{t.health.catalog_source===null?C(F):C(P,-1)})}var te=r(E,2);{var T=C=>{var D=Fs();l(C,D)},y=C=>{var D=Ms(),Z=K(D),pe=a(Z),Oe=r(Z,2);Za(Oe,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives},get onshowrows(){return t.onshowrows}}),p((Ae,S)=>i(pe,`Dernier essai : ${Ae??""} — + ${S??""}${t.health.catalog.file_name===""?"":" ("+t.health.catalog.file_name+")"}${t.health.catalog.reason===""?"":" : "+t.health.catalog.reason}`),[()=>Mt(t.health.catalog.occurred_at),()=>$s(t.health.catalog.result)]),l(C,D)};m(te,C=>{t.health.catalog===null?C(T):C(y,-1)})}l(X,me)},$$slots:{default:!0}});var ye=r(be,2);Qe(ye,{title:"Décisions locales en vigueur",note:"Ce qu’un humain a décidé de ce catalogue, avec son motif et sa date.",children:(X,we)=>{var me=Dt(),E=K(me);{var H=P=>{var te=Vs();l(P,te)},F=P=>{var te=Js(),T=K(te);{var y=D=>{var Z=Ws(),pe=a(Z);p(Oe=>i(pe,`${Oe??""} décisions en vigueur — les + 5 plus récentes ci-dessous. La liste entière est sur la page + Catalogue.`),[()=>ge(e(R).length)]),l(D,Z)};m(T,D=>{e(R).length>N&&D(y)})}var C=r(T,2);rt(C,21,()=>e($),D=>D.product_id,(D,Z)=>{var pe=Hs(),Oe=a(pe),Ae=a(Oe),S=r(Oe,2);{var z=ce=>{var Ee=Gs(),x=a(Ee);p(M=>i(x,`peut peser à partir de ${M??""} g`),[()=>ge(e(Z).min_weight_g)]),l(ce,Ee)};m(S,ce=>{e(Z).min_weight_g!==null&&ce(z)})}var g=r(S,2),O=a(g),I=r(g,2),J=a(I);p(ce=>{i(Ae,`${e(Z).offered?"Dérogation de poids":"Produit retiré"} — ${e(Z).product_id??""}`),i(O,e(Z).reason),i(J,`${ce??""}, ${e(Z).decided_by??""}`)},[()=>da(e(Z).decided_at)]),l(D,pe)}),l(P,te)};m(E,P=>{e(R).length===0?P(H):P(F,-1)})}l(X,me)},$$slots:{default:!0}});var ie=r(ye,2);Qe(ie,{title:"Redémarrage sans intervention",children:(X,we)=>{var me=Zs(),E=K(me),H=a(E);{var F=C=>{var D=Ks();l(C,D)},P=C=>{var D=Xs(),Z=r(K(D));p(()=>i(Z,` — après une coupure de courant, ce poste revient seul sur + l’écran client. ${e(b).detail??""}`)),l(C,D)},te=C=>{var D=Ys(),Z=r(K(D));p(()=>i(Z,` — ${e(b).detail??""}`)),l(C,D)};m(H,C=>{e(b)===null||!e(b).known?C(F):e(b).configured?C(P,1):C(te,-1)})}var T=r(E,2);{var y=C=>{var D=Qs(),Z=a(D);p(()=>i(Z,e(b).remedy)),l(C,D)};m(T,C=>{e(b)!==null&&e(b).known&&!e(b).configured&&e(b).remedy!==""&&C(y)})}p(C=>Be(E,"data-verdict",C),[()=>Ns(e(b))]),l(X,me)},$$slots:{default:!0}});var j=r(ie,2);Qe(j,{title:"Dix derniers événements",children:(X,we)=>{var me=Dt(),E=K(me);{var H=P=>{var te=el();l(P,te)},F=P=>{var te=rl();rt(te,21,()=>t.health.events,T=>T.id,(T,y)=>{var C=al(),D=a(C),Z=a(D),pe=r(D,2),Oe=a(pe),Ae=r(pe,2);{var S=O=>{var I=tl(),J=a(I);p(()=>i(J,e(y).code)),l(O,I)};m(Ae,O=>{e(y).code!==""&&Gt.showTechnicalNames&&O(S)})}var z=r(Ae,2),g=a(z);p((O,I)=>{Be(C,"data-level",e(y).level),i(Z,O),i(Oe,I),i(g,e(y).message)},[()=>zn(e(y).occurred_at),()=>Xa(e(y).source)]),l(T,C)}),l(P,te)};m(E,P=>{t.health.events.length===0?P(H):P(F,-1)})}l(X,me)},$$slots:{default:!0}});var oe=r(j,2);Qe(oe,{title:"Ce poste",children:(X,we)=>{var me=ll(),E=K(me),H=r(a(E),2),F=a(H),P=r(H,4),te=a(P),T=r(P,4),y=a(T),C=r(T,4),D=a(C),Z=r(C,4),pe=a(Z),Oe=r(E,2);{var Ae=S=>{var z=sl(),g=a(z),O=r(g);{var I=J=>{var ce=nl();et("click",ce,function(...Ee){t.onshowupdate?.apply(this,Ee)}),l(J,ce)};m(O,J=>{t.onshowupdate!==void 0&&J(I)})}p(()=>{Be(z,"data-update-available",e(s)),i(g,`Version ${e(s)??""} disponible. `)}),l(S,z)};m(Oe,S=>{e(s)!==""&&S(Ae)})}p(()=>{i(F,t.health.station),i(te,t.health.station_name===""?"non renseigné":t.health.station_name),i(y,t.health.coop),i(D,t.health.version),i(pe,t.health.config_fingerprint)}),l(X,me)},$$slots:{default:!0}}),l(n,G),Rt()}Tt(["click"]);var ul=u(''),cl=u(`

                                                                                                                                              Lecture de la configuration en cours… tant qu’elle n’est pas arrivée, cette page ne + déclare rien de ce poste.

                                                                                                                                              `),Na=u('
                                                                                                                                            • '),dl=u('
                                                                                                                                                '),vl=u('

                                                                                                                                                ',1),pl=u('
                                                                                                                                              • '),fl=u('

                                                                                                                                                  ',1),gl=u('

                                                                                                                                                  '),ml=u('
                                                                                                                                                • '),hl=u('
                                                                                                                                                    '),_l=u('

                                                                                                                                                      ',1),bl=u('

                                                                                                                                                      Balance

                                                                                                                                                      L’état et la cadence viennent de ce que le poste observe vraiment, jamais d’un réglage.

                                                                                                                                                      Réglages série de la balance

                                                                                                                                                      Imprimante

                                                                                                                                                      Réglages de l’imprimante
                                                                                                                                                      ');function wl(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=20,_=3,b=250,R=50,N=_*1e3+b+1e3,$=12;let G=U(Lt([])),V=U(Lt([])),se=U(Lt([])),be=U(Lt([])),ye=U(""),ie=U(!1),j=U(""),oe=U(0),X=U(0),we=U(!1),me=U(null),E=!1;const H=o(()=>t.health.state.scale),F=o(()=>t.health.state.printer),P=o(()=>t.draft.config!==null),te=o(()=>e(P)&&!t.draft.flag("scale.present")),T=o(()=>t.draft.text("scale.options.port")),y=o(()=>e(me)!==null&&e(me).port===e(T)?e(me).reason:""),C=o(()=>e(ye)===e(T)?e(be):[]),D=o(d),Z=o(Y),pe=o(L),Oe=o(qe),Ae=o(()=>Le("scale.type")!==""||Le("scale.options.port")!==""),S=o(()=>Le("printer.type")!==""||Le("printer.options.transport")!==""||Le("printer.options.queue")!=="");br(()=>{E=!0}),ka(()=>{x("ports",M)}),aa(()=>{e(we)||!z(e(T))||O(e(T))});function z(h){return!E&&e(P)&&!e(te)&&h!==""&&h===e(T)&&g(h)&&e(y)===""&&e(j)===""}function g(h){return e(G).some(A=>A.name===h)}async function O(h){v(we,!0);try{for(;z(h);){try{J(h,await ja(h,_))}catch(A){v(me,{port:h,reason:Je(A)},!0);return}await ce()}}finally{v(we,!1)}}async function I(){const h=e(T),A=await s().protect(()=>ja(h,_));A!==null&&(J(h,A),v(me,null))}function J(h,A){if(E||A.length===0)return;const Ce=h===e(ye)?e(be):[];v(ye,h,!0),v(be,[...Ce,...A].slice(-c),!0)}function ce(h=b){return new Promise(A=>setTimeout(A,h))}async function Ee(){const h=Date.now()+N;for(;e(we)&&Date.now()Ha(h));A!==null&&(s().notice=A.message,await s().refresh())}async function He(){v(oe,0),v(X,0),e(ie)||await M();const h=e(G);if(h.length===0){v(se,[],!0);return}await s().protect(()=>Xe(h))}async function Xe(h){v(se,[],!0),v(X,h.length,!0);for(const[A,Ce]of h.entries()){v(oe,A+1);try{const Me=await Ur(Ce.name);v(se,[...e(se),{port:Ce.name,message:Me.message,refused:!1}],!0)}catch(Me){if(ae(Me))throw Me;v(se,[...e(se),{port:Ce.name,message:Je(Me),refused:!0}],!0)}}}function xe(){return e(j)!=="detect"?"Détecter automatiquement":e(we)?"Détection : le port se libère…":e(X)===0?"Détection : énumération des ports…":`Détection : port ${ge(e(oe))} sur ${ge(e(X))}…`}function d(){return t.health.scale_present?e(H).connected?e(H).too_slow?{level:"warn",word:"Trop lente",detail:w()+" À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante."}:{level:"ok",word:"Connectée",detail:"Elle répond. "+w()}:{level:"fault",word:"Sans réponse",detail:"Elle ne répond plus. Vérifiez le câble et l’alimentation, puis « Tester la balance » sur la page Dépannage."}:{level:"off",word:"Sans balance",detail:"Ce poste est déclaré sans balance : le feu est éteint et le poids se saisit à la main."}}function w(){return e(H).observations_count===0?"Aucun intervalle n’a encore été mesuré : la cadence sera connue dès les premières trames.":`Une mesure toutes les ${ca(e(H).median_ms)} sur ${ge(e(H).observations_count)} intervalles`+(e(H).provisional?", cadence encore provisoire.":".")}function Y(){const h=f[e(F).health]??{level:"unknown",word:"État inconnu",detail:"Le poste a répondu un état que cet écran ne sait pas nommer."};return{...h,detail:e(F).detail===""?h.detail:e(F).detail}}const f={ready:{level:"ok",word:"Prête",detail:"Elle répond et n’a rien à signaler."},consumable:{level:"warn",word:"Rouleau en fin de vie",detail:"Elle imprime, mais le rouleau arrive en fin de vie."},faulted:{level:"fault",word:"En panne",detail:"Elle ne peut pas imprimer."},unknown:{level:"unknown",word:"Silencieuse",detail:"Elle prend les étiquettes et ne dit rien en retour : c’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne."}};function q(){const h=e(F).observed_at===""?"Jamais observée depuis le démarrage":`Observée le ${Mt(e(F).observed_at)}`,A=e(F).pending_jobs_count;return`${h}, ${ge(A)} ${A>1?"travaux":"travail"} en attente.`}function L(){const h=ne();return h===""?_e():`${_e()} ${h}`}function _e(){return e(P)?e(te)?"Ce poste est déclaré sans balance : aucun port n’est écouté.":e(T)===""?"Aucun port n’est indiqué : choisissez-en un dans la liste ci-dessus pour écouter les trames.":e(ie)?g(e(T))?e(y)!==""?`L’écoute de ${e(T)} est arrêtée.`:e(j)!==""?`L’écoute de ${e(T)} est suspendue le temps de l’acte en cours.`:`Écoute de ${e(T)}.`:`${e(T)} n’est pas visible depuis ce poste : rien n’est écouté en continu.`:e(j)==="ports"?`Énumération des ports en cours : l’écoute de ${e(T)} démarre dès qu’il est vu.`:`Les ports de ce poste n’ont pas été énumérés : « Lister les ports » dira si ${e(T)} existe.`:"Lecture de la configuration en cours : le port à écouter n’est pas encore connu."}function ne(){return e(C).length===0?"Aucune trame reçue pour l’instant.":e(C).length===1?`Une seule trame reçue — ${ge(c)} au plus sont gardées.`:`Les ${ge(e(C).length)} dernières trames — ${ge(c)} au plus, la plus récente en bas.`}function qe(){return!e(P)||e(te)||e(T)===""?"":e(y)!==""?"Reprendre l’écoute":e(ie)&&!g(e(T))?"Écouter ce port une fois":""}function Se(h,A,Ce){const Me=`${ge(Ce)} ${Ce>1?A:h}`;return Ce<=$?Me+".":`${Me} — seules les ${ge($)} premières lignes sont affichées.`}function ue(h,A){const Ce=Te(h);return Ce.length>0?Ce:A.slice(0,$)}function W(h){return[...new TextEncoder().encode(h)].map(A=>A.toString(16).toUpperCase().padStart(2,"0")).join(" ")}const Ue={0:"NUL",2:"STX",3:"ETX",4:"EOT",5:"ENQ",6:"ACK",9:"TAB",10:"LF",13:"CR",21:"NAK",27:"ESC",127:"DEL"};function Ve(h){let A="";for(const Ce of h){const Me=Ce.codePointAt(0)??0;if(Me>=32&&Me!==127){A+=Ce;continue}A+=`⟨${Ue[Me]??Me.toString(16).toUpperCase().padStart(2,"0")}⟩`}return A}function Le(h){return t.draft.faults.find(A=>A.field===h)?.message??""}function Te(h){return t.draft.faults.find(A=>A.field===h)?.allowed??[]}function Je(h){return h instanceof It?h.message:h instanceof Error?"Le poste n’a pas répondu : "+h.message:"Le poste n’a pas répondu."}function ae(h){return h instanceof It&&(h.status===401||h.status===409)}const Ie=[{what:"label",name:"étiquette"},{what:"alignment",name:"alignement"},{what:"ruler",name:"réglette"}];var ve=bl(),ze=a(ve),Re=a(ze),nt=r(a(Re),2),ut=r(a(nt),1,!0),pt=r(Re,2),je=a(pt),st=r(pt,4);{var Ze=h=>{var A=ul(),Ce=a(A);p(()=>ua(Ce,e(te))),et("change",Ce,Me=>t.draft.set("scale.present",!Me.currentTarget.checked)),l(h,A)},bt=h=>{var A=cl();l(h,A)};m(st,h=>{e(P)?h(Ze):h(bt,-1)})}var Ot=r(st,2),Ut=a(Ot);{let h=o(()=>e(j)==="ports"),A=o(()=>e(j)!=="");at(Ut,{label:"Lister les ports",get busy(){return e(h)},get disabled(){return e(A)},onrun:()=>{x("ports",M)}})}var k=r(Ut,2);{let h=o(xe),A=o(()=>e(j)!=="");at(k,{act:"detect",get label(){return e(h)},protected:!0,get disabled(){return e(A)},onrun:()=>{x("detect",He)}})}var Q=r(Ot,2);{var le=h=>{var A=vl(),Ce=K(A),Me=a(Ce),lt=r(Ce,2);{var mt=wt=>{var zt=dl();rt(zt,21,()=>e(G).slice(0,$),jt=>jt.name,(jt,St)=>{var Kt=Na(),ea=a(Kt),ga=a(ea),er=r(ea,2),tr=a(er);p(()=>{i(ga,e(St).name),i(tr,`${(e(St).description===""?"aucune description USB":e(St).description)??""} + ${e(St).vid===""?"":` — VID ${e(St).vid} PID ${e(St).pid}`}`)}),et("click",ea,()=>t.draft.set("scale.options.port",e(St).name)),l(jt,Kt)}),l(wt,zt)};m(lt,wt=>{e(G).length>0&&wt(mt)})}p(wt=>i(Me,wt),[()=>e(G).length===0?"Aucun port série n’est visible depuis ce poste.":Se("port détecté","ports détectés",e(G).length)]),l(h,A)};m(Q,h=>{e(ie)&&h(le)})}var Ne=r(Q,2);{var We=h=>{var A=fl(),Ce=K(A),Me=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(se).slice(0,$),mt=>mt.port,(mt,wt)=>{var zt=pl();let jt;var St=a(zt),Kt=a(St),ea=r(St,2),ga=a(ea);p(()=>{jt=Ht(zt,1,"svelte-1p4i0xm",null,jt,{refused:e(wt).refused}),i(Kt,e(wt).port),i(ga,e(wt).message)}),l(mt,zt)}),p(mt=>i(Me,mt),[()=>Se("port interrogé","ports interrogés",e(se).length)]),l(h,A)};m(Ne,h=>{e(se).length>0&&h(We)})}var Ye=r(Ne,2),Ke=r(a(Ye),2),B=a(Ke);{let h=o(()=>t.draft.text("scale.type")),A=o(()=>Le("scale.type")),Ce=o(()=>Te("scale.type")),Me=o(()=>!e(P));_t(B,{label:"Protocole",path:"scale.type",get value(){return e(h)},hint:"Les valeurs acceptées apparaissent ici si l’enregistrement est refusé.",get fault(){return e(A)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("scale.type",lt)})}var ee=r(B,2);{let h=o(()=>t.draft.text("scale.options.port")),A=o(()=>Le("scale.options.port")),Ce=o(()=>ue("scale.options.port",e(G).map(lt=>lt.name))),Me=o(()=>!e(P));_t(ee,{label:"Port série",path:"scale.options.port",get value(){return e(h)},hint:"Choisissez-le dans la liste détectée ci-dessus plutôt que de le taper : l’écoute permanente ne suit que des ports détectés.",get fault(){return e(A)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("scale.options.port",lt)})}var fe=r(Ye,2),$e=a(fe),ke=a($e),De=r($e,2);{var Pe=h=>{var A=gl(),Ce=a(A),Me=r(Ce);{var lt=mt=>{{let wt=o(()=>e(j)==="listen"),zt=o(()=>e(j)!=="");at(mt,{act:"listen",get label(){return e(Oe)},protected:!0,get busy(){return e(wt)},get disabled(){return e(zt)},onrun:()=>{x("listen",I)}})}};m(Me,mt=>{e(Oe)!==""&&mt(lt)})}p(()=>i(Ce,`${e(y)??""} `)),l(h,A)};m(De,h=>{(e(y)!==""||e(Oe)!=="")&&h(Pe)})}var Fe=r(De,2);{var Ge=h=>{var A=hl();rt(A,21,()=>e(C),fa,(Ce,Me)=>{var lt=ml(),mt=a(lt),wt=a(mt),zt=r(mt,2),jt=a(zt);p((St,Kt)=>{i(wt,St),i(jt,Kt)},[()=>W(e(Me)),()=>Ve(e(Me))]),l(Ce,lt)}),l(h,A)};m(Fe,h=>{e(C).length>0&&h(Ge)})}var ft=r(ze,2),ot=a(ft),ct=r(a(ot),2),it=r(a(ct),1,!0),dt=r(ot,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),At=a(Bt);{let h=o(()=>e(j)==="printers"),A=o(()=>e(j)!=="");at(At,{label:"Lister les files",get busy(){return e(h)},get disabled(){return e(A)},onrun:()=>{x("printers",re)}})}var Vt=r(At,2);{let h=o(()=>e(j)==="discover"),A=o(()=>e(j)!=="");at(Vt,{label:"Rechercher l’imprimante",protected:!0,get busy(){return e(h)},get disabled(){return e(A)},onrun:()=>{x("discover",he)}})}var kt=r(Vt,2);rt(kt,17,()=>Ie,h=>h.what,(h,A)=>{{let Ce=o(()=>`Auto-test : ${e(A).name}${e(j)===e(A).what?" — en cours…":""}`),Me=o(()=>e(j)!=="");at(h,{get act(){return e(A).what},get label(){return e(Ce)},protected:!0,get disabled(){return e(Me)},onrun:()=>{x(e(A).what,()=>de(e(A).what))}})}});var Et=r(Bt,2);{var Wt=h=>{var A=_l(),Ce=K(A),Me=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(V).slice(0,$),mt=>mt.name,(mt,wt)=>{var zt=Na(),jt=a(zt),St=a(jt),Kt=r(jt,2),ea=a(Kt);p(()=>{i(St,e(wt).name),i(ea,`${e(wt).detail??""}${e(wt).default?" — file par défaut du système":""}`)}),et("click",jt,()=>t.draft.set("printer.options.queue",e(wt).name)),l(mt,zt)}),p(mt=>i(Me,mt),[()=>Se("file d’impression","files d’impression",e(V).length)]),l(h,A)};m(Et,h=>{e(V).length>0&&h(Wt)})}var tt=r(Et,2),qt=r(a(tt),2),$t=a(qt);{let h=o(()=>t.draft.text("printer.type")),A=o(()=>Le("printer.type")),Ce=o(()=>Te("printer.type")),Me=o(()=>!e(P));_t($t,{label:"Driver",path:"printer.type",get value(){return e(h)},hint:"Gardez le driver raster : c’est celui que les postes en service utilisent.",get fault(){return e(A)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.type",lt)})}var Zt=r($t,2);{let h=o(()=>t.draft.text("printer.options.transport")),A=o(()=>Le("printer.options.transport")),Ce=o(()=>Te("printer.options.transport")),Me=o(()=>!e(P));_t(Zt,{label:"Transport",path:"printer.options.transport",get value(){return e(h)},hint:"Local par défaut : une file Windows ou un fichier de périphérique de ce poste.",get fault(){return e(A)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.options.transport",lt)})}var ra=r(Zt,2);{let h=o(()=>t.draft.text("printer.options.queue")),A=o(()=>Le("printer.options.queue")),Ce=o(()=>ue("printer.options.queue",e(V).map(lt=>lt.name))),Me=o(()=>!e(P));_t(ra,{label:"File ou chemin",path:"printer.options.queue",get value(){return e(h)},hint:"Choisissez-la dans la liste ci-dessus : une file mal orthographiée ne s’imprime pas.",get fault(){return e(A)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.options.queue",lt)})}p(h=>{Be(nt,"data-level",e(D).level),i(ut,e(D).word),i(je,e(D).detail),Ye.open=e(Ae),i(ke,e(pe)),Be(ct,"data-level",e(Z).level),i(it,e(Z).word),i(gt,e(Z).detail),i(Nt,h),tt.open=e(S)},[()=>q()]),l(n,ve),Rt()}Tt(["change","click"]);var yl=u(""),xl=u('L’export sera proposé quand le journal aura répondu.'),kl=u('L’export n’est pas proposé : ce poste n’a pas répondu à la lecture du journal.'),ql=u('
                                                                                                                                                      Exporter en CSV'),Sl=u('

                                                                                                                                                      '),Ll=u('

                                                                                                                                                      '),El=u(`

                                                                                                                                                      Aucune trame brute n’a été enregistrée pour cette pesée : il n’y a + rien à rejouer.

                                                                                                                                                      `),zl=u(`

                                                                                                                                                      La trame repart dans le décodeur du poste EN SERVICE : le poids + affiché au client change, et rien ne le remet comme il était. C’est + ce qui fait d’un refus inexpliqué un test permanent, sans + déplacement au magasin et sans balance.

                                                                                                                                                      `,1),jl=u('

                                                                                                                                                      Produit
                                                                                                                                                      Référence
                                                                                                                                                      Vente
                                                                                                                                                      Brut / tare / net
                                                                                                                                                      Stabilité
                                                                                                                                                      Origine du poids
                                                                                                                                                      Résultat
                                                                                                                                                      Trame brute
                                                                                                                                                      '),Pl=u(' ',1),Tl=u('

                                                                                                                                                      QuandProduitNetCode-barresRésultatDuréeDétail
                                                                                                                                                      ',1),Cl=u('
                                                                                                                                                      ',1),Rl=u('

                                                                                                                                                      '),Ol=u(' '),Al=u(' '),Nl=u('
                                                                                                                                                    • '),$l=u('

                                                                                                                                                        ',1),Dl=u('
                                                                                                                                                        ');function Il(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=200,_=5e3,b=50,R=500,N=7,$={sent:"envoyée à l’imprimante",rejected:"refusée",failed:"en échec",reprint:"réimpression"},G=[{value:"",label:"toutes"},{value:"sent",label:"envoyées à l’imprimante"},{value:"rejected",label:"refusées"},{value:"failed",label:"en échec"},{value:"reprint",label:"réimpressions"}],V={scale:"balance",manual:"saisie manuelle",replay:"trame rejouée"},se={stable:"stable",unstable:"instable",unknown:"non déclarée par la balance",not_applicable:"sans objet — saisie manuelle"},be={by_weight:"au poids",by_unit:"à l’unité"},ye={debug:"mise au point",info:"information",warn:"avertissement",error:"erreur",critical:"critique"};let ie=U(Lt([])),j=U(Lt([])),oe=U(""),X=U(null),we=U(!1),me=U(!1),E=U("loading"),H=U("loading"),F=U(""),P=U("");const te=o(()=>({limit:String(c),...e(oe)===""?{}:{result:e(oe)}})),T=o(()=>({...e(te),limit:String(_)})),y=o(()=>e(ie).find(I=>I.id===e(X))??null),C=o(()=>G.find(I=>I.value===e(oe))?.label??""),D=o(()=>Ae(e(ie).length,c,"pesée","pesées",`L’export CSV en emporte jusqu’à ${ge(_)}.`)),Z=o(()=>Ae(e(j).length,b,"ligne","lignes",`Le fichier de diagnostic emporte les ${ge(R)} dernières.`));pe();async function pe(){v(X,null),v(me,!0),v(E,"loading"),v(H,"loading"),v(ie,[],!0),v(j,[],!0),v(F,""),v(P,"");try{const I=await s().load(()=>Mr(e(te)));v(E,I===null?"unread":"read",!0),v(F,I===null?s().actionError:"",!0),v(ie,I??[],!0);const J=await s().load(()=>Vr({limit:String(b)}));v(H,J===null?"unread":"read",!0),v(P,J===null?s().actionError:"",!0),v(j,J??[],!0)}finally{v(me,!1),s().actionError=""}}async function Oe(I){s().notice="",s().actionError="",v(we,!0);try{const J=await s().protect(()=>Jr(I));J!==null&&(s().notice=J.message)}finally{v(we,!1)}}function Ae(I,J,ce,Ee,x){const M=I>1?Ee:ce;return I{var ce=Cl(),Ee=K(ce),x=r(a(Ee),2);rt(x,21,()=>G,f=>f.value,(f,q)=>{var L=yl(),_e=a(L),ne={};p(()=>{i(_e,e(q).label),ne!==(ne=e(q).value)&&(L.value=(L.__value=e(q).value)??"")}),l(f,L)});var M=r(x,2);at(M,{label:"Rafraîchir",get busy(){return e(me)},onrun:()=>{pe()}});var re=r(M,2);{var he=f=>{var q=xl();l(f,q)},de=f=>{var q=kl();l(f,q)},He=f=>{var q=ql();p(L=>Be(q,"href",L),[()=>Br(e(T))]),l(f,q)};m(re,f=>{e(E)==="loading"?f(he):e(E)==="unread"?f(de,1):f(He,-1)})}var Xe=r(Ee,2);{var xe=f=>{var q=Sl(),L=a(q);p(_e=>i(L,`L’export emporte le même filtre que le tableau, mais pas son plafond : il descend + jusqu’à ${_e??""} pesées, en point-virgule et en UTF-8 — il s’ouvre + tel quel dans le tableur d’un Windows français. Il ne demande aucun mot de passe : la + lecture du journal n’en demande pas non plus, et le fichier de diagnostic emporte déjà + les deux cents dernières pesées.`),[()=>ge(_)]),l(f,q)};m(Xe,f=>{e(E)==="read"&&f(xe)})}var d=r(Xe,2);{var w=f=>{var q=Ll(),L=a(q);{var _e=ue=>{var W=yt("Lecture du journal…");l(ue,W)},ne=ue=>{var W=yt();p(()=>i(W,`Le journal n’a pas pu être lu : ${e(F)??""} Ce n’est pas « aucune pesée ».`)),l(ue,W)},qe=ue=>{var W=yt("Le journal ne contient aucune pesée.");l(ue,W)},Se=ue=>{var W=yt();p(()=>i(W,`Aucune pesée ne correspond au filtre « ${e(C)??""} ».`)),l(ue,W)};m(L,ue=>{e(E)==="loading"?ue(_e):e(E)==="unread"?ue(ne,1):e(oe)===""?ue(qe,2):ue(Se,-1)})}l(f,q)},Y=f=>{var q=Tl(),L=K(q),_e=a(L),ne=r(L,2),qe=a(ne),Se=r(a(qe));rt(Se,21,()=>e(ie),ue=>ue.id,(ue,W)=>{var Ue=Pl(),Ve=K(Ue);let Le;var Te=a(Ve),Je=a(Te),ae=r(Te),Ie=a(ae),ve=r(ae),ze=a(ve),Re=r(ve),nt=a(Re),ut=r(Re),pt=a(ut),je=r(ut),st=a(je),Ze=r(je),bt=a(Ze),Ot=a(bt),Ut=r(Ve,2);{var k=Q=>{var le=jl(),Ne=a(le);Be(Ne,"colspan",N);var We=a(Ne),Ye=a(We),Ke=r(We,2),B=r(a(Ke),2),ee=a(B),fe=r(B,4),$e=a(fe),ke=r(fe,4),De=a(ke),Pe=r(ke,4),Fe=a(Pe),Ge=r(Pe,4),ft=a(Ge),ot=r(Ge,4),ct=a(ot),it=r(ot,4),dt=a(it),gt=r(it,4),xt=a(gt),Nt=a(xt),Bt=r(Ke,2);{var At=kt=>{var Et=El();l(kt,Et)},Vt=kt=>{var Et=zl(),Wt=K(Et),tt=a(Wt);at(tt,{kind:"destructive",label:"Rejouer cette trame",protected:!0,get busy(){return e(we)},onrun:()=>{Oe(e(y).frame)}}),l(kt,Et)};m(Bt,kt=>{e(y).frame===""?kt(At):kt(Vt,-1)})}p((kt,Et,Wt,tt,qt,$t,Zt,ra,h)=>{Be(le,"data-detail",e(W).id),i(Ye,`Pesée ${e(y).id??""}`),i(ee,`${e(y).product_name??""} (${e(y).product_id??""})`),i($e,e(y).reference),i(De,`${kt??""} — + ${Et??""} + ${e(y).quantity>1?"unités":"unité"}`),i(Fe,`${Wt??""} g / ${tt??""} g / + ${qt??""} g`),i(ft,`${$t??""} — cadence + médiane ${Zt??""}`),i(ct,ra),i(dt,`${h??""}${e(y).detail===""?"":` — ${e(y).detail}`}`),i(Nt,e(y).frame===""?"aucune trame enregistrée":e(y).frame)},[()=>S(be,e(y).mode,"mode de vente inconnu"),()=>ge(e(y).quantity),()=>ge(e(y).gross_g),()=>ge(e(y).tare_g),()=>ge(e(y).net_g),()=>S(se,e(y).stability,"stabilité inconnue"),()=>ca(e(y).rate_ms),()=>S(V,e(y).source,"origine inconnue"),()=>S($,e(y).result,"résultat inconnu")]),l(Q,le)};m(Ut,Q=>{e(X)===e(W).id&&e(y)!==null&&Q(k)})}p((Q,le,Ne,We)=>{Le=Ht(Ve,1,"svelte-86o8oz",null,Le,{open:e(X)===e(W).id}),i(Je,Q),i(Ie,e(W).product_name),i(ze,`${le??""} g`),i(nt,e(W).barcode),i(pt,Ne),i(st,We),Be(bt,"aria-expanded",e(X)===e(W).id),i(Ot,e(X)===e(W).id?"fermer":"détail")},[()=>Mt(e(W).occurred_at),()=>ge(e(W).net_g),()=>S($,e(W).result,"résultat inconnu"),()=>ca(e(W).duration_ms)]),et("click",bt,()=>v(X,e(X)===e(W).id?null:e(W).id,!0)),l(ue,Ue)}),p(()=>i(_e,e(D))),l(f,q)};m(d,f=>{e(ie).length===0?f(w):f(Y,-1)})}et("change",x,()=>{pe()}),_r(x,()=>e(oe),f=>v(oe,f)),l(I,ce)},$$slots:{default:!0}});var O=r(g,2);Qe(O,{title:"Journal technique",children:(I,J)=>{var ce=Dt(),Ee=K(ce);{var x=re=>{var he=Rl(),de=a(he);{var He=d=>{var w=yt("Lecture du journal technique…");l(d,w)},Xe=d=>{var w=yt();p(()=>i(w,`Le journal technique n’a pas pu être lu : ${e(P)??""} Ce n’est pas « aucune + ligne ».`)),l(d,w)},xe=d=>{var w=yt("Aucune ligne technique.");l(d,w)};m(de,d=>{e(H)==="loading"?d(He):e(H)==="unread"?d(Xe,1):d(xe,-1)})}l(re,he)},M=re=>{var he=$l(),de=K(he),He=a(de),Xe=r(de,2),xe=a(Xe);rt(xe,21,()=>e(j),d=>d.id,(d,w)=>{var Y=Nl(),f=a(Y),q=a(f),L=r(f,2),_e=a(L),ne=r(L,2),qe=a(ne),Se=r(ne,2);{var ue=Te=>{var Je=Ol(),ae=a(Je);p(()=>i(ae,e(w).code)),l(Te,Je)};m(Se,Te=>{e(w).code!==""&&Gt.showTechnicalNames&&Te(ue)})}var W=r(Se,2),Ue=a(W),Ve=r(W,2);{var Le=Te=>{var Je=Al(),ae=a(Je);p(()=>i(ae,e(w).detail)),l(Te,Je)};m(Ve,Te=>{e(w).detail!==""&&Te(Le)})}p((Te,Je,ae)=>{Be(Y,"data-level",e(w).level),i(q,Te),i(_e,Je),i(qe,ae),i(Ue,e(w).message)},[()=>Mt(e(w).occurred_at),()=>S(ye,e(w).level,"niveau inconnu"),()=>Xa(e(w).source)]),l(d,Y)}),p(()=>i(He,e(Z))),l(re,he)};m(Ee,re=>{e(j).length===0?re(x):re(M,-1)})}l(I,ce)},$$slots:{default:!0}}),l(n,z),Rt()}Tt(["change","click"]);var Ul=u('

                                                                                                                                                        '),Fl=u('

                                                                                                                                                        '),Ml=u('

                                                                                                                                                        '),Bl=u('

                                                                                                                                                        '),Vl=u('

                                                                                                                                                        Lecture de la configuration en cours… les flèches attendent qu’elle soit arrivée.

                                                                                                                                                        '),Wl=u(`
                                                                                                                                                        Aperçu de l’étiquette telle qu’elle serait imprimée

                                                                                                                                                        Décalage horizontal

                                                                                                                                                        Décalage vertical

                                                                                                                                                        Le décalage ne descend pas sous zéro : le poste refuse un décalage négatif quel que + soit le gabarit. Le maximum, lui, dépend de la géométrie du gabarit ; il est annoncé + ici si l’enregistrement le dépasse.

                                                                                                                                                        Le symbole code-barres est volontairement tronqué : un symbole conforme n’entre pas + sur 40 × 25 mm avec les cinq champs texte. Ce n’est pas un défaut de rendu et il n’y + a rien à corriger.

                                                                                                                                                        Le détail chiffré du symbole — largeur de module, modules rendus, modules attendus — + n’est pas servi par ce poste. Cet écran n’affiche pas un chiffre qu’il aurait deviné.

                                                                                                                                                        `,1),Gl=u(`

                                                                                                                                                        Chaque appui sort une étiquette pour de bon : le mot de passe est demandé au moment + d’imprimer, et l’impression repart d’elle-même une fois la session ouverte.

                                                                                                                                                        `,1),Hl=u('
                                                                                                                                                        ');function Jl(n,t){Ct(t,!0);const s=vt(t,"admin",7),c="printer.template",_="printer.options.offset_x",b="printer.options.offset_y",R=0,N="L’aperçu n’a pas pu être rendu par le poste.",$="Le poste a rendu l’aperçu, mais le navigateur ne l’a pas affiché.",G=[{what:"alignment",label:"Imprimer la mire d’alignement"},{what:"ruler",label:"Imprimer la réglette"}];let V=U(1),se=U(!0),be=U(!1),ye=U(""),ie=U(null),j=U(""),oe=null,X=!1;const we=o(()=>t.draft.text(c)),me=o(()=>Fr(e(we),e(se),e(be),e(V))),E=o(()=>t.draft.config!==null),H=o(()=>t.draft.number(_)),F=o(()=>t.draft.number(b)),P=o(()=>O(_)),te=o(()=>O(b)),T=o(S),y=o(()=>s().busy||e(j)!=="");aa(()=>{if(t.draft.config===null)return;if(t.draft.dirty){oe===null&&C();return}const x={x:t.draft.number(_),y:t.draft.number(b)};if(oe!==null&&oe.x===x.x&&oe.y===x.y)return;const M=oe===null;oe=x,v(ie,x,!0),M||pe()});async function C(){if(!X){X=!0;try{const x=await qa(),M={x:D(x.config,_),y:D(x.config,b)};oe=M,v(ie,M,!0)}catch{}finally{X=!1}}}function D(x,M){let re=x;for(const he of M.split(".")){if(re===null||typeof re!="object")return 0;re=re[he]}return typeof re=="number"?re:0}function Z(x,M){const re=t.draft.number(x)+M;re=2?"s":""}`}function g(x,M){const re=Number(M);M.trim()===""||Number.isNaN(re)||t.draft.set(x,re)}function O(x){return t.draft.faults.find(M=>M.field===x)?.message??""}async function I(x){if(!e(y)){v(j,x,!0),s().actionError="",s().notice="";try{const M=await s().protect(()=>Ha(x));if(M===null)return;s().notice=M.message,await s().refresh()}finally{v(j,"")}}}var J=Hl(),ce=a(J);Qe(ce,{title:"Aperçu de l’étiquette",note:"Le même moteur que l’impression (A2) : le décalage se voit parce qu’il est cuit dans le bitmap. L’image porte le gabarit en cours d’édition, mais le décalage ENREGISTRÉ — jamais celui que les flèches sont en train de régler.",children:(x,M)=>{var re=Wl(),he=K(re);{var de=je=>{var st=Ul(),Ze=a(st);p(()=>i(Ze,e(T))),l(je,st)};m(he,je=>{e(T)!==""&&je(de)})}var He=r(he,2),Xe=a(He),xe=a(Xe),d=r(xe,2);{var w=je=>{var st=Fl(),Ze=a(st);p(()=>i(Ze,e(ye))),l(je,st)};m(d,je=>{e(ye)!==""&&je(w)})}var Y=r(Xe,2),f=a(Y),q=r(a(f),2),L=a(q),_e=r(f,2),ne=a(_e);{let je=o(()=>!e(E)||e(H)<=R);at(ne,{label:"← 1 dot",get disabled(){return e(je)},onrun:()=>Z(_,-1)})}var qe=r(ne,2);{let je=o(()=>!e(E));at(qe,{label:"1 dot →",get disabled(){return e(je)},onrun:()=>Z(_,1)})}var Se=r(_e,2);{var ue=je=>{var st=Ml(),Ze=a(st);p(()=>i(Ze,e(P))),l(je,st)};m(Se,je=>{e(P)!==""&&je(ue)})}var W=r(Se,2),Ue=r(a(W),2),Ve=a(Ue),Le=r(W,2),Te=a(Le);{let je=o(()=>!e(E)||e(F)<=R);at(Te,{label:"↑ 1 dot",get disabled(){return e(je)},onrun:()=>Z(b,-1)})}var Je=r(Te,2);{let je=o(()=>!e(E));at(Je,{label:"1 dot ↓",get disabled(){return e(je)},onrun:()=>Z(b,1)})}var ae=r(Le,2);{var Ie=je=>{var st=Bl(),Ze=a(st);p(()=>i(Ze,e(te))),l(je,st)};m(ae,je=>{e(te)!==""&&je(Ie)})}var ve=r(ae,4),ze=a(ve),Re=r(ve,2),nt=a(Re),ut=r(Re,2);{var pt=je=>{var st=Vl();l(je,st)};m(ut,je=>{e(E)||je(pt)})}p((je,st)=>{Be(xe,"src",e(me)),i(L,je),i(Ve,st)},[()=>z(e(H)),()=>z(e(F))]),Qt("load",xe,()=>v(ye,"")),Qt("error",xe,()=>{Oe(e(me))}),et("change",ze,()=>pe()),za(ze,()=>e(se),je=>v(se,je)),et("change",nt,()=>pe()),za(nt,()=>e(be),je=>v(be,je)),l(x,re)},$$slots:{default:!0}});var Ee=r(ce,2);Qe(Ee,{title:"Gabarit et impression",children:(x,M)=>{var re=Gl(),he=K(re);{let d=o(()=>O(c)),w=o(()=>!e(E));_t(he,{label:"Gabarit",path:c,get value(){return e(we)},hint:"Le gabarit reproduit à l’identique s’appelle weighing_identical (A1).",get fault(){return e(d)},get disabled(){return e(w)},onchange:Y=>{t.draft.set(c,Y),pe()}})}var de=r(he,2);{let d=o(()=>t.draft.text("printer.options.darkness")),w=o(()=>O("printer.options.darkness")),Y=o(()=>!e(E));_t(de,{label:"Noircissement",path:"printer.options.darkness",kind:"number",get value(){return e(d)},hint:"Trop bas, l’étiquette pâlit au soleil ; trop haut, elle bave et le scanner refuse.",get fault(){return e(w)},get disabled(){return e(Y)},onchange:f=>g("printer.options.darkness",f)})}var He=r(de,2);{let d=o(()=>t.draft.text("printer.options.speed")),w=o(()=>O("printer.options.speed")),Y=o(()=>!e(E));_t(He,{label:"Vitesse",path:"printer.options.speed",kind:"number",get value(){return e(d)},get fault(){return e(w)},get disabled(){return e(Y)},onchange:f=>g("printer.options.speed",f)})}var Xe=r(He,2);{let d=o(()=>t.draft.text("printer.options.copies")),w=o(()=>O("printer.options.copies")),Y=o(()=>!e(E));_t(Xe,{label:"Exemplaires",path:"printer.options.copies",kind:"number",get value(){return e(d)},hint:"Un client repart avec une étiquette : deux exemplaires se justifient, pas se devinent.",get fault(){return e(w)},get disabled(){return e(Y)},onchange:f=>g("printer.options.copies",f)})}var xe=r(Xe,2);rt(xe,21,()=>G,d=>d.what,(d,w)=>{{let Y=o(()=>e(j)===e(w).what);at(d,{get act(){return e(w).what},get label(){return e(w).label},protected:!0,get busy(){return e(Y)},get disabled(){return e(y)},onrun:()=>{I(e(w).what)}})}}),l(x,re)},$$slots:{default:!0}}),l(n,J),Rt()}Tt(["change"]);var Kl=u(" "),Xl=u(' '),Yl=u(''),Ql=u('

                                                                                                                                                        Aucun tarif déclaré dans la configuration lue.

                                                                                                                                                        '),Zl=u('Prix du catalogue Odoo — pas de remise'),$a=u(' '),ei=u(' % ',1),ti=u(' '),ai=u(`

                                                                                                                                                        CodeLibelléAbrégéRemiseOrdre

                                                                                                                                                        Un champ vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la + retrouve dès qu’on quitte le champ. Une remise effacée serait le plein tarif pour + tous les adhérents.

                                                                                                                                                        `,1),ri=u(" ",1),ni=u(`

                                                                                                                                                        Les deux arrondis sont distincts parce qu’ils tombent à des endroits différents du + calcul : arrondir le prix au kilo puis le multiplier, ou multiplier puis arrondir, ne + donne pas le même centime sur une étiquette. Le poste applique l’arrondi commercial, + et l’écart se voit au centime près sur la même pesée.

                                                                                                                                                        `,1),si=u('

                                                                                                                                                        Rien ne s’affiche au client : c’est une information.

                                                                                                                                                        '),li=u('

                                                                                                                                                        '),ii=u('
                                                                                                                                                        '),oi=u('

                                                                                                                                                        '),ui=u('
                                                                                                                                                      • '),ci=u(`

                                                                                                                                                        Aucun verdict en ce moment : ces lignes portent sur la pesée EN COURS, et le poste + est au repos. Elles apparaissent le temps d’un cycle, quand un client pose son sac + et touche une tuile.

                                                                                                                                                        `),di=u(' '),vi=u(' ',1),pi=u('
                                                                                                                                                      • '),fi=u('

                                                                                                                                                          ',1),gi=u(`

                                                                                                                                                          L’ordre compte : le premier verdict bloquant décide de ce que le client lit. Les + garde-fous 1 à 7 portent sur l’état de la balance, c’est-à-dire le poids brut ; les + garde-fous 8 à 14 portent sur la vente, c’est-à-dire le net. Le code en gris est + celui du journal, celui qu’on lit au téléphone.

                                                                                                                                                            Un seuil vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la retrouve + dès qu’on quitte le champ. Pour changer un seuil, on tape l’autre valeur.

                                                                                                                                                            Ce que les garde-fous disent de la pesée en cours

                                                                                                                                                            `,1),mi=u(`

                                                                                                                                                            Le plan de numérotation n’est PAS ici, et c’est tout l’intérêt : préfixes, largeur de + la référence, largeur de la charge utile, décimales et mode de vente sont une + constante du binaire, indexée par préfixe et vérifiée au démarrage. + Un champ qui change le SENS du code lu par la caisse n’est pas un réglage, c’est un + contrat externe : il change avec une version du binaire, relue et testée, jamais + depuis l’écran d’un poste.

                                                                                                                                                            `,1),hi=u('

                                                                                                                                                            Aucune dérogation : la limite générale s’applique à tous les produits.

                                                                                                                                                            '),_i=u(`

                                                                                                                                                            Les noms de produits n’ont pas pu être lus : le catalogue en service n’a pas + répondu. Les identifiants Odoo restent affichés.

                                                                                                                                                            `),bi=u(`Produit retiré : le garde-fou 14 refuse le produit avant que cette + dérogation ait un sens.`),wi=u('
                                                                                                                                                          1. '),yi=u('

                                                                                                                                                              ',1),xi=u('
                                                                                                                                                              ');function ki(n,t){Ct(t,!0);const s=(g,O=sa,I=sa,J=sa)=>{var ce=Yl(),Ee=a(ce),x=r(Ee,2),M=a(x),re=a(M),he=r(M,2);{var de=xe=>{var d=Kl(),w=a(d);p(()=>i(w,O())),l(xe,d)};m(he,xe=>{Gt.showTechnicalNames&&xe(de)})}var He=r(he,2);{var Xe=xe=>{var d=Xl(),w=a(d);p(()=>i(w,J())),l(xe,d)};m(He,xe=>{J()!==""&&xe(Xe)})}p((xe,d)=>{Be(ce,"data-flag",O()),Be(ce,"data-on",xe),ua(Ee,d),i(re,I())},[()=>String(t.draft.flag(O())),()=>t.draft.flag(O())]),et("change",Ee,xe=>t.draft.set(O(),xe.currentTarget.checked)),l(g,ce)},c=20,_=o(()=>se(t.draft)),b=o(()=>t.health.state.diagnostics),R=o(()=>t.health.decisions.filter(g=>g.min_weight_g!==null)),N=o(()=>e(R).filter(g=>!g.offered)),$=o(()=>e(R).slice(0,c));let G=U(Lt({})),V=U("loading");me();function se(g){const O=g.value("pricing.tiers");return Array.isArray(O)?O.map(I=>{const J=I??{},ce=J.discount_percent;return{code:String(J.code??""),label:String(J.label??""),abbrev:String(J.abbrev??""),written:ce===void 0?null:String(ce),discount:be(ce)?ce:null,rank:Number(J.rank??0)}}):[]}function be(g){return typeof g!="number"||!Number.isFinite(g)||g<0||g>100?!1:Math.abs(g*10-Math.round(g*10))<1e-9}const ye=o(()=>String(t.draft.value("pricing.reference_code")??""));function ie(g){return g===null?"":String(g).replace(".",",")}function j(g){const O=1e3-Math.round(g*10);return`${String(Math.trunc(O/100))},${String(O%100).padStart(2,"0")}`}function oe(g,O){const I=Number(O);O.trim()===""||Number.isNaN(I)||t.draft.set(g,I)}function X(g,O){const I=O.trim().replace(",",".");if(!/^\d{1,3}(\.\d)?$/u.test(I))return;const J=Number(I);J>100||t.draft.set(g,J)}function we(g,O){!(g instanceof HTMLInputElement)||g.value===O||(g.value=O)}async function me(){try{const g=await Ba();v(G,Object.fromEntries(g.products.map(O=>[O.id,O.name])),!0),v(V,"read")}catch{v(V,"unread")}}function E(g){const O=e(G)[g];return O!==void 0?O:e(V)==="loading"?"Lecture du nom…":e(V)==="read"?"Produit absent du catalogue en service":"Nom non lu"}function H(g){return g<=1?`${String(g)} tarif déclaré`:`${String(g)} tarifs déclarés`}const F=o(()=>`${ge(e(b).length)} ${e(b).length>1?"verdicts":"verdict"} sur les quatorze garde-fous.`),P=o(()=>`${ge(e($).length)} ${e($).length>1?"lignes affichées":"ligne affichée"} sur ${ge(e(R).length)} `+(e(N).length===0?`${e(R).length>1?"dérogations en vigueur":"dérogation en vigueur"}.`:`${e(R).length>1?"dérogations enregistrées":"dérogation enregistrée"}, dont ${ge(e(N).length)} sur un produit retiré, sans effet : le garde-fou 14 refuse le produit avant que le 8 ait un sens.`)+(e(R).length>e($).length?" Les autres se lisent produit par produit depuis l’onglet Catalogue.":""));function te(g){return`plancher ${ge(g)} g : refusé à ${ge(g)} g et en dessous`}const T=[{rank:1,code:"OVERLOAD",label:"Surcharge",when:"La balance annonce elle-même OL, ou le poids brut dépasse la capacité.",severity:"Bloquant",blocking:!0,message:"La balance est en surcharge. Retirez votre article.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la capacité, réglée au garde-fou 9 sous « ${Ft("limits.max_weight_g")} ».`},{rank:2,code:"MEASUREMENT_EXPIRED",label:"Poids périmé",when:"La mesure est plus vieille que la péremption, dans les deux modes de stabilité.",severity:"Bloquant",blocking:!0,message:"Poids indisponible. Patientez ou appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil à régler : le poste calcule lui-même à partir du rythme de la balance."},{rank:3,code:"BASKET_MISSING",label:"Panier absent",when:"Le poids brut tombe dans la fenêtre négative du panier : il a été soulevé.",severity:"Bloquant",blocking:!0,message:"Le panier n'est pas sur la balance. Reposez-le.",thresholds:[{path:"limits.basket_min_g",label:"Bas de la fenêtre du panier",hint:"En grammes, NÉGATIF : c’est le poids du panier que la balance a perdu."},{path:"limits.basket_max_g",label:"Haut de la fenêtre du panier",hint:"Négatif lui aussi, et plus proche de zéro que le bas."}],switchPath:"limits.basket_check_enabled",switchLabel:"Ce poste travaille avec un panier taré",note:"La règle s’active ou non, en bloc : il n’y a pas de demi-mesure à régler."},{rank:4,code:"SCALE_EMPTY",label:"Plateau vide",when:"Le poids brut ne sort pas de la bande « il n’y a rien sur le plateau ».",severity:"Bloquant — un filet, hors parcours nominal",blocking:!0,message:"Posez votre produit.",thresholds:[{path:"limits.empty_max_g",label:"Plateau considéré vide",hint:"En dessous, le poste considère qu’il n’y a rien sur le plateau."}],switchPath:"",switchLabel:"",note:"Toucher une tuile sur un plateau vide ARME la sélection au lieu d’être refusé : la règle reste évaluée pour la saisie manuelle et les chemins dérivés."},{rank:5,code:"TARE_REQUIRED",label:"Remise à zéro nécessaire",when:"Le brut est sous la bande du plateau vide, et hors de la fenêtre du panier.",severity:"Bloquant",blocking:!0,message:"La balance doit être remise à zéro.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la valeur négative de celui du garde-fou 4, « ${Ft("limits.empty_max_g")} ».`},{rank:6,code:"WEIGHT_UNSTABLE",label:"Pesée instable",when:"La trame déclare la mesure instable.",severity:"Information par défaut (A3)",blocking:!1,message:"Pesée en cours…",thresholds:[],switchPath:"",switchLabel:"",note:"La sévérité suit l’exigence de stabilité : elle passe à Bloquant quand celle-ci est réglée sur « blocking ». L’impression n’est jamais bloquée par défaut."},{rank:7,code:"TARE_INVALID",label:"Emballage incohérent",when:"Une tare a été saisie, et elle atteint la pesée ou dépasse le maximum.",severity:"Bloquant",blocking:!0,message:"Le poids de l'emballage est supérieur ou égal à la pesée.",thresholds:[{path:"limits.max_tare_g",label:"Tare maximum",hint:"Une tare plus lourde que le maximum est une faute de frappe."}],switchPath:"",switchLabel:"",note:""},{rank:8,code:"WEIGHT_TOO_LOW",label:"Poids trop faible",when:"Vente au poids : le NET est strictement positif et ne dépasse pas le plancher.",severity:"Bloquant",blocking:!0,message:"La balance doit être retarée, ou l'emballage est trop lourd.",thresholds:[{path:"limits.min_weight_g",label:"Poids minimum",hint:"Une dérogation par produit existe, dans l’onglet Catalogue."}],switchPath:"",switchLabel:"",note:""},{rank:9,code:"WEIGHT_TOO_HIGH",label:"Poids trop élevé",when:"Le NET dépasse la capacité — strictement, pour que la capacité reste atteignable.",severity:"Bloquant",blocking:!0,message:"{{.Weight}} kg, ça paraît un peu lourd !",thresholds:[{path:"limits.max_weight_g",label:"Poids maximum",hint:"C’est la capacité du champ NNDDD du code-barres, pas un seuil de vraisemblance."}],switchPath:"",switchLabel:"",note:""},{rank:10,code:"UNITS_OUT_OF_RANGE",label:"Nombre d’unités hors plage",when:"Vente à l’unité : la quantité sort de la plage.",severity:"Bloquant",blocking:!0,message:"{{.Quantity}} unités, ça paraît un peu beaucoup !",thresholds:[{path:"limits.min_units",label:"Unités minimum",hint:""},{path:"limits.max_units",label:"Unités maximum",hint:""}],switchPath:"",switchLabel:"",note:""},{rank:11,code:"AMOUNT_OUT_OF_CAPACITY",label:"Montant hors capacité du code-barres",when:"La charge utile encode un PRIX, et il dépasse ce que le champ peut porter.",severity:"Bloquant",blocking:!0,message:"Prix trop élevé pour le code-barres.",thresholds:[{path:"limits.max_amount_cents",label:"Montant maximum",hint:"En centimes. Aucun préfixe du plan livré n’encode un prix : la règle est éprouvée sans qu’aucun produit puisse l’atteindre."}],switchPath:"",switchLabel:"",note:""},{rank:12,code:"ZERO_PRICE",label:"Prix nul",when:"Le montant du tarif imprimé en grand vaut zéro.",severity:"Bloquant",blocking:!0,message:"Prix nul. Appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : un produit à 0 € est une anomalie sans nuance."},{rank:13,code:"LIGHT_PRODUCT_ALLOWED",label:"Produit léger autorisé",when:"Le garde-fou 8 n’a pas déclenché grâce à la dérogation du produit.",severity:"Information",blocking:!1,message:"",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil général : c’est la dérogation par produit, listée plus bas et posée depuis l’onglet Catalogue. Rien ne s’affiche au client ; l’id du produit est journalisé."},{rank:14,code:"PRODUCT_WITHDRAWN",label:"Produit retiré",when:"Quelqu’un a décidé de ne plus proposer ce produit.",severity:"Bloquant",blocking:!0,message:"Ce produit n'est pas disponible.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : c’est une décision humaine, prise depuis l’onglet Catalogue. Aucune règle d’import ne peut la déduire."}];function y(g){return T.find(O=>O.code===g)?.label??"Garde-fou inconnu de cet écran"}const C=["{{.Weight}}","{{.Quantity}}"];function D(g){return g==="blocking"?"Bloquant":g==="info"?"Information":"Sévérité inconnue de cet écran"}var Z=xi(),pe=a(Z);Qe(pe,{title:"Grille de tarifs",note:"Un second tarif n’est pas une case à cocher : c’est une ligne de plus dans cette grille.",children:(g,O)=>{var I=ri(),J=K(I);{var ce=re=>{var he=Ql();l(re,he)},Ee=re=>{var he=ai(),de=K(he),He=a(de),Xe=r(de,2),xe=a(Xe),d=r(a(xe));rt(d,21,()=>e(_),fa,(w,Y,f)=>{var q=ti(),L=a(q),_e=a(L),ne=r(L),qe=a(ne);Be(qe,"aria-label",`Libellé du tarif ${f+1}`);var Se=r(ne),ue=a(Se);Be(ue,"aria-label",`Abrégé du tarif ${f+1}`);var W=r(Se),Ue=a(W);{var Ve=ve=>{var ze=Zl();l(ve,ze)},Le=ve=>{var ze=$a(),Re=a(ze);p(()=>i(Re,`${e(Y).written??""} — le tarif de référence est le prix du catalogue : il + ne peut pas porter de remise, et l’enregistrement la refusera.`)),l(ve,ze)},Te=ve=>{var ze=$a(),Re=a(ze);p(()=>i(Re,`${e(Y).written??""} — une remise s’écrit au dixième de point ; celle-ci se + change dans le fichier de configuration.`)),l(ve,ze)},Je=ve=>{var ze=ei(),Re=K(ze);Be(Re,"aria-label",`Remise du tarif ${f+1}`);var nt=r(Re,2),ut=a(nt);p((pt,je)=>{na(Re,pt),i(ut,`un produit à 10,00 €/kg s’affiche ${je??""} €/kg`)},[()=>ie(e(Y).discount??0),()=>j(e(Y).discount??0)]),et("input",Re,pt=>X(`pricing.tiers.${String(f)}.discount_percent`,pt.currentTarget.value)),et("focusout",Re,pt=>we(pt.currentTarget,ie(e(Y).discount??0))),l(ve,ze)};m(Ue,ve=>{e(Y).code===e(ye)&&e(Y).written===null?ve(Ve):e(Y).code===e(ye)?ve(Le,1):e(Y).written!==null&&e(Y).discount===null?ve(Te,2):ve(Je,-1)})}var ae=r(W),Ie=a(ae);p(()=>{i(_e,e(Y).code),na(qe,e(Y).label),na(ue,e(Y).abbrev),i(Ie,e(Y).rank)}),et("input",qe,ve=>t.draft.set(`pricing.tiers.${String(f)}.label`,ve.currentTarget.value)),et("input",ue,ve=>t.draft.set(`pricing.tiers.${String(f)}.abbrev`,ve.currentTarget.value)),l(w,q)}),p(w=>i(He,`${w??""}.`),[()=>H(e(_).length)]),l(re,he)};m(J,re=>{e(_).length===0?re(ce):re(Ee,-1)})}var x=r(J,2);{let re=o(()=>t.draft.text("pricing.primary_code"));_t(x,{label:"Tarif imprimé en grand",path:"pricing.primary_code",get value(){return e(re)},hint:"Le prix que le client lit sur l’étiquette (A7).",onchange:he=>t.draft.set("pricing.primary_code",he)})}var M=r(x,2);{let re=o(()=>t.draft.text("pricing.reference_code"));_t(M,{label:"Tarif qui serait encodé si le code-barres portait un prix",path:"pricing.reference_code",get value(){return e(re)},hint:"Le plan livré ne porte pas de prix, mais un poids ou un nombre d’unités : la caisse retrouve le prix par la référence, dans Odoo, et ne doit jamais sous-facturer.",onchange:he=>t.draft.set("pricing.reference_code",he)})}l(g,I)},$$slots:{default:!0}});var Oe=r(pe,2);Qe(Oe,{title:"Les deux arrondis",children:(g,O)=>{var I=ni(),J=K(I);{let Ee=o(()=>t.draft.text("pricing.unit_price_rounding"));_t(J,{label:"Arrondi du prix unitaire dérivé",path:"pricing.unit_price_rounding",get value(){return e(Ee)},hint:"Il s’applique au prix au kilo calculé par la remise.",onchange:x=>t.draft.set("pricing.unit_price_rounding",x)})}var ce=r(J,2);{let Ee=o(()=>t.draft.text("pricing.amount_rounding"));_t(ce,{label:"Arrondi du montant",path:"pricing.amount_rounding",get value(){return e(Ee)},hint:"Il s’applique au montant de l’étiquette.",onchange:x=>t.draft.set("pricing.amount_rounding",x)})}l(g,I)},$$slots:{default:!0}});var Ae=r(Oe,2);Qe(Ae,{title:"Les quatorze garde-fous, dans l’ordre d’évaluation",note:"Le seuil se modifie ici. La sévérité ne se règle pas : elle dit si le poste refuse ou avertit, et cela ne dépend pas du magasin. Le message affiché au client n’est pas encore modifiable depuis cet écran.",children:(g,O)=>{var I=gi(),J=r(K(I),2);rt(J,21,()=>T,he=>he.code,(he,de)=>{var He=ui(),Xe=a(He),xe=a(Xe),d=a(xe),w=r(xe,2),Y=a(w),f=r(w,2),q=a(f),L=r(f,2),_e=a(L),ne=r(Xe,2),qe=a(ne),Se=r(ne,2);{var ue=ae=>{var Ie=si();l(ae,Ie)},W=ae=>{var Ie=li(),ve=a(Ie);p(()=>i(ve,`« ${e(de).message??""} »`)),l(ae,Ie)};m(Se,ae=>{e(de).message===""?ae(ue):ae(W,-1)})}var Ue=r(Se,2);{var Ve=ae=>{s(ae,()=>e(de).switchPath,()=>e(de).switchLabel,()=>"")};m(Ue,ae=>{e(de).switchPath!==""&&ae(Ve)})}var Le=r(Ue,2);rt(Le,17,()=>e(de).thresholds,ae=>ae.path,(ae,Ie)=>{var ve=ii(),ze=a(ve);{let Re=o(()=>t.draft.text(e(Ie).path));_t(ze,{get label(){return e(Ie).label},get path(){return e(Ie).path},kind:"number",get value(){return e(Re)},get hint(){return e(Ie).hint},onchange:nt=>oe(e(Ie).path,nt)})}et("focusout",ve,Re=>we(Re.target,t.draft.text(e(Ie).path))),l(ae,ve)});var Te=r(Le,2);{var Je=ae=>{var Ie=oi(),ve=a(Ie);p(()=>i(ve,e(de).note)),l(ae,Ie)};m(Te,ae=>{e(de).note!==""&&ae(Je)})}p(ae=>{Be(He,"data-code",e(de).code),i(d,e(de).rank),i(Y,e(de).label),i(q,e(de).code),Be(L,"data-blocking",ae),i(_e,e(de).severity),i(qe,e(de).when)},[()=>String(e(de).blocking)]),l(he,He)});var ce=r(J,4),Ee=a(ce),x=r(ce,4);{var M=he=>{var de=ci();l(he,de)},re=he=>{var de=fi(),He=K(de),Xe=a(He),xe=r(He,2);rt(xe,21,()=>e(b),d=>d.code,(d,w)=>{var Y=pi(),f=a(Y),q=a(f),L=r(f,2),_e=a(L),ne=r(L,2),qe=a(ne),Se=r(ne,2);{var ue=Ve=>{var Le=di(),Te=a(Le);p(()=>i(Te,`« ${e(w).message??""} »`)),l(Ve,Le)};m(Se,Ve=>{e(w).message!==""&&Ve(ue)})}var W=r(Se,2);{var Ue=Ve=>{var Le=vi(),Te=K(Le),Je=a(Te),ae=r(Te,2),Ie=a(ae);p(ve=>{i(Je,ve),i(Ie,e(w).product_id)},[()=>E(e(w).product_id)]),l(Ve,Le)};m(W,Ve=>{e(w).product_id!==""&&Ve(Ue)})}p((Ve,Le,Te)=>{Be(Y,"data-blocking",Ve),i(q,Le),i(_e,e(w).code),i(qe,Te)},[()=>String(e(w).blocking),()=>y(e(w).code),()=>D(e(w).severity)]),l(d,Y)}),p(d=>{Be(He,"data-verdicts",d),i(Xe,e(F))},[()=>String(e(b).length)]),l(he,de)};m(x,he=>{e(b).length===0?he(M):he(re,-1)})}p(he=>i(Ee,`Les marqueurs ${he??""} sont remplacés par les valeurs de la pesée au + moment où le message s’affiche.`),[()=>C.join(" et ")]),l(g,I)},$$slots:{default:!0}});var S=r(Ae,2);Qe(S,{title:"Code-barres",note:"Un seul réglage, et c’est voulu : le reste du plan de numérotation n’est pas de la configuration.",children:(g,O)=>{var I=mi(),J=K(I);s(J,()=>"barcode.verify_reference_check_digit",()=>"Refuser une référence dont la clé de contrôle est fausse",()=>"Décoché, le poste recalcule une clé juste sur une référence fausse, en silence — et la caisse encaisse un autre article."),l(g,I)},$$slots:{default:!0}});var z=r(S,2);Qe(z,{title:"Dérogations de poids minimum",note:"En lecture ici ; elles se modifient depuis l’onglet Catalogue, là où se trouve le produit.",children:(g,O)=>{var I=Dt(),J=K(I);{var ce=x=>{var M=hi();l(x,M)},Ee=x=>{var M=yi(),re=K(M),he=a(re),de=r(re,2);{var He=xe=>{var d=_i();l(xe,d)};m(de,xe=>{e(V)==="unread"&&xe(He)})}var Xe=r(de,2);rt(Xe,21,()=>e($),xe=>xe.product_id,(xe,d)=>{var w=wi(),Y=a(w),f=a(Y),q=r(Y,2),L=a(q),_e=r(q,2),ne=a(_e),qe=r(_e,2),Se=a(qe),ue=r(qe,2),W=a(ue),Ue=r(ue,2);{var Ve=Le=>{var Te=bi();l(Le,Te)};m(Ue,Le=>{e(d).offered||Le(Ve)})}p((Le,Te,Je,ae)=>{Be(w,"data-withdrawn",Le),i(f,Te),i(L,e(d).product_id),i(ne,Je),i(Se,e(d).reason),i(W,`${ae??""}, ${e(d).decided_by??""}`)},[()=>String(!e(d).offered),()=>E(e(d).product_id),()=>te(e(d).min_weight_g??0),()=>da(e(d).decided_at)]),l(xe,w)}),p(()=>i(he,e(P))),l(x,M)};m(J,x=>{e(R).length===0?x(ce):x(Ee,-1)})}l(g,I)},$$slots:{default:!0}}),l(n,Z),Rt()}Tt(["change","input","focusout"]);function qi(n,t){const s=[];for(const c of[...ya(n),...ya(t)]){if(s.some(R=>R.path===c))continue;const _=Da(va(n,c)),b=Da(va(t,c));_!==b&&s.push({path:c,before:_,after:b})}return s}function va(n,t){let s=n;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}function ya(n,t=""){if(n===null||typeof n!="object"||Array.isArray(n))return t===""?[]:[t];const s=[];for(const[c,_]of Object.entries(n))s.push(...ya(_,t===""?c:`${t}.${c}`));return s}function Da(n){return n===void 0?"—":n===null?"vide":typeof n=="boolean"?n?"oui":"non":typeof n=="object"?JSON.stringify(n):String(n)}var Si=u('
                                                                                                                                                              Empreinte de la configuration en service
                                                                                                                                                              Version du binaire
                                                                                                                                                              Répertoire de données
                                                                                                                                                              Espace disque
                                                                                                                                                              ',1),Li=u('

                                                                                                                                                              '),Ia=u(" "),Ei=u(' '),zi=u("
                                                                                                                                                            • "),ji=u(`

                                                                                                                                                                Recopier reste possible : les valeurs entrent dans le brouillon, où elles se + corrigent champ par champ avant l’enregistrement.

                                                                                                                                                                `),Pi=u(`

                                                                                                                                                                Ce fichier n’a été comparé à rien : la configuration en service n’a pas pu être + lue. Ni « identique », ni « n champs changent » — ce que ce fichier changerait + sur ce poste reste inconnu.

                                                                                                                                                                `),Ti=u(`

                                                                                                                                                                Ce fichier décrit la même configuration que celle en service : il n’y a rien à + recopier. C’est ce qu’on veut lire à la fin d’un clonage. Deux champs ne sont pas + comparés : la date du dernier enregistrement, que chaque poste écrit lui-même, et + le mot de passe du catalogue, qu’aucun des deux ne porte en clair.

                                                                                                                                                                `),Ci=u(' '),Ri=u('

                                                                                                                                                                '),Oi=u(`

                                                                                                                                                                ChampEn serviceDans le fichier

                                                                                                                                                                Recopier n’applique rien : les valeurs entrent dans le brouillon, et c’est + « Enregistrer » qui les met en service.

                                                                                                                                                                `,1),Ai=u('

                                                                                                                                                                ',1),Ni=u(`

                                                                                                                                                                L’export emporte encore l’empreinte du mot de passe : c’est la seule lecture que le + poste garde derrière la clé. L’import, lui, est lu PAR LE POSTE, qui écarte les deux + secrets et le numéro de poste avant de dire ce qui changerait.

                                                                                                                                                                `,1),$i=u('

                                                                                                                                                                Lecture des versions enregistrées…

                                                                                                                                                                '),Di=u(`

                                                                                                                                                                Les versions enregistrées n’ont pas pu être lues : cette liste ne dit donc rien de + ce que ce poste garde. Ce n’est pas « aucune version ».

                                                                                                                                                                `),Ii=u('

                                                                                                                                                                Aucune version enregistrée : ce poste n’a jamais été reconfiguré.

                                                                                                                                                                '),Ui=u('
                                                                                                                                                              • '),Fi=u(`

                                                                                                                                                                  Remettre une version en service remplace la configuration du poste sur-le-champ, et + ce qui n’a pas été enregistré est perdu : c’est le seul geste de cette page qui + change le poste tout de suite, et le seul qui garde ses 72 px.

                                                                                                                                                                  `,1),Mi=u('
                                                                                                                                                                  ');function Bi(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=40,_=20,b=5,R=new Set(["modified_at","catalog.options.password"]),N=[{path:"station.name",name:"le nom du poste"},{path:"network.listen",name:"l’adresse d’écoute"},{path:"scale.options.port",name:"le port de la balance"},{path:"printer.options.queue",name:"la file d’impression"},{path:"catalog.options.url",name:"l’adresse du partage"},{path:"catalog.options.username",name:"le compte du partage"},{path:"catalog.images.path",name:"le chemin des images"}];let $=U(Lt([])),G=U("reading"),V=U(null),se=U(""),be=U(null),ye=U(""),ie=U(Lt([])),j=U(""),oe="";const X=o(()=>s().busy||e(j)!==""),we=o(()=>D(e(V),e(be))),me=o(()=>e(V)!==null&&e(be)!==null),E=o(()=>Z(e(V),e(be))),H=o(()=>e(we).slice(0,c)),F=o(()=>e(ie).slice(0,_)),P=o(()=>e($).slice(0,b)),te=o(()=>Xe(e(H).length,e(we).length,"champ qui change","champs qui changent")),T=o(()=>Xe(e(F).length,e(ie).length,"contrôle refuse une clé","contrôles refusent une clé")),y=o(()=>Xe(e(P).length,e($).length,"version enregistrée","versions enregistrées")),C=o(()=>e(we).length>1?`Recopier ces ${ge(e(we).length)} champs dans le brouillon`:"Recopier ce champ dans le brouillon");Ae();function D(f,q){return f===null||q===null?[]:qi(f,q).filter(L=>!R.has(L.path))}function Z(f,q){if(f===null||q===null)return[];const L=f,_e=q;return N.filter(ne=>pe(_e,ne.path)&&!pe(L,ne.path))}function pe(f,q){const L=va(f,q);return L==null||L===""?!0:typeof L!="object"||Array.isArray(L)?!1:Object.keys(L).length===0}function Oe(f){return f.length<2?f.join(""):`${f.slice(0,-1).join(", ")} et ${f[f.length-1]??""}`}aa(()=>{const f=t.health.config_fingerprint;f!==oe&&(oe=f,S())});async function Ae(){const f=await s().load(Ar);if(f===null){v(G,"unreadable");return}v($,f,!0),v(G,"read")}async function S(){try{v(V,(await qa()).config,!0),v(se,"")}catch(f){v(V,null),v(se,f instanceof Error?f.message:"Le poste n’a pas répondu.",!0)}}async function z(f){v(j,f?"export-all":"export-clone",!0),s().notice="",s().actionError="";try{const q=await s().protect(()=>g(f));if(q===null)return;J(q.name,q.blob),s().notice=`${q.name} est remis au navigateur : c’est lui qui l’enregistre, voyez ses téléchargements.`}finally{v(j,"")}}async function g(f){const L=await fetch(`/admin/api/config/export?hardware=${f?"1":"0"}`,{headers:{accept:"application/json"}});if(!L.ok)throw new It(L.status,he(await L.text(),"L’export"));return{name:O(f),blob:await L.blob()}}function O(f){const q=f?"":"-sans-materiel";return`config-poste${String(t.health.station)}${q}-${I()}.json`}function I(){const f=new Date,q=String(f.getMonth()+1).padStart(2,"0"),L=String(f.getDate()).padStart(2,"0");return`${String(f.getFullYear())}-${q}-${L}`}function J(f,q){const L=URL.createObjectURL(q),_e=document.createElement("a");_e.href=L,_e.download=f,document.body.appendChild(_e),_e.click(),_e.remove(),setTimeout(()=>{URL.revokeObjectURL(L)},0)}async function ce(f){const q=await fetch("/admin/api/config/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)}),L=await q.text();if(!q.ok)throw new It(q.status,he(L,"L’import"));return JSON.parse(L)}async function Ee(f){if(f==null)return;s().notice="",s().actionError="";let q;try{q=JSON.parse(await f.text())}catch{s().actionError=`${f.name} n’est pas un fichier JSON lisible.`;return}v(j,"import");try{const L=await s().protect(()=>ce(q));if(L===null)return;v(be,L.config,!0),v(ye,f.name,!0),v(ie,L.faults,!0),await S(),s().notice=x(f.name)}finally{v(j,"")}}function x(f){return e(me)?e(we).length===0?`${f} décrit la même configuration que celle en service.`:`${f} est lu. Rien n’est appliqué : relisez le tableau.`:`${f} est lu, mais la configuration en service ne l’est pas : rien ne peut être comparé.`}async function M(){if(e(be)===null)return;const f=e(be),q=[...e(we)];for(const L of q)t.draft.set(L.path,va(f,L.path));s().actionError="",s().notice="Le fichier est recopié dans le brouillon. Rien n’est appliqué avant « Enregistrer ».",await S()}async function re(f){v(j,`restore-${String(f)}`),s().notice="",s().actionError="";try{if(await s().protect(()=>Nr(f))===null)return;await t.draft.load(),await Ae(),await S(),await s().refresh(),s().notice=`La version ${String(f)} est remise en service.`}finally{v(j,"")}}function he(f,q){try{const L=JSON.parse(f);if(typeof L?.message=="string"&&L.message!=="")return L.message}catch{}return`${q} a été refusé par le poste.`}function de(f){return t.draft.faults.find(q=>q.field===f)?.message??""}function He(f){return t.draft.faults.find(q=>q.field===f)?.allowed??[]}function Xe(f,q,L,_e){const ne=q>1?_e:L;return f>=q?`${ge(q)} ${ne}.`:`${ge(f)} lignes affichées sur ${ge(q)} ${ne}.`}var xe=Mi(),d=a(xe);Qe(d,{title:"Identité du poste",children:(f,q)=>{var L=Si(),_e=K(L);{let ze=o(()=>t.draft.text("station.number")),Re=o(()=>de("station.number")),nt=o(()=>He("station.number"));_t(_e,{label:"Numéro du poste",path:"station.number",kind:"number",get value(){return e(ze)},get fault(){return e(Re)},get allowed(){return e(nt)},hint:"C’est de lui que dérive le nom du fichier de catalogue attendu, flv_.csv.",onchange:ut=>t.draft.set("station.number",Number(ut))})}var ne=r(_e,2);{let ze=o(()=>t.draft.text("station.name")),Re=o(()=>de("station.name")),nt=o(()=>He("station.name"));_t(ne,{label:"Nom du poste",path:"station.name",get value(){return e(ze)},get fault(){return e(Re)},get allowed(){return e(nt)},hint:"Ce que lit un bénévole : « Poste 2 — fruits ».",onchange:ut=>t.draft.set("station.name",ut)})}var qe=r(ne,2);{let ze=o(()=>t.draft.text("station.coop")),Re=o(()=>de("station.coop")),nt=o(()=>He("station.coop"));_t(qe,{label:"Coopérative",path:"station.coop",get value(){return e(ze)},get fault(){return e(Re)},get allowed(){return e(nt)},onchange:ut=>t.draft.set("station.coop",ut)})}var Se=r(qe,2),ue=r(a(Se),2),W=a(ue),Ue=r(ue,4),Ve=a(Ue),Le=r(Ue,4),Te=a(Le),Je=r(Le,4),ae=a(Je);{var Ie=ze=>{var Re=yt("non mesuré");l(ze,Re)},ve=ze=>{var Re=yt();p((nt,ut,pt)=>i(Re,`${nt??""} libres sur + ${ut??""} — seuil d’alerte + ${pt??""} Mo`),[()=>wa(t.health.disk.free_bytes),()=>wa(t.health.disk.total_bytes),()=>ge(t.health.disk.alert_mb)]),l(ze,Re)};m(ae,ze=>{t.health.disk===null?ze(Ie):ze(ve,-1)})}p(()=>{i(W,t.health.config_fingerprint),i(Ve,t.health.version),i(Te,t.health.disk===null?"non publié par ce poste":t.health.disk.path)}),l(f,L)},$$slots:{default:!0}});var w=r(d,2);Qe(w,{title:"Exporter, importer",note:"Pour installer un autre poste : ce fichier emporte les tarifs, les garde-fous, l’étiquette, les catégories, et les réglages du matériel que les quatre postes partagent — le décalage d’étiquette, le noircissement, la vitesse, le débit de la balance. Reste ici ce qui désigne ce poste-ci ou ce magasin — le mot de passe, le code de secours, le numéro et le nom du poste, le port de la balance, la file d’impression, l’adresse du partage et son compte, le chemin des images et le réseau.",children:(f,q)=>{var L=Ni(),_e=K(L),ne=a(_e);{let ae=o(()=>e(j)==="export-all");at(ne,{label:"Exporter tout",protected:!0,get busy(){return e(ae)},get disabled(){return e(X)},onrun:()=>{z(!0)}})}var qe=r(ne,2);{let ae=o(()=>e(j)==="export-clone");at(qe,{label:"Exporter sans le matériel",protected:!0,get busy(){return e(ae)},get disabled(){return e(X)},onrun:()=>{z(!1)}})}var Se=r(qe,2);let ue;var W=a(Se),Ue=r(W,3),Ve=r(_e,4);{var Le=ae=>{var Ie=Li(),ve=a(Ie);p(()=>i(ve,`La configuration en service n’a pas pu être lue : ${e(se)??""} La colonne + « En service » ne peut donc rien affirmer.`)),l(ae,Ie)};m(Ve,ae=>{e(se)!==""&&ae(Le)})}var Te=r(Ve,2);{var Je=ae=>{var Ie=Ai(),ve=K(Ie),ze=a(ve),Re=r(ve,2);{var nt=Ze=>{var bt=ji(),Ot=a(bt),Ut=a(Ot),k=r(Ot,2);rt(k,21,()=>e(F),fa,(Q,le)=>{var Ne=zi(),We=a(Ne),Ye=a(We),Ke=r(We,2);{var B=ke=>{var De=Ia(),Pe=a(De);p(()=>i(Pe,e(le).field)),l(ke,De)};m(Ke,ke=>{Gt.showTechnicalNames&&ke(B)})}var ee=r(Ke),fe=r(ee);{var $e=ke=>{var De=Ei(),Pe=a(De);p(Fe=>i(Pe,`Valeurs acceptées : ${Fe??""}.`),[()=>e(le).allowed.join(", ")]),l(ke,De)};m(fe,ke=>{e(le).allowed!==void 0&&e(le).allowed.length>0&&ke($e)})}p(ke=>{i(Ye,ke),i(ee,` ${e(le).message??""} `)},[()=>Ft(e(le).field)]),l(Q,Ne)}),p(()=>i(Ut,`Ce fichier serait refusé en l’état : ${e(T)??""}`)),l(Ze,bt)};m(Re,Ze=>{e(ie).length>0&&Ze(nt)})}var ut=r(Re,2);{var pt=Ze=>{var bt=Pi();l(Ze,bt)},je=Ze=>{var bt=Ti();l(Ze,bt)},st=Ze=>{var bt=Oi(),Ot=K(bt),Ut=a(Ot),k=r(Ut);{var Q=fe=>{var $e=yt("Les autres sont dans le fichier, et « Recopier » les prend tous.");l(fe,$e)};m(k,fe=>{e(we).length>e(H).length&&fe(Q)})}var le=r(Ot,2),Ne=a(le),We=r(a(Ne));rt(We,21,()=>e(H),fe=>fe.path,(fe,$e)=>{const ke=o(()=>Ft(e($e).path));var De=Ci(),Pe=a(De),Fe=a(Pe),Ge=r(Fe);{var ft=gt=>{var xt=Ia(),Nt=a(xt);p(()=>i(Nt,e($e).path)),l(gt,xt)};m(Ge,gt=>{Gt.showTechnicalNames&&e(ke)!==e($e).path&>(ft)})}var ot=r(Pe),ct=a(ot),it=r(ot),dt=a(it);p(()=>{Be(De,"data-path",e($e).path),i(Fe,`${e(ke)??""} `),i(ct,e($e).before),i(dt,e($e).after)}),l(fe,De)});var Ye=r(le,2);{var Ke=fe=>{var $e=Ri(),ke=a($e);p(De=>i(ke,`Ce fichier ne porte pas ${De??""} : + l’export sans le matériel les retire, et l’import ne remet que le numéro du + poste. Les lignes correspondantes sont VIDES ci-dessus, et + « Recopier » recopie ce vide dans le brouillon.`),[()=>Oe(e(E).map(De=>De.name))]),l(fe,$e)};m(Ye,fe=>{e(E).length>0&&fe(Ke)})}var B=r(Ye,2),ee=a(B);at(ee,{kind:"write",get label(){return e(C)},get disabled(){return e(X)},onrun:()=>{M()}}),p(()=>i(Ut,`${e(te)??""} `)),l(Ze,bt)};m(ut,Ze=>{e(me)?e(we).length===0?Ze(je,1):Ze(st,-1):Ze(pt)})}p(()=>i(ze,`Fichier lu : ${e(ye)??""}`)),l(ae,Ie)};m(Te,ae=>{e(be)!==null&&ae(Je)})}p(()=>{ue=Ht(Se,1,"choose svelte-18wbxwu",null,ue,{working:e(j)==="import",off:e(X)}),i(W,`${e(j)==="import"?"Lecture du fichier…":"Importer un fichier"} `),Ue.disabled=e(X)}),et("change",Ue,ae=>{Ee(ae.currentTarget.files?.item(0))}),l(f,L)},$$slots:{default:!0}});var Y=r(w,2);Qe(Y,{title:"Cinq versions restaurables",note:"Chaque enregistrement fait tourner les versions : la plus récente est la 1.",children:(f,q)=>{var L=Dt(),_e=K(L);{var ne=W=>{var Ue=$i();l(W,Ue)},qe=W=>{var Ue=Di();l(W,Ue)},Se=W=>{var Ue=Ii();l(W,Ue)},ue=W=>{var Ue=Fi(),Ve=K(Ue),Le=a(Ve),Te=r(Ve,2),Je=a(Te);rt(Je,21,()=>e(P),ae=>ae.version,(ae,Ie)=>{var ve=Ui(),ze=a(ve),Re=a(ze),nt=r(ze,2),ut=a(nt),pt=r(nt,2),je=a(pt),st=r(pt,2);{let Ze=o(()=>e(j)===`restore-${String(e(Ie).version)}`);at(st,{kind:"destructive",label:"Remettre cette version en service",protected:!0,get busy(){return e(Ze)},get disabled(){return e(X)},onrun:()=>{re(e(Ie).version)}})}p((Ze,bt)=>{i(Re,`version ${Ze??""}`),i(ut,bt),i(je,e(Ie).config_fingerprint)},[()=>ge(e(Ie).version),()=>Mt(e(Ie).modified_at)]),l(ae,ve)}),p(()=>i(Le,`${e(y)??""} Le poste n’en garde jamais plus de cinq.`)),l(W,Ue)};m(_e,W=>{e(G)==="reading"?W(ne):e(G)==="unreadable"?W(qe,1):e($).length===0?W(Se,2):W(ue,-1)})}l(f,L)},$$slots:{default:!0}}),l(n,xe),Rt()}Tt(["change"]);var Vi=u('clé'),Wi=u(' '),Gi=u('');function Xt(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"hint",3,""),_=vt(t,"disabled",3,!1),b=vt(t,"engaged",3,!1),R=vt(t,"busy",3,!1),N=vt(t,"protected",3,!1);var $=Gi();let G;var V=a($),se=a(V),be=r(se);{var ye=oe=>{var X=Vi();l(oe,X)};m(be,oe=>{N()&&oe(ye)})}var ie=r(V,2);{var j=oe=>{var X=Wi(),we=a(X);p(()=>i(we,R()?"En cours…":c())),l(oe,X)};m(ie,oe=>{c()!==""&&oe(j)})}p(()=>{G=Ht($,1,`big touch-target ${s()??""}`,"svelte-15nlt6i",G,{engaged:b(),busy:R()}),Be($,"data-kind",s()),$.disabled=_()||R(),i(se,`${t.label??""} `)}),et("click",$,function(...oe){t.onrun?.apply(this,oe)}),l(n,$)}Tt(["click"]);const Hi="ERR-SCL-09";var Ji=u('

                                                                                                                                                                  '),Ki=u('

                                                                                                                                                                  '),Xi=u('

                                                                                                                                                                  Déposez ici le fichier clé

                                                                                                                                                                  '),Yi=u('

                                                                                                                                                                  ',1),Qi=u('');function Zi(n,t){Ct(t,!0);const s=vt(t,"admin",7);let c=U("");const _=o(()=>t.health.state.degraded?.code===Hi),b=o(()=>t.health.printing),R=o(()=>e(b)?.on_fallback===!0);let N=U(!1),$=U("");const G=o(()=>s().busy||e($)!=="");async function V(S,z){v(c,""),v($,S,!0);try{await s().run(z)}finally{v($,"")}}async function se(S,z){v(c,""),v($,S,!0);try{const g=await s().load(z);g!==null&&v(c,g.message,!0)}finally{v($,"")}}async function be(S,z){v(c,""),v($,S,!0);try{const g=await s().protect(z);g!==null&&(s().notice=g.message,await s().refresh())}finally{v($,"")}}async function ye(S){if(v(N,!1),S!=null){v(c,""),v($,"import");try{const z=await s().protect(()=>Ga(S));if(z===null)return;v(c,z.result==="rejected"||z.result==="failed"?`${S.name} : REFUSÉ${z.reason===""?"":" — "+z.reason}. Le catalogue en service n’a pas changé.`:`${S.name} : ${String(z.rows_read_count)} lignes lues, ${String(z.weighable_count)} pesables. La veille l’appliquera dans la seconde.`,!0),await s().refresh()}finally{v($,"")}}}var ie=Qi(),j=a(ie);{var oe=S=>{var z=Ji(),g=a(z);p(()=>i(g,e(c))),l(S,z)};m(j,S=>{e(c)!==""&&S(oe)})}var X=r(j,2);{var we=S=>{var z=Ki(),g=a(z);p(()=>i(g,e(b).banner)),l(S,z)};m(X,S=>{e(b)!==null&&e(b).banner!==""&&S(we)})}var me=r(X,2),E=a(me);{let S=o(()=>e($)==="scale");Xt(E,{label:"Tester la balance",hint:"Ce que le poste a déjà observé — le port n’est pas rouvert.",get busy(){return e(S)},get disabled(){return e(G)},onrun:()=>{se("scale",Lr)}})}var H=r(E,2);{let S=o(()=>e($)==="printer");Xt(H,{label:"Tester l’imprimante",hint:"Ce que le superviseur a vu il y a moins d’une seconde.",get busy(){return e(S)},get disabled(){return e(G)},onrun:()=>{se("printer",Er)}})}var F=r(H,2);{let S=o(()=>e($)==="label");Xt(F,{label:"Imprimer une étiquette de test",hint:"Une étiquette de démonstration sort de l’imprimante du poste.",get busy(){return e(S)},get disabled(){return e(G)},onrun:()=>{V("label",zr)}})}var P=r(F,2);{let S=o(()=>e($)==="reprint");Xt(P,{label:"Réimprimer la dernière",hint:"La dernière étiquette imprimée sort une seconde fois.",get busy(){return e(S)},get disabled(){return e(G)},onrun:()=>{V("reprint",yr)}})}var te=r(P,2);{let S=o(()=>e($)==="reload");Xt(te,{label:"Recharger le catalogue",kind:"write",hint:"La veille refait tout de suite le contrôle qu’elle fait toutes les cinq secondes.",get busy(){return e(S)},get disabled(){return e(G)},onrun:()=>{V("reload",xr)}})}var T=r(te,2);{let S=o(()=>e(_)?"Revenir à la balance":"Basculer en saisie manuelle"),z=o(()=>e(_)?"Le poids sera de nouveau lu sur la balance.":"Le poids se tape à la main : le poste continue de servir sans balance."),g=o(()=>e($)==="manual");Xt(T,{get label(){return e(S)},kind:"destructive",get hint(){return e(z)},get engaged(){return e(_)},protected:!0,get busy(){return e(g)},get disabled(){return e(G)},onrun:()=>{be("manual",()=>kr(!e(_)))}})}var y=r(T,2);{let S=o(()=>e($)==="roll");Xt(y,{label:"J’ai changé le rouleau",kind:"write",hint:"Le compteur d’étiquettes repart à zéro. C’est le seul geste qui dise quelque chose de vrai du papier.",get busy(){return e(S)},get disabled(){return e(G)},onrun:()=>{V("roll",qr)}})}var C=r(y,2);{var D=S=>{{let z=o(()=>e(R)?"Revenir à l’imprimante du poste":"Imprimer sur l’imprimante du poste voisin"),g=o(()=>e(R)?"Les étiquettes repartiront sur l’imprimante de ce poste.":"Les étiquettes sortiront sur l’imprimante voisine, pour cette session seulement."),O=o(()=>e($)==="fallback");Xt(S,{get label(){return e(z)},kind:"write",get hint(){return e(g)},get engaged(){return e(R)},get busy(){return e(O)},get disabled(){return e(G)},onrun:()=>{V("fallback",()=>Sr(!e(R)))}})}};m(C,S=>{e(b)!==null&&e(b).fallback_available&&S(D)})}var Z=r(C,2),pe=r(me,2);Qe(pe,{title:"Importer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(S,z)=>{var g=Xi();let O;var I=a(g),J=r(a(I)),ce=a(J),Ee=r(I,2),x=r(a(Ee));p(()=>{O=Ht(g,1,"drop svelte-1yd0nzg",null,O,{dropping:e(N)}),i(ce,`flv_${t.health.station??""}.csv`)}),Qt("dragover",g,M=>{M.preventDefault(),v(N,!0)}),Qt("dragleave",g,()=>v(N,!1)),Qt("drop",g,M=>{M.preventDefault(),ye(M.dataTransfer?.files.item(0))}),et("change",x,M=>{ye(M.currentTarget.files?.item(0))}),l(S,g)},$$slots:{default:!0}});var Oe=r(pe,2);{var Ae=S=>{Qe(S,{title:"Le dernier fichier a été refusé",children:(z,g)=>{var O=Yi(),I=K(O),J=a(I),ce=r(I,2),Ee=a(ce);p(x=>{i(J,`${(t.health.catalog.reason===""?"Aucun motif enregistré.":t.health.catalog.reason)??""} + Le catalogue en service n’a pas changé.`),i(Ee,`Dernier essai : ${x??""}.`)},[()=>Mt(t.health.catalog.occurred_at)]),l(z,O)},$$slots:{default:!0}})};m(Oe,S=>{t.health.catalog!==null&&t.health.catalog.result==="rejected"&&S(Ae)})}p(()=>Be(Z,"href",jr)),l(n,ie),Rt()}Tt(["change"]);const eo={succeeded:"La dernière mise à jour a réussi.","rolled-back":"La dernière mise à jour a échoué. La version précédente a été remise et le poste fonctionne.","rolled-back-unhealthy":"La dernière mise à jour a échoué et le poste n’a pas redémarré. Appelez le support.","not-started":"La dernière mise à jour n’a pas démarré : rien n’a été remplacé. Vous pouvez réessayer."},to=2e3,ao=300*1e3;var ro=u('

                                                                                                                                                                  '),Ua=u('

                                                                                                                                                                  '),no=u("
                                                                                                                                                                  Dernière vérification
                                                                                                                                                                  ",1),so=u(`

                                                                                                                                                                  La mise à jour depuis cet écran n’existe que sur les postes Windows. Sur les + autres, elle se fait à la main — voir la notice d’installation.

                                                                                                                                                                  `),lo=u('Voir les nouveautés'),io=u('

                                                                                                                                                                  Version disponible : .

                                                                                                                                                                  '),oo=u('

                                                                                                                                                                  Ce poste est à jour.

                                                                                                                                                                  '),uo=u("
                                                                                                                                                                  Version installée
                                                                                                                                                                  Dépôt suivi
                                                                                                                                                                  ",1),co=u(`

                                                                                                                                                                  Le poste va s’arrêter environ une minute. L’écran client s’éteindra puis reviendra + tout seul. Si la nouvelle version ne démarre pas, la précédente sera remise + automatiquement — mais les données enregistrées, elles, ne reviendront pas en + arrière.

                                                                                                                                                                  `),vo=u('

                                                                                                                                                                  Mise à jour en cours. Le poste redémarre, ne le débranchez pas.

                                                                                                                                                                  '),po=u('
                                                                                                                                                                  ',1),fo=u("
                                                                                                                                                                  Terminée le
                                                                                                                                                                  ",1),go=u("
                                                                                                                                                                  Raison
                                                                                                                                                                  ",1),mo=u('

                                                                                                                                                                  Depuis
                                                                                                                                                                  Vers
                                                                                                                                                                  ',1),ho=u(" ",1);function _o(n,t){Ct(t,!0);let s=U(null),c=U(""),_=U(""),b=U(!1),R=U("");const N=o(()=>e(s)?.outcome??null),$=o(()=>e(s)?.supported===!0&&e(s).available),G=o(()=>e(s)?.latest??"");aa(()=>{V()});async function V(){try{v(s,await Yr(),!0),v(c,"")}catch(E){v(c,E instanceof Error?E.message:"Lecture impossible.",!0)}}async function se(){v(_,"check");try{const E=await t.admin.protect(()=>Qr());E!==null&&(v(s,E,!0),v(c,""))}finally{v(_,"")}}async function be(E){v(_,"apply"),v(R,"");try{if(await t.admin.protect(()=>Zr(E))===null)return;v(b,!0),v(R,await ye()?"":"Le poste n’a pas répondu dans les cinq minutes. Allez le voir.",!0),await V()}finally{v(_,""),v(b,!1)}}async function ye(){const E=Date.now()+ao;for(;Date.now()setTimeout(H,to));try{if((await fetch("/healthz",{cache:"no-store"})).ok)return!0}catch{}}return!1}var ie=ho(),j=K(ie);Qe(j,{title:"Version de ce poste",children:(E,H)=>{var F=Dt(),P=K(F);{var te=y=>{var C=ro(),D=a(C);p(()=>i(D,e(c)===""?"Lecture…":e(c))),l(y,C)},T=y=>{var C=uo(),D=K(C);{var Z=x=>{var M=Ua(),re=a(M);p(()=>i(re,e(c))),l(x,M)};m(D,x=>{e(c)!==""&&x(Z)})}var pe=r(D,2),Oe=r(a(pe),2),Ae=a(Oe),S=r(Oe,4),z=a(S),g=r(S,2);{var O=x=>{var M=no(),re=r(K(M),2),he=a(re);p(de=>i(he,de),[()=>Mt(e(s).checked_at)]),l(x,M)};m(g,x=>{e(s).checked_at!==""&&x(O)})}var I=r(pe,2);{var J=x=>{var M=so();l(x,M)},ce=x=>{var M=io(),re=r(a(M)),he=a(re),de=r(re,2);{var He=d=>{var w=yt();p(Y=>i(w,`, publiée le ${Y??""}`),[()=>Mt(e(s).published_at)]),l(d,w)};m(de,d=>{e(s).published_at!==""&&d(He)})}var Xe=r(de,2);{var xe=d=>{var w=lo();p(()=>Be(w,"href",e(s).html_url)),l(d,w)};m(Xe,d=>{e(s).html_url!==""&&d(xe)})}p(()=>i(he,e(s).latest)),l(x,M)},Ee=x=>{var M=oo();l(x,M)};m(I,x=>{e(s).supported?e(s).available?x(ce,1):x(Ee,-1):x(J)})}p(()=>{i(Ae,e(s).running),i(z,e(s).repository)}),l(y,C)};m(P,y=>{e(s)===null?y(te):y(T,-1)})}l(E,F)},$$slots:{default:!0}});var oe=r(j,2);{var X=E=>{Qe(E,{title:"Installer",children:(H,F)=>{var P=po(),te=K(P),T=a(te);{let z=o(()=>e(_)==="check");at(T,{kind:"read",label:"Vérifier maintenant",protected:!0,act:"check",get busy(){return e(z)},get disabled(){return e(b)},onrun:()=>{se()}})}var y=r(T,2);{var C=z=>{{let g=o(()=>`Installer la version ${e(G)}`),O=o(()=>e(_)==="apply");at(z,{kind:"destructive",get label(){return e(g)},protected:!0,act:"apply",get busy(){return e(O)},onrun:()=>{be(e(G))}})}};m(y,z=>{e($)&&z(C)})}var D=r(te,2);{var Z=z=>{var g=co();l(z,g)};m(D,z=>{e($)&&z(Z)})}var pe=r(D,2);{var Oe=z=>{var g=vo();l(z,g)};m(pe,z=>{e(b)&&z(Oe)})}var Ae=r(pe,2);{var S=z=>{var g=Ua(),O=a(g);p(()=>i(O,e(R))),l(z,g)};m(Ae,z=>{e(R)!==""&&z(S)})}l(H,P)},$$slots:{default:!0}})};m(oe,E=>{e(s)!==null&&e(s).supported&&E(X)})}var we=r(oe,2);{var me=E=>{Qe(E,{title:"Dernière tentative",children:(H,F)=>{var P=mo(),te=K(P),T=a(te),y=r(te,2),C=r(a(y),2),D=a(C),Z=r(C,4),pe=a(Z),Oe=r(Z,2);{var Ae=g=>{var O=fo(),I=r(K(O),2),J=a(I);p(ce=>i(J,ce),[()=>Mt(e(N).finished_at)]),l(g,O)};m(Oe,g=>{e(N).finished_at!==""&&g(Ae)})}var S=r(Oe,2);{var z=g=>{var O=go(),I=r(K(O),2),J=a(I);p(()=>i(J,e(N).reason)),l(g,O)};m(S,g=>{e(N).reason!==""&&g(z)})}p(()=>{Be(te,"data-outcome",e(N).status),i(T,eo[e(N).status]),i(D,e(N).from===""?"—":e(N).from),i(pe,e(N).to===""?"—":e(N).to)}),l(H,P)},$$slots:{default:!0}})};m(we,E=>{e(N)!==null&&E(me)})}l(n,ie),Rt()}var bo=u(''),wo=u('

                                                                                                                                                                  ',1),yo=u('

                                                                                                                                                                  ',1),xo=u(''),ko=u(''),qo=u(''),So=u(''),Lo=u(' '),Eo=u(''),zo=u('

                                                                                                                                                                  Lecture de l’état du poste…

                                                                                                                                                                  '),jo=u('

                                                                                                                                                                  '),Po=u(" "),To=u("
                                                                                                                                                                • "),Co=u('
                                                                                                                                                                    '),Ro=u('
                                                                                                                                                                    '),Oo=u('

                                                                                                                                                                    ');function Ao(n,t){Ct(t,!0);const s=new pn,c=new en(s),_=[{title:"Au quotidien",pages:[{id:"dashboard",label:"Tableau de bord"},{id:"troubleshooting",label:"Dépannage"}]},{title:"Réglages",pages:[{id:"hardware",label:"Matériel"},{id:"label",label:"Étiquette"},{id:"rules",label:"Règles"},{id:"catalog",label:"Catalogue"},{id:"journal",label:"Journal"},{id:"station",label:"Poste"},{id:"update",label:"Mise à jour"}]}],b=o(()=>_.flatMap(d=>d.pages).find(d=>d.id===s.page)?.label??""),R=o(()=>s.health===null||s.health.station_name===""?`Poste ${String(s.health?.station??"")}`:s.health.station_name),N=o(()=>(c.pending?.changed_blocks??[]).map(d=>nn(d)).join(", ")),$=o(()=>(c.pending?.changed_blocks??[]).join(", "));ka(()=>(s.start(),()=>s.stop()));async function G(d){s.open(d),Ca(d)&&c.config===null&&await c.load()}async function V(){await s.protect(()=>c.save())===!0&&(s.notice="La configuration est enregistrée et appliquée.")}var se=Oo(),be=a(se),ye=r(a(be),2);rt(ye,17,()=>_,d=>d.title,(d,w)=>{var Y=wo(),f=K(Y),q=a(f),L=r(f,2);rt(L,17,()=>e(w).pages,_e=>_e.id,(_e,ne)=>{var qe=bo();let Se;var ue=a(qe);p(()=>{Se=Ht(qe,1,"entry svelte-9b2mjq",null,Se,{current:s.page===e(ne).id}),Be(qe,"aria-current",s.page===e(ne).id?"page":void 0),i(ue,e(ne).label)}),et("click",qe,()=>{G(e(ne).id)}),l(_e,qe)}),p(()=>i(q,e(w).title)),l(d,Y)});var ie=r(ye,2),j=a(ie);{var oe=d=>{var w=yo(),Y=K(w),f=a(Y),q=r(Y,2),L=a(q),_e=r(q,2),ne=a(_e);p(()=>{i(f,e(R)),i(L,`${s.health.coop??""} · version ${s.health.version??""}`),i(ne,`configuration ${s.health.config_fingerprint??""}`)}),l(d,w)};m(j,d=>{s.health!==null&&d(oe)})}var X=r(j,2),we=a(X),me=r(X,2);{var E=d=>{var w=xo();et("click",w,function(...Y){t.onclose?.apply(this,Y)}),l(d,w)};m(me,d=>{t.onclose!==void 0&&d(E)})}var H=r(be,2),F=a(H);{var P=d=>{var w=ko(),Y=a(w);p(()=>i(Y,s.notice)),l(d,w)};m(F,d=>{s.notice!==""&&d(P)})}var te=r(F,2);{var T=d=>{var w=qo(),Y=a(w);p(()=>i(Y,s.linkError)),l(d,w)};m(te,d=>{s.linkError!==""&&d(T)})}var y=r(te,2);{var C=d=>{var w=So(),Y=a(w);p(()=>i(Y,s.actionError)),l(d,w)};m(y,d=>{s.actionError!==""&&d(C)})}var D=r(y,2);{var Z=d=>{var w=Eo(),Y=a(w),f=a(Y),q=r(f);{var L=qe=>{var Se=Lo(),ue=a(Se);p(()=>i(ue,e($))),l(qe,Se)};m(q,qe=>{Gt.showTechnicalNames&&qe(L)})}var _e=r(q),ne=r(Y,2);at(ne,{kind:"write",label:"Tout fonctionne : confirmer",onrun:()=>{s.protect(()=>c.confirm())}}),p(()=>{i(f,`Configuration appliquée mais NON CONFIRMÉE. Ce qui a changé : ${e(N)??""}. `),i(_e,` Le poste reviendra tout seul à la version précédente dans + ${c.pending.seconds_left??""} secondes si personne ne confirme.`)}),l(d,w)};m(D,d=>{c.pending!==null&&d(Z)})}var pe=r(D,2),Oe=a(pe),Ae=a(Oe),S=r(Oe,2);{var z=d=>{var w=zo();l(d,w)},g=d=>{ol(d,{get health(){return s.health},onshowrows:()=>{G("catalog")},onshowupdate:()=>{G("update")}})},O=d=>{Zi(d,{get admin(){return s},get health(){return s.health}})},I=d=>{wl(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},J=d=>{Jl(d,{get admin(){return s},get draft(){return c}})},ce=d=>{ki(d,{get draft(){return c},get health(){return s.health}})},Ee=d=>{ys(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},x=d=>{Il(d,{get admin(){return s}})},M=d=>{Bi(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},re=d=>{_o(d,{get admin(){return s}})};m(S,d=>{s.health===null?d(z):s.page==="dashboard"?d(g,1):s.page==="troubleshooting"?d(O,2):s.page==="hardware"?d(I,3):s.page==="label"?d(J,4):s.page==="rules"?d(ce,5):s.page==="catalog"?d(Ee,6):s.page==="journal"?d(x,7):s.page==="station"?d(M,8):s.page==="update"&&d(re,9)})}var he=r(pe,2);{var de=d=>{var w=Ro(),Y=a(w);{var f=ne=>{var qe=jo(),Se=a(qe),ue=r(Se);rt(ue,16,()=>c.retired,W=>W,(W,Ue)=>{{let Ve=o(()=>`retirer ${Ue}`);at(W,{kind:"write",get label(){return e(Ve)},onrun:()=>c.dropRetired(Ue)})}}),p(W=>i(Se,`Ce fichier porte des réglages que cette version du poste ne connaît plus : + ${W??""}. `),[()=>c.retired.map(W=>Ft(W)).join(", ")]),l(ne,qe)};m(Y,ne=>{c.retired.length>0&&ne(f)})}var q=r(Y,2);{var L=ne=>{var qe=Co();rt(qe,21,()=>c.faults,Se=>Se.field,(Se,ue)=>{var W=To(),Ue=a(W),Ve=a(Ue),Le=r(Ue,2);{var Te=ve=>{var ze=Po(),Re=a(ze);p(()=>i(Re,e(ue).field)),l(ve,ze)};m(Le,ve=>{Gt.showTechnicalNames&&ve(Te)})}var Je=r(Le),ae=r(Je);{var Ie=ve=>{var ze=yt();p(Re=>i(ze,`— valeurs acceptées : ${Re??""}`),[()=>e(ue).allowed.join(", ")]),l(ve,ze)};m(ae,ve=>{e(ue).allowed!==void 0&&e(ue).allowed.length>0&&ve(Ie)})}p(ve=>{i(Ve,ve),i(Je,` ${e(ue).message??""} `)},[()=>Ft(e(ue).field)]),l(Se,W)}),l(ne,qe)};m(q,ne=>{c.faults.length>0&&ne(L)})}var _e=r(q,2);{let ne=o(()=>c.dirty?"Enregistrer la configuration":"Aucune modification à enregistrer"),qe=o(()=>!c.dirty||s.busy);at(_e,{kind:"write",get label(){return e(ne)},get disabled(){return e(qe)},onrun:()=>{V()}})}l(d,w)},He=o(()=>Ca(s.page)&&c.config!==null);m(he,d=>{e(He)&&d(de)})}var Xe=r(H,2);{var xe=d=>{wn(d,{get admin(){return s}})};m(Xe,d=>{s.pending!==null&&d(xe)})}p(()=>{ua(we,Gt.showTechnicalNames),i(Ae,e(b))}),et("change",we,()=>Gt.toggleTechnicalNames()),l(n,se),Rt()}Tt(["click","change"]);let oa=null;function Do(n){if(oa!==null||n.querySelector("[data-admin]")!==null)return;const t=document.createElement("div");n.appendChild(t),oa={component:fr(Ao,{target:t,props:{onclose:No}}),host:t}}function No(){if(oa===null)return;const{component:n,host:t}=oa;oa=null,gr(n),t.remove()}export{No as closeAdmin,Do as mountAdmin}; diff --git a/internal/web/dist/assets/mount-D4FDGy22.js b/internal/web/dist/assets/mount-D4FDGy22.js deleted file mode 100644 index 8463f6d..0000000 --- a/internal/web/dist/assets/mount-D4FDGy22.js +++ /dev/null @@ -1,93 +0,0 @@ -import{R as ar,T as rr,U as nr,V as xa,W as Yt,e as sr,X as lr,Y as ir,Z as or,_ as ur,u as pa,r as Fa,$ as cr,c as Ma,B as aa,A as F,k as e,z as v,F as Lt,d as Tt,b as vt,s as r,i as m,t as p,n as Ht,l as Be,o as i,f as et,g as l,v as a,j as o,p as Ct,I as dr,h as Rt,q as u,w as H,a0 as Ea,E as na,x as rt,G as Ba,a1 as ua,y as Qt,a2 as Dt,a3 as yt,a4 as sa,C as fa,K as vr,H as pr,P as fr,a5 as gr}from"./app-Dn-BjSsJ.js";function Va(n){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function mr(n,t,...s){var c=new nr(n);ar(()=>{const _=t()??null;c.ensure(_,_&&(w=>_(w,...s)))},rr)}function Wa(n,t,s=!1){if(n.multiple){if(t==null)return;if(!lr(t))return ir();for(var c of n.options)c.selected=t.includes(la(c));return}for(c of n.options){var _=la(c);if(or(_,t)){c.selected=!0;return}}(!s||t!==void 0)&&(n.selectedIndex=-1)}function hr(n){var t=new MutationObserver(()=>{"__value"in n&&Wa(n,n.__value)});t.observe(n,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["value"]}),ur(()=>{t.disconnect()})}function _r(n,t,s=t){var c=new WeakSet,_=!0;xa(n,"change",w=>{var C=w?"[selected]":":checked",O;if(n.multiple)O=[].map.call(n.querySelectorAll(C),la);else{var A=n.querySelector(C)??n.querySelector("option:not([disabled])");O=A&&la(A)}s(O),n.__value=O,Yt!==null&&c.add(Yt)}),sr(()=>{var w=t();if(n===document.activeElement){var C=Yt;if(c.has(C))return}if(Wa(n,w,_),_&&w===void 0){var O=n.querySelector(":checked");O!==null&&(w=la(O),s(w))}n.__value=w,_=!1}),hr(n)}function la(n){return"__value"in n?n.__value:n.value}function ia(n,t,s=t){var c=new WeakSet;xa(n,"input",async _=>{var w=_?n.defaultValue:n.value;if(w=ma(n)?ha(w):w,s(w),Yt!==null&&c.add(Yt),await cr(),w!==(w=t())){var C=n.selectionStart,O=n.selectionEnd,A=n.value.length;if(n.value=w??"",O!==null){var W=n.value.length;C===O&&O===A&&W>A?(n.selectionStart=W,n.selectionEnd=W):(n.selectionStart=C,n.selectionEnd=Math.min(O,W))}}}),pa(t)==null&&n.value&&(s(ma(n)?ha(n.value):n.value),Yt!==null&&c.add(Yt)),Fa(()=>{var _=t();if(n===document.activeElement){var w=Yt;if(c.has(w))return}ma(n)&&_===ha(n.value)||n.type==="date"&&!_&&!n.value||_!==n.value&&(n.value=_??"")})}function za(n,t,s=t){xa(n,"change",c=>{var _=c?n.defaultChecked:n.checked;s(_)}),pa(t)==null&&s(n.checked),Fa(()=>{var c=t();n.checked=!!c})}function ma(n){var t=n.type;return t==="number"||t==="range"}function ha(n){return n===""?null:+n}function ka(n){Ma===null&&Va(),aa(()=>{const t=pa(n);if(typeof t=="function")return t})}function br(n){Ma===null&&Va(),ka(()=>()=>pa(n))}class It extends Error{constructor(t,s,c="",_=[]){super(s),this.status=t,this.code=c,this.faults=_,this.name="AdminError"}status;code;faults;get needsPassword(){return this.status===401}}function wr(){return Jt("/admin/api/health")}function yr(){return ht("/admin/api/troubleshooting/reprint",{})}function xr(){return ht("/admin/api/troubleshooting/reload-catalog",{})}function kr(n){return ht("/admin/api/troubleshooting/manual-entry",{on:n})}function qr(){return ht("/admin/api/troubleshooting/roll-changed",{})}function Sr(n){return ht("/admin/api/troubleshooting/fallback-printer",{on:n})}function Lr(){return ht("/admin/api/troubleshooting/test-scale",{})}function Er(){return ht("/admin/api/troubleshooting/test-printer",{})}function zr(){return ht("/admin/api/troubleshooting/test-label",{})}async function Ga(n){const t=new FormData;t.append("file",n,n.name);const s=await fetch("/admin/api/catalog/import",{method:"POST",body:t});return Sa(s,"POST /admin/api/catalog/import")}const jr="/admin/api/diagnostic.zip";function Pr(n){return ht("/admin/api/session",{password:n})}async function Tr(){await fetch("/admin/api/session",{method:"DELETE"})}function Cr(n,t){return ht("/admin/api/session/recovery",{code:n,password:t})}function qa(){return Jt("/admin/api/config")}function Rr(n){return Ja("PUT","/admin/api/config",n)}function Or(){return ht("/admin/api/config/confirm",{})}async function Ar(){return(await Jt("/admin/api/config/versions")).versions}function Nr(n){return ht("/admin/api/config/restore",{version:n})}async function $r(){return(await Jt("/admin/api/ports")).ports}async function Dr(){return(await Jt("/admin/api/printers")).printers}async function Ir(){return(await ht("/admin/api/printers/discover",{})).printers}function Ur(n){return ht("/admin/api/scale/detect",{port:n})}async function ja(n,t){return(await ht("/admin/api/scale/capture",{port:n,seconds:t})).frames}function Ha(n){return ht("/admin/api/printer/test?what="+n,{})}function Fr(n,t,s,c){const _=new URLSearchParams({template:n,demo:t?"1":"0",dual:s?"1":"0"});return _.set("t",String(c)),"/admin/api/label/preview.png?"+_.toString()}async function Mr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/journal?"+t)).weighings}function Br(n){return"/admin/api/journal/export.csv?"+new URLSearchParams(n).toString()}async function Vr(n){const t=new URLSearchParams(n);return(await Jt("/admin/api/technical?"+t)).entries}function Wr(n){const t=n===void 0?"":"?id="+String(n);return Jt("/admin/api/imports"+t)}function Gr(){return ht("/admin/api/catalog/reload",{})}function Hr(){return ht("/admin/api/catalog/forget-quarantine",{})}function Pa(n,t){return ht("/admin/api/products/"+encodeURIComponent(n)+"/decision",t)}function Jr(n){return ht("/admin/api/replay",{frame:n})}async function Jt(n){const t=await fetch(n,{headers:{accept:"application/json"}});return Sa(t,"GET "+n)}function ht(n,t){return Ja("POST",n,t)}async function Ja(n,t,s){const c=await fetch(t,{method:n,headers:{"content-type":"application/json"},body:JSON.stringify(s)});return Sa(c,n+" "+t)}async function Sa(n,t){const s=await n.text();if(!n.ok){const c=Ka(s);throw new It(n.status,Xr(s,t,n.status)+Kr(n),c?.code??"",c?.faults??[])}if(s!=="")return JSON.parse(s)}function Kr(n){if(n.status!==429)return"";const t=Number(n.headers.get("Retry-After")??"");if(!Number.isFinite(t)||t<=0)return"";const s=Math.ceil(t/60);return s>1?` Réessayez dans ${String(s)} minutes.`:" Réessayez dans une minute."}function Xr(n,t,s){const c=Ka(n);return c?.message!==void 0&&c.message!==""?c.message:`${t} a répondu ${String(s)}.`}function Ka(n){try{return JSON.parse(n)}catch{return null}}function Yr(){return Jt("/admin/api/update")}function Qr(){return ht("/admin/api/update/check",{})}function Zr(n){return ht("/admin/api/update/apply",{version:n})}class en{constructor(t){this.admin=t}admin;#e=F(null);get config(){return e(this.#e)}set config(t){v(this.#e,t,!0)}#a=F("");get fingerprint(){return e(this.#a)}set fingerprint(t){v(this.#a,t,!0)}#r=F(Lt([]));get retired(){return e(this.#r)}set retired(t){v(this.#r,t,!0)}#n=F(null);get pending(){return e(this.#n)}set pending(t){v(this.#n,t,!0)}#s=F(Lt([]));get faults(){return e(this.#s)}set faults(t){v(this.#s,t,!0)}#l=F(!1);get dirty(){return e(this.#l)}set dirty(t){v(this.#l,t,!0)}async load(){const t=await this.admin.load(()=>qa());t!==null&&(this.config=t.config,this.fingerprint=t.config_fingerprint,this.retired=t.retired_keys??[],this.pending=t.pending_confirmation,this.faults=[],this.dirty=!1)}value(t){let s=this.config;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}text(t){const s=this.value(t);return s==null?"":String(s)}number(t){const s=this.value(t);return typeof s=="number"?s:Number(s??0)}flag(t){return this.value(t)===!0}set(t,s){if(this.config===null)return;const c=t.split("."),_=c.pop();if(_===void 0)return;let w=this.config;for(const C of c){const O=w[C];(O===null||typeof O!="object")&&(w[C]={}),w=w[C]}w[_]=s,this.dirty=!0}unset(t){const s=t.split("."),c=s.pop();if(c===void 0||this.config===null)return!1;let _=this.config;for(const w of s){if(_===null||typeof _!="object")return!1;_=_[w]}return _===null||typeof _!="object"?!1:(delete _[c],this.dirty=!0,!0)}async save(){if(this.config===null)return!1;this.faults=[];const t=this.config;let s;try{s=await Rr(t)}catch(c){if(Ta(c))throw c;return this.admin.report(c),this.faults=tn(this.admin),!1}return this.config=s.config,this.fingerprint=s.config_fingerprint,this.retired=s.retired_keys??[],this.pending=s.pending_confirmation,this.dirty=!1,!0}async confirm(){try{await Or()}catch(t){if(Ta(t))throw t;this.admin.report(t);return}this.admin.notice="La configuration est confirmée.",this.pending=null,await this.admin.refresh()}dropRetired(t){if(t.includes("[")){this.admin.actionError=`« ${t} » est logée dans un tableau : cet écran ne sait pas la retirer de là. Modifiez le fichier de configuration lui-même.`;return}this.unset(t)&&(this.retired=this.retired.filter(s=>s!==t))}}function tn(n){return n.lastFaults}function Ta(n){return n instanceof It&&(n.status===401||n.status===409)}const an={"station.number":"Numéro du poste","station.name":"Nom du poste","station.coop":"Nom de la coopérative","network.listen":"Adresse d’écoute","network.admin_on_lan":"Administration accessible depuis le réseau","ui.language":"Langue","ui.sound":"Son","ui.idle_timeout_s":"Retour à l’accueil après (secondes)","ui.reprint_window_s":"Réimpression possible pendant (secondes)","ui.show_grid_prices":"Afficher les prix sur les tuiles","scale.type":"Protocole de la balance","scale.present":"Ce poste a une balance","scale.manual_entry_allowed":"Saisie manuelle autorisée","scale.degrade_after_s":"Passer en mode dégradé après (secondes)","scale.options.port":"Port série","scale.options.baud":"Vitesse (bauds)","scale.options.bits":"Bits de données","scale.options.parity":"Parité","scale.options.stop":"Bits d’arrêt","scale.options.backoff_min_ms":"Attente minimale avant réessai (ms)","scale.options.backoff_max_ms":"Attente maximale avant réessai (ms)","printer.type":"Pilote d’impression","printer.template":"Gabarit d’étiquette","printer.options.transport":"Transport","printer.options.queue":"File d’impression","printer.options.path":"Fichier de périphérique","printer.options.address":"Adresse réseau","printer.options.darkness":"Noircissement","printer.options.speed":"Vitesse d’impression","printer.options.offset_x":"Décalage horizontal (dots)","printer.options.offset_y":"Décalage vertical (dots)","printer.options.invert_bits":"Inverser les points","printer.options.copies":"Exemplaires","printer.options.roll_capacity":"Étiquettes par rouleau","pricing.amount_rounding":"Arrondi du montant","pricing.unit_price_rounding":"Arrondi du prix au kilo","pricing.primary_code":"Tarif principal","pricing.reference_code":"Tarif de référence","barcode.verify_reference_check_digit":"Vérifier la clé de contrôle","limits.empty_max_g":"Plateau considéré vide en dessous de (g)","limits.basket_check_enabled":"Vérifier la présence du panier","limits.basket_min_g":"Poids du panier, borne basse (g)","limits.basket_max_g":"Poids du panier, borne haute (g)","limits.min_weight_g":"Poids minimum accepté","limits.max_weight_g":"Poids maximum accepté","limits.max_tare_g":"Tare maximale","limits.min_units":"Unités minimum","limits.max_units":"Unités maximum","limits.max_amount_cents":"Montant maximum (centimes)","stability.mode":"Exigence de stabilité","stability.min_duration_ms":"Durée de stabilité (ms)","stability.tolerance_g":"Tolérance de stabilité (g)","stability.timeout_ms":"Délai d’attente de stabilité (ms)","stability.on_timeout":"Au bout du délai","stability.min_latch_rate":"Taux d’accroche minimal","stability.latch_rate_window_ms":"Fenêtre de mesure du taux (ms)","stability.expiry_floor_ms":"Péremption, plancher (ms)","stability.expiry_ceiling_ms":"Péremption, plafond (ms)","stability.expiry_factor":"Péremption, facteur","catalog.type":"Où le poste va chercher le catalogue","catalog.options.directory":"Répertoire surveillé","catalog.options.url":"Adresse du serveur","catalog.options.username":"Compte","catalog.options.password":"Mot de passe","catalog.options.separator":"Séparateur du CSV","catalog.options.poll_interval_s":"Vérifier toutes les (secondes)","catalog.options.stable_polls":"Vérifications avant lecture","catalog.options.max_file_size_mb":"Taille maximale du fichier (Mo)","catalog.options.max_image_size_kb":"Taille maximale d’une image (Ko)","catalog.options.min_readable_ratio":"Part minimale de lignes lisibles","catalog.options.max_weighable_drop":"Baisse maximale des produits pesables","catalog.options.max_archives":"Archives conservées","catalog.options.archive_days":"Archives conservées (jours)","catalog.options.failures_before_reject":"Échecs avant mise en quarantaine","catalog.images.source":"Origine des photos","catalog.images.path":"Répertoire des photos","catalog.fallback_category":"Rayon par défaut","journal.max_rows":"Pesées conservées","journal.max_days":"Pesées conservées (jours)","journal.max_technical":"Événements techniques conservés","admin.session_minutes":"Durée d’une session (minutes)","admin.attempts_per_minute":"Tentatives par minute","maintenance.weekly_integrity_check":"Contrôle d’intégrité hebdomadaire","maintenance.disk_alert_mb":"Alerte disque en dessous de (Mo)"};function Ft(n){return an[n]??n}const rn={station:"l’identité du poste",network:"le réseau",ui:"l’écran client",scale:"la balance",printer:"l’imprimante",pricing:"les tarifs",barcode:"le code-barres",limits:"les garde-fous",stability:"la stabilité",catalog:"le catalogue",journal:"le journal",maintenance:"la maintenance"};function nn(n){return rn[n]??n}const sn={scale:"balance",printer:"imprimante",catalog:"catalogue",ui:"écran",config:"configuration",http:"réseau",system:"système"};function Xa(n){return sn[n]??"origine inconnue"}const ba="openscale.admin.preferences",Ya="technical";class ln{#e=F(Lt(on()));get showTechnicalNames(){return e(this.#e)}set showTechnicalNames(t){v(this.#e,t,!0)}toggleTechnicalNames(){this.showTechnicalNames=!this.showTechnicalNames,un(this.showTechnicalNames)}}function on(){try{return globalThis.localStorage?.getItem(ba)===Ya}catch{return!1}}function un(n){try{n?globalThis.localStorage?.setItem(ba,Ya):globalThis.localStorage?.removeItem(ba)}catch{}}const Gt=new ln,cn=3e3,dn=["hardware","label","rules","catalog","journal","station","update"];function Ca(n){return dn.includes(n)}function vn(n){return n instanceof It&&(n.status===401||n.status===409)}class pn{constructor(t=cn){this.periodMs=t}periodMs;#e=F(null);get health(){return e(this.#e)}set health(t){v(this.#e,t,!0)}#a=F("dashboard");get page(){return e(this.#a)}set page(t){v(this.#a,t,!0)}#r=F(!1);get expert(){return e(this.#r)}set expert(t){v(this.#r,t,!0)}#n=F("");get linkError(){return e(this.#n)}set linkError(t){v(this.#n,t,!0)}#s=F("");get actionError(){return e(this.#s)}set actionError(t){v(this.#s,t,!0)}#l=F(!1);get needsFirstPassword(){return e(this.#l)}set needsFirstPassword(t){v(this.#l,t,!0)}#d=F("");get notice(){return e(this.#d)}set notice(t){v(this.#d,t,!0)}#v=F(!1);get busy(){return e(this.#v)}set busy(t){v(this.#v,t,!0)}#p=F(Lt([]));get lastFaults(){return e(this.#p)}set lastFaults(t){v(this.#p,t,!0)}#f=F(null);get pending(){return e(this.#f)}set pending(t){v(this.#f,t,!0)}#i=null;#o=null;start(){this.refresh(),this.#i=setInterval(()=>{this.refresh()},this.periodMs)}stop(){this.#i!==null&&clearInterval(this.#i),this.#i=null}async refresh(){try{this.health=await wr(),this.linkError=""}catch(t){this.linkError=Ra(t)}}#u(){this.busy=!0,this.notice="",this.actionError="",this.needsFirstPassword=!1}#t(t){this.actionError=Ra(t),this.needsFirstPassword=t instanceof It&&t.status===409,this.#m(t)}async run(t){this.#u();try{const s=await t();this.notice=s.message}catch(s){this.#t(s)}finally{this.busy=!1}await this.refresh()}async login(t){this.#u();try{return await Pr(t),this.expert=!0,this.notice="Session d’administration ouverte.",!0}catch(s){return this.expert=!1,this.#t(s),!1}finally{this.busy=!1}}async recover(t,s){this.#u();try{const c=await Cr(t,s);return this.expert=!0,this.notice=c.warning??"Le mot de passe est remplacé et la session est ouverte.",!0}catch(c){return this.#t(c),!1}finally{this.busy=!1}}async protect(t){try{return await t()}catch(s){if(!vn(s))return this.#t(s),null;if(!await this.#g(s))return null;try{return await t()}catch(c){return this.#t(c),null}}}#g(t){return this.pending={kind:t.status===409?"first-password":"password",message:t.message},new Promise(s=>{this.#o=s})}async answerPassword(t){await this.login(t)&&this.#c(!0)}async answerRecovery(t,s){await this.recover(t,s)&&this.#c(!0)}cancelPassword(){this.#c(!1)}#c(t){this.pending=null,this.notice="";const s=this.#o;this.#o=null,s?.(t)}async logout(){await Tr(),this.expert=!1,this.page="dashboard",this.notice="Session d’administration fermée."}open(t){this.actionError="",this.notice="",this.needsFirstPassword=!1,this.page=t}async load(t){try{const s=await t();return this.actionError="",this.lastFaults=[],s}catch(s){return this.lastFaults=s instanceof It?s.faults:[],this.#t(s),null}}report(t){this.lastFaults=t instanceof It?t.faults:[],this.#t(t)}#m(t){t instanceof It&&t.needsPassword&&(this.expert=!1)}}function Ra(n){return n instanceof It?n.message:n instanceof Error?"Le poste n’a pas répondu : "+n.message:"Le poste n’a pas répondu."}var fn=o('clé'),gn=o('');function at(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"busy",3,!1),_=vt(t,"disabled",3,!1),w=vt(t,"protected",3,!1),C=vt(t,"act",3,void 0);var O=gn();let A;var W=a(O),G=r(W);{var le=we=>{var ke=fn();l(we,ke)};m(G,we=>{w()&&we(le)})}p(()=>{A=Ht(O,1,`act ${s()??""}`,"svelte-5tpq0o",A,{"touch-target":s()==="destructive",busy:c()}),Be(O,"data-kind",s()),Be(O,"data-act",C()),O.disabled=_()||c(),i(W,`${(c()?"En cours…":t.label)??""} `)}),et("click",O,function(...we){t.onrun?.apply(this,we)}),l(n,O)}Tt(["click"]);var mn=o(`

                                                                                                                                                                    Le code de secours a été tiré à l’installation de ce poste et imprimé - sur sa fiche, rangée dans le classeur du magasin. Il n’existe nulle part ailleurs.

                                                                                                                                                                    `,1),hn=o(`

                                                                                                                                                                    Oublié ? Le code de secours de la fiche d’installation en repose un. - Un responsable peut aussi le faire en ligne de commande, avec openscale config password.

                                                                                                                                                                    `,1),_n=o('

                                                                                                                                                                    '),bn=o('');function wn(n,t){Ct(t,!0);const s=u(()=>t.admin.pending?.kind==="first-password");let c=F(""),_=F("");const w=8,C=u(()=>e(s)?e(_).trim().length>0&&e(c).length>=w:e(c).length>0);async function O(){!e(C)||t.admin.busy||(e(s)?await t.admin.answerRecovery(e(_),e(c)):await t.admin.answerPassword(e(c)),v(c,""),v(_,""))}var A=bn(),W=a(A),G=a(W),le=a(G),we=a(le);dr(we,{name:"settings",size:"1.5rem"});var ke=r(le,2),ue=a(ke),j=r(G,2),de=a(j),J=r(j,2);{var ye=S=>{var y=mn(),P=r(H(y),2),I=r(a(P),2);Ea(I);var Q=r(P,2),pe=a(Q);pe.textContent="Nouveau mot de passe — 8 caractères au moins";var De=r(pe,2);et("keydown",I,Re=>Re.key==="Enter"&&void O()),ia(I,()=>e(_),Re=>v(_,Re)),et("keydown",De,Re=>Re.key==="Enter"&&void O()),ia(De,()=>e(c),Re=>v(c,Re)),l(S,y)},B=S=>{var y=hn(),P=H(y),I=r(a(P),2);Ea(I),et("keydown",I,Q=>Q.key==="Enter"&&void O()),ia(I,()=>e(c),Q=>v(c,Q)),l(S,y)};m(J,S=>{e(s)?S(ye):S(B,-1)})}var $=r(J,2);{var Z=S=>{var y=_n(),P=a(y);p(()=>i(P,t.admin.actionError)),l(S,y)};m($,S=>{t.admin.actionError!==""&&S(Z)})}var D=r($,2),z=a(D);at(z,{label:"Annuler",onrun:()=>t.admin.cancelPassword()});var re=r(z,2);{let S=u(()=>e(s)?"Poser ce mot de passe":"Continuer"),y=u(()=>!e(C)||t.admin.busy);at(re,{kind:"write",get label(){return e(S)},get disabled(){return e(y)},onrun:()=>{O()}})}p(()=>{i(ue,e(s)?"Ce poste n’a pas encore de mot de passe":"Mot de passe d’administration"),i(de,t.admin.pending?.message??"")}),l(n,A),Rt()}Tt(["keydown"]);var yn=o(' '),xn=o(""),kn=o(""),qn=o('

                                                                                                                                                                    '),Sn=o(" "),Ln=o('

                                                                                                                                                                    '),En=o('
                                                                                                                                                                    ');function _t(n,t){Ct(t,!0);const s=vt(t,"hint",3,""),c=vt(t,"kind",3,"text"),_=vt(t,"disabled",3,!1),w=vt(t,"fault",3,""),C=vt(t,"allowed",19,()=>[]),O=u(()=>`field-${t.path.replace(/\./gu,"-")}`);var A=En();let W;var G=a(A),le=a(G),we=a(le),ke=r(le,2);{var ue=D=>{var z=yn(),re=a(z);p(()=>i(re,t.path)),l(D,z)};m(ke,D=>{Gt.showTechnicalNames&&D(ue)})}var j=r(G,2),de=r(j,2);{var J=D=>{var z=kn();rt(z,20,C,re=>re,(re,S)=>{var y=xn(),P={};p(()=>{P!==(P=S)&&(y.value=(y.__value=S)??"")}),l(re,y)}),p(()=>Be(z,"id",e(O)+"-allowed")),l(D,z)};m(de,D=>{C().length>0&&D(J)})}var ye=r(de,2);{var B=D=>{var z=qn(),re=a(z);p(()=>i(re,s())),l(D,z)};m(ye,D=>{s()!==""&&D(B)})}var $=r(ye,2);{var Z=D=>{var z=Ln(),re=a(z),S=r(re);{var y=P=>{var I=Sn(),Q=a(I);p(pe=>i(Q,`Valeurs acceptées : ${pe??""}.`),[()=>C().join(", ")]),l(P,I)};m(S,P=>{C().length>0&&P(y)})}p(()=>i(re,`${w()??""} `)),l(D,z)};m($,D=>{w()!==""&&D(Z)})}p(()=>{W=Ht(A,1,"field svelte-1oilv5s",null,W,{refused:w()!==""}),Be(G,"for",e(O)),i(we,t.label),Be(j,"id",e(O)),Be(j,"type",c()),na(j,t.value),j.disabled=_(),Be(j,"list",C().length>0?e(O)+"-allowed":void 0)}),et("input",j,D=>t.onchange(D.currentTarget.value)),l(n,A),Rt()}Tt(["input"]);const Oa=1e3;function da(n){const t=La(n);return t===null?"":[ta(t.getDate()),ta(t.getMonth()+1),String(t.getFullYear())].join("/")}function Mt(n){const t=La(n);return t===null?"":`${da(n)} à ${ta(t.getHours())}:${ta(t.getMinutes())}`}function zn(n){const t=La(n);return t===null?"":[ta(t.getHours()),ta(t.getMinutes()),ta(t.getSeconds())].join(":")}function wa(n){const t=n/1e6;return t<1e3?`${Math.round(t)} Mo`:`${Qa(t/1e3)} Go`}function he(n){const t=String(Math.abs(Math.trunc(n))),s=[];for(let c=t.length;c>0;c-=3)s.unshift(t.slice(Math.max(0,c-3),c));return(n<0?"-":"")+s.join(" ")}function ca(n){return n0&&w.push({count:Pt(n.anomalies_count),label:"anomalies",note:"à corriger dans Odoo",link:Aa(n.anomalies_count)}),n.unit_mismatches_count>0){const C=n.unit_mismatches_count;w.push({count:"+ "+Pt(C),label:C===1?"unité divergente":"unités divergentes",note:"pesable, unité à corriger",link:Aa(C)})}return{headline:`Catalogue du ${da(n.occurred_at)} — ${Pt(s)} produits reçus`,lines:w,oneLine:Pn(n)}}function Pn(n){const t=[`${Pt(n.rows_read_count)} reçus`,`${Pt(n.weighable_count)} pesables`,`${Pt(n.not_weighable_count)} non pesables`,`${Pt(n.anomalies_count)} anomalies`],s=n.unit_mismatches_count;return s>0&&t.push(`${Pt(s)} ${s===1?"unité divergente":"unités divergentes"}`),t.join(" · ")}function Aa(n){return n===1?"voir la ligne":`voir les ${Pt(n)} lignes`}function Tn(n){return n.map(t=>`${Cn(t)} (${Pt(t.count)})`).join(", ")}function Cn(n){switch(n.code){case"PREPACKAGED_PRODUCT":return"préemballés";case"INTERNAL_CODE_NOT_WEIGHABLE":return n.value===""?"code interne":"code interne "+n.value;case"NO_BARCODE":return"sans code-barres";default:return n.code}}function Pt(n){return String(n)}var Rn=o(' '),On=o(''),An=o('
                                                                                                                                                                    '),Nn=o('

                                                                                                                                                                    ');function Za(n,t){Ct(t,!0);const s=u(()=>jn(t.record,t.motives));var c=Nn(),_=a(c),w=a(_),C=r(_,2);rt(C,21,()=>e(s).lines,W=>W.label,(W,G)=>{var le=An(),we=a(le),ke=a(we),ue=r(we,2),j=a(ue),de=a(j),J=r(j,2);{var ye=Z=>{var D=Rn(),z=a(D);p(()=>i(z,e(G).note)),l(Z,D)};m(J,Z=>{e(G).note!==""&&Z(ye)})}var B=r(J,2);{var $=Z=>{var D=On(),z=a(D);p(()=>i(z,e(G).link)),et("click",D,()=>t.onshowrows(e(G).label==="anomalies"?"anomalies":"units")),l(Z,D)};m(B,Z=>{e(G).link!==""&&t.onshowrows!==void 0&&Z($)})}p(()=>{Be(le,"data-row",e(G).label),i(ke,e(G).count),i(de,e(G).label)}),l(W,le)});var O=r(C,2),A=a(O);p(()=>{i(w,e(s).headline),i(A,e(s).oneLine)}),l(n,c),Rt()}Tt(["click"]);var $n=o('

                                                                                                                                                                    '),Dn=o('

                                                                                                                                                                    ');function Qe(n,t){const s=vt(t,"note",3,"");var c=Dn(),_=a(c),w=a(_),C=r(_,2);{var O=W=>{var G=$n(),le=a(G);p(()=>i(le,s())),l(W,G)};m(C,W=>{s()!==""&&W(O)})}var A=r(C,2);mr(A,()=>t.children),p(()=>i(w,t.title)),l(n,c)}const _a=(n,t=sa,s=sa)=>{var c=Un(),_=a(c);rt(_,21,t,fa,(w,C)=>{var O=In(),A=a(O),W=a(A),G=r(A,2),le=a(G),we=r(G,2),ke=a(we),ue=r(we,2),j=a(ue);p(de=>{i(W,`ligne ${de??""}`),i(le,e(C).product_id),i(ke,e(C).value),i(j,e(C).message)},[()=>he(e(C).csv_line)]),l(w,O)}),p(()=>Be(c,"data-rows",s())),l(n,c)};var In=o('
                                                                                                                                                                  • '),Un=o('
                                                                                                                                                                      '),Fn=o('

                                                                                                                                                                      Lecture des réglages du poste…

                                                                                                                                                                      '),Mn=o(`

                                                                                                                                                                      Le poste y cherche le fichier , et le supprime - une fois lu : c’est ce qui dit au producteur que la livraison est prise.

                                                                                                                                                                      `,1),Bn=o(`

                                                                                                                                                                      Sur un serveur WebDAV, le dépôt d’un fichier CSV depuis cet écran n’est plus - possible : le poste n’a plus de répertoire local où l’écrire. C’est le seul recours - du jour de la mise en service.

                                                                                                                                                                      `,1),Vn=o(`

                                                                                                                                                                      Ce poste ne déclare aucune source : choisissez-en une ci-dessus, sinon il n’ira - chercher aucun catalogue.

                                                                                                                                                                      `),Wn=o('
                                                                                                                                                                      ',1),Gn=o('

                                                                                                                                                                      Aucun import enregistré sur ce poste.

                                                                                                                                                                      '),Hn=o(`

                                                                                                                                                                      « Oublier la quarantaine » fait relire un fichier que le poste avait écarté : c’est le - seul geste de cette page qui puisse remettre en service un catalogue refusé.

                                                                                                                                                                      `,1),Jn=o('

                                                                                                                                                                      '),Kn=o(`

                                                                                                                                                                      Déposez ici le fichier clé

                                                                                                                                                                      Ce dépôt remplace toute la grille par le fichier apporté : il change ce que le poste - vend, et le mot de passe est donc demandé au moment du dépôt.

                                                                                                                                                                      `,1),Xn=o('

                                                                                                                                                                      '),Yn=o('

                                                                                                                                                                      Aucune anomalie sur le dernier import.

                                                                                                                                                                      '),Qn=o('

                                                                                                                                                                      ',1),Zn=o('

                                                                                                                                                                      '),es=o('

                                                                                                                                                                      Aucune unité divergente sur le dernier import.

                                                                                                                                                                      '),ts=o('

                                                                                                                                                                      ',1),as=o('

                                                                                                                                                                      '),rs=o('

                                                                                                                                                                      Aucun produit non pesable sur le dernier import.

                                                                                                                                                                      '),ns=o('

                                                                                                                                                                      ',1),ss=o('

                                                                                                                                                                      Aucun import enregistré : rien n’a encore pu être retiré.

                                                                                                                                                                      '),ls=o('

                                                                                                                                                                      Aucun produit retiré par le dernier import.

                                                                                                                                                                      '),is=o(`

                                                                                                                                                                      Ils restent enregistrés avec leur historique : une étiquette déjà collée reste - lisible en caisse, et un produit qui revient dans un prochain fichier retrouve sa - tuile. Ce poste n’en publie encore que le nombre — leurs noms se lisent dans Odoo, - en comparant avec l’export précédent.

                                                                                                                                                                      `,1),os=o('
                                                                                                                                                                    • '),us=o(`

                                                                                                                                                                        Un produit retiré de la grille ne se trouve plus ici : il se reprend dans - « Décisions en vigueur », plus bas.

                                                                                                                                                                        `,1),cs=o(`

                                                                                                                                                                        Sans motif, le poste refuse la décision. Le seul acte qui s’en passe est celui qui - EFFACE la décision en vigueur : proposer de nouveau un produit qui ne porte aucune - dérogation.

                                                                                                                                                                        `),ds=o(`

                                                                                                                                                                        Produit choisi :

                                                                                                                                                                        Ce produit est-il proposé dans la grille ?

                                                                                                                                                                        La dérogation s’enregistre seule : elle ne remet pas dans la grille un produit - qui en a été retiré, et retirer un produit n’efface pas sa dérogation.

                                                                                                                                                                        `),vs=o(' ',1),ps=o('

                                                                                                                                                                        Aucune décision locale : la grille est celle du fichier.

                                                                                                                                                                        '),fs=o('
                                                                                                                                                                      • '),gs=o('

                                                                                                                                                                          ',1),ms=o('

                                                                                                                                                                          '),hs=o('

                                                                                                                                                                          Aucun import dans l’historique.

                                                                                                                                                                          '),_s=o(' '),bs=o('

                                                                                                                                                                          QuandFichierSourceRésultatMotifLuesPesablesNon pesablesAnomaliesRetirés
                                                                                                                                                                          ',1),ws=o('
                                                                                                                                                                          ');function ys(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=20,_=50,w=20,C=["NO_BARCODE","PREPACKAGED_PRODUCT","INTERNAL_CODE_NOT_WEIGHABLE"],O={applied:"appliqué",unchanged:"identique au précédent",rejected:"refusé",failed:"échec"},A={local_drop:"dépôt local",webdav:"WebDAV",manual:"déposé sur l’écran"},W={local_drop:["catalog.options.directory"],webdav:["catalog.options.url","catalog.options.username","catalog.options.password"]},G=u(()=>t.draft.text("catalog.type"));let le=F(Lt([])),we=F(Lt([])),ke=F(Lt([])),ue=F(""),j=F(""),de=F(""),J=F(""),ye=F(!1),B=F(""),$=F(""),Z=F("loading"),D=F("loading");const z=u(()=>s().busy||e($)!==""),re=u(()=>e(we).filter(k=>k.issue==="anomaly")),S=u(()=>e(we).filter(k=>C.includes(k.code))),y=u(()=>e(we).filter(k=>k.code==="UNIT_MISMATCH")),P=u(()=>new Map(e(ke).map(k=>[k.id,k.name]))),I=u(()=>e(ue)===""?[]:vr(e(ke),pr,e(ue))),Q=u(()=>e(I).slice(0,c)),pe=u(()=>e(re).slice(0,_)),De=u(()=>e(y).slice(0,_)),Re=u(()=>e(S).slice(0,_)),b=u(()=>[...t.health.decisions].sort((k,Y)=>Y.decided_at.localeCompare(k.decided_at))),T=u(()=>e(b).slice(0,w)),g=u(()=>e(j)===""?null:e(b).find(k=>k.product_id===e(j))??null),U=u(()=>e(g)===null?!0:e(g).offered),N=u(()=>e(g)?.min_weight_g??null),K=u(()=>e(J).trim()===""?null:Number(e(J))),_e=u(()=>e(K)!==null&&Number.isFinite(e(K))&&e(K)>0),ne=u(()=>e(de).trim()),L=u(()=>e(ne)!==""||!Le(!1,e(N))),X=u(()=>e(ne)!==""||!Le(!0,e(N))),ie=u(()=>e(_e)&&(e(ne)!==""||!Le(e(U),e(K)))),fe=u(()=>e(ne)!==""||!Le(e(U),null)),xe=u(()=>t.health.catalog?.products_withdrawn_count??0),He=u(()=>e(le).find(k=>t.health.catalog!==null&&k.id`${he(e(xe))} ${e(xe)>1?"produits retirés":"produit retiré"}`+(e(He)===null?".":` depuis l’import du ${Mt(e(He).occurred_at)}.`)),ge=u(()=>e(I).length>e(Q).length?`${he(e(Q).length)} produits affichés sur ${he(e(I).length)} trouvés — précisez votre recherche.`:`${he(e(I).length)} ${e(I).length>1?"produits trouvés":"produit trouvé"}.`),d=u(()=>`${he(e(le).length)} ${e(le).length>1?"imports affichés":"import affiché"} : le poste n’en publie jamais plus de vingt.`),x=u(()=>e(Z)==="loading"?"Lecture des signalements du dernier import…":"Les signalements du dernier import n’ont pas pu être lus : cet écran ne sait pas ce qu’ils disent."),ee=u(()=>e(Z)==="loading"?"Lecture de l’historique des imports…":"L’historique des imports n’a pas pu être lu : cet écran ne sait pas ce qu’il contient.");let f;aa(()=>{const k=t.health.catalog?.id??null;k!==f&&(f=k,q())});async function q(){const k=await s().load(()=>Wr(t.health.catalog?.id));k===null?v(Z,"unread"):(v(le,k.imports,!0),v(we,k.findings,!0),v(Z,"read"));const Y=await s().load(()=>Ba());if(Y===null){v(D,"unread");return}v(ke,Y.products,!0),v(D,"read")}async function E(k,Y){v($,k,!0),s().actionError="",s().notice="";try{const oe=await s().protect(Y);return oe===null?!1:(s().notice=oe.message,await s().refresh(),!0)}finally{v($,"")}}function be(k){t.draft.set("catalog.type",k);for(const[Y,oe]of Object.entries(W))if(Y!==k)for(const Ae of oe)t.draft.unset(Ae)}function se(k){return t.draft.faults.find(Y=>Y.field===k)?.message??""}function Se(k){v(j,k,!0),v(de,"");const Y=e(b).find(oe=>oe.product_id===k)?.min_weight_g??null;v(J,Y===null?"":String(Y),!0)}function Le(k,Y){return!(k&&Y===null)}async function ce(k){if(e(j)==="")return;const Y=e(j),oe=e(N),Ae=e(ne);await E("offered",()=>Pa(Y,{offered:k,min_weight_g:oe,reason:Ae}))&&v(de,"")}async function V(k){if(e(j)==="")return;const Y=e(j),oe=e(U),Ae=e(ne);await E(k===null?"waiver-off":"waiver",()=>Pa(Y,{offered:oe,min_weight_g:k,reason:Ae}))&&v(de,"")}async function Ue(k){if(v(ye,!1),k!=null){v(B,""),s().actionError="",s().notice="",v($,"import");try{const Y=await s().protect(()=>Ga(k));if(Y===null)return;v(B,`${k.name} : ${he(Y.rows_read_count)} lignes lues, ${he(Y.weighable_count)} pesables. `+(Y.reason===""?"Le fichier est déposé ; son résultat s’inscrira dans l’historique des imports.":Y.reason)),await s().refresh()}finally{v($,"")}}}function Ve(k){k.preventDefault(),v(ye,!1);const Y=k.dataTransfer?.files.item(0);if(e(z)){v(B,`${Y?.name??"Ce fichier"} n’a pas été déposé : un acte est déjà en cours sur cette page. Réessayez quand il aura répondu.`);return}Ue(Y)}function Ee(k){const Y=k.files?.item(0);k.value="",Ue(Y)}function Te(k){const Y=e(P).get(k);return Y!==void 0?Y:e(D)==="loading"?"Lecture du nom…":e(D)==="unread"?"Nom inconnu : le catalogue n’a pas pu être lu":"Produit absent du catalogue en service"}function Je(k,Y,oe,Ae){const We=Y>1?Ae:oe;return k>=Y?`${he(Y)} ${We}.`:`${he(k)} lignes affichées sur ${he(Y)} ${We}.`}function ae(k){const Y=[];return k.offered||Y.push("retiré de la grille"),k.min_weight_g!==null&&Y.push(`peut peser à partir de ${he(k.min_weight_g)} g`),Y.length===0?"aucune restriction":Y.join(" · ")}function Ie(k){const Y=O[k.result]??"résultat inconnu";return k.code===""?Y:`${Y} (${k.code})`}function ve(k){return A[k.source]??"source inconnue"}var ze=ws(),Oe=a(ze);Qe(Oe,{title:"Où le poste va chercher le catalogue",children:(k,Y)=>{var oe=Wn(),Ae=H(oe),We=a(Ae),Ye=a(We),Ke=r(We,2),M=a(Ke),te=r(Ae,2);{var me=Pe=>{var Fe=Fn();l(Pe,Fe)},Ne=Pe=>{var Fe=Mn(),Ge=H(Fe);{let it=u(()=>Ft("catalog.options.directory")),dt=u(()=>t.draft.text("catalog.options.directory")),gt=u(()=>se("catalog.options.directory"));_t(Ge,{get label(){return e(it)},path:"catalog.options.directory",get value(){return e(dt)},hint:"Laissez vide pour le répertoire du poste, celui que le service crée lui-même. Un répertoire nommé ici doit exister : le poste ne le crée pas.",get fault(){return e(gt)},onchange:xt=>t.draft.set("catalog.options.directory",xt)})}var ft=r(Ge,2),ot=r(a(ft)),ct=a(ot);p(()=>i(ct,`flv_${t.health.station??""}.csv`)),l(Pe,Fe)},qe=Pe=>{var Fe=Bn(),Ge=H(Fe);{let ct=u(()=>Ft("catalog.options.url")),it=u(()=>t.draft.text("catalog.options.url")),dt=u(()=>se("catalog.options.url"));_t(Ge,{get label(){return e(ct)},path:"catalog.options.url",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.url",gt)})}var ft=r(Ge,2);{let ct=u(()=>Ft("catalog.options.username")),it=u(()=>t.draft.text("catalog.options.username")),dt=u(()=>se("catalog.options.username"));_t(ft,{get label(){return e(ct)},path:"catalog.options.username",get value(){return e(it)},get fault(){return e(dt)},onchange:gt=>t.draft.set("catalog.options.username",gt)})}var ot=r(ft,2);{let ct=u(()=>Ft("catalog.options.password")),it=u(()=>se("catalog.options.password"));_t(ot,{get label(){return e(ct)},path:"catalog.options.password",kind:"password",value:"",hint:"Laissez vide : le mot de passe actuel est conservé.",get fault(){return e(it)},onchange:dt=>t.draft.set("catalog.options.password",dt)})}l(Pe,Fe)},$e=Pe=>{var Fe=Vn();l(Pe,Fe)};m(te,Pe=>{t.draft.config===null?Pe(me):e(G)==="local_drop"?Pe(Ne,1):e(G)==="webdav"?Pe(qe,2):Pe($e,-1)})}p(()=>{ua(Ye,e(G)==="local_drop"),ua(M,e(G)==="webdav")}),et("change",Ye,()=>be("local_drop")),et("change",M,()=>be("webdav")),l(k,oe)},$$slots:{default:!0}});var nt=r(Oe,2);Qe(nt,{title:"Dernier import",children:(k,Y)=>{var oe=Hn(),Ae=H(oe);{var We=qe=>{var $e=Gn();l(qe,$e)},Ye=qe=>{Za(qe,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives}})};m(Ae,qe=>{t.health.catalog===null?qe(We):qe(Ye,-1)})}var Ke=r(Ae,2),M=a(Ke),te=r(Ke,2),me=a(te);{let qe=u(()=>e($)==="reload");at(me,{act:"reload",kind:"write",label:"Recharger le catalogue",protected:!0,get busy(){return e(qe)},get disabled(){return e(z)},onrun:()=>{E("reload",Gr)}})}var Ne=r(me,2);{let qe=u(()=>e($)==="quarantine");at(Ne,{act:"quarantine",kind:"destructive",label:"Oublier la quarantaine",protected:!0,get busy(){return e(qe)},get disabled(){return e(z)},onrun:()=>{E("quarantine",Hr)}})}p(()=>i(M,t.health.catalog_source===null?"Aucune source de catalogue publiée par ce poste.":"Source : "+t.health.catalog_source.label)),l(k,oe)},$$slots:{default:!0}});var ut=r(nt,2);Qe(ut,{title:"Déposer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(k,Y)=>{var oe=Kn(),Ae=H(oe);{var We=Pe=>{var Fe=Jn(),Ge=a(Fe);p(()=>i(Ge,e(B))),l(Pe,Fe)};m(Ae,Pe=>{e(B)!==""&&Pe(We)})}var Ye=r(Ae,2);let Ke;var M=a(Ye),te=r(a(M)),me=a(te),Ne=r(M,2),qe=a(Ne),$e=r(qe);p(()=>{Ke=Ht(Ye,1,"drop svelte-3gk7u7",null,Ke,{dropping:e(ye),working:e($)==="import"}),i(me,`flv_${t.health.station??""}.csv`),i(qe,`${e($)==="import"?"Import en cours…":"Choisir un fichier"} `),$e.disabled=e(z)}),Qt("dragover",Ye,Pe=>{Pe.preventDefault(),!e(z)&&v(ye,!0)}),Qt("dragleave",Ye,()=>v(ye,!1)),Qt("drop",Ye,Ve),et("change",$e,Pe=>Ee(Pe.currentTarget)),l(k,oe)},$$slots:{default:!0}});var pt=r(ut,2);Qe(pt,{title:"Anomalies à corriger dans Odoo",note:"Chaque ligne porte son numéro dans le CSV, son motif et la valeur fautive.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=Xn(),me=a(te);p(()=>i(me,e(x))),l(M,te)},Ye=M=>{var te=Yn();l(M,te)},Ke=M=>{var te=Qn(),me=H(te),Ne=a(me),qe=r(Ne);{var $e=Fe=>{var Ge=yt("Corrigez celles-ci dans Odoo : l’import suivant ne signalera que ce qui reste.");l(Fe,Ge)};m(qe,Fe=>{e(re).length>e(pe).length&&Fe($e)})}var Pe=r(me,2);_a(Pe,()=>e(pe),()=>"anomalies"),p(Fe=>i(Ne,`${Fe??""} `),[()=>Je(e(pe).length,e(re).length,"anomalie","anomalies")]),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(re).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var je=r(pt,2);Qe(je,{title:"Unités divergentes",note:"Le produit reste proposé : le code-barres fait foi, seul le libellé du prix est faux.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=Zn(),me=a(te);p(()=>i(me,e(x))),l(M,te)},Ye=M=>{var te=es();l(M,te)},Ke=M=>{var te=ts(),me=H(te),Ne=a(me),qe=r(me,2);_a(qe,()=>e(De),()=>"mismatches"),p($e=>i(Ne,$e),[()=>Je(e(De).length,e(y).length,"unité divergente","unités divergentes")]),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(y).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var st=r(je,2);Qe(st,{title:"Produits non pesables",note:"Un inventaire, pas une liste d’erreurs : ces produits portent déjà leur code-barres et n’ont aucune raison d’être pesés.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=as(),me=a(te);p(()=>i(me,e(x))),l(M,te)},Ye=M=>{var te=rs();l(M,te)},Ke=M=>{var te=ns(),me=H(te),Ne=a(me),qe=r(me,2);_a(qe,()=>e(Re),()=>"not-weighable"),p($e=>i(Ne,$e),[()=>Je(e(Re).length,e(S).length,"produit non pesable","produits non pesables")]),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(S).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var Ze=r(st,2);Qe(Ze,{title:"Produits retirés depuis l’import précédent",note:"Un produit absent du nouveau fichier est marqué retiré à sa date, jamais supprimé.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=ss();l(M,te)},Ye=M=>{var te=ls();l(M,te)},Ke=M=>{var te=is(),me=H(te),Ne=a(me);p(()=>i(Ne,e(Xe))),l(M,te)};m(Ae,M=>{t.health.catalog===null?M(We):e(xe)===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}});var bt=r(Ze,2);Qe(bt,{title:"Décider d’un produit",note:"Retirer un produit et l’autoriser à peser moins sont deux décisions séparées : l’une n’efface pas l’autre.",children:(k,Y)=>{var oe=vs(),Ae=r(H(oe),2),We=r(Ae,2);{var Ye=te=>{var me=us(),Ne=H(me),qe=a(Ne),$e=r(Ne,2),Pe=a($e);rt(Pe,21,()=>e(Q),Fe=>Fe.id,(Fe,Ge)=>{var ft=os(),ot=a(ft),ct=a(ot),it=r(ot,2),dt=a(it),gt=r(it,2),xt=a(gt);p(()=>{i(ct,e(Ge).name),i(dt,e(Ge).id),i(xt,`${e(Ge).unit_price_text??""}${e(Ge).price_suffix??""}`)}),et("click",ot,()=>Se(e(Ge).id)),l(Fe,ft)}),p(()=>i(qe,e(ge))),l(te,me)};m(We,te=>{e(ue)!==""&&te(Ye)})}var Ke=r(We,2);{var M=te=>{var me=ds(),Ne=a(me),qe=r(a(Ne)),$e=a(qe),Pe=r(qe),Fe=r(Ne,2),Ge=a(Fe),ft=r(Fe,4),ot=r(ft,2);{var ct=tt=>{var qt=cs();l(tt,qt)};m(ot,tt=>{e(ne)===""&&tt(ct)})}var it=r(ot,2),dt=r(a(it),2),gt=a(dt);{var xt=tt=>{{let qt=u(()=>e($)==="offered"),$t=u(()=>e(z)||!e(L));at(tt,{act:"offered",kind:"destructive",label:"Ne plus proposer ce produit",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{ce(!1)}})}},Nt=tt=>{{let qt=u(()=>e($)==="offered"),$t=u(()=>e(z)||!e(X));at(tt,{act:"offered",kind:"write",label:"Le proposer de nouveau",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{ce(!0)}})}};m(gt,tt=>{e(U)?tt(xt):tt(Nt,-1)})}var Bt=r(it,2),At=r(a(Bt),2),Vt=r(At,2),kt=a(Vt);{let tt=u(()=>e($)==="waiver"),qt=u(()=>e(z)||!e(ie));at(kt,{act:"waiver",kind:"write",label:"Enregistrer la dérogation",protected:!0,get busy(){return e(tt)},get disabled(){return e(qt)},onrun:()=>{V(e(K))}})}var Et=r(kt,2);{var Wt=tt=>{{let qt=u(()=>e($)==="waiver-off"),$t=u(()=>e(z)||!e(fe));at(tt,{act:"waiver-off",kind:"write",label:"Retirer la dérogation",protected:!0,get busy(){return e(qt)},get disabled(){return e($t)},onrun:()=>{V(null)}})}};m(Et,tt=>{e(N)!==null&&tt(Wt)})}p((tt,qt)=>{Be(me,"data-decision",e(j)),i($e,tt),i(Pe,` (${e(j)??""})`),i(Ge,`En vigueur : ${qt??""}`),na(At,e(J))},[()=>Te(e(j)),()=>e(g)===null?"aucune décision — ce produit suit les règles générales":ae(e(g))]),ia(ft,()=>e(de),tt=>v(de,tt)),et("input",At,tt=>v(J,tt.currentTarget.value,!0)),l(te,me)};m(Ke,te=>{e(j)!==""&&te(M)})}ia(Ae,()=>e(ue),te=>v(ue,te)),l(k,oe)},$$slots:{default:!0}});var Ot=r(bt,2);Qe(Ot,{title:"Décisions en vigueur",note:"Ce qu’un humain a décidé, produit par produit. C’est ici que se reprend un produit retiré de la grille.",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=Ke=>{var M=ps();l(Ke,M)},Ye=Ke=>{var M=gs(),te=H(M),me=a(te),Ne=r(te,2),qe=a(Ne);rt(qe,21,()=>e(T),$e=>$e.product_id,($e,Pe)=>{var Fe=fs(),Ge=a(Fe),ft=r(Ge,2),ot=a(ft),ct=r(ft,2),it=a(ct),dt=r(ct,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),At=a(Bt);p((Vt,kt,Et)=>{i(ot,Vt),i(it,e(Pe).product_id),i(gt,kt),i(Nt,e(Pe).reason),i(At,Et)},[()=>Te(e(Pe).product_id),()=>ae(e(Pe)),()=>da(e(Pe).decided_at)]),et("click",Ge,()=>Se(e(Pe).product_id)),l($e,Fe)}),p($e=>i(me,$e),[()=>Je(e(T).length,e(b).length,"décision en vigueur","décisions en vigueur")]),l(Ke,M)};m(Ae,Ke=>{e(b).length===0?Ke(We):Ke(Ye,-1)})}l(k,oe)},$$slots:{default:!0}});var Ut=r(Ot,2);Qe(Ut,{title:"Vingt derniers imports",children:(k,Y)=>{var oe=Dt(),Ae=H(oe);{var We=M=>{var te=ms(),me=a(te);p(()=>i(me,e(ee))),l(M,te)},Ye=M=>{var te=hs();l(M,te)},Ke=M=>{var te=bs(),me=H(te),Ne=a(me),qe=r(me,2),$e=a(qe),Pe=r(a($e));rt(Pe,21,()=>e(le),Fe=>Fe.id,(Fe,Ge)=>{var ft=_s(),ot=a(ft),ct=a(ot),it=r(ot),dt=a(it),gt=r(it),xt=a(gt),Nt=r(gt),Bt=a(Nt),At=r(Nt),Vt=a(At),kt=r(At),Et=a(kt),Wt=r(kt),tt=a(Wt),qt=r(Wt),$t=a(qt),Zt=r(qt),ra=a(Zt),h=r(Zt),R=a(h);p((Ce,Me,lt,mt,wt,zt,jt,St)=>{i(ct,Ce),i(dt,e(Ge).file_name),i(xt,Me),i(Bt,lt),i(Vt,e(Ge).reason),i(Et,mt),i(tt,wt),i($t,zt),i(ra,jt),i(R,St)},[()=>Mt(e(Ge).occurred_at),()=>ve(e(Ge)),()=>Ie(e(Ge)),()=>he(e(Ge).rows_read_count),()=>he(e(Ge).weighable_count),()=>he(e(Ge).not_weighable_count),()=>he(e(Ge).anomalies_count),()=>he(e(Ge).products_withdrawn_count)]),l(Fe,ft)}),p(()=>i(Ne,e(d))),l(M,te)};m(Ae,M=>{e(Z)!=="read"?M(We):e(le).length===0?M(Ye,1):M(Ke,-1)})}l(k,oe)},$$slots:{default:!0}}),l(n,ze),Rt()}Tt(["change","click","input"]);function xs(n){switch(n){case"ok":return"OK";case"warn":return"À SURVEILLER";case"fault":return"EN PANNE";case"off":return"SANS OBJET";default:return"INCONNU"}}var ks=o('

                                                                                                                                                                          '),qs=o('

                                                                                                                                                                          ');function Ss(n,t){Ct(t,!0);var s=qs(),c=a(s),_=a(c),w=r(_,2),C=a(w),O=r(w,2),A=a(O),W=r(c,2),G=a(W),le=r(W,2);{var we=ke=>{var ue=ks(),j=a(ue);p(()=>i(j,t.light.remedy)),l(ke,ue)};m(le,ke=>{t.light.remedy!==""&&ke(we)})}p(ke=>{Be(s,"data-light",t.light.id),Be(s,"data-level",t.light.level),Be(_,"data-level",t.light.level),i(C,t.light.label),i(A,ke),i(G,t.light.value)},[()=>xs(t.light.level)]),l(n,s),Rt()}function Ls(n){return[Es(n),zs(n),Ps(n),Ts(n),Rs(n),Os(n)]}function Es(n){const t=n.state.scale,s=ca(t.median_ms);return n.scale_present?t.connected?t.too_slow?{id:"scale",label:"Balance",level:"warn",value:`une mesure toutes les ${s}, plus lent que la péremption`,remedy:"À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante. Vérifiez le câble et l’adaptateur USB, puis la cadence sur la page Matériel."}:{id:"scale",label:"Balance",level:"ok",value:As(t.provisional,`une mesure toutes les ${s}`),remedy:""}:{id:"scale",label:"Balance",level:"fault",value:"elle ne répond plus",remedy:"Vérifiez le câble et l’alimentation de la balance, puis touchez « Tester la balance ». En attendant, « Basculer en saisie manuelle » permet de continuer à servir."}:{id:"scale",label:"Balance",level:"off",value:"ce poste est déclaré sans balance",remedy:""}}function zs(n){const t=n.state.printer;switch(t.health){case"faulted":return{id:"printer",label:"Imprimante",level:"fault",value:t.detail===""?"elle ne peut pas imprimer":t.detail,remedy:js(n)};case"consumable":return{id:"printer",label:"Imprimante",level:"warn",value:"elle imprime, mais le rouleau arrive en fin de vie",remedy:"Changez le rouleau quand vous passez derrière le comptoir, puis touchez « J’ai changé le rouleau ». Le poste continue de servir en attendant."};case"ready":return{id:"printer",label:"Imprimante",level:"ok",value:"elle répond et n’a rien à signaler",remedy:""};default:return{id:"printer",label:"Imprimante",level:"unknown",value:"elle prend les étiquettes et ne dit rien en retour",remedy:"C’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne. Pour savoir si elle imprime, touchez « Imprimer une étiquette de test »."}}}function js(n){const t="Regardez le capot, le rouleau et le câble, puis touchez « Tester l’imprimante ».";return n.printing?.fallback_available===!0?t+" Le poste peut servir en attendant : « Imprimer sur l’imprimante du poste voisin ».":t}function Ps(n){const t=n.roll;return t===null?{id:"roll",label:"Rouleau",level:"unknown",value:"aucun compteur d’étiquettes sur ce poste",remedy:`Sans imprimante construite, il n’y a pas de rouleau à compter : vérifiez le « ${Ft("printer.type")} » et ses réglages sur la page Matériel.`}:t.known?t.level==="warn"?{id:"roll",label:"Rouleau",level:"warn",value:t.message,remedy:"Changez le rouleau, puis touchez « J’ai changé le rouleau »."}:{id:"roll",label:"Rouleau",level:"ok",value:t.message,remedy:""}:{id:"roll",label:"Rouleau",level:"unknown",value:t.message,remedy:"Touchez « J’ai changé le rouleau » en mettant un rouleau neuf : c’est le seul geste qui dise quelque chose de vrai du papier."}}function Ts(n){const t=n.catalog_source?.label??"";if(n.state.catalog_count===0)return{id:"catalog",label:"Catalogue",level:"fault",value:"aucun produit dans la grille",remedy:Cs(t)};const s=n.catalog;return s!==null&&s.result!=="applied"&&s.result!=="unchanged"?{id:"catalog",label:"Catalogue",level:"warn",value:`dernier fichier refusé : ${s.reason===""?s.result:s.reason}`,remedy:"La grille tourne toujours sur le catalogue précédent. Corrigez le fichier dans Odoo, ou touchez « Oublier la quarantaine » puis redéposez-le."}:{id:"catalog",label:"Catalogue",level:"ok",value:`${he(n.state.catalog_count)} produits dans la grille`,remedy:""}}function Cs(n){return"Déposez le fichier du catalogue là où le poste le guette, ou utilisez « Importer un catalogue » ci-contre pour le glisser depuis une clé USB."+(n===""?"":` Ce poste surveille : ${n}.`)}function Rs(n){const t=n.disk;if(t===null)return{id:"disk",label:"Disque",level:"unknown",value:"la place libre n’a pas pu être mesurée",remedy:"Téléchargez le fichier de diagnostic : il porte ce que le poste a pu lire du volume, et c’est la pièce qu’un support demandera."};const s=wa(t.free_bytes),c=`seuil ${he(t.alert_mb)} Mo`;return t.free_bytes===0?{id:"disk",label:"Disque",level:"fault",value:`plus un octet libre sur ${t.path}`,remedy:"Le journal ne peut plus rien écrire, alors que les étiquettes continuent de sortir. Faites de la place sur le disque, puis rechargez cette page."}:t.alert_mb>0&&t.free_bytes0?{id:"journal",label:"Journal",level:"fault",value:`${he(t)} pesées imprimées mais non enregistrées`,remedy:"Les étiquettes sortent, les ventes sont bonnes, c’est la trace qui manque. Téléchargez le fichier de diagnostic et prévenez le support : ne redémarrez rien."}:n.counters.journal_rows_count<0?{id:"journal",label:"Journal",level:"unknown",value:"ce poste n’a pas de journal ouvert",remedy:"Le poste pèse et imprime quand même. Téléchargez le fichier de diagnostic : il dit pourquoi la base n’a pas pu être ouverte."}:{id:"journal",label:"Journal",level:"ok",value:`${he(n.counters.journal_rows_count)} pesées enregistrées`,remedy:""}}function As(n,t){return n?t+" (cadence encore provisoire)":t}function Ns(n){return n===null||!n.known?"unknown":n.configured?"ok":"missing"}function $s(n){switch(n){case"applied":return"appliqué";case"unchanged":return"inchangé, déjà appliqué";case"rejected":return"refusé";case"failed":return"échec";default:return n}}var Ds=o("Une mesure toutes les ",1),Is=o('

                                                                                                                                                                          '),Us=o("Source : ",1),Fs=o(`

                                                                                                                                                                          Aucun import enregistré sur ce poste : le catalogue n’est jamais arrivé, ou le - journal ne le porte pas.

                                                                                                                                                                          `),Ms=o('

                                                                                                                                                                          ',1),Bs=o('

                                                                                                                                                                          ',1),Vs=o('

                                                                                                                                                                          Aucune décision locale : le catalogue est proposé tel qu’il arrive.

                                                                                                                                                                          '),Ws=o('

                                                                                                                                                                          '),Gs=o(' '),Hs=o('
                                                                                                                                                                        • '),Js=o('
                                                                                                                                                                            ',1),Ks=o(`INCONNU — la question n’a pas pu être posée à ce système. Ce - n’est pas « non configuré » : personne ne sait encore.`,1),Xs=o('OK ',1),Ys=o('NON CONFIGURÉ ',1),Qs=o('

                                                                                                                                                                            '),Zs=o('

                                                                                                                                                                            ',1),el=o('

                                                                                                                                                                            Rien à signaler depuis le démarrage du poste.

                                                                                                                                                                            '),tl=o(' '),al=o('
                                                                                                                                                                          • '),rl=o('
                                                                                                                                                                              '),nl=o(''),sl=o('

                                                                                                                                                                              '),ll=o('
                                                                                                                                                                              Numéro de poste
                                                                                                                                                                              Nom
                                                                                                                                                                              Coopérative
                                                                                                                                                                              Version
                                                                                                                                                                              Empreinte de configuration
                                                                                                                                                                              ',1),il=o('
                                                                                                                                                                              ');function ol(n,t){Ct(t,!0);const s=u(()=>t.health.new_version),c=u(()=>Ls(t.health)),_=u(()=>t.health.state.scale),w=u(()=>t.health.unattended_restart),C=u(()=>t.health.decisions),O=5,A=u(()=>e(C).slice(0,O));var W=il(),G=a(W);rt(G,21,()=>e(c),J=>J.id,(J,ye)=>{Ss(J,{get light(){return e(ye)}})});var le=r(G,2);Qe(le,{title:"Cadence de la balance",children:(J,ye)=>{var B=Is(),$=a(B);{var Z=S=>{var y=yt("Ce poste est déclaré sans balance : le poids est saisi à la main.");l(S,y)},D=S=>{var y=yt(`La balance ne répond pas : aucune cadence n’est mesurable tant qu’aucune trame - n’arrive.`);l(S,y)},z=S=>{var y=yt(`Aucun intervalle n’a encore été mesuré : la cadence apparaîtra dès les premières - trames.`);l(S,y)},re=S=>{var y=Ds(),P=r(H(y)),I=a(P),Q=r(P),pe=r(Q);{var De=T=>{var g=yt("C’est encore une valeur d’attente : moins de huit intervalles ont été observés.");l(T,g)};m(pe,T=>{e(_).provisional&&T(De)})}var Re=r(pe,2);{var b=T=>{var g=yt("À cette cadence, un poids serait périmé avant l’arrivée de la mesure suivante.");l(T,g)};m(Re,T=>{e(_).too_slow&&T(b)})}p((T,g)=>{i(I,T),i(Q,`, médiane - observée sur ${g??""} intervalles. `)},[()=>ca(e(_).median_ms),()=>he(e(_).observations_count)]),l(S,y)};m($,S=>{t.health.scale_present?e(_).connected?e(_).observations_count===0?S(z,2):S(re,-1):S(D,1):S(Z)})}l(J,B)},$$slots:{default:!0}});var we=r(le,2);Qe(we,{title:"Catalogue",children:(J,ye)=>{var B=Bs(),$=H(B),Z=a($);{var D=P=>{var I=yt("Aucune source de catalogue n’est publiée par ce poste.");l(P,I)},z=P=>{var I=Us(),Q=r(H(I)),pe=a(Q);p(()=>i(pe,t.health.catalog_source.label)),l(P,I)};m(Z,P=>{t.health.catalog_source===null?P(D):P(z,-1)})}var re=r($,2);{var S=P=>{var I=Fs();l(P,I)},y=P=>{var I=Ms(),Q=H(I),pe=a(Q),De=r(Q,2);Za(De,{get record(){return t.health.catalog},get motives(){return t.health.catalog_motives},get onshowrows(){return t.onshowrows}}),p((Re,b)=>i(pe,`Dernier essai : ${Re??""} — - ${b??""}${t.health.catalog.file_name===""?"":" ("+t.health.catalog.file_name+")"}${t.health.catalog.reason===""?"":" : "+t.health.catalog.reason}`),[()=>Mt(t.health.catalog.occurred_at),()=>$s(t.health.catalog.result)]),l(P,I)};m(re,P=>{t.health.catalog===null?P(S):P(y,-1)})}l(J,B)},$$slots:{default:!0}});var ke=r(we,2);Qe(ke,{title:"Décisions locales en vigueur",note:"Ce qu’un humain a décidé de ce catalogue, avec son motif et sa date.",children:(J,ye)=>{var B=Dt(),$=H(B);{var Z=z=>{var re=Vs();l(z,re)},D=z=>{var re=Js(),S=H(re);{var y=I=>{var Q=Ws(),pe=a(Q);p(De=>i(pe,`${De??""} décisions en vigueur — les - 5 plus récentes ci-dessous. La liste entière est sur la page - Catalogue.`),[()=>he(e(C).length)]),l(I,Q)};m(S,I=>{e(C).length>O&&I(y)})}var P=r(S,2);rt(P,21,()=>e(A),I=>I.product_id,(I,Q)=>{var pe=Hs(),De=a(pe),Re=a(De),b=r(De,2);{var T=_e=>{var ne=Gs(),L=a(ne);p(X=>i(L,`peut peser à partir de ${X??""} g`),[()=>he(e(Q).min_weight_g)]),l(_e,ne)};m(b,_e=>{e(Q).min_weight_g!==null&&_e(T)})}var g=r(b,2),U=a(g),N=r(g,2),K=a(N);p(_e=>{i(Re,`${e(Q).offered?"Dérogation de poids":"Produit retiré"} — ${e(Q).product_id??""}`),i(U,e(Q).reason),i(K,`${_e??""}, ${e(Q).decided_by??""}`)},[()=>da(e(Q).decided_at)]),l(I,pe)}),l(z,re)};m($,z=>{e(C).length===0?z(Z):z(D,-1)})}l(J,B)},$$slots:{default:!0}});var ue=r(ke,2);Qe(ue,{title:"Redémarrage sans intervention",children:(J,ye)=>{var B=Zs(),$=H(B),Z=a($);{var D=P=>{var I=Ks();l(P,I)},z=P=>{var I=Xs(),Q=r(H(I));p(()=>i(Q,` — après une coupure de courant, ce poste revient seul sur - l’écran client. ${e(w).detail??""}`)),l(P,I)},re=P=>{var I=Ys(),Q=r(H(I));p(()=>i(Q,` — ${e(w).detail??""}`)),l(P,I)};m(Z,P=>{e(w)===null||!e(w).known?P(D):e(w).configured?P(z,1):P(re,-1)})}var S=r($,2);{var y=P=>{var I=Qs(),Q=a(I);p(()=>i(Q,e(w).remedy)),l(P,I)};m(S,P=>{e(w)!==null&&e(w).known&&!e(w).configured&&e(w).remedy!==""&&P(y)})}p(P=>Be($,"data-verdict",P),[()=>Ns(e(w))]),l(J,B)},$$slots:{default:!0}});var j=r(ue,2);Qe(j,{title:"Dix derniers événements",children:(J,ye)=>{var B=Dt(),$=H(B);{var Z=z=>{var re=el();l(z,re)},D=z=>{var re=rl();rt(re,21,()=>t.health.events,S=>S.id,(S,y)=>{var P=al(),I=a(P),Q=a(I),pe=r(I,2),De=a(pe),Re=r(pe,2);{var b=U=>{var N=tl(),K=a(N);p(()=>i(K,e(y).code)),l(U,N)};m(Re,U=>{e(y).code!==""&&Gt.showTechnicalNames&&U(b)})}var T=r(Re,2),g=a(T);p((U,N)=>{Be(P,"data-level",e(y).level),i(Q,U),i(De,N),i(g,e(y).message)},[()=>zn(e(y).occurred_at),()=>Xa(e(y).source)]),l(S,P)}),l(z,re)};m($,z=>{t.health.events.length===0?z(Z):z(D,-1)})}l(J,B)},$$slots:{default:!0}});var de=r(j,2);Qe(de,{title:"Ce poste",children:(J,ye)=>{var B=ll(),$=H(B),Z=r(a($),2),D=a(Z),z=r(Z,4),re=a(z),S=r(z,4),y=a(S),P=r(S,4),I=a(P),Q=r(P,4),pe=a(Q),De=r($,2);{var Re=b=>{var T=sl(),g=a(T),U=r(g);{var N=K=>{var _e=nl();et("click",_e,function(...ne){t.onshowupdate?.apply(this,ne)}),l(K,_e)};m(U,K=>{t.onshowupdate!==void 0&&K(N)})}p(()=>{Be(T,"data-update-available",e(s)),i(g,`Version ${e(s)??""} disponible. `)}),l(b,T)};m(De,b=>{e(s)!==""&&b(Re)})}p(()=>{i(D,t.health.station),i(re,t.health.station_name===""?"non renseigné":t.health.station_name),i(y,t.health.coop),i(I,t.health.version),i(pe,t.health.config_fingerprint)}),l(J,B)},$$slots:{default:!0}}),l(n,W),Rt()}Tt(["click"]);var ul=o(''),cl=o(`

                                                                                                                                                                              Lecture de la configuration en cours… tant qu’elle n’est pas arrivée, cette page ne - déclare rien de ce poste.

                                                                                                                                                                              `),Na=o('
                                                                                                                                                                            • '),dl=o('
                                                                                                                                                                                '),vl=o('

                                                                                                                                                                                ',1),pl=o('
                                                                                                                                                                              • '),fl=o('

                                                                                                                                                                                  ',1),gl=o('

                                                                                                                                                                                  '),ml=o('
                                                                                                                                                                                • '),hl=o('
                                                                                                                                                                                    '),_l=o('

                                                                                                                                                                                      ',1),bl=o('

                                                                                                                                                                                      Balance

                                                                                                                                                                                      L’état et la cadence viennent de ce que le poste observe vraiment, jamais d’un réglage.

                                                                                                                                                                                      Réglages série de la balance

                                                                                                                                                                                      Imprimante

                                                                                                                                                                                      Réglages de l’imprimante
                                                                                                                                                                                      ');function wl(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=20,_=3,w=250,C=50,O=_*1e3+w+1e3,A=12;let W=F(Lt([])),G=F(Lt([])),le=F(Lt([])),we=F(Lt([])),ke=F(""),ue=F(!1),j=F(""),de=F(0),J=F(0),ye=F(!1),B=F(null),$=!1;const Z=u(()=>t.health.state.scale),D=u(()=>t.health.state.printer),z=u(()=>t.draft.config!==null),re=u(()=>e(z)&&!t.draft.flag("scale.present")),S=u(()=>t.draft.text("scale.options.port")),y=u(()=>e(B)!==null&&e(B).port===e(S)?e(B).reason:""),P=u(()=>e(ke)===e(S)?e(we):[]),I=u(d),Q=u(ee),pe=u(E),De=u(Se),Re=u(()=>Ee("scale.type")!==""||Ee("scale.options.port")!==""),b=u(()=>Ee("printer.type")!==""||Ee("printer.options.transport")!==""||Ee("printer.options.queue")!=="");br(()=>{$=!0}),ka(()=>{L("ports",X)}),aa(()=>{e(ye)||!T(e(S))||U(e(S))});function T(h){return!$&&e(z)&&!e(re)&&h!==""&&h===e(S)&&g(h)&&e(y)===""&&e(j)===""}function g(h){return e(W).some(R=>R.name===h)}async function U(h){v(ye,!0);try{for(;T(h);){try{K(h,await ja(h,_))}catch(R){v(B,{port:h,reason:Je(R)},!0);return}await _e()}}finally{v(ye,!1)}}async function N(){const h=e(S),R=await s().protect(()=>ja(h,_));R!==null&&(K(h,R),v(B,null))}function K(h,R){if($||R.length===0)return;const Ce=h===e(ke)?e(we):[];v(ke,h,!0),v(we,[...Ce,...R].slice(-c),!0)}function _e(h=w){return new Promise(R=>setTimeout(R,h))}async function ne(){const h=Date.now()+O;for(;e(ye)&&Date.now()Ha(h));R!==null&&(s().notice=R.message,await s().refresh())}async function He(){v(de,0),v(J,0),e(ue)||await X();const h=e(W);if(h.length===0){v(le,[],!0);return}await s().protect(()=>Xe(h))}async function Xe(h){v(le,[],!0),v(J,h.length,!0);for(const[R,Ce]of h.entries()){v(de,R+1);try{const Me=await Ur(Ce.name);v(le,[...e(le),{port:Ce.name,message:Me.message,refused:!1}],!0)}catch(Me){if(ae(Me))throw Me;v(le,[...e(le),{port:Ce.name,message:Je(Me),refused:!0}],!0)}}}function ge(){return e(j)!=="detect"?"Détecter automatiquement":e(ye)?"Détection : le port se libère…":e(J)===0?"Détection : énumération des ports…":`Détection : port ${he(e(de))} sur ${he(e(J))}…`}function d(){return t.health.scale_present?e(Z).connected?e(Z).too_slow?{level:"warn",word:"Trop lente",detail:x()+" À cette cadence, un poids serait déclaré périmé avant l’arrivée de la mesure suivante."}:{level:"ok",word:"Connectée",detail:"Elle répond. "+x()}:{level:"fault",word:"Sans réponse",detail:"Elle ne répond plus. Vérifiez le câble et l’alimentation, puis « Tester la balance » sur la page Dépannage."}:{level:"off",word:"Sans balance",detail:"Ce poste est déclaré sans balance : le feu est éteint et le poids se saisit à la main."}}function x(){return e(Z).observations_count===0?"Aucun intervalle n’a encore été mesuré : la cadence sera connue dès les premières trames.":`Une mesure toutes les ${ca(e(Z).median_ms)} sur ${he(e(Z).observations_count)} intervalles`+(e(Z).provisional?", cadence encore provisoire.":".")}function ee(){const h=f[e(D).health]??{level:"unknown",word:"État inconnu",detail:"Le poste a répondu un état que cet écran ne sait pas nommer."};return{...h,detail:e(D).detail===""?h.detail:e(D).detail}}const f={ready:{level:"ok",word:"Prête",detail:"Elle répond et n’a rien à signaler."},consumable:{level:"warn",word:"Rouleau en fin de vie",detail:"Elle imprime, mais le rouleau arrive en fin de vie."},faulted:{level:"fault",word:"En panne",detail:"Elle ne peut pas imprimer."},unknown:{level:"unknown",word:"Silencieuse",detail:"Elle prend les étiquettes et ne dit rien en retour : c’est la réponse normale d’une file Windows en RAW ou d’un fichier de périphérique, pas une panne."}};function q(){const h=e(D).observed_at===""?"Jamais observée depuis le démarrage":`Observée le ${Mt(e(D).observed_at)}`,R=e(D).pending_jobs_count;return`${h}, ${he(R)} ${R>1?"travaux":"travail"} en attente.`}function E(){const h=se();return h===""?be():`${be()} ${h}`}function be(){return e(z)?e(re)?"Ce poste est déclaré sans balance : aucun port n’est écouté.":e(S)===""?"Aucun port n’est indiqué : choisissez-en un dans la liste ci-dessus pour écouter les trames.":e(ue)?g(e(S))?e(y)!==""?`L’écoute de ${e(S)} est arrêtée.`:e(j)!==""?`L’écoute de ${e(S)} est suspendue le temps de l’acte en cours.`:`Écoute de ${e(S)}.`:`${e(S)} n’est pas visible depuis ce poste : rien n’est écouté en continu.`:e(j)==="ports"?`Énumération des ports en cours : l’écoute de ${e(S)} démarre dès qu’il est vu.`:`Les ports de ce poste n’ont pas été énumérés : « Lister les ports » dira si ${e(S)} existe.`:"Lecture de la configuration en cours : le port à écouter n’est pas encore connu."}function se(){return e(P).length===0?"Aucune trame reçue pour l’instant.":e(P).length===1?`Une seule trame reçue — ${he(c)} au plus sont gardées.`:`Les ${he(e(P).length)} dernières trames — ${he(c)} au plus, la plus récente en bas.`}function Se(){return!e(z)||e(re)||e(S)===""?"":e(y)!==""?"Reprendre l’écoute":e(ue)&&!g(e(S))?"Écouter ce port une fois":""}function Le(h,R,Ce){const Me=`${he(Ce)} ${Ce>1?R:h}`;return Ce<=A?Me+".":`${Me} — seules les ${he(A)} premières lignes sont affichées.`}function ce(h,R){const Ce=Te(h);return Ce.length>0?Ce:R.slice(0,A)}function V(h){return[...new TextEncoder().encode(h)].map(R=>R.toString(16).toUpperCase().padStart(2,"0")).join(" ")}const Ue={0:"NUL",2:"STX",3:"ETX",4:"EOT",5:"ENQ",6:"ACK",9:"TAB",10:"LF",13:"CR",21:"NAK",27:"ESC",127:"DEL"};function Ve(h){let R="";for(const Ce of h){const Me=Ce.codePointAt(0)??0;if(Me>=32&&Me!==127){R+=Ce;continue}R+=`⟨${Ue[Me]??Me.toString(16).toUpperCase().padStart(2,"0")}⟩`}return R}function Ee(h){return t.draft.faults.find(R=>R.field===h)?.message??""}function Te(h){return t.draft.faults.find(R=>R.field===h)?.allowed??[]}function Je(h){return h instanceof It?h.message:h instanceof Error?"Le poste n’a pas répondu : "+h.message:"Le poste n’a pas répondu."}function ae(h){return h instanceof It&&(h.status===401||h.status===409)}const Ie=[{what:"label",name:"étiquette"},{what:"alignment",name:"alignement"},{what:"ruler",name:"réglette"}];var ve=bl(),ze=a(ve),Oe=a(ze),nt=r(a(Oe),2),ut=r(a(nt),1,!0),pt=r(Oe,2),je=a(pt),st=r(pt,4);{var Ze=h=>{var R=ul(),Ce=a(R);p(()=>ua(Ce,e(re))),et("change",Ce,Me=>t.draft.set("scale.present",!Me.currentTarget.checked)),l(h,R)},bt=h=>{var R=cl();l(h,R)};m(st,h=>{e(z)?h(Ze):h(bt,-1)})}var Ot=r(st,2),Ut=a(Ot);{let h=u(()=>e(j)==="ports"),R=u(()=>e(j)!=="");at(Ut,{label:"Lister les ports",get busy(){return e(h)},get disabled(){return e(R)},onrun:()=>{L("ports",X)}})}var k=r(Ut,2);{let h=u(ge),R=u(()=>e(j)!=="");at(k,{act:"detect",get label(){return e(h)},protected:!0,get disabled(){return e(R)},onrun:()=>{L("detect",He)}})}var Y=r(Ot,2);{var oe=h=>{var R=vl(),Ce=H(R),Me=a(Ce),lt=r(Ce,2);{var mt=wt=>{var zt=dl();rt(zt,21,()=>e(W).slice(0,A),jt=>jt.name,(jt,St)=>{var Kt=Na(),ea=a(Kt),ga=a(ea),er=r(ea,2),tr=a(er);p(()=>{i(ga,e(St).name),i(tr,`${(e(St).description===""?"aucune description USB":e(St).description)??""} - ${e(St).vid===""?"":` — VID ${e(St).vid} PID ${e(St).pid}`}`)}),et("click",ea,()=>t.draft.set("scale.options.port",e(St).name)),l(jt,Kt)}),l(wt,zt)};m(lt,wt=>{e(W).length>0&&wt(mt)})}p(wt=>i(Me,wt),[()=>e(W).length===0?"Aucun port série n’est visible depuis ce poste.":Le("port détecté","ports détectés",e(W).length)]),l(h,R)};m(Y,h=>{e(ue)&&h(oe)})}var Ae=r(Y,2);{var We=h=>{var R=fl(),Ce=H(R),Me=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(le).slice(0,A),mt=>mt.port,(mt,wt)=>{var zt=pl();let jt;var St=a(zt),Kt=a(St),ea=r(St,2),ga=a(ea);p(()=>{jt=Ht(zt,1,"svelte-1p4i0xm",null,jt,{refused:e(wt).refused}),i(Kt,e(wt).port),i(ga,e(wt).message)}),l(mt,zt)}),p(mt=>i(Me,mt),[()=>Le("port interrogé","ports interrogés",e(le).length)]),l(h,R)};m(Ae,h=>{e(le).length>0&&h(We)})}var Ye=r(Ae,2),Ke=r(a(Ye),2),M=a(Ke);{let h=u(()=>t.draft.text("scale.type")),R=u(()=>Ee("scale.type")),Ce=u(()=>Te("scale.type")),Me=u(()=>!e(z));_t(M,{label:"Protocole",path:"scale.type",get value(){return e(h)},hint:"Les valeurs acceptées apparaissent ici si l’enregistrement est refusé.",get fault(){return e(R)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("scale.type",lt)})}var te=r(M,2);{let h=u(()=>t.draft.text("scale.options.port")),R=u(()=>Ee("scale.options.port")),Ce=u(()=>ce("scale.options.port",e(W).map(lt=>lt.name))),Me=u(()=>!e(z));_t(te,{label:"Port série",path:"scale.options.port",get value(){return e(h)},hint:"Choisissez-le dans la liste détectée ci-dessus plutôt que de le taper : l’écoute permanente ne suit que des ports détectés.",get fault(){return e(R)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("scale.options.port",lt)})}var me=r(Ye,2),Ne=a(me),qe=a(Ne),$e=r(Ne,2);{var Pe=h=>{var R=gl(),Ce=a(R),Me=r(Ce);{var lt=mt=>{{let wt=u(()=>e(j)==="listen"),zt=u(()=>e(j)!=="");at(mt,{act:"listen",get label(){return e(De)},protected:!0,get busy(){return e(wt)},get disabled(){return e(zt)},onrun:()=>{L("listen",N)}})}};m(Me,mt=>{e(De)!==""&&mt(lt)})}p(()=>i(Ce,`${e(y)??""} `)),l(h,R)};m($e,h=>{(e(y)!==""||e(De)!=="")&&h(Pe)})}var Fe=r($e,2);{var Ge=h=>{var R=hl();rt(R,21,()=>e(P),fa,(Ce,Me)=>{var lt=ml(),mt=a(lt),wt=a(mt),zt=r(mt,2),jt=a(zt);p((St,Kt)=>{i(wt,St),i(jt,Kt)},[()=>V(e(Me)),()=>Ve(e(Me))]),l(Ce,lt)}),l(h,R)};m(Fe,h=>{e(P).length>0&&h(Ge)})}var ft=r(ze,2),ot=a(ft),ct=r(a(ot),2),it=r(a(ct),1,!0),dt=r(ot,2),gt=a(dt),xt=r(dt,2),Nt=a(xt),Bt=r(xt,2),At=a(Bt);{let h=u(()=>e(j)==="printers"),R=u(()=>e(j)!=="");at(At,{label:"Lister les files",get busy(){return e(h)},get disabled(){return e(R)},onrun:()=>{L("printers",ie)}})}var Vt=r(At,2);{let h=u(()=>e(j)==="discover"),R=u(()=>e(j)!=="");at(Vt,{label:"Rechercher l’imprimante",protected:!0,get busy(){return e(h)},get disabled(){return e(R)},onrun:()=>{L("discover",fe)}})}var kt=r(Vt,2);rt(kt,17,()=>Ie,h=>h.what,(h,R)=>{{let Ce=u(()=>`Auto-test : ${e(R).name}${e(j)===e(R).what?" — en cours…":""}`),Me=u(()=>e(j)!=="");at(h,{get act(){return e(R).what},get label(){return e(Ce)},protected:!0,get disabled(){return e(Me)},onrun:()=>{L(e(R).what,()=>xe(e(R).what))}})}});var Et=r(Bt,2);{var Wt=h=>{var R=_l(),Ce=H(R),Me=a(Ce),lt=r(Ce,2);rt(lt,21,()=>e(G).slice(0,A),mt=>mt.name,(mt,wt)=>{var zt=Na(),jt=a(zt),St=a(jt),Kt=r(jt,2),ea=a(Kt);p(()=>{i(St,e(wt).name),i(ea,`${e(wt).detail??""}${e(wt).default?" — file par défaut du système":""}`)}),et("click",jt,()=>t.draft.set("printer.options.queue",e(wt).name)),l(mt,zt)}),p(mt=>i(Me,mt),[()=>Le("file d’impression","files d’impression",e(G).length)]),l(h,R)};m(Et,h=>{e(G).length>0&&h(Wt)})}var tt=r(Et,2),qt=r(a(tt),2),$t=a(qt);{let h=u(()=>t.draft.text("printer.type")),R=u(()=>Ee("printer.type")),Ce=u(()=>Te("printer.type")),Me=u(()=>!e(z));_t($t,{label:"Driver",path:"printer.type",get value(){return e(h)},hint:"Gardez le driver raster : c’est celui que les postes en service utilisent.",get fault(){return e(R)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.type",lt)})}var Zt=r($t,2);{let h=u(()=>t.draft.text("printer.options.transport")),R=u(()=>Ee("printer.options.transport")),Ce=u(()=>Te("printer.options.transport")),Me=u(()=>!e(z));_t(Zt,{label:"Transport",path:"printer.options.transport",get value(){return e(h)},hint:"Local par défaut : une file Windows ou un fichier de périphérique de ce poste.",get fault(){return e(R)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.options.transport",lt)})}var ra=r(Zt,2);{let h=u(()=>t.draft.text("printer.options.queue")),R=u(()=>Ee("printer.options.queue")),Ce=u(()=>ce("printer.options.queue",e(G).map(lt=>lt.name))),Me=u(()=>!e(z));_t(ra,{label:"File ou chemin",path:"printer.options.queue",get value(){return e(h)},hint:"Choisissez-la dans la liste ci-dessus : une file mal orthographiée ne s’imprime pas.",get fault(){return e(R)},get allowed(){return e(Ce)},get disabled(){return e(Me)},onchange:lt=>t.draft.set("printer.options.queue",lt)})}p(h=>{Be(nt,"data-level",e(I).level),i(ut,e(I).word),i(je,e(I).detail),Ye.open=e(Re),i(qe,e(pe)),Be(ct,"data-level",e(Q).level),i(it,e(Q).word),i(gt,e(Q).detail),i(Nt,h),tt.open=e(b)},[()=>q()]),l(n,ve),Rt()}Tt(["change","click"]);var yl=o(""),xl=o('L’export sera proposé quand le journal aura répondu.'),kl=o('L’export n’est pas proposé : ce poste n’a pas répondu à la lecture du journal.'),ql=o('Exporter en CSV'),Sl=o('

                                                                                                                                                                                      '),Ll=o('

                                                                                                                                                                                      '),El=o(`

                                                                                                                                                                                      Aucune trame brute n’a été enregistrée pour cette pesée : il n’y a - rien à rejouer.

                                                                                                                                                                                      `),zl=o(`

                                                                                                                                                                                      La trame repart dans le décodeur du poste EN SERVICE : le poids - affiché au client change, et rien ne le remet comme il était. C’est - ce qui fait d’un refus inexpliqué un test permanent, sans - déplacement au magasin et sans balance.

                                                                                                                                                                                      `,1),jl=o('

                                                                                                                                                                                      Produit
                                                                                                                                                                                      Référence
                                                                                                                                                                                      Vente
                                                                                                                                                                                      Brut / tare / net
                                                                                                                                                                                      Stabilité
                                                                                                                                                                                      Origine du poids
                                                                                                                                                                                      Résultat
                                                                                                                                                                                      Trame brute
                                                                                                                                                                                      '),Pl=o(' ',1),Tl=o('

                                                                                                                                                                                      QuandProduitNetCode-barresRésultatDuréeDétail
                                                                                                                                                                                      ',1),Cl=o('
                                                                                                                                                                                      ',1),Rl=o('

                                                                                                                                                                                      '),Ol=o(' '),Al=o(' '),Nl=o('
                                                                                                                                                                                    • '),$l=o('

                                                                                                                                                                                        ',1),Dl=o('
                                                                                                                                                                                        ');function Il(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=200,_=5e3,w=50,C=500,O=7,A={sent:"envoyée à l’imprimante",rejected:"refusée",failed:"en échec",reprint:"réimpression"},W=[{value:"",label:"toutes"},{value:"sent",label:"envoyées à l’imprimante"},{value:"rejected",label:"refusées"},{value:"failed",label:"en échec"},{value:"reprint",label:"réimpressions"}],G={scale:"balance",manual:"saisie manuelle",replay:"trame rejouée"},le={stable:"stable",unstable:"instable",unknown:"non déclarée par la balance",not_applicable:"sans objet — saisie manuelle"},we={by_weight:"au poids",by_unit:"à l’unité"},ke={debug:"mise au point",info:"information",warn:"avertissement",error:"erreur",critical:"critique"};let ue=F(Lt([])),j=F(Lt([])),de=F(""),J=F(null),ye=F(!1),B=F(!1),$=F("loading"),Z=F("loading"),D=F(""),z=F("");const re=u(()=>({limit:String(c),...e(de)===""?{}:{result:e(de)}})),S=u(()=>({...e(re),limit:String(_)})),y=u(()=>e(ue).find(N=>N.id===e(J))??null),P=u(()=>W.find(N=>N.value===e(de))?.label??""),I=u(()=>Re(e(ue).length,c,"pesée","pesées",`L’export CSV en emporte jusqu’à ${he(_)}.`)),Q=u(()=>Re(e(j).length,w,"ligne","lignes",`Le fichier de diagnostic emporte les ${he(C)} dernières.`));pe();async function pe(){v(J,null),v(B,!0),v($,"loading"),v(Z,"loading"),v(ue,[],!0),v(j,[],!0),v(D,""),v(z,"");try{const N=await s().load(()=>Mr(e(re)));v($,N===null?"unread":"read",!0),v(D,N===null?s().actionError:"",!0),v(ue,N??[],!0);const K=await s().load(()=>Vr({limit:String(w)}));v(Z,K===null?"unread":"read",!0),v(z,K===null?s().actionError:"",!0),v(j,K??[],!0)}finally{v(B,!1),s().actionError=""}}async function De(N){s().notice="",s().actionError="",v(ye,!0);try{const K=await s().protect(()=>Jr(N));K!==null&&(s().notice=K.message)}finally{v(ye,!1)}}function Re(N,K,_e,ne,L){const X=N>1?ne:_e;return N{var _e=Cl(),ne=H(_e),L=r(a(ne),2);rt(L,21,()=>W,f=>f.value,(f,q)=>{var E=yl(),be=a(E),se={};p(()=>{i(be,e(q).label),se!==(se=e(q).value)&&(E.value=(E.__value=e(q).value)??"")}),l(f,E)});var X=r(L,2);at(X,{label:"Rafraîchir",get busy(){return e(B)},onrun:()=>{pe()}});var ie=r(X,2);{var fe=f=>{var q=xl();l(f,q)},xe=f=>{var q=kl();l(f,q)},He=f=>{var q=ql();p(E=>Be(q,"href",E),[()=>Br(e(S))]),l(f,q)};m(ie,f=>{e($)==="loading"?f(fe):e($)==="unread"?f(xe,1):f(He,-1)})}var Xe=r(ne,2);{var ge=f=>{var q=Sl(),E=a(q);p(be=>i(E,`L’export emporte le même filtre que le tableau, mais pas son plafond : il descend - jusqu’à ${be??""} pesées, en point-virgule et en UTF-8 — il s’ouvre - tel quel dans le tableur d’un Windows français. Il ne demande aucun mot de passe : la - lecture du journal n’en demande pas non plus, et le fichier de diagnostic emporte déjà - les deux cents dernières pesées.`),[()=>he(_)]),l(f,q)};m(Xe,f=>{e($)==="read"&&f(ge)})}var d=r(Xe,2);{var x=f=>{var q=Ll(),E=a(q);{var be=ce=>{var V=yt("Lecture du journal…");l(ce,V)},se=ce=>{var V=yt();p(()=>i(V,`Le journal n’a pas pu être lu : ${e(D)??""} Ce n’est pas « aucune pesée ».`)),l(ce,V)},Se=ce=>{var V=yt("Le journal ne contient aucune pesée.");l(ce,V)},Le=ce=>{var V=yt();p(()=>i(V,`Aucune pesée ne correspond au filtre « ${e(P)??""} ».`)),l(ce,V)};m(E,ce=>{e($)==="loading"?ce(be):e($)==="unread"?ce(se,1):e(de)===""?ce(Se,2):ce(Le,-1)})}l(f,q)},ee=f=>{var q=Tl(),E=H(q),be=a(E),se=r(E,2),Se=a(se),Le=r(a(Se));rt(Le,21,()=>e(ue),ce=>ce.id,(ce,V)=>{var Ue=Pl(),Ve=H(Ue);let Ee;var Te=a(Ve),Je=a(Te),ae=r(Te),Ie=a(ae),ve=r(ae),ze=a(ve),Oe=r(ve),nt=a(Oe),ut=r(Oe),pt=a(ut),je=r(ut),st=a(je),Ze=r(je),bt=a(Ze),Ot=a(bt),Ut=r(Ve,2);{var k=Y=>{var oe=jl(),Ae=a(oe);Be(Ae,"colspan",O);var We=a(Ae),Ye=a(We),Ke=r(We,2),M=r(a(Ke),2),te=a(M),me=r(M,4),Ne=a(me),qe=r(me,4),$e=a(qe),Pe=r(qe,4),Fe=a(Pe),Ge=r(Pe,4),ft=a(Ge),ot=r(Ge,4),ct=a(ot),it=r(ot,4),dt=a(it),gt=r(it,4),xt=a(gt),Nt=a(xt),Bt=r(Ke,2);{var At=kt=>{var Et=El();l(kt,Et)},Vt=kt=>{var Et=zl(),Wt=H(Et),tt=a(Wt);at(tt,{kind:"destructive",label:"Rejouer cette trame",protected:!0,get busy(){return e(ye)},onrun:()=>{De(e(y).frame)}}),l(kt,Et)};m(Bt,kt=>{e(y).frame===""?kt(At):kt(Vt,-1)})}p((kt,Et,Wt,tt,qt,$t,Zt,ra,h)=>{Be(oe,"data-detail",e(V).id),i(Ye,`Pesée ${e(y).id??""}`),i(te,`${e(y).product_name??""} (${e(y).product_id??""})`),i(Ne,e(y).reference),i($e,`${kt??""} — - ${Et??""} - ${e(y).quantity>1?"unités":"unité"}`),i(Fe,`${Wt??""} g / ${tt??""} g / - ${qt??""} g`),i(ft,`${$t??""} — cadence - médiane ${Zt??""}`),i(ct,ra),i(dt,`${h??""}${e(y).detail===""?"":` — ${e(y).detail}`}`),i(Nt,e(y).frame===""?"aucune trame enregistrée":e(y).frame)},[()=>b(we,e(y).mode,"mode de vente inconnu"),()=>he(e(y).quantity),()=>he(e(y).gross_g),()=>he(e(y).tare_g),()=>he(e(y).net_g),()=>b(le,e(y).stability,"stabilité inconnue"),()=>ca(e(y).rate_ms),()=>b(G,e(y).source,"origine inconnue"),()=>b(A,e(y).result,"résultat inconnu")]),l(Y,oe)};m(Ut,Y=>{e(J)===e(V).id&&e(y)!==null&&Y(k)})}p((Y,oe,Ae,We)=>{Ee=Ht(Ve,1,"svelte-86o8oz",null,Ee,{open:e(J)===e(V).id}),i(Je,Y),i(Ie,e(V).product_name),i(ze,`${oe??""} g`),i(nt,e(V).barcode),i(pt,Ae),i(st,We),Be(bt,"aria-expanded",e(J)===e(V).id),i(Ot,e(J)===e(V).id?"fermer":"détail")},[()=>Mt(e(V).occurred_at),()=>he(e(V).net_g),()=>b(A,e(V).result,"résultat inconnu"),()=>ca(e(V).duration_ms)]),et("click",bt,()=>v(J,e(J)===e(V).id?null:e(V).id,!0)),l(ce,Ue)}),p(()=>i(be,e(I))),l(f,q)};m(d,f=>{e(ue).length===0?f(x):f(ee,-1)})}et("change",L,()=>{pe()}),_r(L,()=>e(de),f=>v(de,f)),l(N,_e)},$$slots:{default:!0}});var U=r(g,2);Qe(U,{title:"Journal technique",children:(N,K)=>{var _e=Dt(),ne=H(_e);{var L=ie=>{var fe=Rl(),xe=a(fe);{var He=d=>{var x=yt("Lecture du journal technique…");l(d,x)},Xe=d=>{var x=yt();p(()=>i(x,`Le journal technique n’a pas pu être lu : ${e(z)??""} Ce n’est pas « aucune - ligne ».`)),l(d,x)},ge=d=>{var x=yt("Aucune ligne technique.");l(d,x)};m(xe,d=>{e(Z)==="loading"?d(He):e(Z)==="unread"?d(Xe,1):d(ge,-1)})}l(ie,fe)},X=ie=>{var fe=$l(),xe=H(fe),He=a(xe),Xe=r(xe,2),ge=a(Xe);rt(ge,21,()=>e(j),d=>d.id,(d,x)=>{var ee=Nl(),f=a(ee),q=a(f),E=r(f,2),be=a(E),se=r(E,2),Se=a(se),Le=r(se,2);{var ce=Te=>{var Je=Ol(),ae=a(Je);p(()=>i(ae,e(x).code)),l(Te,Je)};m(Le,Te=>{e(x).code!==""&&Gt.showTechnicalNames&&Te(ce)})}var V=r(Le,2),Ue=a(V),Ve=r(V,2);{var Ee=Te=>{var Je=Al(),ae=a(Je);p(()=>i(ae,e(x).detail)),l(Te,Je)};m(Ve,Te=>{e(x).detail!==""&&Te(Ee)})}p((Te,Je,ae)=>{Be(ee,"data-level",e(x).level),i(q,Te),i(be,Je),i(Se,ae),i(Ue,e(x).message)},[()=>Mt(e(x).occurred_at),()=>b(ke,e(x).level,"niveau inconnu"),()=>Xa(e(x).source)]),l(d,ee)}),p(()=>i(He,e(Q))),l(ie,fe)};m(ne,ie=>{e(j).length===0?ie(L):ie(X,-1)})}l(N,_e)},$$slots:{default:!0}}),l(n,T),Rt()}Tt(["change","click"]);var Ul=o('

                                                                                                                                                                                        '),Fl=o('

                                                                                                                                                                                        '),Ml=o('

                                                                                                                                                                                        '),Bl=o('

                                                                                                                                                                                        '),Vl=o('

                                                                                                                                                                                        Lecture de la configuration en cours… les flèches attendent qu’elle soit arrivée.

                                                                                                                                                                                        '),Wl=o(`
                                                                                                                                                                                        Aperçu de l’étiquette telle qu’elle serait imprimée

                                                                                                                                                                                        Décalage horizontal

                                                                                                                                                                                        Décalage vertical

                                                                                                                                                                                        Le décalage ne descend pas sous zéro : le poste refuse un décalage négatif quel que - soit le gabarit. Le maximum, lui, dépend de la géométrie du gabarit ; il est annoncé - ici si l’enregistrement le dépasse.

                                                                                                                                                                                        Le symbole code-barres est volontairement tronqué : un symbole conforme n’entre pas - sur 40 × 25 mm avec les cinq champs texte. Ce n’est pas un défaut de rendu et il n’y - a rien à corriger.

                                                                                                                                                                                        Le détail chiffré du symbole — largeur de module, modules rendus, modules attendus — - n’est pas servi par ce poste. Cet écran n’affiche pas un chiffre qu’il aurait deviné.

                                                                                                                                                                                        `,1),Gl=o(`

                                                                                                                                                                                        Chaque appui sort une étiquette pour de bon : le mot de passe est demandé au moment - d’imprimer, et l’impression repart d’elle-même une fois la session ouverte.

                                                                                                                                                                                        `,1),Hl=o('
                                                                                                                                                                                        ');function Jl(n,t){Ct(t,!0);const s=vt(t,"admin",7),c="printer.template",_="printer.options.offset_x",w="printer.options.offset_y",C=0,O="L’aperçu n’a pas pu être rendu par le poste.",A="Le poste a rendu l’aperçu, mais le navigateur ne l’a pas affiché.",W=[{what:"alignment",label:"Imprimer la mire d’alignement"},{what:"ruler",label:"Imprimer la réglette"}];let G=F(1),le=F(!0),we=F(!1),ke=F(""),ue=F(null),j=F(""),de=null,J=!1;const ye=u(()=>t.draft.text(c)),B=u(()=>Fr(e(ye),e(le),e(we),e(G))),$=u(()=>t.draft.config!==null),Z=u(()=>t.draft.number(_)),D=u(()=>t.draft.number(w)),z=u(()=>U(_)),re=u(()=>U(w)),S=u(b),y=u(()=>s().busy||e(j)!=="");aa(()=>{if(t.draft.config===null)return;if(t.draft.dirty){de===null&&P();return}const L={x:t.draft.number(_),y:t.draft.number(w)};if(de!==null&&de.x===L.x&&de.y===L.y)return;const X=de===null;de=L,v(ue,L,!0),X||pe()});async function P(){if(!J){J=!0;try{const L=await qa(),X={x:I(L.config,_),y:I(L.config,w)};de=X,v(ue,X,!0)}catch{}finally{J=!1}}}function I(L,X){let ie=L;for(const fe of X.split(".")){if(ie===null||typeof ie!="object")return 0;ie=ie[fe]}return typeof ie=="number"?ie:0}function Q(L,X){const ie=t.draft.number(L)+X;ie=2?"s":""}`}function g(L,X){const ie=Number(X);X.trim()===""||Number.isNaN(ie)||t.draft.set(L,ie)}function U(L){return t.draft.faults.find(X=>X.field===L)?.message??""}async function N(L){if(!e(y)){v(j,L,!0),s().actionError="",s().notice="";try{const X=await s().protect(()=>Ha(L));if(X===null)return;s().notice=X.message,await s().refresh()}finally{v(j,"")}}}var K=Hl(),_e=a(K);Qe(_e,{title:"Aperçu de l’étiquette",note:"Le même moteur que l’impression (A2) : le décalage se voit parce qu’il est cuit dans le bitmap. L’image porte le gabarit en cours d’édition, mais le décalage ENREGISTRÉ — jamais celui que les flèches sont en train de régler.",children:(L,X)=>{var ie=Wl(),fe=H(ie);{var xe=je=>{var st=Ul(),Ze=a(st);p(()=>i(Ze,e(S))),l(je,st)};m(fe,je=>{e(S)!==""&&je(xe)})}var He=r(fe,2),Xe=a(He),ge=a(Xe),d=r(ge,2);{var x=je=>{var st=Fl(),Ze=a(st);p(()=>i(Ze,e(ke))),l(je,st)};m(d,je=>{e(ke)!==""&&je(x)})}var ee=r(Xe,2),f=a(ee),q=r(a(f),2),E=a(q),be=r(f,2),se=a(be);{let je=u(()=>!e($)||e(Z)<=C);at(se,{label:"← 1 dot",get disabled(){return e(je)},onrun:()=>Q(_,-1)})}var Se=r(se,2);{let je=u(()=>!e($));at(Se,{label:"1 dot →",get disabled(){return e(je)},onrun:()=>Q(_,1)})}var Le=r(be,2);{var ce=je=>{var st=Ml(),Ze=a(st);p(()=>i(Ze,e(z))),l(je,st)};m(Le,je=>{e(z)!==""&&je(ce)})}var V=r(Le,2),Ue=r(a(V),2),Ve=a(Ue),Ee=r(V,2),Te=a(Ee);{let je=u(()=>!e($)||e(D)<=C);at(Te,{label:"↑ 1 dot",get disabled(){return e(je)},onrun:()=>Q(w,-1)})}var Je=r(Te,2);{let je=u(()=>!e($));at(Je,{label:"1 dot ↓",get disabled(){return e(je)},onrun:()=>Q(w,1)})}var ae=r(Ee,2);{var Ie=je=>{var st=Bl(),Ze=a(st);p(()=>i(Ze,e(re))),l(je,st)};m(ae,je=>{e(re)!==""&&je(Ie)})}var ve=r(ae,4),ze=a(ve),Oe=r(ve,2),nt=a(Oe),ut=r(Oe,2);{var pt=je=>{var st=Vl();l(je,st)};m(ut,je=>{e($)||je(pt)})}p((je,st)=>{Be(ge,"src",e(B)),i(E,je),i(Ve,st)},[()=>T(e(Z)),()=>T(e(D))]),Qt("load",ge,()=>v(ke,"")),Qt("error",ge,()=>{De(e(B))}),et("change",ze,()=>pe()),za(ze,()=>e(le),je=>v(le,je)),et("change",nt,()=>pe()),za(nt,()=>e(we),je=>v(we,je)),l(L,ie)},$$slots:{default:!0}});var ne=r(_e,2);Qe(ne,{title:"Gabarit et impression",children:(L,X)=>{var ie=Gl(),fe=H(ie);{let d=u(()=>U(c)),x=u(()=>!e($));_t(fe,{label:"Gabarit",path:c,get value(){return e(ye)},hint:"Le gabarit reproduit à l’identique s’appelle weighing_identical (A1).",get fault(){return e(d)},get disabled(){return e(x)},onchange:ee=>{t.draft.set(c,ee),pe()}})}var xe=r(fe,2);{let d=u(()=>t.draft.text("printer.options.darkness")),x=u(()=>U("printer.options.darkness")),ee=u(()=>!e($));_t(xe,{label:"Noircissement",path:"printer.options.darkness",kind:"number",get value(){return e(d)},hint:"Trop bas, l’étiquette pâlit au soleil ; trop haut, elle bave et le scanner refuse.",get fault(){return e(x)},get disabled(){return e(ee)},onchange:f=>g("printer.options.darkness",f)})}var He=r(xe,2);{let d=u(()=>t.draft.text("printer.options.speed")),x=u(()=>U("printer.options.speed")),ee=u(()=>!e($));_t(He,{label:"Vitesse",path:"printer.options.speed",kind:"number",get value(){return e(d)},get fault(){return e(x)},get disabled(){return e(ee)},onchange:f=>g("printer.options.speed",f)})}var Xe=r(He,2);{let d=u(()=>t.draft.text("printer.options.copies")),x=u(()=>U("printer.options.copies")),ee=u(()=>!e($));_t(Xe,{label:"Exemplaires",path:"printer.options.copies",kind:"number",get value(){return e(d)},hint:"Un client repart avec une étiquette : deux exemplaires se justifient, pas se devinent.",get fault(){return e(x)},get disabled(){return e(ee)},onchange:f=>g("printer.options.copies",f)})}var ge=r(Xe,2);rt(ge,21,()=>W,d=>d.what,(d,x)=>{{let ee=u(()=>e(j)===e(x).what);at(d,{get act(){return e(x).what},get label(){return e(x).label},protected:!0,get busy(){return e(ee)},get disabled(){return e(y)},onrun:()=>{N(e(x).what)}})}}),l(L,ie)},$$slots:{default:!0}}),l(n,K),Rt()}Tt(["change"]);var Kl=o(" "),Xl=o(' '),Yl=o(''),Ql=o('

                                                                                                                                                                                        Aucun tarif déclaré dans la configuration lue.

                                                                                                                                                                                        '),Zl=o('Prix du catalogue Odoo — pas de remise'),$a=o(' '),ei=o(' % ',1),ti=o(' '),ai=o(`

                                                                                                                                                                                        CodeLibelléAbrégéRemiseOrdre

                                                                                                                                                                                        Un champ vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la - retrouve dès qu’on quitte le champ. Une remise effacée serait le plein tarif pour - tous les adhérents.

                                                                                                                                                                                        `,1),ri=o(" ",1),ni=o(`

                                                                                                                                                                                        Les deux arrondis sont distincts parce qu’ils tombent à des endroits différents du - calcul : arrondir le prix au kilo puis le multiplier, ou multiplier puis arrondir, ne - donne pas le même centime sur une étiquette. Le poste applique l’arrondi commercial, - et l’écart se voit au centime près sur la même pesée.

                                                                                                                                                                                        `,1),si=o('

                                                                                                                                                                                        Rien ne s’affiche au client : c’est une information.

                                                                                                                                                                                        '),li=o('

                                                                                                                                                                                        '),ii=o('
                                                                                                                                                                                        '),oi=o('

                                                                                                                                                                                        '),ui=o('
                                                                                                                                                                                      • '),ci=o(`

                                                                                                                                                                                        Aucun verdict en ce moment : ces lignes portent sur la pesée EN COURS, et le poste - est au repos. Elles apparaissent le temps d’un cycle, quand un client pose son sac - et touche une tuile.

                                                                                                                                                                                        `),di=o(' '),vi=o(' ',1),pi=o('
                                                                                                                                                                                      • '),fi=o('

                                                                                                                                                                                          ',1),gi=o(`

                                                                                                                                                                                          L’ordre compte : le premier verdict bloquant décide de ce que le client lit. Les - garde-fous 1 à 7 portent sur l’état de la balance, c’est-à-dire le poids brut ; les - garde-fous 8 à 14 portent sur la vente, c’est-à-dire le net. Le code en gris est - celui du journal, celui qu’on lit au téléphone.

                                                                                                                                                                                            Un seuil vidé garde la valeur du fichier : il n’écrit pas zéro, et la case la retrouve - dès qu’on quitte le champ. Pour changer un seuil, on tape l’autre valeur.

                                                                                                                                                                                            Ce que les garde-fous disent de la pesée en cours

                                                                                                                                                                                            `,1),mi=o(`

                                                                                                                                                                                            Le plan de numérotation n’est PAS ici, et c’est tout l’intérêt : préfixes, largeur de - la référence, largeur de la charge utile, décimales et mode de vente sont une - constante du binaire, indexée par préfixe et vérifiée au démarrage. - Un champ qui change le SENS du code lu par la caisse n’est pas un réglage, c’est un - contrat externe : il change avec une version du binaire, relue et testée, jamais - depuis l’écran d’un poste.

                                                                                                                                                                                            `,1),hi=o('

                                                                                                                                                                                            Aucune dérogation : la limite générale s’applique à tous les produits.

                                                                                                                                                                                            '),_i=o(`

                                                                                                                                                                                            Les noms de produits n’ont pas pu être lus : le catalogue en service n’a pas - répondu. Les identifiants Odoo restent affichés.

                                                                                                                                                                                            `),bi=o(`Produit retiré : le garde-fou 14 refuse le produit avant que cette - dérogation ait un sens.`),wi=o('
                                                                                                                                                                                          1. '),yi=o('

                                                                                                                                                                                              ',1),xi=o('
                                                                                                                                                                                              ');function ki(n,t){Ct(t,!0);const s=(g,U=sa,N=sa,K=sa)=>{var _e=Yl(),ne=a(_e),L=r(ne,2),X=a(L),ie=a(X),fe=r(X,2);{var xe=ge=>{var d=Kl(),x=a(d);p(()=>i(x,U())),l(ge,d)};m(fe,ge=>{Gt.showTechnicalNames&&ge(xe)})}var He=r(fe,2);{var Xe=ge=>{var d=Xl(),x=a(d);p(()=>i(x,K())),l(ge,d)};m(He,ge=>{K()!==""&&ge(Xe)})}p((ge,d)=>{Be(_e,"data-flag",U()),Be(_e,"data-on",ge),ua(ne,d),i(ie,N())},[()=>String(t.draft.flag(U())),()=>t.draft.flag(U())]),et("change",ne,ge=>t.draft.set(U(),ge.currentTarget.checked)),l(g,_e)},c=20,_=u(()=>le(t.draft)),w=u(()=>t.health.state.diagnostics),C=u(()=>t.health.decisions.filter(g=>g.min_weight_g!==null)),O=u(()=>e(C).filter(g=>!g.offered)),A=u(()=>e(C).slice(0,c));let W=F(Lt({})),G=F("loading");B();function le(g){const U=g.value("pricing.tiers");return Array.isArray(U)?U.map(N=>{const K=N??{},_e=K.discount_percent;return{code:String(K.code??""),label:String(K.label??""),abbrev:String(K.abbrev??""),written:_e===void 0?null:String(_e),discount:we(_e)?_e:null,rank:Number(K.rank??0)}}):[]}function we(g){return typeof g!="number"||!Number.isFinite(g)||g<0||g>100?!1:Math.abs(g*10-Math.round(g*10))<1e-9}const ke=u(()=>String(t.draft.value("pricing.reference_code")??""));function ue(g){return g===null?"":String(g).replace(".",",")}function j(g){const U=1e3-Math.round(g*10);return`${String(Math.trunc(U/100))},${String(U%100).padStart(2,"0")}`}function de(g,U){const N=Number(U);U.trim()===""||Number.isNaN(N)||t.draft.set(g,N)}function J(g,U){const N=U.trim().replace(",",".");if(!/^\d{1,3}(\.\d)?$/u.test(N))return;const K=Number(N);K>100||t.draft.set(g,K)}function ye(g,U){!(g instanceof HTMLInputElement)||g.value===U||(g.value=U)}async function B(){try{const g=await Ba();v(W,Object.fromEntries(g.products.map(U=>[U.id,U.name])),!0),v(G,"read")}catch{v(G,"unread")}}function $(g){const U=e(W)[g];return U!==void 0?U:e(G)==="loading"?"Lecture du nom…":e(G)==="read"?"Produit absent du catalogue en service":"Nom non lu"}function Z(g){return g<=1?`${String(g)} tarif déclaré`:`${String(g)} tarifs déclarés`}const D=u(()=>`${he(e(w).length)} ${e(w).length>1?"verdicts":"verdict"} sur les quatorze garde-fous.`),z=u(()=>`${he(e(A).length)} ${e(A).length>1?"lignes affichées":"ligne affichée"} sur ${he(e(C).length)} `+(e(O).length===0?`${e(C).length>1?"dérogations en vigueur":"dérogation en vigueur"}.`:`${e(C).length>1?"dérogations enregistrées":"dérogation enregistrée"}, dont ${he(e(O).length)} sur un produit retiré, sans effet : le garde-fou 14 refuse le produit avant que le 8 ait un sens.`)+(e(C).length>e(A).length?" Les autres se lisent produit par produit depuis l’onglet Catalogue.":""));function re(g){return`plancher ${he(g)} g : refusé à ${he(g)} g et en dessous`}const S=[{rank:1,code:"OVERLOAD",label:"Surcharge",when:"La balance annonce elle-même OL, ou le poids brut dépasse la capacité.",severity:"Bloquant",blocking:!0,message:"La balance est en surcharge. Retirez votre article.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la capacité, réglée au garde-fou 9 sous « ${Ft("limits.max_weight_g")} ».`},{rank:2,code:"MEASUREMENT_EXPIRED",label:"Poids périmé",when:"La mesure est plus vieille que la péremption, dans les deux modes de stabilité.",severity:"Bloquant",blocking:!0,message:"Poids indisponible. Patientez ou appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil à régler : le poste calcule lui-même à partir du rythme de la balance."},{rank:3,code:"BASKET_MISSING",label:"Panier absent",when:"Le poids brut tombe dans la fenêtre négative du panier : il a été soulevé.",severity:"Bloquant",blocking:!0,message:"Le panier n'est pas sur la balance. Reposez-le.",thresholds:[{path:"limits.basket_min_g",label:"Bas de la fenêtre du panier",hint:"En grammes, NÉGATIF : c’est le poids du panier que la balance a perdu."},{path:"limits.basket_max_g",label:"Haut de la fenêtre du panier",hint:"Négatif lui aussi, et plus proche de zéro que le bas."}],switchPath:"limits.basket_check_enabled",switchLabel:"Ce poste travaille avec un panier taré",note:"La règle s’active ou non, en bloc : il n’y a pas de demi-mesure à régler."},{rank:4,code:"SCALE_EMPTY",label:"Plateau vide",when:"Le poids brut ne sort pas de la bande « il n’y a rien sur le plateau ».",severity:"Bloquant — un filet, hors parcours nominal",blocking:!0,message:"Posez votre produit.",thresholds:[{path:"limits.empty_max_g",label:"Plateau considéré vide",hint:"En dessous, le poste considère qu’il n’y a rien sur le plateau."}],switchPath:"",switchLabel:"",note:"Toucher une tuile sur un plateau vide ARME la sélection au lieu d’être refusé : la règle reste évaluée pour la saisie manuelle et les chemins dérivés."},{rank:5,code:"TARE_REQUIRED",label:"Remise à zéro nécessaire",when:"Le brut est sous la bande du plateau vide, et hors de la fenêtre du panier.",severity:"Bloquant",blocking:!0,message:"La balance doit être remise à zéro.",thresholds:[],switchPath:"",switchLabel:"",note:`Seuil : la valeur négative de celui du garde-fou 4, « ${Ft("limits.empty_max_g")} ».`},{rank:6,code:"WEIGHT_UNSTABLE",label:"Pesée instable",when:"La trame déclare la mesure instable.",severity:"Information par défaut (A3)",blocking:!1,message:"Pesée en cours…",thresholds:[],switchPath:"",switchLabel:"",note:"La sévérité suit l’exigence de stabilité : elle passe à Bloquant quand celle-ci est réglée sur « blocking ». L’impression n’est jamais bloquée par défaut."},{rank:7,code:"TARE_INVALID",label:"Emballage incohérent",when:"Une tare a été saisie, et elle atteint la pesée ou dépasse le maximum.",severity:"Bloquant",blocking:!0,message:"Le poids de l'emballage est supérieur ou égal à la pesée.",thresholds:[{path:"limits.max_tare_g",label:"Tare maximum",hint:"Une tare plus lourde que le maximum est une faute de frappe."}],switchPath:"",switchLabel:"",note:""},{rank:8,code:"WEIGHT_TOO_LOW",label:"Poids trop faible",when:"Vente au poids : le NET est strictement positif et ne dépasse pas le plancher.",severity:"Bloquant",blocking:!0,message:"La balance doit être retarée, ou l'emballage est trop lourd.",thresholds:[{path:"limits.min_weight_g",label:"Poids minimum",hint:"Une dérogation par produit existe, dans l’onglet Catalogue."}],switchPath:"",switchLabel:"",note:""},{rank:9,code:"WEIGHT_TOO_HIGH",label:"Poids trop élevé",when:"Le NET dépasse la capacité — strictement, pour que la capacité reste atteignable.",severity:"Bloquant",blocking:!0,message:"{{.Weight}} kg, ça paraît un peu lourd !",thresholds:[{path:"limits.max_weight_g",label:"Poids maximum",hint:"C’est la capacité du champ NNDDD du code-barres, pas un seuil de vraisemblance."}],switchPath:"",switchLabel:"",note:""},{rank:10,code:"UNITS_OUT_OF_RANGE",label:"Nombre d’unités hors plage",when:"Vente à l’unité : la quantité sort de la plage.",severity:"Bloquant",blocking:!0,message:"{{.Quantity}} unités, ça paraît un peu beaucoup !",thresholds:[{path:"limits.min_units",label:"Unités minimum",hint:""},{path:"limits.max_units",label:"Unités maximum",hint:""}],switchPath:"",switchLabel:"",note:""},{rank:11,code:"AMOUNT_OUT_OF_CAPACITY",label:"Montant hors capacité du code-barres",when:"La charge utile encode un PRIX, et il dépasse ce que le champ peut porter.",severity:"Bloquant",blocking:!0,message:"Prix trop élevé pour le code-barres.",thresholds:[{path:"limits.max_amount_cents",label:"Montant maximum",hint:"En centimes. Aucun préfixe du plan livré n’encode un prix : la règle est éprouvée sans qu’aucun produit puisse l’atteindre."}],switchPath:"",switchLabel:"",note:""},{rank:12,code:"ZERO_PRICE",label:"Prix nul",when:"Le montant du tarif imprimé en grand vaut zéro.",severity:"Bloquant",blocking:!0,message:"Prix nul. Appelez un bénévole.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : un produit à 0 € est une anomalie sans nuance."},{rank:13,code:"LIGHT_PRODUCT_ALLOWED",label:"Produit léger autorisé",when:"Le garde-fou 8 n’a pas déclenché grâce à la dérogation du produit.",severity:"Information",blocking:!1,message:"",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil général : c’est la dérogation par produit, listée plus bas et posée depuis l’onglet Catalogue. Rien ne s’affiche au client ; l’id du produit est journalisé."},{rank:14,code:"PRODUCT_WITHDRAWN",label:"Produit retiré",when:"Quelqu’un a décidé de ne plus proposer ce produit.",severity:"Bloquant",blocking:!0,message:"Ce produit n'est pas disponible.",thresholds:[],switchPath:"",switchLabel:"",note:"Aucun seuil : c’est une décision humaine, prise depuis l’onglet Catalogue. Aucune règle d’import ne peut la déduire."}];function y(g){return S.find(U=>U.code===g)?.label??"Garde-fou inconnu de cet écran"}const P=["{{.Weight}}","{{.Quantity}}"];function I(g){return g==="blocking"?"Bloquant":g==="info"?"Information":"Sévérité inconnue de cet écran"}var Q=xi(),pe=a(Q);Qe(pe,{title:"Grille de tarifs",note:"Un second tarif n’est pas une case à cocher : c’est une ligne de plus dans cette grille.",children:(g,U)=>{var N=ri(),K=H(N);{var _e=ie=>{var fe=Ql();l(ie,fe)},ne=ie=>{var fe=ai(),xe=H(fe),He=a(xe),Xe=r(xe,2),ge=a(Xe),d=r(a(ge));rt(d,21,()=>e(_),fa,(x,ee,f)=>{var q=ti(),E=a(q),be=a(E),se=r(E),Se=a(se);Be(Se,"aria-label",`Libellé du tarif ${f+1}`);var Le=r(se),ce=a(Le);Be(ce,"aria-label",`Abrégé du tarif ${f+1}`);var V=r(Le),Ue=a(V);{var Ve=ve=>{var ze=Zl();l(ve,ze)},Ee=ve=>{var ze=$a(),Oe=a(ze);p(()=>i(Oe,`${e(ee).written??""} — le tarif de référence est le prix du catalogue : il - ne peut pas porter de remise, et l’enregistrement la refusera.`)),l(ve,ze)},Te=ve=>{var ze=$a(),Oe=a(ze);p(()=>i(Oe,`${e(ee).written??""} — une remise s’écrit au dixième de point ; celle-ci se - change dans le fichier de configuration.`)),l(ve,ze)},Je=ve=>{var ze=ei(),Oe=H(ze);Be(Oe,"aria-label",`Remise du tarif ${f+1}`);var nt=r(Oe,2),ut=a(nt);p((pt,je)=>{na(Oe,pt),i(ut,`un produit à 10,00 €/kg s’affiche ${je??""} €/kg`)},[()=>ue(e(ee).discount??0),()=>j(e(ee).discount??0)]),et("input",Oe,pt=>J(`pricing.tiers.${String(f)}.discount_percent`,pt.currentTarget.value)),et("focusout",Oe,pt=>ye(pt.currentTarget,ue(e(ee).discount??0))),l(ve,ze)};m(Ue,ve=>{e(ee).code===e(ke)&&e(ee).written===null?ve(Ve):e(ee).code===e(ke)?ve(Ee,1):e(ee).written!==null&&e(ee).discount===null?ve(Te,2):ve(Je,-1)})}var ae=r(V),Ie=a(ae);p(()=>{i(be,e(ee).code),na(Se,e(ee).label),na(ce,e(ee).abbrev),i(Ie,e(ee).rank)}),et("input",Se,ve=>t.draft.set(`pricing.tiers.${String(f)}.label`,ve.currentTarget.value)),et("input",ce,ve=>t.draft.set(`pricing.tiers.${String(f)}.abbrev`,ve.currentTarget.value)),l(x,q)}),p(x=>i(He,`${x??""}.`),[()=>Z(e(_).length)]),l(ie,fe)};m(K,ie=>{e(_).length===0?ie(_e):ie(ne,-1)})}var L=r(K,2);{let ie=u(()=>t.draft.text("pricing.primary_code"));_t(L,{label:"Tarif imprimé en grand",path:"pricing.primary_code",get value(){return e(ie)},hint:"Le prix que le client lit sur l’étiquette (A7).",onchange:fe=>t.draft.set("pricing.primary_code",fe)})}var X=r(L,2);{let ie=u(()=>t.draft.text("pricing.reference_code"));_t(X,{label:"Tarif qui serait encodé si le code-barres portait un prix",path:"pricing.reference_code",get value(){return e(ie)},hint:"Le plan livré ne porte pas de prix, mais un poids ou un nombre d’unités : la caisse retrouve le prix par la référence, dans Odoo, et ne doit jamais sous-facturer.",onchange:fe=>t.draft.set("pricing.reference_code",fe)})}l(g,N)},$$slots:{default:!0}});var De=r(pe,2);Qe(De,{title:"Les deux arrondis",children:(g,U)=>{var N=ni(),K=H(N);{let ne=u(()=>t.draft.text("pricing.unit_price_rounding"));_t(K,{label:"Arrondi du prix unitaire dérivé",path:"pricing.unit_price_rounding",get value(){return e(ne)},hint:"Il s’applique au prix au kilo calculé par la remise.",onchange:L=>t.draft.set("pricing.unit_price_rounding",L)})}var _e=r(K,2);{let ne=u(()=>t.draft.text("pricing.amount_rounding"));_t(_e,{label:"Arrondi du montant",path:"pricing.amount_rounding",get value(){return e(ne)},hint:"Il s’applique au montant de l’étiquette.",onchange:L=>t.draft.set("pricing.amount_rounding",L)})}l(g,N)},$$slots:{default:!0}});var Re=r(De,2);Qe(Re,{title:"Les quatorze garde-fous, dans l’ordre d’évaluation",note:"Le seuil se modifie ici. La sévérité ne se règle pas : elle dit si le poste refuse ou avertit, et cela ne dépend pas du magasin. Le message affiché au client n’est pas encore modifiable depuis cet écran.",children:(g,U)=>{var N=gi(),K=r(H(N),2);rt(K,21,()=>S,fe=>fe.code,(fe,xe)=>{var He=ui(),Xe=a(He),ge=a(Xe),d=a(ge),x=r(ge,2),ee=a(x),f=r(x,2),q=a(f),E=r(f,2),be=a(E),se=r(Xe,2),Se=a(se),Le=r(se,2);{var ce=ae=>{var Ie=si();l(ae,Ie)},V=ae=>{var Ie=li(),ve=a(Ie);p(()=>i(ve,`« ${e(xe).message??""} »`)),l(ae,Ie)};m(Le,ae=>{e(xe).message===""?ae(ce):ae(V,-1)})}var Ue=r(Le,2);{var Ve=ae=>{s(ae,()=>e(xe).switchPath,()=>e(xe).switchLabel,()=>"")};m(Ue,ae=>{e(xe).switchPath!==""&&ae(Ve)})}var Ee=r(Ue,2);rt(Ee,17,()=>e(xe).thresholds,ae=>ae.path,(ae,Ie)=>{var ve=ii(),ze=a(ve);{let Oe=u(()=>t.draft.text(e(Ie).path));_t(ze,{get label(){return e(Ie).label},get path(){return e(Ie).path},kind:"number",get value(){return e(Oe)},get hint(){return e(Ie).hint},onchange:nt=>de(e(Ie).path,nt)})}et("focusout",ve,Oe=>ye(Oe.target,t.draft.text(e(Ie).path))),l(ae,ve)});var Te=r(Ee,2);{var Je=ae=>{var Ie=oi(),ve=a(Ie);p(()=>i(ve,e(xe).note)),l(ae,Ie)};m(Te,ae=>{e(xe).note!==""&&ae(Je)})}p(ae=>{Be(He,"data-code",e(xe).code),i(d,e(xe).rank),i(ee,e(xe).label),i(q,e(xe).code),Be(E,"data-blocking",ae),i(be,e(xe).severity),i(Se,e(xe).when)},[()=>String(e(xe).blocking)]),l(fe,He)});var _e=r(K,4),ne=a(_e),L=r(_e,4);{var X=fe=>{var xe=ci();l(fe,xe)},ie=fe=>{var xe=fi(),He=H(xe),Xe=a(He),ge=r(He,2);rt(ge,21,()=>e(w),d=>d.code,(d,x)=>{var ee=pi(),f=a(ee),q=a(f),E=r(f,2),be=a(E),se=r(E,2),Se=a(se),Le=r(se,2);{var ce=Ve=>{var Ee=di(),Te=a(Ee);p(()=>i(Te,`« ${e(x).message??""} »`)),l(Ve,Ee)};m(Le,Ve=>{e(x).message!==""&&Ve(ce)})}var V=r(Le,2);{var Ue=Ve=>{var Ee=vi(),Te=H(Ee),Je=a(Te),ae=r(Te,2),Ie=a(ae);p(ve=>{i(Je,ve),i(Ie,e(x).product_id)},[()=>$(e(x).product_id)]),l(Ve,Ee)};m(V,Ve=>{e(x).product_id!==""&&Ve(Ue)})}p((Ve,Ee,Te)=>{Be(ee,"data-blocking",Ve),i(q,Ee),i(be,e(x).code),i(Se,Te)},[()=>String(e(x).blocking),()=>y(e(x).code),()=>I(e(x).severity)]),l(d,ee)}),p(d=>{Be(He,"data-verdicts",d),i(Xe,e(D))},[()=>String(e(w).length)]),l(fe,xe)};m(L,fe=>{e(w).length===0?fe(X):fe(ie,-1)})}p(fe=>i(ne,`Les marqueurs ${fe??""} sont remplacés par les valeurs de la pesée au - moment où le message s’affiche.`),[()=>P.join(" et ")]),l(g,N)},$$slots:{default:!0}});var b=r(Re,2);Qe(b,{title:"Code-barres",note:"Un seul réglage, et c’est voulu : le reste du plan de numérotation n’est pas de la configuration.",children:(g,U)=>{var N=mi(),K=H(N);s(K,()=>"barcode.verify_reference_check_digit",()=>"Refuser une référence dont la clé de contrôle est fausse",()=>"Décoché, le poste recalcule une clé juste sur une référence fausse, en silence — et la caisse encaisse un autre article."),l(g,N)},$$slots:{default:!0}});var T=r(b,2);Qe(T,{title:"Dérogations de poids minimum",note:"En lecture ici ; elles se modifient depuis l’onglet Catalogue, là où se trouve le produit.",children:(g,U)=>{var N=Dt(),K=H(N);{var _e=L=>{var X=hi();l(L,X)},ne=L=>{var X=yi(),ie=H(X),fe=a(ie),xe=r(ie,2);{var He=ge=>{var d=_i();l(ge,d)};m(xe,ge=>{e(G)==="unread"&&ge(He)})}var Xe=r(xe,2);rt(Xe,21,()=>e(A),ge=>ge.product_id,(ge,d)=>{var x=wi(),ee=a(x),f=a(ee),q=r(ee,2),E=a(q),be=r(q,2),se=a(be),Se=r(be,2),Le=a(Se),ce=r(Se,2),V=a(ce),Ue=r(ce,2);{var Ve=Ee=>{var Te=bi();l(Ee,Te)};m(Ue,Ee=>{e(d).offered||Ee(Ve)})}p((Ee,Te,Je,ae)=>{Be(x,"data-withdrawn",Ee),i(f,Te),i(E,e(d).product_id),i(se,Je),i(Le,e(d).reason),i(V,`${ae??""}, ${e(d).decided_by??""}`)},[()=>String(!e(d).offered),()=>$(e(d).product_id),()=>re(e(d).min_weight_g??0),()=>da(e(d).decided_at)]),l(ge,x)}),p(()=>i(fe,e(z))),l(L,X)};m(K,L=>{e(C).length===0?L(_e):L(ne,-1)})}l(g,N)},$$slots:{default:!0}}),l(n,Q),Rt()}Tt(["change","input","focusout"]);function qi(n,t){const s=[];for(const c of[...ya(n),...ya(t)]){if(s.some(C=>C.path===c))continue;const _=Da(va(n,c)),w=Da(va(t,c));_!==w&&s.push({path:c,before:_,after:w})}return s}function va(n,t){let s=n;for(const c of t.split(".")){if(s===null||typeof s!="object")return;s=s[c]}return s}function ya(n,t=""){if(n===null||typeof n!="object"||Array.isArray(n))return t===""?[]:[t];const s=[];for(const[c,_]of Object.entries(n))s.push(...ya(_,t===""?c:`${t}.${c}`));return s}function Da(n){return n===void 0?"—":n===null?"vide":typeof n=="boolean"?n?"oui":"non":typeof n=="object"?JSON.stringify(n):String(n)}var Si=o('
                                                                                                                                                                                              Empreinte de la configuration en service
                                                                                                                                                                                              Version du binaire
                                                                                                                                                                                              Répertoire de données
                                                                                                                                                                                              Espace disque
                                                                                                                                                                                              ',1),Li=o('

                                                                                                                                                                                              '),Ia=o(" "),Ei=o(' '),zi=o("
                                                                                                                                                                                            • "),ji=o(`

                                                                                                                                                                                                Recopier reste possible : les valeurs entrent dans le brouillon, où elles se - corrigent champ par champ avant l’enregistrement.

                                                                                                                                                                                                `),Pi=o(`

                                                                                                                                                                                                Ce fichier n’a été comparé à rien : la configuration en service n’a pas pu être - lue. Ni « identique », ni « n champs changent » — ce que ce fichier changerait - sur ce poste reste inconnu.

                                                                                                                                                                                                `),Ti=o(`

                                                                                                                                                                                                Ce fichier décrit la même configuration que celle en service : il n’y a rien à - recopier. C’est ce qu’on veut lire à la fin d’un clonage. Deux champs ne sont pas - comparés : la date du dernier enregistrement, que chaque poste écrit lui-même, et - le mot de passe du catalogue, qu’aucun des deux ne porte en clair.

                                                                                                                                                                                                `),Ci=o(' '),Ri=o('

                                                                                                                                                                                                '),Oi=o(`

                                                                                                                                                                                                ChampEn serviceDans le fichier

                                                                                                                                                                                                Recopier n’applique rien : les valeurs entrent dans le brouillon, et c’est - « Enregistrer » qui les met en service.

                                                                                                                                                                                                `,1),Ai=o('

                                                                                                                                                                                                ',1),Ni=o(`

                                                                                                                                                                                                L’export emporte encore l’empreinte du mot de passe : c’est la seule lecture que le - poste garde derrière la clé. L’import, lui, est lu PAR LE POSTE, qui écarte les deux - secrets et le numéro de poste avant de dire ce qui changerait.

                                                                                                                                                                                                `,1),$i=o('

                                                                                                                                                                                                Lecture des versions enregistrées…

                                                                                                                                                                                                '),Di=o(`

                                                                                                                                                                                                Les versions enregistrées n’ont pas pu être lues : cette liste ne dit donc rien de - ce que ce poste garde. Ce n’est pas « aucune version ».

                                                                                                                                                                                                `),Ii=o('

                                                                                                                                                                                                Aucune version enregistrée : ce poste n’a jamais été reconfiguré.

                                                                                                                                                                                                '),Ui=o('
                                                                                                                                                                                              • '),Fi=o(`

                                                                                                                                                                                                  Remettre une version en service remplace la configuration du poste sur-le-champ, et - ce qui n’a pas été enregistré est perdu : c’est le seul geste de cette page qui - change le poste tout de suite, et le seul qui garde ses 72 px.

                                                                                                                                                                                                  `,1),Mi=o('
                                                                                                                                                                                                  ');function Bi(n,t){Ct(t,!0);const s=vt(t,"admin",7),c=40,_=20,w=5,C=new Set(["modified_at","catalog.options.password"]),O=[{path:"station.name",name:"le nom du poste"},{path:"network.listen",name:"l’adresse d’écoute"},{path:"scale.options.port",name:"le port de la balance"},{path:"printer.options.queue",name:"la file d’impression"},{path:"catalog.options.url",name:"l’adresse du partage"},{path:"catalog.options.username",name:"le compte du partage"},{path:"catalog.images.path",name:"le chemin des images"}];let A=F(Lt([])),W=F("reading"),G=F(null),le=F(""),we=F(null),ke=F(""),ue=F(Lt([])),j=F(""),de="";const J=u(()=>s().busy||e(j)!==""),ye=u(()=>I(e(G),e(we))),B=u(()=>e(G)!==null&&e(we)!==null),$=u(()=>Q(e(G),e(we))),Z=u(()=>e(ye).slice(0,c)),D=u(()=>e(ue).slice(0,_)),z=u(()=>e(A).slice(0,w)),re=u(()=>Xe(e(Z).length,e(ye).length,"champ qui change","champs qui changent")),S=u(()=>Xe(e(D).length,e(ue).length,"contrôle refuse une clé","contrôles refusent une clé")),y=u(()=>Xe(e(z).length,e(A).length,"version enregistrée","versions enregistrées")),P=u(()=>e(ye).length>1?`Recopier ces ${he(e(ye).length)} champs dans le brouillon`:"Recopier ce champ dans le brouillon");Re();function I(f,q){return f===null||q===null?[]:qi(f,q).filter(E=>!C.has(E.path))}function Q(f,q){if(f===null||q===null)return[];const E=f,be=q;return O.filter(se=>pe(be,se.path)&&!pe(E,se.path))}function pe(f,q){const E=va(f,q);return E==null||E===""?!0:typeof E!="object"||Array.isArray(E)?!1:Object.keys(E).length===0}function De(f){return f.length<2?f.join(""):`${f.slice(0,-1).join(", ")} et ${f[f.length-1]??""}`}aa(()=>{const f=t.health.config_fingerprint;f!==de&&(de=f,b())});async function Re(){const f=await s().load(Ar);if(f===null){v(W,"unreadable");return}v(A,f,!0),v(W,"read")}async function b(){try{v(G,(await qa()).config,!0),v(le,"")}catch(f){v(G,null),v(le,f instanceof Error?f.message:"Le poste n’a pas répondu.",!0)}}async function T(f){v(j,f?"export-all":"export-clone",!0),s().notice="",s().actionError="";try{const q=await s().protect(()=>g(f));if(q===null)return;K(q.name,q.blob),s().notice=`${q.name} est remis au navigateur : c’est lui qui l’enregistre, voyez ses téléchargements.`}finally{v(j,"")}}async function g(f){const E=await fetch(`/admin/api/config/export?hardware=${f?"1":"0"}`,{headers:{accept:"application/json"}});if(!E.ok)throw new It(E.status,fe(await E.text(),"L’export"));return{name:U(f),blob:await E.blob()}}function U(f){const q=f?"":"-sans-materiel";return`config-poste${String(t.health.station)}${q}-${N()}.json`}function N(){const f=new Date,q=String(f.getMonth()+1).padStart(2,"0"),E=String(f.getDate()).padStart(2,"0");return`${String(f.getFullYear())}-${q}-${E}`}function K(f,q){const E=URL.createObjectURL(q),be=document.createElement("a");be.href=E,be.download=f,document.body.appendChild(be),be.click(),be.remove(),setTimeout(()=>{URL.revokeObjectURL(E)},0)}async function _e(f){const q=await fetch("/admin/api/config/import",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)}),E=await q.text();if(!q.ok)throw new It(q.status,fe(E,"L’import"));return JSON.parse(E)}async function ne(f){if(f==null)return;s().notice="",s().actionError="";let q;try{q=JSON.parse(await f.text())}catch{s().actionError=`${f.name} n’est pas un fichier JSON lisible.`;return}v(j,"import");try{const E=await s().protect(()=>_e(q));if(E===null)return;v(we,E.config,!0),v(ke,f.name,!0),v(ue,E.faults,!0),await b(),s().notice=L(f.name)}finally{v(j,"")}}function L(f){return e(B)?e(ye).length===0?`${f} décrit la même configuration que celle en service.`:`${f} est lu. Rien n’est appliqué : relisez le tableau.`:`${f} est lu, mais la configuration en service ne l’est pas : rien ne peut être comparé.`}async function X(){if(e(we)===null)return;const f=e(we),q=[...e(ye)];for(const E of q)t.draft.set(E.path,va(f,E.path));s().actionError="",s().notice="Le fichier est recopié dans le brouillon. Rien n’est appliqué avant « Enregistrer ».",await b()}async function ie(f){v(j,`restore-${String(f)}`),s().notice="",s().actionError="";try{if(await s().protect(()=>Nr(f))===null)return;await t.draft.load(),await Re(),await b(),await s().refresh(),s().notice=`La version ${String(f)} est remise en service.`}finally{v(j,"")}}function fe(f,q){try{const E=JSON.parse(f);if(typeof E?.message=="string"&&E.message!=="")return E.message}catch{}return`${q} a été refusé par le poste.`}function xe(f){return t.draft.faults.find(q=>q.field===f)?.message??""}function He(f){return t.draft.faults.find(q=>q.field===f)?.allowed??[]}function Xe(f,q,E,be){const se=q>1?be:E;return f>=q?`${he(q)} ${se}.`:`${he(f)} lignes affichées sur ${he(q)} ${se}.`}var ge=Mi(),d=a(ge);Qe(d,{title:"Identité du poste",children:(f,q)=>{var E=Si(),be=H(E);{let ze=u(()=>t.draft.text("station.number")),Oe=u(()=>xe("station.number")),nt=u(()=>He("station.number"));_t(be,{label:"Numéro du poste",path:"station.number",kind:"number",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},hint:"C’est de lui que dérive le nom du fichier de catalogue attendu, flv_.csv.",onchange:ut=>t.draft.set("station.number",Number(ut))})}var se=r(be,2);{let ze=u(()=>t.draft.text("station.name")),Oe=u(()=>xe("station.name")),nt=u(()=>He("station.name"));_t(se,{label:"Nom du poste",path:"station.name",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},hint:"Ce que lit un bénévole : « Poste 2 — fruits ».",onchange:ut=>t.draft.set("station.name",ut)})}var Se=r(se,2);{let ze=u(()=>t.draft.text("station.coop")),Oe=u(()=>xe("station.coop")),nt=u(()=>He("station.coop"));_t(Se,{label:"Coopérative",path:"station.coop",get value(){return e(ze)},get fault(){return e(Oe)},get allowed(){return e(nt)},onchange:ut=>t.draft.set("station.coop",ut)})}var Le=r(Se,2),ce=r(a(Le),2),V=a(ce),Ue=r(ce,4),Ve=a(Ue),Ee=r(Ue,4),Te=a(Ee),Je=r(Ee,4),ae=a(Je);{var Ie=ze=>{var Oe=yt("non mesuré");l(ze,Oe)},ve=ze=>{var Oe=yt();p((nt,ut,pt)=>i(Oe,`${nt??""} libres sur - ${ut??""} — seuil d’alerte - ${pt??""} Mo`),[()=>wa(t.health.disk.free_bytes),()=>wa(t.health.disk.total_bytes),()=>he(t.health.disk.alert_mb)]),l(ze,Oe)};m(ae,ze=>{t.health.disk===null?ze(Ie):ze(ve,-1)})}p(()=>{i(V,t.health.config_fingerprint),i(Ve,t.health.version),i(Te,t.health.disk===null?"non publié par ce poste":t.health.disk.path)}),l(f,E)},$$slots:{default:!0}});var x=r(d,2);Qe(x,{title:"Exporter, importer",note:"Pour installer un autre poste : ce fichier emporte les tarifs, les garde-fous, l’étiquette, les catégories, et les réglages du matériel que les quatre postes partagent — le décalage d’étiquette, le noircissement, la vitesse, le débit de la balance. Reste ici ce qui désigne ce poste-ci ou ce magasin — le mot de passe, le code de secours, le numéro et le nom du poste, le port de la balance, la file d’impression, l’adresse du partage et son compte, le chemin des images et le réseau.",children:(f,q)=>{var E=Ni(),be=H(E),se=a(be);{let ae=u(()=>e(j)==="export-all");at(se,{label:"Exporter tout",protected:!0,get busy(){return e(ae)},get disabled(){return e(J)},onrun:()=>{T(!0)}})}var Se=r(se,2);{let ae=u(()=>e(j)==="export-clone");at(Se,{label:"Exporter sans le matériel",protected:!0,get busy(){return e(ae)},get disabled(){return e(J)},onrun:()=>{T(!1)}})}var Le=r(Se,2);let ce;var V=a(Le),Ue=r(V,3),Ve=r(be,4);{var Ee=ae=>{var Ie=Li(),ve=a(Ie);p(()=>i(ve,`La configuration en service n’a pas pu être lue : ${e(le)??""} La colonne - « En service » ne peut donc rien affirmer.`)),l(ae,Ie)};m(Ve,ae=>{e(le)!==""&&ae(Ee)})}var Te=r(Ve,2);{var Je=ae=>{var Ie=Ai(),ve=H(Ie),ze=a(ve),Oe=r(ve,2);{var nt=Ze=>{var bt=ji(),Ot=a(bt),Ut=a(Ot),k=r(Ot,2);rt(k,21,()=>e(D),fa,(Y,oe)=>{var Ae=zi(),We=a(Ae),Ye=a(We),Ke=r(We,2);{var M=qe=>{var $e=Ia(),Pe=a($e);p(()=>i(Pe,e(oe).field)),l(qe,$e)};m(Ke,qe=>{Gt.showTechnicalNames&&qe(M)})}var te=r(Ke),me=r(te);{var Ne=qe=>{var $e=Ei(),Pe=a($e);p(Fe=>i(Pe,`Valeurs acceptées : ${Fe??""}.`),[()=>e(oe).allowed.join(", ")]),l(qe,$e)};m(me,qe=>{e(oe).allowed!==void 0&&e(oe).allowed.length>0&&qe(Ne)})}p(qe=>{i(Ye,qe),i(te,` ${e(oe).message??""} `)},[()=>Ft(e(oe).field)]),l(Y,Ae)}),p(()=>i(Ut,`Ce fichier serait refusé en l’état : ${e(S)??""}`)),l(Ze,bt)};m(Oe,Ze=>{e(ue).length>0&&Ze(nt)})}var ut=r(Oe,2);{var pt=Ze=>{var bt=Pi();l(Ze,bt)},je=Ze=>{var bt=Ti();l(Ze,bt)},st=Ze=>{var bt=Oi(),Ot=H(bt),Ut=a(Ot),k=r(Ut);{var Y=me=>{var Ne=yt("Les autres sont dans le fichier, et « Recopier » les prend tous.");l(me,Ne)};m(k,me=>{e(ye).length>e(Z).length&&me(Y)})}var oe=r(Ot,2),Ae=a(oe),We=r(a(Ae));rt(We,21,()=>e(Z),me=>me.path,(me,Ne)=>{const qe=u(()=>Ft(e(Ne).path));var $e=Ci(),Pe=a($e),Fe=a(Pe),Ge=r(Fe);{var ft=gt=>{var xt=Ia(),Nt=a(xt);p(()=>i(Nt,e(Ne).path)),l(gt,xt)};m(Ge,gt=>{Gt.showTechnicalNames&&e(qe)!==e(Ne).path&>(ft)})}var ot=r(Pe),ct=a(ot),it=r(ot),dt=a(it);p(()=>{Be($e,"data-path",e(Ne).path),i(Fe,`${e(qe)??""} `),i(ct,e(Ne).before),i(dt,e(Ne).after)}),l(me,$e)});var Ye=r(oe,2);{var Ke=me=>{var Ne=Ri(),qe=a(Ne);p($e=>i(qe,`Ce fichier ne porte pas ${$e??""} : - l’export sans le matériel les retire, et l’import ne remet que le numéro du - poste. Les lignes correspondantes sont VIDES ci-dessus, et - « Recopier » recopie ce vide dans le brouillon.`),[()=>De(e($).map($e=>$e.name))]),l(me,Ne)};m(Ye,me=>{e($).length>0&&me(Ke)})}var M=r(Ye,2),te=a(M);at(te,{kind:"write",get label(){return e(P)},get disabled(){return e(J)},onrun:()=>{X()}}),p(()=>i(Ut,`${e(re)??""} `)),l(Ze,bt)};m(ut,Ze=>{e(B)?e(ye).length===0?Ze(je,1):Ze(st,-1):Ze(pt)})}p(()=>i(ze,`Fichier lu : ${e(ke)??""}`)),l(ae,Ie)};m(Te,ae=>{e(we)!==null&&ae(Je)})}p(()=>{ce=Ht(Le,1,"choose svelte-18wbxwu",null,ce,{working:e(j)==="import",off:e(J)}),i(V,`${e(j)==="import"?"Lecture du fichier…":"Importer un fichier"} `),Ue.disabled=e(J)}),et("change",Ue,ae=>{ne(ae.currentTarget.files?.item(0))}),l(f,E)},$$slots:{default:!0}});var ee=r(x,2);Qe(ee,{title:"Cinq versions restaurables",note:"Chaque enregistrement fait tourner les versions : la plus récente est la 1.",children:(f,q)=>{var E=Dt(),be=H(E);{var se=V=>{var Ue=$i();l(V,Ue)},Se=V=>{var Ue=Di();l(V,Ue)},Le=V=>{var Ue=Ii();l(V,Ue)},ce=V=>{var Ue=Fi(),Ve=H(Ue),Ee=a(Ve),Te=r(Ve,2),Je=a(Te);rt(Je,21,()=>e(z),ae=>ae.version,(ae,Ie)=>{var ve=Ui(),ze=a(ve),Oe=a(ze),nt=r(ze,2),ut=a(nt),pt=r(nt,2),je=a(pt),st=r(pt,2);{let Ze=u(()=>e(j)===`restore-${String(e(Ie).version)}`);at(st,{kind:"destructive",label:"Remettre cette version en service",protected:!0,get busy(){return e(Ze)},get disabled(){return e(J)},onrun:()=>{ie(e(Ie).version)}})}p((Ze,bt)=>{i(Oe,`version ${Ze??""}`),i(ut,bt),i(je,e(Ie).config_fingerprint)},[()=>he(e(Ie).version),()=>Mt(e(Ie).modified_at)]),l(ae,ve)}),p(()=>i(Ee,`${e(y)??""} Le poste n’en garde jamais plus de cinq.`)),l(V,Ue)};m(be,V=>{e(W)==="reading"?V(se):e(W)==="unreadable"?V(Se,1):e(A).length===0?V(Le,2):V(ce,-1)})}l(f,E)},$$slots:{default:!0}}),l(n,ge),Rt()}Tt(["change"]);var Vi=o('clé'),Wi=o(' '),Gi=o('');function Xt(n,t){const s=vt(t,"kind",3,"read"),c=vt(t,"hint",3,""),_=vt(t,"disabled",3,!1),w=vt(t,"engaged",3,!1),C=vt(t,"busy",3,!1),O=vt(t,"protected",3,!1);var A=Gi();let W;var G=a(A),le=a(G),we=r(le);{var ke=de=>{var J=Vi();l(de,J)};m(we,de=>{O()&&de(ke)})}var ue=r(G,2);{var j=de=>{var J=Wi(),ye=a(J);p(()=>i(ye,C()?"En cours…":c())),l(de,J)};m(ue,de=>{c()!==""&&de(j)})}p(()=>{W=Ht(A,1,`big touch-target ${s()??""}`,"svelte-15nlt6i",W,{engaged:w(),busy:C()}),Be(A,"data-kind",s()),A.disabled=_()||C(),i(le,`${t.label??""} `)}),et("click",A,function(...de){t.onrun?.apply(this,de)}),l(n,A)}Tt(["click"]);const Hi="ERR-SCL-09";var Ji=o('

                                                                                                                                                                                                  '),Ki=o('

                                                                                                                                                                                                  '),Xi=o('

                                                                                                                                                                                                  Déposez ici le fichier clé

                                                                                                                                                                                                  '),Yi=o('

                                                                                                                                                                                                  ',1),Qi=o('');function Zi(n,t){Ct(t,!0);const s=vt(t,"admin",7);let c=F("");const _=u(()=>t.health.state.degraded?.code===Hi),w=u(()=>t.health.printing),C=u(()=>e(w)?.on_fallback===!0);let O=F(!1),A=F("");const W=u(()=>s().busy||e(A)!=="");async function G(b,T){v(c,""),v(A,b,!0);try{await s().run(T)}finally{v(A,"")}}async function le(b,T){v(c,""),v(A,b,!0);try{const g=await s().load(T);g!==null&&v(c,g.message,!0)}finally{v(A,"")}}async function we(b,T){v(c,""),v(A,b,!0);try{const g=await s().protect(T);g!==null&&(s().notice=g.message,await s().refresh())}finally{v(A,"")}}async function ke(b){if(v(O,!1),b!=null){v(c,""),v(A,"import");try{const T=await s().protect(()=>Ga(b));if(T===null)return;v(c,T.result==="rejected"||T.result==="failed"?`${b.name} : REFUSÉ${T.reason===""?"":" — "+T.reason}. Le catalogue en service n’a pas changé.`:`${b.name} : ${String(T.rows_read_count)} lignes lues, ${String(T.weighable_count)} pesables. La veille l’appliquera dans la seconde.`,!0),await s().refresh()}finally{v(A,"")}}}var ue=Qi(),j=a(ue);{var de=b=>{var T=Ji(),g=a(T);p(()=>i(g,e(c))),l(b,T)};m(j,b=>{e(c)!==""&&b(de)})}var J=r(j,2);{var ye=b=>{var T=Ki(),g=a(T);p(()=>i(g,e(w).banner)),l(b,T)};m(J,b=>{e(w)!==null&&e(w).banner!==""&&b(ye)})}var B=r(J,2),$=a(B);{let b=u(()=>e(A)==="scale");Xt($,{label:"Tester la balance",hint:"Ce que le poste a déjà observé — le port n’est pas rouvert.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{le("scale",Lr)}})}var Z=r($,2);{let b=u(()=>e(A)==="printer");Xt(Z,{label:"Tester l’imprimante",hint:"Ce que le superviseur a vu il y a moins d’une seconde.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{le("printer",Er)}})}var D=r(Z,2);{let b=u(()=>e(A)==="label");Xt(D,{label:"Imprimer une étiquette de test",hint:"Une étiquette de démonstration sort de l’imprimante du poste.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("label",zr)}})}var z=r(D,2);{let b=u(()=>e(A)==="reprint");Xt(z,{label:"Réimprimer la dernière",hint:"La dernière étiquette imprimée sort une seconde fois.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("reprint",yr)}})}var re=r(z,2);{let b=u(()=>e(A)==="reload");Xt(re,{label:"Recharger le catalogue",kind:"write",hint:"La veille refait tout de suite le contrôle qu’elle fait toutes les cinq secondes.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("reload",xr)}})}var S=r(re,2);{let b=u(()=>e(_)?"Revenir à la balance":"Basculer en saisie manuelle"),T=u(()=>e(_)?"Le poids sera de nouveau lu sur la balance.":"Le poids se tape à la main : le poste continue de servir sans balance."),g=u(()=>e(A)==="manual");Xt(S,{get label(){return e(b)},kind:"destructive",get hint(){return e(T)},get engaged(){return e(_)},protected:!0,get busy(){return e(g)},get disabled(){return e(W)},onrun:()=>{we("manual",()=>kr(!e(_)))}})}var y=r(S,2);{let b=u(()=>e(A)==="roll");Xt(y,{label:"J’ai changé le rouleau",kind:"write",hint:"Le compteur d’étiquettes repart à zéro. C’est le seul geste qui dise quelque chose de vrai du papier.",get busy(){return e(b)},get disabled(){return e(W)},onrun:()=>{G("roll",qr)}})}var P=r(y,2);{var I=b=>{{let T=u(()=>e(C)?"Revenir à l’imprimante du poste":"Imprimer sur l’imprimante du poste voisin"),g=u(()=>e(C)?"Les étiquettes repartiront sur l’imprimante de ce poste.":"Les étiquettes sortiront sur l’imprimante voisine, pour cette session seulement."),U=u(()=>e(A)==="fallback");Xt(b,{get label(){return e(T)},kind:"write",get hint(){return e(g)},get engaged(){return e(C)},get busy(){return e(U)},get disabled(){return e(W)},onrun:()=>{G("fallback",()=>Sr(!e(C)))}})}};m(P,b=>{e(w)!==null&&e(w).fallback_available&&b(I)})}var Q=r(P,2),pe=r(B,2);Qe(pe,{title:"Importer un catalogue",note:"Glissez le fichier CSV ici, ou choisissez-le. Il passe par le même chemin que le fichier du producteur.",children:(b,T)=>{var g=Xi();let U;var N=a(g),K=r(a(N)),_e=a(K),ne=r(N,2),L=r(a(ne));p(()=>{U=Ht(g,1,"drop svelte-1yd0nzg",null,U,{dropping:e(O)}),i(_e,`flv_${t.health.station??""}.csv`)}),Qt("dragover",g,X=>{X.preventDefault(),v(O,!0)}),Qt("dragleave",g,()=>v(O,!1)),Qt("drop",g,X=>{X.preventDefault(),ke(X.dataTransfer?.files.item(0))}),et("change",L,X=>{ke(X.currentTarget.files?.item(0))}),l(b,g)},$$slots:{default:!0}});var De=r(pe,2);{var Re=b=>{Qe(b,{title:"Le dernier fichier a été refusé",children:(T,g)=>{var U=Yi(),N=H(U),K=a(N),_e=r(N,2),ne=a(_e);p(L=>{i(K,`${(t.health.catalog.reason===""?"Aucun motif enregistré.":t.health.catalog.reason)??""} - Le catalogue en service n’a pas changé.`),i(ne,`Dernier essai : ${L??""}.`)},[()=>Mt(t.health.catalog.occurred_at)]),l(T,U)},$$slots:{default:!0}})};m(De,b=>{t.health.catalog!==null&&t.health.catalog.result==="rejected"&&b(Re)})}p(()=>Be(Q,"href",jr)),l(n,ue),Rt()}Tt(["change"]);const eo={succeeded:"La dernière mise à jour a réussi.","rolled-back":"La dernière mise à jour a échoué. La version précédente a été remise et le poste fonctionne.","rolled-back-unhealthy":"La dernière mise à jour a échoué et le poste n’a pas redémarré. Appelez le support.","not-started":"La dernière mise à jour n’a pas démarré : rien n’a été remplacé. Vous pouvez réessayer."},to=2e3,ao=300*1e3;var ro=o('

                                                                                                                                                                                                  '),Ua=o('

                                                                                                                                                                                                  '),no=o("
                                                                                                                                                                                                  Dernière vérification
                                                                                                                                                                                                  ",1),so=o(`

                                                                                                                                                                                                  La mise à jour depuis cet écran n’existe que sur les postes Windows. Sur les - autres, elle se fait à la main — voir la notice d’installation.

                                                                                                                                                                                                  `),lo=o('Voir les nouveautés'),io=o('

                                                                                                                                                                                                  Version disponible : .

                                                                                                                                                                                                  '),oo=o('

                                                                                                                                                                                                  Ce poste est à jour.

                                                                                                                                                                                                  '),uo=o("
                                                                                                                                                                                                  Version installée
                                                                                                                                                                                                  Dépôt suivi
                                                                                                                                                                                                  ",1),co=o(`

                                                                                                                                                                                                  Le poste va s’arrêter environ une minute. L’écran client s’éteindra puis reviendra - tout seul. Si la nouvelle version ne démarre pas, la précédente sera remise - automatiquement — mais les données enregistrées, elles, ne reviendront pas en - arrière.

                                                                                                                                                                                                  `),vo=o('

                                                                                                                                                                                                  Mise à jour en cours. Le poste redémarre, ne le débranchez pas.

                                                                                                                                                                                                  '),po=o('
                                                                                                                                                                                                  ',1),fo=o("
                                                                                                                                                                                                  Terminée le
                                                                                                                                                                                                  ",1),go=o("
                                                                                                                                                                                                  Raison
                                                                                                                                                                                                  ",1),mo=o('

                                                                                                                                                                                                  Depuis
                                                                                                                                                                                                  Vers
                                                                                                                                                                                                  ',1),ho=o(" ",1);function _o(n,t){Ct(t,!0);let s=F(null),c=F(""),_=F(""),w=F(!1),C=F("");const O=u(()=>e(s)?.outcome??null),A=u(()=>e(s)?.supported===!0&&e(s).available);aa(()=>{W()});async function W(){try{v(s,await Yr(),!0),v(c,"")}catch(B){v(c,B instanceof Error?B.message:"Lecture impossible.",!0)}}async function G(){v(_,"check");try{const B=await t.admin.protect(()=>Qr());B!==null&&(v(s,B,!0),v(c,""))}finally{v(_,"")}}async function le(B){v(_,"apply"),v(C,"");try{if(await t.admin.protect(()=>Zr(B))===null)return;v(w,!0),v(C,await we()?"":"Le poste n’a pas répondu dans les cinq minutes. Allez le voir.",!0),await W()}finally{v(_,""),v(w,!1)}}async function we(){const B=Date.now()+ao;for(;Date.now()setTimeout($,to));try{if((await fetch("/healthz",{cache:"no-store"})).ok)return!0}catch{}}return!1}var ke=ho(),ue=H(ke);Qe(ue,{title:"Version de ce poste",children:(B,$)=>{var Z=Dt(),D=H(Z);{var z=S=>{var y=ro(),P=a(y);p(()=>i(P,e(c)===""?"Lecture…":e(c))),l(S,y)},re=S=>{var y=uo(),P=H(y);{var I=ne=>{var L=Ua(),X=a(L);p(()=>i(X,e(c))),l(ne,L)};m(P,ne=>{e(c)!==""&&ne(I)})}var Q=r(P,2),pe=r(a(Q),2),De=a(pe),Re=r(pe,4),b=a(Re),T=r(Re,2);{var g=ne=>{var L=no(),X=r(H(L),2),ie=a(X);p(fe=>i(ie,fe),[()=>Mt(e(s).checked_at)]),l(ne,L)};m(T,ne=>{e(s).checked_at!==""&&ne(g)})}var U=r(Q,2);{var N=ne=>{var L=so();l(ne,L)},K=ne=>{var L=io(),X=r(a(L)),ie=a(X),fe=r(X,2);{var xe=ge=>{var d=yt();p(x=>i(d,`, publiée le ${x??""}`),[()=>Mt(e(s).published_at)]),l(ge,d)};m(fe,ge=>{e(s).published_at!==""&&ge(xe)})}var He=r(fe,2);{var Xe=ge=>{var d=lo();p(()=>Be(d,"href",e(s).html_url)),l(ge,d)};m(He,ge=>{e(s).html_url!==""&&ge(Xe)})}p(()=>i(ie,e(s).latest)),l(ne,L)},_e=ne=>{var L=oo();l(ne,L)};m(U,ne=>{e(s).supported?e(s).available?ne(K,1):ne(_e,-1):ne(N)})}p(()=>{i(De,e(s).running),i(b,e(s).repository)}),l(S,y)};m(D,S=>{e(s)===null?S(z):S(re,-1)})}l(B,Z)},$$slots:{default:!0}});var j=r(ue,2);{var de=B=>{Qe(B,{title:"Installer",children:($,Z)=>{var D=po(),z=H(D),re=a(z);{let b=u(()=>e(_)==="check");at(re,{kind:"read",label:"Vérifier maintenant",protected:!0,act:"check",get busy(){return e(b)},get disabled(){return e(w)},onrun:()=>{G()}})}var S=r(re,2);{var y=b=>{{let T=u(()=>`Installer la version ${e(s).latest}`),g=u(()=>e(_)==="apply");at(b,{kind:"destructive",get label(){return e(T)},protected:!0,act:"apply",get busy(){return e(g)},onrun:()=>{le(e(s).latest)}})}};m(S,b=>{e(A)&&b(y)})}var P=r(z,2);{var I=b=>{var T=co();l(b,T)};m(P,b=>{e(A)&&b(I)})}var Q=r(P,2);{var pe=b=>{var T=vo();l(b,T)};m(Q,b=>{e(w)&&b(pe)})}var De=r(Q,2);{var Re=b=>{var T=Ua(),g=a(T);p(()=>i(g,e(C))),l(b,T)};m(De,b=>{e(C)!==""&&b(Re)})}l($,D)},$$slots:{default:!0}})};m(j,B=>{e(s)!==null&&e(s).supported&&B(de)})}var J=r(j,2);{var ye=B=>{Qe(B,{title:"Dernière tentative",children:($,Z)=>{var D=mo(),z=H(D),re=a(z),S=r(z,2),y=r(a(S),2),P=a(y),I=r(y,4),Q=a(I),pe=r(I,2);{var De=T=>{var g=fo(),U=r(H(g),2),N=a(U);p(K=>i(N,K),[()=>Mt(e(O).finished_at)]),l(T,g)};m(pe,T=>{e(O).finished_at!==""&&T(De)})}var Re=r(pe,2);{var b=T=>{var g=go(),U=r(H(g),2),N=a(U);p(()=>i(N,e(O).reason)),l(T,g)};m(Re,T=>{e(O).reason!==""&&T(b)})}p(()=>{Be(z,"data-outcome",e(O).status),i(re,eo[e(O).status]),i(P,e(O).from===""?"—":e(O).from),i(Q,e(O).to===""?"—":e(O).to)}),l($,D)},$$slots:{default:!0}})};m(J,B=>{e(O)!==null&&B(ye)})}l(n,ke),Rt()}var bo=o(''),wo=o('

                                                                                                                                                                                                  ',1),yo=o('

                                                                                                                                                                                                  ',1),xo=o(''),ko=o(''),qo=o(''),So=o(''),Lo=o(' '),Eo=o(''),zo=o('

                                                                                                                                                                                                  Lecture de l’état du poste…

                                                                                                                                                                                                  '),jo=o('

                                                                                                                                                                                                  '),Po=o(" "),To=o("
                                                                                                                                                                                                • "),Co=o('
                                                                                                                                                                                                    '),Ro=o('
                                                                                                                                                                                                    '),Oo=o('

                                                                                                                                                                                                    ');function Ao(n,t){Ct(t,!0);const s=new pn,c=new en(s),_=[{title:"Au quotidien",pages:[{id:"dashboard",label:"Tableau de bord"},{id:"troubleshooting",label:"Dépannage"}]},{title:"Réglages",pages:[{id:"hardware",label:"Matériel"},{id:"label",label:"Étiquette"},{id:"rules",label:"Règles"},{id:"catalog",label:"Catalogue"},{id:"journal",label:"Journal"},{id:"station",label:"Poste"},{id:"update",label:"Mise à jour"}]}],w=u(()=>_.flatMap(d=>d.pages).find(d=>d.id===s.page)?.label??""),C=u(()=>s.health===null||s.health.station_name===""?`Poste ${String(s.health?.station??"")}`:s.health.station_name),O=u(()=>(c.pending?.changed_blocks??[]).map(d=>nn(d)).join(", ")),A=u(()=>(c.pending?.changed_blocks??[]).join(", "));ka(()=>(s.start(),()=>s.stop()));async function W(d){s.open(d),Ca(d)&&c.config===null&&await c.load()}async function G(){await s.protect(()=>c.save())===!0&&(s.notice="La configuration est enregistrée et appliquée.")}var le=Oo(),we=a(le),ke=r(a(we),2);rt(ke,17,()=>_,d=>d.title,(d,x)=>{var ee=wo(),f=H(ee),q=a(f),E=r(f,2);rt(E,17,()=>e(x).pages,be=>be.id,(be,se)=>{var Se=bo();let Le;var ce=a(Se);p(()=>{Le=Ht(Se,1,"entry svelte-9b2mjq",null,Le,{current:s.page===e(se).id}),Be(Se,"aria-current",s.page===e(se).id?"page":void 0),i(ce,e(se).label)}),et("click",Se,()=>{W(e(se).id)}),l(be,Se)}),p(()=>i(q,e(x).title)),l(d,ee)});var ue=r(ke,2),j=a(ue);{var de=d=>{var x=yo(),ee=H(x),f=a(ee),q=r(ee,2),E=a(q),be=r(q,2),se=a(be);p(()=>{i(f,e(C)),i(E,`${s.health.coop??""} · version ${s.health.version??""}`),i(se,`configuration ${s.health.config_fingerprint??""}`)}),l(d,x)};m(j,d=>{s.health!==null&&d(de)})}var J=r(j,2),ye=a(J),B=r(J,2);{var $=d=>{var x=xo();et("click",x,function(...ee){t.onclose?.apply(this,ee)}),l(d,x)};m(B,d=>{t.onclose!==void 0&&d($)})}var Z=r(we,2),D=a(Z);{var z=d=>{var x=ko(),ee=a(x);p(()=>i(ee,s.notice)),l(d,x)};m(D,d=>{s.notice!==""&&d(z)})}var re=r(D,2);{var S=d=>{var x=qo(),ee=a(x);p(()=>i(ee,s.linkError)),l(d,x)};m(re,d=>{s.linkError!==""&&d(S)})}var y=r(re,2);{var P=d=>{var x=So(),ee=a(x);p(()=>i(ee,s.actionError)),l(d,x)};m(y,d=>{s.actionError!==""&&d(P)})}var I=r(y,2);{var Q=d=>{var x=Eo(),ee=a(x),f=a(ee),q=r(f);{var E=Se=>{var Le=Lo(),ce=a(Le);p(()=>i(ce,e(A))),l(Se,Le)};m(q,Se=>{Gt.showTechnicalNames&&Se(E)})}var be=r(q),se=r(ee,2);at(se,{kind:"write",label:"Tout fonctionne : confirmer",onrun:()=>{s.protect(()=>c.confirm())}}),p(()=>{i(f,`Configuration appliquée mais NON CONFIRMÉE. Ce qui a changé : ${e(O)??""}. `),i(be,` Le poste reviendra tout seul à la version précédente dans - ${c.pending.seconds_left??""} secondes si personne ne confirme.`)}),l(d,x)};m(I,d=>{c.pending!==null&&d(Q)})}var pe=r(I,2),De=a(pe),Re=a(De),b=r(De,2);{var T=d=>{var x=zo();l(d,x)},g=d=>{ol(d,{get health(){return s.health},onshowrows:()=>{W("catalog")},onshowupdate:()=>{W("update")}})},U=d=>{Zi(d,{get admin(){return s},get health(){return s.health}})},N=d=>{wl(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},K=d=>{Jl(d,{get admin(){return s},get draft(){return c}})},_e=d=>{ki(d,{get draft(){return c},get health(){return s.health}})},ne=d=>{ys(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},L=d=>{Il(d,{get admin(){return s}})},X=d=>{Bi(d,{get admin(){return s},get draft(){return c},get health(){return s.health}})},ie=d=>{_o(d,{get admin(){return s}})};m(b,d=>{s.health===null?d(T):s.page==="dashboard"?d(g,1):s.page==="troubleshooting"?d(U,2):s.page==="hardware"?d(N,3):s.page==="label"?d(K,4):s.page==="rules"?d(_e,5):s.page==="catalog"?d(ne,6):s.page==="journal"?d(L,7):s.page==="station"?d(X,8):s.page==="update"&&d(ie,9)})}var fe=r(pe,2);{var xe=d=>{var x=Ro(),ee=a(x);{var f=se=>{var Se=jo(),Le=a(Se),ce=r(Le);rt(ce,16,()=>c.retired,V=>V,(V,Ue)=>{{let Ve=u(()=>`retirer ${Ue}`);at(V,{kind:"write",get label(){return e(Ve)},onrun:()=>c.dropRetired(Ue)})}}),p(V=>i(Le,`Ce fichier porte des réglages que cette version du poste ne connaît plus : - ${V??""}. `),[()=>c.retired.map(V=>Ft(V)).join(", ")]),l(se,Se)};m(ee,se=>{c.retired.length>0&&se(f)})}var q=r(ee,2);{var E=se=>{var Se=Co();rt(Se,21,()=>c.faults,Le=>Le.field,(Le,ce)=>{var V=To(),Ue=a(V),Ve=a(Ue),Ee=r(Ue,2);{var Te=ve=>{var ze=Po(),Oe=a(ze);p(()=>i(Oe,e(ce).field)),l(ve,ze)};m(Ee,ve=>{Gt.showTechnicalNames&&ve(Te)})}var Je=r(Ee),ae=r(Je);{var Ie=ve=>{var ze=yt();p(Oe=>i(ze,`— valeurs acceptées : ${Oe??""}`),[()=>e(ce).allowed.join(", ")]),l(ve,ze)};m(ae,ve=>{e(ce).allowed!==void 0&&e(ce).allowed.length>0&&ve(Ie)})}p(ve=>{i(Ve,ve),i(Je,` ${e(ce).message??""} `)},[()=>Ft(e(ce).field)]),l(Le,V)}),l(se,Se)};m(q,se=>{c.faults.length>0&&se(E)})}var be=r(q,2);{let se=u(()=>c.dirty?"Enregistrer la configuration":"Aucune modification à enregistrer"),Se=u(()=>!c.dirty||s.busy);at(be,{kind:"write",get label(){return e(se)},get disabled(){return e(Se)},onrun:()=>{G()}})}l(d,x)},He=u(()=>Ca(s.page)&&c.config!==null);m(fe,d=>{e(He)&&d(xe)})}var Xe=r(Z,2);{var ge=d=>{wn(d,{get admin(){return s}})};m(Xe,d=>{s.pending!==null&&d(ge)})}p(()=>{ua(ye,Gt.showTechnicalNames),i(Re,e(w))}),et("change",ye,()=>Gt.toggleTechnicalNames()),l(n,le),Rt()}Tt(["click","change"]);let oa=null;function Do(n){if(oa!==null||n.querySelector("[data-admin]")!==null)return;const t=document.createElement("div");n.appendChild(t),oa={component:fr(Ao,{target:t,props:{onclose:No}}),host:t}}function No(){if(oa===null)return;const{component:n,host:t}=oa;oa=null,gr(n),t.remove()}export{No as closeAdmin,Do as mountAdmin}; diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html index 17bb67f..38fc3ed 100644 --- a/internal/web/dist/index.html +++ b/internal/web/dist/index.html @@ -6,7 +6,7 @@ Pesée - + diff --git a/web/src/admin/pages/Update.svelte b/web/src/admin/pages/Update.svelte index a27fd42..7d7ee68 100644 --- a/web/src/admin/pages/Update.svelte +++ b/web/src/admin/pages/Update.svelte @@ -52,8 +52,14 @@ const { admin }: Props = $props() - /** Ce que le service dit, ou null tant qu'on n'a pas lu. */ - let state = $state(null) + /** + * Ce que le service dit, ou null tant qu'on n'a pas lu. + * + * Le nom N'EST PAS `state` : une variable de ce nom rendrait `$state` ambigu — Svelte + * lirait la rune comme l'abonnement au store `state` —, et le fichier ne compilerait + * plus. `svelte-check` le dit, `vitest` ne le voit pas. + */ + let current = $state(null) /** * Ce qui a raté à la LECTURE, distinct de ce qui rate à un ACTE. @@ -73,8 +79,16 @@ /** Ce que l'attente du retour a donné, une fois qu'elle est finie. */ let switchReport = $state('') - const outcome = $derived(state?.outcome ?? null) - const canInstall = $derived(state?.supported === true && state.available) + const outcome = $derived(current?.outcome ?? null) + const canInstall = $derived(current?.supported === true && current.available) + /** + * La version que le bouton installera. + * + * Extraite ici et non lue dans le gabarit : le rappel du bouton s'exécute plus tard, + * et TypeScript ne peut pas savoir que `current` sera encore non nul à ce moment-là. + * Une chaîne capturée dit exactement ce qui a été affiché — ce que le service exige. + */ + const latest = $derived(current?.latest ?? '') $effect(() => { void read() @@ -88,7 +102,7 @@ */ async function read(): Promise { try { - state = await api.fetchUpdate() + current = await api.fetchUpdate() readFailure = '' } catch (failure) { readFailure = failure instanceof Error ? failure.message : 'Lecture impossible.' @@ -101,7 +115,7 @@ try { const fresh = await admin.protect(() => api.checkForUpdate()) if (fresh !== null) { - state = fresh + current = fresh readFailure = '' } } finally { @@ -152,7 +166,7 @@ - {#if state === null} + {#if current === null}

                                                                                                                                                                                                    {readFailure === '' ? 'Lecture…' : readFailure}

                                                                                                                                                                                                    {:else} {#if readFailure !== ''} @@ -160,26 +174,26 @@ {/if}
                                                                                                                                                                                                    Version installée
                                                                                                                                                                                                    -
                                                                                                                                                                                                    {state.running}
                                                                                                                                                                                                    +
                                                                                                                                                                                                    {current.running}
                                                                                                                                                                                                    Dépôt suivi
                                                                                                                                                                                                    -
                                                                                                                                                                                                    {state.repository}
                                                                                                                                                                                                    - {#if state.checked_at !== ''} +
                                                                                                                                                                                                    {current.repository}
                                                                                                                                                                                                    + {#if current.checked_at !== ''}
                                                                                                                                                                                                    Dernière vérification
                                                                                                                                                                                                    -
                                                                                                                                                                                                    {frenchDateTime(state.checked_at)}
                                                                                                                                                                                                    +
                                                                                                                                                                                                    {frenchDateTime(current.checked_at)}
                                                                                                                                                                                                    {/if}
                                                                                                                                                                                                    - {#if !state.supported} + {#if !current.supported}

                                                                                                                                                                                                    La mise à jour depuis cet écran n’existe que sur les postes Windows. Sur les autres, elle se fait à la main — voir la notice d’installation.

                                                                                                                                                                                                    - {:else if state.available} + {:else if current.available}

                                                                                                                                                                                                    - Version disponible : {state.latest} - {#if state.published_at !== ''}, publiée le {frenchDateTime(state.published_at)}{/if}. - {#if state.html_url !== ''} - + Version disponible : {current.latest} + {#if current.published_at !== ''}, publiée le {frenchDateTime(current.published_at)}{/if}. + {#if current.html_url !== ''} + Voir les nouveautés {/if} @@ -190,7 +204,7 @@ {/if} -{#if state !== null && state.supported} +{#if current !== null && current.supported}

                                                                                                                                                                                                    void install(state.latest)} + onrun={() => void install(latest)} /> {/if}
                                                                                                                                                                                                    From 5f068ba1c450d7ccc20bc3c60f78cb9a3fda34e2 Mon Sep 17 00:00:00 2001 From: "Fabien F." Date: Wed, 29 Jul 2026 17:19:47 +0200 Subject: [PATCH 19/19] style: gofmt, et un godoc que j'avais soude a un autre Trois fichiers non formates. `go vet` ne verifie pas le format et `make test` non plus : c'est une etape a part du job "Tests et frontieres", et ma verification l'omettait. Le reformatage a montre une vraie faute au passage. Dans cmd/openscale/update.go, guardFunc avait ete insere ENTRE le commentaire de newUpdateService et sa fonction : les deux godoc s'etaient soudes, et godoc aurait affiche sous guardFunc les deux cas ou le service ne se construit pas. Le type passe avant, le commentaire retrouve sa fonction. Chaine de la CI rejouee localement dans son ordre : gofmt, vet, race -short, CGO_ENABLED=0 -short, boundary, deps, et les cinq planchers de couverture -- domain 96,9 / store 86,5 / scale 99,4 / printing 94,4 / catalog 89,4. --- cmd/openscale/update.go | 20 ++++++++++---------- internal/station/hub.go | 10 +++++----- internal/update/service_test.go | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cmd/openscale/update.go b/cmd/openscale/update.go index e626e8a..bf133e5 100644 --- a/cmd/openscale/update.go +++ b/cmd/openscale/update.go @@ -13,6 +13,16 @@ import ( "openscale/internal/web" ) +// guardFunc lets a plain function answer update.Guard. +// +// It exists so that the service can be built BEFORE the station whose Hub answers +// the question: the two need each other, and the closure resolves the Hub when a +// volunteer touches a button rather than when the wiring is laid out. +type guardFunc func() (bool, string) + +// UpdateGuard answers by calling the function. +func (f guardFunc) UpdateGuard() (bool, string) { return f() } + // newUpdateService wires the update service over the running station. // // It returns nil -- and the routes then answer honestly that this binary cannot @@ -24,16 +34,6 @@ import ( // - a platform with no swap. platform.ApplyUpdate answers ErrUpdateUnsupported // off Windows, and a screen that discovered that only at the last click would // have let a volunteer confirm an irreversible-looking act for nothing. -// guardFunc lets a plain function answer update.Guard. -// -// It exists so that the service can be built BEFORE the station whose Hub answers -// the question: the two need each other, and the closure resolves the Hub when a -// volunteer touches a button rather than when the wiring is laid out. -type guardFunc func() (bool, string) - -// UpdateGuard answers by calling the function. -func (f guardFunc) UpdateGuard() (bool, string) { return f() } - func newUpdateService(clock ports.Clock, guard update.Guard, dataDir string) *update.Service { running, err := update.ParseVersion(version) if err != nil { diff --git a/internal/station/hub.go b/internal/station/hub.go index 03fe552..31f5d1c 100644 --- a/internal/station/hub.go +++ b/internal/station/hub.go @@ -170,11 +170,11 @@ type Hub struct { // the products exist only in this process's memory. Stopping the station there // does not defer the catalogue, it loses it. catalogWaiting atomic.Bool - idempotency IdempotencyCache - message *Message - sound string - armExpiresAt time.Time - pendingReply chan<- domain.Ack + idempotency IdempotencyCache + message *Message + sound string + armExpiresAt time.Time + pendingReply chan<- domain.Ack // subscribersMu guards subscribers, and it is NOT a retreat from the design. // // The map is still written in ONE place — applySubscription, inside the select — diff --git a/internal/update/service_test.go b/internal/update/service_test.go index f66b46c..8f1c4a8 100644 --- a/internal/update/service_test.go +++ b/internal/update/service_test.go @@ -377,7 +377,7 @@ func TestTakeOutcomeAtStartupIsWhatTheStationTellsAfterwards(t *testing.T) { b := newServiceBench(t, stubGuard{allow: true}) writeOutcomeFile(t, b.dir, Outcome{ Status: StatusRolledBack, ExitCode: 10, From: "2.0.3", To: "2.1.0", - Reason: "le poste ne répond pas sur 127.0.0.1:8085/healthz après 60 s", + Reason: "le poste ne répond pas sur 127.0.0.1:8085/healthz après 60 s", FinishedAt: benchEpoch.Add(-time.Hour), })