diff --git a/.claude/settings.local.json b/.claude/settings.local.json index f36f964..5373f35 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -20,7 +20,12 @@ "Skill(run)", "Bash(ddev describe *)", "Bash(curl *)", - "Bash(ddev mysql *)" + "Bash(ddev mysql *)", + "Bash(git status *)", + "Bash(npx playwright *)", + "Bash(npm init *)", + "Bash(npm install *)", + "Bash(NODE_TLS_REJECT_UNAUTHORIZED=0 node test.mjs)" ] } } diff --git a/httpdocs/_backups/spawntree-2026-06-22-backup.xml b/httpdocs/_backups/spawntree-2026-06-22-backup.xml new file mode 100644 index 0000000..d2796ce --- /dev/null +++ b/httpdocs/_backups/spawntree-2026-06-22-backup.xml @@ -0,0 +1,5800 @@ + + + 2026-06-22T10:34:01+02:00 + 1.1 + 2018-03-19T15:03:50+01:00 + EUR + 43709 + spawntree + spawntree GmbH + 2025-04-19T03:35:15+02:00 + + + + 2018-03-19T15:05:22+01:00 + 7500 + 487895 + Dr. Odin + + 2019-01-10T09:57:28+01:00 + false + + + + + 2018-07-26T10:32:39+02:00 + 9000 + 515306 + Matsen + + 2022-06-03T09:52:58+02:00 + false + hourly_rate + + + + 2018-11-27T17:13:25+01:00 + 0 + 531184 + spawntree GmbH + + 2019-09-17T12:44:14+02:00 + false + hourly_rate + + + + 2019-01-02T13:02:55+01:00 + 11250 + 534086 + followfood + 100 EUR + 12,50 EUR an TankTank + 2025-01-29T17:40:30+01:00 + false + hourly_rate + + + 402469 + 7500 + + + 402470 + 7500 + + + 402471 + 7500 + + + 402472 + 7500 + + + 402473 + 7500 + + + 403333 + 7500 + + + 404635 + 7500 + + + 425720 + 7500 + + + + + 2019-01-02T13:05:14+01:00 + 0 + 534087 + Northworks GmbH + + 2019-01-10T09:59:10+01:00 + false + hourly_rates_per_service + + + 402469 + 6875 + + + 402470 + 6875 + + + 402471 + 6875 + + + 402472 + 6875 + + + 402473 + 6875 + + + 403333 + 6875 + + + 404635 + 6875 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 6875 + + + + + 2019-01-07T15:32:43+01:00 + 0 + 534671 + Colt UX + + 2019-01-16T20:13:28+01:00 + false + hourly_rates_per_service + + + 402469 + 6500 + + + 402470 + 6500 + + + 402471 + 6500 + + + 402472 + 6500 + + + 402473 + 6500 + + + 403333 + 6500 + + + 404635 + 6500 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 6500 + + + + + 2019-01-07T17:01:56+01:00 + 7500 + 534716 + hauni + + 2019-01-10T09:57:35+01:00 + false + + + + + 2019-01-10T10:03:26+01:00 + 8000 + 535258 + Heike Manthey + + 2023-03-02T16:04:39+01:00 + false + hourly_rates_per_service + + + 402469 + 8000 + + + 402470 + 8000 + + + 402471 + 8000 + + + 402472 + 8000 + + + 402473 + 8000 + + + 403333 + 8000 + + + 404635 + 8000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 8000 + + + + + 2019-01-10T12:24:19+01:00 + 8500 + 535326 + Kita Himmelblau + + 2022-06-16T17:05:48+02:00 + false + hourly_rate + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + + + 2019-01-10T12:26:15+01:00 + 0 + 535327 + SMC Consult GmbH + + 2019-01-10T12:26:15+01:00 + false + hourly_rates_per_service + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + + + 2019-01-16T15:12:09+01:00 + 9000 + 536229 + Thielbeer Consulting + + 2022-09-20T13:13:56+02:00 + false + hourly_rate + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + + + 2019-01-17T15:57:45+01:00 + 0 + 536420 + Wood Agency + + 2019-01-17T15:57:45+01:00 + false + + + + + 2019-01-17T17:25:43+01:00 + 9000 + 536439 + Dogument + + 2022-11-25T09:59:04+01:00 + false + hourly_rate + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + + + 2019-01-29T12:56:25+01:00 + 0 + 537958 + Frauenarztpraxis Bramfeld (Möckel) + + 2020-01-07T14:14:09+01:00 + false + hourly_rates_per_service + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + + + 2019-01-29T16:36:05+01:00 + 0 + 537995 + TankTank + + 2019-01-29T16:36:05+01:00 + false + hourly_rates_per_service + + + 402469 + 8000 + + + 402470 + 8000 + + + 402471 + 8000 + + + 402472 + 8000 + + + 402473 + 8000 + + + 403333 + 8000 + + + 404635 + 8000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 8000 + + + + + 2019-02-15T11:43:11+01:00 + 0 + 540111 + Lohmann Pflegesachverständige + + 2019-02-15T11:43:11+01:00 + false + hourly_rates_per_service + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + + + 2019-02-21T20:20:30+01:00 + 8000 + 540862 + PLP GmbH + + 2022-09-30T16:47:22+02:00 + false + hourly_rate + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + + + 2019-03-05T09:15:18+01:00 + 0 + 541986 + Desert Flower Foundation + + 2019-03-05T09:15:18+01:00 + false + hourly_rates_per_service + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + 430725 + 0 + + + + + 2019-03-08T09:06:38+01:00 + 0 + 542508 + h plus p werbeagentur GmbH + + 2019-03-08T09:06:38+01:00 + false + hourly_rates_per_service + + + 402469 + 6500 + + + 402470 + 6500 + + + 402471 + 6500 + + + 402472 + 6500 + + + 402473 + 6500 + + + 403333 + 6500 + + + 404635 + 6500 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 6500 + + + 430725 + 0 + + + + + 2019-03-19T14:54:23+01:00 + 0 + 543816 + Hamburg Dermatologie + + 2019-03-19T14:54:23+01:00 + false + hourly_rates_per_service + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + 430725 + 0 + + + + + 2019-06-06T14:20:52+02:00 + 0 + 551802 + nordwärts - Simone Meseberg + + 2022-06-03T16:01:04+02:00 + false + + + + + 2019-07-17T15:39:12+02:00 + 0 + 557912 + DolceRita + + 2019-07-17T15:39:12+02:00 + false + + + + + 2019-09-16T10:10:20+02:00 + 0 + 563666 + Shaken not Stirred Advertising + + 2019-09-16T10:10:20+02:00 + false + + + + + 2019-09-28T15:38:51+02:00 + 0 + 566133 + Shaken not Stirred + + 2019-09-28T15:38:51+02:00 + false + + + + + 2019-11-15T12:57:02+01:00 + 0 + 576885 + Angelika Ballosch + + 2019-11-15T12:57:02+01:00 + false + hourly_rates_per_service + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + 430725 + 0 + + + + + 2019-12-09T17:51:34+01:00 + 8000 + 581212 + MarxKrontal Partner + + 2022-04-07T14:18:47+02:00 + false + hourly_rate + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + 430725 + 0 + + + + + 2020-01-07T14:14:53+01:00 + 0 + 584073 + Frauenarztpraxis Sasel (Pelzl) + + 2020-01-07T14:15:40+01:00 + false + hourly_rate + + + + 2020-02-12T16:17:18+01:00 + 0 + 589306 + KPS Concertbuero + + 2020-02-12T16:17:18+01:00 + false + + + + + 2020-02-20T12:29:03+01:00 + 8000 + 590244 + Cabernet & Co. + + 2020-02-20T12:31:19+01:00 + false + hourly_rate + + + + 2020-03-22T23:54:45+01:00 + 8000 + 593164 + crazyartists + + 2020-03-22T23:56:02+01:00 + false + hourly_rate + + + + 2020-05-29T16:55:27+02:00 + 8000 + 600144 + Irena Celecki + + 2021-05-10T15:57:54+02:00 + false + hourly_rate + + + 402469 + 8000 + + + 402470 + 8000 + + + 402471 + 8000 + + + 402472 + 8000 + + + 402473 + 8000 + + + 403333 + 8000 + + + 404635 + 8000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 8000 + + + 430725 + 0 + + + + + 2020-06-18T10:57:24+02:00 + 0 + 601836 + Laserzentrum Hamburg + + 2020-06-18T10:57:24+02:00 + false + + + + + 2020-06-26T17:05:07+02:00 + 0 + 602745 + DUBBEL SPÄTH GmbH & Co. KG + + 2020-06-26T17:05:07+02:00 + false + + + + + 2020-09-04T18:44:53+02:00 + 0 + 610604 + HORST Baumarkt + + 2020-09-04T18:44:53+02:00 + false + + + + + 2020-09-24T09:52:20+02:00 + 0 + 612931 + Fabian Rädecke + + 2020-09-24T09:52:20+02:00 + false + + + + + 2020-10-07T19:02:13+02:00 + 0 + 614387 + Lichtblick + + 2020-10-07T19:02:13+02:00 + false + + + + + 2020-10-08T09:20:08+02:00 + 8000 + 614408 + webschuppen + + 2021-12-07T15:48:19+01:00 + false + hourly_rate + + + + 2020-10-30T09:44:45+01:00 + 7500 + 616357 + E-Formel + + 2021-02-10T09:59:31+01:00 + false + hourly_rate + + + + 2021-01-18T14:25:12+01:00 + 8000 + 623582 + Champagner Galerie + + 2021-01-18T14:25:12+01:00 + false + hourly_rates_per_service + + + 402469 + 8000 + + + 402470 + 8000 + + + 402471 + 8000 + + + 402472 + 8000 + + + 402473 + 8000 + + + 403333 + 8000 + + + 404635 + 8000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 8000 + + + 430725 + 0 + + + + + 2021-03-09T11:17:07+01:00 + 7500 + 628868 + RealFM + + 2021-03-09T11:17:07+01:00 + false + hourly_rate + + + + 2021-05-07T11:12:38+02:00 + 8000 + 634746 + fatchd + + 2021-05-07T11:12:38+02:00 + false + hourly_rate + + + + 2021-05-19T10:54:05+02:00 + 8000 + 635656 + Kantar Media GmbH + + 2021-05-19T10:54:05+02:00 + false + hourly_rate + + + + 2021-05-28T17:17:50+02:00 + 8000 + 636434 + Elbkapitäne + + 2021-05-28T17:17:50+02:00 + false + hourly_rate + + + + 2021-06-29T11:00:29+02:00 + 8500 + 639151 + webförsterei + + 2021-06-29T11:00:29+02:00 + false + hourly_rate + + + + 2021-08-09T12:33:23+02:00 + 8500 + 642389 + Mukig.net - Mund-Kiefer-Gesichtschirurgie + + 2021-08-09T12:33:23+02:00 + false + hourly_rate + + + + 2021-08-24T11:08:48+02:00 + 7700 + 643501 + CodeControl + + 2021-09-02T13:08:10+02:00 + false + hourly_rate + + + + 2021-10-28T15:15:04+02:00 + 8000 + 648589 + FRG Hansa + + 2021-10-28T15:15:04+02:00 + false + hourly_rate + + + + 2021-11-11T16:52:57+01:00 + 8000 + 649777 + André Firmenich + + 2021-11-11T16:52:57+01:00 + false + hourly_rate + + + + 2021-12-01T17:12:13+01:00 + 8000 + 651610 + Diäko GmbH + 112,50 EUR abrechnen + 2021-12-01T17:12:24+01:00 + false + hourly_rate + + + + 2021-12-09T09:44:58+01:00 + 9000 + 652191 + Bauer Advance + + 2021-12-09T09:44:58+01:00 + false + hourly_rate + + + + 2022-01-10T10:22:58+01:00 + 9000 + 654013 + Fuchsbau Timmendorf + + 2022-01-10T10:22:58+01:00 + false + hourly_rate + + + + 2022-01-14T14:08:07+01:00 + 9000 + 654633 + PR-Affairs + + 2022-01-14T14:08:07+01:00 + false + hourly_rate + + + + 2022-02-08T12:20:51+01:00 + 9000 + 657073 + DHV Prüfungsverband + + 2022-02-08T12:20:51+01:00 + false + hourly_rate + + + + 2022-02-17T09:52:27+01:00 + 9000 + 658161 + AKN + + 2022-02-17T09:52:27+01:00 + false + hourly_rate + + + + 2022-02-25T18:02:20+01:00 + 9000 + 659032 + signalkontor° + + 2022-02-25T18:02:20+01:00 + false + hourly_rate + + + + 2022-03-16T14:53:53+01:00 + 8000 + 660872 + Nadine Patrizia Scheel + + 2022-03-16T14:53:53+01:00 + false + hourly_rate + + + + 2022-04-11T12:45:20+02:00 + 5000 + 663077 + Never play alone + + 2022-04-11T12:45:20+02:00 + false + hourly_rate + + + + 2022-04-22T09:46:55+02:00 + 9000 + 663879 + Praxis Laage + + 2022-04-22T09:46:55+02:00 + false + hourly_rate + + + + 2022-05-12T13:26:08+02:00 + 8000 + 668857 + tBuddy + + 2023-12-05T10:21:23+01:00 + false + hourly_rate + + + + 2022-05-12T15:34:19+02:00 + 9000 + 668873 + Rumpel Süsel + + 2022-05-12T15:34:19+02:00 + false + hourly_rate + + + + 2022-06-01T16:59:02+02:00 + 9000 + 670291 + Tatjana Meyer + + 2022-06-01T16:59:02+02:00 + false + hourly_rate + + + + 2022-06-01T17:02:07+02:00 + 9000 + 670292 + Wash 'n Roll + + 2022-06-01T17:02:07+02:00 + false + hourly_rate + + + + 2022-06-08T15:03:49+02:00 + 9000 + 670793 + BSB Rohrreinigung + + 2022-06-08T15:03:49+02:00 + false + hourly_rate + + + + 2022-08-16T11:33:00+02:00 + 9000 + 675486 + Stop the water while using me + + 2022-08-16T11:33:00+02:00 + false + hourly_rate + + + + 2022-11-07T10:08:02+01:00 + 9000 + 681218 + IB Borowski + + 2022-11-07T10:08:02+01:00 + false + hourly_rate + + + + 2022-11-07T11:50:31+01:00 + 9000 + 681243 + Suzlon Energy Ltd. + + 2022-11-07T11:50:31+01:00 + false + hourly_rate + + + + 2022-11-16T13:13:07+01:00 + 9000 + 682039 + Britta Edl + + 2022-11-16T13:13:07+01:00 + false + hourly_rate + + + + 2022-11-18T15:56:06+01:00 + 9000 + 682261 + Salt & Rocks + + 2022-11-18T15:56:06+01:00 + false + hourly_rate + + + + 2023-02-21T18:02:29+01:00 + 9000 + 691756 + Sara Aduse Foundation + + 2023-02-21T18:02:29+01:00 + false + hourly_rate + + + + 2023-02-27T17:36:37+01:00 + 0 + 693457 + Polardot Studios + + 2023-02-27T17:36:37+01:00 + false + + + + + 2023-05-25T16:25:26+02:00 + 9000 + 702560 + FeWo Ruhepol + + 2023-05-25T16:25:26+02:00 + false + hourly_rate + + + + 2023-09-07T21:47:27+02:00 + 9000 + 711030 + Lenk Seafood + + 2023-09-07T21:47:27+02:00 + false + hourly_rate + + + + 2023-09-19T12:05:25+02:00 + 9000 + 711675 + Matthias Scharf Fotografie + + 2023-09-19T12:05:25+02:00 + false + hourly_rate + + + + 2024-02-09T11:09:49+01:00 + 9000 + 723675 + Imaq Pilot + + 2024-02-09T11:09:49+01:00 + false + hourly_rate + + + + 2024-04-24T18:00:32+02:00 + 9000 + 730679 + Pönninghaus + + 2024-04-24T18:00:32+02:00 + false + hourly_rate + + + + 2024-08-28T09:52:22+02:00 + 9000 + 738052 + Bremer Ortungstechnik Aßmuth + + 2024-08-28T09:52:22+02:00 + false + hourly_rate + + + + 2024-09-02T10:59:21+02:00 + 10000 + 738291 + Neurologie Neuer Wall + + 2024-09-02T10:59:21+02:00 + false + hourly_rate + + + + 2024-09-05T11:37:42+02:00 + 9000 + 738577 + David Sattler + + 2024-09-05T11:37:42+02:00 + false + hourly_rate + + + + 2024-10-21T15:18:42+02:00 + 10000 + 741322 + LaPure + + 2025-05-14T12:33:22+02:00 + false + hourly_rate + + + + 2024-10-23T10:31:19+02:00 + 8000 + 741450 + Annika Teerling + + 2024-10-23T10:31:19+02:00 + false + hourly_rate + + + + 2024-11-22T12:03:49+01:00 + 9000 + 743312 + Sebastian Salmhofer + Bit Pulse Digital PTE Limited + 2024-11-22T12:03:49+01:00 + false + hourly_rate + + + + 2024-12-11T10:28:12+01:00 + 9000 + 744846 + NorthData + + 2024-12-11T10:28:12+01:00 + false + hourly_rate + + + + 2024-12-17T10:45:18+01:00 + 11250 + 745164 + Securvita + + 2024-12-17T10:45:18+01:00 + false + hourly_rate + + + + 2025-05-05T15:56:35+02:00 + 10000 + 837030 + Hauke Fuhrmann + + 2025-05-05T15:56:35+02:00 + false + hourly_rate + + + + 2025-05-06T13:05:27+02:00 + 10000 + 837093 + Massgebend GmbH + + 2025-05-06T13:05:27+02:00 + false + hourly_rate + + + + 2025-06-30T12:05:46+02:00 + 9000 + 848981 + Hospizverein Sylt + + 2025-12-17T13:25:30+01:00 + false + hourly_rate + + + + 2025-08-11T18:04:34+02:00 + 10000 + 853397 + Medienkapitän + + 2025-08-11T18:04:34+02:00 + false + hourly_rate + + + + 2025-10-10T14:11:09+02:00 + 10000 + 863730 + Car GmbH + + 2025-10-10T14:11:09+02:00 + false + hourly_rate + + + + 2025-12-03T13:00:09+01:00 + 10000 + 873570 + Stach Installations + + 2025-12-03T13:00:09+01:00 + false + hourly_rate + + + + 2026-04-15T11:18:00+02:00 + 0 + 902974 + Zoomulate + + 2026-04-15T11:18:00+02:00 + false + + + + + 2026-04-29T10:41:10+02:00 + 0 + 905212 + Altoelankauf.de + + 2026-04-29T10:41:10+02:00 + false + + + + + 2026-05-20T10:45:28+02:00 + 0 + 908443 + Flashpoint Germany + + 2026-05-20T10:45:28+02:00 + false + + + + + 2026-06-12T11:00:33+02:00 + 10000 + 912127 + Dressler Elektroanlagenbau + + 2026-06-12T11:00:33+02:00 + false + hourly_rate + + + + + + 0 + minutes + 2018-03-19T15:05:51+01:00 + 487895 + 8000 + 2411023 + too1s + + 2021-05-12T16:15:36+02:00 + true + hourly_rate + + + + 0 + minutes + 2018-11-27T17:14:59+01:00 + 531184 + 0 + 2645017 + Intern + + 2018-11-27T17:15:10+01:00 + false + + + + + 6840 + minutes + 2019-01-02T13:04:10+01:00 + 534086 + 0 + 2666337 + Erstellung Website + + 2021-07-15T10:03:23+02:00 + true + + + + + 0 + minutes + 2019-01-02T13:05:36+01:00 + 534087 + 0 + 2666339 + Goalunited + + 2021-07-15T10:03:42+02:00 + true + + + + + 0 + minutes + 2019-01-02T13:13:24+01:00 + 487895 + 0 + 2666347 + Erfassungsblatt + + 2022-04-05T17:52:47+02:00 + true + + + + + 1440 + minutes + 2019-01-07T15:33:15+01:00 + 534671 + 0 + 2670344 + Website Update / Refresh + + 2019-01-07T15:33:15+01:00 + false + + + + + 0 + minutes + 2019-01-07T17:02:17+01:00 + 534716 + 0 + 2670539 + Relaunch 1.0 + + 2021-07-15T10:04:40+02:00 + true + + + + + 0 + minutes + 2019-01-10T10:04:31+01:00 + 535258 + 0 + 2674318 + Leseludi + + 2019-01-10T10:04:31+01:00 + false + + + + + 0 + minutes + 2019-01-10T10:04:37+01:00 + 535258 + 0 + 2674319 + Lernrudi + + 2019-01-10T10:04:37+01:00 + false + + + + + 0 + minutes + 2019-01-10T10:05:24+01:00 + 531184 + 0 + 2674322 + Krankheit + + 2019-01-10T10:05:24+01:00 + false + hourly_rate + + + + 0 + minutes + 2019-01-10T12:24:35+01:00 + 535326 + 0 + 2674622 + Maintenance + + 2019-01-10T12:24:35+01:00 + false + + + + + 0 + minutes + 2019-01-10T12:26:28+01:00 + 535327 + 0 + 2674625 + Fuchsbau Timmendorf + + 2022-04-05T17:52:28+02:00 + true + + + + + 0 + minutes + 2019-01-10T12:26:38+01:00 + 535327 + 0 + 2674626 + AKN + + 2022-04-05T17:52:00+02:00 + true + + + + + 0 + minutes + 2019-01-16T15:12:21+01:00 + 536229 + 0 + 2680371 + Maintenance + + 2019-01-16T15:12:21+01:00 + false + + + + + 0 + minutes + 2019-01-17T15:57:57+01:00 + 536420 + 0 + 2681725 + Loeschner LLP + + 2019-01-17T15:57:57+01:00 + false + + + + + 0 + minutes + 2019-01-17T15:58:43+01:00 + 536420 + 0 + 2681729 + Popp Feinkost + + 2019-01-17T15:58:43+01:00 + false + + + + + 0 + minutes + 2019-01-17T17:25:54+01:00 + 536439 + 0 + 2681882 + Maintenance + + 2019-01-17T17:25:54+01:00 + false + + + + + 0 + minutes + 2019-01-18T15:00:17+01:00 + 515306 + 0 + 2682677 + Maintenance + + 2019-01-18T15:00:17+01:00 + false + + + + + 0 + minutes + 2019-01-23T17:08:32+01:00 + 534716 + 0 + 2686638 + hauni 2.0 + + 2021-07-15T10:03:46+02:00 + true + + + + + 0 + minutes + 2019-01-29T12:56:37+01:00 + 537958 + 0 + 2690887 + Maintenance + + 2019-01-29T12:56:37+01:00 + false + + + + + 0 + minutes + 2019-01-29T16:36:19+01:00 + 537995 + 0 + 2691216 + Huk24 + + 2021-07-15T10:03:49+02:00 + true + + + + + 0 + minutes + 2019-02-01T20:21:41+01:00 + 535327 + 0 + 2694880 + savvynet + + 2022-04-05T17:52:33+02:00 + true + + + + + 0 + minutes + 2019-02-04T19:20:00+01:00 + 535327 + 0 + 2696468 + Praxis Laage + + 2022-04-05T17:52:32+02:00 + true + + + + + 0 + minutes + 2019-02-15T11:43:26+01:00 + 540111 + 0 + 2706042 + Maintenance + + 2019-02-15T11:43:26+01:00 + false + + + + + 0 + minutes + 2019-02-18T09:06:49+01:00 + 535327 + 0 + 2706914 + Vitiligo Verein + + 2022-04-05T17:52:34+02:00 + true + + + + + 0 + minutes + 2019-02-21T20:18:35+01:00 + 535327 + 0 + 2711494 + VWAEK Hamburg + + 2022-04-05T17:52:35+02:00 + true + + + + + 0 + minutes + 2019-02-21T20:20:47+01:00 + 540862 + 0 + 2711497 + Maintenance + + 2019-02-21T20:20:47+01:00 + false + + + + + 0 + minutes + 2019-03-05T09:17:41+01:00 + 541986 + 0 + 2720195 + Maintenance + + 2019-03-05T09:17:41+01:00 + false + + + + + 0 + minutes + 2019-03-07T10:05:29+01:00 + 487895 + 8000 + 2722465 + Website + + 2021-02-26T10:11:44+01:00 + false + hourly_rate + + + + 0 + minutes + 2019-03-08T09:06:50+01:00 + 542508 + 0 + 2723407 + SAE Dental + + 2021-07-15T10:04:44+02:00 + true + + + + + 0 + minutes + 2019-03-15T18:09:24+01:00 + 534086 + 8000 + 2729350 + Erweiterungsstufe 01 + + 2022-04-05T17:51:31+02:00 + true + hourly_rate + + + + 0 + minutes + 2019-03-19T14:53:39+01:00 + 535327 + 0 + 2731957 + Quarree + + 2021-07-15T10:04:35+02:00 + true + + + + + 0 + minutes + 2019-03-19T14:54:37+01:00 + 543816 + 0 + 2731958 + Maintenance + + 2019-03-19T14:54:37+01:00 + false + + + + + 0 + minutes + 2019-04-01T22:12:48+02:00 + 534086 + 8000 + 2742845 + followfish - Optimierungen + + 2022-04-05T17:51:40+02:00 + true + hourly_rate + + + + 0 + minutes + 2019-05-09T17:44:33+02:00 + 536420 + 0 + 2769545 + IHK Kiel + + 2021-07-15T10:03:52+02:00 + true + hourly_rates_per_service + + + 402469 + 7000 + + + 402470 + 7000 + + + 402471 + 7000 + + + 402472 + 7000 + + + 402473 + 7000 + + + 403333 + 7000 + + + 404635 + 7000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 7000 + + + 430725 + 0 + + + + + 0 + minutes + 2019-06-03T20:36:56+02:00 + 536420 + 0 + 2785029 + Camping Booknis + + 2019-06-03T20:36:56+02:00 + false + + + + + 0 + minutes + 2019-06-06T14:21:14+02:00 + 551802 + 0 + 2787792 + Maintenance + + 2019-06-06T14:21:14+02:00 + false + + + + + 0 + minutes + 2019-07-09T14:11:55+02:00 + 535327 + 0 + 2809130 + DHV Prüfungsverband + + 2022-04-05T17:52:19+02:00 + true + + + + + 2400 + minutes + 2019-07-17T15:39:35+02:00 + 557912 + 0 + 2814457 + Erstellung Website + + 2019-07-17T15:39:35+02:00 + false + + + + + 0 + minutes + 2019-07-17T15:42:45+02:00 + 535327 + 0 + 2814461 + Ehlers & Partner + + 2022-04-05T17:52:22+02:00 + true + + + + + 0 + minutes + 2019-08-06T16:56:38+02:00 + 535327 + 0 + 2826756 + BSB Bremen + + 2022-04-05T17:52:12+02:00 + true + + + + + 0 + minutes + 2019-08-20T17:12:06+02:00 + 535327 + 0 + 2835339 + FC St. Pauli Handball + + 2019-08-20T17:12:06+02:00 + false + + + + + 2400 + minutes + 2019-09-16T10:10:42+02:00 + 563666 + 0 + 2852343 + Landhaus an de Dün + + 2021-07-15T10:03:57+02:00 + true + + + + + 0 + minutes + 2019-09-17T12:42:41+02:00 + 540862 + 6250 + 2853600 + PLP internes Tool + + 2022-09-30T16:47:37+02:00 + false + + + + + 0 + minutes + 2019-09-18T12:40:18+02:00 + 535327 + 0 + 2854592 + Diverse + + 2022-04-05T17:52:20+02:00 + true + + + + + 0 + minutes + 2019-09-28T15:39:03+02:00 + 566133 + 0 + 2865415 + Caplantic + + 2021-07-15T10:03:09+02:00 + true + + + + + 0 + minutes + 2019-10-09T14:15:15+02:00 + 534086 + 0 + 2875415 + Maintenance + + 2019-10-09T14:15:15+02:00 + false + + + + + 0 + minutes + 2019-10-21T17:52:19+02:00 + 535327 + 0 + 2887446 + Hautsache Magazin + + 2022-04-05T17:52:29+02:00 + true + + + + + 0 + minutes + 2019-10-28T20:25:01+01:00 + 535327 + 0 + 2896389 + FeWo Ruhepol + + 2022-04-05T17:52:26+02:00 + true + + + + + 0 + minutes + 2019-10-31T10:15:02+01:00 + 563666 + 0 + 2899018 + Dünen Stuuv + + 2021-07-15T10:03:18+02:00 + true + + + + + 0 + minutes + 2019-11-14T14:10:00+01:00 + 535327 + 0 + 2909722 + DC Casitas + + 2022-04-05T17:52:16+02:00 + true + + + + + 0 + minutes + 2019-11-15T12:57:13+01:00 + 576885 + 0 + 2910543 + Maintenance + + 2019-11-15T12:57:13+01:00 + false + + + + + 0 + minutes + 2019-12-09T17:51:54+01:00 + 581212 + 0 + 2938527 + Maintenance + + 2019-12-09T17:51:54+01:00 + false + + + + + 0 + minutes + 2020-01-07T14:16:05+01:00 + 584073 + 0 + 2958076 + Maintenance + + 2021-07-15T10:04:14+02:00 + true + + + + + 0 + minutes + 2020-01-15T11:15:50+01:00 + 535327 + 0 + 2965511 + Gavin Schilling + + 2021-07-15T10:03:44+02:00 + true + + + + + 0 + minutes + 2020-02-12T16:17:32+01:00 + 589306 + 0 + 2992396 + Maintenance + + 2020-02-12T16:17:32+01:00 + false + + + + + 0 + minutes + 2020-02-20T12:31:32+01:00 + 590244 + 0 + 2998252 + Maintenance + + 2020-02-20T12:31:32+01:00 + false + + + + + 0 + minutes + 2020-03-22T23:54:57+01:00 + 593164 + 0 + 3018720 + Maintenance + + 2020-03-22T23:54:57+01:00 + false + + + + + 2280 + minutes + 2020-04-28T15:59:09+02:00 + 515306 + 0 + 3044133 + ICOA + + 2021-07-15T10:03:51+02:00 + true + + + + + 4800 + minutes + 2020-04-29T16:21:27+02:00 + 563666 + 0 + 3045256 + Lamy US + + 2022-04-05T17:53:36+02:00 + true + + + + + 0 + minutes + 2020-05-29T16:55:46+02:00 + 600144 + 0 + 3062261 + Karina Ewertz - Innenarchitektin + + 2021-04-29T16:43:23+02:00 + false + + + + + 2040 + minutes + 2020-06-14T23:24:40+02:00 + 534086 + 8000 + 3069495 + fish & more + + 2020-07-31T17:04:38+02:00 + false + hourly_rate + + + + 0 + minutes + 2020-06-16T14:36:39+02:00 + 535327 + 0 + 3071059 + ITU + + 2022-04-05T17:52:30+02:00 + true + + + + + 3720 + minutes + 2020-06-18T09:45:04+02:00 + 563666 + 0 + 3072362 + Lamy DE + + 2022-04-05T17:53:35+02:00 + true + + + + + 0 + minutes + 2020-06-18T10:57:36+02:00 + 601836 + 0 + 3072458 + Maintenance + + 2020-06-18T10:57:36+02:00 + false + + + + + 0 + minutes + 2020-06-24T09:35:34+02:00 + 537995 + 0 + 3075541 + Cover my ass + + 2020-06-24T09:35:34+02:00 + false + + + + + 2640 + minutes + 2020-06-26T17:05:42+02:00 + 602745 + 0 + 3077516 + Schule auf der Aue Münster + + 2021-07-15T10:04:48+02:00 + true + + + + + 1200 + minutes + 2020-07-02T15:21:59+02:00 + 563666 + 0 + 3091383 + Smart Forward + + 2021-07-15T10:04:55+02:00 + true + + + + + 0 + minutes + 2020-07-09T13:36:18+02:00 + 515306 + 0 + 3095483 + Matsen Stiftung + + 2020-07-09T13:36:18+02:00 + false + + + + + 0 + minutes + 2020-09-04T18:45:23+02:00 + 610604 + 0 + 3127165 + Relaunch + + 2022-04-05T17:54:59+02:00 + true + + + + + 0 + minutes + 2020-09-24T09:52:33+02:00 + 612931 + 0 + 3141725 + Maintenance + + 2020-09-24T09:52:33+02:00 + false + + + + + 0 + minutes + 2020-09-25T17:51:52+02:00 + 566133 + 0 + 3142916 + Diverse + + 2020-09-25T17:51:52+02:00 + false + + + + + 0 + minutes + 2020-09-29T16:37:42+02:00 + 515306 + 0 + 3144867 + Fesago + + 2020-09-29T16:37:42+02:00 + false + + + + + 0 + minutes + 2020-10-07T19:03:00+02:00 + 614387 + 0 + 3150700 + Expertum + + 2021-07-15T10:03:27+02:00 + true + + + + + 0 + minutes + 2020-10-07T19:03:09+02:00 + 614387 + 0 + 3150702 + TimePartner + + 2021-07-15T10:04:50+02:00 + true + + + + + 0 + minutes + 2020-10-08T09:20:31+02:00 + 614408 + 0 + 3150821 + Lilly Pharma + + 2020-10-08T09:21:04+02:00 + false + + + + + 4200 + minutes + 2020-10-13T11:50:00+02:00 + 602745 + 6500 + 3153702 + Medintim + + 2021-07-15T10:04:26+02:00 + true + hourly_rate + + + + 0 + minutes + 2020-10-20T19:49:11+02:00 + 614408 + 0 + 3158769 + Faktor A + + 2021-07-15T10:03:30+02:00 + true + + + + + 0 + minutes + 2020-10-23T14:25:48+02:00 + 614408 + 0 + 3160931 + Diverse + + 2020-10-23T14:25:48+02:00 + false + + + + + 0 + minutes + 2020-10-30T09:45:17+01:00 + 616357 + 3250 + 3164955 + z-E-Formel (Launch) + + 2022-04-05T17:54:45+02:00 + true + hourly_rate + + + + 0 + minutes + 2020-11-16T11:43:53+01:00 + 610604 + 0 + 3177395 + Maintenance + + 2020-11-16T11:43:53+01:00 + false + + + + + 0 + minutes + 2021-01-12T15:01:32+01:00 + 535327 + 0 + 3209601 + Bauer Advance + + 2022-04-05T17:52:06+02:00 + true + + + + + 0 + minutes + 2021-01-18T14:25:24+01:00 + 623582 + 0 + 3213871 + Maintenance + + 2021-01-18T14:25:24+01:00 + false + + + + + 0 + minutes + 2021-02-10T10:01:07+01:00 + 616357 + 7000 + 3230616 + Maintenance (rabattiert) + + 2021-02-22T17:51:15+01:00 + false + hourly_rate + + + + 0 + minutes + 2021-02-19T10:52:38+01:00 + 614408 + 8000 + 3237023 + Fester Tag (WAJE) + + 2021-07-15T10:03:36+02:00 + true + hourly_rate + + + + 2880 + minutes + 2021-02-26T09:57:57+01:00 + 487895 + 8000 + 3241460 + too1s - Anlagenkataster funktionsfähig machen + + 2022-04-05T17:52:58+02:00 + true + hourly_rate + + + 402469 + 8000 + + + 402470 + 8000 + + + 402471 + 8000 + + + 402472 + 8000 + + + 402473 + 8000 + + + 403333 + 8000 + + + 404635 + 8000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 8000 + + + 430725 + 0 + + + + + 4800 + minutes + 2021-03-09T11:17:26+01:00 + 628868 + 0 + 3248636 + BIM Tool + + 2021-04-07T11:28:35+02:00 + false + + + + + 0 + minutes + 2021-03-30T23:22:09+02:00 + 614408 + 8000 + 3262287 + Bürgerumfrage Buxtehude + + 2021-03-30T23:22:09+02:00 + false + hourly_rate + + + + 0 + minutes + 2021-04-20T10:32:55+02:00 + 535258 + 0 + 3273177 + Schreibsusi + + 2021-09-28T12:59:06+02:00 + false + + + + + 0 + minutes + 2021-05-07T11:13:15+02:00 + 634746 + 0 + 3288686 + Video-Speed-Dating Landingpage + + 2021-05-07T11:13:15+02:00 + false + + + + + 0 + minutes + 2021-05-12T16:17:52+02:00 + 487895 + 8000 + 3291589 + too1s - Sonstiges + + 2022-04-05T17:52:59+02:00 + true + hourly_rate + + + + 1920 + minutes + 2021-05-12T16:18:04+02:00 + 487895 + 8000 + 3291590 + too1s - Vorbereitung Anlagenkataster + + 2022-04-05T17:53:01+02:00 + true + hourly_rate + + + + 2400 + minutes + 2021-05-12T16:18:21+02:00 + 487895 + 8000 + 3291591 + too1s - Preisblatt und Quellkalkulation + + 2022-04-05T17:52:56+02:00 + true + hourly_rate + + + + 0 + minutes + 2021-05-19T10:54:17+02:00 + 635656 + 0 + 3294705 + Newsletter-Erstellung + + 2022-04-05T17:54:28+02:00 + true + + + + + 0 + minutes + 2021-05-28T17:18:03+02:00 + 636434 + 0 + 3300284 + New Work + + 2022-04-05T17:54:23+02:00 + true + + + + + 0 + minutes + 2021-06-01T16:08:00+02:00 + 557912 + 0 + 3302255 + Maintenance + + 2021-06-01T16:08:00+02:00 + false + + + + + 0 + minutes + 2021-06-18T10:26:30+02:00 + 487895 + 8000 + 3313296 + tools - Gesamtimport erweitern + + 2022-04-05T17:53:02+02:00 + true + hourly_rates_per_service + + + 402469 + 8000 + + + 402470 + 8000 + + + 402471 + 8000 + + + 402472 + 8000 + + + 402473 + 8000 + + + 403333 + 8000 + + + 404635 + 8000 + + + 425205 + 0 + + + 425206 + 0 + + + 425720 + 8000 + + + 430725 + 0 + + + + + 0 + minutes + 2021-06-29T11:02:34+02:00 + 639151 + 0 + 3318954 + 24hfahrzeugabgabe + + 2021-06-29T11:47:50+02:00 + false + + + + + 0 + minutes + 2021-08-02T09:04:44+02:00 + 535327 + 0 + 3338275 + Adore & Enjoy + + 2022-04-05T17:51:51+02:00 + true + + + + + 0 + minutes + 2021-08-09T13:50:42+02:00 + 642389 + 0 + 3342424 + Maintenance + + 2021-08-09T13:50:42+02:00 + false + + + + + 0 + minutes + 2021-08-24T11:09:42+02:00 + 643501 + 0 + 3350074 + Kollex PHP Entwicklung + + 2021-08-24T11:09:42+02:00 + false + + + + + 0 + minutes + 2021-08-26T11:50:05+02:00 + 536420 + 8000 + 3351575 + Zmile + + 2021-08-26T11:50:05+02:00 + false + hourly_rate + + + + 0 + minutes + 2021-09-17T15:20:25+02:00 + 536420 + 8000 + 3364339 + Puraglobe + + 2021-09-17T15:20:45+02:00 + false + hourly_rate + + + + 0 + minutes + 2021-10-28T15:15:21+02:00 + 648589 + 0 + 3389443 + Konzeption Webpräsenzen + + 2021-10-28T15:15:21+02:00 + false + + + + + 0 + minutes + 2021-11-11T16:53:41+01:00 + 660872 + 8000 + 3398850 + Maintenance + + 2022-03-16T14:54:16+01:00 + false + + + + + 10080 + minutes + 2021-12-01T17:13:18+01:00 + 651610 + 0 + 3410990 + Shopware 6 Implementierung + + 2021-12-01T17:13:18+01:00 + false + + + + + 0 + minutes + 2021-12-09T09:45:12+01:00 + 652191 + 0 + 3415777 + Maintenance + + 2021-12-09T09:45:12+01:00 + false + + + + + 0 + minutes + 2021-12-25T10:11:51+01:00 + 537995 + 0 + 3424130 + Diverse + + 2021-12-25T10:11:51+01:00 + false + + + + + 0 + minutes + 2022-01-10T10:23:10+01:00 + 654013 + 0 + 3430490 + Maintenance + + 2022-01-10T10:23:10+01:00 + false + + + + + 0 + minutes + 2022-01-10T12:32:34+01:00 + 542508 + 0 + 3430763 + SAE Dental + + 2022-01-10T12:32:34+01:00 + false + + + + + 0 + minutes + 2022-01-14T14:08:17+01:00 + 654633 + 0 + 3435108 + Maintenance + + 2022-01-14T14:08:17+01:00 + false + + + + + 1200 + minutes + 2022-01-17T12:38:00+01:00 + 487895 + 8000 + 3436139 + Usermenu + + 2022-04-05T17:53:07+02:00 + true + hourly_rate + + + + 960 + minutes + 2022-01-17T12:38:25+01:00 + 487895 + 8000 + 3436141 + Umbau Menu-Struktur + + 2022-04-05T17:53:04+02:00 + true + hourly_rate + + + + 5760 + minutes + 2022-01-17T12:38:50+01:00 + 487895 + 8000 + 3436143 + Delta Norm Tracker + + 2022-02-07T21:05:18+01:00 + false + hourly_rate + + + + 0 + minutes + 2022-02-07T18:23:20+01:00 + 616357 + 0 + 3451033 + E-Formel Maintenance ab 2022 + Maintenance ab 2022 (nicht rabattiert) + 2022-02-07T18:23:20+01:00 + false + + + + + 0 + minutes + 2022-02-08T12:21:04+01:00 + 657073 + 0 + 3451551 + DHV Maintenance + + 2022-02-08T12:21:04+01:00 + false + + + + + 0 + minutes + 2022-02-08T12:21:37+01:00 + 657073 + 0 + 3451553 + HMT Maintenance + + 2022-02-08T12:21:37+01:00 + false + + + + + 0 + minutes + 2022-02-16T16:59:35+01:00 + 487895 + 0 + 3460193 + Support / Maintenance + + 2022-02-16T16:59:35+01:00 + false + + + + + 0 + minutes + 2022-02-17T09:52:37+01:00 + 658161 + 0 + 3460509 + Maintenance - 4600000451 + Kontraktnummer 4600000451 + 2022-06-14T12:00:25+02:00 + false + + + + + 0 + minutes + 2022-02-25T18:02:32+01:00 + 659032 + 0 + 3466117 + Microsoft Newsletter + + 2022-02-25T18:02:32+01:00 + false + + + + + 0 + minutes + 2022-03-14T11:39:28+01:00 + 649777 + 0 + 3475490 + Webumed + + 2022-03-14T11:39:28+01:00 + false + + + + + 0 + minutes + 2022-04-11T12:45:32+02:00 + 663077 + 0 + 3492885 + Maintenance + + 2022-04-11T12:45:32+02:00 + false + + + + + 0 + minutes + 2022-04-22T09:47:11+02:00 + 663879 + 0 + 3498043 + Maintenance + + 2022-04-22T09:47:11+02:00 + false + + + + + 0 + minutes + 2022-05-02T10:24:38+02:00 + 541986 + 0 + 3502877 + Website 2023 + + 2023-10-17T11:42:59+02:00 + false + + + + + 0 + minutes + 2022-05-05T16:04:59+02:00 + 649777 + 0 + 3506292 + German Health Tech + + 2022-05-05T16:04:59+02:00 + false + + + + + 0 + minutes + 2022-05-10T10:01:19+02:00 + 648589 + 0 + 3508348 + Erstellung Webseiten + + 2022-05-10T10:01:19+02:00 + false + + + + + 0 + minutes + 2022-05-12T13:26:18+02:00 + 668857 + 0 + 3529775 + Maintenance + + 2022-05-12T13:26:18+02:00 + false + + + + + 0 + minutes + 2022-05-12T15:34:29+02:00 + 668873 + 0 + 3529923 + Maintenance + + 2022-05-12T15:34:29+02:00 + false + + + + + 0 + minutes + 2022-06-01T16:59:13+02:00 + 670291 + 0 + 3540448 + Maintenance + + 2022-06-01T16:59:13+02:00 + false + + + + + 0 + minutes + 2022-06-01T17:02:17+02:00 + 670292 + 0 + 3540455 + Shop Relaunch + + 2022-06-01T17:02:17+02:00 + false + + + + + 0 + minutes + 2022-06-08T15:03:59+02:00 + 670793 + 0 + 3543767 + Maintenance + + 2022-06-08T15:03:59+02:00 + false + + + + + 0 + minutes + 2022-07-11T15:37:41+02:00 + 536420 + 0 + 3561703 + IPDynamics + + 2022-07-11T15:37:41+02:00 + false + + + + + 0 + minutes + 2022-08-02T11:09:09+02:00 + 675486 + 9000 + 3572317 + Shopify Relaunch + + 2022-08-16T11:33:29+02:00 + false + + + + + 0 + minutes + 2022-08-22T10:15:49+02:00 + 652191 + 0 + 3584216 + Umfeldplaner + + 2022-08-22T10:15:49+02:00 + false + + + + + 0 + minutes + 2022-11-07T10:08:16+01:00 + 681218 + 0 + 3627497 + Maintenance + + 2022-11-07T10:08:16+01:00 + false + + + + + 0 + minutes + 2022-11-07T11:50:42+01:00 + 681243 + 0 + 3627656 + Maintenance + + 2022-11-07T11:50:42+01:00 + false + + + + + 0 + minutes + 2022-11-16T13:13:18+01:00 + 682039 + 0 + 3634709 + Maintenance + + 2022-11-16T13:13:18+01:00 + false + + + + + 0 + minutes + 2022-11-18T15:56:21+01:00 + 682261 + 0 + 3636811 + Landhaus an de Dün + + 2022-11-18T15:56:21+01:00 + false + + + + + 0 + minutes + 2022-11-18T15:56:31+01:00 + 682261 + 0 + 3636812 + Dünen Stuuv + + 2022-11-18T15:56:31+01:00 + false + + + + + 0 + minutes + 2023-01-10T11:31:25+01:00 + 682261 + 0 + 3661969 + Das Lornsen + + 2023-01-10T11:31:25+01:00 + false + + + + + 0 + minutes + 2023-01-25T12:56:33+01:00 + 566133 + 8500 + 3679525 + cardentity + + 2023-01-25T12:56:33+01:00 + false + hourly_rate + + + + 0 + minutes + 2023-02-14T17:02:44+01:00 + 600144 + 9000 + 3692697 + Besler & Söhne + + 2023-02-14T17:02:44+01:00 + false + hourly_rate + + + + 0 + minutes + 2023-02-21T18:02:39+01:00 + 691756 + 0 + 3704328 + Erstellung Website + + 2023-02-21T18:02:47+01:00 + false + + + + + 0 + minutes + 2023-02-27T17:36:50+01:00 + 693457 + 0 + 3714684 + Corner House Altafulla + + 2023-02-27T17:36:50+01:00 + false + + + + + 0 + minutes + 2023-03-14T15:25:00+01:00 + 614408 + 0 + 3739809 + carglass + + 2023-03-14T15:25:00+01:00 + false + + + + + 0 + minutes + 2023-04-06T11:27:41+02:00 + 536420 + 0 + 3754111 + IHK + + 2023-04-12T15:42:37+02:00 + false + + + + + 0 + minutes + 2023-04-06T12:29:09+02:00 + 682261 + 0 + 3754186 + Das Windhuk + + 2023-04-06T12:29:09+02:00 + false + + + + + 0 + minutes + 2023-04-06T12:29:17+02:00 + 682261 + 0 + 3754187 + Maritas Logierhaus + + 2023-04-06T12:29:17+02:00 + false + + + + + 0 + minutes + 2023-05-25T16:25:35+02:00 + 702560 + 0 + 3780140 + Maintenance + + 2023-05-25T16:25:35+02:00 + false + + + + + 6600 + minutes + 2023-06-07T17:29:28+02:00 + 628868 + 0 + 3794844 + MPP Tool + + 2023-07-04T17:52:40+02:00 + false + + + + + 0 + minutes + 2023-06-28T15:34:16+02:00 + 691756 + 0 + 3805120 + Maintenance + + 2023-06-28T15:34:16+02:00 + false + + + + + 0 + minutes + 2023-08-21T11:46:51+02:00 + 566133 + 0 + 3834270 + Eucon + + 2023-08-21T11:46:51+02:00 + false + + + + + 0 + minutes + 2023-09-07T21:45:17+02:00 + 515306 + 0 + 3843731 + Intranet-Tool + + 2023-09-07T21:45:17+02:00 + false + + + + + 0 + minutes + 2023-09-07T21:49:29+02:00 + 711030 + 0 + 3843733 + Luna Garnelen + + 2023-09-07T21:49:29+02:00 + false + + + + + 0 + minutes + 2023-09-19T12:05:51+02:00 + 711675 + 0 + 3848972 + Maintenance + + 2023-09-19T12:05:51+02:00 + false + + + + + 0 + minutes + 2023-09-20T13:11:06+02:00 + 614408 + 0 + 3849877 + Spitzner B2B + + 2023-09-20T13:11:06+02:00 + false + + + + + 0 + minutes + 2023-10-31T11:39:38+01:00 + 614408 + 0 + 3877107 + Spitzner Support + + 2023-10-31T11:39:38+01:00 + false + + + + + 0 + minutes + 2023-12-04T11:07:47+01:00 + 535326 + 9000 + 3905504 + Website 2024 + + 2023-12-04T11:07:47+01:00 + false + hourly_rate + + + + 0 + minutes + 2024-02-06T15:31:46+01:00 + 614408 + 0 + 3949946 + Spitzner SAP Schnittstelle + + 2024-02-06T15:31:46+01:00 + false + + + + + 0 + minutes + 2024-02-09T11:10:05+01:00 + 723675 + 0 + 3951946 + App (intern) + + 2024-02-09T11:10:05+01:00 + false + + + + + 0 + minutes + 2024-04-23T11:42:12+02:00 + 536420 + 9000 + 3994020 + #meermachen + + 2024-04-23T11:42:12+02:00 + false + hourly_rate + + + + 0 + minutes + 2024-04-24T18:00:43+02:00 + 730679 + 0 + 3995138 + Shop + + 2024-04-24T18:00:43+02:00 + false + + + + + 0 + minutes + 2024-07-09T12:20:21+02:00 + 536420 + 0 + 4029587 + #meermachen Betreuung + + 2024-07-09T12:20:21+02:00 + false + + + + + 0 + minutes + 2024-07-09T17:39:27+02:00 + 614408 + 0 + 4029864 + Spitzner ReDesign + + 2024-07-09T17:39:27+02:00 + false + + + + + 0 + minutes + 2024-08-28T09:52:38+02:00 + 738052 + 0 + 4049805 + Website 2024 + + 2024-08-28T09:52:38+02:00 + false + + + + + 0 + minutes + 2024-09-02T10:59:34+02:00 + 738291 + 0 + 4051725 + Website Relaunch + + 2024-09-02T10:59:34+02:00 + false + + + + + 0 + minutes + 2024-09-05T11:37:54+02:00 + 738577 + 0 + 4054610 + Website + + 2024-09-05T11:37:54+02:00 + false + + + + + 0 + minutes + 2024-10-21T15:18:54+02:00 + 741322 + 0 + 4074788 + Website 2024 + + 2024-10-21T15:18:54+02:00 + false + + + + + 0 + minutes + 2024-10-23T10:31:28+02:00 + 741450 + 0 + 4075849 + Website + + 2024-10-23T10:31:28+02:00 + false + + + + + 0 + minutes + 2024-11-20T10:34:17+01:00 + 515306 + 0 + 4088839 + Intranet-Tool Einarbeitung / Nicht verrechenbar + + 2024-11-20T10:34:17+01:00 + false + hourly_rate + + + + 0 + minutes + 2024-11-22T12:04:00+01:00 + 743312 + 0 + 4090119 + SBC Solver + + 2024-11-22T12:04:00+01:00 + false + + + + + 0 + minutes + 2024-12-11T10:28:23+01:00 + 744846 + 0 + 4099135 + Maintenance + + 2024-12-11T10:28:23+01:00 + false + + + + + 0 + minutes + 2024-12-17T10:45:53+01:00 + 745164 + 0 + 4101605 + GmbH + + 2024-12-17T10:45:53+01:00 + false + + + + + 0 + minutes + 2024-12-17T10:46:00+01:00 + 745164 + 0 + 4101606 + Website + + 2024-12-17T10:46:00+01:00 + false + + + + + 0 + minutes + 2025-01-24T10:01:13+01:00 + 711030 + 0 + 4122888 + Lenk Frozen + + 2025-01-24T10:01:13+01:00 + false + + + + + 0 + minutes + 2025-01-24T10:01:26+01:00 + 711030 + 0 + 4122889 + Lenk-Seafood + + 2025-01-24T10:01:26+01:00 + false + + + + + 0 + minutes + 2025-05-05T15:56:46+02:00 + 837030 + 0 + 4172624 + Sylt Häuser + + 2025-05-05T15:56:46+02:00 + false + + + + + 0 + minutes + 2025-05-06T13:05:51+02:00 + 837093 + 0 + 4173176 + Einladungs- und Teilnehmermanagement Tool + + 2025-05-06T13:05:51+02:00 + false + + + + + 0 + minutes + 2025-05-07T16:46:11+02:00 + 654013 + 0 + 4174087 + Website 2025 + + 2025-05-07T16:46:11+02:00 + false + + + + + 0 + minutes + 2025-05-14T12:32:48+02:00 + 837093 + 0 + 4176819 + Sonstiges + + 2025-05-14T12:32:48+02:00 + false + + + + + 0 + minutes + 2025-06-30T12:05:56+02:00 + 848981 + 0 + 4224541 + Maintenance + + 2025-06-30T12:05:56+02:00 + false + + + + + 2700 + minutes + 2025-08-11T18:04:44+02:00 + 853397 + 0 + 4256153 + Lebenshilfe Pinneberg + + 2025-08-11T18:04:44+02:00 + false + + + + + 0 + minutes + 2025-10-10T14:11:20+02:00 + 863730 + 0 + 4332363 + Maintenance + + 2025-10-10T14:11:20+02:00 + false + + + + + 0 + minutes + 2025-12-03T13:00:22+01:00 + 873570 + 0 + 4406001 + Neue Website + + 2025-12-03T13:00:22+01:00 + false + + + + + 0 + minutes + 2025-12-11T15:26:03+01:00 + 660872 + 9000 + 4416930 + be2grow + + 2025-12-11T15:26:03+01:00 + false + hourly_rate + + + + 0 + minutes + 2026-03-23T18:02:41+01:00 + 534086 + 0 + 4603894 + Shopify Anbindung + + 2026-03-23T18:02:41+01:00 + false + + + + + 0 + minutes + 2026-04-08T12:41:19+02:00 + 853397 + 0 + 4625749 + Nutzwedel + + 2026-04-08T12:41:19+02:00 + false + + + + + 0 + minutes + 2026-04-08T12:41:27+02:00 + 853397 + 0 + 4625752 + Das Kontaktwerk + + 2026-04-08T12:41:27+02:00 + false + + + + + 0 + minutes + 2026-04-15T11:18:22+02:00 + 902974 + 0 + 4635262 + Erstellung Portal und Applikation + + 2026-04-15T11:18:22+02:00 + false + + + + + 0 + minutes + 2026-04-29T10:41:19+02:00 + 905212 + 0 + 4651246 + Shop + + 2026-04-29T10:41:19+02:00 + false + + + + + 0 + minutes + 2026-05-20T10:45:47+02:00 + 908443 + 0 + 4674925 + Shopware 6 Shop + + 2026-05-20T10:45:47+02:00 + false + + + + + 0 + minutes + 2026-06-12T11:01:01+02:00 + 912127 + 0 + 4699930 + Website-Erstellung + + 2026-06-12T11:01:01+02:00 + false + + + + + + + true + 2018-03-19T15:06:16+01:00 + 7500 + 402469 + Recherche + + 2019-01-10T09:51:42+01:00 + false + + + true + 2018-03-19T15:06:46+01:00 + 7500 + 402470 + Software-Entwicklung + + 2019-01-10T09:51:54+01:00 + false + + + true + 2018-03-19T15:07:01+01:00 + 7500 + 402471 + Frontend-Entwicklung + + 2019-01-10T09:51:22+01:00 + false + + + true + 2018-03-19T15:07:14+01:00 + 7500 + 402472 + Einarbeitung + + 2019-01-10T09:51:14+01:00 + false + + + true + 2018-03-19T15:07:40+01:00 + 7500 + 402473 + Meeting + + 2019-01-10T09:51:29+01:00 + false + + + true + 2018-03-29T17:34:32+02:00 + 7500 + 403333 + Schätzung + + 2019-01-10T09:51:48+01:00 + false + + + true + 2018-04-13T16:09:27+02:00 + 7500 + 404635 + Dokumentation + + 2019-01-10T09:51:07+01:00 + false + + + true + 2018-05-07T21:35:20+02:00 + 7500 + 406328 + Erfassungsblatt + + 2019-01-02T13:18:36+01:00 + true + + + true + 2018-07-26T10:33:36+02:00 + 6000 + 413930 + Frontend Standard + Frontend zu 60 EUR / Stunde + 2018-08-22T17:59:57+02:00 + true + + + false + 2019-01-02T13:21:45+01:00 + + 425205 + spawntree Intern + + 2019-01-02T13:21:45+01:00 + false + + + false + 2019-01-02T13:22:53+01:00 + + 425206 + Diverse Projektarbeit + + 2019-01-02T13:22:53+01:00 + false + + + true + 2019-01-07T15:33:31+01:00 + 7500 + 425720 + Projektmanagement + + 2019-01-10T09:51:35+01:00 + false + + + false + 2019-03-01T17:36:21+01:00 + + 430725 + Urlaub + + 2026-05-22T16:36:05+02:00 + true + + + false + 2023-04-24T10:48:40+02:00 + + 515259 + Nicht monitäre Gegenleistung + + 2023-06-09T15:31:29+02:00 + false + + + false + 2026-02-26T09:52:51+01:00 + + 571381 + Später verrechnen + Stunden, die wir bislang nicht abrechnen konnten. + 2026-02-26T09:56:28+01:00 + false + + + + + true + 2018-03-19T15:08:22+01:00 + 2018-03-12 + 59420587 + true + 360 + 2411023 + 45000.0 + 402473 + 2019-01-10T09:57:29+01:00 + 115462 + Onboarding + + + true + 2018-03-19T15:08:44+01:00 + 2018-03-13 + 59420610 + true + 450 + 2411023 + 56250.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche und Machbarkeitsstudie + + + true + 2018-03-19T15:08:54+01:00 + 2018-03-14 + 59420620 + true + 300 + 2411023 + 37500.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche und Machbarkeitsstudie + + + true + 2018-03-19T15:09:15+01:00 + 2018-03-16 + 59420639 + true + 300 + 2411023 + 37500.0 + 402473 + 2019-01-10T09:57:29+01:00 + 115462 + Absprachen und Machbarkeitsbesprechung + + + true + 2018-03-19T15:09:53+01:00 + 2018-03-19 + 59420677 + true + 390 + 2411023 + 48750.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche (Datenbanken) + + + true + 2018-03-19T15:13:18+01:00 + 2018-03-12 + 59420870 + true + 360 + 2411023 + 45000.0 + 402473 + 2019-01-10T09:57:29+01:00 + 115462 + Onboarding + + + true + 2018-03-19T15:15:27+01:00 + 2018-03-13 + 59420975 + true + 480 + 2411023 + 60000.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche und Machbarkeitsstudie + + + true + 2018-03-19T15:15:33+01:00 + 2018-03-14 + 59420982 + true + 300 + 2411023 + 37500.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche und Machbarkeitsstudie + + + true + 2018-03-19T15:16:02+01:00 + 2018-03-15 + 59421014 + true + 450 + 2411023 + 56250.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche und Machbarkeitsstudie + + + true + 2018-03-19T15:16:09+01:00 + 2018-03-15 + 59421022 + true + 480 + 2411023 + 60000.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche und Machbarkeitsstudie + + + true + 2018-03-19T15:16:21+01:00 + 2018-03-16 + 59421034 + true + 300 + 2411023 + 37500.0 + 402473 + 2019-01-10T09:57:29+01:00 + 115462 + Absprachen und Machbarkeitsbesprechung + + + true + 2018-03-19T15:16:34+01:00 + 2018-03-19 + 59421041 + true + 450 + 2411023 + 56250.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche (Datenbanken) + + + true + 2018-03-20T17:44:11+01:00 + 2018-03-20 + 59470791 + true + 150 + 2411023 + 18750.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche (Datenbanken) + + + true + 2018-03-20T17:44:20+01:00 + 2018-03-20 + 59470799 + true + 150 + 2411023 + 18750.0 + 402469 + 2019-01-10T09:57:29+01:00 + 115462 + Recherche (Datenbanken) + + + true + 2018-03-20T17:44:45+01:00 + 2018-03-20 + 59470829 + true + 255 + 2411023 + 31875.0 + 402470 + 2019-01-10T09:57:29+01:00 + 115462 + Git und Electron Setup + + + true + 2018-03-20T17:44:53+01:00 + 2018-03-20 + 59470835 + true + 315 + 2411023 + 39375.0 + 402470 + 2019-01-10T09:57:29+01:00 + 115462 + Git und Electron Setup + + + true + 2018-03-21T17:37:38+01:00 + 2018-03-21 + 59510306 + true + 240 + 2411023 + 30000.0 + 402470 + 2019-01-10T09:57:29+01:00 + 115462 + Git und Angular Setup + + + true + 2018-03-21T17:37:41+01:00 + 2018-03-21 + 59510310 + true + 240 + 2411023 + 30000.0 + 402470 + 2019-01-10T09:57:29+01:00 + 115462 + Git und Angular Setup + + + true + 2018-03-22T16:08:39+01:00 + 2018-03-22 + 59548378 + true + 270 + 2411023 + 33750.0 + 402470 + 2019-01-10T09:57:29+01:00 + 115462 + Git und Symfony Setup / Datenbankmodell entwickeln + + + true + 2018-03-22T16:08:44+01:00 + 2018-03-22 + 59548383 + true + 270 + 2411023 + 33750.0 + 402470 + 2019-01-10T09:57:29+01:00 + 115462 + Git und Symfony Setup / Datenbankmodell entwickeln + + + true + 2018-03-23T17:26:40+01:00 + 2018-03-23 + 59591694 + true + 300 + 2411023 + 37500.0 + 402470 + 2019-01-10T09:57:29+01:00 + 115462 + Datenbank-Design + + + true + 2026-06-12T12:38:58+02:00 + 2026-06-12 + 163809280 + false + 30 + 4674925 + 3750.0 + 402473 + 2026-06-12T12:38:58+02:00 + 115462 + Absprachen weiteres Vorgehen + + + true + 2026-06-12T17:24:55+02:00 + 2026-06-12 + 163837672 + false + 150 + 3075541 + 20000.0 + 402471 + 2026-06-12T17:24:55+02:00 + 115462 + Language: Spanisch, Sustain Änderungen, Textanpassungen, Foto Seite erstellt + + + true + 2026-06-12T17:34:41+02:00 + 2026-06-12 + 163838215 + false + 180 + 4173176 + 30000.0 + 402470 + 2026-06-12T17:34:41+02:00 + 123162 + Excel export, imporvement + + + true + 2026-06-12T17:51:46+02:00 + 2026-06-12 + 163839187 + false + 30 + 4674925 + 3750.0 + 402473 + 2026-06-12T17:51:46+02:00 + 123163 + Teams Meeting Shopware 6 + + + true + 2026-06-12T17:52:20+02:00 + 2026-06-12 + 163839211 + false + 240 + 4674925 + 30000.0 + 402471 + 2026-06-15T18:12:15+02:00 + 123163 + Aufsetzen neuer Shopware 6 Shop. Einrichtung, Anlegen Kategorien und Shop Seiten + + + true + 2026-06-13T13:07:26+02:00 + 2026-06-03 + 163848253 + false + 45 + 3661969 + 6750.0 + 402471 + 2026-06-13T13:07:26+02:00 + 214511 + Cookiebar Anpassungen + + + true + 2026-06-13T13:08:47+02:00 + 2026-06-10 + 163848310 + false + 15 + 3661969 + 2250.0 + 402471 + 2026-06-13T13:08:47+02:00 + 214511 + Kontakt + + + true + 2026-06-13T13:09:38+02:00 + 2026-06-11 + 163848313 + false + 30 + + 3750.0 + 402471 + 2026-06-13T13:09:38+02:00 + 214511 + whizita / Flickenschild + + + true + 2026-06-15T11:29:41+02:00 + 2026-06-15 + 163902820 + false + 30 + 3754111 + 3750.0 + 402473 + 2026-06-15T11:29:41+02:00 + 115462 + Weekly + + + true + 2026-06-15T12:25:21+02:00 + 2026-06-15 + 163909471 + false + 60 + 3754111 + 7500.0 + 402471 + 2026-06-15T12:25:21+02:00 + 115462 + Farbvarianten 5, 6, 7, 8, 9, 15 angelegt + + + true + 2026-06-15T16:50:09+02:00 + 2026-06-15 + 163939247 + false + 150 + 3754111 + 18750.0 + 402471 + 2026-06-15T16:50:09+02:00 + 115462 + Anpassungen / Bugfixing Farbvarianten + + + true + 2026-06-15T17:48:09+02:00 + 2026-06-15 + 163946216 + false + 240 + 4173176 + 40000.0 + 402470 + 2026-06-15T17:48:09+02:00 + 123162 + Ticket mit Registrierung, Excel file names, Mailing stats PDF + + + true + 2026-06-15T18:11:38+02:00 + 2026-06-15 + 163948091 + false + 240 + 4674925 + 30000.0 + 402471 + 2026-06-15T18:11:45+02:00 + 123163 + Anlegen Zusatzfelder und Import Profil. Programmierung Import Converter + + + true + 2026-06-16T11:45:41+02:00 + 2026-06-16 + 163994732 + false + 60 + 3754111 + 7500.0 + 402471 + 2026-06-16T11:45:41+02:00 + 115462 + Farben: Trello Karten + + + true + 2026-06-16T11:45:58+02:00 + 2026-06-16 + 163994762 + false + 30 + 3754111 + 3750.0 + 402471 + 2026-06-16T11:45:58+02:00 + 115462 + Bugfixing (IHK Maintenance) + + + true + 2026-06-16T13:13:52+02:00 + 2026-06-16 + 164004155 + false + 30 + 3754111 + 3750.0 + 402471 + 2026-06-16T13:13:52+02:00 + 115462 + Trello: Bugfixing + + + true + 2026-06-16T17:18:44+02:00 + 2026-06-16 + 164031713 + false + 510 + 4173176 + 85000.0 + 402470 + 2026-06-16T17:18:44+02:00 + 123162 + Mailing stats pdf, register mail resend, etc. + + + true + 2026-06-16T18:14:35+02:00 + 2026-06-16 + 164036432 + false + 240 + 4674925 + 30000.0 + 402471 + 2026-06-16T18:14:35+02:00 + 123163 + Programmierung Import Converter und API Schnittstelle + + + true + 2026-06-17T11:04:19+02:00 + 2026-06-17 + 164075924 + false + 15 + 4051725 + 2500.0 + 402471 + 2026-06-17T11:04:19+02:00 + 115462 + Anpassungen Bild in News + + + true + 2026-06-17T15:24:21+02:00 + 2026-06-17 + 164102336 + false + 360 + 4173176 + 60000.0 + 402470 + 2026-06-17T15:24:37+02:00 + 123162 + Excel Exporte, Support etc. + + + true + 2026-06-17T18:06:35+02:00 + 2026-06-17 + 164168690 + false + 60 + 4674925 + 7500.0 + 402471 + 2026-06-17T18:06:35+02:00 + 123163 + Programmierung Import Converter und API Schnittstelle + + + true + 2026-06-17T18:07:17+02:00 + 2026-06-17 + 164168726 + false + 60 + 4603894 + 11250.0 + 402471 + 2026-06-17T18:07:17+02:00 + 123163 + Abarbeiten offene Trello Tickets + + + true + 2026-06-18T15:13:31+02:00 + 2026-06-18 + 164283350 + false + 15 + 3754111 + 1875.0 + 402471 + 2026-06-18T15:13:31+02:00 + 115462 + IHK Karlsruhe: Kontrolle Signatur "Verkehr" + + + true + 2026-06-18T18:16:59+02:00 + 2026-06-18 + 164301914 + false + 60 + 4603894 + 11250.0 + 402471 + 2026-06-18T18:16:59+02:00 + 123163 + Programmierung Storefront Plugin + + + true + 2026-06-19T18:19:49+02:00 + 2026-06-19 + 164378054 + false + 180 + 4603894 + 33750.0 + 402471 + 2026-06-19T18:19:49+02:00 + 123163 + Anpassungen Import Script + + + true + 2026-06-21T22:39:17+02:00 + 2026-06-21 + 164395181 + false + 30 + 3754111 + 3750.0 + 402471 + 2026-06-21T22:39:17+02:00 + 115462 + Anpassungen laut Asana Tickets + + + true + 2026-06-22T00:27:29+02:00 + 2026-06-21 + 164395856 + false + 60 + 2875415 + 11250.0 + 402471 + 2026-06-22T00:27:29+02:00 + 115462 + Cron Jobs anlegen + + + true + 2026-06-22T10:14:19+02:00 + 2026-06-22 + 164426144 + false + 15 + 4224541 + 2250.0 + 402471 + 2026-06-22T10:14:19+02:00 + 115462 + Hilfe Einrichtung E-Mail Adresse + + + + + 2018-03-19T15:03:50+01:00 + f.eisenmenger@spawntree.de + 115462 + Florian Eisenmenger + + 2026-06-22T10:33:51+02:00 + false + de + owner + + + 2019-01-02T13:01:25+01:00 + d.knudsen@spawntree.de + 123162 + Daniel Knudsen + + 2026-06-17T15:23:59+02:00 + false + de + admin + + + 2019-01-02T13:08:36+01:00 + dirktietze@nova-sign.de + 123163 + Dirk Tietze + + 2026-06-19T18:19:23+02:00 + false + de + time_tracker + + + 2022-02-18T09:45:06+01:00 + info@zimmer-lukas.de + 214511 + Lukas Zimmer + + 2026-06-13T13:06:29+02:00 + false + de + time_tracker + + + 2025-01-08T23:08:16+01:00 + michael.schild@edit.de + 232991 + Michael Schild + + 2025-04-07T10:55:28+02:00 + true + de + time_tracker + + + + + 183240 + 2666337 + 123162 + + + 209991 + 2674625 + 123163 + + + 187147 + 2674626 + 123163 + + + 225235 + 2681729 + 123163 + + + 202239 + 2682677 + 123163 + + + 188341 + 2742845 + 123163 + + + 223679 + 2785029 + 123163 + + + 229530 + 2809130 + 123163 + + + 202240 + 2854592 + 123163 + + + 244311 + 2875415 + 123163 + + + 228553 + 2938527 + 123163 + + + 209764 + 3095483 + 123163 + + + 221480 + 3209601 + 123163 + + + 228590 + 3338275 + 123163 + + + 241387 + 3398850 + 123163 + + + 241583 + 3460509 + 123163 + + + 250002 + 3498043 + 123163 + + + 263810 + 3502877 + 123163 + + + 259124 + 3754111 + 123163 + + + 262638 + 3843733 + 123163 + + + 263447 + 3905504 + 123163 + + + 268760 + 3994020 + 123163 + + + 270425 + 3995138 + 123163 + + + 271142 + 4029587 + 123163 + + + 272326 + 4049805 + 123163 + + + 281221 + 4051725 + 123163 + + + 272575 + 4054610 + 123163 + + + 273652 + 4074788 + 123163 + + + 273699 + 4075849 + 123163 + + + 279938 + 4172624 + 123163 + + + 281333 + 4174087 + 123163 + + + 282968 + 4256153 + 123163 + + + 303303 + 4416930 + 123163 + + + 504721 + 4603894 + 123163 + + + 514237 + 4674925 + 123163 + + + 260498 + 2682677 + 214511 + + + 244714 + 2875415 + 214511 + + + 250223 + 3424130 + 214511 + + + 238715 + 3451553 + 214511 + + + 252087 + 3460509 + 214511 + + + 253051 + 3661969 + 214511 + + + 255049 + 3714684 + 214511 + + + 261563 + 3754111 + 214511 + + + 263844 + 3848972 + 214511 + + + 507340 + 4625749 + 214511 + + + 507343 + 4625752 + 214511 + + + 516835 + 4699930 + 214511 + + + 276081 + 4101606 + 232991 + + + diff --git a/httpdocs/assets/scripts/account.js b/httpdocs/assets/scripts/account.js index 4bb3b60..a93a8bc 100644 --- a/httpdocs/assets/scripts/account.js +++ b/httpdocs/assets/scripts/account.js @@ -212,4 +212,220 @@ document.addEventListener('DOMContentLoaded', () => { } }); } + + // ── mite-Import ───────────────────────────────────────────────────────────── + + const importFile = document.getElementById('import-file'); + const importFileInfo = document.getElementById('import-file-info'); + const importFileName = document.getElementById('import-file-name'); + const importFileRemove = document.getElementById('import-file-remove'); + const btnAnalyze = document.getElementById('btn-import-analyze'); + const btnExecute = document.getElementById('btn-import-execute'); + const btnReset = document.getElementById('btn-import-reset'); + const previewCard = document.getElementById('import-preview'); + const previewContent = document.getElementById('import-preview-content'); + const resultCard = document.getElementById('import-result'); + const resultContent = document.getElementById('import-result-content'); + const dropArea = document.getElementById('import-drop-area'); + + if (importFile) { + let selectedFile = null; + + function setFile(file) { + if (!file || !file.name.toLowerCase().endsWith('.xml')) { + showToast(t('importErrorNoFile'), true); + return; + } + selectedFile = file; + importFileName.textContent = file.name + ' (' + formatFileSize(file.size) + ')'; + importFileInfo.hidden = false; + btnAnalyze.disabled = false; + previewCard.hidden = true; + resultCard.hidden = true; + } + + function clearFile() { + selectedFile = null; + importFile.value = ''; + importFileInfo.hidden = true; + btnAnalyze.disabled = true; + previewCard.hidden = true; + resultCard.hidden = true; + } + + function formatFileSize(bytes) { + if (bytes < 1024) return bytes + ' B'; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; + } + + importFile.addEventListener('change', () => { + if (importFile.files.length > 0) setFile(importFile.files[0]); + }); + + importFileRemove.addEventListener('click', clearFile); + + // Drag & Drop + ['dragenter', 'dragover'].forEach(evt => { + dropArea.addEventListener(evt, (e) => { + e.preventDefault(); + dropArea.classList.add('import-upload__area--dragover'); + }); + }); + ['dragleave', 'drop'].forEach(evt => { + dropArea.addEventListener(evt, () => { + dropArea.classList.remove('import-upload__area--dragover'); + }); + }); + dropArea.addEventListener('drop', (e) => { + e.preventDefault(); + if (e.dataTransfer.files.length > 0) setFile(e.dataTransfer.files[0]); + }); + + // Analyse + btnAnalyze.addEventListener('click', async () => { + if (!selectedFile) return; + + btnAnalyze.disabled = true; + btnAnalyze.textContent = t('importAnalyzing'); + + const form = new FormData(); + form.append('file', selectedFile); + + try { + const res = await fetch('/api/import/mite/preview', { method: 'POST', body: form }); + const json = await res.json(); + if (!res.ok) throw new Error(json.error ?? t('errorGeneric')); + + renderPreview(json); + previewCard.hidden = false; + resultCard.hidden = true; + } catch (e) { + showToast(e.message, true); + } finally { + btnAnalyze.textContent = t('importAnalyze'); + btnAnalyze.disabled = false; + } + }); + + // Import ausführen + btnExecute.addEventListener('click', async () => { + if (!selectedFile) return; + if (!confirm(t('importConfirm'))) return; + + btnExecute.disabled = true; + btnExecute.textContent = t('importExecuting'); + + const form = new FormData(); + form.append('file', selectedFile); + + const createUserIds = []; + previewContent.querySelectorAll('input[name^="user-mode-"]:checked').forEach(radio => { + if (radio.value === 'create') { + createUserIds.push(parseInt(radio.name.replace('user-mode-', ''), 10)); + } + }); + form.append('createUsers', JSON.stringify(createUserIds)); + + try { + const res = await fetch('/api/import/mite/execute', { method: 'POST', body: form }); + const json = await res.json(); + if (!res.ok) throw new Error(json.error ?? t('errorGeneric')); + + renderResult(json); + previewCard.hidden = true; + resultCard.hidden = false; + showToast(t('importSuccess')); + } catch (e) { + showToast(e.message, true); + } finally { + btnExecute.textContent = t('importExecute'); + btnExecute.disabled = false; + } + }); + + // Reset + btnReset.addEventListener('click', () => { + previewCard.hidden = true; + resultCard.hidden = true; + }); + + function renderPreview(data) { + const rows = [ + { label: t('importLabelClients'), value: data.customers }, + { label: t('importLabelProjects'), value: data.projects }, + { label: t('importLabelServices'), value: data.services }, + { label: t('importLabelEntries'), value: data.timeEntries }, + ]; + + let html = '
'; + for (const row of rows) { + html += `
${esc(row.label)}
${esc(String(row.value))}
`; + } + + if (data.dateRange.from && data.dateRange.to) { + html += `
${esc(t('importLabelDateRange'))}
${esc(formatDate(data.dateRange.from))} – ${esc(formatDate(data.dateRange.to))}
`; + } + html += '
'; + + // Benutzer-Zuordnung + if (data.users.length > 0) { + html += `

