polishing of the backend
This commit is contained in:
450
invoice-print-setup.md
Normal file
450
invoice-print-setup.md
Normal file
@@ -0,0 +1,450 @@
|
||||
# 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.
|
||||
2
server
2
server
Submodule server updated: 79afc75211...33db98ce94
Reference in New Issue
Block a user