Compare commits

...

1216 Commits

Author SHA1 Message Date
af5bd12577 Added Frontend 2026-01-06 12:09:31 +01:00
6f3d4c0bff Added Backend 2026-01-06 12:07:43 +01:00
125c16a20c Merge branch 'beta' into 'main'
Beta

See merge request fedeo/software!60
2026-01-06 11:06:07 +00:00
d01bd7fc0c Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!59
2026-01-06 11:02:25 +00:00
d3fc2e6ad3 LabelPrinter Implementation 2026-01-06 11:58:29 +01:00
c1ed8cd028 Try for SEPA 2026-01-06 11:58:19 +01:00
786ac06e09 Fix SyncedAt Display 2026-01-06 11:57:33 +01:00
a00fd6d51f Added Workflows 2026-01-06 11:57:00 +01:00
c439396595 Fix for deliveryNotes 2026-01-06 11:56:35 +01:00
b013ef8f4b Merge Backend in FEDEO MONO 2026-01-06 11:54:19 +01:00
7c3968636d Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!18
2026-01-06 10:45:43 +00:00
bf3f0cc784 Added Public Links 2026-01-06 11:43:51 +01:00
7f09ef2911 Added Dokubox Service & Mailparser 2026-01-06 11:43:43 +01:00
8b9b5744bf Fix Time Issue 2026-01-06 11:43:19 +01:00
fa369f7b81 Added Public Pin Header to Cors 2026-01-06 11:41:34 +01:00
5485d2c0cc Fix Synced At 2026-01-06 11:41:13 +01:00
c1ff4d1a08 Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!17
2026-01-02 13:35:59 +00:00
8a2e91e702 TS Fix 2026-01-02 14:34:53 +01:00
65acad1c0d Added Prepare Service 2026-01-02 12:45:47 +01:00
bc3b944f5d Added Prepare Service 2026-01-02 12:45:24 +01:00
e8fe6940c2 Added Prepare Service 2026-01-02 12:45:14 +01:00
7df88ef5e4 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!58
2026-01-02 11:44:48 +00:00
8a87113275 Added Prepare Service 2026-01-02 12:44:26 +01:00
94ed9ac343 Added Preparing 2026-01-02 12:39:47 +01:00
848c9e4b2f Added Prepare Service 2026-01-02 12:39:20 +01:00
b26c9432f1 Added Payment Type to list 2026-01-02 12:13:11 +01:00
3c573380a8 Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!16
2026-01-01 15:39:55 +00:00
a5f82c3ef3 Fix TS 2026-01-01 16:37:40 +01:00
78ae8989ba Fix TS 2026-01-01 16:35:43 +01:00
cebe20fd43 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!57
2026-01-01 15:28:02 +00:00
3da3aee50d Fixed Dates in create resource 2026-01-01 16:24:26 +01:00
2c7449ef7a Added Leave Guard 2026-01-01 16:24:00 +01:00
fb746b4d59 Statement Sync 2026-01-01 15:52:05 +01:00
b3fd5eafbc Bankstatementsync 2026-01-01 15:51:47 +01:00
4121666c70 Serial and Other 2026-01-01 15:34:35 +01:00
a3df87caee Serial and Other 2026-01-01 15:33:48 +01:00
8db4ad2203 Changes 2026-01-01 15:31:38 +01:00
0d1b4b7eb8 Added Page Leave Guard 2026-01-01 15:30:06 +01:00
e0f73c20c9 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!56
2025-12-28 13:53:52 +00:00
3e5c6ca8da Fix 2025-12-28 14:49:51 +01:00
bffc5666fe Fix 2025-12-28 14:39:54 +01:00
7d7689999e Merge branch 'orm' into 'main'
Change in Resource

See merge request fedeo/backend!15
2025-12-28 13:32:11 +00:00
fb52f88ec3 Merge branch 'beta' into 'main'
Beta

See merge request fedeo/software!55
2025-12-27 13:06:20 +00:00
902e3ac59a Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!54
2025-12-27 12:07:48 +00:00
97f66f808a Change in Resource 2025-12-27 13:05:01 +01:00
240264aafb Fix Serialinvoice table 2025-12-27 13:04:02 +01:00
d5999bfb20 Fix 2025-12-27 12:56:54 +01:00
bded9189e3 Merge branch 'orm' into 'main'
Changed Resource Config

See merge request fedeo/backend!14
2025-12-19 08:39:47 +00:00
282d1eb052 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!53
2025-12-19 08:39:20 +00:00
7d4adbb3e4 Fix Service Saving 2025-12-18 12:45:49 +01:00
8e9b9dfc98 Changed Resource Config 2025-12-18 12:19:09 +01:00
bb1e73443b Fixed Banking Page 2025-12-18 12:18:57 +01:00
2b12e6ada7 Merge branch 'orm' into 'main'
Fix E-Mail

See merge request fedeo/backend!13
2025-12-17 13:36:02 +00:00
1ae640dc02 Merge branch 'devCorrected' into 'beta'
Fix E-Mail

See merge request fedeo/software!52
2025-12-17 13:35:37 +00:00
9d6f2de4ab Fix E-Mail 2025-12-17 09:23:25 +01:00
280cf4518a Fix E-Mail 2025-12-17 09:23:07 +01:00
439ec3438d Merge branch 'devCorrected' into 'beta'
Time Changes

See merge request fedeo/software!51
2025-12-17 07:30:58 +00:00
049a5b2264 packages changes 2025-12-16 16:28:10 +01:00
5aac81f601 Time Changes 2025-12-16 16:27:17 +01:00
6053b7340f Time Changes 2025-12-16 16:27:04 +01:00
8e1a28577c Merge branch 'orm' into 'main'
Time

See merge request fedeo/backend!12
2025-12-16 15:25:43 +00:00
e6bc123481 Time 2025-12-16 16:23:25 +01:00
6d8193296e Time Changes 2025-12-16 16:23:12 +01:00
6ded3b859f Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!11
2025-12-16 13:14:49 +00:00
94c252ec6c rfid 2025-12-16 14:13:48 +01:00
9a0a757757 Merge branch 'devCorrected' into 'beta'
Fixed Times

See merge request fedeo/software!50
2025-12-14 16:20:33 +00:00
780b899d42 Fixed Times 2025-12-14 17:15:24 +01:00
5270313b3d Time Migration 2025-12-14 17:14:50 +01:00
1281976ec3 Time Migration 2025-12-14 16:29:08 +01:00
267a57c4ea Time Migration 2025-12-14 16:29:00 +01:00
e5c3863eee Schema Migrations 2025-12-14 16:28:30 +01:00
c8f85496ce Merge branch 'devCorrected' into 'beta'
Fixed linkedDocuments Agrar

See merge request fedeo/software!49
2025-12-13 13:04:17 +00:00
9ddda1a933 Fixed linkedDocuments Agrar 2025-12-13 14:00:14 +01:00
ca393d1625 Merge branch 'devCorrected' into 'beta'
Fix for Invoice Gen

See merge request fedeo/software!48
2025-12-10 14:06:06 +00:00
d916ed471b Fix for Invoice Gen 2025-12-10 14:49:40 +01:00
3d51a1aa6d Merge branch 'devCorrected' into 'beta'
Fix for Copying

See merge request fedeo/software!47
2025-12-10 11:44:25 +00:00
3328279581 Fix for Copying 2025-12-10 12:40:34 +01:00
053f3ce557 Merge branch 'orm' into 'main'
Fixed Put

See merge request fedeo/backend!9
2025-12-10 11:35:37 +00:00
6d936c4be7 Fixed Put 2025-12-10 12:31:10 +01:00
d210eb857f Merge branch 'devCorrected' into 'beta'
Fix for Copying

See merge request fedeo/software!46
2025-12-10 11:27:24 +00:00
1e0f18c854 Fix for Copying 2025-12-10 12:23:41 +01:00
9263c24ede Merge branch 'orm' into 'main'
Added Plants to resource config

See merge request fedeo/backend!8
2025-12-10 09:29:42 +00:00
3f7f8a4498 Added Plants to resource config 2025-12-10 10:28:53 +01:00
1ab124ac7a Merge branch 'devCorrected' into 'beta'
Fix for Copying

See merge request fedeo/software!45
2025-12-10 07:43:41 +00:00
65da63d1c8 Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!7
2025-12-10 07:40:46 +00:00
eeceb4a8f6 Fix for Copying 2025-12-10 08:39:51 +01:00
58836fb0ef Redone 2025-12-10 08:39:44 +01:00
4016c8b6b5 Redone 2025-12-10 08:32:22 +01:00
ab2b5135c0 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!44
2025-12-10 07:25:44 +00:00
ac2ed79e10 Fix for Copying 2025-12-10 08:21:59 +01:00
7ce5616e65 Fix for Copying 2025-12-09 16:00:01 +01:00
aefd8ffba4 Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!6
2025-12-09 11:27:54 +00:00
c3467bdd9d Redone 2025-12-09 12:27:20 +01:00
641130e506 Redone 2025-12-09 12:26:18 +01:00
6d05001812 Tidying 2025-12-09 12:26:10 +01:00
4a3515f6f3 Schema and Config Changes 2025-12-09 12:12:05 +01:00
b88e840ff5 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!43
2025-12-09 11:11:42 +00:00
84c88ef69e Added Buttons for all connected Documents 2025-12-09 12:07:00 +01:00
8b70b77b85 Added Buttons for all connected Documents 2025-12-09 11:41:21 +01:00
76941abbf2 BETA for new DB 2025-12-09 11:36:12 +01:00
a161306359 BETA for new DB 2025-12-09 11:05:56 +01:00
8a528f0711 Merge branch 'devCorrected' into 'beta'
BETA for new DB

See merge request fedeo/software!41
2025-12-09 07:35:57 +00:00
6c1f95db9f Merge branch 'orm' into 'main'
fix laoding issue

See merge request fedeo/backend!5
2025-12-09 07:35:27 +00:00
861984e4b1 fix laoding issue
fix missing mtm
2025-12-09 08:29:24 +01:00
c6354ac656 BETA for new DB 2025-12-09 08:28:41 +01:00
0c1287d3b9 Fix NumberRange 2025-12-09 08:17:52 +01:00
862912cd66 Merge branch 'orm' into 'main'
Redone

See merge request fedeo/backend!4
2025-12-08 19:25:30 +00:00
a605fdc351 Redone 2025-12-08 20:23:59 +01:00
d234a2371b Merge branch 'beta' into 'main'
2025.21.0

See merge request fedeo/software!40
2025-12-08 16:14:48 +00:00
183fefb557 Merge branch 'devCorrected' into 'beta'
BETA for new DB

See merge request fedeo/software!39
2025-12-08 14:15:32 +00:00
64b3ec626c docker-compose.yml bearbeiten 2025-12-08 14:11:25 +00:00
fd52cadbff Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!3
2025-12-08 14:09:37 +00:00
b694340f38 Redone 2025-12-08 15:09:15 +01:00
888336dd04 BETA for new DB 2025-12-08 14:40:21 +01:00
e35e857380 Redone 2025-12-08 12:27:28 +01:00
c1120d1878 Schema Changes 2025-12-08 12:15:27 +01:00
428a002e9f Redone 2025-12-08 12:15:20 +01:00
1d3bf94b88 Route Changes 2025-12-08 11:45:50 +01:00
8f0efc0d72 schema changes 2025-12-08 11:45:37 +01:00
e760bd5f97 tsconfig.json 2025-12-07 23:30:33 +01:00
899f8dce20 tsconfig.json 2025-12-07 23:28:36 +01:00
aa1f3b1cb3 tsconfig.json 2025-12-07 23:23:18 +01:00
f5825f9a18 tsconfig.json 2025-12-07 23:14:31 +01:00
607024c813 Changed Dockerfile 2025-12-07 23:06:29 +01:00
206bdc6392 docker-compose.yml bearbeiten 2025-12-07 22:03:06 +00:00
eda8d357da Merge branch 'orm' into 'main'
Orm

See merge request fedeo/backend!2
2025-12-07 21:49:31 +00:00
32b46f58a4 TS fixes 2025-12-07 22:45:49 +01:00
e92eccc7d3 TS fixes 2025-12-07 22:45:02 +01:00
af5f9567f1 TS fixes 2025-12-07 22:43:01 +01:00
8faaef1ef5 TS fixes 2025-12-07 22:42:23 +01:00
d33b908775 TS fixes 2025-12-07 22:40:15 +01:00
5968329c8f package changes 2025-12-07 22:34:48 +01:00
09d67a7522 redone routes 2025-12-07 22:27:57 +01:00
0b32b69d10 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!38
2025-12-07 21:22:02 +00:00
9bfb880f7b BETA for new DB 2025-12-07 22:17:57 +01:00
b90e056e7c redone routes 2025-12-07 22:06:37 +01:00
dc0b49355d db changes 2025-12-07 22:06:26 +01:00
76e40cd9e1 db changes 2025-12-07 17:11:26 +01:00
0f3c8c862f removed Routes, Introduced Single Route with Cecking 2025-12-06 22:50:15 +01:00
dff2b05401 Redone Routes customers,contracts,contacts,vendors 2025-12-06 20:33:27 +01:00
765f3c42c1 Added dbSearch util 2025-12-06 20:31:59 +01:00
1e71e54314 Schema 2025-12-06 20:10:57 +01:00
d895583ea2 Converted Routes 2025-12-06 19:29:47 +01:00
7450f90a0f Schema Changes 2025-12-06 19:29:28 +01:00
63af22b671 Introduced New DB 2025-12-06 10:34:58 +01:00
407592680a Open Changes 2025-12-05 11:49:33 +01:00
d6badafeb9 Added Health Endpoint 2025-11-30 15:08:18 +01:00
d408dadd88 Added Health Endpoint 2025-11-30 15:04:47 +01:00
253b04ec0d Added Download Option for Drafts/Base64 2025-11-30 13:37:38 +01:00
de2692f704 Mobile 2025-11-30 13:37:19 +01:00
665f0d1454 Added NoControls Option 2025-11-20 12:38:49 +01:00
24f576aeaa Remodel for Mobile 2025-11-20 12:38:38 +01:00
f8c62a90f0 PDF Time Eval Corrections 2025-11-14 20:13:54 +01:00
951011bc0e Merge branch 'devCorrected' into 'beta'
Introduced Employee Number

See merge request fedeo/software!37
2025-11-14 19:01:20 +00:00
5fd683eacc Merge branch 'beta' into 'main'
2025.20.1

See merge request fedeo/software!36
2025-11-14 19:00:37 +00:00
5f6df7c69d Fixed Some Errors in IncomingInvoices 2025-11-14 17:54:27 +01:00
da074df63c Redone IncomingInvoice Show/Edit 2025-11-14 17:48:15 +01:00
32c71fe49b Introduced Employee Number
Added Ability to Save Bericht
2025-11-14 17:34:15 +01:00
c4d0a20bbc Added dataUriToFile Function 2025-11-14 17:33:41 +01:00
263f389e91 Removed Download Button for Temporary Display 2025-11-14 17:33:27 +01:00
24e80a6a0a Added Pagebreak Error 2025-11-11 19:36:38 +01:00
e0638b5a98 Merge branch 'devCorrected' into 'beta'
Fixed Phase Change

See merge request fedeo/software!35
2025-11-11 08:00:28 +00:00
57abbe8534 Fixed Phase Change
Fixed Evaluate Date
2025-11-11 08:56:52 +01:00
f61c780185 Fixed not included last day 2025-11-11 08:52:00 +01:00
48263b28b5 Fixed Search Speed 2025-11-11 08:36:34 +01:00
7cd9a3e074 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!34
2025-11-10 14:05:48 +00:00
914c7e4fc1 Fixed Dumb Error 2025-11-10 14:40:13 +01:00
94aee4540a Fixed Filters 2025-11-10 13:57:57 +01:00
aaf61760a3 Fixed No Category Error 2025-11-10 13:57:45 +01:00
8a88c6878e Fixed RecreationDays Compensation 2025-11-10 13:50:30 +01:00
4f01d2407b Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!33
2025-11-10 11:14:31 +00:00
d6dd0a1602 Fixed loading 2025-11-10 12:06:34 +01:00
c1dc88cdd9 Fixed Search 2025-11-10 12:03:43 +01:00
1beb0dbaf3 Tidying 2025-11-10 11:44:27 +01:00
de32d72eda Allowed Filters to stay Persistent, Introduced Extra Column for ActivePhase 2025-11-10 11:42:20 +01:00
87aaa28d92 Redone Submit/approve Process 2025-11-10 11:07:11 +01:00
70347920f2 Merge branch 'beta' into 'main'
Beta

See merge request fedeo/software!32
2025-11-09 19:31:52 +00:00
2f177ad603 Merge branch 'devCorrected' into 'beta'
Changed Auth

See merge request fedeo/software!31
2025-11-09 19:28:08 +00:00
06b97cbdae Changed Auth 2025-11-09 19:51:32 +01:00
2382b2dfae Fixed Role Loading 2025-11-09 18:52:44 +01:00
fc3ed1fb11 Fixed Tenant Error 2025-11-09 18:49:30 +01:00
29cfaccd02 Fixes 2025-11-09 18:47:30 +01:00
d6e11766da Merge branch 'devCorrected' into 'beta'
Changed DC

See merge request fedeo/software!30
2025-11-09 17:42:29 +00:00
3cd7120b4b Changed DC 2025-11-09 18:38:31 +01:00
433bb7d05e Merge branch 'devCorrected' into 'beta'
Fix Auth

See merge request fedeo/software!29
2025-11-09 17:31:32 +00:00
65598bceac Fix Auth 2025-11-09 18:27:09 +01:00
67389a5f78 Merge branch 'beta' into 'main'
2025.20.0

See merge request fedeo/software!28
2025-11-09 17:10:06 +00:00
0909f34649 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!27
2025-11-09 17:04:53 +00:00
4cfa4dbffd Fixed Scroll in EntityShowSub.vue 2025-11-09 17:57:58 +01:00
f3892861b1 Removed Abwesenheiten 2025-11-09 17:53:29 +01:00
572d564135 TS Fixes 2025-11-09 16:12:18 +01:00
7eb4a32552 Redone Update Modal 2025-11-09 16:06:45 +01:00
9d2cecd55b Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!26
2025-11-09 14:53:54 +00:00
16962a6df8 Changed Node Version 2025-11-09 15:48:30 +01:00
cf2b88199a Removed Module Chatwoot 2025-11-09 15:37:37 +01:00
75e2f84726 Redone Auth 2025-11-08 19:02:08 +01:00
8b34609e53 Added GoCardless Secrets 2025-11-08 19:01:52 +01:00
d7939a9ca4 New Staff Functions 2025-11-08 19:01:22 +01:00
ee8e17d80e Index Registers 2025-11-08 19:00:46 +01:00
29bebe6149 Functions Time Eval and PDF 2025-11-08 18:59:47 +01:00
6d0b764ee2 Introduced Dayjs as Server Plugin 2025-11-08 18:59:25 +01:00
89abbde753 Fixed Datev Export 2025-11-08 18:59:10 +01:00
61d22dbfa1 Added GoCardless Generation 2025-11-08 18:58:54 +01:00
62146ff970 Tidying 2025-11-08 18:57:28 +01:00
802c0c3f09 Tidying 2025-11-08 18:57:22 +01:00
d54a3aab7a Tidying, Zip Check 2025-11-08 18:57:09 +01:00
6d22aec2f4 Tidying 2025-11-08 18:56:41 +01:00
86a5b7e63e Added Location Prop 2025-11-08 18:56:33 +01:00
3a0c0fc9a7 Added Tempstore Saving 2025-11-08 18:56:21 +01:00
10b29c336a PDF Viewer Location 2025-11-08 18:55:46 +01:00
e4ae514830 Tidying and PDF Viewer Location 2025-11-08 18:55:35 +01:00
d9f35602b2 Introduced Helpdesk 2025-11-08 18:54:52 +01:00
1838d07082 Fixed Some Functions 2025-11-08 18:54:31 +01:00
655601dddb Time Composable 2025-11-08 18:53:56 +01:00
3ac8b63164 Redone Banksettings 2025-11-08 18:53:48 +01:00
43a6d1d631 Added Location to PDF Viewer in EVal 2025-11-08 18:52:25 +01:00
1526879a38 MainNav Links Times/Profiles 2025-11-08 18:50:06 +01:00
157533bb9d Added Dayjs Plugin 2025-11-08 18:49:31 +01:00
cf31d43702 Redone Times 2025-11-08 18:49:21 +01:00
26b7dfc06c Redone Profiles Edit 2025-11-08 18:47:46 +01:00
61835c3f26 Fixed TS Errors 2025-10-31 16:45:27 +01:00
55c101df12 Fixed TS Errors 2025-10-31 16:39:49 +01:00
c439eec66c Added Notifications Basics 2025-10-31 16:29:36 +01:00
75518897f1 Fixed Paginated Search 2025-10-31 16:29:20 +01:00
3bd4ac1f56 Added Notification Basics 2025-10-31 16:28:17 +01:00
2eb19b36a6 Added Helpdesk
Added M2M Auth
2025-10-31 16:27:56 +01:00
bbd5bbab9b Added Helpdesk 2025-10-27 17:40:43 +01:00
8ff63fadec Added Paginated Endpoint
Reformatted Code
2025-10-27 17:40:25 +01:00
8af09e1841 Added Paginated Endpoint
Reformatted Code
2025-10-27 17:39:32 +01:00
5d3cdeb960 Added Paginated Endpoint
Reformatted Code
2025-10-27 17:39:25 +01:00
d4fe665462 Added Standard E-Mail in Select 2025-10-27 17:38:11 +01:00
560b15ec93 Removed output 2025-10-27 17:37:57 +01:00
efaebb2f4e Added Save in Sent for emailAsUser.ts 2025-10-27 17:37:51 +01:00
9658ad621a Introduced Paginated Data, Listing to StandardEntitys 2025-10-14 15:13:30 +02:00
f918e735a5 fixed ts errors 2025-10-13 21:42:39 +02:00
4907dd478a Merge branch 'beta' into 'main'
2025.19.5

See merge request fedeo/software!25
2025-10-13 19:40:19 +00:00
31c7bd34b7 Merge branch 'devCorrected' into 'beta'
Fixed Show Workingtimes

See merge request fedeo/software!24
2025-10-13 19:35:34 +00:00
e0ed8f41bb Working times and tenant profiles 2025-10-13 21:33:28 +02:00
ef0af906ff Fixed standardE-Mail for Invoices 2025-10-13 21:32:59 +02:00
2d332f1ded Fixed Show Workingtimes 2025-10-13 21:32:22 +02:00
6c3189c8f0 Merge branch 'beta' into 'main'
2025.19.4

See merge request fedeo/software!23
2025-10-10 06:25:08 +00:00
4abb3917e6 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!22
2025-10-10 06:21:25 +00:00
59c32ef8d8 Fixed Load of existing Doc without Created_by 2025-10-09 18:29:24 +02:00
4514627ec0 Fixed Wrong Bic Key 2025-10-09 18:29:07 +02:00
ceae4972b8 Merge branch 'beta' into 'main'
2025.19.3

See merge request fedeo/software!21
2025-10-09 16:04:18 +00:00
4b754b5ef5 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!20
2025-10-09 16:00:38 +00:00
f7f239fcb9 Fixed Service Editing 2025-10-09 17:22:43 +02:00
78846fd85a Added Contract Column 2025-10-09 17:04:49 +02:00
33411c4e7d Added Errors for Plant and Contract 2025-10-09 17:02:30 +02:00
0a9731be37 Fixed Ansprechpartner Loading 2025-10-09 17:02:19 +02:00
7bf29ffe32 Fix Set Booked State 2025-10-07 15:05:39 +02:00
ce8e35b628 Merge branch 'beta' into 'main'
2025.19.3

See merge request fedeo/software!19
2025-10-06 14:39:21 +00:00
c85b61000a Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!18
2025-10-06 14:35:43 +00:00
39820d7122 Fix Composing 2025-10-06 15:51:40 +02:00
4054d1acf8 Fix Change Phase 2025-10-06 15:49:23 +02:00
02330e2173 Fix TaxType 2025-10-06 15:47:53 +02:00
7ab53557ce Merge branch 'beta' into 'main'
2025.19.2

See merge request fedeo/software!17
2025-10-05 15:34:57 +00:00
50f1ed8d41 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!16
2025-10-05 15:30:41 +00:00
f9b69be791 Cahnge token lifespan 2025-10-05 17:24:01 +02:00
c092696a9a Added E-Mail Account Saving 2025-10-05 17:23:39 +02:00
d1cf575845 Fixed Contract Modal Buttons 2025-10-05 17:23:09 +02:00
ce3f6323ea Tidying 2025-10-05 16:56:51 +02:00
008d96d0b8 Added Contract to Createddocuments 2025-10-05 16:56:41 +02:00
473980d6b4 Added Zahlungsart 2025-10-05 16:56:25 +02:00
625bb4be4a Added Banking Link to show Document 2025-10-05 16:15:51 +02:00
f74b717bc0 Added E-Mail Account Adding/Editing 2025-10-05 16:02:22 +02:00
ffb91586a0 Merge branch 'beta' into 'main'
2025.19.1

See merge request fedeo/software!15
2025-10-05 13:17:24 +00:00
103499a163 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!14
2025-10-05 13:00:58 +00:00
1fd568c562 Tidying 2025-10-05 15:00:21 +02:00
b61a3de40a Fixed Vorschau Reloading 2025-10-02 19:29:06 +02:00
c729c595ee Fixed Edit in Modal 2025-10-02 19:25:59 +02:00
f05c222c42 Fixed Product Creation Error 2025-10-02 19:23:42 +02:00
3b9c77f321 Merge branch 'devCorrected' into 'beta'
Revert "Deleted Loggin Output"

See merge request fedeo/software!13
2025-10-01 17:25:30 +00:00
ccf25a69f0 Deleted Loggin Output 2025-10-01 19:20:31 +02:00
5744e19344 Revert "Deleted Loggin Output"
This reverts commit 29a6d885b4.
2025-10-01 19:19:53 +02:00
29a6d885b4 Deleted Loggin Output 2025-10-01 19:18:25 +02:00
80d1d205a1 Merge branch 'devCorrected' into 'beta'
Added ENV Var for PDF Viewer License

See merge request fedeo/software!12
2025-10-01 17:08:24 +00:00
f81d9ebb40 Added ENV Var for PDF Viewer License 2025-10-01 19:04:25 +02:00
4588c2b9e9 Fixed Mailer 2025-09-29 20:49:29 +02:00
22ce0d6e7a Fixed Mailer 2025-09-29 20:47:25 +02:00
26cb53b231 Secrets 2025-09-29 20:45:22 +02:00
183313b550 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!11
2025-09-29 17:03:50 +00:00
f917d8e370 Added Types
Corrected Return
2025-09-29 18:50:35 +02:00
fc6e76d756 Added Types 2025-09-29 18:47:21 +02:00
8750ccbb7d Removed Critical Log Output 2025-09-29 18:40:22 +02:00
87411d9b87 Added Number Ranges to DataTypes with Holder in Creation 2025-09-29 18:40:13 +02:00
1dfcc694ef Added Types 2025-09-28 17:57:28 +02:00
67a86ed9ab Added ENV Var for PDF Viewer License 2025-09-28 17:49:22 +02:00
5f31d0a89c Added PDF Viewer 2025-09-28 17:47:26 +02:00
6d6f8b419a Added Types for Tickets 2025-09-28 17:47:17 +02:00
2cfdafa507 Added Env Vars 2025-09-28 17:47:03 +02:00
6d0f83fab1 Disabled TIckets 2025-09-28 17:46:55 +02:00
bae41efdf5 Changed to PDF Viewer 2025-09-28 17:46:41 +02:00
467410af6a Added API Base VAR for Runtime Config 2025-09-28 17:46:23 +02:00
5b09a11a6e Added API Base VAR for Runtime Config 2025-09-28 17:45:46 +02:00
72f8064d06 Fixed E-Mail Sending for New Api 2025-09-28 17:45:28 +02:00
a222da1970 Fixed Spaces 2025-09-28 17:45:10 +02:00
3cbd6b265e Removed Env Vars from Compose 2025-09-28 17:44:43 +02:00
9aefa5d08f Added E-Mail Sending Features 2025-09-28 17:44:31 +02:00
f51ced1c31 Added Types for Tickets 2025-09-28 17:44:17 +02:00
f683f149e6 Added S3 Init Function 2025-09-28 17:44:00 +02:00
7482e974f2 Introduced Crypt Functions 2025-09-28 17:43:44 +02:00
83fc24be0c Introduced Secrets Manager 2025-09-28 17:43:21 +02:00
4e61b20ba8 Removed 2025-09-28 14:32:21 +02:00
1c3c6adcc2 Removed Supabase 2025-09-28 14:32:11 +02:00
2a2078a85f Removed Supabase 2025-09-28 14:28:19 +02:00
3e17709998 Removed Supabase 2025-09-28 14:27:29 +02:00
e2b24964bc Removed Supabase 2025-09-28 14:26:51 +02:00
cfcbdfb749 Removed Supabase 2025-09-28 14:24:58 +02:00
fbf8e883d9 Removed Supabase 2025-09-28 14:24:52 +02:00
969fa60518 Removed Supabase 2025-09-28 14:24:46 +02:00
63ee3c7b9f Removed Supabase 2025-09-28 14:24:40 +02:00
6bb1222e0b Removed Supabase 2025-09-28 14:24:32 +02:00
baa59f16ce Removed Supabase 2025-09-28 14:24:25 +02:00
906e893bf0 Removed Rights 2025-09-28 14:24:17 +02:00
c68fe87622 Removed Old Update and Create 2025-09-28 14:21:19 +02:00
7851dd80ca Removed Old Update and Create 2025-09-28 14:21:12 +02:00
c95cc818b5 Removed IncomingInvoices Create 2025-09-28 14:20:02 +02:00
25483dd8f2 Removed TrackingTrips 2025-09-28 14:19:06 +02:00
c81ab45919 Merge branch 'devCorrected' into 'beta'
fixed archived filtering

See merge request fedeo/software!9
2025-09-26 16:22:38 +00:00
4d9b1f1dff Removed Archived 2025-09-26 18:13:17 +02:00
3aed09d023 Removed package-lock.json
Migrated Capacitor
2025-09-26 17:47:28 +02:00
68763996ad Removed Nuxt Content Module 2025-09-26 17:46:59 +02:00
347bc0cf7b Removed PDF Viewer Size 2025-09-26 17:46:26 +02:00
29b12cdc46 Changed Toolbar in PDFViewer 2025-09-26 17:46:02 +02:00
0e03a5914b Renamed useErrorLogging.js to prevent error from nuxt 2025-09-26 17:45:25 +02:00
936c6cdf00 Introduced PDFViewer 2025-09-25 18:32:20 +02:00
efba076c41 Removed some Entries in EntityShow because not working 2025-09-25 17:21:13 +02:00
7f0f6567b0 DocumentDisplay On Mobile 2025-09-25 17:20:54 +02:00
e5696191af Tidying 2025-09-25 17:20:33 +02:00
62c403307f Fixed some Redirects for Mobile
Fixed other Login stuff on mobile
2025-09-25 17:19:46 +02:00
7f2a6ba438 Added Maintenance Modes TO APP 2025-09-25 16:45:37 +02:00
9afd5c3bf5 Fixed Redirect Loop when already on /mobile 2025-09-25 16:45:25 +02:00
dd2bf7a8ff Added ArchiveButton.vue 2025-09-24 19:43:04 +02:00
4a05bd5bc3 removed dev output 2025-09-24 19:33:07 +02:00
850ef006c5 fixed archived filtering
added tabs to incominginvoices
added Badges to tabs
2025-09-24 19:33:00 +02:00
ce39e96c0a added Filters and templatecolumns in datatypes 2025-09-24 19:32:25 +02:00
2366790cc0 Added Link Button to Createddocuments when linked document is Present 2025-09-24 17:52:55 +02:00
4fa752f7c9 Changed App Version for IOS 2025-09-24 17:51:12 +02:00
76e1a333dc Added Capacitor isNative Function 2025-09-24 17:51:02 +02:00
0da2beaa64 Added Capacitor preferences 2025-09-24 17:50:46 +02:00
40c12ae487 Added Capacitor preferences 2025-09-24 17:50:41 +02:00
2656341956 Added Capacitor preferences 2025-09-24 17:50:34 +02:00
9894f003e3 Added new Auth Store 2025-09-24 17:50:20 +02:00
b57b11ca9a Added Redirect to mobile Index 2025-09-24 17:50:02 +02:00
46d0cbfd41 Removed not Functioning Cards 2025-09-24 17:49:49 +02:00
a207237bc2 Renamed EntityTableMobile.vue 2025-09-24 17:49:27 +02:00
476b4b5d4f Fied Display Welcome 2025-09-24 17:49:08 +02:00
911f5f049a Implemented Native Token Storage other than Cookies 2025-09-24 17:48:56 +02:00
5588d5a76c Fixed Layout Issues 2025-09-24 17:48:29 +02:00
0ae247ac98 Added Capacitor CORS 2025-09-24 16:55:18 +02:00
8480ce1512 Fixed Error when no IDs are present 2025-09-20 19:07:03 +02:00
07b914675d Fixed Error when no IDs are present 2025-09-19 17:08:59 +02:00
64d4569655 Removed Archived Filtering 2025-09-19 17:08:43 +02:00
8ab20cee51 Merge branch 'devCorrected' into 'beta'
Added Archived Showing,

