- Datenmodell: Tenant.tier (Default PROFI), lotseChatSeats, lotseChatHardLimit;
Tabelle lotse_chat_seats (RLS, TENANT_MODELS, pii-fields), Index für die
Monatszählung der Chat-Nachrichten (Migration 20260921100000_pakete)
- src/lib/plans.ts: Stufenregeln, 150 Chats je Platz, Mehrverbrauch in 100er-Paketen
- src/server/plan.ts: effektive Freischaltung = Stufe UND TenantModule, genutzt von
requireModule, assertModuleEnabled, API, Sync (Offline-Op → rejected mit Klartext),
Navigation, isLotseEnabled und planningAccess (Planung nur in Profi)
- Lotse-Chat: Platzprüfung (no_seat), Testphase ohne Platz, Kontingent mit
hartem Limit (quota_exhausted); Platzvergabe durch den Mandanten-Admin
- Betreiber: Stufe/Plätze/hartes Limit im Mandantendetail mit Bestätigung
und Plattform-Audit, Verbrauch laufender Monat/Vormonat, Stufe als Badge
- Demo-Seed: demo = Profi mit 3 Plätzen, demo2 = Basis
- Tests: test-pakete-{rules,gates,seats}
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
177 lines
7.9 KiB
JSON
177 lines
7.9 KiB
JSON
{
|
||
"badge": {
|
||
"label": "Open synchronisation",
|
||
"synced": "Synced",
|
||
"pending": "{count} open",
|
||
"problem": "Check",
|
||
"syncing": "Sync …"
|
||
},
|
||
"update": {
|
||
"available": "New version available – reload",
|
||
"reload": "Reload"
|
||
},
|
||
"sync": {
|
||
"title": "Synchronisation",
|
||
"connection": "Connection",
|
||
"offlineHint": "Entries are stored on this device and sent as soon as a connection is available.",
|
||
"lastSync": "Last synchronisation",
|
||
"lastPull": "Orders saved for offline use",
|
||
"never": "Never",
|
||
"stale": "Offline data is older than {days} days. Please update with a connection.",
|
||
"status": "As of",
|
||
"pendingOps": "{count, plural, =0 {No pending changes} one {# change waiting to be sent} other {# changes waiting to be sent}}",
|
||
"pendingUploads": "{count, plural, =0 {No pending files} one {# file waiting for upload} other {# files waiting for upload}}",
|
||
"uploadSize": "{size} to transfer",
|
||
"uploading": "Uploading file … {percent} %",
|
||
"syncing": "Synchronising …",
|
||
"syncNow": "Synchronise now",
|
||
"allSynced": "Everything sent.",
|
||
"waitingForAuth": "Your session expired. Please sign in again – your entries stay on this device.",
|
||
"login": "Sign in again",
|
||
"problems": "Errors and conflicts",
|
||
"noProblems": "No errors or conflicts.",
|
||
"queue": "Queue",
|
||
"attempts": "{count, plural, one {# attempt} other {# attempts}}",
|
||
"nextAttempt": "Next attempt at {time}",
|
||
"createdAt": "Recorded {time}",
|
||
"retry": "Try again",
|
||
"discard": "Discard entry",
|
||
"discardConfirm": "Discard this entry permanently? The input will be lost.",
|
||
"discardYes": "Discard permanently",
|
||
"cancel": "Cancel",
|
||
"hide": "Hide notice",
|
||
"storage": "Storage on this device",
|
||
"storageUsage": "{usage} of {quota} used",
|
||
"storagePersisted": "Offline data is stored persistently.",
|
||
"storageNotPersisted": "The browser may delete offline data when storage runs low.",
|
||
"storageUnknown": "Storage usage not available.",
|
||
"offlineOrders": "{count, plural, =0 {No orders available offline} one {# order available offline} other {# orders available offline}}",
|
||
"saveOffline": "Save for offline use",
|
||
"saveOfflineHint": "Today's, the next 3 days' and running orders including drawings, assembly instructions and safety documents.",
|
||
"openOffline": "Open offline view",
|
||
"reset": "Reset local data",
|
||
"resetConfirm": "Delete all offline data of your account on this device?",
|
||
"resetWarning": "{count, plural, one {# entry has} other {# entries have}} not been sent yet and will be lost.",
|
||
"resetYes": "Reset now",
|
||
"resetDone": "Local data deleted."
|
||
},
|
||
"op": {
|
||
"session_start": "Job started",
|
||
"session_pause": "Break",
|
||
"session_resume": "Resume",
|
||
"session_end": "Time tracking ended",
|
||
"work_order_transition": "Status change",
|
||
"note_create": "Note",
|
||
"checklist_toggle": "Checklist",
|
||
"material_upsert": "Material",
|
||
"photo_attach": "Photo",
|
||
"voice_attach": "Voice note",
|
||
"report_save_draft": "Report draft",
|
||
"report_submit": "Report",
|
||
"signature_capture": "Signature",
|
||
"emergency_create": "Emergency job"
|
||
},
|
||
"state": {
|
||
"queued": "Waiting",
|
||
"sending": "Sending",
|
||
"applied": "Sent",
|
||
"conflict": "Conflict",
|
||
"rejected": "Not accepted"
|
||
},
|
||
"problem": {
|
||
"conflictTransition": "The order was changed in the office – your status change was not applied. The office will review it.",
|
||
"conflictReport": "The order was changed in the office – your report was not applied. The office will review it.",
|
||
"conflict": "The order was changed in the office – your change was not applied. The office will review it.",
|
||
"notFound": "The order is no longer assigned to you. The entry was not applied.",
|
||
"forbidden": "You are not allowed to perform this action.",
|
||
"blocked": "Required information is still missing – please complete it on the order and try again.",
|
||
"invalid": "The entry is incomplete or no longer valid.",
|
||
"upload": "The file was not accepted (format or size).",
|
||
"unauthorized": "Your session expired – please sign in again.",
|
||
"network": "No connection – will retry automatically.",
|
||
"internal": "Technical error – will retry automatically.",
|
||
"timeOverlap": "The added time overlaps with time already recorded and was not accepted.",
|
||
"timeWindow": "The added time is outside the allowed 7 days, in the future or too long and was not accepted.",
|
||
"otherSession": "Time was still running on another order – the start was not accepted. Please start again and switch.",
|
||
"notInPlan": "Included in the Profi package – not activated for this company. The entry was not applied, please contact the office.",
|
||
"moduleDisabled": "This function is switched off for this company. The entry was not applied, please contact the office."
|
||
},
|
||
"view": {
|
||
"title": "Offline view",
|
||
"offlineHint": "No connection. You are viewing the orders stored on this device.",
|
||
"onlineHint": "Connection is back.",
|
||
"backOnline": "Back to the regular view",
|
||
"empty": "No orders stored on this device. With a connection, today's and upcoming orders are saved automatically.",
|
||
"loading": "Loading offline data …",
|
||
"notReady": "Offline data is available after signing in with a connection.",
|
||
"notFound": "This order is not stored on this device.",
|
||
"backToList": "All stored orders",
|
||
"open": "Open order",
|
||
"count": "{count, plural, one {# order} other {# orders}}",
|
||
"pending": "{count, plural, one {# change waiting} other {# changes waiting}}",
|
||
"conflict": "Conflict – see synchronisation",
|
||
"rejected": "Not accepted – see synchronisation",
|
||
"savedAt": "As of: {time}",
|
||
"noDate": "No date",
|
||
"customer": "Customer",
|
||
"site": "Site",
|
||
"contact": "Contact",
|
||
"call": "Call",
|
||
"access": "Access",
|
||
"parking": "Parking",
|
||
"safety": "Safety",
|
||
"technical": "Technical",
|
||
"hints": "Notes for technicians",
|
||
"scope": "Scope of work",
|
||
"description": "Description",
|
||
"checklist": "Checklist",
|
||
"checklistEmpty": "No checklist.",
|
||
"required": "Required",
|
||
"done": "Done",
|
||
"open_item": "Open",
|
||
"materials": "Material",
|
||
"materialsProgress": "{done} of {total} items recorded",
|
||
"documents": "Documents",
|
||
"noDocuments": "No documents.",
|
||
"docOffline": "Available offline",
|
||
"docOnline": "Online only",
|
||
"history": "Site history",
|
||
"noHistory": "No approved reports at this site.",
|
||
"notes": "Notes",
|
||
"notePlaceholder": "Briefly describe what happened on site …",
|
||
"noteSave": "Save note",
|
||
"draftRestored": "Draft restored.",
|
||
"photos": "Photos",
|
||
"photo": "Take photo",
|
||
"photoRequirements": "Required photos",
|
||
"photoMissing": "Missing",
|
||
"photoDone": "Present",
|
||
"pendingBadge": "Waiting to be sent",
|
||
"time": "Time tracking",
|
||
"actionAccept": "Accept",
|
||
"actionTravel": "Start travel",
|
||
"actionStart": "Start work",
|
||
"actionPause": "Break",
|
||
"actionResume": "Resume",
|
||
"actionEnd": "End time tracking",
|
||
"completeOnline": "Completing requires a connection (required information is checked).",
|
||
"saving": "Saving …",
|
||
"queued": "Saved – will be sent as soon as a connection is available.",
|
||
"saved": "Saved and sent.",
|
||
"error": "Could not be saved."
|
||
},
|
||
"logout": {
|
||
"warning": "{count, plural, one {# entry has} other {# entries have}} not been sent yet. Signing out deletes the offline data on this device – {count, plural, one {it} other {they}} will be lost.",
|
||
"syncFirst": "Synchronise first",
|
||
"confirm": "Sign out anyway",
|
||
"cancel": "Cancel"
|
||
},
|
||
"install": {
|
||
"title": "App on this device",
|
||
"installed": "Craftvia is installed as an app.",
|
||
"button": "Install on home screen",
|
||
"ios": "On iPhone or iPad: tap the share icon and choose “Add to Home Screen”.",
|
||
"other": "Choose “Install app” or “Add to home screen” in the browser menu."
|
||
}
|
||
}
|