Files
komp_ac/invoice-print-setup.md
2026-07-21 22:20:20 +02:00

495 lines
15 KiB
Markdown

# 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.