CRM & targeting platform
A full-stack .NET 8 / Angular 19 CRM and targeting platform on Capcod's Infinity. The biggest piece I built: the engine that migrated about 43,000 records out of the client's two legacy CRMs.
.NET 8
Angular 19
EF Core
Data migration
A full-stack CRM and commercial-targeting app built on Capcod's in-house Infinity platform: accounts, contacts, targets, missions and follow-ups, with forms driven by the backend's field config (FEI). I built most of the .NET API and the Angular front.
#The big piece: migrating two legacy CRMs
A large part of the project was a one-shot migration of all the client's data out of their two old CRMs into the new platform. I built the import engine for it. It moves about 43,000 records (~5.8k companies, ~8.4k contacts, ~5.5k targets, ~15.9k follow-ups) in a single all-or-nothing transaction, and the tricky parts are the interesting ones:
- Generic column-to-field mapping. A reflection-based builder maps spreadsheet columns to typed fields from
[DisplayName]annotations, so adding an importable field is a one-line change. Type coercion is locale-aware (French dates, comma decimals, "oui/non" booleans) and fails soft instead of throwing. - Relational linking inside one open transaction. Rows link to each other by business keys (SIREN/DUNS, mission code, email), not database ids. Since nothing is committed yet, I keep an in-memory cache of the pending entities so children can find their parents. That same cache drives deduplication and contact reuse, so one person spread across several companies becomes a single contact.
- All-or-nothing with clear errors. If any row fails, the whole import rolls back and returns per-row, per-sheet, localized error messages instead of a half-migrated database.
- Cached reference-data resolvers to avoid N+1 lookups (users by email, workflow statuses by name) across the whole dataset.
#Other things I built
- A round-trip Excel workflow for targeting. I generate an import template whose dropdowns are filled from live database reference data, then re-import it with conflict detection: instead of overwriting, it surfaces existing-vs-incoming differences in a reconcile screen.
- A dynamic segmentation engine. Composable LINQ predicates (LinqKit) that filter accounts and targets across nested relations (address, NAF code, referrer contact, account type), with per-field operators.
- French business-registry validators (SIREN, NAF) wired into the Infinity validation pipeline.
- An event-driven worker that consumes workflow status events over AWS SQS and updates the matching mission.
#Stack
.NET 8, EF Core, Infinity, Jarvis / Workflow / DocGen SDKs, AWS SQS, Angular 19, TypeScript, Capcod Clarity UI.