Skip to content

Modern Stack Migration

LayerTechnology
Frontend + SSRSvelteKit on Cloudflare Pages
Backend / APISvelteKit API routes (server-side) + Cloudflare Workers
Primary databasePocketBase (SQLite) — or Supabase/Neon for PostgreSQL
LanguageTypeScript throughout
AuthPocketBase built-in auth
File storageCloudflare R2
PDF generationCloudflare Worker + @react-pdf/renderer or Puppeteer

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.


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]

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.svelte
saf.do?cod_formular=N → /routes/[module]/[entity]/[id]/+page.svelte
saveForm.do → form actions (actions.ts)
processActionRecords.do → server-side action handler
pivot.do → /routes/reports/pivot/+page.svelte
printModelPdf.do → /api/pdf/[template]/+server.ts
executegeneralselect.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_dump equivalent for large datasets

Recommended DB stack:

Production: Supabase (PostgreSQL 15) or Neon
Dev: 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:

src/hooks.server.ts
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:

IntegrationComplexityNotes
BNR exchange ratesLOWSimple XML fetch → DB insert. Easy Cloudflare Worker.
ANAF CUI lookupMEDIUMREST/SOAP API still works. Wrap in /api/anaf/+server.ts.
e-Factura (XML invoices)HIGHRomania mandates XML e-invoices since 2024. Requires ANAF SPV OAuth + XML signing.
D394 VAT declarationVERY HIGHMonthly XML file with all invoice parties. Currently via DarkCloud.
SAF-T reportingHIGHNew requirement since 2025. Large XML export.
Payroll declarations (D112)HIGHMonthly payroll XML to ANAF.

e-Factura Cloudflare Worker example:

workers/efactura/index.ts
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);
}
};

Current: Java-based PDF generation (printModelPdf.do), templates likely in JasperReports or custom.

Cloudflare approach:

  • Cloudflare Workers: use @cf/jsPDF or pdf-lib (no browser, ~50ms CPU limit!)
  • For heavy PDFs: Cloudflare Browser Rendering API (Puppeteer) — render HTML → PDF
  • Store output in R2, return signed URL
/api/pdf/invoice/+server.ts
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 jobCloudflare Worker Cron
BNR rate fetch (daily 23:30)Cron Trigger 0 23 * * *
DB vacuum (PostgreSQL)Automated in Supabase/Neon
Backup pg_dumpSupabase PITR or Neon branching
auto_actdb schema migrationDrizzle Kit migrate in CI/CD pipeline
Supplier price feed importsCron Trigger or queue (Cloudflare Queues)
Daily email reportCron Trigger + Resend/SendGrid API

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

  • 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)
  • 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)
  • ANAF CUI lookup integration
  • e-Factura XML generation + ANAF SPV submission
  • PDF invoice rendering (Cloudflare Browser Rendering)
  • VAT computation logic
  • 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)

  • 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)
  • 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
  • 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)

FactorAssessment
Codebase size~2,000 JSP files + ~50K lines Java in JARs
Estimated rewrite effort3-5 developer-years for full feature parity
Main blockerRomanian fiscal compliance expertise (not just code)
Quick winBNR + ANAF + product/client CRUD modules as standalone tools
Biggest riske-Factura / D394 / payroll — legal penalties for errors
PocketBase fitPoor for this use case — use PostgreSQL + Supabase instead
Cloudflare Pages fitGood for UI/SSR; Workers have 50ms CPU limit (watch for complex computations)
Recommended first stepSchema introspection + Drizzle types + auth layer