# Invoice print setup This guide creates the invoice tables and Steel calculations through the gRPC API, installs the Typst template used by the print page, inserts example data, and compiles the exported JSON into a PDF. Run the commands from the repository root. The server address used by the client is `[::1]:50051`. ## 1. Configure grpcurl ```bash PROFILE="fakturacia_2026" GRPC_ADDR="[::1]:50051" ``` Log in. Keep `-d` and its JSON argument on the same logical command line: ```bash grpcurl -plaintext \ -import-path common/proto \ -proto auth.proto \ -d '{"identifier":"YOUR_USERNAME","password":"YOUR_PASSWORD"}' \ "$GRPC_ADDR" \ komp_ac.auth.AuthService/Login ``` Copy the returned `access_token`: ```bash TOKEN="PASTE_ACCESS_TOKEN_HERE" AUTH_HEADER="authorization: Bearer $TOKEN" ``` The account must have the administrator role for table and script creation. Table definitions and Steel scripts require an `admin` token. Row data writes require an `accountant` token. Keep the two tokens in separate shell variables, for example: ```bash ADMIN_TOKEN="PASTE_ADMIN_ACCESS_TOKEN_HERE" ACCOUNTANT_TOKEN="PASTE_ACCOUNTANT_ACCESS_TOKEN_HERE" ADMIN_AUTH_HEADER="authorization: Bearer $ADMIN_TOKEN" ACCOUNTANT_AUTH_HEADER="authorization: Bearer $ACCOUNTANT_TOKEN" ``` Use `ADMIN_AUTH_HEADER` for the table-definition and table-script calls. Use `ACCOUNTANT_AUTH_HEADER` for the `TablesData/PostTableData` calls below. If you do not know an existing password, create a new administrator account on a trusted local development server. The registration endpoint currently accepts the requested role, so do not expose this unauthenticated endpoint to an untrusted network. ```bash grpcurl -plaintext \ -import-path common/proto \ -proto auth.proto \ -d '{ "username":"invoice_admin", "email":"invoice_admin@example.test", "password":"CHANGE_THIS_PASSWORD", "password_confirmation":"CHANGE_THIS_PASSWORD", "role":"admin", "timezone":"Europe/Bratislava", "phone_country":"SK" }' \ "$GRPC_ADDR" \ komp_ac.auth.AuthService/Register ``` Then log in with the same credentials and copy the returned token: ```bash grpcurl -plaintext \ -import-path common/proto \ -proto auth.proto \ -d '{"identifier":"invoice_admin","password":"CHANGE_THIS_PASSWORD"}' \ "$GRPC_ADDR" \ komp_ac.auth.AuthService/Login ``` The `Invalid credentials` response means the username/email or password is wrong. Sending only `{"identifier":"admin"}` cannot work because the login request still requires a valid password. A temporary `connection refused` means the server was not listening at that moment; retry after starting the server. ## 2. Create the tables Create referenced tables first. ### Supplier ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_definition.proto \ -d '{ "profile_name":"fakturacia_2026", "table_name":"dodavatel", "row_display_column":"nazov", "columns":[ {"name":"nazov","field_type":"TEXT"}, {"name":"ulica","field_type":"TEXT"}, {"name":"mesto","field_type":"TEXT"}, {"name":"ico","field_type":"TEXT"}, {"name":"dic","field_type":"TEXT"}, {"name":"icdph","field_type":"TEXT"}, {"name":"iban","field_type":"TEXT"}, {"name":"banka","field_type":"TEXT"}, {"name":"telefon","field_type":"TEXT"}, {"name":"email","field_type":"TEXT"} ] }' \ "$GRPC_ADDR" komp_ac.table_definition.TableDefinition/PostTableDefinition ``` ### Customer ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_definition.proto \ -d '{ "profile_name":"fakturacia_2026", "table_name":"odberatel", "row_display_column":"nazov", "columns":[ {"name":"nazov","field_type":"TEXT"}, {"name":"ulica","field_type":"TEXT"}, {"name":"mesto","field_type":"TEXT"}, {"name":"ico","field_type":"TEXT"}, {"name":"dic","field_type":"TEXT"}, {"name":"icdph","field_type":"TEXT"} ] }' \ "$GRPC_ADDR" komp_ac.table_definition.TableDefinition/PostTableDefinition ``` ### Invoice ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_definition.proto \ -d '{ "profile_name":"fakturacia_2026", "table_name":"faktura", "row_display_column":"cislo", "base_currency":"EUR", "links":[ {"linked_table_name":"dodavatel","required":true}, {"linked_table_name":"odberatel","required":true} ], "columns":[ {"name":"cislo","field_type":"TEXT"}, {"name":"datum_vystavenia","field_type":"DATE"}, {"name":"datum_dodania","field_type":"DATE"}, {"name":"datum_splatnosti","field_type":"DATE"}, {"name":"variabilny_symbol","field_type":"TEXT"}, {"name":"sposob_uhrady","field_type":"TEXT"}, {"name":"zaklad_dane","field_type":"MONEY","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true}, {"name":"dph_celkom","field_type":"MONEY","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true}, {"name":"celkom_spolu","field_type":"MONEY","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true}, {"name":"poznamka","field_type":"TEXT"} ] }' \ "$GRPC_ADDR" komp_ac.table_definition.TableDefinition/PostTableDefinition ``` ### Invoice item ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_definition.proto \ -d '{ "profile_name":"fakturacia_2026", "table_name":"polozka", "row_display_column":"nazov", "base_currency":"EUR", "links":[{"linked_table_name":"faktura","required":true}], "columns":[ {"name":"nazov","field_type":"TEXT"}, {"name":"mnozstvo","field_type":"DECIMAL(12,3)"}, {"name":"mj","field_type":"TEXT"}, {"name":"cena","field_type":"MONEY","rounding":"MONEY_ROUNDING_HALF_UP"}, {"name":"dph","field_type":"DECIMAL(5,2)"}, {"name":"bez","field_type":"MONEY","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true}, {"name":"dph_suma","field_type":"MONEY","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true}, {"name":"spolu","field_type":"MONEY","rounding":"MONEY_ROUNDING_HALF_UP","recompute_on_dependency_change":true} ] }' \ "$GRPC_ADDR" komp_ac.table_definition.TableDefinition/PostTableDefinition ``` ## 3. Get table IDs Use `table_definition.proto`, not `common.proto`, for this service call: ```bash TREE=$(grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_definition.proto \ -d '{}' "$GRPC_ADDR" \ komp_ac.table_definition.TableDefinition/GetProfileTree) echo "$TREE" | jq . FAKTURA_ID=$(echo "$TREE" | jq -r \ '.profiles[] | select(.name=="fakturacia_2026") | .tables[] | select(.name=="faktura") | .id') POLOZKA_ID=$(echo "$TREE" | jq -r \ '.profiles[] | select(.name=="fakturacia_2026") | .tables[] | select(.name=="polozka") | .id') ``` ## 4. Add Steel calculations The script API requires expressions beginning with `(`. Money values must be combined using the money functions; do not use ordinary decimal `+` on MONEY. ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_script.proto \ -d "{ \"table_definition_id\":$POLOZKA_ID, \"target_column\":\"bez\", \"script\":\"(money-mul (steel_get_column \\\"polozka\\\" \\\"cena\\\") (steel_get_column \\\"polozka\\\" \\\"mnozstvo\\\"))\", \"description\":\"Quantity multiplied by unit price\" }" \ "$GRPC_ADDR" komp_ac.table_script.TableScript/PostTableScript ``` ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_script.proto \ -d "{ \"table_definition_id\":$POLOZKA_ID, \"target_column\":\"dph_suma\", \"script\":\"(money-div (money-mul (steel_get_column \\\"polozka\\\" \\\"bez\\\") (steel_get_column \\\"polozka\\\" \\\"dph\\\")) \\\"100\\\")\", \"description\":\"VAT amount\" }" \ "$GRPC_ADDR" komp_ac.table_script.TableScript/PostTableScript ``` ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_script.proto \ -d "{ \"table_definition_id\":$POLOZKA_ID, \"target_column\":\"spolu\", \"script\":\"(money-add (steel_get_column \\\"polozka\\\" \\\"bez\\\") (steel_get_column \\\"polozka\\\" \\\"dph_suma\\\"))\", \"description\":\"Total including VAT\" }" \ "$GRPC_ADDR" komp_ac.table_script.TableScript/PostTableScript ``` Invoice aggregate scripts: ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_script.proto \ -d "{ \"table_definition_id\":$FAKTURA_ID, \"target_column\":\"zaklad_dane\", \"script\":\"(money-add (steel_related_aggregate \\\"sum\\\" \\\"polozka\\\" \\\"bez\\\" \\\"faktura\\\") (money-new \\\"EUR\\\" \\\"0\\\"))\" }" \ "$GRPC_ADDR" komp_ac.table_script.TableScript/PostTableScript ``` ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_script.proto \ -d "{ \"table_definition_id\":$FAKTURA_ID, \"target_column\":\"dph_celkom\", \"script\":\"(money-add (steel_related_aggregate \\\"sum\\\" \\\"polozka\\\" \\\"dph_suma\\\" \\\"faktura\\\") (money-new \\\"EUR\\\" \\\"0\\\"))\" }" \ "$GRPC_ADDR" komp_ac.table_script.TableScript/PostTableScript ``` ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto table_script.proto \ -d "{ \"table_definition_id\":$FAKTURA_ID, \"target_column\":\"celkom_spolu\", \"script\":\"(money-add (steel_related_aggregate \\\"sum\\\" \\\"polozka\\\" \\\"spolu\\\" \\\"faktura\\\") (money-new \\\"EUR\\\" \\\"0\\\"))\" }" \ "$GRPC_ADDR" komp_ac.table_script.TableScript/PostTableScript ``` ## 5. Insert data Insert supplier and customer rows first. The API returns their IDs. ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto tables_data.proto \ -d '{ "profile_name":"fakturacia_2026", "table_name":"dodavatel", "data":{ "nazov":"Kreatív Studio s.r.o.", "ulica":"Štúrova 8", "mesto":"040 01 Košice", "ico":"45612378", "dic":"2022998877", "icdph":"SK2022998877", "iban":"SK89 1100 0000 0026 1234 5678", "banka":"Tatra banka, a.s.", "telefon":"+421 911 222 333", "email":"fakturacia@kreativstudio.sk" } }' \ "$GRPC_ADDR" komp_ac.tables_data.TablesData/PostTableData ``` ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto tables_data.proto \ -d '{ "profile_name":"fakturacia_2026", "table_name":"odberatel", "data":{ "nazov":"Nová Kaviareň s.r.o.", "ulica":"Námestie SNP 12", "mesto":"974 01 Banská Bystrica", "ico":"51239876", "dic":"2120654321", "icdph":"SK2120654321" } }' \ "$GRPC_ADDR" komp_ac.tables_data.TablesData/PostTableData ``` Set the returned IDs: ```bash DODAVATEL_ID="1" ODBERATEL_ID="1" ``` Insert the invoice. Scripted totals are initially zero because it has no items yet. ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto tables_data.proto \ -d "{ \"profile_name\":\"fakturacia_2026\", \"table_name\":\"faktura\", \"data\":{ \"dodavatel_id\":$DODAVATEL_ID, \"odberatel_id\":$ODBERATEL_ID, \"cislo\":\"2026042\", \"datum_vystavenia\":\"2026-06-15\", \"datum_dodania\":\"2026-06-12\", \"datum_splatnosti\":\"2026-06-29\", \"variabilny_symbol\":\"2026042\", \"sposob_uhrady\":\"Prevodný príkaz\", \"zaklad_dane\":\"0\", \"dph_celkom\":\"0\", \"celkom_spolu\":\"0\", \"poznamka\":\"Ďakujeme za Vašu dôveru.\" } }" \ "$GRPC_ADDR" komp_ac.tables_data.TablesData/PostTableData ``` Set the returned invoice ID: ```bash FAKTURA_ROW_ID="PASTE_INVOICE_ID" ``` Insert invoice items. Because the item columns are scripted, include their calculated values in each request. ```bash grpcurl -plaintext -H "$AUTH_HEADER" \ -import-path common/proto -proto tables_data.proto \ -d "{ \"profile_name\":\"fakturacia_2026\", \"table_name\":\"polozka\", \"data\":{ \"faktura_id\":$FAKTURA_ROW_ID, \"nazov\":\"Návrh loga a vizuálnej identity\", \"mnozstvo\":\"1\", \"mj\":\"ks\", \"cena\":\"850.00\", \"dph\":\"20\", \"bez\":\"850.00\", \"dph_suma\":\"170.00\", \"spolu\":\"1020.00\" } }" \ "$GRPC_ADDR" komp_ac.tables_data.TablesData/PostTableData ``` Repeat the same request for additional items, changing the values. For the sample invoice, the calculated values are: | Item | `bez` | `dph_suma` | `spolu` | |---|---:|---:|---:| | Logo | 850.00 | 170.00 | 1020.00 | | Menu, quantity 2 | 240.00 | 48.00 | 288.00 | | Printed menus | 255.00 | 25.50 | 280.50 | | Interior photography | 260.00 | 52.00 | 312.00 | The resulting invoice totals are `zaklad_dane = 1605.00`, `dph_celkom = 295.50`, and `celkom_spolu = 1900.50`. ## 6. Install the Typst template The Linux client discovers templates in: ```text ~/.config/komp-ac/komp_ac_client/print_templates/ ``` Place the supplied files there with matching stems: ```bash mkdir -p ~/.config/komp-ac/komp_ac_client/print_templates cp inv2_generic.typ ~/.config/komp-ac/komp_ac_client/print_templates/faktura.typ cp inv2_generic.json ~/.config/komp-ac/komp_ac_client/print_templates/faktura.json ``` The JSON must contain the target paths used by the print mapper: ```text faktura.* dodavatel.* odberatel.* polozky[].* ``` Open the invoice row in the client, open the Print page, select `faktura.typ`, select the related `polozka` rows, and export. The client writes the generated JSON as `faktura.export.json` beside the template. ## 7. Compile the PDF ```bash typst compile \ --input data="$HOME/.config/komp-ac/komp_ac_client/print_templates/faktura.export.json" \ "$HOME/.config/komp-ac/komp_ac_client/print_templates/faktura.typ" \ faktura.pdf ``` The client currently exports the API-backed JSON and saves the document snapshot, but it does not invoke the Typst compiler itself. ## Deterministic five-person company simulation The repository also contains a standalone bootstrap that creates a separate `small_company_2026` profile, installs its Steel calculations, and pushes all fixture rows through the same gRPC APIs used by the client: ```text server/scripts/bootstrap_small_company.py ``` It expects the existing passwordless users `admin` and `filipko`. The admin token is used only for table definitions and scripts. The accountant token is used for every business-row insert. Preview the scope without contacting the server: ```bash python3 server/scripts/bootstrap_small_company.py --dry-run ``` Create and populate the profile: ```bash python3 server/scripts/bootstrap_small_company.py ``` If a run stops after creating some objects, resume from its checkpoint: ```bash python3 server/scripts/bootstrap_small_company.py --resume ``` The checkpoint defaults to `small_company_2026.bootstrap-state.json`. It contains table IDs, script IDs, and inserted row IDs, but no access tokens or passwords. The bootstrap does not delete an existing profile. If the profile already exists without its matching checkpoint, it stops instead of modifying unknown data. The fixed scenario includes five employees, customers, suppliers, products, CRM leads, sales orders, invoices and partial payments, purchase orders, supplier invoices, stock movements and positions, expenses, and service time entries. It verifies parent totals, outstanding balances, and stock values by reading the rows back after insertion.