diff --git a/prisma/migrations/20260609190000_drop_document_export_perms/migration.sql b/prisma/migrations/20260609190000_drop_document_export_perms/migration.sql
new file mode 100644
index 0000000..88ca15a
--- /dev/null
+++ b/prisma/migrations/20260609190000_drop_document_export_perms/migration.sql
@@ -0,0 +1,61 @@
+-- Drop the three DEAD document "export" permissions and make the 12 surviving
+-- document permissions reproducible on a fresh production database.
+--
+-- Why: `offers.export`, `orders.export`, `invoices.export` gate NOTHING on the
+-- backend — every PDF/export route is guarded by `*.view`. They only ever
+-- toggled frontend buttons and showed up as dead checkboxes in role management.
+-- The 12 real keys (offers/orders/invoices x view/create/edit/delete) ARE each
+-- enforced on >=1 route and must survive.
+--
+-- All 15 keys were previously defined ONLY in prisma/seed.ts (dev-only), so a
+-- fresh production DB never had ANY of them. This migration both removes the 3
+-- dead keys AND seeds the 12 survivors + their admin grant, mirroring the
+-- prisma/seed.ts column set exactly (name, display_name, module, description,
+-- created_at; role_permissions has only the composite PK).
+--
+-- Idempotent: safe whether the rows exist or not, and safe on a prod DB that
+-- never had them. Deletes are no-ops when absent; INSERT IGNORE skips on the
+-- unique `permissions.name` key and on the `role_permissions` composite PK.
+
+-- 1. Remove the dead export role assignments first (FK-safe: children before
+-- parents). No-op when the permissions/assignments don't exist.
+DELETE FROM `role_permissions`
+WHERE `permission_id` IN (
+ SELECT `id` FROM `permissions`
+ WHERE `name` IN ('offers.export', 'orders.export', 'invoices.export')
+);
+
+-- 2. Remove the dead export permissions themselves.
+DELETE FROM `permissions`
+WHERE `name` IN ('offers.export', 'orders.export', 'invoices.export');
+
+-- 3. Idempotently (re)insert the 12 surviving document permissions. INSERT
+-- IGNORE skips any that already exist (unique `name`). display_name /
+-- description / module match prisma/seed.ts verbatim.
+INSERT IGNORE INTO `permissions` (`name`, `display_name`, `module`, `description`, `created_at`) VALUES
+ ('offers.view', 'Zobrazit nabídky', 'offers', 'Prohlížet seznam nabídek', NOW()),
+ ('offers.create', 'Vytvořit nabídku', 'offers', 'Vytvářet nové nabídky', NOW()),
+ ('offers.edit', 'Upravit nabídku', 'offers', 'Upravovat existující nabídky', NOW()),
+ ('offers.delete', 'Smazat nabídku', 'offers', 'Mazat nabídky', NOW()),
+ ('orders.view', 'Zobrazit objednávky', 'orders', 'Prohlížet seznam objednávek', NOW()),
+ ('orders.create', 'Vytvořit objednávku', 'orders', 'Vytvářet nové objednávky', NOW()),
+ ('orders.edit', 'Upravit objednávku', 'orders', 'Upravovat existující objednávky', NOW()),
+ ('orders.delete', 'Smazat objednávku', 'orders', 'Mazat objednávky', NOW()),
+ ('invoices.view', 'Zobrazit faktury', 'invoices', 'Prohlížet seznam faktur', NOW()),
+ ('invoices.create', 'Vytvořit fakturu', 'invoices', 'Vytvářet nové faktury', NOW()),
+ ('invoices.edit', 'Upravit fakturu', 'invoices', 'Upravovat existující faktury', NOW()),
+ ('invoices.delete', 'Smazat fakturu', 'invoices', 'Mazat faktury', NOW());
+
+-- 4. Grant all 12 survivors to the admin role. INSERT IGNORE on the composite
+-- PK (role_id, permission_id) makes this idempotent and preserves any other
+-- role assignments that already exist.
+INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
+SELECT r.id, p.id
+FROM `roles` r
+CROSS JOIN `permissions` p
+WHERE r.name = 'admin'
+ AND p.name IN (
+ 'offers.view', 'offers.create', 'offers.edit', 'offers.delete',
+ 'orders.view', 'orders.create', 'orders.edit', 'orders.delete',
+ 'invoices.view', 'invoices.create', 'invoices.edit', 'invoices.delete'
+ );
diff --git a/prisma/seed.ts b/prisma/seed.ts
index 65f2d97..cdbb541 100644
--- a/prisma/seed.ts
+++ b/prisma/seed.ts
@@ -95,12 +95,6 @@ const PERMISSIONS: {
module: "offers",
description: "Mazat nabídky",
},
- {
- name: "offers.export",
- display_name: "Exportovat nabídku",
- module: "offers",
- description: "Exportovat nabídky do PDF",
- },
// Objednávky
{
@@ -127,12 +121,6 @@ const PERMISSIONS: {
module: "orders",
description: "Mazat objednávky",
},
- {
- name: "orders.export",
- display_name: "Exportovat objednávku",
- module: "orders",
- description: "Exportovat objednávky do PDF",
- },
// Faktury
{
@@ -159,12 +147,6 @@ const PERMISSIONS: {
module: "invoices",
description: "Mazat faktury",
},
- {
- name: "invoices.export",
- display_name: "Exportovat fakturu",
- module: "invoices",
- description: "Exportovat faktury do PDF",
- },
// Projekty
{
diff --git a/src/admin/pages/InvoiceDetail.tsx b/src/admin/pages/InvoiceDetail.tsx
index 0a58baf..ff94429 100644
--- a/src/admin/pages/InvoiceDetail.tsx
+++ b/src/admin/pages/InvoiceDetail.tsx
@@ -1147,7 +1147,7 @@ export default function InvoiceDetail() {
- {hasPermission("invoices.export") && (
+ {hasPermission("invoices.view") && (
- {isEdit && invoice && hasPermission("invoices.export") && (
+ {isEdit && invoice && hasPermission("invoices.view") && (
- {isEdit && hasPermission("offers.export") && (
+ {isEdit && hasPermission("offers.view") && (
)
)}
- {hasPermission("orders.export") && (
+ {hasPermission("orders.view") && (