See merge request fedeo/software!8
2025-09-19 14:01:35 +00:00
3f4636d698 Removed Driver from Vehicles until Users is built 2025-09-19 15:35:17 +02:00
cc9f7e58ec Removed Type Filtering, reduntant to tabs 2025-09-19 15:30:53 +02:00
168ebead92 Added Archived Showing,
Fixed Archived Filtering
2025-09-19 15:30:33 +02:00
f79c569383 Added Archived Showing,
Fixed Archived Filtering
2025-09-19 15:26:05 +02:00
b67e4cc6d2 Fixed Files Loading 2025-09-18 17:13:45 +02:00
cb19fa63f8 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!7
2025-09-17 19:29:32 +00:00
fd3ac28ae2 Fixed IncomingInvoices 2025-09-17 21:25:20 +02:00
e504ca60ce Fixed SerialInvoices 2025-09-17 21:25:11 +02:00
d3f70942b2 Changed Resources with Info 2025-09-16 18:34:48 +02:00
51ca916056 Merge branch 'devCorrected' into 'beta'
Fixed Createddocuments in Projects

See merge request fedeo/software!6
2025-09-16 16:34:08 +00:00
4ae55a4956 Fixed Createddocuments in Projects
Fixed FinalInvoices
2025-09-16 18:29:20 +02:00
a23376c727 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!5
2025-09-16 16:26:17 +00:00
55ac79c717 Corrected Archiving UX 2025-09-16 17:34:07 +02:00
95a122c4bf Fixed Loading Error at Login/Tenant Switch 2025-09-16 17:33:57 +02:00
f30ccd6c45 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!4
2025-09-15 15:43:41 +00:00
cd1d9f4cf2 Added Message 2025-09-13 18:13:19 +02:00
caa92843b3 Changed Backend URL 2025-09-13 18:11:09 +02:00
717eaf0851 Added Cors Hosts 2025-09-13 18:10:43 +02:00
975acb9833 Merge branch 'devCorrected' into 'beta'
Dev corrected

See merge request fedeo/software!3
2025-09-13 15:39:19 +00:00
85479435b0 Added created_by Error 2025-09-13 17:34:42 +02:00
d134f5541d Fixed Loading in Edit 2025-09-13 17:32:28 +02:00
18b63272b7 Changes 2025-09-13 17:32:21 +02:00
e60c0df77b Changed Error Page 2025-09-13 17:14:50 +02:00
eea7937225 Added Pinning to Standard Entity 2025-09-13 17:08:30 +02:00
9fad0eafd0 Added Archiving to Created Documents 2025-09-13 16:32:37 +02:00
91a62f88d2 Added Cors Hosts 2025-09-13 16:23:53 +02:00
8f0e5ad37a Merge branch 'devCorrected' into 'beta'
Added select-special

See merge request fedeo/software!2
2025-09-13 14:05:05 +00:00
cf42caa519 Changed Backend to Hosted 2025-09-13 16:00:31 +02:00
8f7ce314fb Changed host 2025-09-12 19:51:05 +02:00
d45d16a810 Changed host 2025-09-12 19:50:34 +02:00
0fe16ad79e Changed types 2025-09-12 19:40:53 +02:00
af1bf82c75 Changed Dockerfile 2025-09-12 19:30:01 +02:00
9b6af75e98 Changed Dockerfile 2025-09-12 19:28:35 +02:00
f35d375c5f Changed Dockerfile 2025-09-12 19:27:21 +02:00
522af71f9b Changed tsconfig 2025-09-12 19:23:18 +02:00
1dfc1fd2c1 Changed tsconfig 2025-09-12 19:22:05 +02:00
a9d0a060b8 Changed tsconfig 2025-09-12 19:21:24 +02:00
d3104bb5a2 Changed tsconfig 2025-09-12 19:18:51 +02:00
800b7d883e Changed Dockerfile 2025-09-12 19:16:37 +02:00
6d190d2501 Changed Dockerfile 2025-09-12 19:13:50 +02:00
0f03deeadd Changed Dockerfile 2025-09-12 18:59:08 +02:00
fc46e807e7 Added Running Config 2025-09-12 18:38:13 +02:00
c98394b5bf Changes 2025-09-12 18:29:13 +02:00
95e8da2e39 Fix Porfile Index 2025-09-12 18:28:53 +02:00
a19aa47e34 Fixed Accounts for new Backend 2025-09-12 18:22:15 +02:00
00d9361aad Fixed Createddocuments Sort 2025-09-12 18:13:24 +02:00
577e5d947a Added MaxLength 2025-09-12 18:07:05 +02:00
1985f7b725 Minor Change in E-Mail New 2025-09-12 17:45:02 +02:00
30da08689d Implemented new Backend in E-Mail Sending
Implemented Blob Return Method for File Download
2025-09-12 17:42:24 +02:00
9a210bceb9 Added navigate to archive in use entities
added archiving in incoming invoices
2025-09-07 19:45:45 +02:00
8302b00a9c Corrected displayIncomeAndExpenditure.vue displayOpenBalances.vue 2025-09-07 19:39:14 +02:00
0d918b8719 added createdletters 2025-09-07 19:36:37 +02:00
278afb5ee8 Fix partly email 2025-09-07 19:34:24 +02:00
c7e18d689e Fix Createddocs 2025-09-07 19:32:59 +02:00
6d233f5bfb Fixed incoming EDIT, useFiles, useFunctions, usePermission 2025-09-07 19:29:41 +02:00
949b094490 Fixed auth, NAV, projecttypes,numberranges,tenant,textemplates 2025-09-07 19:26:46 +02:00
34c5764472 Added select-special
Fixed Banking Page
2025-09-07 19:20:50 +02:00
9f59b94336 Removed Unused Files 2025-09-07 19:10:08 +02:00
f5f2949545 Added Export Page 2025-09-07 19:09:23 +02:00
01e3d878d3 Changed Main NAV 2025-09-07 19:09:15 +02:00
45741a6295 New Backend changes 2025-09-07 19:04:39 +02:00
42edd626fd New Backend changes 2025-09-07 18:59:08 +02:00
af86fdd8ed New Backend changes 2025-09-07 18:59:04 +02:00
dc385b8422 Changes 2025-09-02 19:11:05 +02:00
8d5e158d59 New Backend changes 2025-09-02 19:10:59 +02:00
7c4272ffe9 Changes 2025-09-02 18:47:44 +02:00
27af6a0953 New Backend changes 2025-09-02 18:47:12 +02:00
97a095b422 Changes 2025-08-31 18:29:29 +02:00
6d76acc0bc New Backend changes 2025-08-31 18:28:59 +02:00
aeaba64865 Initial commit 2025-08-29 14:44:56 +00:00
c492442d3d Merge branch 'refs/heads/beta' 2025-07-31 21:02:08 +02:00
b0497142ed Tidying 2025-07-31 21:01:05 +02:00
95b1c16cf1 Added Title to FinalInvoice Load Mode 2025-07-31 20:46:15 +02:00
7265164b0a Tidying 2025-07-31 20:38:38 +02:00
841bb67d60 Added Title Transfer Sums 2025-07-31 20:38:30 +02:00
c70cd797ac Rebuild Inovice LoadModes 2025-07-31 20:37:51 +02:00
80d68e0aa9 Tidying 2025-07-31 20:34:29 +02:00
23573caceb Changed Global Messages to Modal 2025-07-31 20:28:35 +02:00
661e826767 Added Description 2025-07-31 20:20:35 +02:00
8989975be1 Added Steuertyp Error 2025-07-31 20:20:17 +02:00
a4d68cafd8 Combined Rechnungen, Abschlag and Storno 2025-07-31 17:52:35 +02:00
472ee0fd53 Added TempStore to createddocuments and incominginvoices 2025-07-31 17:52:17 +02:00
86a12cc223 Added net Sums to Tax 2025-07-24 15:20:42 +02:00
01469d97f0 Added Tabs to Createddocuments Index 2025-07-23 20:38:27 +02:00
c58b6ca2aa Fix displayIncomeAndExpenditure.vue 2025-07-23 20:33:46 +02:00
58ef4ec620 Removed Unused Function 2025-07-22 17:46:27 +02:00
e3c3a5c444 Changed Supabase Selects 2025-07-22 17:46:00 +02:00
a7336999ef Fixed Scroll 2025-07-22 17:45:37 +02:00
0e3586dccf Some Fixes in Times 2025-07-22 17:45:16 +02:00
2490c69beb Added Vars in texttemplates.vue 2025-07-22 17:44:50 +02:00
986f27a48c Added Multiple Tax Percentages
Fixed Pagebreak colspan
2025-07-22 17:44:09 +02:00
6bc8e4e7f0 Fixed Scroll 2025-07-22 17:13:34 +02:00
dcac52ff9d Fixed Gebucht in IncomingInvoice Edit 2025-07-15 16:28:36 +02:00
6cfaf3fdbe Fixed scroll in EntityShowSub.vue 2025-07-01 10:41:47 +02:00
10c686bfa1 Corrected Set Booked 2025-06-30 14:23:22 +02:00
f77ffeaf4c Added Remove Vendor 2025-06-30 14:11:20 +02:00
ff6ee91075 Added Errors to IncomingInvoices
Added Vorbereitet to IncomingInvoices
2025-06-30 13:45:06 +02:00
93d0f97a56 Added search to serialinvoices 2025-06-27 18:23:54 +02:00
e5b4409df7 Fixed Storno 2025-06-27 18:02:22 +02:00
f4fae86d31 Added Display for cancelled Invoices 2025-06-27 17:46:17 +02:00
fe8aa2e758 Added getIsPaid 2025-06-26 11:37:51 +02:00
0209c49503 Changes 2025-06-26 11:37:39 +02:00
2b1e2a12eb Changes 2025-06-26 11:37:28 +02:00
e98e1dc634 Changed some in EntityShowSubCreatedDocuments.vue 2025-06-26 11:36:54 +02:00
583ca15dcc Added Sorting and Display Rendering 2025-06-26 11:35:17 +02:00
1264b987fe Added openSum 2025-06-26 11:34:46 +02:00
ffec9b7dc4 Fixed Overdue Invoices 2025-06-26 11:34:04 +02:00
a742fc8c54 EDIT Graph 2025-06-26 11:33:29 +02:00
cf8845848a Changed Redirect after Create 2025-06-26 11:31:30 +02:00
6b1e697209 Added Dev Only Logging 2025-06-26 11:31:16 +02:00
a6b9247305 Added Dev Only Logging 2025-06-26 11:31:12 +02:00
bfc11bcace Added AI Suggestion to IncomingInvoice Create 2025-06-07 16:44:27 +02:00
132016f562 Added AI Suggestion to IncomingInvoice Create 2025-06-07 16:34:44 +02:00
96cd94d77f Cahnges in Projektzeiten and Naming 2025-06-07 16:33:57 +02:00
bab0187467 Fix for Leistungszeitraum in SerialInvoices 2025-06-07 16:33:16 +02:00
8d4ff3c88e Added Features to Mobile Styling 2025-06-03 16:35:30 +02:00
dd89a70789 Corrected Markup Calc in Products 2025-06-03 16:35:10 +02:00
50e583389e Merge branch 'refs/heads/beta' 2025-06-01 18:37:27 +02:00
ef8e0d0e83 Added Tenant Changing in App 2025-06-01 18:30:57 +02:00
08f104d4c9 Added Times Entry for in project Display 2025-06-01 18:23:34 +02:00
4346fbffe5 moved times/index 2025-06-01 18:23:14 +02:00
03231ab3f3 added displayRunningTime.vue 2025-06-01 18:23:01 +02:00
80f05c7029 moved displayRunningTime.vue to displayRunningWorkingTime.vue 2025-06-01 18:22:41 +02:00
5199afb9b5 Changes to Word Breaking 2025-06-01 18:22:20 +02:00
3fa50c158c Merge branch 'refs/heads/beta' 2025-05-31 20:45:35 +02:00
cdbf1785a3 Added Pagebreak to Workintime Evaluation 2025-05-31 20:45:04 +02:00
3eadedcb19 removed vite pwa 2025-05-31 20:10:49 +02:00
4e469a3ffc fix for optional and alternative 2025-05-31 20:10:38 +02:00
f93eb9ec71 Added Markup Percentage for Products 2025-05-31 20:10:14 +02:00
eb37257ae8 Some Corrections For Mobile 2025-05-31 20:09:36 +02:00
bb99d5ae1c Added Optional and Alternative in Quotes
Redesigned the Row Edit Modal
Added Errors for Optional and Alternative Rows
2025-05-25 16:39:03 +02:00
e982684d95 Removed Required for Lastname in Customers 2025-05-25 16:39:03 +02:00
e2e368e9f8 Removed Log Output 2025-05-25 16:39:03 +02:00
4a3c4f4bd7 Added Support for Conditional Rendering of Inputs
Added Some InputColumns in anticipation of required InputColumns
2025-05-25 16:39:02 +02:00
fd3b96a11c Added Support for Conditional Rendering of Inputs 2025-05-25 16:39:02 +02:00
9d3aba9179 Added Name Edition to Company Customers 2025-05-25 16:39:02 +02:00
1b30825b76 Fixed BCC when no standard E-Mail is present 2025-05-25 16:39:01 +02:00
6f6754db39 Rechnungs E-Mail Adresse zu Kunden hinzugefügt 2025-05-25 16:39:01 +02:00
f1eb857eda Removed Text Justify in GlobalMessages.vue 2025-05-25 16:39:01 +02:00
cad2ed9dba Added Editing for Automatic Working Hour Corrections 2025-05-25 16:27:56 +02:00
c94e4c3194 Added Optional and Alternative in Quotes
Redesigned the Row Edit Modal
Added Errors for Optional and Alternative Rows
2025-05-25 15:54:08 +02:00
6599fdc6c2 Removed Required for Lastname in Customers 2025-05-25 15:53:14 +02:00
f5431ddade Removed Log Output 2025-05-20 17:15:01 +02:00
835486d04f Added Support for Conditional Rendering of Inputs
Added Some InputColumns in anticipation of required InputColumns
2025-05-20 17:13:06 +02:00
db53043b19 Added Support for Conditional Rendering of Inputs 2025-05-20 17:12:35 +02:00
131a7a95b9 Added Name Edition to Company Customers 2025-05-20 16:52:17 +02:00
6a68c2fbcd Fixed BCC when no standard E-Mail is present 2025-05-20 16:24:16 +02:00
5606858dc7 Rechnungs E-Mail Adresse zu Kunden hinzugefügt 2025-05-20 16:24:02 +02:00
b17af2620b Removed Text Justify in GlobalMessages.vue 2025-05-20 16:18:47 +02:00
55038d2bcb Added Salutation and Title to Customer and Contact
Added Vars to GenerateContext for Salutation and title
2025-05-18 19:09:32 +02:00
a0ffdce1bb Added Saldo and Count 2025-05-13 17:06:55 +02:00
a9ea5982cf Fixed Profile Selection 2025-05-13 15:59:02 +02:00
557d0eff2f Fixed Texttemplates and Title Import from Copy 2025-05-13 15:43:59 +02:00
241a912f08 Added Statements to OwnAccounts 2025-05-08 11:08:00 +02:00
caba8348bf Added rows length condition to auswertung 2025-05-07 19:15:59 +02:00
3332b7f258 Cahnged Color 2025-05-07 19:15:41 +02:00
f1ef5fce58 Fixed Typo 2025-05-07 19:15:34 +02:00
b647518d2f Fixed Driver 2025-05-03 12:52:52 +02:00
834f96f258 Added Display for Dokubox 2025-05-02 17:03:35 +02:00
57b7d0b35b Fixed Booking Sum 2025-04-30 15:13:06 +02:00
1679bfdc7d Changed Colors
Added IncomingInvoicesUnfinishedCount
2025-04-24 15:42:58 +02:00
d9dc7c67be Changed Linebreak in Dashboard Accounting 2025-04-24 15:36:24 +02:00
3986c9cd0a Added Search to Files 2025-04-24 15:34:34 +02:00
e71118ad09 Corrected Assigning 2025-04-24 15:19:22 +02:00
795a8662f9 Fixed Auswertung Calc 2025-04-24 15:03:05 +02:00
81165c6954 Added Filter Saving in Banking 2025-04-24 14:56:53 +02:00
c419fe610d Added Filters to Banking 2025-04-24 14:53:55 +02:00
87dc8ead08 Added Dots to useCurrency.js 2025-04-23 09:16:50 +02:00
c231994ad1 Added Count to Eingangsbelege Index 2025-04-22 15:32:30 +02:00
fe8902ba7f Added Separation to Offen/Überfällig 2025-04-22 15:32:19 +02:00
f4f28c095d Merge branch 'dev' into beta
# Conflicts:
#	package-lock.json
2025-04-22 14:47:06 +02:00
50a22877e4 Added Default Payment Method to Vendors
Added Auto Select to Create incoming
2025-04-22 14:46:12 +02:00
1b2bc1424d Added Account Sorting 2025-04-22 14:38:20 +02:00
e6c9942d7d Added File Sorting 2025-04-22 14:37:41 +02:00
3998c997d4 Fixed Create Vendor 2025-04-22 14:18:22 +02:00
207ed7ce36 Fixed Search for Customers in Banking 2025-04-22 14:12:30 +02:00
415dbed67a Merge branch 'dev' into beta 2025-04-22 09:38:06 +02:00
e3196460b9 Fixed Open Calc 2025-04-22 09:37:59 +02:00
34b566343f Fixed Calc on select 2025-04-22 09:37:47 +02:00
7b4d6d7858 Merge branch 'dev' into beta 2025-04-21 13:40:20 +02:00
b310d96482 app changes 2025-04-21 13:40:07 +02:00
9877206825 tidying 2025-04-21 12:20:48 +02:00
7fd088b758 Removed Wrap 2025-04-21 12:20:30 +02:00
8220db14f3 Fixed Document Number in banking 2025-04-19 21:25:21 +02:00
dc79787f70 fixed tab change with arrows 2025-04-19 21:12:13 +02:00
0272c9434c fixed update 2025-04-19 21:11:54 +02:00
658e3f66b3 Merge branch 'dev' into beta 2025-04-19 17:48:52 +02:00
0d8f403058 added report saving
added var lohnkosten to generatecontext
2025-04-19 17:48:42 +02:00
4623bb3bb7 Merge branch 'dev' into beta 2025-04-19 17:35:12 +02:00
ec741acf7c Added THs 2025-04-19 17:34:58 +02:00
8edd996b89 Added Document Report 2025-04-19 17:34:45 +02:00
d762bb3228 Added Personal Composition 2025-04-19 17:34:29 +02:00
842afcfdb2 Added Hourrates 2025-04-19 17:34:02 +02:00
e17f8aa734 Merge branch 'refs/heads/dev' into beta 2025-04-17 14:10:14 +02:00
577b2dd035 Removed Paid and DueDate in Drafts 2025-04-17 14:09:58 +02:00
8f6ca1593e added archived filtering 2025-04-17 14:07:58 +02:00
7e95547eb1 Removed debug info 2025-04-17 14:07:49 +02:00
8d39c16e26 Added displayBankaccounts.vue 2025-04-17 14:00:00 +02:00
fa25923e62 Removed Bank Bookings from displayOpenBalances.vue
Added displayBankaccounts.vue
2025-04-17 13:54:32 +02:00
b9cd79cabe Fixed Mode in StandardEntityModal.vue 2025-04-17 13:47:05 +02:00
6049fcbb1b Added Tenant sorting 2025-04-16 12:44:14 +02:00
93b36be42c Displaying correction
Fixed Week selector
2025-04-16 12:44:06 +02:00
e21e46b2c8 Changed Open Sum Calc in displayOpenBalances.vue 2025-04-14 19:06:19 +02:00
78b16d2c1c Merge branch 'dev' into beta 2025-04-14 18:56:54 +02:00
9afe62eae5 Changed DC.yml 2025-04-14 18:56:44 +02:00
bd2fe9bb03 Merge branch 'dev' into beta 2025-04-14 15:11:25 +02:00
dd10760da1 Fix Calc in Banking Index
Show Document Ref instead of Partner Ref
2025-04-14 15:11:18 +02:00
3bf54a4e32 Merge branch 'dev' into beta 2025-04-14 13:21:19 +02:00
7750415e9a Fix Table Select 2025-04-14 13:21:10 +02:00
ba1aa84ac4 Merge branch 'dev' into beta 2025-04-14 13:13:38 +02:00
9d89b547b2 Fix Table Select 2025-04-14 13:13:27 +02:00
808df9a6ad Merge branch 'dev' into beta 2025-04-14 10:15:30 +02:00
abc10a77f8 Changed to HTML 2025-04-14 10:15:23 +02:00
bc5ba9c352 Merge branch 'dev' into beta 2025-04-12 12:31:53 +02:00
554f871a1a Added IncomingInvoices to Accounts 2025-04-12 12:31:47 +02:00
af90c6fcf3 Fixed DocumentUpload and Loading in Files 2025-04-12 12:06:52 +02:00
977e9e0344 Fixed DocumentUpload and Loading in Files 2025-04-12 12:01:21 +02:00
b924c92908 Added TabIndex to Route in Standard Entity 2025-04-12 11:30:51 +02:00
b7ff4a915b Added Rights for DocumentBoxes 2025-04-12 11:26:00 +02:00
56334128ae Corrected Type 2025-04-12 11:25:50 +02:00
fe14591e02 Changed Document Upload to the Upload Modal 2025-04-12 11:25:38 +02:00
ab7f8b0846 Made Card Fullscreen 2025-04-12 11:25:18 +02:00
bb4aea512d Changed PDF Detection 2025-04-12 11:25:08 +02:00
f49d6f45b6 Added DocumentBoxes 2025-04-12 11:24:48 +02:00
74be47b84c Merge branch 'dev' into beta 2025-04-11 18:31:32 +02:00
1a29b47583 Added Pages for Accounts with Allocation Listings 2025-04-11 18:31:16 +02:00
d3b4f217e2 Added Pages for Accounts with Allocation Listings 2025-04-11 18:31:10 +02:00
cfcfceb9e8 Adde Back Button 2025-04-11 18:30:49 +02:00
5b913799d0 Umami Umstellung 2025-04-09 20:24:45 +02:00
f0c96f5e10 Corrected TaxType Saving and Display of DeliveryDate when "Kein Lieferdatum anzeigen" 2025-04-09 20:24:30 +02:00
e45ff93100 Counter in BankingStatement 2025-04-08 15:33:04 +02:00
05b47fa2e9 tidying 2025-04-05 19:55:34 +02:00
c53154d115 tidying 2025-04-05 19:46:16 +02:00
e79d034c2c Added Glitchtip 2025-04-05 19:36:44 +02:00
1136d59f20 Added Glitchtip 2025-04-05 19:36:39 +02:00
b1ed0b17ed added globalmessages removed condition on Search Button 2025-04-05 19:36:10 +02:00
8604a18ed5 moved filter 2025-04-05 19:35:36 +02:00
267b68283f added globalMessages 2025-04-05 19:35:16 +02:00
a47426af70 Added Statement Description to Allocations 2025-04-01 16:19:13 +02:00
035b5f2eac removed PhasesTemplates Remainings 2025-04-01 16:18:42 +02:00
70000c776e removed getDocumentSum Function 2025-04-01 16:18:21 +02:00
8593720c15 corrected isphone loading 2025-04-01 16:17:45 +02:00
9f5409cc70 added redirectToMobileIndex.ts 2025-04-01 16:17:28 +02:00
621c61c094 added type change and rounding to useSum 2025-04-01 16:17:07 +02:00
b5287f4a7d Merge branch 'devWithMobile' into dev 2025-03-30 16:52:06 +02:00
5f483b4980 Corrected Icons in Footer 2025-03-30 16:51:48 +02:00
ff5593ebc5 Added Redirect if User is not on Mobile Index 2025-03-30 16:51:38 +02:00
684f65250f Capcitor/Xcode Config / REDO 2025-03-28 18:01:28 +01:00
7a986189df Added Mobile Rendering to EntityShow 2025-03-28 17:25:31 +01:00
a3bec6a9b1 Introduced EntityShowSubComponents 2025-03-28 17:05:08 +01:00
ea6181265d Added Right Side to Toolbar 2025-03-28 17:04:57 +01:00
65e1025289 Removed Path Subconfig in nuxt.config.ts 2025-03-28 17:04:46 +01:00
a144bba54b Introduced EntityTable 2025-03-28 17:04:27 +01:00
7f8efcf32f Added Scroll Class 2025-03-28 17:04:12 +01:00
95968681ce Changed Supabase Select Query for Projects 2025-03-28 17:04:00 +01:00
338bcd0df9 Conditional Rendering 2025-03-28 15:02:59 +01:00
eeec6d2f92 Simplified GetIsPhone Return 2025-03-28 15:02:50 +01:00
6ea58d7990 Changed Bottom Spacing 2025-03-28 15:02:15 +01:00
7d3cba7e79 Added Orientation 2025-03-28 15:01:56 +01:00
f4ab1b382d Renaming 2025-03-28 15:01:46 +01:00
d9a2384701 Added Assets for Splash and App Icon 2025-03-28 15:01:37 +01:00
235f3690cb Disabled Devtools and Hot Reload 2025-03-28 15:01:11 +01:00
548e85a00a Added Phone Redirect 2025-03-28 15:00:56 +01:00
4d2401ff59 Fixed Setup Query 2025-03-28 15:00:38 +01:00
eac0a6573a Removed User Zoom 2025-03-28 15:00:24 +01:00
c77198625c Fixed Setup Query 2025-03-28 15:00:03 +01:00
b3fd996f3f Some Changes 2025-03-27 20:36:08 +01:00
830c71ada7 Added ContractNumber Field 2025-03-27 16:10:50 +01:00
4fbde89251 Added Filters to Serial Invoice 2025-03-27 16:00:56 +01:00
9eae5740a8 Corrected Error in SerialInvoice Creation 2025-03-27 15:30:37 +01:00
c019b2608c Changed Date Type in contracts
AAdded Sign Date and SEPA Date
2025-03-27 15:25:17 +01:00
57a4512a0e Changes 2025-03-27 14:33:34 +01:00
7deffc885e Merge branch 'dev' into devWithMobile 2025-03-25 15:17:34 +01:00
b33ad857b1 Added Archiving 2025-03-24 19:07:26 +01:00
207d9ce5f6 Corrected Spacing on Right Side 2025-03-24 18:54:39 +01:00
ee1aa1bbed Changed Booking
Changed Columns in Incoming Invoices
2025-03-24 13:33:39 +01:00
856ebd1b60 Cahnges to make compatible 2025-03-24 11:55:32 +01:00
bb7d7ae3a9 Cahnges to make compatible 2025-03-24 11:54:56 +01:00
5418c0195e Added Plugins for Network, Device and Plattform 2025-03-23 16:35:37 +01:00
35c98ebb97 Corrected Display for Created Documents 2025-03-23 16:35:19 +01:00
6cb9653dea Changed Sidebar Footer to Full W 2025-03-23 16:10:58 +01:00
f0550f5233 Added OneSignal 2025-03-23 16:08:15 +01:00
f21cd63367 Made Index Scrollable 2025-03-23 15:21:17 +01:00
325c37e26e Made Hilfe&Info Sticky 2025-03-23 15:21:07 +01:00
4ff9fed3df Added OneSignal Cordova 2025-03-23 15:20:50 +01:00
ff69796eab Added Assets 2025-03-23 15:20:42 +01:00
cd7cecb61b Remove package-lock.json 2025-03-23 14:47:52 +01:00
e85a1def90 Added CAp Dir 2025-03-23 13:18:27 +01:00
ba72a896bb Added Capacitor 2025-03-23 13:17:03 +01:00
b5b1741f21 Added Ability to Book to Customers and Vendors 2025-03-23 13:09:56 +01:00
19a5d906da Removed temp Store in Datastore 2025-03-23 13:08:56 +01:00
34e6fb7b64 Added Customer to Events 2025-03-23 13:08:45 +01:00
bd15ac8b2d Added Kostenstellen und Buchungskonten 2025-03-21 14:53:27 +01:00
76e207cd20 Introduced OwnAccounts
Introduced CostCentres Rebuild
Added EntityModalButtons to IncomingInvoice Create
2025-03-21 14:24:05 +01:00
bd1916bdaa Corrected Column Rendering 2025-03-21 12:23:06 +01:00
3dcaeb1d4f Corrected Some Rendering
Corrected Booking Sum
2025-03-21 12:20:02 +01:00
f1fa6d2762 Corrected Title Colspan 2025-03-18 09:53:09 +01:00
bb4b9734aa Added PV Mode for Documents 2025-03-18 09:52:58 +01:00
78b0ee6f9b Added Compatibility Check for Old Documents
Added Width to Product and Service Select
2025-03-18 08:16:41 +01:00
6d234ffb5b Corrected Error 2025-03-17 14:09:05 +01:00
be4f586613 Merge branch 'refs/heads/dev' into beta 2025-03-16 19:14:15 +01:00
02b8768f8e Fixed CustomSurcharge and Introducted Input Price 2025-03-16 19:13:58 +01:00
38be27a89c Fix in Archived Filtering 2025-03-16 18:39:36 +01:00
cff5d9be29 Rebuild Document Copy Process 2025-03-16 18:36:12 +01:00
11594e6dc7 Fixed Create Change 2025-03-16 18:36:00 +01:00
44e8ce0105 Made SellingPrice Total required in Services 2025-03-16 18:01:21 +01:00
ea7ffaca5d Removed Steuer & Rabatt from Direct View 2025-03-16 18:00:57 +01:00
898663344b Added Create,Edit,Show to Products and Services 2025-03-16 18:00:37 +01:00
15a889dfcf Added Archivable Prop to Datatypes wich are archivable
Added Filtering to SupabaseSelect Composable for Archived
2025-03-16 17:36:16 +01:00
5442accb48 Merge branch 'refs/heads/dev' into beta 2025-03-14 18:32:47 +01:00
b7d64b1f9e Added Tenant Filtering 2025-03-14 18:32:36 +01:00
aa849f591b Added Standard File Type in Folders and Made it optional 2025-03-14 18:07:21 +01:00
c3fcb83da2 Added Remove Button to CostCentre in IncomingInvoices 2025-03-14 17:54:15 +01:00
0d75678ffd Merge branch 'refs/heads/dev' into beta 2025-03-14 17:50:12 +01:00
d907f1ca01 Added Modals for Adding, Editing, Showing 2025-03-14 17:49:54 +01:00
e42dd9cc6e Removed Title from Freitext
Correct Advance Invoice Loading
2025-03-12 10:54:03 +01:00
14e4e79c43 Corrected Usesum 2025-03-10 14:59:54 +01:00
d83f31c3c2 Introduced central Sum for Createddocuments 2025-03-09 18:09:20 +01:00
6f47b12501 Added Editing to IncomingInvoices 2025-03-09 17:51:54 +01:00
99cd9dd195 Added Filter in banking to show only booked incominginvoices 2025-03-09 17:51:30 +01:00
5fb62313d4 Added Customer, Vendor to Events 2025-03-09 17:39:25 +01:00
41aa40b237 Added Open Tasks Card
Added Some Titles
2025-03-05 19:12:31 +01:00
1f5d4434be Removed Logs 2025-03-05 19:12:14 +01:00
3d83100eda Added Categorie Input to Tasks 2025-03-05 19:12:03 +01:00
29860684cb Fixed displayIncomeAndExpenditure.vue 2025-03-05 18:54:13 +01:00
4c6cce416d Added Filter "nur offene anzeigen" 2025-03-04 17:20:18 +01:00
449386a906 Fix Quickaction Buttons 2025-03-04 17:14:12 +01:00
3be4b4eb50 Merge branch 'beta' into dev 2025-03-04 17:10:36 +01:00
f8af82d0a8 Zurück zu Übersicht hinzugefügt 2025-03-04 17:09:49 +01:00
855f527a87 Fixed TaxType, Added Taxtype to Quotes and Confirmation Orders 2025-03-04 17:07:22 +01:00
69c056a35f added conditional rendering to checkbox 2025-03-04 15:53:12 +01:00
11e43e6a9b Added Disabling for moving 2025-03-04 15:52:30 +01:00
8b49ae373e Removed SerialInvoices
Added Clearing after Edit
2025-03-04 15:44:33 +01:00
b084f13f0c Corrected Banking page
Added Scroll
2025-03-04 15:37:38 +01:00
c616fe1154 Added Document Number trycatch 2025-03-03 11:36:27 +01:00
89cb068db6 Added Missing Unit Error
Added Surcharge in Saving
Added Contact Auto Loading when just one is present
2025-03-03 11:35:50 +01:00
c96ff65bc7 Added CustomSurchargePercentage
Added RowEdit Modal
2025-03-03 11:31:44 +01:00
57b631ad72 Added Required to Taxpercentage in Products and Services 2025-03-03 10:59:59 +01:00
7980efec50 Added UST Input for Services 2025-03-03 10:56:25 +01:00
79c1d2d361 Fixed Link in Incoming Invoice Create 2025-02-27 11:53:31 +01:00
ba0646e768 Added Push Notification for Ticket Messages 2025-02-27 09:37:08 +01:00
58a9d72946 Added Columnrendering for Driver 2025-02-27 09:28:00 +01:00
f3ce52d247 Added Start End Filtering for Texttemplates in Invoices 2025-02-27 09:21:18 +01:00
08acee792b Added texttemplates Archiving 2025-02-27 09:21:03 +01:00
183bac752e Corrected MainNav Textvorlagen 2025-02-27 09:20:52 +01:00
dc587029e1 Changed Textemplate Edit from Slideover to Modal 2025-02-27 09:11:14 +01:00
bb7cd71a07 Added TaxType automatic correction
Added Conditional Rendering for DocumentTotal in Editor
2025-02-27 08:49:41 +01:00
bb68f29d4d Corrected Christmas Logo 2025-02-27 08:49:07 +01:00
1b1ef80983 Corrected Ad Blue Calc
Added Adblue Usage and Price to Agridata Modal
2025-02-26 09:56:03 +01:00
eb99adafab Merge branch 'dev' into beta 2025-02-25 23:04:50 +01:00
96341185c0 Added some columnrenderings
Added InventoryitemGroups
Removed some stuff from dataStore
2025-02-25 23:01:00 +01:00
07e7c43a2e Merge branch 'dev' into beta 2025-02-24 04:00:23 +01:00
f1e50f7c38 Changed Abschlagszahlungen to Auto Create Position 2025-02-24 03:59:51 +01:00
288163aded Merge branch 'dev' into beta 2025-02-21 17:51:58 +01:00
4e37915516 Restructured Ticket Chat 2025-02-21 17:51:44 +01:00
53b13bc6a5 Added Reasons to absencerequests 2025-02-21 17:51:35 +01:00
ff27862bdd Added standard phases to projecttype creation 2025-02-21 17:33:03 +01:00
3eda49d468 remodel of texttemplates Screen 2025-02-21 17:30:03 +01:00
62e891f594 Restructured Banking Allocation 2025-02-21 17:19:42 +01:00
2ed4d9d1d4 Fixed Dropdown in incominginvoices 2025-02-21 17:19:26 +01:00
1beeb4ea6d Added 19 USTG 2025-02-21 17:17:40 +01:00
48ee83a77e Fixed Scroll 2025-02-21 16:43:51 +01:00
f01b307865 Added Ticket Badge 2025-02-21 16:37:08 +01:00
f315c920e7 Corrected Adblue Processing 2025-02-21 16:37:00 +01:00
79c169ef2a Introduced useSum Composable 2025-02-19 19:06:02 +01:00
ac6aa470af Corrected Vendor Link 2025-02-19 19:04:15 +01:00
e8263a8bb5 Corrected Archived in displayProjectsInPhases.vue 2025-02-19 13:08:34 +01:00
7c64447c9d Fixed Dropdown Truncation 2025-02-18 22:12:15 +01:00
2a728e0ee7 Fixed IsPaid for Incoming 2025-02-18 22:10:55 +01:00
855e137376 Added Search Temp Save 2025-02-18 22:10:23 +01:00
074985a2ac Fixed Colors 2025-02-18 22:10:10 +01:00
eedc8fe67f Fixed Counter Bankstatements 2025-02-18 22:04:53 +01:00
c63bcb9246 Merge branch 'dev' into beta 2025-02-16 18:47:35 +01:00
efd929b153 Removed JS Print Manager Packet 2025-02-16 18:47:21 +01:00
a7ed13ae9a Merge branch 'dev' into beta 2025-02-16 18:36:21 +01:00
8ffc070622 Removed JS Print Manager Packet 2025-02-16 18:36:06 +01:00
9e67654272 Changed Color in EntityEdit 2025-02-16 18:35:51 +01:00
d81d2d89d4 Added Required to Absence Reason 2025-02-16 18:35:34 +01:00
c1591099c6 Changed Event Title with Project 2025-02-16 18:35:13 +01:00
a84f906d43 Changed Recipient Rendering to Array 2025-02-16 18:34:54 +01:00
f6c1dc1cab Added startDateTime.vue endDateTime.vue
Added components to Events Datatype
2025-02-14 17:21:20 +01:00
e448bac0d8 Tidying 2025-02-14 17:17:51 +01:00
1c22561acc Fixed Archived 2025-02-14 17:16:36 +01:00
e1ee5a89f0 Merge branch 'dev' into beta 2025-02-13 18:26:37 +01:00
c75e97252c Fixed Calendar Display 2025-02-13 17:55:28 +01:00
20ec16c3d1 Added Adblue Calculation 2025-02-13 17:44:29 +01:00
36a039f72e Added Absence Reason Schulung 2025-02-13 17:44:17 +01:00
21f5b17673 XXXX in Entwurf for Dates 2025-02-13 17:36:06 +01:00
d675eb96a6 Some Fixes in Tickets 2025-02-13 17:30:00 +01:00
4401692de0 Merge branch 'dev' into beta 2025-02-12 10:20:08 +01:00
85ffd61c16 Added Required Values 2025-02-12 10:19:50 +01:00
abbc8bf945 Merge branch 'dev' into beta 2025-02-11 19:23:58 +01:00
291762a49e Changes 2025-02-11 19:23:30 +01:00
05d2575544 Changed Title of Absences 2025-02-11 19:23:14 +01:00
83b2120ef5 Added Sick Days 2025-02-11 19:23:02 +01:00
b0d99f0a86 Added Status to Tickets
Fixed Telegram Notification in Tickets
Added Ticket Closing
2025-02-11 19:22:52 +01:00
80af069669 Added Status to Tickets
Fixed Telegram Notification in Tickets
Added Ticket Closing
2025-02-11 19:22:49 +01:00
1dc71d5791 Added Sick Days 2025-02-11 19:22:05 +01:00
abc5b8ff38 Fixed Shortcuts 2025-02-10 12:11:46 +01:00
6dc61da519 Changed Base URL 2025-02-10 09:15:45 +01:00
52f3eb15a7 Introduced Help Tickets 2025-02-09 23:22:40 +01:00
524b4b17c8 Correction in workingtime pdf 2025-02-09 23:22:15 +01:00
c109344b07 Changed HelpSlideover 2025-02-05 22:16:19 +01:00
15e480d26d Corrected Document Loading 2025-02-05 22:16:06 +01:00
807503c88f Reactivated Kein lieferdatum 2025-02-05 15:11:09 +01:00
d05d5c687c Changes in Übernehmen Buttons 2025-02-05 15:10:49 +01:00
8b6aa4b502 Introduced Temp for Columns and Filters
Implemented Filters in createddocuments
2025-02-05 15:10:08 +01:00
d586f8d9a6 Added Titlesums Correction 2025-02-04 14:48:08 +01:00
4f72c53648 Added TitleSims 2025-02-04 12:45:50 +01:00
4bcce61829 Changes in EntityEdit Query Loading 2025-02-04 12:45:32 +01:00
aebfe7dac6 Added Events to Projects
Added Events to EntityShow.vue
2025-02-04 12:45:11 +01:00
2e45ff3561 Tiny Fixes in Workingtimes Evaluate 2025-02-03 20:17:02 +01:00
d3d7799812 Tiny Fixes in Profile Editor 2025-02-03 20:16:49 +01:00
984831eaff Added Ability to jump to Abgeschlossen 2025-02-03 20:02:42 +01:00
ceb0f7c33e Tidying
Added Color Check
Added Due Date = Date
Changed UST and Price
Relabled Button to Betrag aufteilen
2025-02-03 20:00:45 +01:00
75b6e630fb Tidying in Removing
Added ID Check to SetPosnumber
2025-02-03 19:59:54 +01:00
70cca68f30 Added Edit for Filetype 2025-02-03 18:11:22 +01:00
2a63ff827b Added File Download
Added File Loading Screen
2025-02-03 18:06:20 +01:00
ce52c983a8 Changed Soll stunden
Added dieser monat bis heute
2025-02-03 11:41:27 +01:00
cc81d18344 Changed Back Button 2025-02-03 11:05:47 +01:00
c207329a3e Introduced TempStore to Store SearchStrings and Selections etc 2025-02-03 11:05:35 +01:00
f4b0964e25 Merge branch 'beta' 2025-02-02 19:35:27 +01:00
162f516647 Added Document Date to Title pos
Added Automatic timespan
2025-02-02 19:34:38 +01:00
e459c0a4e5 Added Taxtypes
Added 13b
Added Error Check for Timespan
2025-02-02 18:30:37 +01:00
d5378f1f83 Added Plant 2025-02-02 18:13:35 +01:00
1d5471f463 Table Tidying 2025-02-02 18:12:03 +01:00
673f1b8d58 Table Tidying 2025-02-02 18:11:59 +01:00
85c7a18b1a Added Plant to createddocuments
Added Timespan to createddocuments
Changed Info to Array
Removed some Datastore remains
2025-02-02 18:11:48 +01:00
6676d6314b Changed Title of + Dokuemnt 2025-02-02 13:18:10 +01:00
8048d73551 Changes in CreateDocument Index 2025-02-02 13:04:40 +01:00
88103c01eb Cahnges in File Upload 2025-02-02 13:04:27 +01:00
06aa639ae4 Cahnged Hochladen to + Datei 2025-02-02 11:53:26 +01:00
7385fc1a7e Added Archived Sorting to Banking 2025-02-02 11:53:12 +01:00
3711fa75cf Added Badge In Serial Invoices 2025-01-31 12:33:12 +01:00
66feca3323 Added Archived
Changed fucntions baseurl
2025-01-31 11:15:35 +01:00
148619eb05 Changed PDF Creation to Functionsserver 2025-01-30 14:32:23 +01:00
30a658b083 added Tenant ordering 2025-01-29 18:12:02 +01:00
4f45bd2b76 Changed None to keine phase 2025-01-29 18:11:51 +01:00
5098332d20 Restructuring 2025-01-29 18:11:39 +01:00
335a01d448 Changed Sum to use 2025-01-29 18:11:27 +01:00
61de673741 Corrected Color in Error Alert in Whitemode
Added Firstname and Lastname to Customer disabled when company
2025-01-29 18:10:59 +01:00
7ea5da8d0f Cahnges in Show Document 2025-01-27 18:14:43 +01:00
eb1eacdad9 Changes in Statement Allocation and Open Sum Calc 2025-01-27 18:14:33 +01:00
3188409bac Changes in Props 2025-01-27 18:14:03 +01:00
cbc1f5a84d Merge branch 'dev' into beta 2025-01-24 15:26:00 +01:00
6139f12e0d Cahgned Buttons 2025-01-24 15:25:40 +01:00
ce4efeaf2d Made Titel and Message Mandatory 2025-01-24 15:25:27 +01:00
7dcddba401 Changes in Dashboard Display
Added Project Card
Commented Graph Card
2025-01-24 15:25:16 +01:00
0bd894a61b Added Phases in Project Rendering 2025-01-24 15:24:53 +01:00
ab3437989a Merge branch 'beta' 2025-01-23 16:07:48 +01:00
540fe100cf Cahnged Base Url for Functions 2025-01-23 16:07:33 +01:00
b388f29df5 Merge remote-tracking branch 'origin/main' 2025-01-23 16:04:58 +01:00
b3f1295809 Merge branch 'beta' 2025-01-23 16:04:31 +01:00
80a465fad8 Added DP to Bankstatements 2025-01-23 16:04:01 +01:00
00e7e7e01e Rebuild Banking Integration to Function Server 2025-01-23 16:03:17 +01:00
43a28c9b2e Merge branch 'beta' into 'main'
Version 1.1.1

