Modern Stack Migration
Target Stack
Section titled “Target Stack”| Layer | Technology |
|---|---|
| Frontend + SSR | SvelteKit on Cloudflare Pages |
| Backend / API | SvelteKit API routes (server-side) + Cloudflare Workers |
| Primary database | PocketBase (SQLite) — or Supabase/Neon for PostgreSQL |
| Language | TypeScript throughout |
| Auth | PocketBase built-in auth |
| File storage | Cloudflare R2 |
| PDF generation | Cloudflare Worker + @react-pdf/renderer or Puppeteer |
Overall Complexity: 8 / 10 — HIGH
Section titled “Overall Complexity: 8 / 10 — HIGH”AncoraERP is not a small CRUD app. It’s a 15+ year old multi-module ERP with deep Romanian fiscal compliance requirements. A full rewrite is a multi-year project. Below is a per-module assessment.
Module Complexity Breakdown
Section titled “Module Complexity Breakdown”quadrantChart
title Migration Complexity vs Business Value
x-axis Low Complexity --> High Complexity
y-axis Low Value --> High Value
quadrant-1 Migrate First
quadrant-2 Careful Planning
quadrant-3 Consider Dropping
quadrant-4 Hardest to Migrate
Master Data: [0.25, 0.70]
User Auth + Multi-Tenant: [0.45, 0.90]
Accounting: [0.75, 0.85]
Payroll: [0.80, 0.80]
Sales: [0.55, 0.95]
Purchasing: [0.50, 0.90]
Inventory: [0.45, 0.85]
Supplier Price Feeds: [0.30, 0.60]
BNR Integration: [0.20, 0.75]
ANAF e-Factura: [0.70, 0.95]
D394 Declaration: [0.85, 0.90]
PDF Generation: [0.60, 0.80]
Reports + Pivot: [0.65, 0.70]
Daily Operations: [0.35, 0.65]
Critical Challenges
Section titled “Critical Challenges”1. The DarkQS XML Form Engine — Rating: 9/10 Hardest
Section titled “1. The DarkQS XML Form Engine — Rating: 9/10 Hardest”The entire UI is driven by XML config files + Java servlet rendering. There is no standard component library. To replicate:
- Every form/grid screen needs to be rebuilt as a Svelte component
- ~200+ distinct UI screens in
ancoraerp/(each module subfolder) - The “saf.do”, “dsi.do”, “pivot.do” generic engines need equivalent SvelteKit pages
SvelteKit equivalent approach:
DarkQS concept → SvelteKit equivalent---------------------------------------------dsi.do?cod_formular=N → /routes/[module]/[entity]/+page.sveltesaf.do?cod_formular=N → /routes/[module]/[entity]/[id]/+page.sveltesaveForm.do → form actions (actions.ts)processActionRecords.do → server-side action handlerpivot.do → /routes/reports/pivot/+page.svelteprintModelPdf.do → /api/pdf/[template]/+server.tsexecutegeneralselect.jsp → /api/query/+server.ts (parameterized only!)2. 1,493 DB Migration Scripts — Rating: 7/10
Section titled “2. 1,493 DB Migration Scripts — Rating: 7/10”The database schema is well-evolved with 1,493 numbered SQL scripts. Full schema must be reverse-engineered.
Options:
- Keep PostgreSQL (Supabase/Neon) — import current schema directly, use Drizzle ORM for TypeScript types. This is strongly recommended over SQLite.
- PocketBase SQLite — NOT recommended for this use case. SQLite limitations:
- No stored procedures (
dm2_*functions must be rewritten in TypeScript) - Row-level locking issues under concurrent ERP use
- Multi-tenant = multiple SQLite files (messy)
- Poor support for complex JOIN + GROUP BY queries
- No
pg_dumpequivalent for large datasets
- No stored procedures (
Recommended DB stack:
Production: Supabase (PostgreSQL 15) or NeonDev: local PostgreSQL 15 (Docker)ORM: Drizzle ORM (TypeScript-native, excellent PostgreSQL support)Types: generated from schema with `drizzle-kit introspect`3. Multi-Tenant Auth Architecture — Rating: 6/10
Section titled “3. Multi-Tenant Auth Architecture — Rating: 6/10”Current: public.unitati registry → resolve bazadate → connect to tenant DB.
SvelteKit + PocketBase approach:
// Option A: Single PocketBase instance, company_id foreign key on every table// (simple but requires migrating separate DBs into one + DB-level isolation gone)
// Option B: Keep PostgreSQL multi-tenant, PocketBase only for auth/sessions// (PocketBase handles login + JWT, app connects to correct PG schema per tenant)
// Option C: PostgreSQL row-level security (RLS) — one DB, isolated by policy// (cleanest for Supabase, requires RLS policies on every table)Recommended: Option C (PostgreSQL + Supabase RLS) or Option B (PocketBase auth + PG data).
Auth flow in SvelteKit:
export const handle: Handle = async ({ event, resolve }) => { const jwt = event.cookies.get('pb_auth'); if (jwt) { const user = await pb.collection('users').authRefresh(); event.locals.user = user; event.locals.tenantDb = getTenantConnection(user.company_code); } return resolve(event);};4. Romanian Fiscal Compliance — Rating: 9/10
Section titled “4. Romanian Fiscal Compliance — Rating: 9/10”This is the hardest part. AncoraERP has deep legal integrations:
| Integration | Complexity | Notes |
|---|---|---|
| BNR exchange rates | LOW | Simple XML fetch → DB insert. Easy Cloudflare Worker. |
| ANAF CUI lookup | MEDIUM | REST/SOAP API still works. Wrap in /api/anaf/+server.ts. |
| e-Factura (XML invoices) | HIGH | Romania mandates XML e-invoices since 2024. Requires ANAF SPV OAuth + XML signing. |
| D394 VAT declaration | VERY HIGH | Monthly XML file with all invoice parties. Currently via DarkCloud. |
| SAF-T reporting | HIGH | New requirement since 2025. Large XML export. |
| Payroll declarations (D112) | HIGH | Monthly payroll XML to ANAF. |
e-Factura Cloudflare Worker example:
export default { async fetch(req: Request, env: Env) { const invoice = await req.json(); const xml = buildUBLXml(invoice); // UBL 2.1 format const signed = await signXml(xml, env.CERT); // XML DSig required const token = await getAnafToken(env); // OAuth 2.0 return await submitToAnafSpv(signed, token); }};5. PDF Generation — Rating: 6/10
Section titled “5. PDF Generation — Rating: 6/10”Current: Java-based PDF generation (printModelPdf.do), templates likely in JasperReports or custom.
Cloudflare approach:
- Cloudflare Workers: use
@cf/jsPDForpdf-lib(no browser, ~50ms CPU limit!) - For heavy PDFs: Cloudflare Browser Rendering API (Puppeteer) — render HTML → PDF
- Store output in R2, return signed URL
import { renderToBuffer } from '@react-pdf/renderer';import { InvoiceTemplate } from '$lib/pdf/InvoiceTemplate';
export const GET: RequestHandler = async ({ url, locals }) => { const id = url.searchParams.get('id'); const invoice = await locals.tenantDb.query.invoices.findFirst({ where: eq(invoices.id, id) }); const buffer = await renderToBuffer(<InvoiceTemplate invoice={invoice} />); return new Response(buffer, { headers: { 'Content-Type': 'application/pdf' } });};6. Background Jobs (Cron) — Rating: 5/10
Section titled “6. Background Jobs (Cron) — Rating: 5/10”Current cron runs on the Linux server. Cloudflare Pages has no persistent server.
Cloudflare equivalent:
| Current job | Cloudflare Worker Cron |
|---|---|
| BNR rate fetch (daily 23:30) | Cron Trigger 0 23 * * * |
| DB vacuum (PostgreSQL) | Automated in Supabase/Neon |
| Backup pg_dump | Supabase PITR or Neon branching |
| auto_actdb schema migration | Drizzle Kit migrate in CI/CD pipeline |
| Supplier price feed imports | Cron Trigger or queue (Cloudflare Queues) |
| Daily email report | Cron Trigger + Resend/SendGrid API |
Recommended Migration Architecture
Section titled “Recommended Migration Architecture”graph TD
subgraph Cloudflare["Cloudflare"]
CP["Cloudflare Pages\nSvelteKit SSR app\n- All UI routes\n- API routes (/api/...)"]
CW["Cloudflare Workers\n- BNR cron fetch\n- ANAF integration\n- e-Factura signing\n- Supplier price imports\n- PDF rendering"]
R2["Cloudflare R2\n- Invoice PDFs\n- Uploaded documents\n- Report exports"]
Q["Cloudflare Queues\n- Supplier feed import jobs\n- Email send queue"]
end
subgraph Database["Database (Supabase/Neon)"]
PG["PostgreSQL 15\n- Migrated from maxx DB\n- Row-level security per tenant\n- Drizzle ORM schema"]
end
subgraph Auth["Auth"]
PB["PocketBase\n(self-hosted or Fly.io)\n- User accounts\n- Company registry\n- JWT sessions"]
end
subgraph External["External"]
BNR2["BNR XML feed"]
ANAF2["ANAF REST/SPV"]
SUP["Supplier APIs"]
EMAIL["Resend / SendGrid"]
end
CP <--> PB
CP <--> PG
CW -->|scheduled| BNR2
CW -->|on-demand| ANAF2
CW -->|scheduled| SUP
CW -->|async| Q
Q -->|email| EMAIL
CW --> R2
CP --> R2
Phased Migration Plan
Section titled “Phased Migration Plan”Phase 1 — Foundation (3-4 months)
Section titled “Phase 1 — Foundation (3-4 months)”- Introspect PostgreSQL schema → generate Drizzle ORM types
- Set up Supabase project, migrate schema (no data yet)
- SvelteKit skeleton app on Cloudflare Pages
- Implement auth: PocketBase login → JWT → tenant DB resolver
- BNR rate fetch Cloudflare Worker (easy win, high value)
Phase 2 — Core Modules (6-9 months)
Section titled “Phase 2 — Core Modules (6-9 months)”- Master data (products, clients, suppliers —
codificari/) - Inventory module (warehouse movements, current stock)
- Sales: customer orders + invoices (cfi/comenzi_clienti)
- Purchasing: supplier orders + invoices (ffi/comenzi_furnizori)
- Basic reports (sales, stock)
Phase 3 — Compliance (3-6 months)
Section titled “Phase 3 — Compliance (3-6 months)”- ANAF CUI lookup integration
- e-Factura XML generation + ANAF SPV submission
- PDF invoice rendering (Cloudflare Browser Rendering)
- VAT computation logic
Phase 4 — Advanced (6-12 months)
Section titled “Phase 4 — Advanced (6-12 months)”- Accounting module (balanta / carte mare / bilant)
- Payroll + HR (salarii — most complex, legal requirements)
- D394 VAT declaration builder
- SAF-T export
- All 20+ supplier price feed integrations
- Pivot / advanced reporting engine
- Dashboard (tabloubord)
What Maps Easily vs What Doesn’t
Section titled “What Maps Easily vs What Doesn’t”Easy to migrate ✅
Section titled “Easy to migrate ✅”- BNR exchange rate fetch
- ANAF CUI lookup
- Product/client/supplier CRUD (codificari/)
- Inventory read queries (current stock, movements)
- Simple reports (list views with filters)
- Email sending (replace broken SMTP with Resend)
- Authentication (PocketBase is much cleaner than current JSP)
- File uploads (R2 instead of local disk)
Hard to migrate ⚠️
Section titled “Hard to migrate ⚠️”- XML-driven form engine (DarkQS) — must rebuild ~200 screens as Svelte components
- All PostgreSQL stored functions (
dm2_*) — must rewrite in TypeScript or SQL views - Multi-tenant connection routing — requires careful Supabase RLS design
- 1,493 migration scripts — must consolidate into current schema baseline
Very hard / requires specialists 🔴
Section titled “Very hard / requires specialists 🔴”- Payroll legal compliance (D112 declaration, REVISAL, CAS/CASS computation)
- D394 VAT declaration (complex matching logic, ANAF format changes yearly)
- SAF-T reporting (very large XML, ANAF validator is strict)
- e-Factura XML signing (requires qualified digital certificate)
- Accounting module (Romanian PCG-specific, statutory reporting)
Cost-Benefit Summary
Section titled “Cost-Benefit Summary”| Factor | Assessment |
|---|---|
| Codebase size | ~2,000 JSP files + ~50K lines Java in JARs |
| Estimated rewrite effort | 3-5 developer-years for full feature parity |
| Main blocker | Romanian fiscal compliance expertise (not just code) |
| Quick win | BNR + ANAF + product/client CRUD modules as standalone tools |
| Biggest risk | e-Factura / D394 / payroll — legal penalties for errors |
| PocketBase fit | Poor for this use case — use PostgreSQL + Supabase instead |
| Cloudflare Pages fit | Good for UI/SSR; Workers have 50ms CPU limit (watch for complex computations) |
| Recommended first step | Schema introspection + Drizzle types + auth layer |