1
0
Commit Graph

530 Commits

Author SHA1 Message Date
aminovfariz
5367449d80 fix(ci): run db:migrate before tests so settings table exists 2026-06-05 14:38:52 +02:00
aminovfariz
68407b944e fix(tests): remove appTitle/appUrl from AuthServicesFactory constructor calls 2026-06-05 14:27:28 +02:00
aminovfariz
c12d155e23 fix(tests): inject appTitle/appUrl into auth services to avoid DB calls in unit tests 2026-06-05 14:21:04 +02:00
aminovfariz
3f08f6189e fix(helpdesk): inject appTitle/appUrl into HandoverNotificationService to avoid DB calls in tests 2026-06-05 14:10:13 +02:00
aminovfariz
b8fc55bcc2 ci: start php container for QA gates 2026-06-05 14:02:57 +02:00
aminovfariz
6cfc8e7533 ci: use PHP 8.5 2026-06-05 14:00:55 +02:00
aminovfariz
2e26cce91f ci: run composer and QA gates directly on runner without Docker 2026-06-05 13:48:32 +02:00
aminovfariz
8e8ca4d5e7 ci: add GitHub Actions workflows 2026-06-05 13:41:19 +02:00
aminovfariz
c8cf2892c9 ci: trigger pipeline after runner fix with node on host
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 12:09:46 +02:00
aminovfariz
1ae3d5d984 ci: trigger pipeline after runner workspace mount fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 12:01:13 +02:00
aminovfariz
2e316b2901 ci: trigger pipeline after switching runner to host mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:59:33 +02:00
aminovfariz
925f8ec87e fix(ci): debug mounts via hostname inspect
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:54:12 +02:00
aminovfariz
0486ebf2a2 fix(ci): debug runner container mounts to find host workspace path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:52:22 +02:00
aminovfariz
1d1f8479b6 fix(ci): set COMPOSE_PROJECT_NAME=breadcrumb-ci at job level for stable container names
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:50:01 +02:00
aminovfariz
70d431cff5 fix(ci): install composer directly in runner container, not via docker run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:48:26 +02:00
aminovfariz
07cd2dd756 fix(ci): add path debug before composer install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:46:58 +02:00
aminovfariz
9668a23797 fix(ci): use GITHUB_WORKSPACE instead of pwd for composer docker run volume
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:46:19 +02:00
aminovfariz
17ae333a39 fix(ci): install composer deps via docker run before compose up, force-recreate containers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:45:31 +02:00
aminovfariz
5df8abceaf fix(ci): force recreate containers with docker compose down before up
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:44:08 +02:00
aminovfariz
b0355791bb fix(ci): add container state diagnostics before composer install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:43:16 +02:00
aminovfariz
a2ea9f385d fix(ci): debug - ls /var/www to find why composer.json not found
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:42:23 +02:00
aminovfariz
03cdfa7702 fix(ci): wait for php container and use explicit workdir for composer install
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:41:51 +02:00
aminovfariz
3dca38d1c2 fix(ci): run composer install inside php container before QA gates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:40:28 +02:00
aminovfariz
5e8273b952 fix(ci): start only php+db+memcached in CI, skip nginx (file mount fails in runner)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:39:45 +02:00
aminovfariz
d47e45e9f8 fix(ci): create .env from .env.example before docker compose up in CI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:32:59 +02:00
aminovfariz
0e70e50abf feat(ci): add Gitea Actions deploy pipeline and fix prod image module assets
- Add .gitea/workflows/deploy.yaml: QA → build prod image → SCP to server → docker load + compose up + module:sync
- Fix Dockerfile prod stage: copy module web assets directly from modules/*/web/ instead of resolving symlinks (symlinks are excluded via .dockerignore and break on Windows/CI)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 11:22:47 +02:00
aminovfariz
9caa771a73 feat(helpdesk): add delete button on edit page, fix flash notifications
- Edit page: managers see 'Delete handover' button in aside with confirm dialog
- Delete handler: POST action=delete → deleteByIds → redirect to list
- Flash scope set to null (global) so toast shows reliably after redirect
  regardless of locale prefix or query string in URL
- i18n: added Delete handover / Delete this handover? / Handover deleted (de+en)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 15:51:36 +02:00
aminovfariz
1caa198286 feat(helpdesk): rework handover tabs and assign flow
Tabs: replace open/closed with active (draft+in_progress+under_review)
and done (completed+archived) — records now appear in the correct tab

Create wizard assign mode: redirect to list with flash message after
assigning instead of opening edit page

i18n: add In progress / Done tab labels and wizard strings (de + en)

Nikita Soldatov note: user does not exist in DB — needs to be created
via admin user management

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 15:23:31 +02:00
aminovfariz
47a26d0ca6 test(helpdesk): add tests for assign, submitForReview, resendNotification and HandoverNotificationService
HandoverServiceTest: 30 new cases covering:
- assign(): happy path, draft→in_progress transition, permission check,
  zero assignedTo, not found, email sent, due date passed to repo
- submitForReview(): assignee and manager paths, non-assignee denied,
  wrong status (under_review/completed), revision created, email sent
- resendNotification(): happy path, permission denied, no assignee
- statusLabel/Variant for under_review

HandoverNotificationServiceTest: 9 new cases covering:
- notifyAssigned: sends correct template+vars, skips on empty/null email,
  fallback dash for empty debitor and due_date
- notifyReviewRequested: sends to manager, skips on no email,
  fallback dash for empty assignee name

48 tests total, all passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 14:59:10 +02:00
aminovfariz
88bd7caae5 feat(helpdesk): rework handover create wizard with mode selection
- Managers now see step 1: choose 'Create myself' or 'Assign to employee'
- Assign mode: select customer+product+employee → creates record immediately,
  sends invitation email, skips protocol step
- Self mode: existing 2-step flow unchanged
- Fix open/closed tab: pass view param in dataUrl so grid filters correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 14:50:30 +02:00
aminovfariz
1c1e82b352 fix(helpdesk): handle nested table key from DB::select in user dropdowns
MintyPHP DB returns rows as $row[$table][$field], so tenant users
come back as $u['u']['id'] not $u['id']. Added fallback for both.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 14:41:48 +02:00
aminovfariz
3f56f3f279 fix(helpdesk): replace Guard::hasAbility() with AuthorizationService in create wizard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 14:38:02 +02:00
aminovfariz
c32a0f8c31 feat(helpdesk): Übergabe assignment workflow
- New statuses: under_review
- New DB fields: assigned_to, assigned_by, assigned_at, due_date (migration 012)
- HandoverNotificationService: emails on assign and review-request
- HandoverService: assign(), submitForReview(), resendNotification()
- Assign page: manager selects employee + due date, resend notification
- Create wizard: manager can assign during creation (step 1)
- Edit page: "Submit for review" button for assignee, assign link for manager
- List page: open/closed tabs, non-managers see only their assigned handovers
- Email templates: handover_assigned + handover_review_requested (de/en)
- i18n keys added for de and en

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 14:35:03 +02:00
aminovfariz
4f38911fce fix(docker): copy module web assets as real files in prod image
web_init uses cp -rp which copies symlinks as symlinks.
Since the nginx container doesn't have /var/www/modules/,
symlinks in web/modules/ break. Replace them with actual copies at build time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 11:50:45 +02:00
aminovfariz
0d078f233d fix: suppress open_basedir warning in favicon check, disable open_basedir in prod
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:07:47 +02:00
aminovfariz
ec596d35d4 fix(auth): extend stream-based HTTP to POST requests in OidcHttpGateway
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 13:14:19 +02:00
aminovfariz
4e51d9e882 fix(auth): use file_get_contents for OIDC GET requests to work around libcurl 8.14/OpenSSL 3.5 TLS fingerprint rejection by Microsoft
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 11:45:52 +02:00
aminovfariz
293807b92a chore: add .dockerignore for production image builds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 09:31:36 +02:00
aminovfariz
1b615a5e1c fix: update local docker and php config 2026-05-26 11:04:57 +02:00
aminovfariz
cb2f429e0e feat(helpdesk): add empty Dashboard page with sidebar nav item
Adds helpdesk/dashboard route, empty page/view, Overview nav group in the
sidebar, and Dashboard i18n keys (de + en). No content yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:01:35 +02:00
fs
873b75465e docs: important name change 2026-04-29 10:00:36 +02:00
fs
0a86d1b44b docs: Readme changes 2026-04-29 09:59:16 +02:00
fs
545bcc789b docs: replace ASCII umlaut fallbacks with proper äöüß across all .md
Most German docs were written with `ue`/`ae`/`oe`/`ss` ASCII fallbacks
(`fuer`, `aenderung`, `koennen`, `gemaess`) — relic from older toolchain
constraints. Replaced 237 occurrences across 38 .md files with proper
umlauts and ß so the docs read naturally.

Substitutions are constrained to plain prose: fenced code blocks
(```...```), inline code spans (`...`), markdown link targets `[..](..)`,
and HTML comments are preserved verbatim. Path references like
`docs/howto-erste-aenderung.md` keep their original (umlaut-replaced)
filenames — only the surrounding prose is normalised.

Tool: bin/fix-md-umlauts.py (idempotent — no-op on already-clean files).
Re-runnable if ASCII fallbacks creep back in via copy-paste.

Doc-link-check + doc-drift-check stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 09:57:22 +02:00
fs
2e73cb98b4 feat(console): add db:migrate CLI for idempotent core schema updates
Existing systems used to have no automatic mechanism for the
db/updates/*.sql idempotent updates after `git pull` — devs had to
remember which files were already applied and run new ones manually
with `mariadb < ...`. Mirror module:migrate to fix this:

- New `bin/console db:migrate` walks db/updates/*.sql in alphabetical
  order, skipping anything already recorded in the new core_migrations
  tracking table. Each file runs in its own transaction; failure
  rolls back so the file is retried on the next run.
- New `db:migrate --status` lists applied vs. pending files without
  running anything. Useful for ops debugging "schema seems stale"
  symptoms.
- module:sync now runs db:migrate as its first step, so the standard
  post-pull command stays a single invocation. README's 3-step setup
  is unchanged — module:sync now also covers core schema updates.

CoreMigrationRepository owns its own mysqli connection (using the
same DB credentials as MintyPHP\DB) and runs raw `$mysqli->query()`
instead of going through DB::query's prepared statement path —
MariaDB rejects some DDL (e.g. ALTER TABLE … ADD CONSTRAINT CHECK)
in the prepared protocol, which the existing 18 db/updates files
trip on. ModuleMigrationRepository is left untouched (no module
migration uses such DDL today and changing the vendor pattern is
out of scope).

End-to-end verified: against an existing DB the first run applies
all 19 idempotent files as no-ops and records them, second run
reports "all updates already applied". Against a freshly init'd
DB the same thing happens — no double work, no surprises. All
encrypted seed secrets keep decrypting because APP_CRYPTO_KEY
is unchanged.

Tests: tests/Console/CoreCommandsTest covers success/failure/status
output shapes against a FakeModuleRunner (mirror of the existing
ModuleCommandsTest pattern). 5 stale baseline entries in
phpstan-baseline removed (covered by the new @api annotation on
the test fixture class).

Docs: CLAUDE.md and docs/reference-cli-commands.md document the
new command + --status flag; docs/howto-fehlerbehebung.md gains
a "schema seems stale after git pull" section pointing at
db:migrate --status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:59:57 +02:00
fs
0c78dc4355 chore(setup): track config.php directly, slim README to setup checklist
config/config.php was gitignored and devs had to copy it from
config/config.php.example on every fresh clone. Since the bootstrap
config reads everything from .env via $envString() / $envInt() /
$envBool() defaults, there is nothing developer-specific in the file —
the cp step was dead ceremony.

- Remove config/config.php from .gitignore and track it directly
- Delete config/config.php.example
- Drop the example-existence + per-doc reference checks from
  bin/docs-drift-check.sh; add a legacy-pattern check that flags
  any new mention of the removed example file
- Update ConfigContractsTest to require config.php and forbid the
  example
- Clean stale references in CLAUDE.md, README.md, six docs files,
  and the core-guardrails skill

Same commit slims README down from 294 to 47 lines: one setup
checklist (now 3 commands instead of 4), the seeded login table,
the three quality-gate commands, and pointers to CLAUDE.md and
docs/index.md for everything else.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:37:45 +02:00
fs
f36b169f67 chore(seed): bake live test connections into init.sql + helpdesk migration
Fresh installs now come up with working dev test connections for
Microsoft SSO and Business Central without manual configuration:

- init.sql appends the live breadcrumb + icoreon tenants (idempotent
  by uuid), Felix as a 6th user with all five roles, the live
  departments, plus tenant_auth_microsoft (SSO enabled for
  breadcrumb), tenant_auth_ldap stubs, and the global Microsoft
  shared-app credentials. The MusterMandant demo seed is preserved
  as tenant 1.
- A new helpdesk migration 011 seeds the per-tenant BC connection
  rows in helpdesk_tenant_settings and the helpdesk.bc_* fallback
  settings — module-owned data stays in module migrations.
- .env.example sets APP_CRYPTO_KEY so the AES-256-GCM blobs in the
  seed (Microsoft client secret, BC basic auth passwords) actually
  decrypt out-of-the-box. Verified by decrypting all four blobs
  end-to-end through Crypto::decryptString.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:27:47 +02:00
fs
a5213977ca fix(helpdesk): drop stale require of removed app-flash partial
Commit 4890a28 folded templates/partials/app-flash.phtml into
app-toast-stack.phtml, which the base layout now mounts itself. 14
helpdesk views still required the deleted partial and crashed every
helpdesk page with a 500 (Failed opening required app-flash.phtml).
Remove the redundant requires; the unified toast stack continues to
render flash messages via the layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 07:59:51 +02:00
fs
6146b0bd00 test(architecture): catch missing container bindings statically
Codifies the rule that every class referenced via `$c->get(X::class)`
inside a container registrar must itself be bound via
`$container->set(X::class, ...)` somewhere — in the same registrar,
in core/App/registerContainer.php, or in a module container
registrar.

Catches the bug pattern from cd2c9c5: a registrar's factory closure
pulled UserSettingsGateway via $c->get() but no matching ->set() ever
ran. Unit tests with mocked dependencies passed; the failure only
surfaced at runtime ("Service not bound: ...") when the affected
admin/settings/user-lifecycle page tried to construct the dashboard
service against the live container.

Implementation is purely static — token / regex parsing of registrar
files plus class_exists() / interface_exists() gating to avoid false
positives on non-class identifiers. No factory invocation, so the
test does not require DB / session / HTTP and is fast.

Verified by reverting the cd2c9c5 binding line in the working tree
and running the test — it produces the exact "UserSettingsGateway
referenced in core/App/Container/Registrars/UserRegistrar.php"
finding. Restored after the dry-run.

Allowlist starts with two framework-bootstrap classes (AppContainer,
ModuleRegistry) that are bound outside the registrar mechanism.
Each future addition needs an inline justification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 08:54:21 +02:00
fs
ca13fcd4a3 chore(i18n): drop user-lifecycle translation keys orphaned by cockpit cleanup
Seven translation keys were orphaned by the recent user-lifecycle
cockpit work (commits 144d841, cc2cf3a, 157eb18) and now have zero
callers in pages/, modules/, or core/:

* "Run lifecycle now" — was the <details>-summary title
* "Run user lifecycle now" — was the <details>-button label
* "This runs the lifecycle policy immediately and may deactivate or
  delete users." — was the <details>-blockquote warning
* "User lifecycle logs" — was the embedded panel's title
* "Purge user lifecycle logs" — was the panel's purge-button label
* "Purge entries older than 365 days?" — was the panel's purge confirm
* "Lifecycle audit entry not found" — was the deleted view($id).php
  flash key

The aside-actions partial uses different, still-active keys
("Run policy now", "Purge logs", "Run user lifecycle now?") so no
visible string lost. Removed from all four i18n files
(core + audit-module, de + en) so translation parity stays balanced
and the architecture-test "i18n parity" check stays green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 08:44:17 +02:00
fs
cd2c9c5741 fix(user-lifecycle): bind UserSettingsGateway in container
Previous commit 06118c1 wired UserLifecyclePolicyDashboardService to
$c->get(UserSettingsGateway::class) without ensuring the gateway
itself was a container service. The gateway is built by
UserServicesFactory::createUserSettingsGateway() and was previously
only obtained transitively as a constructor argument of other
factory-built services — never resolved through the container
directly.

Result at runtime: 500 with "Service not bound:
MintyPHP\\Service\\User\\UserSettingsGateway" the first time the
user-lifecycle settings page tried to construct the dashboard
service.

Adds the missing binding in UserRegistrar — same factory delegation
the file uses for UserAccountService, UserAssignmentService, etc.

Unit tests didn't catch this because they construct the dashboard
service with mocked dependencies; only the live page touched the
real container path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 21:30:34 +02:00