See merge request fedeo/software!4
2025-01-23 10:40:39 +00:00
06c775f563 Cahnged Ticket Creation 2025-01-22 18:32:02 +01:00
2f9bad0f8f Added Sort Column 2025-01-22 18:31:50 +01:00
ec9fae9e5a Added Sort Column 2025-01-22 18:31:39 +01:00
9efa596059 Changes in workingtimes 2025-01-21 09:54:05 +01:00
01914fcc1b Merge branch 'beta'
# Conflicts:
#	components/DocumentDisplay.vue
#	components/MainNav.vue
#	composables/useRole.js
#	composables/useSupabase.js
#	pages/createDocument/edit/[[id]].vue
#	pages/files/index.vue
#	pages/inventoryitems/index.vue
#	pages/services/[mode]/[[id]].vue
#	pages/vendors/[mode]/[[id]].vue
#	stores/data.js
2025-01-20 11:05:22 +01:00
33818033a4 Changes 2025-01-20 11:01:28 +01:00
e655d9cecd Cahngees in Cards 2025-01-20 11:00:52 +01:00
94d61a6d52 Changed usenextnumber from supabase to fs 2025-01-20 10:07:30 +01:00
830e5d3602 Introduced Functions Composable 2025-01-20 09:58:25 +01:00
ada3143ffb Removed Unused 2025-01-20 09:58:03 +01:00
879a8da1f4 Added Right Check to Kontakte
icon Changes
2025-01-20 09:57:56 +01:00
96a66f24e6 Tidying in Table 2025-01-20 09:57:33 +01:00
f6aae59e72 Corrected E-Mail link 2025-01-20 09:57:20 +01:00
9758c7b406 Removed DEV Output 2025-01-19 18:40:06 +01:00
bec94e4c6a Added Update Emit after Phase Change 2025-01-18 21:05:27 +01:00
e71281dcee Fixed Problems in LinkedDocuments and Storno 2025-01-17 18:49:19 +01:00
6626bd568d Added Function to Cancel Invoices 2025-01-17 17:26:59 +01:00
cdb3131185 Added E-Mail loading from Customer 2025-01-17 11:53:25 +01:00
57c0f81263 Added Archiving 2025-01-17 11:27:18 +01:00
b40553cc54 Changed NumberRange to EdgeFunction 2025-01-13 20:19:57 +01:00
62b29fb644 Changes in Evaluate 2025-01-13 11:27:36 +01:00
5555239fb3 Cahnges in E-Mail Creation 2025-01-11 19:04:24 +01:00
b2bc69f782 Changed Create Link 2025-01-11 18:59:39 +01:00
eff2b8a0bb Introduced UploadModal from Component 2025-01-11 18:59:22 +01:00
f2d4107172 Cahnges 2025-01-11 18:58:54 +01:00
0911678db0 Added own file for incominginvoice creation 2025-01-11 18:58:36 +01:00
120aa12727 Removed PWA Comment 2025-01-11 18:58:08 +01:00
f48bcdcd7b Removed Filetags 2025-01-11 18:57:56 +01:00
39b6ad28d0 Changes in Create New Item 2025-01-11 18:57:35 +01:00
2bd8b2b03a Changes in Document Display Modal 2025-01-11 18:57:05 +01:00
0d4d5227a0 Added DocumentUploadModal 2025-01-11 18:56:52 +01:00
07909550c3 Fixed Calendar 2025-01-11 18:54:04 +01:00
4f7bffe6fe Changed FileBucket 2025-01-11 18:53:47 +01:00
f3590ebf7c Changes 2025-01-11 12:41:02 +01:00
8b1950fc54 Changes 2025-01-11 12:29:15 +01:00
9a61a309a7 Changes 2025-01-11 12:25:13 +01:00
542f941879 Changes in DocumentDisplayModal.vue 2025-01-10 16:27:26 +01:00
c57ea6add6 Corrected key in checks 2025-01-10 16:20:56 +01:00
0d949c7894 Commented Own Fields 2025-01-10 16:19:02 +01:00
f6accf0aa7 Changes 2025-01-10 15:52:49 +01:00
1ecf89a2b1 Added Search to materialComposing.vue 2025-01-10 14:15:50 +01:00
630d04fb46 Added Sorting to createddocument Loading 2025-01-10 14:15:39 +01:00
8a00dffce0 Added CustomerRef to HistoryItemGen 2025-01-10 14:15:22 +01:00
423733967e Added Loading for DeliveryDate 2025-01-10 14:15:06 +01:00
38c6e01d9f Added Customer Ref
Added Document Error in createddocuments
2025-01-09 11:50:13 +01:00
566efb0d2c Added Trailing to Datatypes
Added Description Loading in LinkedDocument
2025-01-09 11:31:26 +01:00
06725ee497 Corrected Setting Error for Product in createddocument 2025-01-08 10:03:40 +01:00
4df416a818 Corrected Error in Dashboard 2025-01-07 16:57:34 +01:00
26fef320ef Corrected Errors in Workintimes
Introduced Error Logging to Supabase
2025-01-07 16:43:24 +01:00
ff1685a721 Added rounding and total calc to materialComposing.vue 2025-01-07 15:36:12 +01:00
0baba63542 Added MaterialComposition
Added Service Categorie Rendering
2025-01-07 15:25:02 +01:00
2c175ad1b2 Removed logs 2025-01-07 14:46:29 +01:00
cd190132f9 Fix in Filters 2025-01-07 14:29:44 +01:00
0bab558c75 Updated in Files and Archiving 2025-01-07 12:42:00 +01:00
af38afc5b5 Added Reloaded after File Upload
Change selection for Images
2025-01-07 12:03:14 +01:00
96184e4853 Fixed Document Upload 2025-01-07 11:22:03 +01:00
46fb5de260 Added tenant log when no role is Present 2025-01-07 11:21:55 +01:00
ceed45e409 Added Filters for Archived 2025-01-07 11:21:37 +01:00
8d9aed0ee1 Added Drafts
Tidying in displayOpenBalances.vue
2025-01-07 10:28:28 +01:00
308e9629b5 Corrected Event Handler for ContactPerson 2025-01-07 10:17:28 +01:00
778308940a Corrected absencerequests
Corrected RecreationDays to 2025
2025-01-07 10:17:03 +01:00
5ee5e671e7 Added start/enddate rendering 2025-01-07 10:16:36 +01:00
7f23d8efd8 Added File Moving 2025-01-05 22:11:42 +01:00
639cdb4c1f Added File Moving 2025-01-05 21:59:04 +01:00
3a4b1a7a56 Corrected Tabs 2025-01-05 21:16:31 +01:00
c103e9402a Corrected Show File Modal 2025-01-05 21:16:13 +01:00
a326c6ab3a Fix in Calendar 2025-01-05 21:15:51 +01:00
efbb97967a Many Changes 2025-01-05 18:23:44 +01:00
1c6c6e4a33 Corrected 2025-01-03 11:01:06 +01:00
159b77334b Corrected 2025-01-03 10:50:20 +01:00
cae33c92c9 Added Scroll to createDocument Show 2025-01-03 09:32:49 +01:00
cbdf3edd8d Added Birthday & EntryDate 2025-01-03 09:32:32 +01:00
8723540b0d Added GetAvailableStringVars 2025-01-02 19:23:31 +01:00
4f395a01d3 Added Link to MAWEBFORM Only for Tenant 21 2025-01-02 19:08:14 +01:00
b877d5f91b Changes in Timetracking
Added Lieferscheine Button to EntityShow
DataType Changes
Fixed Loading Errors in createDocument
Added Telephone to profile show
2025-01-02 14:04:55 +01:00
595531683b Fixed Calendar Link 2024-12-31 23:56:54 +01:00
6989dd61f7 Fixed Calendar & Timeline
Fixed AbsenceRequest Manual Selects
2024-12-31 23:40:12 +01:00
4ef7d410f4 Changes 2024-12-31 18:02:15 +01:00
cacdb442ca Fixed Invoicing Problem and some other Changes 2024-12-31 16:07:15 +01:00
9a70879778 Changes 2024-12-30 11:22:58 +01:00
d5ef6469bd Changes 2024-12-30 10:44:55 +01:00
f44ac5960c Changes 2024-12-30 10:44:30 +01:00
803b155f65 Changes 2024-12-30 10:35:20 +01:00
a55d4817bd Changes in CreatedDocuments Edit 2024-12-30 09:32:44 +01:00
da3dc1e663 DataType Changes 2024-12-30 09:31:28 +01:00
9884a08e83 Tidying in Document Display 2024-12-30 09:31:03 +01:00
cd463bd1d1 Added Billing to Nav 2024-12-30 09:30:47 +01:00
ee5ebfe0b9 Changes in Standard Entity Components 2024-12-30 09:30:30 +01:00
92ec684066 Added Rendering Components 2024-12-30 09:30:14 +01:00
4dcce6238f Fixed Profile Edit 2024-12-28 14:44:07 +01:00
98619a9082 Fixed Profile Edit 2024-12-28 14:41:50 +01:00
334ee2f897 Removed Big Comment 2024-12-28 14:34:25 +01:00
156ca348aa Tidying and Datatype Changes 2024-12-28 14:34:07 +01:00
8cfa5eaa43 Cleared Deprecated Folder 2024-12-28 14:33:36 +01:00
d3c865a10f Minor Changes 2024-12-27 12:22:37 +01:00
42686efbe7 Added PLZ Autocompletion & InputChangeFunctions 2024-12-27 12:14:27 +01:00
a1e6061579 Fixed Creation Error 2024-12-27 11:37:32 +01:00
2bda15d264 Deprecated Events as non Standard Entity
Repaired query
Reparied Document Show
2024-12-27 11:05:59 +01:00
43c7148637 Removed Deprecated Components 2024-12-27 11:04:07 +01:00
1a79f9dbd9 Deprecated absencerequests as non Standard Entity
Added Respective Rights
Changes to show title
2024-12-25 17:00:09 +01:00
325d25034d Minor Changes, Tidying, Removed Module Google Fonts 2024-12-25 16:21:55 +01:00
4a0e092115 Deprecated following as non standardEntity checks, inventoryitems, spaces 2024-12-25 16:21:22 +01:00
1ba3d9c3e9 Deprecated following as non standardEntity tasks, products, productcategories, services, servicecategories 2024-12-22 22:13:34 +01:00
61110da453 Introduced EntityEdit.vue
Introduced EntityShow.vue

Deprecated following as non standardEntity contracts, customers, vendors, projects, plants, vehicles
2024-12-22 17:00:21 +01:00
565d531376 Introduced EntityEdit.vue
Introduced EntityShow.vue

Deprecated following as non standardEntity contracts, customers, vendors, projects, plants, vehicles
2024-12-22 16:59:18 +01:00
b465f4a75a Introduced ProfileStore
Corrected All Links to DataStore
2024-12-21 22:33:42 +01:00
813944fc23 Changes in Rights and Roles 2024-12-21 18:53:53 +01:00
c8521ad1f6 Reparied Cancel Button 2024-12-20 20:00:23 +01:00
0d849f5fcb Reffractored vendors,vehicles,services,servicecategories,products,productcategories, projects, plants, customers, contracts, contacts 2024-12-20 20:00:08 +01:00
8abfce0fa1 Revert "Start for Dev Branch"
This reverts commit acf5d1c2ea
2024-12-20 17:49:24 +00:00
acf5d1c2ea Start for Dev Branch 2024-12-20 18:46:52 +01:00
a6c1eaf69f Changed users to profiles in projects 2024-12-18 20:47:01 +01:00
756bfb8478 Introduced EntityList.vue and implemented it in projects 2024-12-18 20:46:28 +01:00
1af63705ff Updated vue & nuxt 2024-12-18 20:44:20 +01:00
9e02e1f99d Fixed Error in Working Times Display 2024-12-18 08:35:29 +01:00
05a4ecc654 Change in Table Expansion 2024-12-11 21:56:05 +01:00
847d8512e7 Updated Nuxt Ui 2024-12-11 20:59:43 +01:00
6b8bf96bec Added Bank Account Updating 2024-12-11 20:47:19 +01:00
586a2f5fc1 Added Bank Account Updating 2024-12-04 10:45:41 +01:00
ff5d20421e Added Checks
Added Documents to Inventoryitems and Checks
2024-12-01 23:10:34 +01:00
8e68858489 Added Christmas Logo 2024-12-01 13:44:55 +01:00
a6712f7c98 Changes 2024-12-01 13:44:38 +01:00
9993217ed1 Corrected Errors in Document Creation 2024-11-25 19:43:57 +01:00
5928c34b03 Added Button for DeliveryNote Invoicing 2024-11-21 21:56:27 +01:00
5817931099 Added Shoe Size to Profile 2024-11-21 21:56:07 +01:00
024adba069 Update file [[id]].vue 2024-11-21 20:35:23 +00:00
c093f9d191 Some Changes 2024-11-21 21:28:29 +01:00
60658a2ef9 Added Agriculture
Removed Prices for Delivery Notes
2024-11-21 21:27:40 +01:00
3633b8ee37 Some Changes 2024-11-20 23:00:43 +01:00
46ddd5659b Some Changes 2024-11-20 22:55:55 +01:00
f9ab8f4050 Some Changes 2024-11-20 22:42:13 +01:00
4b6857958c Some Changes 2024-11-20 22:33:16 +01:00
d9a22518da Some Changes 2024-11-20 21:23:15 +01:00
818bfd5b1c Some Changes 2024-11-20 21:10:20 +01:00
fbf1f78d64 Added Chats 2024-11-20 21:08:50 +01:00
59424f067c Removed Edit button when document is finished 2024-11-20 13:49:16 +01:00
869f381a1b Introduced New Projecttypes and Phase Display 2024-11-20 13:48:13 +01:00
988c33d07d Tidying in Product Page 2024-11-20 13:45:18 +01:00
fc9984eb3a Added Fields to Vehicles 2024-11-20 13:44:54 +01:00
ce4a6fcd67 Removed WorkingTimeEvaluation in Profile Page
Added Document Upload to Profile
2024-11-20 13:44:32 +01:00
60c2870123 Added TODO 2024-11-20 13:43:41 +01:00
1c15f02d34 Removed Unused Tabs 2024-11-20 13:43:20 +01:00
0140ffaf5e Added Projecttypes 2024-11-20 13:42:12 +01:00
2899633436 Added UUID Package 2024-11-20 13:41:50 +01:00
9812c0a122 Moved Selection for Letterhead 2024-11-20 13:41:36 +01:00
947fe710a3 Added Agriculture Module to Invoice Creation 2024-11-13 19:13:45 +01:00
a246263424 Added Notifications
Added Mentions in HistoryItems
2024-11-08 20:18:50 +01:00
f69b30b5ba Added Notifications
Added Mentions in HistoryItems
2024-11-08 20:17:56 +01:00
928e4d0ca9 Moved Dark Mode 2024-11-08 18:56:41 +01:00
45a81d90df Moved Dark Mode 2024-11-08 18:54:42 +01:00
27ccfd2c72 Some Tidying 2024-11-08 18:42:42 +01:00
a61382486f Added Archiving & Some Tidying in Vehicles 2024-11-08 18:38:54 +01:00
c67c4a70c4 Added Archiving 2024-11-08 18:19:50 +01:00
53b59bd95f Integration of MS365 Login into Login 2024-11-08 17:45:34 +01:00
3a55c80ae2 Tidying in Plants 2024-11-08 17:45:14 +01:00
34533b401c Change in Numberranges of data type 2024-10-29 15:58:35 +01:00
ecf7110781 Changes in Workingtimes 2024-10-29 15:53:59 +01:00
d3a7f0636b Changes in text templates 2024-10-20 16:34:45 +02:00
13c91fe728 Changes in Times Evaluation 2024-10-20 16:28:28 +02:00
d3c2c8f642 Changes in Times Evaluation 2024-10-20 16:14:01 +02:00
a905b1f966 Changes 2024-10-18 08:58:11 +02:00
8dbf43b672 Changes in Contact Request 2024-10-08 17:22:55 +02:00
ca15cfbd0b Changes 2024-10-08 16:08:30 +02:00
2cbc30de6f Changes 2024-10-08 14:30:52 +02:00
163c6232b8 Changes 2024-10-08 14:30:11 +02:00
3efbd9065a Changes 2024-10-08 14:12:04 +02:00
7fb9744847 Cahnges 2024-10-08 13:57:23 +02:00
06badd590c Added Email-Signature to Profile 2024-10-08 13:53:36 +02:00
e1f94cfb72 Changes 2024-10-08 13:37:03 +02:00
16932ad71c Changes 2024-10-08 13:08:13 +02:00
856628bce6 Fixed #51 2024-10-06 13:20:41 +02:00
1e1f82cc2d Added Searching in Service & Product Selection
Introduced Service Categories
2024-10-05 12:28:56 +02:00
0f92dbe61c Standard BCC in E-Mail 2024-10-01 13:48:52 +02:00
42bfb2d8eb Changed Search Function 2024-10-01 13:06:21 +02:00
f461bb7694 Tidying 2024-10-01 13:06:10 +02:00
da62213579 Added E-Mail Sending 2024-09-30 18:49:43 +02:00
c1282c613e Added E-Mail Sending 2024-09-30 18:34:33 +02:00
782f97afcc Added E-Mail Sending 2024-09-30 18:34:16 +02:00
a75506b183 Some Serial Invoice
Free Text Pos
Minor Changes
2024-09-29 19:17:26 +02:00
5d7714519a Pushed Serial Invoices 2024-09-29 19:02:35 +02:00
082573f6d9 Added Document Linking
Minor Changes in Document Creation
2024-09-29 17:57:38 +02:00
ffea7627cf Rendering Corrections 2024-09-29 11:40:03 +02:00
f9995505a8 Help Slideover and trackingtrips 2024-09-27 21:12:47 +02:00
f31e76ac3a Help Slideover 2024-09-27 13:02:45 +02:00
2458b3573f Some Changes in Pos Numbering 2024-09-26 18:56:19 +02:00
3c5f80f8b5 Some Changes in Pos Numbering and Problem Display 2024-09-26 18:42:17 +02:00
eee6060e58 Added Error Blocking in Invoice Creation 2024-09-25 19:38:00 +02:00
b0f186dc4e Added Error Display in Invoice Creation 2024-09-25 19:35:22 +02:00
db3880a31a Minor Changes 2024-09-25 19:30:55 +02:00
673bcb279a Added Scrollcontainer 2024-09-25 19:29:35 +02:00
805ed8e9f4 Changes in Layout 2024-09-22 14:14:02 +02:00
51dbb10b45 Changes in Tracking and some changes in general historyitems 2024-09-22 14:10:04 +02:00
516bf5dd7f Pushing Tracking 2024-09-22 12:09:39 +02:00
fc0a6eaa76 Minor Changes in InventoryItems 2024-09-20 12:28:04 +02:00
93a2d96c21 Minor Changes in Invoice Creation 2024-09-19 19:34:41 +02:00
c546e2cf3c Added Productcategories 2024-09-18 20:00:12 +02:00
1e4d0153b4 Letterhead Change & Minor Changes in Doc Creation 2024-09-16 19:07:51 +02:00
d5c2ed86f7 Changes 2024-09-14 17:22:07 +02:00
c70b172b08 Changes 2024-09-14 17:09:29 +02:00
6f965b1704 added allocating for more than one bankstatement 2024-09-06 21:15:26 +02:00
9cad48cb31 Started open stuff card 2024-09-06 21:15:00 +02:00
5a8d8fd5a9 Bug Fixe in Invoice Date 2024-09-06 20:10:54 +02:00
acd8fc8290 Implemented Document Folders 2024-09-06 12:16:50 +02:00
c8265aebea QUick fixes in inventoryitems 2024-09-06 09:43:02 +02:00
ab7df093c4 Added Umami 2024-09-03 11:40:37 +02:00
876c8774fa Minor Changes 2024-08-30 18:36:18 +02:00
26f8d8f710 Restructuring in Project Page
Added Project Number
2024-08-29 22:31:34 +02:00
133fd1d892 Reactivated Phases 2024-08-29 20:43:15 +02:00
60a860d9b5 Tidying 2024-08-28 21:49:51 +02:00
725fe01b69 Change to Edge Functions 2024-08-28 21:39:29 +02:00
3b8aeb3b87 Changes 2024-08-27 17:27:58 +02:00
22d99ba39e Changes 2024-08-26 18:34:54 +02:00
5e4118acdd Changes 2024-08-25 20:46:59 +02:00
734c9ec8f3 Added Chart to HomeScreen 2024-08-25 20:45:52 +02:00
4127c6d4fb Added HistoryItems to Spaces
Changed Loading of HistoryItems to directly Supabase
Minor Changes in inventoryitems
2024-08-23 17:39:42 +02:00
b71814369d Light Restructuring 2024-08-23 12:09:53 +02:00
1c76d8e63c Changed Notification Position 2024-08-23 12:09:35 +02:00
7457abb173 Added Title to Document Creation
Added Height Calc for Row Description
2024-08-22 19:48:11 +02:00
a349b1eb4f Changed Listing in Spaces Index 2024-08-22 19:12:18 +02:00
0bc0a59939 Changed Listing in Spaces Index 2024-08-21 19:10:00 +02:00
75963ce8b4 Added Space Type "Standort" 2024-08-21 19:04:29 +02:00
db096e462c Added Numberrange to inventoryitems 2024-08-10 16:30:43 +02:00
9846e91c2f Changes in Numberranges 2024-08-10 14:39:26 +02:00
98d14a3e34 Changed NumberRange Structure to Tenant JSON Column 2024-08-10 14:25:56 +02:00
4ded159c49 Cahnges in Invoice allocation in statements 2024-08-09 11:52:11 +02:00
baa06b60fb Cahnges 2024-07-30 12:01:07 +02:00
c776100ed0 Removed Draft Filtering
Changed Invoice Sorting
2024-07-22 11:01:20 +02:00
8f7cfe8362 Added Page Title 2024-07-21 22:57:42 +02:00
d915cce277 Added Highlighting for Accordions 2024-07-21 22:53:17 +02:00
d31478a858 Corrected Scrollbar 2024-07-21 22:48:20 +02:00
dc480f38b6 Changes in Tags and Updating 2024-07-21 22:34:10 +02:00
510ce73ecd Corrected Minor Issues with Taxing 2024-07-21 22:33:27 +02:00
91bc893001 Remodeled MainNav with an extra component 2024-07-21 22:32:46 +02:00
269c5f4b59 Restructured Pages
Added parent Spaces
2024-07-20 14:50:58 +02:00
013ae3c69c Added Quantity to Invetoryitems 2024-07-17 10:27:20 +02:00
de327850ae Added Quantity to Invetoryitems 2024-07-17 10:20:16 +02:00
53000988d9 Changed Docker Loading 2024-07-14 21:52:26 +02:00
02beb31e43 Changed Docker Loading 2024-07-14 21:27:29 +02:00
c27fd4cd2d Changes 2024-07-14 21:26:46 +02:00
fc0caf2d00 Changes 2024-07-12 22:20:45 +02:00
d01869fca8 Many Changes 2024-07-12 22:05:48 +02:00
1e952e008e Some Changes in Projects 2024-07-10 21:22:24 +02:00
f9909a87aa Changes in Events
Changes in Times
2024-07-10 21:10:39 +02:00
dc3c8a2b60 Some Changes 2024-07-09 21:53:11 +02:00
eb4b8a8d9b Some Changes 2024-07-09 21:35:19 +02:00
101b8d3361 Added Styling to Balance in Account Table 2024-07-04 19:04:34 +02:00
669f50de6c Changed Bas URL 2024-07-04 18:28:10 +02:00
10c6e72d87 Changes in Texttemplate Creation 2024-07-03 14:23:32 +02:00
d327277bac Changes 2024-07-02 18:38:11 +02:00
d2da96eaa3 added clothing size to Profile Page 2024-07-02 18:19:52 +02:00
e8b75956d0 added articleNumber to barcode input 2024-06-14 17:23:09 +02:00
de1fe083d8 added column 2024-06-14 17:00:44 +02:00
de70c4ca80 Changed Toast Position to Bottom Right 2024-06-10 19:26:04 +02:00
bc24f49476 Many Changes in Navigation, Shortcuts, Search and Data Pulling directly from Supabase 2024-06-05 14:34:23 +02:00
e139eb771c Changes 2024-06-02 21:39:26 +02:00
4cd4a9c7c4 Added Contract to Document Assign 2024-05-31 11:18:10 +02:00
26d0148525 Added Contract to Document Assign 2024-05-23 10:31:07 +02:00
eca1cd380b Changes 2024-05-23 10:27:52 +02:00
28ff274107 Many Changes 2024-05-13 10:43:23 +02:00
491d502c40 Many Changes 2024-05-10 22:41:32 +02:00
7966173385 Added Reset in Inventory
Added Keyboard Controls for Tab Nav and Back to List
Added Select On Enter for First Entry in Search

