Integration with project/issue tracker to ease management. The worklogs and projects are synced from a project tracker (e.g. Leantime).
The project consists of the following parts:
- Invoices: Create invoices for projects and clients. These can consist of manual invoice entries and invoice entries created from worklogs.
- Project Billing: Automatically create invoices from a project for a given period.
- Planning: Overview of planned work for the coming sprints.
- Sprint Report: Detailed overview of the work for a given project/version.
- Projects: Overview of which projects to work with in the system.
- Project Creator: Create a new project in the Project Tracker.
graph TD;
ProjectBilling-->Invoice;
ProjectBilling-->Project;
Invoice-->Project;
Invoice-->Client;
Invoice-->InvoiceEntry;
InvoiceEntry-->Worklog;
Issue-->Worklog;
Issue-->Project;
Issue-->Account;
Client-->Account;
The system is build around Invoices. Each invoice is connected to a project and consists of invoice entries. The output is a .csv file.
Projects, clients, accounts, issues and worklogs are synchronized from a project tracker.
Invoice entries can be manual entries or connected to a number of worklogs from a project tracker.
Project billing can create a number of invoices for a given project for a given project. Only issues from the project tracker that have a connected account will be included. This is used to create invoices for the Support project, where issues are billed to different accounts.
Economics depends on data from external systems. The integrations with external systems are called Data Providers.
Each Data Provider integration should implement App\Interface\DataProviderInterface. The only
implementation today is App\Service\LeantimeApiService, which pulls from the
data-api plugin running in Leantime.
Synchronization is a paged pull driven by Symfony Messenger: a command dispatches one message per entity type, each message fetches a page of at most 100 rows and re-dispatches itself for the next page, and every fetched row becomes an upsert message of its own.
See docs/leantime-sync.md for the full picture, the scheduled jobs and the command options.
Cron jobs on the server run the sync commands — see the cron section of
.woodpecker/prod_itk_economics.yml. Supervisor is added in docker-compose.server.override.yml to
keep a worker consuming the async transport.
In development the job queue should be run manually.
task messengerJobs can be queued manually.
# Everything modified within the last hour.
task phpfpm -- bin/console app:data-providers:sync-modified
# Entities deleted within the last hour.
task phpfpm -- bin/console app:data-providers:sync-deleted
# A full sync of a single entity type, as async jobs, ignoring modified timestamps.
task phpfpm -- bin/console app:data-providers:sync -j -p -dJobs can also be queued in the admin interface in the bottom left corner.
Getting started — Taskfile.yml wraps the container commands, so install
go-task (brew install go-task) and run:
task setupThat creates the frontend network, pulls the images, installs the npm and Composer dependencies,
starts the stack and runs the migrations. Run task to list every available task.
Create .env.local with the following values set:
###> itk-dev/openid-connect-bundle ###
USER_OIDC_METADATA_URL=<VALUE>
USER_OIDC_CLIENT_ID=<VALUE>
USER_OIDC_CLIENT_SECRET=<VALUE>
USER_OIDC_REDIRECT_URI=https://economics.local.itkdev.dk/openid-connect/generic
USER_OIDC_ALLOW_HTTP=<VALUE>
USER_OIDC_LEEWAY=<VALUE>
###< itk-dev/openid-connect-bundle ###
APP_INVOICE_SUPPLIER_ACCOUNT=<VALUE>
APP_INVOICE_EXTERNAL_RECEIVER_ACCOUNT=<VALUE>
APP_INVOICE_DESCRIPTION_TEMPLATE=<VALUE>The project tracker connection is not configured through the environment. Each integration is a
DataProvider row holding its own URL and token, created with:
task phpfpm -- bin/console app:data-provider:createVisit /admin/project and "include" the projects that should be synchronized in the installation.
Then synchronize:
task phpfpm -- bin/console app:data-providers:syncRun app:data-providers:sync --help for the per-entity flags (-p projects, -r workers,
-s versions, -i issues, -w worklogs).
The node container will watch for code changes in the assets folder and
recompile.
Use
task compose -- logs --tail 0 --follow nodeto see the compilation log, e.g. to detect errors.
Each PR is reviewed with Github Actions.
Check coding standards with:
# Check coding standards for php and twig
task coding-standards:php:check
# Check coding standards for assets and markdown
task coding-standards:js:checkApply coding standards with:
# Apply coding standards for php and twig
task coding-standards:php:apply
# Apply coding standards for assets and markdown
task coding-standards:js:applytask prepare-code runs the whole set — Composer normalization, coding standards, static analysis
and the tests — and is the check to run before pushing.
We use PHPStan for static code analysis, at level 8 over src and tests:
task code-analysisPre-existing errors are recorded in phpstan-baseline.neon. New code is expected to be clean rather
than added to the baseline.
The test setup follows the guidelines from: https://symfony.com/doc/current/testing.html.
To run tests:
task test
# a single file
task test:file -- tests/Unit/Service/LeantimeApiServiceTest.php
# fail if line coverage drops below the threshold, as CI does
task test:coverage:checktests/bootstrap.php rebuilds the test database on every run — it clears the cache, then drops,
creates and migrates the database and loads App\DataFixtures\AppFixtures. Tests are not wrapped in
transactions, so a test that writes is responsible for cleaning up after itself.
Build the assets locally
task assets:buildCopy the /public/build folder to the server.
docker compose up --detach
docker compose exec phpfpm composer install --no-dev --classmap-authoritative
docker compose exec phpfpm bin/console doctrine:migrations:migrateRun synchronization with a cron process with a given interval to synchronize with the project tracker:
bin/console app:data-providers:syncWe need an initial product import to get going. Use
task phpfpm -- bin/console app:products:import «CSV filename»to import from a CSV file.
The CSV must contain the following headers:
id,name,price,project.id,project.name