Treziqo v1.7 — Try it free, plan precisely, confirm quickly
The seventh release changes three things that touch your daily routine: sign-up now comes with a 14-day trial without payment details, the liability payment schedule handles refixation and per-diem calculation of the first installment (accuracy against the bank < 1%), and recurring payments in "Remind only" mode are collected in a new inbox with quick confirmation and the option to adjust the actual date and amount.
✨ 14 days free — sign-up without payment details
Pricing for new users is changing:
| Plan |
CZK |
EUR |
USD |
| Trial (first 14 days) |
free |
free |
free |
| Basic |
69 Kč / mo |
€2.99 / mo |
$3.49 / mo |
| Pro |
149 Kč / mo |
€5.99 / mo |
$6.99 / mo |
- After sign-up you get 14 days free — no card, no PayPal. Just click and you can do everything.
- Early activation — at any time during the trial, click "Activate plan" on the dashboard and confirm PayPal. You don't lose your remaining free days — the PayPal trial cycle additionally runs 14 days, so you can have your first ~28 days with us covered without being charged.
- When the trial expires without activation — the account is suspended (your data stays), and the app asks you to confirm payment. After activation you pick up right where you left off.
- Existing subscribers stay on the original price (49/99 Kč). A loyalty bonus.
In the /admin/users administration you see each user's trial status (days remaining / expired / has a PayPal subscription / no trial) as a micro-badge below the plan badge.
📅 Liability payment schedule — precision and flexibility
We introduced the amortization plan back in v1.6. v1.7 brings it to production quality:
Per-diem calculation of the first installment
Banks usually charge interest from the drawdown date, not from the signing of the contract — and the first period is often shorter than a full month. A standard annuity calculates it as 1/12 of a year, which means a systematic deviation of hundreds of crowns for loans taken out mid-month.
New fields when editing a liability:
- Drawdown date — the day the money physically arrived (typically 1–3 days after signing). If you leave it empty, it falls back to the signing date.
- Day-count convention —
act/365 (default, most Czech loans) / act/360 / 30/360. You'll find it in your contract in the "interest" section.
The algorithm calculates the very first installment as interest = balance × rate × days / N; the second and later installments use the standard annuity. When the plan is regenerated (with paid rows already present), per-diem isn't applied again — the balance already reflects the previous payments.
Example: ČSOB loan 1 M @ 4.99% p.a., signed March 19, first installment April 14.
- Before the fix: interest 4,158 Kč (−805 Kč off from reality)
- After the fix: interest 3,418 Kč (−64 Kč off from reality) — the error dropped by 92%
Interest rate changes (refixation, refinancing)
Mortgages typically change both the rate and the installment when the fixation period ends. Instead of regenerating the whole plan with new parameters, you now add a rate change with an effective_from date:
In the liability detail, the "Interest rate changes" section:
- A table of existing changes (date + rate + new installment + note, delete)
- Add form: effective date, new rate (% p.a.), new monthly installment, optional note
For each installment in the plan, the algorithm then picks the latest rate change with effective_from ≤ due_date, otherwise the default from the liability edit. After adding one, click "Regenerate future installments" — installments before effective_from run at the old rate, and from that date on at the new one.
Plan from the start + mark past installments as paid
For a liability you've been repaying for a while and want to catch up on retroactively:
- "Plan from the start…" → a modal with the checkbox "Mark past installments as paid"
- If you check it (and the liability has a linked account) → a real payment is created for each past installment (Transfer + Transaction), the money is deducted from the account, and the remaining principal is reduced
- If you don't check it → past installments stay as planned/overdue (in red), and you pay them manually via "Pay"
Race-safe: the pre-check of existing payments runs inside a DB transaction with lockForUpdate on the liability — a double submit from the browser won't create duplicate payments.
Editing an individual row in the plan
Every row in the schedule (except skipped ones) now has an edit icon. Clicking it opens a modal:
- Due date
- Principal (the only editable amount)
- Interest = read-only, auto-derived as
total − principal − fee
- Fee
- Total = read-only (the bank deducts a fixed installment; only the internal split changes)
For paid rows, the linked LiabilityPayment, Transaction.transaction_date/amount, and current_principal (by the difference in principal) are synchronized, with automatic status PAID_OFF ⇄ ACTIVE.
Automatic creation of a recurring payment
After the payment schedule is generated, a RecurringItem is created automatically with target_kind=liability_payment:
- Monthly frequency, payment day = the liability's
installment_day, anchor = the first planned row
- Amount = monthly_installment, source = liability.account_id
- Mode defaults to
remind_only (the user confirms via the inbox)
When an occurrence is generated, a matching planned installment is looked up in the schedule (±3 days + amount ±0.5 Kč) — if it matches, the exact annuity split (principal/interest/fee) is used, the installment is marked paid, and current_principal goes down only by the principal (not the whole amount). Fallback (no match): a custom payment outside the plan.
✅ Pending confirmations inbox (/recurring/inbox)
Recurring payments in "Remind only" mode generate PLANNED occurrences that wait for you to confirm reality. Instead of going through each recurring item separately, you now have a central inbox:
- Topbar badge (next to the dark mode toggle) shows the number of pending items. Click → /recurring/inbox.
- "Confirm pending" button with a count badge on
/recurring.
- Grid of cards sorted by due_date (overdue at the top, in red):
- Tint per target_kind (investment green, liability red, transfer blue)
- Item icon + name + account(s, with an arrow for transfers)
- Expected amount on the right
- Clicking "Confirm" expands an inline form: actual date (default = due_date), actual amount (default = expected), optional note
- Submit creates a Transaction via service.executeCycle (respecting target_kind) and marks the occurrence CONFIRMED
- Skip action next to Confirm for cycles that won't happen (refinancing, one-off exceptions)
🔁 Recurring payments — a new type and visual distinction
Internal transfer (transfer between your own accounts)
A new target_kind=internal_transfer for the typical "salary from the company to personal" or "automatic saving from checking to savings" scenario. Instead of two recurring items (expense + income), a single Transfer record with from_account + to_account + amount.
The card in /recurring shows both accounts with an arrow and a 2-column amount grid — −amount on the source account, +amount on the target. It's visually clear where the money goes.
V1 is same-currency only. Cross-currency (CZK → EUR between your own accounts) is postponed to V2 — it requires a daily exchange rate lookup.
Visual distinction of cards
Cards in /recurring have a light colored tint by target_kind:
- 🟢 Investment (deposit) — emerald tint
- 🔴 Liability (payment) — rose tint
- 🔵 Transfer between accounts — blue tint
- ⚪ Regular expense/income — no tint
Dark mode variants keep a subtle opacity (900/20 → 30 hover) instead of light (50/60 → 100), so the tint is recognizable but doesn't dominate on the slate-950 background.
📰 News feed and logo branding
- Public news feed at
/news — all published news grouped by month, full body, no auth. Plus the 8 newest in the login sidebar carousel.
- Admin CRUD for news in
/admin/news — text management with CS+EN per-row content, draft / publish toggle, ordering via sort_order.
- Logo from the admin upload everywhere:
- Transactional email headers get a 48×48 logo (from the
/admin/general upload)
- PWA install icon (Android Chrome / iOS "Add to Home Screen") via a dynamic
/manifest.json endpoint
- Favicon + apple-touch-icon in all layouts (login, register, news, landing, dashboard) via
<link rel="icon" href="logoUrl()">
- A logo change in
/admin/general takes effect within an hour (Cache-Control 1h) — no redeploy
🗑️ Safer deletion of transactions from the trash
/transactions/trash now has bulk selection (a checkbox on each row + select-all) and a sticky toolbar with a "Delete selected" button. Clicking it doesn't delete right away — instead:
- The backend creates a one-time token (SHA-256 hash, 15-min expiry) and sends the plaintext by email
- You click the confirm link in the email → the server consumes the token and deletes the transactions atomically (incl. Transfer-pair handling)
Handy when you want to clean out the trash and also have an email audit trail. Single use + 15-min expiry + scoped to user_id (a token in someone else's email doesn't work).
🔧 Small things
- Account order in the transaction form —
/transactions/new on desktop now respects the user-defined sort_order from /accounts (mobile already did; now unified)
- Dynamic default prices — all pricing spots (landing, register, admin user-plan picker) read from the
plan_prices table. Single source of truth = /admin/plans. A price change by the admin shows up in the UI immediately without a redeploy.
- PayPal webhook: 400 Bad Request for an invalid signature / missing headers (previously 202). Clean code semantics: 200 ok, 202 benign probe, 400 client error (no retry), 500 our error (retry).
- Logout 419 fix — when a user leaves a tab open past the session lifetime and clicks logout, instead of "Page Expired" there's a smooth redirect to
/login.
- New "School" subcategory under Kids — for school attendance expenses (fees, supplies, lunches, field trips), separate from "Preschool".
⚠️ Changes and upgrade
What's available where
| Feature |
FREE |
BASIC |
PRO |
| 14-day free trial |
✅ |
✅ |
✅ |
| Payment schedule (per-diem, refixation, edit) |
— |
— |
✅ |
| Pending confirmations inbox |
✅ |
✅ |
✅ |
| Internal transfer recurring |
✅ |
✅ |
✅ |
| Bulk delete with email confirm |
✅ |
✅ |
✅ |
| News feed on /login + /news |
✅ |
✅ |
✅ |
What to do after the upgrade
Nothing. No migration tasks for existing users:
- If you have a liability with a payment schedule, you can add the drawdown date and day-count convention in the liability edit → click "Regenerate" → the plan is recalculated per-diem.
- If you want an auto-created recurring installment, generate the liability plan again — the RecurringItem is created automatically (idempotently, it doesn't clone an existing one).
- If you have recurring items in remind_only mode, you'll find the PLANNED occurrences in
/recurring/inbox.
For existing subscribers
If you signed up before v1.7 and have an active PayPal subscription, you keep paying the original price (49/99 Kč). A PayPal subscription is tied to the original billing plan — it won't be raised without you explicitly switching to the new plan.
🛠 Under the hood
- Data model: new columns
users.trial_ends_at, liabilities.{drawdown_date, day_count_convention}, recurring_items.{to_account_id, to_account_currency_id}. New tables liability_rate_changes, pending_bulk_actions, news_posts.
- Race condition fix in
LiabilityAmortizationService::generate(mode=full + markPastAsPaid) — pre-check of existing payments inside DB::transaction with lockForUpdate on the liability row. Previously two parallel requests both saw 0 payments before the first one committed and created duplicate simulated payments.
- Single source of truth for prices — the View Composer
PlanPrice::activeKeyedByPlanAndCurrency() injects $planPrices into the landing, register, checkout, admin.user-plan views. UserPlan::priceCzk() deprecated.
- PayPal billing plans — the admin created 6 new plans in PayPal Business (Basic/Pro × CZK/USD/EUR), all with a 14d trial cycle. The
set_v17_paypal_plan_ids migration writes the IDs into plan_prices.
- Defense-in-depth security audit (2026-04-25) —
Rule::in($accessibleAcIds) on from_account_currency_id in LiabilityController storePayment + storeDrawdown. No CRITICAL/HIGH vulnerabilities.
- Laravel 11 gotcha — the base
Controller has no middleware() method (moved to the HasMiddleware interface). A defense-in-depth fix in Admin\NewsPostController::__construct threw a 500; removed, the route-level admin group is enough.
- Idempotent migrations everywhere — v1.7 adds 9 new migrations (trial_ends_at, drawdown_date, day_count_convention, rate_changes, pending_bulk_actions, force_update_translations, set_paypal_ids, to_account_id on recurring, news_posts). All
hasTable/hasColumn guarded.
Thanks for using Treziqo. Send bugs and ideas to info@treziqo.com.