for Customers and Vendors
2024-05-01 21:55:41 +02:00
86cd55102c Changed layout for Vendors 2024-05-01 09:52:33 +02:00
7a337482d4 Changed layout for Vendors 2024-05-01 09:16:43 +02:00
1cbb8cbacf Rebuild Inventory
Added Advance Invoices
2024-04-23 20:28:26 +02:00
633d75b798 Changed Rendering of IncomingInvoice when not in editing 2024-04-16 18:13:49 +02:00
ac9fadf922 Some Changes in E-Mail Page 2024-04-16 18:01:46 +02:00
eddaae2d87 Restructured IncomingInvoices
Added Aspect Ration to DocumentDisplay.vue
2024-04-16 18:00:35 +02:00
1269d3b838 Added ProfileSelection when no profile is selected 2024-04-16 11:34:13 +02:00
c1108314c1 Moved Buchhaltung 2024-04-16 11:33:55 +02:00
0fd9db2357 Added More Features to Disable 2024-04-16 10:59:55 +02:00
aa322a3234 Fixed Adress Loading when customer is preset
Added more Display Options
Enabled Timeline in Devtools
Changed Style of Document Display
2024-04-16 10:51:33 +02:00
34dfb334ec Added TextTemplates Page and Function 2024-04-14 21:54:23 +02:00
f9d7c93d21 Changed typo in absenceRequests 2024-04-11 13:41:48 +02:00
6114c96b14 Changed filteredRows 2024-04-11 12:09:08 +02:00
bb158c1353 Added Automatic ZIP Completion 2024-04-10 11:14:57 +02:00
bf04b9c563 Changes 2024-04-10 10:09:56 +02:00
9fa1059301 Changes 2024-04-08 21:33:10 +02:00
0f7555907b Added Automatic HistoryItems to more DataTypes 2024-04-08 21:04:32 +02:00
ca62d492ba Changes in CI/CD
Added Entries to HelpSlideover.vue
Removed Chip Display from index notifications bell
2024-04-08 15:42:05 +02:00
44a0e10a94 Added Automatic HistoryItems to Events,Customers,Vendors 2024-04-08 15:41:28 +02:00
3a0f3f75b4 Changed Notifications to Benachrichtigungen
Correcte getStartetWorkingTimes Functions
2024-04-08 13:06:11 +02:00
e2a15644ce Added Event Table to projects
Added Notes to Vendors
Added Notes and Link to Event
2024-04-08 09:37:04 +02:00
129c0e4d25 Lösche .DS_Store 2024-04-07 20:27:25 +00:00
2fe1aeb4f2 Restructured Project Rep 2024-04-07 22:26:47 +02:00
d51ad2c4eb Restructured Project Rep 2024-04-07 22:25:16 +02:00
895f508c29 Changes 2024-04-07 19:10:17 +02:00
e0750e755f Restructured Workingtimes without date to start and end
Added Page to Edit Workingtimes
2024-04-07 18:26:14 +02:00
491cbf15b6 Changes 2024-04-07 17:18:58 +02:00
d7fafda78e Added Page Events
Added WeekNumbers to DatePicker
Changed Coloring in DatePicker
2024-04-07 14:21:14 +02:00
092f3aa6bd Added Business Info Editing 2024-04-07 12:55:56 +02:00
281f3562ec Added Content to HelpSlideover
Added Shortcut to Settings
Added Feature Disabling Settings to tenant.vue
2024-04-07 12:44:23 +02:00
7c162f157a Added Feature disabling for some Features 2024-04-07 12:21:06 +02:00
e4b02af524 Removed unnecessary console.log 2024-04-07 12:05:07 +02:00
cd79725a27 Changed Sorting in Profiles to Lastname
Added Index Card check for Anwesenheiten
Added Type Sorting to Receipts list
2024-04-07 12:04:36 +02:00
88bca67745 Changes 2024-04-01 17:36:31 +02:00
c0e0345faa Changes 2024-03-19 13:59:11 +01:00
34096f877a Changes in times and Dashboard 2024-03-19 13:13:41 +01:00
66ee33cdde Changes in times and Dashboard 2024-03-19 12:26:16 +01:00
edf1de189b Changes 2024-03-19 10:23:57 +01:00
d4edc66c2c Changes 2024-03-18 21:20:23 +01:00
6ef8573032 Changes 2024-03-18 20:50:20 +01:00
e4a41d9126 Changes 2024-03-18 10:22:11 +01:00
39a2f19b0d Changes in workingtimes.vue 2024-03-18 09:10:18 +01:00
73d3c6311a Changes in Document Creation 2024-03-17 12:14:11 +01:00
aef8cce755 Remodel of Profile System
Added isCompany to Customers
changes in workingtimes.vue
2024-03-17 11:01:19 +01:00
874ff01551 Changes 2024-03-14 08:13:05 +01:00
d1419fdad1 Added Zahlungsziel 2024-03-13 08:37:37 +01:00
85dd60c197 Added Fullname to Contact Creation 2024-03-12 19:09:03 +01:00
6791342879 Changes 2024-03-12 13:38:24 +01:00
423b2638aa Changes 2024-03-11 15:07:18 +01:00
99b8738c31 Changes 2024-03-11 14:51:13 +01:00
62b5e1bc57 Changed E-Mail Field
Changes
2024-03-07 11:53:41 +01:00
5783fb1f2e Changes 2024-03-07 11:26:18 +01:00
5cbfbff4ac Changes 2024-03-07 11:04:58 +01:00
1cbea757d0 Changes 2024-03-07 10:32:47 +01:00
fb8041c32e Changes 2024-03-04 22:19:37 +01:00
8f444bd917 Changes in Document Creation, Calendar, Notifications, Settings 2024-03-04 20:26:30 +01:00
8e69cda09b Changes 2024-03-03 20:38:59 +01:00
b4b70d8e7c Changes 2024-03-01 22:48:03 +01:00
3924fd79d2 Changed to new Layout System 2024-02-23 22:34:39 +01:00
0d86e4c4f9 Changed to new Layout System 2024-02-23 19:30:43 +01:00
96d4ee7356 Rebuild General Layout to Nuxt UI PRO Dashboard 2024-02-22 22:23:15 +01:00
c6e0854544 Changes 2024-02-22 19:01:12 +01:00
4e3ac183d4 Corrected Incoming Invoice
Added Column Selection to Customers,Vendors and Contacts
2024-02-22 17:29:33 +01:00
436cb2c163 Changes 2024-02-21 16:53:54 +01:00
ddb3b90788 Changes 2024-02-21 16:38:48 +01:00
6e2e419a1c Changes 2024-02-19 22:25:58 +01:00
d5c3034758 Added Italien 2024-02-19 15:58:56 +01:00
0ccd5635e7 Included Tenant Value in Creation
Added Fields to Customer Info Data
2024-02-19 15:49:04 +01:00
0495f40bef Changed Dockerfile to index.mjs 2024-02-19 08:21:30 +01:00
630798c89f Many Changes 2024-02-19 07:46:28 +01:00
fa0eb73363 Many Changes 2024-02-18 19:51:00 +01:00
1a0b7288df Added Phases to Projects 2024-02-15 22:49:09 +01:00
2fc45b3ea0 Added Phases to Projects 2024-02-05 20:48:15 +01:00
22a3b8698c Corrected Loading error 2024-02-04 20:43:45 +01:00
fa661ff6b3 Many Changes for 1.0 2024-02-04 20:21:09 +01:00
bd09d07698 Many Changes 2024-02-01 22:17:56 +01:00
34d1eb9c71 Many Changes 2024-02-01 21:00:59 +01:00
fe74e7d91b Changes in RLS and Tenants
Many Changes in Invoice Editor
Page Loading Rebuilt
Started Rights Management
2024-01-30 21:18:46 +01:00
3167b6a20a Changed Plants to Objects
Changes in outgoinginvoices
2024-01-27 11:54:14 +01:00
6f6e835b0a Added HistoryDisplay to IncomingInvoices 2024-01-16 21:41:35 +01:00
497d768d4e Restructured Update Process Into Store 2024-01-15 21:54:12 +01:00
9f5a142680 Restructured Create Process Into Store 2024-01-15 19:50:26 +01:00
f6c1f4219b Changed TimeGrid 2024-01-15 12:28:53 +01:00
f5e7700809 Added Chat
Restructured Calendar
Some Changes in Timetracking
2024-01-15 10:19:06 +01:00
05130052af Added HistoryDisplay.vue to Projects and Plants
Visual Restructure in Plants
2024-01-12 18:38:26 +01:00
291b0350e8 Build Profile View with Times View and some Profile Editing
Some Changes in timetracking.vue
2024-01-12 18:21:53 +01:00
7aa06f595d correted Error with new Time Creation
made Times Editable
created field for user filter
2024-01-12 17:01:45 +01:00
3227414a92 added expense field to incominginvoice 2024-01-11 18:40:28 +01:00
12323382a5 many changes 2024-01-11 18:33:56 +01:00
d62fc5d668 Changed DayJS Namespace 2024-01-05 18:24:58 +01:00
8822854040 Changed DayJS Namespace 2024-01-05 18:17:50 +01:00
61793838bb Many Changes
Introduced Plants
Some Polishing
Some Resources got Query Params
Extended GlobalSearch.vue
Removed Jobs
2024-01-05 18:06:09 +01:00
991cac18f2 Many Changes 2024-01-04 12:27:46 +01:00
57e856c71c Store change rest 2023-12-27 22:11:41 +01:00
c41b99f29d Changed STore Type and corrected all Pages
Added HistoryDisplay.vue
Added NumberRanges
2023-12-27 21:52:55 +01:00
9e092823e4 Added Vehicles
Added Bankimport, BankAccounts, BankStatements
Some Visual Changes
Added Contacts
Changes in VendorInvoices
Added layouts with default an one for Login PAge
Added Input Group Component
2023-12-22 17:50:22 +01:00
8a1e2384d1 Corrected Imapsync
Added different Tags feature to imapsync
2023-12-22 10:38:30 +01:00
8d777a3d50 Removed vue-barcode-reader import 2023-12-21 16:20:35 +01:00
3625db30ec Corrected Empty Slot for Tables 2023-12-21 16:17:43 +01:00
1573cb2b1e Some Tests 2023-12-21 16:05:22 +01:00
537896503f Some Changes 2023-12-21 16:05:13 +01:00
e792ed39c9 Changed Tasks List
Created Time creation
Added VueDatepicker Package
2023-12-21 16:04:52 +01:00
cc636ce040 Removed Vue Barcode Reader 2023-12-19 15:18:18 +01:00
c82a0e5e1c Document Restructure
Introduced ExternalDevices Settingspage
Added DocumentDisplay.vue Component
Some Changes to Spaces
2023-12-18 19:43:50 +01:00
b9772def05 Many Changes 2023-12-15 20:48:47 +01:00
0590fa0875 Tests with PDF library 2023-12-14 20:59:44 +01:00
cd36514e1c Added DayJS
Restructured Products
Some Changes in Documents
Some Changes with Logo
2023-12-14 20:58:52 +01:00
2dd31690e5 Added New Viewport Config 2023-12-11 20:30:10 +01:00
2721a3b2d4 Changes Logos 2023-12-11 12:23:39 +01:00
5503c572f1 Added Logo
Added Document Download
Added zipjs
2023-12-11 12:04:32 +01:00
6ffc4f01d9 Color Changes
Login Changes
Misc
2023-12-10 15:06:16 +01:00
5182959881 Rebuild Times in Projects
Added Icons
Rebuild Time Duration
2023-12-07 12:14:19 +01:00
36371f94e8 Some Changes in TimeTracking and boosting Mobile View 2023-12-06 22:26:36 +01:00
2b7bf12bc7 Removed Dev Flag from PWA Module 2023-12-06 22:07:14 +01:00
ce3a013f86 Changed Customers to Multi Page with Route Params to add mobile performance 2023-12-06 22:06:57 +01:00
987f8a0bec Changes to Timetracking 2023-12-05 22:35:25 +01:00
73678f0507 Changes to Timetracking 2023-12-05 22:18:30 +01:00
0d6357af26 Changes to PWA 2023-12-05 14:24:25 +01:00
e863bea269 Added PWA 2023-12-05 13:30:16 +01:00
6d13d02efb Changed Software Name 2023-12-04 20:52:45 +01:00
6a5238d2bb Changed Software Name 2023-12-04 20:50:09 +01:00
76aa6631f3 Readded Progress Bar 2023-12-04 20:48:13 +01:00
4b413e0209 Added Reset after Creation & Save for Customers 2023-12-04 20:40:33 +01:00
04f4ccbe03 Added Nuxt UI Pro License 2023-12-04 20:30:15 +01:00
ef9105e286 Changes 2023-12-04 20:24:01 +01:00
68a5775717 Removed Name from documents
Added Customers Reload
2023-12-02 17:33:33 +01:00
fb6e8a89c0 Merge remote-tracking branch 'origin/main' 2023-12-02 17:12:51 +01:00
999738ed4b Changed Dockerfile 2023-12-02 17:12:43 +01:00
Florian Federspiel
19401d27c0 Delete package-lock.json 2023-12-02 16:11:40 +00:00
465a531bf4 added package-lock.json 2023-12-02 17:09:48 +01:00
Florian Federspiel
5da6b4ae99 Merge branch 'bryntum' into 'main'
Implemented Fullcalendar

See merge request cmykmedia/spaces!1
2023-12-02 16:07:38 +00:00
f63bda171f Implemented Fullcalendar 2023-12-02 17:06:39 +01:00
45da05c9a4 Changes 2023-12-02 13:09:23 +01:00
098bc97fa4 Added Nodemon to IMAPSYNC 2023-11-29 19:58:21 +01:00
d74d7abc90 Added Dockerfile and CI/CD for Imapsync
Added Core Functionality for Imapsync
2023-11-29 19:52:37 +01:00
ff966418b2 Removed Bugs 2023-11-28 08:13:21 +01:00
7cc942b42f Removed Tenant from Dropdown 2023-11-26 17:45:09 +01:00
dea4f7eddc Changed Backend to Supabase 2023-11-26 17:38:37 +01:00
cb3d48d42c Changed Backend to Supabase 2023-11-26 17:15:55 +01:00
8b76434b41 Added Vue-PDF to Spaces 2023-11-25 17:29:13 +01:00
2ed3ed4b45 Added Dockerfile 2023-11-25 17:24:17 +01:00
f400833213 New changelist 2023-11-25 17:12:19 +01:00
Florian Federspiel
832a0b9f29 added ci 2023-11-25 17:00:33 +01:00
Florian Federspiel
677030f712 Initial 2023-11-25 16:53:52 +01:00
415 changed files with 65953 additions and 0 deletions

5
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
node_modules
# Keep environment variables out of version control
.env
/src/generated/prisma

18
backend/.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,18 @@
before_script:
- docker info
stages:
- build
build-backend:
stage: build
tags:
- shell
- docker-daemon
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
script:
- echo $IMAGE_TAG
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG

20
backend/Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM node:20-alpine
WORKDIR /usr/src/app
# Package-Dateien
COPY package*.json ./
# Dev + Prod Dependencies (für TS-Build nötig)
RUN npm install
# Restlicher Sourcecode
COPY . .
# TypeScript Build
RUN npm run build
# Port freigeben
EXPOSE 3100
# Start der App
CMD ["node", "dist/src/index.js"]

32
backend/TODO.md Normal file
View File

@@ -0,0 +1,32 @@
# Projekt To-Do Liste
## ✅ Erledigt
- JWT-basierte Authentifizierung mit Cookie
- Prefix für Auth-Tabellen (`auth_users`, `auth_roles`, …)
- `/me` liefert User + Rechte (via `auth_get_user_permissions`)
- Basis-Seed für Standardrollen + Rechte eingespielt
- Auth Middleware im Frontend korrigiert (Login-Redirects)
- Nuxt API Plugin unterstützt JWT im Header
- Login-Seite an Nuxt UI Pro (v2) anpassen
- `usePermission()` Composable im Frontend vorbereitet
---
## 🔄 Offene Punkte
### Backend
- [ ] `/me` erweitern: Rollen + deren Rechte strukturiert zurückgeben (`{ role: "manager", permissions: [...] }`)
- [ ] Loading Flag im Auth-Flow berücksichtigen (damit `me` nicht doppelt feuert)
- [ ] Gemeinsames Schema für Entities (Backend stellt per Endpoint bereit)
- [ ] Soft Delete vereinheitlichen (`archived = true` statt DELETE)
- [ ] Swagger-Doku verbessern (Schemas, Beispiele)
### Frontend
- [ ] Loading Flag in Middleware/Store einbauen
- [ ] Einheitliches Laden des Schemas beim Start
- [ ] Pinia-Store für Auth/User/Tenant konsolidieren
- [ ] Composable `usePermission(key)` implementieren, um Rechte einfach im Template zu prüfen
- [ ] Entity-Seiten schrittweise auf API-Routen umstellen
- [ ] Page Guards für Routen einbauen (z. B. `/projects/create` nur bei `projects-create`)
---

10
backend/db/index.ts Normal file
View File

@@ -0,0 +1,10 @@
import { drizzle } from "drizzle-orm/node-postgres"
import { Pool } from "pg"
import {secrets} from "../src/utils/secrets";
const pool = new Pool({
connectionString: secrets.DATABASE_URL,
max: 10, // je nach Last
})
export const db = drizzle(pool)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
CREATE TABLE "time_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"tenant_id" bigint NOT NULL,
"user_id" uuid NOT NULL,
"actor_type" text NOT NULL,
"actor_user_id" uuid,
"event_time" timestamp with time zone NOT NULL,
"event_type" text NOT NULL,
"source" text NOT NULL,
"invalidates_event_id" uuid,
"metadata" jsonb,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "time_events_actor_user_check" CHECK (
(actor_type = 'system' AND actor_user_id IS NULL)
OR
(actor_type = 'user' AND actor_user_id IS NOT NULL)
)
);
--> statement-breakpoint
ALTER TABLE "bankstatements" DROP CONSTRAINT "bankstatements_incomingInvoice_incominginvoices_id_fk";
--> statement-breakpoint
ALTER TABLE "bankstatements" DROP CONSTRAINT "bankstatements_createdDocument_createddocuments_id_fk";
--> statement-breakpoint
ALTER TABLE "time_events" ADD CONSTRAINT "time_events_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "time_events" ADD CONSTRAINT "time_events_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "time_events" ADD CONSTRAINT "time_events_actor_user_id_auth_users_id_fk" FOREIGN KEY ("actor_user_id") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "time_events" ADD CONSTRAINT "time_events_invalidates_event_id_time_events_id_fk" FOREIGN KEY ("invalidates_event_id") REFERENCES "public"."time_events"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_time_events_tenant_user_time" ON "time_events" USING btree ("tenant_id","user_id","event_time");--> statement-breakpoint
CREATE INDEX "idx_time_events_created_at" ON "time_events" USING btree ("created_at");--> statement-breakpoint
CREATE INDEX "idx_time_events_invalidates" ON "time_events" USING btree ("invalidates_event_id");--> statement-breakpoint
ALTER TABLE "bankstatements" DROP COLUMN "incomingInvoice";--> statement-breakpoint
ALTER TABLE "bankstatements" DROP COLUMN "createdDocument";

View File

@@ -0,0 +1,13 @@
ALTER TABLE "time_events" RENAME TO "staff_time_events";--> statement-breakpoint
ALTER TABLE "staff_time_events" DROP CONSTRAINT "time_events_tenant_id_tenants_id_fk";
--> statement-breakpoint
ALTER TABLE "staff_time_events" DROP CONSTRAINT "time_events_user_id_auth_users_id_fk";
--> statement-breakpoint
ALTER TABLE "staff_time_events" DROP CONSTRAINT "time_events_actor_user_id_auth_users_id_fk";
--> statement-breakpoint
ALTER TABLE "staff_time_events" DROP CONSTRAINT "time_events_invalidates_event_id_time_events_id_fk";
--> statement-breakpoint
ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_user_id_auth_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_actor_user_id_auth_users_id_fk" FOREIGN KEY ("actor_user_id") REFERENCES "public"."auth_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "staff_time_events" ADD CONSTRAINT "staff_time_events_invalidates_event_id_staff_time_events_id_fk" FOREIGN KEY ("invalidates_event_id") REFERENCES "public"."staff_time_events"("id") ON DELETE no action ON UPDATE no action;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1764947303113,
"tag": "0000_brief_dark_beast",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1765641431341,
"tag": "0001_medical_big_bertha",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1765642446738,
"tag": "0002_silent_christian_walker",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1765716484200,
"tag": "0003_woozy_adam_destine",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1765716877146,
"tag": "0004_stormy_onslaught",
"breakpoints": true
}
]
}

View File

@@ -0,0 +1,24 @@
import {
pgTable,
bigint,
timestamp,
text,
} from "drizzle-orm/pg-core"
export const accounts = pgTable("accounts", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
number: text("number").notNull(),
label: text("label").notNull(),
description: text("description"),
})
export type Account = typeof accounts.$inferSelect
export type NewAccount = typeof accounts.$inferInsert

View File

@@ -0,0 +1,83 @@
import {
pgTable,
uuid,
text,
timestamp,
date,
boolean,
bigint,
doublePrecision,
jsonb,
} from "drizzle-orm/pg-core"
import { authUsers } from "./auth_users"
export const authProfiles = pgTable("auth_profiles", {
id: uuid("id").primaryKey().defaultRandom(),
user_id: uuid("user_id").references(() => authUsers.id),
tenant_id: bigint("tenant_id", { mode: "number" }).notNull(),
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
first_name: text("first_name").notNull(),
last_name: text("last_name").notNull(),
full_name: text("full_name").generatedAlwaysAs(
`((first_name || ' ') || last_name)`
),
mobile_tel: text("mobile_tel"),
fixed_tel: text("fixed_tel"),
salutation: text("salutation"),
employee_number: text("employee_number"),
weekly_working_hours: doublePrecision("weekly_working_hours").default(0),
annual_paid_leave_days: bigint("annual_paid_leave_days", { mode: "number" }),
weekly_regular_working_hours: jsonb("weekly_regular_working_hours").default("{}"),
clothing_size_top: text("clothing_size_top"),
clothing_size_bottom: text("clothing_size_bottom"),
clothing_size_shoe: text("clothing_size_shoe"),
email_signature: text("email_signature").default("<p>Mit freundlichen Grüßen</p>"),
birthday: date("birthday"),
entry_date: date("entry_date").defaultNow(),
automatic_hour_corrections: jsonb("automatic_hour_corrections").default("[]"),
recreation_days_compensation: boolean("recreation_days_compensation")
.notNull()
.default(true),
customer_for_portal: bigint("customer_for_portal", { mode: "number" }),
pinned_on_navigation: jsonb("pinned_on_navigation").notNull().default("[]"),
email: text("email"),
token_id: text("token_id"),
weekly_working_days: doublePrecision("weekly_working_days"),
old_profile_id: uuid("old_profile_id"),
temp_config: jsonb("temp_config"),
state_code: text("state_code").default("DE-NI"),
contract_type: text("contract_type"),
position: text("position"),
qualification: text("qualification"),
address_street: text("address_street"),
address_zip: text("address_zip"),
address_city: text("address_city"),
active: boolean("active").notNull().default(true),
})
export type AuthProfile = typeof authProfiles.$inferSelect
export type NewAuthProfile = typeof authProfiles.$inferInsert

View File

@@ -0,0 +1,23 @@
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core"
import { authRoles } from "./auth_roles"
export const authRolePermissions = pgTable(
"auth_role_permissions",
{
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
role_id: uuid("role_id")
.notNull()
.references(() => authRoles.id),
permission: text("permission").notNull(),
},
(table) => ({
primaryKey: [table.role_id, table.permission],
})
)
export type AuthRolePermission = typeof authRolePermissions.$inferSelect
export type NewAuthRolePermission = typeof authRolePermissions.$inferInsert

View File

@@ -0,0 +1,19 @@
import { pgTable, uuid, text, timestamp, bigint } from "drizzle-orm/pg-core"
import { authUsers } from "./auth_users"
export const authRoles = pgTable("auth_roles", {
id: uuid("id").primaryKey().defaultRandom(),
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
description: text("description"),
created_by: uuid("created_by").references(() => authUsers.id),
tenant_id: bigint("tenant_id", {mode: "number"}),
})
export type AuthRole = typeof authRoles.$inferSelect
export type NewAuthRole = typeof authRoles.$inferInsert

View File

@@ -0,0 +1,22 @@
import { pgTable, uuid, bigint, timestamp } from "drizzle-orm/pg-core"
import { authUsers } from "./auth_users"
export const authTenantUsers = pgTable(
"auth_tenant_users",
{
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant_id: bigint("tenant_id", { mode: "number" }).notNull(),
user_id: uuid("user_id").notNull(),
created_by: uuid("created_by").references(() => authUsers.id),
},
(table) => ({
primaryKey: [table.tenant_id, table.user_id],
})
)
export type AuthTenantUser = typeof authTenantUsers.$inferSelect
export type NewAuthTenantUser = typeof authTenantUsers.$inferInsert

View File

@@ -0,0 +1,30 @@
import { pgTable, uuid, bigint, timestamp } from "drizzle-orm/pg-core"
import { authUsers } from "./auth_users"
import { authRoles } from "./auth_roles"
export const authUserRoles = pgTable(
"auth_user_roles",
{
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
user_id: uuid("user_id")
.notNull()
.references(() => authUsers.id),
role_id: uuid("role_id")
.notNull()
.references(() => authRoles.id),
tenant_id: bigint("tenant_id", { mode: "number" }).notNull(),
created_by: uuid("created_by").references(() => authUsers.id),
},
(table) => ({
primaryKey: [table.user_id, table.role_id, table.tenant_id],
})
)
export type AuthUserRole = typeof authUserRoles.$inferSelect
export type NewAuthUserRole = typeof authUserRoles.$inferInsert

View File

@@ -0,0 +1,22 @@
import { pgTable, uuid, text, boolean, timestamp } from "drizzle-orm/pg-core"
export const authUsers = pgTable("auth_users", {
id: uuid("id").primaryKey().defaultRandom(),
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
email: text("email").notNull(),
passwordHash: text("password_hash").notNull(),
multiTenant: boolean("multi_tenant").notNull().default(true),
must_change_password: boolean("must_change_password").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
ported: boolean("ported").notNull().default(true),
})
export type AuthUser = typeof authUsers.$inferSelect
export type NewAuthUser = typeof authUsers.$inferInsert

View File