${esc(t('importLabelUsers'))}

`; + html += ''; + } + + // Warnungen + if (data.warnings.length > 0) { + html += `

${esc(t('importLabelWarnings'))}

`; + html += ''; + } + + previewContent.innerHTML = html; + } + + function renderResult(data) { + function statLine(label, total, created) { + const existing = total - created; + let detail = `${created} ${esc(t('importNewLabel'))}`; + if (existing > 0) { + detail += `, ${existing} ${esc(t('importExistingLabel'))}`; + } + return `
${esc(label)}
${detail}
`; + } + + let html = '
'; + html += statLine(t('importLabelClients'), data.clients, data.clientsCreated); + html += statLine(t('importLabelProjects'), data.projects, data.projectsCreated); + html += statLine(t('importLabelServices'), data.services, data.servicesCreated); + html += `
${esc(t('importResultEntries'))}
${data.timeEntries}
`; + if (data.usersCreated > 0) { + html += `
${esc(t('importResultUsers'))}
${data.usersCreated}
`; + } + html += '
'; + resultContent.innerHTML = html; + } + + function formatDate(dateStr) { + const d = new Date(dateStr + 'T00:00:00'); + return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); + } + } }); diff --git a/httpdocs/assets/scripts/statistics.js b/httpdocs/assets/scripts/statistics.js index 7b40a5b..9eebb85 100644 --- a/httpdocs/assets/scripts/statistics.js +++ b/httpdocs/assets/scripts/statistics.js @@ -298,6 +298,27 @@ function renderDonut(canvasId, legendId, chartKey, items, metric) { } } +function updateTotal(data, metric) { + const el = document.getElementById('stats-total'); + if (!el || !data) return; + + const billable = metric === 'revenue' ? data.billableRevenue : data.billable; + const nonBillable = metric === 'revenue' ? data.nonBillableRevenue : data.nonBillable; + const total = [...billable, ...nonBillable].reduce((s, v) => s + v, 0); + + if (total === 0) { + el.hidden = true; + return; + } + + if (metric === 'revenue') { + el.textContent = t('totalRevenue').replace('__REVENUE__', formatCurrency(total)); + } else { + el.textContent = t('totalHours').replace('__HOURS__', formatHours(total) + ' h'); + } + el.hidden = false; +} + function renderDonuts(data, metric) { if (!data?.distribution) return; const d = data.distribution; @@ -321,6 +342,7 @@ async function loadAndRender(range, metric, userId) { cachedData = await res.json(); renderChart(cachedData, metric); renderDonuts(cachedData, metric); + updateTotal(cachedData, metric); } catch { wrap.innerHTML = '

' + t('errorLoad') + '

'; } @@ -353,6 +375,7 @@ document.addEventListener('DOMContentLoaded', () => { if (cachedData) { renderChart(cachedData, metricSelect.value); renderDonuts(cachedData, metricSelect.value); + updateTotal(cachedData, metricSelect.value); } }); }); diff --git a/httpdocs/assets/styles/components/_account.scss b/httpdocs/assets/styles/components/_account.scss index 0b3f6e2..78d8a5b 100644 --- a/httpdocs/assets/styles/components/_account.scss +++ b/httpdocs/assets/styles/components/_account.scss @@ -201,6 +201,212 @@ } } +// ─── Import-Sektion ────────────────────────────────────────────────────── +.import-section__title { + font-size: $font-size-lg; + font-weight: $font-weight-bold; + color: $color-text-dark; + margin-bottom: $space-2; +} + +.import-section__desc { + font-size: $font-size-sm; + color: $color-text-muted; + line-height: 1.55; + margin-bottom: $space-6; +} + +.import-upload__area { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: $space-2; + padding: $space-8 $space-6; + border: 2px dashed $color-input-border; + border-radius: $radius-md; + cursor: pointer; + transition: border-color $transition-fast, background $transition-fast; + + &:hover, + &--dragover { + border-color: var(--color-primary, $color-primary); + background: rgba(var(--color-primary-rgb, 58, 123, 191), 0.04); + } +} + +.import-upload__icon { + color: $color-text-muted; +} + +.import-upload__text { + font-size: $font-size-sm; + font-weight: $font-weight-medium; + color: $color-text-dark; +} + +.import-upload__hint { + font-size: $font-size-xs; + color: $color-text-muted; +} + +.import-upload__file-info { + display: flex; + align-items: center; + gap: $space-3; + margin-top: $space-3; + padding: $space-3 $space-4; + background: $color-bg; + border-radius: $radius-sm; + font-size: $font-size-sm; + color: $color-text-dark; +} + +.import-upload__remove { + margin-left: auto; + background: none; + border: none; + font-size: $font-size-lg; + color: $color-text-muted; + cursor: pointer; + padding: 0 $space-2; + line-height: 1; + + &:hover { color: $color-error; } +} + +.import-actions { + margin-top: $space-5; + display: flex; + gap: $space-4; + + &--preview { + padding-top: $space-5; + border-top: 1px solid $color-border; + } +} + +// ─── Import-Vorschau ───────────────────────────────────────────────────── +.import-preview__stats { + display: grid; + grid-template-columns: auto 1fr; + gap: $space-2 $space-6; + font-size: $font-size-sm; + + dt { + color: $color-text-muted; + font-weight: $font-weight-medium; + } + + dd { + color: $color-text-dark; + font-weight: $font-weight-bold; + margin: 0; + } + + &--result dd { + color: $color-text-dark; + font-weight: $font-weight-regular; + + strong { + color: var(--color-primary, $color-primary); + font-weight: $font-weight-bold; + } + } +} + +.import-preview__subtitle { + font-size: $font-size-sm; + font-weight: $font-weight-bold; + color: $color-text-dark; + margin-top: $space-5; + margin-bottom: $space-2; +} + +.import-preview__users { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: $space-2; + + li { + font-size: $font-size-sm; + color: $color-text-dark; + line-height: 1.6; + } +} + +.import-preview__email { + color: $color-text-muted; +} + +.import-user-options { + display: flex; + gap: $space-4; + margin-top: $space-1; +} + +.import-user-option { + display: inline-flex; + align-items: center; + gap: $space-1; + font-size: $font-size-xs; + color: $color-text-muted; + cursor: pointer; + + input[type="radio"] { + margin: 0; + accent-color: var(--color-primary, $color-primary); + } +} + +.import-badge { + display: inline-block; + font-size: $font-size-xs; + padding: 1px $space-2; + border-radius: $radius-pill; + font-weight: $font-weight-medium; + + &--matched { + background: rgba(34, 197, 94, 0.12); + color: #15803d; + } + + &--fallback { + background: rgba(234, 179, 8, 0.15); + color: #a16207; + } +} + +.import-preview__warnings { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: $space-1; + + li { + font-size: $font-size-sm; + color: $color-text-muted; + padding-left: $space-4; + position: relative; + + &::before { + content: '⚠'; + position: absolute; + left: 0; + font-size: $font-size-xs; + } + } +} + +.import-result { + border-left: 3px solid var(--color-primary, $color-primary); +} + // ─── Toast ─────────────────────────────────────────────────────────────────── .account-toast { position: fixed; diff --git a/httpdocs/assets/styles/sections/_statistics.scss b/httpdocs/assets/styles/sections/_statistics.scss index fab7908..3a7046a 100644 --- a/httpdocs/assets/styles/sections/_statistics.scss +++ b/httpdocs/assets/styles/sections/_statistics.scss @@ -87,6 +87,15 @@ } } +// ─── Gesamtsumme ─────────────────────────────────────────────────────────── +.statistics-total { + text-align: right; + padding: $space-3 $space-5 0; + font-size: $font-size-lg; + font-weight: $font-weight-bold; + color: $color-text-dark; +} + // ─── Chart ────────────────────────────────────────────────────────────────── .statistics-chart-wrap { position: relative; diff --git a/httpdocs/config/services.yaml b/httpdocs/config/services.yaml index 0a8b8ff..9da5307 100644 --- a/httpdocs/config/services.yaml +++ b/httpdocs/config/services.yaml @@ -69,5 +69,13 @@ services: tags: - { name: doctrine.middleware, connection: tenant } + App\Service\MiteImportService: + arguments: + $centralEm: '@doctrine.orm.central_entity_manager' + + App\Controller\ImportController: + arguments: + $tenantEm: '@doctrine.orm.tenant_entity_manager' + App\Controller\InviteController: arguments: ~ diff --git a/httpdocs/src/Controller/AccountController.php b/httpdocs/src/Controller/AccountController.php index 8a3e003..2e5f2e8 100644 --- a/httpdocs/src/Controller/AccountController.php +++ b/httpdocs/src/Controller/AccountController.php @@ -37,7 +37,7 @@ class AccountController extends AbstractController $isAdmin = $accountUser?->isAdmin() ?? false; $tab = $request->query->get('tab', $isAdmin ? 'account' : 'user'); - if (!$isAdmin && $tab === 'account') { + if (!$isAdmin && in_array($tab, ['account', 'import'], true)) { $tab = 'user'; } diff --git a/httpdocs/src/Controller/ImportController.php b/httpdocs/src/Controller/ImportController.php new file mode 100644 index 0000000..311bf3b --- /dev/null +++ b/httpdocs/src/Controller/ImportController.php @@ -0,0 +1,84 @@ +roleHelper->isAdmin()) { + return $this->json(['error' => $this->translator->trans('app.error.access_denied')], 403); + } + + $file = $request->files->get('file'); + if (!$file) { + return $this->json(['error' => $this->translator->trans('app.import.error_no_file')], 400); + } + + if ($file->getSize() > self::MAX_FILE_SIZE) { + return $this->json(['error' => $this->translator->trans('app.import.error_file_too_large')], 400); + } + + $ext = strtolower($file->getClientOriginalExtension()); + if ($ext !== 'xml') { + return $this->json(['error' => $this->translator->trans('app.import.error_invalid_format')], 400); + } + + try { + $xml = file_get_contents($file->getPathname()); + $analysis = $this->miteImportService->analyze($xml); + return $this->json($analysis); + } catch (\RuntimeException $e) { + return $this->json(['error' => $e->getMessage()], 422); + } + } + + #[Route('/api/import/mite/execute', name: 'api_import_mite_execute', methods: ['POST'])] + public function execute(Request $request): JsonResponse + { + if (!$this->roleHelper->isAdmin()) { + return $this->json(['error' => $this->translator->trans('app.error.access_denied')], 403); + } + + $file = $request->files->get('file'); + if (!$file) { + return $this->json(['error' => $this->translator->trans('app.import.error_no_file')], 400); + } + + /** @var User $user */ + $user = $this->getUser(); + + $createUsers = json_decode($request->request->get('createUsers', '[]'), true) ?? []; + $createUsers = array_map('intval', $createUsers); + + try { + $xml = file_get_contents($file->getPathname()); + $result = $this->miteImportService->execute($xml, $user->getId(), $this->tenantEm, $createUsers); + return $this->json($result); + } catch (\RuntimeException $e) { + return $this->json(['error' => $e->getMessage()], 422); + } + } +} diff --git a/httpdocs/src/Service/MiteImportService.php b/httpdocs/src/Service/MiteImportService.php new file mode 100644 index 0000000..68a08df --- /dev/null +++ b/httpdocs/src/Service/MiteImportService.php @@ -0,0 +1,476 @@ +parse($xml); + + $account = $this->tenantContext->getAccount(); + $userMapping = []; + foreach ($data['users'] as $miteUser) { + $matched = $this->userRepo->findByEmail($miteUser['email']); + $matchedInAccount = null; + if ($matched) { + $au = $this->accountUserRepo->findOneBy([ + 'account' => $account, + 'user' => $matched, + ]); + if ($au && !$au->isArchived()) { + $matchedInAccount = $matched; + } + } + + $userMapping[] = [ + 'miteId' => $miteUser['id'], + 'name' => $miteUser['name'], + 'email' => $miteUser['email'], + 'role' => $miteUser['role'], + 'matched' => $matchedInAccount !== null, + 'matchedUserId' => $matchedInAccount?->getId(), + 'matchedUserName' => $matchedInAccount?->getFullName(), + 'entryCount' => $this->countEntriesForUser($data['timeEntries'], $miteUser['id']), + ]; + } + + $warnings = $this->detectWarnings($data); + + $dateRange = $this->getDateRange($data['timeEntries']); + + return [ + 'customers' => count($data['customers']), + 'projects' => count($data['projects']), + 'services' => count($data['services']), + 'timeEntries' => count($data['timeEntries']), + 'users' => $userMapping, + 'warnings' => $warnings, + 'dateRange' => $dateRange, + ]; + } + + /** + * @param int[] $createUserIds mite-User-IDs, für die ein neuer System-User angelegt werden soll + */ + public function execute(string $xml, int $currentUserId, EntityManagerInterface $tenantEm, array $createUserIds = []): array + { + $data = $this->parse($xml); + + $account = $this->tenantContext->getAccount(); + $miteUserToSystemUser = []; + $usersCreated = 0; + + $miteUsersById = []; + foreach ($data['users'] as $mu) { + $miteUsersById[$mu['id']] = $mu; + } + + foreach ($data['users'] as $miteUser) { + $matched = $this->userRepo->findByEmail($miteUser['email']); + if ($matched) { + $au = $this->accountUserRepo->findOneBy([ + 'account' => $account, + 'user' => $matched, + ]); + if ($au && !$au->isArchived()) { + $miteUserToSystemUser[$miteUser['id']] = $matched->getId(); + continue; + } + } + + if (in_array($miteUser['id'], $createUserIds, true)) { + $user = $this->createUserFromMite($miteUser, $account); + $miteUserToSystemUser[$miteUser['id']] = $user->getId(); + $usersCreated++; + continue; + } + + $miteUserToSystemUser[$miteUser['id']] = $currentUserId; + } + + $tenantEm->beginTransaction(); + try { + $serviceMap = $this->importServices($data['services'], $tenantEm); + $clientMap = $this->importClients($data['customers'], $tenantEm); + $projectMap = $this->importProjects($data['projects'], $clientMap, $tenantEm); + + $tenantEm->flush(); + + $entryCount = $this->importTimeEntries( + $data['timeEntries'], + $projectMap, + $serviceMap, + $miteUserToSystemUser, + $currentUserId, + $tenantEm, + ); + + $tenantEm->flush(); + $tenantEm->commit(); + + return [ + 'services' => count($data['services']), + 'servicesCreated' => count($serviceMap['created']), + 'clients' => count($data['customers']), + 'clientsCreated' => count($clientMap['created']), + 'projects' => count($data['projects']), + 'projectsCreated' => count($projectMap['created']), + 'timeEntries' => $entryCount, + 'usersCreated' => $usersCreated, + ]; + } catch (\Throwable $e) { + $tenantEm->rollback(); + throw $e; + } + } + + private function parse(string $xml): array + { + libxml_use_internal_errors(true); + $doc = simplexml_load_string($xml); + if ($doc === false) { + $errors = libxml_get_errors(); + libxml_clear_errors(); + $msg = !empty($errors) ? $errors[0]->message : 'Unbekannter Fehler'; + throw new \RuntimeException('XML-Parsing fehlgeschlagen: ' . trim($msg)); + } + + $customers = []; + foreach ($doc->customers->customer ?? [] as $c) { + $customers[] = [ + 'id' => (int) $c->id, + 'name' => (string) $c->name, + 'hourlyRate' => (int) $c->{'hourly-rate'}, + 'note' => (string) $c->note, + 'archived' => ((string) $c->archived) === 'true', + ]; + } + + $projects = []; + foreach ($doc->projects->project ?? [] as $p) { + $projects[] = [ + 'id' => (int) $p->id, + 'customerId' => (int) $p->{'customer-id'}, + 'name' => (string) $p->name, + 'hourlyRate' => (int) $p->{'hourly-rate'}, + 'note' => (string) $p->note, + 'archived' => ((string) $p->archived) === 'true', + 'budget' => (int) $p->budget, + ]; + } + + $services = []; + foreach ($doc->services->service ?? [] as $s) { + $services[] = [ + 'id' => (int) $s->id, + 'name' => (string) $s->name, + 'billable' => ((string) $s->billable) === 'true', + 'hourlyRate' => (int) $s->{'hourly-rate'}, + 'note' => (string) $s->note, + 'archived' => ((string) $s->archived) === 'true', + ]; + } + + $timeEntries = []; + foreach ($doc->{'time-entries'}->{'time-entry'} ?? [] as $te) { + $timeEntries[] = [ + 'id' => (int) $te->id, + 'dateAt' => (string) $te->{'date-at'}, + 'minutes' => (int) $te->minutes, + 'projectId' => (int) $te->{'project-id'}, + 'serviceId' => (int) $te->{'service-id'}, + 'userId' => (int) $te->{'user-id'}, + 'note' => (string) $te->note, + 'billable' => ((string) $te->billable) === 'true', + 'locked' => ((string) $te->locked) === 'true', + ]; + } + + $users = []; + foreach ($doc->users->user ?? [] as $u) { + $users[] = [ + 'id' => (int) $u->id, + 'name' => (string) $u->name, + 'email' => (string) $u->email, + 'role' => (string) $u->role, + 'archived' => ((string) $u->archived) === 'true', + ]; + } + + return compact('customers', 'projects', 'services', 'timeEntries', 'users'); + } + + /** + * @return array{entities: array, created: int[]} + */ + private function importServices(array $miteServices, EntityManagerInterface $em): array + { + $repo = $em->getRepository(Service::class); + $map = ['entities' => [], 'created' => []]; + + foreach ($miteServices as $ms) { + $existing = $repo->findOneBy(['name' => $ms['name']]); + if ($existing) { + $map['entities'][$ms['id']] = $existing; + continue; + } + + $service = new Service(); + $service->setName($ms['name']); + $service->setBillable($ms['billable']); + $service->setHourlyRate($this->centsToDecimal($ms['hourlyRate'])); + if ($ms['note'] !== '') { + $service->setNote($ms['note']); + } + if ($ms['archived']) { + $service->setArchivedAt(new \DateTimeImmutable()); + } + + $em->persist($service); + $map['entities'][$ms['id']] = $service; + $map['created'][] = $ms['id']; + } + + return $map; + } + + /** + * @return array{entities: array, created: int[]} + */ + private function importClients(array $miteCustomers, EntityManagerInterface $em): array + { + $repo = $em->getRepository(Client::class); + $map = ['entities' => [], 'created' => []]; + + foreach ($miteCustomers as $mc) { + $existing = $repo->findOneBy(['name' => $mc['name']]); + if ($existing) { + $map['entities'][$mc['id']] = $existing; + continue; + } + + $client = new Client(); + $client->setName($mc['name']); + if ($mc['hourlyRate'] > 0) { + $client->setHourlyRate($this->centsToDecimal($mc['hourlyRate'])); + } + if ($mc['note'] !== '') { + $client->setNote($mc['note']); + } + if ($mc['archived']) { + $client->setArchivedAt(new \DateTimeImmutable()); + } + + $em->persist($client); + $map['entities'][$mc['id']] = $client; + $map['created'][] = $mc['id']; + } + + return $map; + } + + /** + * @return array{entities: array, created: int[]} + */ + private function importProjects(array $miteProjects, array $clientMap, EntityManagerInterface $em): array + { + $repo = $em->getRepository(Project::class); + $map = ['entities' => [], 'created' => []]; + + foreach ($miteProjects as $mp) { + $client = $clientMap['entities'][$mp['customerId']] ?? null; + if (!$client) { + continue; + } + + $existing = $repo->findOneBy(['name' => $mp['name'], 'client' => $client]); + if ($existing) { + $map['entities'][$mp['id']] = $existing; + continue; + } + + $project = new Project(); + $project->setName($mp['name']); + $project->setClient($client); + if ($mp['hourlyRate'] > 0) { + $project->setHourlyRate($this->centsToDecimal($mp['hourlyRate'])); + } + if ($mp['note'] !== '') { + $project->setNote($mp['note']); + } + if ($mp['archived']) { + $project->setArchivedAt(new \DateTimeImmutable()); + } + + $em->persist($project); + $map['entities'][$mp['id']] = $project; + $map['created'][] = $mp['id']; + } + + return $map; + } + + private function importTimeEntries( + array $miteEntries, + array $projectMap, + array $serviceMap, + array $userMapping, + int $fallbackUserId, + EntityManagerInterface $em, + ): int { + $count = 0; + + foreach ($miteEntries as $me) { + $project = $projectMap['entities'][$me['projectId']] ?? null; + if (!$project) { + continue; + } + + $service = $serviceMap['entities'][$me['serviceId']] ?? null; + + $entry = new TimeEntry(); + $entry->setDate(new \DateTimeImmutable($me['dateAt'])); + $entry->setDuration($me['minutes']); + $entry->setProject($project); + $entry->setService($service); + $entry->setUserId($userMapping[$me['userId']] ?? $fallbackUserId); + if ($me['note'] !== '') { + $entry->setNote($me['note']); + } + if ($me['locked']) { + $entry->setInvoiced(true); + } + + $em->persist($entry); + $count++; + + if ($count % 100 === 0) { + $em->flush(); + } + } + + return $count; + } + + private function createUserFromMite(array $miteUser, \App\Entity\Central\Account $account): User + { + $existing = $this->userRepo->findByEmail($miteUser['email']); + if ($existing) { + $user = $existing; + } else { + [$firstName, $lastName] = $this->splitName($miteUser['name']); + $user = new User(); + $user->setEmail($miteUser['email']); + $user->setFirstName($firstName); + $user->setLastName($lastName); + $this->centralEm->persist($user); + } + + $existingAu = $this->accountUserRepo->findOneBy([ + 'account' => $account, + 'user' => $user, + ]); + + if (!$existingAu) { + $accountUser = new AccountUser(); + $accountUser->setAccount($account); + $accountUser->setUser($user); + $accountUser->setRole($this->mapMiteRole($miteUser['role'])); + $this->centralEm->persist($accountUser); + } + + $this->centralEm->flush(); + + return $user; + } + + private function splitName(string $fullName): array + { + $parts = preg_split('/\s+/', trim($fullName), 2); + return [$parts[0], $parts[1] ?? '']; + } + + private function mapMiteRole(string $miteRole): string + { + return match ($miteRole) { + 'owner', 'admin' => AccountUser::ROLE_MEMBER, + 'coworker' => AccountUser::ROLE_MEMBER, + default => AccountUser::ROLE_TRACKER, + }; + } + + private function centsToDecimal(int $cents): string + { + return number_format($cents / 100, 2, '.', ''); + } + + private function countEntriesForUser(array $timeEntries, int $miteUserId): int + { + return count(array_filter($timeEntries, fn(array $te) => $te['userId'] === $miteUserId)); + } + + private function detectWarnings(array $data): array + { + $warnings = []; + + $budgetCount = count(array_filter($data['projects'], fn(array $p) => $p['budget'] > 0)); + if ($budgetCount > 0) { + $warnings[] = sprintf('%d Projekte haben Budgets — werden nicht importiert.', $budgetCount); + } + + $lockedCount = count(array_filter($data['timeEntries'], fn(array $te) => $te['locked'])); + if ($lockedCount > 0) { + $warnings[] = sprintf('%d Einträge sind in mite gesperrt — werden als „abgerechnet" importiert.', $lockedCount); + } + + $archivedCustomers = count(array_filter($data['customers'], fn(array $c) => $c['archived'])); + $archivedProjects = count(array_filter($data['projects'], fn(array $p) => $p['archived'])); + $archivedServices = count(array_filter($data['services'], fn(array $s) => $s['archived'])); + if ($archivedCustomers + $archivedProjects + $archivedServices > 0) { + $parts = []; + if ($archivedCustomers > 0) $parts[] = "$archivedCustomers Kunden"; + if ($archivedProjects > 0) $parts[] = "$archivedProjects Projekte"; + if ($archivedServices > 0) $parts[] = "$archivedServices Leistungen"; + $warnings[] = implode(', ', $parts) . ' sind archiviert — werden als archiviert importiert.'; + } + + $warnings[] = 'Stundensätze werden von Cent (mite) in Euro umgerechnet.'; + + return $warnings; + } + + private function getDateRange(array $timeEntries): array + { + if (empty($timeEntries)) { + return ['from' => null, 'to' => null]; + } + + $dates = array_map(fn(array $te) => $te['dateAt'], $timeEntries); + sort($dates); + + return [ + 'from' => $dates[0], + 'to' => $dates[count($dates) - 1], + ]; + } +} diff --git a/httpdocs/templates/account/index.html.twig b/httpdocs/templates/account/index.html.twig index 75fcddb..f9b80d8 100644 --- a/httpdocs/templates/account/index.html.twig +++ b/httpdocs/templates/account/index.html.twig @@ -2,7 +2,7 @@ {% extends 'base.html.twig' %} {% block title %} - {% if tab == 'account' %}{{ 'app.account.page_title_account'|trans }}{% else %}{{ 'app.account.page_title_user'|trans }}{% endif %} + {% if tab == 'import' %}{{ 'app.import.tab'|trans }}{% elseif tab == 'account' %}{{ 'app.account.page_title_account'|trans }}{% else %}{{ 'app.account.page_title_user'|trans }}{% endif %} {% endblock %} {% block body %} @@ -23,6 +23,31 @@ changeLabel: {{ 'app.account.change_label'|trans|json_encode|raw }}, cancelLabel: {{ 'app.account.cancel_label'|trans|json_encode|raw }}, errorGeneric: {{ 'app.account.error_generic'|trans|json_encode|raw }}, + importAnalyze: {{ 'app.import.btn_analyze'|trans|json_encode|raw }}, + importAnalyzing: {{ 'app.import.btn_analyzing'|trans|json_encode|raw }}, + importExecute: {{ 'app.import.btn_execute'|trans|json_encode|raw }}, + importExecuting: {{ 'app.import.btn_executing'|trans|json_encode|raw }}, + importSuccess: {{ 'app.import.success'|trans|json_encode|raw }}, + importConfirm: {{ 'app.import.confirm'|trans|json_encode|raw }}, + importErrorNoFile: {{ 'app.import.error_no_file'|trans|json_encode|raw }}, + importLabelClients: {{ 'app.import.label_clients'|trans|json_encode|raw }}, + importLabelProjects: {{ 'app.import.label_projects'|trans|json_encode|raw }}, + importLabelServices: {{ 'app.import.label_services'|trans|json_encode|raw }}, + importLabelEntries: {{ 'app.import.label_entries'|trans|json_encode|raw }}, + importLabelUsers: {{ 'app.import.label_users'|trans|json_encode|raw }}, + importLabelDateRange: {{ 'app.import.label_date_range'|trans|json_encode|raw }}, + importLabelWarnings: {{ 'app.import.label_warnings'|trans|json_encode|raw }}, + importUserMatched: {{ 'app.import.user_matched'|trans|json_encode|raw }}, + importUserFallback: {{ 'app.import.user_fallback'|trans|json_encode|raw }}, + importNewLabel: {{ 'app.import.new_label'|trans|json_encode|raw }}, + importExistingLabel: {{ 'app.import.existing_label'|trans|json_encode|raw }}, + importResultClients: {{ 'app.import.result_clients'|trans|json_encode|raw }}, + importResultProjects: {{ 'app.import.result_projects'|trans|json_encode|raw }}, + importResultServices: {{ 'app.import.result_services'|trans|json_encode|raw }}, + importResultEntries: {{ 'app.import.result_entries'|trans|json_encode|raw }}, + importResultUsers: {{ 'app.import.result_users'|trans|json_encode|raw }}, + importUserAssignMe: {{ 'app.import.user_assign_me'|trans|json_encode|raw }}, + importUserCreate: {{ 'app.import.user_create'|trans|json_encode|raw }}, }, }; @@ -31,7 +56,7 @@ diff --git a/httpdocs/templates/report/statistics.html.twig b/httpdocs/templates/report/statistics.html.twig index 22adea1..3ef62f9 100644 --- a/httpdocs/templates/report/statistics.html.twig +++ b/httpdocs/templates/report/statistics.html.twig @@ -27,6 +27,8 @@ services: {{ 'app.statistics.services'|trans|json_encode|raw }}, rest: {{ 'app.statistics.rest'|trans|json_encode|raw }}, noService: {{ 'app.statistics.no_service'|trans|json_encode|raw }}, + totalHours: {{ 'app.statistics.total_hours'|trans({'%hours%': '__HOURS__'})|json_encode|raw }}, + totalRevenue: {{ 'app.statistics.total_revenue'|trans({'%revenue%': '__REVENUE__'})|json_encode|raw }}, } }; @@ -84,6 +86,8 @@ + +
diff --git a/httpdocs/translations/messages.de.yaml b/httpdocs/translations/messages.de.yaml index baff9fc..5e0d59f 100644 --- a/httpdocs/translations/messages.de.yaml +++ b/httpdocs/translations/messages.de.yaml @@ -327,6 +327,43 @@ app: interval_half: "Halbe Stunde" interval_hour: "Stunde" + import: + tab: "Daten-Import" + title_mite: "mite-Import" + desc_mite: "Importiere Kunden, Projekte, Leistungen und Zeiteinträge aus einem mite-Backup (XML). Einträge werden zu den bestehenden Daten hinzugefügt — ein doppelter Import erzeugt doppelte Einträge." + upload_text: "XML-Datei auswählen oder hierher ziehen" + upload_hint: "mite-Backup im XML-Format, max. 20 MB" + btn_analyze: "Datei analysieren" + btn_analyzing: "Wird analysiert…" + btn_execute: "Import starten" + btn_executing: "Wird importiert…" + btn_reset: "Abbrechen" + title_preview: "Vorschau" + title_result: "Ergebnis" + confirm: "Bist du sicher? Die Einträge werden zu den bestehenden Daten hinzugefügt. Ein erneuter Import erzeugt Duplikate." + success: "Import erfolgreich abgeschlossen." + error_no_file: "Bitte eine XML-Datei auswählen." + error_file_too_large: "Die Datei ist zu groß (max. 20 MB)." + error_invalid_format: "Nur XML-Dateien werden unterstützt." + label_clients: "Kunden" + label_projects: "Projekte" + label_services: "Leistungen" + label_entries: "Zeiteinträge" + label_users: "Benutzer-Zuordnung" + label_date_range: "Zeitraum" + label_warnings: "Hinweise" + user_matched: "zugeordnet zu" + user_fallback: "wird dir zugeordnet" + user_assign_me: "mir zuordnen" + user_create: "User anlegen" + new_label: "neu" + existing_label: "vorhanden" + result_clients: "Kunden angelegt" + result_projects: "Projekte angelegt" + result_services: "Leistungen angelegt" + result_entries: "Zeiteinträge importiert" + result_users: "Benutzer angelegt" + forgot_password: page_title: "Passwort vergessen – spawntree" subtitle: "Gib deine E-Mail ein und wir schicken dir einen Reset-Link." @@ -469,6 +506,8 @@ app: services: "Leistungen" rest: "Rest" no_service: "Ohne Leistung" + total_hours: "Gesamt: %hours%" + total_revenue: "Gesamt: %revenue%" stopwatch: title: "Stoppuhr"