@@ -0,0 +1,52 @@
import {
pgTable,
bigint,
timestamp,
text,
doublePrecision,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const bankaccounts = pgTable("bankaccounts", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name"),
iban: text("iban").notNull(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
bankId: text("bankId").notNull(),
ownerName: text("ownerName"),
accountId: text("accountId").notNull(),
balance: doublePrecision("balance"),
expired: boolean("expired").notNull().default(false),
datevNumber: text("datevNumber"),
syncedAt: timestamp("synced_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
archived: boolean("archived").notNull().default(false),
})
export type BankAccount = typeof bankaccounts.$inferSelect
export type NewBankAccount = typeof bankaccounts.$inferInsert

View File

@@ -0,0 +1,30 @@
import {
pgTable,
uuid,
timestamp,
text,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const bankrequisitions = pgTable("bankrequisitions", {
id: uuid("id").primaryKey(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
institutionId: text("institutionId"),
tenant: bigint("tenant", { mode: "number" }).references(() => tenants.id),
status: text("status"),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type BankRequisition = typeof bankrequisitions.$inferSelect
export type NewBankRequisition = typeof bankrequisitions.$inferInsert

View File

@@ -0,0 +1,62 @@
import {
pgTable,
bigint,
timestamp,
text,
doublePrecision,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { bankaccounts } from "./bankaccounts"
import { createddocuments } from "./createddocuments"
import { tenants } from "./tenants"
import { incominginvoices } from "./incominginvoices"
import { contracts } from "./contracts"
import { authUsers } from "./auth_users"
export const bankstatements = pgTable("bankstatements", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
account: bigint("account", { mode: "number" })
.notNull()
.references(() => bankaccounts.id),
date: text("date").notNull(),
credIban: text("credIban"),
credName: text("credName"),
text: text("text"),
amount: doublePrecision("amount").notNull(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
debIban: text("debIban"),
debName: text("debName"),
gocardlessId: text("gocardlessId"),
currency: text("currency"),
valueDate: text("valueDate"),
mandateId: text("mandateId"),
contract: bigint("contract", { mode: "number" }).references(
() => contracts.id
),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type BankStatement = typeof bankstatements.$inferSelect
export type NewBankStatement = typeof bankstatements.$inferInsert

View File

@@ -0,0 +1,27 @@
import {
pgTable,
uuid,
timestamp,
text,
} from "drizzle-orm/pg-core"
import { checks } from "./checks"
export const checkexecutions = pgTable("checkexecutions", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
check: uuid("check").references(() => checks.id),
executedAt: timestamp("executed_at"),
// ❌ executed_by removed (was 0_profiles)
description: text("description"),
})
export type CheckExecution = typeof checkexecutions.$inferSelect
export type NewCheckExecution = typeof checkexecutions.$inferInsert

View File

@@ -0,0 +1,52 @@
import {
pgTable,
uuid,
timestamp,
text,
bigint,
boolean,
jsonb,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { vehicles } from "./vehicles"
import { inventoryitems } from "./inventoryitems"
import { authUsers } from "./auth_users"
export const checks = pgTable("checks", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
vehicle: bigint("vehicle", { mode: "number" })
.references(() => vehicles.id),
// ❌ profile removed (old 0_profiles reference)
inventoryItem: bigint("inventoryitem", { mode: "number" })
.references(() => inventoryitems.id),
tenant: bigint("tenant", { mode: "number" })
.references(() => tenants.id),
name: text("name"),
type: text("type"),
distance: bigint("distance", { mode: "number" }).default(1),
distanceUnit: text("distanceUnit").default("days"),
description: text("description"),
profiles: jsonb("profiles").notNull().default([]),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type Check = typeof checks.$inferSelect
export type NewCheck = typeof checks.$inferInsert

View File

@@ -0,0 +1,32 @@
import {
pgTable,
bigint,
text,
jsonb,
} from "drizzle-orm/pg-core"
export const citys = pgTable("citys", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
name: text("name"),
short: text("short"),
long: text("long"),
geometry: jsonb("geometry"),
zip: bigint("zip", { mode: "number" }),
districtCode: bigint("districtCode", { mode: "number" }),
countryName: text("countryName"),
countryCode: bigint("countryCode", { mode: "number" }),
districtName: text("districtName"),
geopoint: text("geopoint"),
})
export type City = typeof citys.$inferSelect
export type NewCity = typeof citys.$inferInsert

View File

@@ -0,0 +1,66 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
date,
uuid,
} from "drizzle-orm/pg-core"
import { customers } from "./customers"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const contacts = pgTable(
"contacts",
{
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
firstName: text("firstName"),
lastName: text("lastName"),
email: text("email"),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
tenant: bigint("tenant", { mode: "number" }).notNull(),
phoneMobile: text("phoneMobile"),
phoneHome: text("phoneHome"),
heroId: text("heroId"),
role: text("role"),
fullName: text("fullName"),
salutation: text("salutation"),
vendor: bigint("vendor", { mode: "number" }), // vendors folgt separat
active: boolean("active").notNull().default(true),
birthday: date("birthday"),
notes: text("notes"),
profiles: jsonb("profiles").notNull().default([]),
archived: boolean("archived").notNull().default(false),
title: text("title"),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
}
)
export type Contact = typeof contacts.$inferSelect
export type NewContact = typeof contacts.$inferInsert

View File

@@ -0,0 +1,76 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { contacts } from "./contacts"
import { authUsers } from "./auth_users"
export const contracts = pgTable(
"contracts",
{
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" }).notNull(),
name: text("name").notNull(),
customer: bigint("customer", { mode: "number" })
.notNull()
.references(() => customers.id),
notes: text("notes"),
active: boolean("active").notNull().default(true),
recurring: boolean("recurring").notNull().default(false),
rhythm: jsonb("rhythm"),
startDate: timestamp("startDate", { withTimezone: true }),
endDate: timestamp("endDate", { withTimezone: true }),
signDate: timestamp("signDate", { withTimezone: true }),
duration: text("duration"),
contact: bigint("contact", { mode: "number" }).references(
() => contacts.id
),
bankingIban: text("bankingIban"),
bankingBIC: text("bankingBIC"),
bankingName: text("bankingName"),
bankingOwner: text("bankingOwner"),
sepaRef: text("sepaRef"),
sepaDate: timestamp("sepaDate", { withTimezone: true }),
paymentType: text("paymentType"),
invoiceDispatch: text("invoiceDispatch"),
ownFields: jsonb("ownFields").notNull().default({}),
profiles: jsonb("profiles").notNull().default([]),
archived: boolean("archived").notNull().default(false),
contractNumber: text("contractNumber"),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
}
)
export type Contract = typeof contracts.$inferSelect
export type NewContract = typeof contracts.$inferInsert

View File

@@ -0,0 +1,50 @@
import {
pgTable,
uuid,
timestamp,
text,
boolean,
jsonb,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { inventoryitems } from "./inventoryitems"
import { projects } from "./projects"
import { vehicles } from "./vehicles"
import { authUsers } from "./auth_users"
export const costcentres = pgTable("costcentres", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
number: text("number").notNull(),
name: text("name").notNull(),
vehicle: bigint("vehicle", { mode: "number" }).references(() => vehicles.id),
project: bigint("project", { mode: "number" }).references(() => projects.id),
inventoryitem: bigint("inventoryitem", { mode: "number" }).references(
() => inventoryitems.id
),
description: text("description"),
archived: boolean("archived").notNull().default(false),
profiles: jsonb("profiles").notNull().default([]),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type CostCentre = typeof costcentres.$inferSelect
export type NewCostCentre = typeof costcentres.$inferInsert

View File

@@ -0,0 +1,21 @@
import {
pgTable,
bigint,
timestamp,
text,
} from "drizzle-orm/pg-core"
export const countrys = pgTable("countrys", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
})
export type Country = typeof countrys.$inferSelect
export type NewCountry = typeof countrys.$inferInsert

View File

@@ -0,0 +1,124 @@
import {
pgTable,
bigint,
timestamp,
text,
jsonb,
boolean,
smallint,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { contacts } from "./contacts"
import { contracts } from "./contracts"
import { letterheads } from "./letterheads"
import { projects } from "./projects"
import { plants } from "./plants"
import { authUsers } from "./auth_users"
import {serialExecutions} from "./serialexecutions";
export const createddocuments = pgTable("createddocuments", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
type: text("type").notNull().default("INVOICE"),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
contact: bigint("contact", { mode: "number" }).references(
() => contacts.id
),
address: jsonb("address"),
project: bigint("project", { mode: "number" }).references(
() => projects.id
),
documentNumber: text("documentNumber"),
documentDate: text("documentDate"),
state: text("state").notNull().default("Entwurf"),
info: jsonb("info"),
createdBy: uuid("createdBy").references(() => authUsers.id),
title: text("title"),
description: text("description"),
startText: text("startText"),
endText: text("endText"),
rows: jsonb("rows").default([]),
deliveryDateType: text("deliveryDateType"),
paymentDays: smallint("paymentDays"),
deliveryDate: text("deliveryDate"),
contactPerson: uuid("contactPerson"),
serialConfig: jsonb("serialConfig").default({}),
createddocument: bigint("linkedDocument", { mode: "number" }).references(
() => createddocuments.id
),
agriculture: jsonb("agriculture"),
letterhead: bigint("letterhead", { mode: "number" }).references(
() => letterheads.id
),
advanceInvoiceResolved: boolean("advanceInvoiceResolved")
.notNull()
.default(false),
usedAdvanceInvoices: jsonb("usedAdvanceInvoices").notNull().default([]),
archived: boolean("archived").notNull().default(false),
deliveryDateEnd: text("deliveryDateEnd"),
plant: bigint("plant", { mode: "number" }).references(() => plants.id),
taxType: text("taxType"),
customSurchargePercentage: smallint("customSurchargePercentage")
.notNull()
.default(0),
report: jsonb("report").notNull().default({}),
availableInPortal: boolean("availableInPortal")
.notNull()
.default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
created_by: uuid("created_by").references(() => authUsers.id),
payment_type: text("payment_type").default("transfer"),
contract: bigint("contract", { mode: "number" }).references(
() => contracts.id
),
serialexecution: uuid("serialexecution").references(() => serialExecutions.id)
})
export type CreatedDocument = typeof createddocuments.$inferSelect
export type NewCreatedDocument = typeof createddocuments.$inferInsert

View File

@@ -0,0 +1,43 @@
import {
pgTable,
uuid,
timestamp,
bigint,
text,
jsonb,
boolean,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { vendors } from "./vendors"
import { authUsers } from "./auth_users"
export const createdletters = pgTable("createdletters", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" }).references(() => tenants.id),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id),
contentJson: jsonb("content_json").default([]),
contentText: text("content_text"),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
archived: boolean("archived").notNull().default(false),
})
export type CreatedLetter = typeof createdletters.$inferSelect
export type NewCreatedLetter = typeof createdletters.$inferInsert

View File

@@ -0,0 +1,69 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
smallint,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const customers = pgTable(
"customers",
{
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
customerNumber: text("customerNumber").notNull(),
name: text("name").notNull(),
tenant: bigint("tenant", { mode: "number" }).notNull(),
infoData: jsonb("infoData").default({}),
active: boolean("active").notNull().default(true),
notes: text("notes"),
type: text("type").default("Privat"),
heroId: text("heroId"),
isCompany: boolean("isCompany").notNull().default(false),
profiles: jsonb("profiles").notNull().default([]),
customPaymentDays: smallint("customPaymentDays"),
firstname: text("firstname"),
lastname: text("lastname"),
archived: boolean("archived").notNull().default(false),
customSurchargePercentage: smallint("customSurchargePercentage")
.notNull()
.default(0),
salutation: text("salutation"),
title: text("title"),
nameAddition: text("nameAddition"),
availableInPortal: boolean("availableInPortal")
.notNull()
.default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
customPaymentType: text("custom_payment_type"), // ENUM payment_types separat?
}
)
export type Customer = typeof customers.$inferSelect
export type NewCustomer = typeof customers.$inferInsert

View File

@@ -0,0 +1,29 @@
import {
pgTable,
uuid,
timestamp,
text,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
export const devices = pgTable("devices", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
type: text("type").notNull(),
tenant: bigint("tenant", { mode: "number" }).references(() => tenants.id),
password: text("password"),
externalId: text("externalId"),
})
export type Device = typeof devices.$inferSelect
export type NewDevice = typeof devices.$inferInsert

View File

@@ -0,0 +1,28 @@
import { pgTable, uuid, timestamp, text, boolean, bigint } from "drizzle-orm/pg-core"
import { spaces } from "./spaces"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const documentboxes = pgTable("documentboxes", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
space: bigint("space", { mode: "number" }).references(() => spaces.id),
key: text("key").notNull(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type DocumentBox = typeof documentboxes.$inferSelect
export type NewDocumentBox = typeof documentboxes.$inferInsert

View File

@@ -0,0 +1,97 @@
import { pgEnum } from "drizzle-orm/pg-core"
// public.textTemplatePositions
export const textTemplatePositionsEnum = pgEnum("texttemplatepositions", [
"startText",
"endText",
])
// public.folderFunctions
export const folderFunctionsEnum = pgEnum("folderfunctions", [
"none",
"yearSubCategory",
"incomingInvoices",
"invoices",
"quotes",
"confirmationOrders",
"deliveryNotes",
"vehicleData",
"reminders",
"taxData",
"deposit",
"timeEvaluations",
])
// public.locked_tenant
export const lockedTenantEnum = pgEnum("locked_tenant", [
"maintenance_tenant",
"maintenance",
"general",
"no_subscription",
])
// public.credential_types
export const credentialTypesEnum = pgEnum("credential_types", [
"mail",
"m365",
])
// public.payment_types
export const paymentTypesEnum = pgEnum("payment_types", [
"transfer",
"direct_debit",
])
// public.notification_status
export const notificationStatusEnum = pgEnum("notification_status", [
"queued",
"sent",
"failed",
"read",
])
// public.notification_channel
export const notificationChannelEnum = pgEnum("notification_channel", [
"email",
"inapp",
"sms",
"push",
"webhook",
])
// public.notification_severity
export const notificationSeverityEnum = pgEnum("notification_severity", [
"info",
"success",
"warning",
"error",
])
// public.times_state
export const timesStateEnum = pgEnum("times_state", [
"submitted",
"approved",
"draft",
])
export const helpdeskStatusEnum = [
"open",
"in_progress",
"waiting_for_customer",
"answered",
"closed",
] as const
export const helpdeskPriorityEnum = [
"low",
"normal",
"high",
] as const
export const helpdeskDirectionEnum = [
"incoming",
"outgoing",
"internal",
"system",
] as const

View File

@@ -0,0 +1,60 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { authUsers } from "./auth_users"
export const events = pgTable(
"events",
{
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" }).notNull(),
name: text("name").notNull(),
startDate: timestamp("startDate", { withTimezone: true }).notNull(),
endDate: timestamp("endDate", { withTimezone: true }),
eventtype: text("eventtype").default("Umsetzung"),
project: bigint("project", { mode: "number" }), // FK follows when projects.ts exists
resources: jsonb("resources").default([]),
notes: text("notes"),
link: text("link"),
profiles: jsonb("profiles").notNull().default([]),
archived: boolean("archived").notNull().default(false),
vehicles: jsonb("vehicles").notNull().default([]),
inventoryitems: jsonb("inventoryitems").notNull().default([]),
inventoryitemgroups: jsonb("inventoryitemgroups").notNull().default([]),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
vendor: bigint("vendor", { mode: "number" }), // will link once vendors.ts is created
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
}
)
export type Event = typeof events.$inferSelect
export type NewEvent = typeof events.$inferInsert

View File

@@ -0,0 +1,79 @@
import {
pgTable,
uuid,
timestamp,
text,
boolean,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { projects } from "./projects"
import { customers } from "./customers"
import { contracts } from "./contracts"
import { vendors } from "./vendors"
import { incominginvoices } from "./incominginvoices"
import { plants } from "./plants"
import { createddocuments } from "./createddocuments"
import { vehicles } from "./vehicles"
import { products } from "./products"
import { inventoryitems } from "./inventoryitems"
import { folders } from "./folders"
import { filetags } from "./filetags"
import { authUsers } from "./auth_users"
import { authProfiles } from "./auth_profiles"
import { spaces } from "./spaces"
import { documentboxes } from "./documentboxes"
import { checks } from "./checks"
export const files = pgTable("files", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
path: text("path"),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
project: bigint("project", { mode: "number" }).references(() => projects.id),
customer: bigint("customer", { mode: "number" }).references(() => customers.id),
contract: bigint("contract", { mode: "number" }).references(() => contracts.id),
vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id),
incominginvoice: bigint("incominginvoice", { mode: "number" }).references(() => incominginvoices.id),
plant: bigint("plant", { mode: "number" }).references(() => plants.id),
createddocument: bigint("createddocument", { mode: "number" }).references(() => createddocuments.id),
vehicle: bigint("vehicle", { mode: "number" }).references(() => vehicles.id),
product: bigint("product", { mode: "number" }).references(() => products.id),
check: uuid("check").references(() => checks.id),
inventoryitem: bigint("inventoryitem", { mode: "number" }).references(() => inventoryitems.id),
folder: uuid("folder").references(() => folders.id),
mimeType: text("mimeType"),
archived: boolean("archived").notNull().default(false),
space: bigint("space", { mode: "number" }).references(() => spaces.id),
type: uuid("type").references(() => filetags.id),
documentbox: uuid("documentbox").references(() => documentboxes.id),
name: text("name"),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
createdBy: uuid("created_by").references(() => authUsers.id),
authProfile: uuid("auth_profile").references(() => authProfiles.id),
})
export type File = typeof files.$inferSelect
export type NewFile = typeof files.$inferInsert

View File

@@ -0,0 +1,33 @@
import {
pgTable,
uuid,
timestamp,
text,
boolean,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
export const filetags = pgTable("filetags", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
color: text("color"),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
createdDocumentType: text("createddocumenttype").default(""),
incomingDocumentType: text("incomingDocumentType"),
archived: boolean("archived").notNull().default(false),
})
export type FileTag = typeof filetags.$inferSelect
export type NewFileTag = typeof filetags.$inferInsert

View File

@@ -0,0 +1,51 @@
import {
pgTable,
uuid,
timestamp,
text,
boolean,
integer,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { filetags } from "./filetags"
import { folderFunctionsEnum } from "./enums"
export const folders = pgTable("folders", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").notNull(),
icon: text("icon"),
parent: uuid("parent").references(() => folders.id),
isSystemUsed: boolean("isSystemUsed").notNull().default(false),
function: folderFunctionsEnum("function"),
year: integer("year"),
standardFiletype: uuid("standardFiletype").references(() => filetags.id),
standardFiletypeIsOptional: boolean("standardFiletypeIsOptional")
.notNull()
.default(true),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
archived: boolean("archived").notNull().default(false),
})
export type Folder = typeof folders.$inferSelect
export type NewFolder = typeof folders.$inferInsert

View File

@@ -0,0 +1,35 @@
import {
pgTable,
bigint,
timestamp,
text,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
export const generatedexports = pgTable("exports", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id),
startDate: timestamp("start_date", { withTimezone: true }).notNull(),
endDate: timestamp("end_date", { withTimezone: true }).notNull(),
validUntil: timestamp("valid_until", { withTimezone: true }),
type: text("type").notNull().default("datev"),
url: text("url").notNull(),
filePath: text("file_path"),
})
export type Export = typeof generatedexports.$inferSelect
export type NewExport = typeof generatedexports.$inferInsert

View File

@@ -0,0 +1,22 @@
import {
pgTable,
bigint,
timestamp,
text,
} from "drizzle-orm/pg-core"
export const globalmessages = pgTable("globalmessages", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
title: text("title"),
description: text("description"),
})
export type GlobalMessage = typeof globalmessages.$inferSelect
export type NewGlobalMessage = typeof globalmessages.$inferInsert

View File

@@ -0,0 +1,17 @@
import {
pgTable,
timestamp,
bigint,
} from "drizzle-orm/pg-core"
import { globalmessages } from "./globalmessages"
export const globalmessagesseen = pgTable("globalmessagesseen", {
message: bigint("message", { mode: "number" })
.notNull()
.references(() => globalmessages.id),
seenAt: timestamp("seen_at", { withTimezone: true })
.notNull()
.defaultNow(),
})

View File

@@ -0,0 +1,44 @@
import {
pgTable,
uuid,
timestamp,
text,
boolean,
jsonb,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { helpdesk_channel_types } from "./helpdesk_channel_types"
export const helpdesk_channel_instances = pgTable("helpdesk_channel_instances", {
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade" }),
typeId: text("type_id")
.notNull()
.references(() => helpdesk_channel_types.id),
name: text("name").notNull(),
isActive: boolean("is_active").notNull().default(true),
config: jsonb("config").notNull(),
publicConfig: jsonb("public_config").notNull().default({}),
publicToken: text("public_token").unique(),
secretToken: text("secret_token"),
createdBy: uuid("created_by").references(() => authUsers.id),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
})
export type HelpdeskChannelInstance =
typeof helpdesk_channel_instances.$inferSelect
export type NewHelpdeskChannelInstance =
typeof helpdesk_channel_instances.$inferInsert

View File

@@ -0,0 +1,9 @@
import { pgTable, text } from "drizzle-orm/pg-core"
export const helpdesk_channel_types = pgTable("helpdesk_channel_types", {
id: text("id").primaryKey(),
description: text("description").notNull(),
})
export type HelpdeskChannelType = typeof helpdesk_channel_types.$inferSelect
export type NewHelpdeskChannelType = typeof helpdesk_channel_types.$inferInsert

View File

@@ -0,0 +1,45 @@
import {
pgTable,
uuid,
timestamp,
text,
jsonb,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { contacts } from "./contacts"
import { helpdesk_channel_instances } from "./helpdesk_channel_instances" // placeholder
export const helpdesk_contacts = pgTable("helpdesk_contacts", {
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade" }),
customerId: bigint("customer_id", { mode: "number" })
.references(() => customers.id, { onDelete: "set null" }),
email: text("email"),
phone: text("phone"),
externalRef: jsonb("external_ref"),
displayName: text("display_name"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
sourceChannelId: uuid("source_channel_id").references(
() => helpdesk_channel_instances.id,
{ onDelete: "set null" }
),
contactId: bigint("contact_id", { mode: "number" }).references(
() => contacts.id,
{ onDelete: "set null" }
),
})
export type HelpdeskContact = typeof helpdesk_contacts.$inferSelect
export type NewHelpdeskContact = typeof helpdesk_contacts.$inferInsert

View File

@@ -0,0 +1,34 @@
import {
pgTable,
uuid,
text,
} from "drizzle-orm/pg-core"
import { helpdesk_conversations } from "./helpdesk_conversations"
import { authUsers } from "./auth_users"
export const helpdesk_conversation_participants = pgTable(
"helpdesk_conversation_participants",
{
conversationId: uuid("conversation_id")
.notNull()
.references(() => helpdesk_conversations.id, { onDelete: "cascade" }),
userId: uuid("user_id")
.notNull()
.references(() => authUsers.id, { onDelete: "cascade" }),
role: text("role"),
},
(table) => ({
pk: {
name: "helpdesk_conversation_participants_pkey",
columns: [table.conversationId, table.userId],
},
})
)
export type HelpdeskConversationParticipant =
typeof helpdesk_conversation_participants.$inferSelect
export type NewHelpdeskConversationParticipant =
typeof helpdesk_conversation_participants.$inferInsert

View File

@@ -0,0 +1,59 @@
import {
pgTable,
uuid,
timestamp,
text,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { helpdesk_contacts } from "./helpdesk_contacts"
import { contacts } from "./contacts"
import { customers } from "./customers"
import { authUsers } from "./auth_users"
import { helpdesk_channel_instances } from "./helpdesk_channel_instances"
export const helpdesk_conversations = pgTable("helpdesk_conversations", {
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade" }),
channelInstanceId: uuid("channel_instance_id")
.notNull()
.references(() => helpdesk_channel_instances.id, { onDelete: "cascade" }),
contactId: uuid("contact_id").references(() => helpdesk_contacts.id, {
onDelete: "set null",
}),
subject: text("subject"),
status: text("status").notNull().default("open"),
priority: text("priority").default("normal"),
assigneeUserId: uuid("assignee_user_id").references(() => authUsers.id),
lastMessageAt: timestamp("last_message_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
customerId: bigint("customer_id", { mode: "number" }).references(
() => customers.id,
{ onDelete: "set null" }
),
contactPersonId: bigint("contact_person_id", { mode: "number" }).references(
() => contacts.id,
{ onDelete: "set null" }
),
ticketNumber: text("ticket_number"),
})
export type HelpdeskConversation =
typeof helpdesk_conversations.$inferSelect
export type NewHelpdeskConversation =
typeof helpdesk_conversations.$inferInsert

View File

@@ -0,0 +1,46 @@
import {
pgTable,
uuid,
timestamp,
text,
jsonb,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { helpdesk_contacts } from "./helpdesk_contacts"
import { helpdesk_conversations } from "./helpdesk_conversations"
import { authUsers } from "./auth_users"
export const helpdesk_messages = pgTable("helpdesk_messages", {
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade" }),
conversationId: uuid("conversation_id")
.notNull()
.references(() => helpdesk_conversations.id, { onDelete: "cascade" }),
direction: text("direction").notNull(),
authorUserId: uuid("author_user_id").references(() => authUsers.id),
payload: jsonb("payload").notNull(),
rawMeta: jsonb("raw_meta"),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
contactId: uuid("contact_id").references(() => helpdesk_contacts.id, {
onDelete: "set null",
}),
externalMessageId: text("external_message_id").unique(),
receivedAt: timestamp("received_at", { withTimezone: true }).defaultNow(),
})
export type HelpdeskMessage = typeof helpdesk_messages.$inferSelect
export type NewHelpdeskMessage = typeof helpdesk_messages.$inferInsert

View File

@@ -0,0 +1,33 @@
import {
pgTable,
uuid,
timestamp,
text,
jsonb,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const helpdesk_routing_rules = pgTable("helpdesk_routing_rules", {
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade" }),
name: text("name").notNull(),
condition: jsonb("condition").notNull(),
action: jsonb("action").notNull(),
createdBy: uuid("created_by").references(() => authUsers.id),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
})
export type HelpdeskRoutingRule =
typeof helpdesk_routing_rules.$inferSelect
export type NewHelpdeskRoutingRule =
typeof helpdesk_routing_rules.$inferInsert

View File

@@ -0,0 +1,140 @@
import {
pgTable,
bigint,
uuid,
timestamp,
text,
jsonb,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { vendors } from "./vendors"
import { projects } from "./projects"
import { plants } from "./plants"
import { incominginvoices } from "./incominginvoices"
import { contacts } from "./contacts"
import { inventoryitems } from "./inventoryitems"
import { products } from "./products"
import { tasks } from "./tasks"
import { vehicles } from "./vehicles"
import { bankstatements } from "./bankstatements"
import { spaces } from "./spaces"
import { costcentres } from "./costcentres"
import { ownaccounts } from "./ownaccounts"
import { createddocuments } from "./createddocuments"
import { documentboxes } from "./documentboxes"
import { hourrates } from "./hourrates"
import { projecttypes } from "./projecttypes"
import { checks } from "./checks"
import { services } from "./services"
import { events } from "./events"
import { inventoryitemgroups } from "./inventoryitemgroups"
import { authUsers } from "./auth_users"
import {files} from "./files";
export const historyitems = pgTable("historyitems", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
text: text("text").notNull(),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id,
{ onDelete: "cascade" }
),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id),
project: bigint("project", { mode: "number" }).references(
() => projects.id,
{ onDelete: "cascade" }
),
plant: bigint("plant", { mode: "number" }).references(
() => plants.id,
{ onDelete: "cascade" }
),
incomingInvoice: bigint("incomingInvoice", { mode: "number" }).references(
() => incominginvoices.id,
{ onDelete: "cascade" }
),
contact: bigint("contact", { mode: "number" }).references(() => contacts.id, {
onDelete: "cascade",
}),
inventoryitem: bigint("inventoryitem", { mode: "number" }).references(
() => inventoryitems.id,
{ onDelete: "cascade" }
),
product: bigint("product", { mode: "number" }).references(
() => products.id,
{ onDelete: "cascade" }
),
event: bigint("event", { mode: "number" }).references(() => events.id),
newVal: text("newVal"),
oldVal: text("oldVal"),
task: bigint("task", { mode: "number" }).references(() => tasks.id),
vehicle: bigint("vehicle", { mode: "number" }).references(() => vehicles.id),
bankstatement: bigint("bankstatement", { mode: "number" }).references(
() => bankstatements.id
),
space: bigint("space", { mode: "number" }).references(() => spaces.id),
config: jsonb("config"),
projecttype: bigint("projecttype", { mode: "number" }).references(
() => projecttypes.id
),
check: uuid("check").references(() => checks.id),
service: bigint("service", { mode: "number" }).references(
() => services.id
),
createddocument: bigint("createddocument", { mode: "number" }).references(
() => createddocuments.id
),
file: uuid("file").references(() => files.id),
inventoryitemgroup: uuid("inventoryitemgroup").references(
() => inventoryitemgroups.id
),
source: text("source").default("Software"),
costcentre: uuid("costcentre").references(() => costcentres.id),
ownaccount: uuid("ownaccount").references(() => ownaccounts.id),
documentbox: uuid("documentbox").references(() => documentboxes.id),
hourrate: uuid("hourrate").references(() => hourrates.id),
createdBy: uuid("created_by").references(() => authUsers.id),
action: text("action"),
})
export type HistoryItem = typeof historyitems.$inferSelect
export type NewHistoryItem = typeof historyitems.$inferInsert

View File

@@ -0,0 +1,18 @@
import { pgTable, bigint, date, text, timestamp } from "drizzle-orm/pg-core"
export const holidays = pgTable("holidays", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedAlwaysAsIdentity(),
date: date("date").notNull(),
name: text("name").notNull(),
state_code: text("state_code").notNull(),
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
})
export type Holiday = typeof holidays.$inferSelect
export type NewHoliday = typeof holidays.$inferInsert

View File

@@ -0,0 +1,27 @@
import { pgTable, uuid, timestamp, text, boolean, bigint, doublePrecision } from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const hourrates = pgTable("hourrates", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").notNull(),
purchasePrice: doublePrecision("purchasePrice").notNull(),
sellingPrice: doublePrecision("sellingPrice").notNull(),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type HourRate = typeof hourrates.$inferSelect
export type NewHourRate = typeof hourrates.$inferInsert

View File

@@ -0,0 +1,63 @@
import {
pgTable,
bigint,
timestamp,
text,
boolean,
jsonb,
uuid,
} from "drizzle-orm/pg-core"
import { vendors } from "./vendors"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const incominginvoices = pgTable("incominginvoices", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
state: text("state").notNull().default("Entwurf"),
vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id),
reference: text("reference"),
date: text("date"),
document: bigint("document", { mode: "number" }),
dueDate: text("dueDate"),
description: text("description"),
paymentType: text("paymentType"),
accounts: jsonb("accounts").notNull().default([
{
account: null,
taxType: null,
amountNet: null,
amountTax: 19,
costCentre: null,
},
]),
paid: boolean("paid").notNull().default(false),
expense: boolean("expense").notNull().default(true),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
archived: boolean("archived").notNull().default(false),
})
export type IncomingInvoice = typeof incominginvoices.$inferSelect
export type NewIncomingInvoice = typeof incominginvoices.$inferInsert

View File

@@ -0,0 +1,74 @@
export * from "./accounts"
export * from "./auth_profiles"
export * from "./auth_role_permisssions"
export * from "./auth_roles"
export * from "./auth_tenant_users"
export * from "./auth_user_roles"
export * from "./auth_users"
export * from "./bankaccounts"
export * from "./bankrequisitions"
export * from "./bankstatements"
export * from "./checkexecutions"
export * from "./checks"
export * from "./citys"
export * from "./contacts"
export * from "./contracts"
export * from "./costcentres"
export * from "./countrys"
export * from "./createddocuments"
export * from "./createdletters"
export * from "./customers"
export * from "./devices"
export * from "./documentboxes"
export * from "./enums"
export * from "./events"
export * from "./files"
export * from "./filetags"
export * from "./folders"
export * from "./generatedexports"
export * from "./globalmessages"
export * from "./globalmessagesseen"
export * from "./helpdesk_channel_instances"
export * from "./helpdesk_channel_types"
export * from "./helpdesk_contacts"
export * from "./helpdesk_conversation_participants"
export * from "./helpdesk_conversations"
export * from "./helpdesk_messages"
export * from "./helpdesk_routing_rules"
export * from "./historyitems"
export * from "./holidays"
export * from "./hourrates"
export * from "./incominginvoices"
export * from "./inventoryitemgroups"
export * from "./inventoryitems"
export * from "./letterheads"
export * from "./movements"
export * from "./notifications_event_types"
export * from "./notifications_items"
export * from "./notifications_preferences"
export * from "./notifications_preferences_defaults"
export * from "./ownaccounts"
export * from "./plants"
export * from "./productcategories"
export * from "./products"
export * from "./projects"
export * from "./projecttypes"
export * from "./servicecategories"
export * from "./services"
export * from "./spaces"
export * from "./staff_time_entries"
export * from "./staff_time_entry_connects"
export * from "./staff_zeitstromtimestamps"
export * from "./statementallocations"
export * from "./tasks"
export * from "./taxtypes"
export * from "./tenants"
export * from "./texttemplates"
export * from "./units"
export * from "./user_credentials"
export * from "./vehicles"
export * from "./vendors"
export * from "./staff_time_events"
export * from "./serialtypes"
export * from "./serialexecutions"
export * from "./public_links"

View File

@@ -0,0 +1,39 @@
import {
pgTable,
uuid,
timestamp,
text,
boolean,
jsonb, bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const inventoryitemgroups = pgTable("inventoryitemgroups", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" }).notNull().references(() => tenants.id),
name: text("name").notNull(),
inventoryitems: jsonb("inventoryitems").notNull().default([]),
description: text("description"),
archived: boolean("archived").notNull().default(false),
profiles: jsonb("profiles").notNull().default([]),
usePlanning: boolean("usePlanning").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type InventoryItemGroup = typeof inventoryitemgroups.$inferSelect
export type NewInventoryItemGroup = typeof inventoryitemgroups.$inferInsert

View File

@@ -0,0 +1,68 @@
import {
pgTable,
bigint,
timestamp,
text,
boolean,
doublePrecision,
uuid,
jsonb,
date,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { vendors } from "./vendors"
import { spaces } from "./spaces"
import { authUsers } from "./auth_users"
export const inventoryitems = pgTable("inventoryitems", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
usePlanning: boolean("usePlanning").notNull().default(false),
description: text("description"),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
currentSpace: bigint("currentSpace", { mode: "number" }).references(
() => spaces.id
),
articleNumber: text("articleNumber"),
serialNumber: text("serialNumber"),
purchaseDate: date("purchaseDate"),
vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id),
quantity: bigint("quantity", { mode: "number" }).notNull().default(0),
purchasePrice: doublePrecision("purchasePrice").default(0),
manufacturer: text("manufacturer"),
manufacturerNumber: text("manufacturerNumber"),
currentValue: doublePrecision("currentValue"),
archived: boolean("archived").notNull().default(false),
profiles: jsonb("profiles").notNull().default([]),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() =>
authUsers.id
),
})
export type InventoryItem = typeof inventoryitems.$inferSelect
export type NewInventoryItem = typeof inventoryitems.$inferInsert

View File

@@ -0,0 +1,39 @@
import {
pgTable,
bigint,
timestamp,
text,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const letterheads = pgTable("letterheads", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").default("Standard"),
path: text("path").notNull(),
documentTypes: text("documentTypes").array().notNull().default([]),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
archived: boolean("archived").notNull().default(false),
})
export type Letterhead = typeof letterheads.$inferSelect
export type NewLetterhead = typeof letterheads.$inferInsert

View File

@@ -0,0 +1,49 @@
import {
pgTable,
bigint,
timestamp,
text,
uuid,
} from "drizzle-orm/pg-core"
import { products } from "./products"
import { spaces } from "./spaces"
import { tenants } from "./tenants"
import { projects } from "./projects"
import { authUsers } from "./auth_users"
export const movements = pgTable("movements", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
quantity: bigint("quantity", { mode: "number" }).notNull(),
productId: bigint("productId", { mode: "number" })
.notNull()
.references(() => products.id),
spaceId: bigint("spaceId", { mode: "number" }).references(() => spaces.id),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
projectId: bigint("projectId", { mode: "number" }).references(
() => projects.id
),
notes: text("notes"),
serials: text("serials").array(),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type Movement = typeof movements.$inferSelect
export type NewMovement = typeof movements.$inferInsert

View File

@@ -0,0 +1,34 @@
import {
pgTable,
text,
jsonb,
boolean,
timestamp,
} from "drizzle-orm/pg-core"
import {notificationSeverityEnum} from "./enums";
export const notificationsEventTypes = pgTable("notifications_event_types", {
eventKey: text("event_key").primaryKey(),
displayName: text("display_name").notNull(),
description: text("description"),
category: text("category"),
severity: notificationSeverityEnum("severity").notNull().default("info"),
allowedChannels: jsonb("allowed_channels").notNull().default(["inapp", "email"]),
payloadSchema: jsonb("payload_schema"),
isActive: boolean("is_active").notNull().default(true),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
})
export type NotificationsEventType =
typeof notificationsEventTypes.$inferSelect
export type NewNotificationsEventType =
typeof notificationsEventTypes.$inferInsert

View File

@@ -0,0 +1,54 @@
import {
pgTable,
uuid,
bigint,
text,
jsonb,
timestamp,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { notificationsEventTypes } from "./notifications_event_types"
import {notificationChannelEnum, notificationStatusEnum} from "./enums";
export const notificationsItems = pgTable("notifications_items", {
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade", onUpdate: "cascade" }),
userId: uuid("user_id")
.notNull()
.references(() => authUsers.id, { onDelete: "cascade", onUpdate: "cascade" }),
eventType: text("event_type")
.notNull()
.references(() => notificationsEventTypes.eventKey, {
onUpdate: "cascade",
onDelete: "restrict",
}),
title: text("title").notNull(),
message: text("message").notNull(),
payload: jsonb("payload"),
channel: notificationChannelEnum("channel").notNull(),
status: notificationStatusEnum("status").notNull().default("queued"),
error: text("error"),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
sentAt: timestamp("sent_at", { withTimezone: true }),
readAt: timestamp("read_at", { withTimezone: true }),
})
export type NotificationItem = typeof notificationsItems.$inferSelect
export type NewNotificationItem = typeof notificationsItems.$inferInsert

View File

@@ -0,0 +1,60 @@
import {
pgTable,
uuid,
bigint,
text,
boolean,
timestamp,
uniqueIndex,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { notificationsEventTypes } from "./notifications_event_types"
import {notificationChannelEnum} from "./enums";
export const notificationsPreferences = pgTable(
"notifications_preferences",
{
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
userId: uuid("user_id")
.notNull()
.references(() => authUsers.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
eventType: text("event_type")
.notNull()
.references(() => notificationsEventTypes.eventKey, {
onDelete: "restrict",
onUpdate: "cascade",
}),
channel: notificationChannelEnum("channel").notNull(),
enabled: boolean("enabled").notNull().default(true),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
},
(table) => ({
uniquePrefs: uniqueIndex(
"notifications_preferences_tenant_id_user_id_event_type_chan_key",
).on(table.tenantId, table.userId, table.eventType, table.channel),
}),
)
export type NotificationPreference =
typeof notificationsPreferences.$inferSelect
export type NewNotificationPreference =
typeof notificationsPreferences.$inferInsert

View File

@@ -0,0 +1,52 @@
import {
pgTable,
uuid,
bigint,
text,
boolean,
timestamp,
uniqueIndex,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { notificationsEventTypes } from "./notifications_event_types"
import {notificationChannelEnum} from "./enums";
export const notificationsPreferencesDefaults = pgTable(
"notifications_preferences_defaults",
{
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
eventKey: text("event_key")
.notNull()
.references(() => notificationsEventTypes.eventKey, {
onDelete: "restrict",
onUpdate: "cascade",
}),
channel: notificationChannelEnum("channel").notNull(),
enabled: boolean("enabled").notNull().default(true),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
},
(table) => ({
uniqueDefaults: uniqueIndex(
"notifications_preferences_defau_tenant_id_event_key_channel_key",
).on(table.tenantId, table.eventKey, table.channel),
}),
)
export type NotificationPreferenceDefault =
typeof notificationsPreferencesDefaults.$inferSelect
export type NewNotificationPreferenceDefault =
typeof notificationsPreferencesDefaults.$inferInsert

View File

@@ -0,0 +1,39 @@
import {
pgTable,
uuid,
timestamp,
text,
boolean,
jsonb,
bigint,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const ownaccounts = pgTable("ownaccounts", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
number: text("number").notNull(),
name: text("name").notNull(),
description: text("description"),
archived: boolean("archived").notNull().default(false),
profiles: jsonb("profiles").notNull().default([]),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type OwnAccount = typeof ownaccounts.$inferSelect
export type NewOwnAccount = typeof ownaccounts.$inferInsert

View File

@@ -0,0 +1,56 @@
import {
pgTable,
bigint,
timestamp,
text,
jsonb,
boolean,
uuid,
date,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { contracts } from "./contracts"
import { authUsers } from "./auth_users"
export const plants = pgTable("plants", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").notNull(),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
infoData: jsonb("infoData"),
contract: bigint("contract", { mode: "number" }).references(
() => contracts.id
),
description: jsonb("description").default({
html: "",
json: [],
text: "",
}),
archived: boolean("archived").notNull().default(false),
profiles: jsonb("profiles").notNull().default([]),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type Plant = typeof plants.$inferSelect
export type NewPlant = typeof plants.$inferInsert

View File

@@ -0,0 +1,37 @@
import {
pgTable,
bigint,
timestamp,
text,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const productcategories = pgTable("productcategories", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").notNull(),
description: text("description"),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type ProductCategory = typeof productcategories.$inferSelect
export type NewProductCategory = typeof productcategories.$inferInsert

View File

@@ -0,0 +1,69 @@
import {
pgTable,
bigint,
timestamp,
text,
doublePrecision,
boolean,
smallint,
uuid,
jsonb,
json,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { units } from "./units"
import { authUsers } from "./auth_users"
export const products = pgTable("products", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
manufacturer: text("manufacturer"),
unit: bigint("unit", { mode: "number" })
.notNull()
.references(() => units.id),
tags: json("tags").notNull().default([]),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
ean: text("ean"),
barcode: text("barcode"),
purchase_price: doublePrecision("purchasePrice"),
selling_price: doublePrecision("sellingPrice"),
description: text("description"),
manufacturer_number: text("manufacturerNumber"),
vendor_allocation: jsonb("vendorAllocation").default([]),
article_number: text("articleNumber"),
barcodes: text("barcodes").array().notNull().default([]),
productcategories: jsonb("productcategories").default([]),
archived: boolean("archived").notNull().default(false),
tax_percentage: smallint("taxPercentage").notNull().default(19),
markup_percentage: doublePrecision("markupPercentage"),
updated_at: timestamp("updated_at", { withTimezone: true }),
updated_by: uuid("updated_by").references(() => authUsers.id),
})
export type Product = typeof products.$inferSelect
export type NewProduct = typeof products.$inferInsert

View File

@@ -0,0 +1,78 @@
import {
pgTable,
bigint,
timestamp,
text,
jsonb,
json,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { customers } from "./customers"
import { contracts } from "./contracts"
import { projecttypes } from "./projecttypes"
import { authUsers } from "./auth_users"
export const projects = pgTable("projects", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").notNull(),
notes: text("notes"),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
phases: jsonb("phases").default([]),
description: json("description"),
forms: jsonb("forms").default([]),
heroId: text("heroId"),
measure: text("measure"),
material: jsonb("material"),
plant: bigint("plant", { mode: "number" }),
profiles: uuid("profiles").array().notNull().default([]),
projectNumber: text("projectNumber"),
contract: bigint("contract", { mode: "number" }).references(
() => contracts.id
),
projectType: text("projectType").default("Projekt"),
projecttype: bigint("projecttype", { mode: "number" }).references(
() => projecttypes.id
),
archived: boolean("archived").notNull().default(false),
customerRef: text("customerRef"),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
active_phase: text("active_phase"),
})
export type Project = typeof projects.$inferSelect
export type NewProject = typeof projects.$inferInsert

View File

@@ -0,0 +1,41 @@
import {
pgTable,
bigint,
timestamp,
text,
jsonb,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const projecttypes = pgTable("projecttypes", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
initialPhases: jsonb("initialPhases"),
addablePhases: jsonb("addablePhases"),
icon: text("icon"),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type ProjectType = typeof projecttypes.$inferSelect
export type NewProjectType = typeof projecttypes.$inferInsert

View File

@@ -0,0 +1,30 @@
import { pgTable, text, integer, boolean, jsonb, timestamp, uuid } from 'drizzle-orm/pg-core';
import { tenants } from './tenants';
import { authProfiles } from './auth_profiles';
export const publicLinks = pgTable('public_links', {
id: uuid("id").primaryKey().defaultRandom(),
// Der öffentliche Token (z.B. "werkstatt-tablet-01")
token: text('token').notNull().unique(),
// Zuordnung zum Tenant (WICHTIG für die Datentrennung)
tenant: integer('tenant').references(() => tenants.id).notNull(),
defaultProfile: uuid('default_profile').references(() => authProfiles.id),
// Sicherheit
isProtected: boolean('is_protected').default(false).notNull(),
pinHash: text('pin_hash'),
// Konfiguration (JSON)
config: jsonb('config').default({}),
// Metadaten
name: text('name').notNull(),
description: text('description'),
active: boolean('active').default(true).notNull(),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});

View File

@@ -0,0 +1,21 @@
import {
pgTable,
bigint,
timestamp,
text,
jsonb,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import {tenants} from "./tenants";
export const serialExecutions = pgTable("serial_executions", {
id: uuid("id").primaryKey().defaultRandom(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id), executionDate: timestamp("execution_date").notNull(),
status: text("status").default("draft"), // 'draft', 'completed'
createdBy: text("created_by"), // oder UUID, je nach Auth-System
createdAt: timestamp("created_at").defaultNow(),
summary: text("summary"), // z.B. "25 Rechnungen erstellt"
});

View File

@@ -0,0 +1,40 @@
import {
pgTable,
bigint,
timestamp,
text,
jsonb,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const serialtypes = pgTable("serialtypes", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
intervall: text("intervall"),
icon: text("icon"),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type SerialType = typeof serialtypes.$inferSelect
export type NewSerialType = typeof serialtypes.$inferInsert

View File

@@ -0,0 +1,39 @@
import {
pgTable,
bigint,
timestamp,
text,
doublePrecision,
boolean,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const servicecategories = pgTable("servicecategories", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").notNull(),
description: text("description"),
discount: doublePrecision("discount").default(0),
archived: boolean("archived").notNull().default(false),
updated_at: timestamp("updated_at", { withTimezone: true }),
updated_by: uuid("updated_by").references(() => authUsers.id),
})
export type ServiceCategory = typeof servicecategories.$inferSelect
export type NewServiceCategory = typeof servicecategories.$inferInsert

View File

@@ -0,0 +1,63 @@
import {
pgTable,
bigint,
timestamp,
text,
doublePrecision,
jsonb,
boolean,
smallint,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { units } from "./units"
import { authUsers } from "./auth_users"
export const services = pgTable("services", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
sellingPrice: doublePrecision("sellingPrice"),
description: text("description"),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
unit: bigint("unit", { mode: "number" }).references(() => units.id),
serviceNumber: bigint("serviceNumber", { mode: "number" }),
tags: jsonb("tags").default([]),
servicecategories: jsonb("servicecategories").notNull().default([]),
archived: boolean("archived").notNull().default(false),
purchasePriceComposed: jsonb("purchasePriceComposed")
.notNull()
.default({ total: 0 }),
sellingPriceComposed: jsonb("sellingPriceComposed")
.notNull()
.default({ total: 0 }),
taxPercentage: smallint("taxPercentage").notNull().default(19),
materialComposition: jsonb("materialComposition").notNull().default([]),
personalComposition: jsonb("personalComposition").notNull().default([]),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type Service = typeof services.$inferSelect
export type NewService = typeof services.$inferInsert

View File

@@ -0,0 +1,49 @@
import {
pgTable,
bigint,
timestamp,
text,
boolean,
jsonb,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const spaces = pgTable("spaces", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name"),
type: text("type").notNull(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
space_number: text("spaceNumber").notNull(),
parentSpace: bigint("parentSpace", { mode: "number" }).references(
() => spaces.id
),
info_data: jsonb("infoData")
.notNull()
.default({ zip: "", city: "", streetNumber: "" }),
description: text("description"),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type Space = typeof spaces.$inferSelect
export type NewSpace = typeof spaces.$inferInsert

View File

@@ -0,0 +1,68 @@
import {
pgTable,
uuid,
bigint,
timestamp,
integer,
text,
boolean,
numeric,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { timesStateEnum } from "./enums"
import {sql} from "drizzle-orm";
export const stafftimeentries = pgTable("staff_time_entries", {
id: uuid("id").primaryKey().defaultRandom(),
tenant_id: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id),
user_id: uuid("user_id")
.notNull()
.references(() => authUsers.id, { onDelete: "cascade" }),
started_at: timestamp("started_at", { withTimezone: true }).notNull(),
stopped_at: timestamp("stopped_at", { withTimezone: true }),
duration_minutes: integer("duration_minutes").generatedAlwaysAs(
sql`CASE
WHEN stopped_at IS NOT NULL
THEN (EXTRACT(epoch FROM (stopped_at - started_at)) / 60)
ELSE NULL
END`
),
type: text("type").default("work"),
description: text("description"),
created_at: timestamp("created_at", { withTimezone: true }).defaultNow(),
updated_at: timestamp("updated_at", { withTimezone: true }).defaultNow(),
archived: boolean("archived").notNull().default(false),
updated_by: uuid("updated_by").references(() => authUsers.id),
source: text("source"),
state: timesStateEnum("state").notNull().default("draft"),
device: uuid("device"),
internal_note: text("internal_note"),
vacation_reason: text("vacation_reason"),
vacation_days: numeric("vacation_days", { precision: 5, scale: 2 }),
approved_by: uuid("approved_by").references(() => authUsers.id),
approved_at: timestamp("approved_at", { withTimezone: true }),
sick_reason: text("sick_reason"),
})
export type StaffTimeEntry = typeof stafftimeentries.$inferSelect
export type NewStaffTimeEntry = typeof stafftimeentries.$inferInsert

View File

@@ -0,0 +1,38 @@
import {
pgTable,
uuid,
bigint,
timestamp,
integer,
text,
} from "drizzle-orm/pg-core"
import { stafftimeentries } from "./staff_time_entries"
import {sql} from "drizzle-orm";
export const stafftimenetryconnects = pgTable("staff_time_entry_connects", {
id: uuid("id").primaryKey().defaultRandom(),
stafftimeentry: uuid("time_entry_id")
.notNull()
.references(() => stafftimeentries.id, { onDelete: "cascade" }),
project_id: bigint("project_id", { mode: "number" }), // referenziert später projects.id
started_at: timestamp("started_at", { withTimezone: true }).notNull(),
stopped_at: timestamp("stopped_at", { withTimezone: true }).notNull(),
durationMinutes: integer("duration_minutes").generatedAlwaysAs(
sql`(EXTRACT(epoch FROM (stopped_at - started_at)) / 60)`
),
notes: text("notes"),
created_at: timestamp("created_at", { withTimezone: true }).defaultNow(),
updated_at: timestamp("updated_at", { withTimezone: true }).defaultNow(),
})
export type StaffTimeEntryConnect =
typeof stafftimenetryconnects.$inferSelect
export type NewStaffTimeEntryConnect =
typeof stafftimenetryconnects.$inferInsert

View File

@@ -0,0 +1,85 @@
import {
pgTable,
uuid,
bigint,
text,
timestamp,
jsonb,
index,
check,
} from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";
import {tenants} from "./tenants";
import {authUsers} from "./auth_users";
export const stafftimeevents = pgTable(
"staff_time_events",
{
id: uuid("id").primaryKey().defaultRandom(),
tenant_id: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id),
user_id: uuid("user_id")
.notNull()
.references(() => authUsers.id),
// Akteur
actortype: text("actor_type").notNull(), // 'user' | 'system'
actoruser_id: uuid("actor_user_id").references(() => authUsers.id),
// Zeit
eventtime: timestamp("event_time", {
withTimezone: true,
}).notNull(),
// Fachliche Bedeutung
eventtype: text("event_type").notNull(),
// Quelle
source: text("source").notNull(), // web | mobile | terminal | system
// Entkräftung
invalidates_event_id: uuid("invalidates_event_id")
.references(() => stafftimeevents.id),
//Beziehung Approval etc
related_event_id: uuid("related_event_id")
.references(() => stafftimeevents.id),
// Zusatzdaten
metadata: jsonb("metadata"),
// Technisch
created_at: timestamp("created_at", {
withTimezone: true,
})
.defaultNow()
.notNull(),
},
(table) => ({
// Indizes
tenantUserTimeIdx: index("idx_time_events_tenant_user_time").on(
table.tenant_id,
table.user_id,
table.eventtime
),
createdAtIdx: index("idx_time_events_created_at").on(table.created_at),
invalidatesIdx: index("idx_time_events_invalidates").on(
table.invalidates_event_id
),
// Constraints
actorUserCheck: check(
"time_events_actor_user_check",
sql`
(actor_type = 'system' AND actor_user_id IS NULL)
OR
(actor_type = 'user' AND actor_user_id IS NOT NULL)
`
),
})
);

View File

@@ -0,0 +1,44 @@
import {
pgTable,
uuid,
timestamp,
bigint,
text,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authProfiles } from "./auth_profiles"
import { stafftimeentries } from "./staff_time_entries"
export const staffZeitstromTimestamps = pgTable("staff_zeitstromtimestamps", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
profile: uuid("profile")
.notNull()
.references(() => authProfiles.id),
key: text("key").notNull(),
intent: text("intent").notNull(),
time: timestamp("time", { withTimezone: true }).notNull(),
staffTimeEntry: uuid("staff_time_entry").references(
() => stafftimeentries.id
),
internalNote: text("internal_note"),
})
export type StaffZeitstromTimestamp =
typeof staffZeitstromTimestamps.$inferSelect
export type NewStaffZeitstromTimestamp =
typeof staffZeitstromTimestamps.$inferInsert

View File

@@ -0,0 +1,69 @@
import {
pgTable,
uuid,
bigint,
integer,
text,
timestamp,
boolean,
doublePrecision,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { customers } from "./customers"
import { vendors } from "./vendors"
import { ownaccounts } from "./ownaccounts"
import { incominginvoices } from "./incominginvoices"
import { createddocuments } from "./createddocuments"
import { bankstatements } from "./bankstatements"
import { accounts } from "./accounts" // Falls noch nicht erstellt → bitte melden!
export const statementallocations = pgTable("statementallocations", {
id: uuid("id").primaryKey().defaultRandom(),
// foreign keys
bankstatement: integer("bs_id")
.notNull()
.references(() => bankstatements.id),
createddocument: integer("cd_id").references(() => createddocuments.id),
amount: doublePrecision("amount").notNull().default(0),
incominginvoice: bigint("ii_id", { mode: "number" }).references(
() => incominginvoices.id
),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
account: bigint("account", { mode: "number" }).references(
() => accounts.id
),
created_at: timestamp("created_at", {
withTimezone: false,
}).defaultNow(),
ownaccount: uuid("ownaccount").references(() => ownaccounts.id),
description: text("description"),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id),
updated_at: timestamp("updated_at", { withTimezone: true }),
updated_by: uuid("updated_by").references(() => authUsers.id),
archived: boolean("archived").notNull().default(false),
})
export type StatementAllocation = typeof statementallocations.$inferSelect
export type NewStatementAllocation =
typeof statementallocations.$inferInsert

View File

@@ -0,0 +1,51 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { customers } from "./customers"
export const tasks = pgTable("tasks", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
description: text("description"),
categorie: text("categorie"),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
// FIXED: user_id statt profile, verweist auf auth_users.id
userId: uuid("user_id").references(() => authUsers.id),
project: bigint("project", { mode: "number" }),
plant: bigint("plant", { mode: "number" }),
customer: bigint("customer", { mode: "number" }).references(
() => customers.id
),
profiles: jsonb("profiles").notNull().default([]),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type Task = typeof tasks.$inferSelect
export type NewTask = typeof tasks.$inferInsert

View File

@@ -0,0 +1,28 @@
import {
pgTable,
bigint,
timestamp,
text,
uuid,
} from "drizzle-orm/pg-core"
import { authUsers } from "./auth_users"
export const taxTypes = pgTable("taxtypes", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
label: text("label").notNull(),
percentage: bigint("percentage", { mode: "number" }).notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type TaxType = typeof taxTypes.$inferSelect
export type NewTaxType = typeof taxTypes.$inferInsert

View File

@@ -0,0 +1,140 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
integer,
smallint,
date,
uuid,
pgEnum,
} from "drizzle-orm/pg-core"
import { authUsers } from "./auth_users"
import {lockedTenantEnum} from "./enums";
export const tenants = pgTable(
"tenants",
{
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
short: text("short").notNull(),
calendarConfig: jsonb("calendarConfig").default({
eventTypes: [
{ color: "blue", label: "Büro" },
{ color: "yellow", label: "Besprechung" },
{ color: "green", label: "Umsetzung" },
{ color: "red", label: "Vor Ort Termin" },
],
}),
timeConfig: jsonb("timeConfig").notNull().default({}),
tags: jsonb("tags").notNull().default({
products: [],
documents: [],
}),
measures: jsonb("measures")
.notNull()
.default([
{ name: "Netzwerktechnik", short: "NWT" },
{ name: "Elektrotechnik", short: "ELT" },
{ name: "Photovoltaik", short: "PV" },
{ name: "Videüberwachung", short: "VÜA" },
{ name: "Projekt", short: "PRJ" },
{ name: "Smart Home", short: "SHO" },
]),
businessInfo: jsonb("businessInfo").default({
zip: "",
city: "",
name: "",
street: "",
}),
features: jsonb("features").default({
objects: true,
calendar: true,
contacts: true,
projects: true,
vehicles: true,
contracts: true,
inventory: true,
accounting: true,
timeTracking: true,
planningBoard: true,
workingTimeTracking: true,
}),
ownFields: jsonb("ownFields"),
numberRanges: jsonb("numberRanges")
.notNull()
.default({
vendors: { prefix: "", suffix: "", nextNumber: 10000 },
customers: { prefix: "", suffix: "", nextNumber: 10000 },
products: { prefix: "AT-", suffix: "", nextNumber: 1000 },
quotes: { prefix: "AN-", suffix: "", nextNumber: 1000 },
confirmationOrders: { prefix: "AB-", suffix: "", nextNumber: 1000 },
invoices: { prefix: "RE-", suffix: "", nextNumber: 1000 },
spaces: { prefix: "LP-", suffix: "", nextNumber: 1000 },
inventoryitems: { prefix: "IA-", suffix: "", nextNumber: 1000 },
projects: { prefix: "PRJ-", suffix: "", nextNumber: 1000 },
costcentres: { prefix: "KST-", suffix: "", nextNumber: 1000 },
}),
standardEmailForInvoices: text("standardEmailForInvoices"),
extraModules: jsonb("extraModules").notNull().default([]),
isInTrial: boolean("isInTrial").default(false),
trialEndDate: date("trialEndDate"),
stripeCustomerId: text("stripeCustomerId"),
hasActiveLicense: boolean("hasActiveLicense").notNull().default(false),
userLicenseCount: integer("userLicenseCount")
.notNull()
.default(0),
workstationLicenseCount: integer("workstationLicenseCount")
.notNull()
.default(0),
standardPaymentDays: smallint("standardPaymentDays")
.notNull()
.default(14),
dokuboxEmailAddresses: jsonb("dokuboxEmailAddresses").default([]),
dokuboxkey: uuid("dokuboxkey").notNull().defaultRandom(),
autoPrepareIncomingInvoices: boolean("autoPrepareIncomingInvoices")
.default(true),
portalDomain: text("portalDomain"),
portalConfig: jsonb("portalConfig")
.notNull()
.default({ primayColor: "#69c350" }),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
locked: lockedTenantEnum("locked"),
}
)
export type Tenant = typeof tenants.$inferSelect
export type NewTenant = typeof tenants.$inferInsert

View File

@@ -0,0 +1,44 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { textTemplatePositionsEnum } from "./enums"
export const texttemplates = pgTable("texttemplates", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
name: text("name").notNull(),
text: text("text").notNull(),
documentType: text("documentType").default(""),
default: boolean("default").notNull().default(false),
pos: textTemplatePositionsEnum("pos").notNull(),
archived: boolean("archived").notNull().default(false),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type TextTemplate = typeof texttemplates.$inferSelect
export type NewTextTemplate = typeof texttemplates.$inferInsert

View File

@@ -0,0 +1,27 @@
import {
pgTable,
bigint,
timestamp,
text,
} from "drizzle-orm/pg-core"
export const units = pgTable("units", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
single: text("single").notNull(),
multiple: text("multiple"),
short: text("short"),
step: text("step").notNull().default("1"),
})
export type Unit = typeof units.$inferSelect
export type NewUnit = typeof units.$inferInsert

View File

@@ -0,0 +1,53 @@
import {
pgTable,
uuid,
timestamp,
bigint,
boolean,
jsonb,
numeric, pgEnum,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import {credentialTypesEnum} from "./enums";
export const userCredentials = pgTable("user_credentials", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
userId: uuid("user_id")
.notNull()
.references(() => authUsers.id),
updatedAt: timestamp("updated_at", { withTimezone: true }),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id),
smtpPort: numeric("smtp_port"),
smtpSsl: boolean("smtp_ssl"),
type: credentialTypesEnum("type").notNull(),
imapPort: numeric("imap_port"),
imapSsl: boolean("imap_ssl"),
emailEncrypted: jsonb("email_encrypted"),
passwordEncrypted: jsonb("password_encrypted"),
smtpHostEncrypted: jsonb("smtp_host_encrypted"),
imapHostEncrypted: jsonb("imap_host_encrypted"),
accessTokenEncrypted: jsonb("access_token_encrypted"),
refreshTokenEncrypted: jsonb("refresh_token_encrypted"),
})
export type UserCredential = typeof userCredentials.$inferSelect
export type NewUserCredential = typeof userCredentials.$inferInsert

View File

@@ -0,0 +1,57 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
uuid,
doublePrecision,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const vehicles = pgTable("vehicles", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
created_at: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
license_plate: text("licensePlate"),
name: text("name"),
type: text("type"),
active: boolean("active").default(true),
// FIXED: driver references auth_users.id
driver: uuid("driver").references(() => authUsers.id),
vin: text("vin"),
tank_size: doublePrecision("tankSize").notNull().default(0),
archived: boolean("archived").notNull().default(false),
build_year: text("buildYear"),
towing_capacity: bigint("towingCapacity", { mode: "number" }),
power_in_kw: bigint("powerInKW", { mode: "number" }),
color: text("color"),
profiles: jsonb("profiles").notNull().default([]),
updated_at: timestamp("updated_at", { withTimezone: true }),
updated_by: uuid("updated_by").references(() => authUsers.id),
})
export type Vehicle = typeof vehicles.$inferSelect
export type NewVehicle = typeof vehicles.$inferInsert

View File

@@ -0,0 +1,45 @@
import {
pgTable,
bigint,
text,
timestamp,
boolean,
jsonb,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
export const vendors = pgTable("vendors", {
id: bigint("id", { mode: "number" })
.primaryKey()
.generatedByDefaultAsIdentity(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
name: text("name").notNull(),
vendorNumber: text("vendorNumber").notNull(),
tenant: bigint("tenant", { mode: "number" })
.notNull()
.references(() => tenants.id),
infoData: jsonb("infoData").notNull().default({}),
notes: text("notes"),
hasSEPA: boolean("hasSEPA").notNull().default(false),
profiles: jsonb("profiles").notNull().default([]),
archived: boolean("archived").notNull().default(false),
defaultPaymentMethod: text("defaultPaymentMethod"),
updatedAt: timestamp("updated_at", { withTimezone: true }),
updatedBy: uuid("updated_by").references(() => authUsers.id),
})
export type Vendor = typeof vendors.$inferSelect
export type NewVendor = typeof vendors.$inferInsert

View File

@@ -0,0 +1,7 @@
services:
backend:
image: reg.federspiel.software/fedeo/backend:main
restart: always
environment:

11
backend/drizzle.config.ts Normal file
View File

@@ -0,0 +1,11 @@
import { defineConfig } from "drizzle-kit"
import {secrets} from "./src/utils/secrets";
export default defineConfig({
dialect: "postgresql",
schema: "./db/schema",
out: "./db/migrations",
dbCredentials: {
url: secrets.DATABASE_URL || process.env.DATABASE_URL,
},
})

64
backend/package.json Normal file
View File

@@ -0,0 +1,64 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/src/index.js",
"schema:index": "ts-node scripts/generate-schema-index.ts"
},
"repository": {
"type": "git",
"url": "https://git.federspiel.software/fedeo/backend.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-s3": "^3.879.0",
"@aws-sdk/s3-request-presigner": "^3.879.0",
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.1.0",
"@fastify/multipart": "^9.0.3",
"@fastify/swagger": "^9.5.1",
"@fastify/swagger-ui": "^5.2.3",
"@infisical/sdk": "^4.0.6",
"@mmote/niimbluelib": "^0.0.1-alpha.29",
"@prisma/client": "^6.15.0",
"@supabase/supabase-js": "^2.56.1",
"@zip.js/zip.js": "^2.7.73",
"archiver": "^7.0.1",
"axios": "^1.12.1",
"bcrypt": "^6.0.0",
"bwip-js": "^4.8.0",
"crypto": "^1.0.1",
"dayjs": "^1.11.18",
"drizzle-orm": "^0.45.0",
"fastify": "^5.5.0",
"fastify-plugin": "^5.0.1",
"handlebars": "^4.7.8",
"imapflow": "^1.1.1",
"jsonwebtoken": "^9.0.2",
"mailparser": "^3.9.0",
"nodemailer": "^7.0.6",
"openai": "^6.10.0",
"pdf-lib": "^1.17.1",
"pg": "^8.16.3",
"pngjs": "^7.0.0",
"sharp": "^0.34.5",
"xmlbuilder": "^15.1.1",
"zpl-image": "^0.2.0",
"zpl-renderer-js": "^2.0.2"
},
"devDependencies": {
"@types/bcrypt": "^6.0.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^24.3.0",
"drizzle-kit": "^0.31.8",
"prisma": "^6.15.0",
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
}

View File

@@ -0,0 +1,16 @@
import fs from "node:fs"
import path from "node:path"
const schemaDir = path.resolve("db/schema")
const indexFile = path.join(schemaDir, "index.ts")
const files = fs
.readdirSync(schemaDir)
.filter((f) => f.endsWith(".ts") && f !== "index.ts")
const exportsToWrite = files
.map((f) => `export * from "./${f.replace(".ts", "")}"`)
.join("\n")
fs.writeFileSync(indexFile, exportsToWrite)
console.log("✓ schema/index.ts generated")

166
backend/src/index.ts Normal file
View File

@@ -0,0 +1,166 @@
import Fastify from "fastify";
import swaggerPlugin from "./plugins/swagger"
import supabasePlugin from "./plugins/supabase";
import dayjsPlugin from "./plugins/dayjs";
import healthRoutes from "./routes/health";
import meRoutes from "./routes/auth/me";
import tenantRoutes from "./routes/tenant";
import tenantPlugin from "./plugins/tenant";
import authRoutes from "./routes/auth/auth";
import authRoutesAuthenticated from "./routes/auth/auth-authenticated";
import authPlugin from "./plugins/auth";
import adminRoutes from "./routes/admin";
import corsPlugin from "./plugins/cors";
import queryConfigPlugin from "./plugins/queryconfig";
import dbPlugin from "./plugins/db";
import resourceRoutesSpecial from "./routes/resourcesSpecial";
import fastifyCookie from "@fastify/cookie";
import historyRoutes from "./routes/history";
import fileRoutes from "./routes/files";
import functionRoutes from "./routes/functions";
import bankingRoutes from "./routes/banking";
import exportRoutes from "./routes/exports"
import emailAsUserRoutes from "./routes/emailAsUser";
import authProfilesRoutes from "./routes/profiles";
import helpdeskRoutes from "./routes/helpdesk";
import helpdeskInboundRoutes from "./routes/helpdesk.inbound";
import notificationsRoutes from "./routes/notifications";
import staffTimeRoutes from "./routes/staff/time";
import staffTimeConnectRoutes from "./routes/staff/timeconnects";
import userRoutes from "./routes/auth/user";
import publiclinksAuthenticatedRoutes from "./routes/publiclinks/publiclinks-authenticated";
//Public Links
import publiclinksNonAuthenticatedRoutes from "./routes/publiclinks/publiclinks-non-authenticated";
//Resources
import resourceRoutes from "./routes/resources/main";
//M2M
import authM2m from "./plugins/auth.m2m";
import helpdeskInboundEmailRoutes from "./routes/helpdesk.inbound.email";
import deviceRoutes from "./routes/internal/devices";
import tenantRoutesInternal from "./routes/internal/tenant";
import staffTimeRoutesInternal from "./routes/internal/time";
//Devices
import devicesRFIDRoutes from "./routes/devices/rfid";
import {sendMail} from "./utils/mailer";
import {loadSecrets, secrets} from "./utils/secrets";
import {initMailer} from "./utils/mailer"
import {initS3} from "./utils/s3";
//Services
import servicesPlugin from "./plugins/services";
async function main() {
const app = Fastify({ logger: false });
await loadSecrets();
await initMailer();
await initS3();
/*app.addHook("onRequest", (req, reply, done) => {
console.log("Incoming:", req.method, req.url, "Headers:", req.headers)
done()
})*/
// Plugins Global verfügbar
await app.register(swaggerPlugin);
await app.register(corsPlugin);
await app.register(supabasePlugin);
await app.register(tenantPlugin);
await app.register(dayjsPlugin);
await app.register(dbPlugin);
await app.register(servicesPlugin);
app.addHook('preHandler', (req, reply, done) => {
console.log(req.method)
console.log('Matched path:', req.routeOptions.url)
console.log('Exact URL:', req.url)
done()
})
app.get('/health', async (req, res) => {
return res.send({ status: 'ok' })
})
//Plugin nur auf bestimmten Routes
await app.register(queryConfigPlugin, {
routes: ['/api/resource/:resource/paginated']
})
app.register(fastifyCookie, {
secret: secrets.COOKIE_SECRET,
})
// Öffentliche Routes
await app.register(authRoutes);
await app.register(healthRoutes);
await app.register(helpdeskInboundRoutes);
await app.register(publiclinksNonAuthenticatedRoutes)
await app.register(async (m2mApp) => {
await m2mApp.register(authM2m)
await m2mApp.register(helpdeskInboundEmailRoutes)
await m2mApp.register(deviceRoutes)
await m2mApp.register(tenantRoutesInternal)
await m2mApp.register(staffTimeRoutesInternal)
},{prefix: "/internal"})
await app.register(async (devicesApp) => {
await devicesApp.register(devicesRFIDRoutes)
},{prefix: "/devices"})
//Geschützte Routes
await app.register(async (subApp) => {
await subApp.register(authPlugin);
await subApp.register(authRoutesAuthenticated);
await subApp.register(meRoutes);
await subApp.register(tenantRoutes);
await subApp.register(adminRoutes);
await subApp.register(resourceRoutesSpecial);
await subApp.register(historyRoutes);
await subApp.register(fileRoutes);
await subApp.register(functionRoutes);
await subApp.register(bankingRoutes);
await subApp.register(exportRoutes);
await subApp.register(emailAsUserRoutes);
await subApp.register(authProfilesRoutes);
await subApp.register(helpdeskRoutes);
await subApp.register(notificationsRoutes);
await subApp.register(staffTimeRoutes);
await subApp.register(staffTimeConnectRoutes);
await subApp.register(userRoutes);
await subApp.register(publiclinksAuthenticatedRoutes);
await subApp.register(resourceRoutes);
},{prefix: "/api"})
app.ready(async () => {
try {
const result = await app.db.execute("SELECT NOW()");
console.log("✓ DB connection OK: " + JSON.stringify(result.rows[0]));
} catch (err) {
console.log("❌ DB connection failed:", err);
}
});
// Start
try {
await app.listen({ port: secrets.PORT, host: secrets.HOST });
console.log(`🚀 Server läuft auf http://${secrets.HOST}:${secrets.PORT}`);
} catch (err) {
app.log.error(err);
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,253 @@
// /services/bankStatementService.ts
import axios from "axios"
import dayjs from "dayjs"
import utc from "dayjs/plugin/utc.js"
import {secrets} from "../../utils/secrets"
import {FastifyInstance} from "fastify"
// Drizzle imports
import {
bankaccounts,
bankstatements,
} from "../../../db/schema"
import {
eq,
and,
isNull,
} from "drizzle-orm"
dayjs.extend(utc)
interface BalanceAmount {
amount: string
currency: string
}
interface BookedTransaction {
bookingDate: string
valueDate: string
internalTransactionId: string
transactionAmount: { amount: string; currency: string }
creditorAccount?: { iban?: string }
creditorName?: string
debtorAccount?: { iban?: string }
debtorName?: string
remittanceInformationUnstructured?: string
remittanceInformationStructured?: string
remittanceInformationStructuredArray?: string[]
additionalInformation?: string
}
interface TransactionsResponse {
transactions: {
booked: BookedTransaction[]
}
}
const normalizeDate = (val: any) => {
if (!val) return null
const d = new Date(val)
return isNaN(d.getTime()) ? null : d
}
export function bankStatementService(server: FastifyInstance) {
let accessToken: string | null = null
// -----------------------------------------------
// ✔ TOKEN LADEN
// -----------------------------------------------
const getToken = async () => {
console.log("Fetching GoCardless token…")
const response = await axios.post(
`${secrets.GOCARDLESS_BASE_URL}/token/new/`,
{
secret_id: secrets.GOCARDLESS_SECRET_ID,
secret_key: secrets.GOCARDLESS_SECRET_KEY,
}
)
accessToken = response.data.access
}
// -----------------------------------------------
// ✔ Salden laden
// -----------------------------------------------
const getBalanceData = async (accountId: string): Promise<any | false> => {
try {
const {data} = await axios.get(
`${secrets.GOCARDLESS_BASE_URL}/accounts/${accountId}/balances`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
},
}
)
return data
} catch (err: any) {
server.log.error(err.response?.data ?? err)
const expired =
err.response?.data?.summary?.includes("expired") ||
err.response?.data?.detail?.includes("expired")
if (expired) {
await server.db
.update(bankaccounts)
.set({expired: true})
.where(eq(bankaccounts.accountId, accountId))
}
return false
}
}
// -----------------------------------------------
// ✔ Transaktionen laden
// -----------------------------------------------
const getTransactionData = async (accountId: string) => {
try {
const {data} = await axios.get<TransactionsResponse>(
`${secrets.GOCARDLESS_BASE_URL}/accounts/${accountId}/transactions`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
},
}
)
return data.transactions.booked
} catch (err: any) {
server.log.error(err.response?.data ?? err)
return null
}
}
// -----------------------------------------------
// ✔ Haupt-Sync-Prozess
// -----------------------------------------------
const syncAccounts = async (tenantId:number) => {
try {
console.log("Starting account sync…")
// 🟦 DB: Aktive Accounts
const accounts = await server.db
.select()
.from(bankaccounts)
.where(and(eq(bankaccounts.expired, false),eq(bankaccounts.tenant, tenantId)))
if (!accounts.length) return
const allNewTransactions: any[] = []
for (const account of accounts) {
// ---------------------------
// 1. BALANCE SYNC
// ---------------------------
const balData = await getBalanceData(account.accountId)
if (balData === false) break
if (balData) {
const closing = balData.balances.find(
(i: any) => i.balanceType === "closingBooked"
)
const bookedBal = Number(closing.balanceAmount.amount)
await server.db
.update(bankaccounts)
.set({balance: bookedBal})
.where(eq(bankaccounts.id, account.id))
}
// ---------------------------
// 2. TRANSACTIONS
// ---------------------------
let transactions = await getTransactionData(account.accountId)
if (!transactions) continue
//@ts-ignore
transactions = transactions.map((item) => ({
account: account.id,
date: normalizeDate(item.bookingDate),
credIban: item.creditorAccount?.iban ?? null,
credName: item.creditorName ?? null,
text: `
${item.remittanceInformationUnstructured ?? ""}
${item.remittanceInformationStructured ?? ""}
${item.additionalInformation ?? ""}
${item.remittanceInformationStructuredArray?.join("") ?? ""}
`.trim(),
amount: Number(item.transactionAmount.amount),
tenant: account.tenant,
debIban: item.debtorAccount?.iban ?? null,
debName: item.debtorName ?? null,
gocardlessId: item.internalTransactionId,
currency: item.transactionAmount.currency,
valueDate: normalizeDate(item.valueDate),
}))
// Existierende Statements laden
const existing = await server.db
.select({gocardlessId: bankstatements.gocardlessId})
.from(bankstatements)
.where(eq(bankstatements.tenant, account.tenant))
const filtered = transactions.filter(
//@ts-ignore
(tx) => !existing.some((x) => x.gocardlessId === tx.gocardlessId)
)
allNewTransactions.push(...filtered)
}
// ---------------------------
// 3. NEW TRANSACTIONS → DB
// ---------------------------
if (allNewTransactions.length > 0) {
await server.db.insert(bankstatements).values(allNewTransactions)
const affectedAccounts = [
...new Set(allNewTransactions.map((t) => t.account)),
]
const normalizeDate = (val: any) => {
if (!val) return null
const d = new Date(val)
return isNaN(d.getTime()) ? null : d
}
for (const accId of affectedAccounts) {
await server.db
.update(bankaccounts)
//@ts-ignore
.set({syncedAt: normalizeDate(dayjs())})
.where(eq(bankaccounts.id, accId))
}
}
console.log("Bank statement sync completed.")
} catch (error) {
console.error(error)
}
}
return {
run: async (tenant) => {
await getToken()
await syncAccounts(tenant)
console.log("Service: Bankstatement sync finished")
}
}
}

View File

@@ -0,0 +1,259 @@
import axios from "axios"
import dayjs from "dayjs"
import { ImapFlow } from "imapflow"
import { simpleParser } from "mailparser"
import { FastifyInstance } from "fastify"
import {saveFile} from "../../utils/files";
import { secrets } from "../../utils/secrets"
// Drizzle Imports
import {
tenants,
folders,
filetags,
} from "../../../db/schema"
import {
eq,
and,
} from "drizzle-orm"
let badMessageDetected = false
let badMessageMessageSent = false
let client: ImapFlow | null = null
// -------------------------------------------------------------
// IMAP CLIENT INITIALIZEN
// -------------------------------------------------------------
export async function initDokuboxClient() {
client = new ImapFlow({
host: secrets.DOKUBOX_IMAP_HOST,
port: secrets.DOKUBOX_IMAP_PORT,
secure: secrets.DOKUBOX_IMAP_SECURE,
auth: {
user: secrets.DOKUBOX_IMAP_USER,
pass: secrets.DOKUBOX_IMAP_PASSWORD
},
logger: false
})
console.log("Dokubox E-Mail Client Initialized")
await client.connect()
}
// -------------------------------------------------------------
// MAIN SYNC FUNCTION (DRIZZLE VERSION)
// -------------------------------------------------------------
export const syncDokubox = (server: FastifyInstance) =>
async () => {
console.log("Perform Dokubox Sync")
await initDokuboxClient()
if (!client?.usable) {
throw new Error("E-Mail Client not usable")
}
// -------------------------------
// TENANTS LADEN (DRIZZLE)
// -------------------------------
const tenantList = await server.db
.select({
id: tenants.id,
name: tenants.name,
emailAddresses: tenants.dokuboxEmailAddresses,
key: tenants.dokuboxkey
})
.from(tenants)
const lock = await client.getMailboxLock("INBOX")
try {
for await (let msg of client.fetch({ seen: false }, { envelope: true, source: true })) {
const parsed = await simpleParser(msg.source)
const message = {
id: msg.uid,
subject: parsed.subject,
to: parsed.to?.value || [],
cc: parsed.cc?.value || [],
attachments: parsed.attachments || []
}
// -------------------------------------------------
// MAPPING / FIND TENANT
// -------------------------------------------------
const config = await getMessageConfigDrizzle(server, message, tenantList)
if (!config) {
badMessageDetected = true
if (!badMessageMessageSent) {
badMessageMessageSent = true
}
return
}
if (message.attachments.length > 0) {
for (const attachment of message.attachments) {
await saveFile(
server,
config.tenant,
message.id,
attachment,
config.folder,
config.filetype
)
}
}
}
if (!badMessageDetected) {
badMessageDetected = false
badMessageMessageSent = false
}
await client.messageFlagsAdd({ seen: false }, ["\\Seen"])
await client.messageDelete({ seen: true })
} finally {
lock.release()
client.close()
}
}
// -------------------------------------------------------------
// TENANT ERKENNEN + FOLDER/FILETYPES (DRIZZLE VERSION)
// -------------------------------------------------------------
const getMessageConfigDrizzle = async (
server: FastifyInstance,
message,
tenantsList: any[]
) => {
let possibleKeys: string[] = []
if (message.to) {
message.to.forEach((item) =>
possibleKeys.push(item.address.split("@")[0].toLowerCase())
)
}
if (message.cc) {
message.cc.forEach((item) =>
possibleKeys.push(item.address.split("@")[0].toLowerCase())
)
}
// -------------------------------------------
// TENANT IDENTIFY
// -------------------------------------------
let tenant = tenantsList.find((t) => possibleKeys.includes(t.key))
if (!tenant && message.to?.length) {
const address = message.to[0].address.toLowerCase()
tenant = tenantsList.find((t) =>
(t.emailAddresses || []).map((m) => m.toLowerCase()).includes(address)
)
}
if (!tenant) return null
// -------------------------------------------
// FOLDER + FILETYPE VIA SUBJECT
// -------------------------------------------
let folderId = null
let filetypeId = null
// -------------------------------------------
// Rechnung / Invoice
// -------------------------------------------
if (message.subject?.match(/(Rechnung|Beleg|Invoice|Quittung)/gi)) {
const folder = await server.db
.select({ id: folders.id })
.from(folders)
.where(
and(
eq(folders.tenant, tenant.id),
and(
eq(folders.function, "incomingInvoices"),
//@ts-ignore
eq(folders.year, dayjs().format("YYYY"))
)
)
)
.limit(1)
folderId = folder[0]?.id ?? null
const tag = await server.db
.select({ id: filetags.id })
.from(filetags)
.where(
and(
eq(filetags.tenant, tenant.id),
eq(filetags.incomingDocumentType, "invoices")
)
)
.limit(1)
filetypeId = tag[0]?.id ?? null
}
// -------------------------------------------
// Mahnung
// -------------------------------------------
else if (message.subject?.match(/(Mahnung|Zahlungsaufforderung|Zahlungsverzug)/gi)) {
const tag = await server.db
.select({ id: filetags.id })
.from(filetags)
.where(
and(
eq(filetags.tenant, tenant.id),
eq(filetags.incomingDocumentType, "reminders")
)
)
.limit(1)
filetypeId = tag[0]?.id ?? null
}
// -------------------------------------------
// Sonstige Dokumente → Deposit Folder
// -------------------------------------------
else {
const folder = await server.db
.select({ id: folders.id })
.from(folders)
.where(
and(
eq(folders.tenant, tenant.id),
eq(folders.function, "deposit")
)
)
.limit(1)
folderId = folder[0]?.id ?? null
}
return {
tenant: tenant.id,
folder: folderId,
filetype: filetypeId
}
}

View File

@@ -0,0 +1,175 @@
import { FastifyInstance } from "fastify"
import dayjs from "dayjs"
import { getInvoiceDataFromGPT } from "../../utils/gpt"
// Drizzle schema
import {
tenants,
files,
filetags,
incominginvoices,
} from "../../../db/schema"
import { eq, and, isNull, not } from "drizzle-orm"
export function prepareIncomingInvoices(server: FastifyInstance) {
const processInvoices = async (tenantId:number) => {
console.log("▶ Starting Incoming Invoice Preparation")
const tenantsRes = await server.db
.select()
.from(tenants)
.where(eq(tenants.id, tenantId))
.orderBy(tenants.id)
if (!tenantsRes.length) {
console.log("No tenants with autoPrepareIncomingInvoices = true")
return
}
console.log(`Processing tenants: ${tenantsRes.map(t => t.id).join(", ")}`)
// -------------------------------------------------------------
// 2⃣ Jeden Tenant einzeln verarbeiten
// -------------------------------------------------------------
for (const tenant of tenantsRes) {
const tenantId = tenant.id
// 2.1 Datei-Tags holen für incoming invoices
const tagRes = await server.db
.select()
.from(filetags)
.where(
and(
eq(filetags.tenant, tenantId),
eq(filetags.incomingDocumentType, "invoices")
)
)
.limit(1)
const invoiceFileTag = tagRes?.[0]?.id
if (!invoiceFileTag) {
server.log.error(`❌ Missing filetag 'invoices' for tenant ${tenantId}`)
continue
}
// 2.2 Alle Dateien laden, die als Invoice markiert sind aber NOCH keine incominginvoice haben
const filesRes = await server.db
.select()
.from(files)
.where(
and(
eq(files.tenant, tenantId),
eq(files.type, invoiceFileTag),
isNull(files.incominginvoice),
eq(files.archived, false),
not(isNull(files.path))
)
)
if (!filesRes.length) {
console.log(`No invoice files for tenant ${tenantId}`)
continue
}
// -------------------------------------------------------------
// 3⃣ Jede Datei einzeln durch GPT jagen & IncomingInvoice erzeugen
// -------------------------------------------------------------
for (const file of filesRes) {
console.log(`Processing file ${file.id} for tenant ${tenantId}`)
const data = await getInvoiceDataFromGPT(server,file, tenantId)
if (!data) {
server.log.warn(`GPT returned no data for file ${file.id}`)
continue
}
// ---------------------------------------------------------
// 3.1 IncomingInvoice-Objekt vorbereiten
// ---------------------------------------------------------
let itemInfo: any = {
tenant: tenantId,
state: "Vorbereitet"
}
if (data.invoice_number) itemInfo.reference = data.invoice_number
if (data.invoice_date) itemInfo.date = dayjs(data.invoice_date).toISOString()
if (data.issuer?.id) itemInfo.vendor = data.issuer.id
if (data.invoice_duedate) itemInfo.dueDate = dayjs(data.invoice_duedate).toISOString()
// Payment terms mapping
const mapPayment: any = {
"Direct Debit": "Einzug",
"Transfer": "Überweisung",
"Credit Card": "Kreditkarte",
"Other": "Sonstiges",
}
if (data.terms) itemInfo.paymentType = mapPayment[data.terms] ?? data.terms
// 3.2 Positionszeilen konvertieren
if (data.invoice_items?.length > 0) {
itemInfo.accounts = data.invoice_items.map(item => ({
account: item.account_id,
description: item.description,
amountNet: item.total_without_tax,
amountTax: Number((item.total - item.total_without_tax).toFixed(2)),
taxType: String(item.tax_rate),
amountGross: item.total,
costCentre: null,
quantity: item.quantity,
}))
}
// 3.3 Beschreibung generieren
let description = ""
if (data.delivery_note_number) description += `Lieferschein: ${data.delivery_note_number}\n`
if (data.reference) description += `Referenz: ${data.reference}\n`
if (data.invoice_items) {
for (const item of data.invoice_items) {
description += `${item.description} - ${item.quantity} ${item.unit} - ${item.total}\n`
}
}
itemInfo.description = description.trim()
// ---------------------------------------------------------
// 4⃣ IncomingInvoice erstellen
// ---------------------------------------------------------
const inserted = await server.db
.insert(incominginvoices)
.values(itemInfo)
.returning()
const newInvoice = inserted?.[0]
if (!newInvoice) {
server.log.error(`Failed to insert incoming invoice for file ${file.id}`)
continue
}
// ---------------------------------------------------------
// 5⃣ Datei mit incominginvoice-ID verbinden
// ---------------------------------------------------------
await server.db
.update(files)
.set({ incominginvoice: newInvoice.id })
.where(eq(files.id, file.id))
console.log(`IncomingInvoice ${newInvoice.id} created for file ${file.id}`)
}
}
return
}
return {
run: async (tenant:number) => {
await processInvoices(tenant)
console.log("Incoming Invoice Preparation Completed.")
}
}
}

View File

@@ -0,0 +1,38 @@
// modules/helpdesk/helpdesk.contact.service.ts
import { FastifyInstance } from 'fastify'
export async function getOrCreateContact(
server: FastifyInstance,
tenant_id: number,
{ email, phone, display_name, customer_id, contact_id }: { email?: string; phone?: string; display_name?: string; customer_id?: number; contact_id?: number }
) {
if (!email && !phone) throw new Error('Contact must have at least an email or phone')
// Bestehenden Kontakt prüfen
const { data: existing, error: findError } = await server.supabase
.from('helpdesk_contacts')
.select('*')
.eq('tenant_id', tenant_id)
.or(`email.eq.${email || ''},phone.eq.${phone || ''}`)
.maybeSingle()
if (findError) throw findError
if (existing) return existing
// Anlegen
const { data: created, error: insertError } = await server.supabase
.from('helpdesk_contacts')
.insert({
tenant_id,
email,
phone,
display_name,
customer_id,
contact_id
})
.select()
.single()
if (insertError) throw insertError
return created
}

View File

@@ -0,0 +1,90 @@
// modules/helpdesk/helpdesk.conversation.service.ts
import { FastifyInstance } from 'fastify'
import { getOrCreateContact } from './helpdesk.contact.service.js'
import {useNextNumberRangeNumber} from "../../utils/functions";
export async function createConversation(
server: FastifyInstance,
{
tenant_id,
contact,
channel_instance_id,
subject,
customer_id = null,
contact_person_id = null,
}: {
tenant_id: number
contact: { email?: string; phone?: string; display_name?: string }
channel_instance_id: string
subject?: string,
customer_id?: number,
contact_person_id?: number
}
) {
const contactRecord = await getOrCreateContact(server, tenant_id, contact)
const {usedNumber } = await useNextNumberRangeNumber(server, tenant_id, "tickets")
const { data, error } = await server.supabase
.from('helpdesk_conversations')
.insert({
tenant_id,
contact_id: contactRecord.id,
channel_instance_id,
subject: subject || null,
status: 'open',
created_at: new Date().toISOString(),
customer_id,
contact_person_id,
ticket_number: usedNumber
})
.select()
.single()
if (error) throw error
return data
}
export async function getConversations(
server: FastifyInstance,
tenant_id: number,
opts?: { status?: string; limit?: number }
) {
const { status, limit = 50 } = opts || {}
let query = server.supabase.from('helpdesk_conversations').select('*, customer_id(*)').eq('tenant_id', tenant_id)
if (status) query = query.eq('status', status)
query = query.order('last_message_at', { ascending: false }).limit(limit)
const { data, error } = await query
if (error) throw error
const mappedData = data.map(entry => {
return {
...entry,
customer: entry.customer_id
}
})
return mappedData
}
export async function updateConversationStatus(
server: FastifyInstance,
conversation_id: string,
status: string
) {
const valid = ['open', 'in_progress', 'waiting_for_customer', 'answered', 'closed']
if (!valid.includes(status)) throw new Error('Invalid status')
const { data, error } = await server.supabase
.from('helpdesk_conversations')
.update({ status })
.eq('id', conversation_id)
.select()
.single()
if (error) throw error
return data
}

View File

@@ -0,0 +1,60 @@
// modules/helpdesk/helpdesk.message.service.ts
import { FastifyInstance } from 'fastify'
export async function addMessage(
server: FastifyInstance,
{
tenant_id,
conversation_id,
author_user_id = null,
direction = 'incoming',
payload,
raw_meta = null,
external_message_id = null,
}: {
tenant_id: number
conversation_id: string
author_user_id?: string | null
direction?: 'incoming' | 'outgoing' | 'internal' | 'system'
payload: any
raw_meta?: any
external_message_id?: string
}
) {
if (!payload?.text) throw new Error('Message payload requires text content')
const { data: message, error } = await server.supabase
.from('helpdesk_messages')
.insert({
tenant_id,
conversation_id,
author_user_id,
direction,
payload,
raw_meta,
created_at: new Date().toISOString(),
})
.select()
.single()
if (error) throw error
// Letzte Nachricht aktualisieren
await server.supabase
.from('helpdesk_conversations')
.update({ last_message_at: new Date().toISOString() })
.eq('id', conversation_id)
return message
}
export async function getMessages(server: FastifyInstance, conversation_id: string) {
const { data, error } = await server.supabase
.from('helpdesk_messages')
.select('*')
.eq('conversation_id', conversation_id)
.order('created_at', { ascending: true })
if (error) throw error
return data
}

View File

@@ -0,0 +1,148 @@
// services/notification.service.ts
import type { FastifyInstance } from 'fastify';
import {secrets} from "../utils/secrets";
export type NotificationStatus = 'queued' | 'sent' | 'failed';
export interface TriggerInput {
tenantId: number;
userId: string; // muss auf public.auth_users.id zeigen
eventType: string; // muss in notifications_event_types existieren
title: string; // Betreff/Title
message: string; // Klartext-Inhalt
payload?: Record<string, unknown>;
}
export interface UserDirectoryInfo {
email?: string;
}
export type UserDirectory = (server: FastifyInstance, userId: string, tenantId: number) => Promise<UserDirectoryInfo | null>;
export class NotificationService {
constructor(
private server: FastifyInstance,
private getUser: UserDirectory
) {}
/**
* Löst eine E-Mail-Benachrichtigung aus:
* - Validiert den Event-Typ
* - Legt einen Datensatz in notifications_items an (status: queued)
* - Versendet E-Mail (FEDEO Branding)
* - Aktualisiert status/sent_at bzw. error
*/
async trigger(input: TriggerInput) {
const { tenantId, userId, eventType, title, message, payload } = input;
const supabase = this.server.supabase;
// 1) Event-Typ prüfen (aktiv?)
const { data: eventTypeRow, error: etErr } = await supabase
.from('notifications_event_types')
.select('event_key,is_active')
.eq('event_key', eventType)
.maybeSingle();
if (etErr || !eventTypeRow || eventTypeRow.is_active !== true) {
throw new Error(`Unbekannter oder inaktiver Event-Typ: ${eventType}`);
}
// 2) Zieladresse beschaffen
const user = await this.getUser(this.server, userId, tenantId);
if (!user?.email) {
throw new Error(`Nutzer ${userId} hat keine E-Mail-Adresse`);
}
// 3) Notification anlegen (status: queued)
const { data: inserted, error: insErr } = await supabase
.from('notifications_items')
.insert({
tenant_id: tenantId,
user_id: userId,
event_type: eventType,
title,
message,
payload: payload ?? null,
channel: 'email',
status: 'queued'
})
.select('id')
.single();
if (insErr || !inserted) {
throw new Error(`Fehler beim Einfügen der Notification: ${insErr?.message}`);
}
// 4) E-Mail versenden
try {
await this.sendEmail(user.email, title, message);
await supabase
.from('notifications_items')
.update({ status: 'sent', sent_at: new Date().toISOString() })
.eq('id', inserted.id);
return { success: true, id: inserted.id };
} catch (err: any) {
await supabase
.from('notifications_items')
.update({ status: 'failed', error: String(err?.message || err) })
.eq('id', inserted.id);
this.server.log.error({ err, notificationId: inserted.id }, 'E-Mail Versand fehlgeschlagen');
return { success: false, error: err?.message || 'E-Mail Versand fehlgeschlagen' };
}
}
// ---- private helpers ------------------------------------------------------
private async sendEmail(to: string, subject: string, message: string) {
const nodemailer = await import('nodemailer');
const transporter = nodemailer.createTransport({
host: secrets.MAILER_SMTP_HOST,
port: Number(secrets.MAILER_SMTP_PORT),
secure: secrets.MAILER_SMTP_SSL === 'true',
auth: {
user: secrets.MAILER_SMTP_USER,
pass: secrets.MAILER_SMTP_PASS
}
});
const html = this.renderFedeoHtml(subject, message);
await transporter.sendMail({
from: secrets.MAILER_FROM,
to,
subject,
text: message,
html
});
}
private renderFedeoHtml(title: string, message: string) {
return `
<html><body style="font-family:sans-serif;color:#222">
<div style="border:1px solid #ddd;border-radius:8px;padding:16px;max-width:600px;margin:auto">
<h2 style="color:#0f62fe;margin:0 0 12px">FEDEO</h2>
<h3 style="margin:0 0 8px">${this.escapeHtml(title)}</h3>
<p>${this.nl2br(this.escapeHtml(message))}</p>
<hr style="margin:16px 0;border:none;border-top:1px solid #eee"/>
<p style="font-size:12px;color:#666">Automatisch generiert von FEDEO</p>
</div>
</body></html>
`;
}
// simple escaping (ausreichend für unser Template)
private escapeHtml(s: string) {
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
private nl2br(s: string) {
return s.replace(/\n/g, '<br/>');
}
}

View File

@@ -0,0 +1,406 @@
import { FastifyInstance } from 'fastify';
import bcrypt from 'bcrypt';
import crypto from 'crypto';
import {and, eq, inArray, not} from 'drizzle-orm';
import * as schema from '../../db/schema';
import {useNextNumberRangeNumber} from "../utils/functions"; // Pfad anpassen
export const publicLinkService = {
/**
* Erstellt einen neuen Public Link
*/
async createLink(server: FastifyInstance, tenantId: number,
name: string,
isProtected?: boolean,
pin?: string,
customToken?: string,
config?: Record<string, any>,
defaultProfileId?: string) {
let pinHash: string | null = null;
// 1. PIN Hashen, falls Schutz aktiviert ist
if (isProtected && pin) {
pinHash = await bcrypt.hash(pin, 10);
} else if (isProtected && !pin) {
throw new Error("Für geschützte Links muss eine PIN angegeben werden.");
}
// 2. Token generieren oder Custom Token verwenden
let token = customToken;
if (!token) {
// Generiere einen zufälligen Token (z.B. hex string)
// Alternativ: nanoid nutzen, falls installiert
token = crypto.randomBytes(12).toString('hex');
}
// Prüfen, ob Token schon existiert (nur bei Custom Token wichtig)
if (customToken) {
const existing = await server.db.query.publicLinks.findFirst({
where: eq(schema.publicLinks.token, token)
});
if (existing) {
throw new Error(`Der Token '${token}' ist bereits vergeben.`);
}
}
// 3. DB Insert
const [newLink] = await server.db.insert(schema.publicLinks).values({
tenant: tenantId,
name: name,
token: token,
isProtected: isProtected || false,
pinHash: pinHash,
config: config || {},
defaultProfile: defaultProfileId || null,
active: true
}).returning();
return newLink;
},
/**
* Listet alle Links für einen Tenant auf (für die Verwaltungs-UI später)
*/
async getLinksByTenant(server: FastifyInstance, tenantId: number) {
return server.db.select()
.from(schema.publicLinks)
.where(eq(schema.publicLinks.tenant, tenantId));
},
async getLinkContext(server: FastifyInstance, token: string, providedPin?: string) {
// 1. Link laden & Checks
const linkConfig = await server.db.query.publicLinks.findFirst({
where: eq(schema.publicLinks.token, token)
});
if (!linkConfig || !linkConfig.active) throw new Error("Link_NotFound");
// 2. PIN Check
if (linkConfig.isProtected) {
if (!providedPin) throw new Error("Pin_Required");
const isValid = await bcrypt.compare(providedPin, linkConfig.pinHash || "");
if (!isValid) throw new Error("Pin_Invalid");
}
const tenantId = linkConfig.tenant;
const config = linkConfig.config as any;
// --- RESSOURCEN & FILTER ---
// Standardmäßig alles laden, wenn 'resources' nicht definiert ist
const requestedResources: string[] = config.resources || ['profiles', 'projects', 'services', 'units'];
const filters = config.filters || {}; // Erwartet jetzt: { projects: [1,2], services: [3,4] }
const queryPromises: Record<string, Promise<any[]>> = {};
// ---------------------------------------------------------
// 1. PROFILES (Mitarbeiter)
// ---------------------------------------------------------
if (requestedResources.includes('profiles')) {
let profileCondition = and(
eq(schema.authProfiles.tenant_id, tenantId),
eq(schema.authProfiles.active, true)
);
// Sicherheits-Feature: Default Profil erzwingen
if (linkConfig.defaultProfile) {
profileCondition = and(profileCondition, eq(schema.authProfiles.id, linkConfig.defaultProfile));
}
// Optional: Auch hier Filter ermöglichen (falls man z.B. nur 3 bestimmte MA zur Auswahl geben will)
if (filters.profiles && Array.isArray(filters.profiles) && filters.profiles.length > 0) {
profileCondition = and(profileCondition, inArray(schema.authProfiles.id, filters.profiles));
}
queryPromises.profiles = server.db.select({
id: schema.authProfiles.id,
fullName: schema.authProfiles.full_name
})
.from(schema.authProfiles)
.where(profileCondition);
}
// ---------------------------------------------------------
// 2. PROJECTS (Aufträge)
// ---------------------------------------------------------
if (requestedResources.includes('projects')) {
let projectCondition = and(
eq(schema.projects.tenant, tenantId),
not(eq(schema.projects.active_phase, 'Abgeschlossen'))
);
// NEU: Zugriff direkt auf filters.projects
if (filters.projects && Array.isArray(filters.projects) && filters.projects.length > 0) {
projectCondition = and(projectCondition, inArray(schema.projects.id, filters.projects));
}
queryPromises.projects = server.db.select({
id: schema.projects.id,
name: schema.projects.name
})
.from(schema.projects)
.where(projectCondition);
}
// ---------------------------------------------------------
// 3. SERVICES (Tätigkeiten)
// ---------------------------------------------------------
if (requestedResources.includes('services')) {
let serviceCondition = eq(schema.services.tenant, tenantId);
// NEU: Zugriff direkt auf filters.services
if (filters.services && Array.isArray(filters.services) && filters.services.length > 0) {
serviceCondition = and(serviceCondition, inArray(schema.services.id, filters.services));
}
queryPromises.services = server.db.select({
id: schema.services.id,
name: schema.services.name,
unit: schema.services.unit
})
.from(schema.services)
.where(serviceCondition);
}
// ---------------------------------------------------------
// 4. UNITS (Einheiten)
// ---------------------------------------------------------
if (requestedResources.includes('units')) {
// Units werden meist global geladen, könnten aber auch gefiltert werden
queryPromises.units = server.db.select().from(schema.units);
}
// --- QUERY AUSFÜHRUNG ---
const results = await Promise.all(Object.values(queryPromises));
const keys = Object.keys(queryPromises);
const dataResponse: Record<string, any[]> = {
profiles: [],
projects: [],
services: [],
units: []
};
keys.forEach((key, index) => {
dataResponse[key] = results[index];
});
return {
config: linkConfig.config,
meta: {
formName: linkConfig.name,
defaultProfileId: linkConfig.defaultProfile
},
data: dataResponse
};
},
async submitFormData(server: FastifyInstance, token: string, payload: any, providedPin?: string) {
// 1. Validierung (Token & PIN)
const linkConfig = await server.db.query.publicLinks.findFirst({
where: eq(schema.publicLinks.token, token)
});
if (!linkConfig || !linkConfig.active) throw new Error("Link_NotFound");
if (linkConfig.isProtected) {
if (!providedPin) throw new Error("Pin_Required");
const isValid = await bcrypt.compare(providedPin, linkConfig.pinHash || "");
if (!isValid) throw new Error("Pin_Invalid");
}
const tenantId = linkConfig.tenant;
const config = linkConfig.config as any;
// 2. USER ID AUFLÖSEN
// Wir holen die profileId aus dem Link (Default) oder dem Payload (User-Auswahl)
const rawProfileId = linkConfig.defaultProfile || payload.profile;
if (!rawProfileId) throw new Error("Profile_Missing");
// Profil laden, um die user_id zu bekommen
const authProfile = await server.db.query.authProfiles.findFirst({
where: eq(schema.authProfiles.id, rawProfileId)
});
if (!authProfile) throw new Error("Profile_Not_Found");
// Da du sagtest, es gibt immer einen User, verlassen wir uns darauf.
// Falls null, werfen wir einen Fehler, da die DB sonst beim Insert knallt.
const userId = authProfile.user_id;
if (!userId) throw new Error("Profile_Has_No_User_Assigned");
// Helper für Datum
const normalizeDate = (val: any) => {
if (!val) return null
const d = new Date(val)
return isNaN(d.getTime()) ? null : d
}
// =================================================================
// SCHRITT A: Stammdaten laden
// =================================================================
const project = await server.db.query.projects.findFirst({
where: eq(schema.projects.id, payload.project)
});
if (!project) throw new Error("Project not found");
const customer = await server.db.query.customers.findFirst({
where: eq(schema.customers.id, project.customer)
});
const service = await server.db.query.services.findFirst({
where: eq(schema.services.id, payload.service)
});
if (!service) throw new Error("Service not found");
// Texttemplates & Letterhead laden
const texttemplates = await server.db.query.texttemplates.findMany({
where: (t, {and, eq}) => and(
eq(t.tenant, tenantId),
eq(t.documentType, 'deliveryNotes')
)
});
const letterhead = await server.db.query.letterheads.findFirst({
where: eq(schema.letterheads.tenant, tenantId)
});
// =================================================================
// SCHRITT B: Nummernkreis generieren
// =================================================================
const {usedNumber} = await useNextNumberRangeNumber(server, tenantId, "deliveryNotes");
// =================================================================
// SCHRITT C: Berechnungen
// =================================================================
const startDate = normalizeDate(payload.startDate) || new Date();
let endDate = normalizeDate(payload.endDate);
// Fallback Endzeit (+1h)
if (!endDate) {
endDate = server.dayjs(startDate).add(1, 'hour').toDate();
}
// Menge berechnen
let quantity = payload.quantity;
if (!quantity && payload.totalHours) quantity = payload.totalHours;
if (!quantity) {
const diffMin = server.dayjs(endDate).diff(server.dayjs(startDate), 'minute');
quantity = Number((diffMin / 60).toFixed(2));
}
// =================================================================
// SCHRITT D: Lieferschein erstellen
// =================================================================
const createDocData = {
tenant: tenantId,
type: "deliveryNotes",
state: "Entwurf",
customer: project.customer,
//@ts-ignore
address: customer ? {zip: customer.infoData.zip, city: customer.infoData.city, street: customer.infoData.street,} : {},
project: project.id,
documentNumber: usedNumber,
documentDate: String(new Date()), // Schema sagt 'text', evtl toISOString() besser?
deliveryDate: String(startDate), // Schema sagt 'text'
deliveryDateType: "Leistungsdatum",
createdBy: userId, // WICHTIG: Hier die User ID
created_by: userId, // WICHTIG: Hier die User ID
title: "Lieferschein",
description: "",
startText: texttemplates.find((i: any) => i.default && i.pos === "startText")?.text || "",
endText: texttemplates.find((i: any) => i.default && i.pos === "endText")?.text || "",
rows: [
{
pos: "1",
mode: "service",
service: service.id,
text: service.name,
unit: service.unit,
quantity: quantity,
description: service.description || null,
descriptionText: service.description || null,
agriculture: {
dieselUsage: payload.dieselUsage || null,
}
}
],
contactPerson: userId, // WICHTIG: Hier die User ID
letterhead: letterhead?.id,
};
const [createdDoc] = await server.db.insert(schema.createddocuments)
//@ts-ignore
.values(createDocData)
.returning();
// =================================================================
// SCHRITT E: Zeiterfassung Events
// =================================================================
if (config.features?.timeTracking) {
// Metadaten für das Event
const eventMetadata = {
project: project.id,
service: service.id,
description: payload.description || "",
generatedDocumentId: createdDoc.id
};
// 1. START EVENT
await server.db.insert(schema.stafftimeevents).values({
tenant_id: tenantId,
user_id: userId, // WICHTIG: User ID
actortype: "user",
actoruser_id: userId, // WICHTIG: User ID
eventtime: startDate,
eventtype: "START",
source: "PUBLIC_LINK",
metadata: eventMetadata // WICHTIG: Schema heißt 'metadata', nicht 'payload'
});
// 2. STOP EVENT
await server.db.insert(schema.stafftimeevents).values({
tenant_id: tenantId,
user_id: userId,
actortype: "user",
actoruser_id: userId,
eventtime: endDate,
eventtype: "STOP",
source: "PUBLIC_LINK",
metadata: eventMetadata
});
}
// =================================================================
// SCHRITT F: History Items
// =================================================================
const historyItemsToCreate = [];
if (payload.description) {
historyItemsToCreate.push({
tenant: tenantId,
createdBy: userId, // WICHTIG: User ID
text: `Notiz aus Webformular Lieferschein ${createdDoc.documentNumber}: ${payload.description}`,
project: project.id,
createdDocument: createdDoc.id
});
}
historyItemsToCreate.push({
tenant: tenantId,
createdBy: userId, // WICHTIG: User ID
text: `Webformular abgeschickt. Lieferschein ${createdDoc.documentNumber} erstellt. Zeit gebucht (Start/Stop).`,
project: project.id,
createdDocument: createdDoc.id
});
await server.db.insert(schema.historyitems).values(historyItemsToCreate);
return {success: true, documentNumber: createdDoc.documentNumber};
}
}

View File

@@ -0,0 +1,725 @@
import dayjs from "dayjs";
import quarterOfYear from "dayjs/plugin/quarterOfYear";
import Handlebars from "handlebars";
import axios from "axios";
import { eq, inArray, and } from "drizzle-orm"; // Drizzle Operatoren
// DEINE IMPORTS
import * as schema from "../../db/schema"; // Importiere dein Drizzle Schema
import { saveFile } from "../utils/files";
import {FastifyInstance} from "fastify";
import {useNextNumberRangeNumber} from "../utils/functions";
import {createInvoicePDF} from "../utils/pdf"; // Achtung: Muss Node.js Buffer unterstützen!
dayjs.extend(quarterOfYear);
export const executeManualGeneration = async (server:FastifyInstance,executionDate,templateIds,tenantId,executedBy) => {
try {
console.log(executedBy)
const executionDayjs = dayjs(executionDate);
console.log(`Starte manuelle Generierung für Tenant ${tenantId} am ${executionDate}`);
// 1. Tenant laden (Drizzle)
// Wir nehmen an, dass 'tenants' im Schema definiert ist
const [tenant] = await server.db
.select()
.from(schema.tenants)
.where(eq(schema.tenants.id, tenantId))
.limit(1);
if (!tenant) throw new Error(`Tenant mit ID ${tenantId} nicht gefunden.`);
// 2. Templates laden
const templates = await server.db
.select()
.from(schema.createddocuments)
.where(
and(
eq(schema.createddocuments.tenant, tenantId),
eq(schema.createddocuments.type, "serialInvoices"),
inArray(schema.createddocuments.id, templateIds)
)
);
if (templates.length === 0) {
console.warn("Keine passenden Vorlagen gefunden.");
return [];
}
// 3. Folder & FileType IDs holen (Hilfsfunktionen unten)
const folderId = await getFolderId(server,tenantId);
const fileTypeId = await getFileTypeId(server,tenantId);
const results = [];
const [executionRecord] = await server.db
.insert(schema.serialExecutions)
.values({
tenant: tenantId,
executionDate: executionDayjs.toDate(),
status: "draft",
createdBy: executedBy,
summary: `${templateIds.length} Vorlagen verarbeitet`
})
.returning();
console.log(executionRecord);
// 4. Loop durch die Templates
for (const template of templates) {
try {
const resultId = await processSingleTemplate(
server,
template,
tenant,
executionDayjs,
folderId,
fileTypeId,
executedBy,
executionRecord.id
);
results.push({ id: template.id, status: "success", newDocumentId: resultId });
} catch (e: any) {
console.error(`Fehler bei Template ${template.id}:`, e);
results.push({ id: template.id, status: "error", error: e.message });
}
}
return results;
} catch (error) {
console.log(error);
}
}
export const finishManualGeneration = async (server: FastifyInstance, executionId: number) => {
try {
console.log(`Beende Ausführung ${executionId}...`);
// 1. Execution und Tenant laden
const [executionRecord] = await server.db
.select()
.from(schema.serialExecutions)// @ts-ignore
.where(eq(schema.serialExecutions.id, executionId))
.limit(1);
if (!executionRecord) throw new Error("Execution nicht gefunden");
console.log(executionRecord);
const tenantId = executionRecord.tenant;
console.log(tenantId)
// Tenant laden (für Settings etc.)
const [tenant] = await server.db
.select()
.from(schema.tenants)
.where(eq(schema.tenants.id, tenantId))
.limit(1);
// 2. Status auf "processing" setzen (optional, damit UI feedback hat)
/*await server.db
.update(schema.serialExecutions)
.set({ status: "processing" })// @ts-ignore
.where(eq(schema.serialExecutions.id, executionId));*/
// 3. Alle erstellten Dokumente dieser Execution laden
const documents = await server.db
.select()
.from(schema.createddocuments)
.where(eq(schema.createddocuments.serialexecution, executionId));
console.log(`${documents.length} Dokumente werden finalisiert...`);
// 4. IDs für File-System laden (nur einmalig nötig)
const folderId = await getFolderId(server, tenantId);
const fileTypeId = await getFileTypeId(server, tenantId);
// Globale Daten laden, die für alle gleich sind (Optimierung)
const [units, products, services] = await Promise.all([
server.db.select().from(schema.units),
server.db.select().from(schema.products).where(eq(schema.products.tenant, tenantId)),
server.db.select().from(schema.services).where(eq(schema.services.tenant, tenantId)),
]);
let successCount = 0;
let errorCount = 0;
// 5. Loop durch Dokumente
for (const doc of documents) {
try {
const [letterhead] = await Promise.all([
/*fetchById(server, schema.contacts, doc.contact),
fetchById(server, schema.customers, doc.customer),
fetchById(server, schema.authProfiles, doc.contactPerson), // oder createdBy, je nach Logik
fetchById(server, schema.projects, doc.project),
fetchById(server, schema.contracts, doc.contract),*/
doc.letterhead ? fetchById(server, schema.letterheads, doc.letterhead) : null
]);
const pdfData = await getCloseData(
server,
doc,
tenant,
units,
products,
services,
);
console.log(pdfData);
// D. PDF Generieren
const pdfBase64 = await createInvoicePDF(server,"base64",pdfData, letterhead?.path);
console.log(pdfBase64);
// E. Datei speichern
// @ts-ignore
const fileBuffer = Buffer.from(pdfBase64.base64, "base64");
const filename = `${pdfData.documentNumber}.pdf`;
await saveFile(
server,
tenantId,
null, // User ID (hier ggf. executionRecord.createdBy nutzen wenn verfügbar)
fileBuffer,
folderId,
fileTypeId,
{
createddocument: doc.id,
filename: filename,
filesize: fileBuffer.length // Falls saveFile das braucht
}
);
// F. Dokument in DB final updaten
await server.db
.update(schema.createddocuments)
.set({
state: "Gebucht",
documentNumber: pdfData.documentNumber,
title: pdfData.title,
pdf_path: filename // Optional, falls du den Pfad direkt am Doc speicherst
})
.where(eq(schema.createddocuments.id, doc.id));
successCount++;
} catch (innerErr) {
console.error(`Fehler beim Finalisieren von Doc ID ${doc.id}:`, innerErr);
errorCount++;
// Optional: Status des einzelnen Dokuments auf Error setzen
}
}
// 6. Execution abschließen
const finalStatus = errorCount > 0 ? "error" : "completed"; // Oder 'completed' auch wenn Teilerfolge
await server.db
.update(schema.serialExecutions)
.set({
status: finalStatus,
summary: `Abgeschlossen: ${successCount} erfolgreich, ${errorCount} Fehler.`
})// @ts-ignore
.where(eq(schema.serialExecutions.id, executionId));
return { success: true, processed: successCount, errors: errorCount };
} catch (error) {
console.error("Critical Error in finishManualGeneration:", error);
// Execution auf Error setzen
// @ts-ignore
await server.db
.update(schema.serialExecutions)
.set({ status: "error", summary: "Kritischer Fehler beim Finalisieren." })
//@ts-ignore
.where(eq(schema.serialExecutions.id, executionId));
throw error;
}
}
/**
* Verarbeitet eine einzelne Vorlage
*/
async function processSingleTemplate(server: FastifyInstance, template: any, tenant: any,executionDate: dayjs.Dayjs,folderId: string,fileTypeId: string,executedBy: string,executionId: string) {
// A. Zugehörige Daten parallel laden
const [contact, customer, profile, project, contract, units, products, services, letterhead] = await Promise.all([
fetchById(server, schema.contacts, template.contact),
fetchById(server, schema.customers, template.customer),
fetchById(server, schema.authProfiles, template.contactPerson),
fetchById(server, schema.projects, template.project),
fetchById(server, schema.contracts, template.contract),
server.db.select().from(schema.units),
server.db.select().from(schema.products).where(eq(schema.products.tenant, tenant.id)),
server.db.select().from(schema.services).where(eq(schema.services.tenant, tenant.id)),
template.letterhead ? fetchById(server, schema.letterheads, template.letterhead) : null
]);
// B. Datumsberechnung (Logik aus dem Original)
const { firstDate, lastDate } = calculateDateRange(template.serialConfig, executionDate);
// C. Rechnungsnummer & Save Data
const savePayload = await getSaveData(
template,
tenant,
firstDate,
lastDate,
executionDate.toISOString(),
executedBy
);
const payloadWithRelation = {
...savePayload,
serialexecution: executionId
};
// D. Dokument in DB anlegen (Drizzle Insert)
const [createdDoc] = await server.db
.insert(schema.createddocuments)
.values(payloadWithRelation)
.returning(); // Wichtig für Postgres: returning() gibt das erstellte Objekt zurück
return createdDoc.id;
}
// --- Drizzle Helper ---
async function fetchById(server: FastifyInstance, table: any, id: number | null) {
if (!id) return null;
const [result] = await server.db.select().from(table).where(eq(table.id, id)).limit(1);
return result || null;
}
async function getFolderId(server:FastifyInstance, tenantId: number) {
const [folder] = await server.db
.select({ id: schema.folders.id })
.from(schema.folders)
.where(
and(
eq(schema.folders.tenant, tenantId),
eq(schema.folders.function, "invoices"), // oder 'invoices', check deine DB
eq(schema.folders.year, dayjs().format("YYYY"))
)
)
.limit(1);
return folder?.id;
}
async function getFileTypeId(server: FastifyInstance,tenantId: number) {
const [tag] = await server.db
.select({ id: schema.filetags.id })
.from(schema.filetags)
.where(
and(
eq(schema.filetags.tenant, tenantId),
eq(schema.filetags.createdDocumentType, "invoices")
)
)
.limit(1);
return tag?.id;
}
// --- Logik Helper (Unverändert zur Business Logik) ---
function calculateDateRange(config: any, executionDate: dayjs.Dayjs) {
// Basis nehmen
let baseDate = executionDate;
let firstDate = baseDate;
let lastDate = baseDate;
if (config.intervall === "monatlich" && config.dateDirection === "Rückwirkend") {
// 1. Monat abziehen
// 2. Start/Ende des Monats berechnen
// 3. WICHTIG: Zeit hart auf 12:00:00 setzen, damit Zeitzonen das Datum nicht kippen
firstDate = baseDate.subtract(1, "month").startOf("month").hour(12).minute(0).second(0).millisecond(0);
lastDate = baseDate.subtract(1, "month").endOf("month").hour(12).minute(0).second(0).millisecond(0);
} else if (config.intervall === "vierteljährlich" && config.dateDirection === "Rückwirkend") {
firstDate = baseDate.subtract(1, "quarter").startOf("quarter").hour(12).minute(0).second(0).millisecond(0);
lastDate = baseDate.subtract(1, "quarter").endOf("quarter").hour(12).minute(0).second(0).millisecond(0);
}
// Das Ergebnis ist nun z.B.:
// firstDate: '2025-12-01T12:00:00.000Z' (Eindeutig der 1. Dezember)
// lastDate: '2025-12-31T12:00:00.000Z' (Eindeutig der 31. Dezember)
return {
firstDate: firstDate.toISOString(),
lastDate: lastDate.toISOString()
};
}
async function getSaveData(item: any, tenant: any, firstDate: string, lastDate: string, executionDate: string, executedBy: string) {
const cleanRows = item.rows.map((row: any) => ({
...row,
descriptionText: row.description || null,
}));
//const documentNumber = await this.useNextInvoicesNumber(item.tenant);
return {
tenant: item.tenant,
type: "invoices",
state: "Entwurf",
customer: item.customer,
contact: item.contact,
contract: item.contract,
address: item.address,
project: item.project,
documentDate: executionDate,
deliveryDate: firstDate,
deliveryDateEnd: lastDate,
paymentDays: item.paymentDays,
payment_type: item.payment_type,
deliveryDateType: item.deliveryDateType,
info: {}, // Achtung: Postgres erwartet hier valides JSON Objekt
createdBy: item.createdBy,
created_by: item.created_by,
title: `Rechnung-Nr. XXX`,
description: item.description,
startText: item.startText,
endText: item.endText,
rows: cleanRows, // JSON Array
contactPerson: item.contactPerson,
linkedDocument: item.linkedDocument,
letterhead: item.letterhead,
taxType: item.taxType,
};
}
async function getCloseData(server:FastifyInstance,item: any, tenant: any, units, products,services) {
const documentNumber = await useNextNumberRangeNumber(server,tenant.id,"invoices");
console.log(item);
const [contact, customer, project, contract] = await Promise.all([
fetchById(server, schema.contacts, item.contact),
fetchById(server, schema.customers, item.customer),
fetchById(server, schema.projects, item.project),
fetchById(server, schema.contracts, item.contract),
item.letterhead ? fetchById(server, schema.letterheads, item.letterhead) : null
]);
const profile = (await server.db.select().from(schema.authProfiles).where(and(eq(schema.authProfiles.user_id, item.created_by),eq(schema.authProfiles.tenant_id,tenant.id))).limit(1))[0];
console.log(profile)
const pdfData = getDocumentDataBackend(
{
...item,
state: "Gebucht",
documentNumber: documentNumber.usedNumber,
title: `Rechnung-Nr. ${documentNumber.usedNumber}`,
}, // Das Dokument (mit neuer Nummer)
tenant, // Tenant Object
customer, // Customer Object
contact, // Contact Object (kann null sein)
profile, // User Profile (Contact Person)
project, // Project Object
contract, // Contract Object
units, // Units Array
products, // Products Array
services // Services Array
);
return pdfData;
}
// Formatiert Zahlen zu deutscher Währung
function renderCurrency(value: any, currency = "€") {
if (value === undefined || value === null) return "0,00 " + currency;
return Number(value).toFixed(2).replace(".", ",") + " " + currency;
}
// Berechnet den Zeilenpreis (Menge * Preis * Rabatt)
function getRowAmount(row: any) {
const price = Number(row.price || 0);
const quantity = Number(row.quantity || 0);
const discount = Number(row.discountPercent || 0);
return quantity * price * (1 - discount / 100);
}
// Berechnet alle Summen (Netto, Brutto, Steuern, Titel-Summen)
// Dies ersetzt 'documentTotal.value' aus dem Frontend
function calculateDocumentTotals(rows: any[], taxType: string) {
console.log(rows);
let totalNet = 0;
let totalNet19 = 0;
let totalNet7 = 0;
let totalNet0 = 0;
let titleSums: Record<string, number> = {};
// Aktueller Titel für Gruppierung
let currentTitle = "";
rows.forEach(row => {
if (row.mode === 'title') {
currentTitle = row.text || row.description || "Titel";
if (!titleSums[currentTitle]) titleSums[currentTitle] = 0;
return;
}
if (['normal', 'service', 'free'].includes(row.mode)) {
const amount = getRowAmount(row);
totalNet += amount;
// Summen pro Titel addieren
//if (!titleSums[currentTitle]) titleSums[currentTitle] = 0;
if(currentTitle.length > 0) titleSums[currentTitle] += amount;
// Steuer-Logik
const tax = taxType === "19 UStG" || taxType === "13b UStG" ? 0 : Number(row.taxPercent);
if (tax === 19) totalNet19 += amount;
else if (tax === 7) totalNet7 += amount;
else totalNet0 += amount;
}
});
const isTaxFree = ["13b UStG", "19 UStG"].includes(taxType);
const tax19 = isTaxFree ? 0 : totalNet19 * 0.19;
const tax7 = isTaxFree ? 0 : totalNet7 * 0.07;
const totalGross = totalNet + tax19 + tax7;
return {
totalNet,
totalNet19,
totalNet7,
totalNet0,
total19: tax19,
total7: tax7,
total0: 0,
totalGross,
titleSums // Gibt ein Objekt zurück: { "Titel A": 150.00, "Titel B": 200.00 }
};
}
export function getDocumentDataBackend(
itemInfo: any, // Das Dokument objekt (createddocument)
tenant: any, // Tenant Infos (auth.activeTenantData)
customerData: any, // Geladener Kunde
contactData: any, // Geladener Kontakt (optional)
contactPerson: any, // Geladenes User-Profil (ersetzt den API Call)
projectData: any, // Projekt
contractData: any, // Vertrag
units: any[], // Array aller Einheiten
products: any[], // Array aller Produkte
services: any[] // Array aller Services
) {
const businessInfo = tenant.businessInfo || {}; // Fallback falls leer
// --- 1. Agriculture Logic ---
// Prüfen ob 'extraModules' existiert, sonst leeres Array annehmen
const modules = tenant.extraModules || [];
if (modules.includes("agriculture")) {
itemInfo.rows.forEach((row: any) => {
if (row.agriculture && row.agriculture.dieselUsage) {
row.agriculture.description = `${row.agriculture.dieselUsage} L Diesel zu ${renderCurrency(row.agriculture.dieselPrice)}/L verbraucht ${row.description ? "\n" + row.description : ""}`;
}
});
}
// --- 2. Tax Override Logic ---
let rows = JSON.parse(JSON.stringify(itemInfo.rows)); // Deep Clone um Original nicht zu mutieren
if (itemInfo.taxType === "13b UStG" || itemInfo.taxType === "19 UStG") {
rows = rows.map((row: any) => ({ ...row, taxPercent: 0 }));
}
// --- 4. Berechnungen (Ersetzt Vue computed props) ---
const totals = calculateDocumentTotals(rows, itemInfo.taxType);
console.log(totals);
// --- 3. Rows Mapping & Processing ---
rows = rows.map((row: any) => {
const unit = units.find(i => i.id === row.unit) || { short: "" };
// Description Text Logic
if (!['pagebreak', 'title'].includes(row.mode)) {
if (row.agriculture && row.agriculture.description) {
row.descriptionText = row.agriculture.description;
} else if (row.description) {
row.descriptionText = row.description;
} else {
delete row.descriptionText;
}
}
// Product/Service Name Resolution
if (!['pagebreak', 'title', 'text'].includes(row.mode)) {
if (row.mode === 'normal') {
const prod = products.find(i => i.id === row.product);
if (prod) row.text = prod.name;
}
if (row.mode === 'service') {
const serv = services.find(i => i.id === row.service);
if (serv) row.text = serv.name;
}
const rowAmount = getRowAmount(row);
return {
...row,
rowAmount: renderCurrency(rowAmount),
quantity: String(row.quantity).replace(".", ","),
unit: unit.short,
pos: String(row.pos),
price: renderCurrency(row.price),
discountText: row.discountPercent > 0 ? `(Rabatt: ${row.discountPercent} %)` : ""
};
} else {
return row;
}
});
// --- 5. Handlebars Context ---
const generateContext = () => {
return {
// lohnkosten: null, // Backend hat diesen Wert oft nicht direkt, ggf. aus itemInfo holen
anrede: (contactData && contactData.salutation) || (customerData && customerData.salutation),
titel: (contactData && contactData.title) || (customerData && customerData.title),
vorname: (contactData && contactData.firstName) || (customerData && customerData.firstname), // Achte auf casing (firstName vs firstname) in deiner DB
nachname: (contactData && contactData.lastName) || (customerData && customerData.lastname),
kundenname: customerData && customerData.name,
zahlungsziel_in_tagen: itemInfo.paymentDays,
zahlungsart: itemInfo.payment_type === "transfer" ? "Überweisung" : "Lastschrift",
diesel_gesamtverbrauch: (itemInfo.agriculture && itemInfo.agriculture.dieselUsageTotal) || null
};
};
const templateStartText = Handlebars.compile(itemInfo.startText || "");
const templateEndText = Handlebars.compile(itemInfo.endText || "");
// --- 6. Title Sums Formatting ---
let returnTitleSums: Record<string, string> = {};
Object.keys(totals.titleSums).forEach(key => {
returnTitleSums[key] = renderCurrency(totals.titleSums[key]);
});
// Transfer logic (Falls nötig, hier vereinfacht)
let returnTitleSumsTransfer = { ...returnTitleSums };
// --- 7. Construct Final Object ---
// Adresse aufbereiten
const recipientArray = [
customerData.name,
...(customerData.nameAddition ? [customerData.nameAddition] : []),
...(contactData ? [`${contactData.firstName} ${contactData.lastName}`] : []),
itemInfo.address?.street || customerData.street || "",
...(itemInfo.address?.special ? [itemInfo.address.special] : []),
`${itemInfo.address?.zip || customerData.zip} ${itemInfo.address?.city || customerData.city}`,
].filter(Boolean); // Leere Einträge entfernen
console.log(contactPerson)
// Info Block aufbereiten
const infoBlock = [
{
label: itemInfo.documentNumberTitle || "Rechnungsnummer",
content: itemInfo.documentNumber || "ENTWURF",
}, {
label: "Kundennummer",
content: customerData.customerNumber,
}, {
label: "Belegdatum",
content: itemInfo.documentDate ? dayjs(itemInfo.documentDate).format("DD.MM.YYYY") : dayjs().format("DD.MM.YYYY"),
},
// Lieferdatum Logik
...(itemInfo.deliveryDateType !== "Kein Lieferdatum anzeigen" ? [{
label: itemInfo.deliveryDateType || "Lieferdatum",
content: !['Lieferzeitraum', 'Leistungszeitraum'].includes(itemInfo.deliveryDateType)
? (itemInfo.deliveryDate ? dayjs(itemInfo.deliveryDate).format("DD.MM.YYYY") : "")
: `${itemInfo.deliveryDate ? dayjs(itemInfo.deliveryDate).format("DD.MM.YYYY") : ""} - ${itemInfo.deliveryDateEnd ? dayjs(itemInfo.deliveryDateEnd).format("DD.MM.YYYY") : ""}`,
}] : []),
{
label: "Ansprechpartner",
content: contactPerson ? (contactPerson.name || contactPerson.full_name || contactPerson.email) : "-",
},
// Kontakt Infos
...((itemInfo.contactTel || contactPerson?.fixed_tel || contactPerson?.mobile_tel) ? [{
label: "Telefon",
content: itemInfo.contactTel || contactPerson?.fixed_tel || contactPerson?.mobile_tel,
}] : []),
...(contactPerson?.email ? [{
label: "E-Mail",
content: contactPerson.email,
}] : []),
// Objekt / Projekt / Vertrag
...(itemInfo.plant ? [{ label: "Objekt", content: "Objekt Name" }] : []), // Hier müsstest du Plant Data übergeben wenn nötig
...(projectData ? [{ label: "Projekt", content: projectData.name }] : []),
...(contractData ? [{ label: "Vertrag", content: contractData.contractNumber }] : [])
];
// Total Array für PDF Footer
const totalArray = [
{
label: "Nettobetrag",
content: renderCurrency(totals.totalNet),
},
...(totals.totalNet19 > 0 && !["13b UStG"].includes(itemInfo.taxType) ? [{
label: `zzgl. 19% USt auf ${renderCurrency(totals.totalNet19)}`,
content: renderCurrency(totals.total19),
}] : []),
...(totals.totalNet7 > 0 && !["13b UStG"].includes(itemInfo.taxType) ? [{
label: `zzgl. 7% USt auf ${renderCurrency(totals.totalNet7)}`,
content: renderCurrency(totals.total7),
}] : []),
{
label: "Gesamtbetrag",
content: renderCurrency(totals.totalGross),
},
];
return {
...itemInfo,
type: itemInfo.type,
taxType: itemInfo.taxType,
adressLine: `${businessInfo.name || ''}, ${businessInfo.street || ''}, ${businessInfo.zip || ''} ${businessInfo.city || ''}`,
recipient: recipientArray,
info: infoBlock,
title: itemInfo.title,
description: itemInfo.description,
// Handlebars Compilation ausführen
endText: templateEndText(generateContext()),
startText: templateStartText(generateContext()),
rows: rows,
totalArray: totalArray,
total: {
totalNet: renderCurrency(totals.totalNet),
total19: renderCurrency(totals.total19),
total0: renderCurrency(totals.total0), // 0% USt Zeilen
totalGross: renderCurrency(totals.totalGross),
// Diese Werte existieren im einfachen Backend-Kontext oft nicht (Zahlungen checken), daher 0 oder Logik bauen
totalGrossAlreadyPaid: renderCurrency(0),
totalSumToPay: renderCurrency(totals.totalGross),
titleSums: returnTitleSums,
titleSumsTransfer: returnTitleSumsTransfer
},
agriculture: itemInfo.agriculture,
// Falls du AdvanceInvoices brauchst, musst du die Objekte hier übergeben oder leer lassen
usedAdvanceInvoices: []
};
}

Some files were not shown because too many files have changed in this diff Show More