CRM · SFE · Pharma
W-CRM — CRM for medical representatives
A field CRM resilient to connection loss. A specialised CRM for medical reps and pharma companies: clients, visits, tasks, sales and employee performance.

View the case
What lives inside the system
- Industry
- Pharmaceuticals
- Status
- In production
- Roles in the system
- 6
- Permissions
- 263
- PWA screens
- 45
- Visit types
- 9
Challenge
Pharma companies tracked rep visits in spreadsheets: no routes, no control and no transparent analytics on doctors and pharmacies.
Solution
We built W-CRM: a unified doctor and pharmacy database with interaction history, route, visit and task planning, sales tracking, a geo-tagged mobile app for field work and employee performance dashboards for managers.
Visit flow
One visit, from prep to report
Every step of field work is digital: the rep has a goal, a script and outlet history, while managers get the data the moment a visit is closed.
01
Preparation
SMART goals for the outlet, product scripts and past visit history, gathered in a single preparation window.


02
At the outlet
The start is confirmed by coordinates: the app checks the rep's position against the outlet radius, so a visit cannot be logged away from the location.

03
Presenting to the doctor
The CLM library opens right inside the visit, and the system tracks time per slide to measure presentation effectiveness.


04
Closing
Merchandising audit, promo materials and questionnaire answers are captured on close and land straight in management reports.

Offline-first
The signal drops. Work goes on
Connectivity in the field is unreliable, but the visit has to go on. Every action is written to a local queue on the device and shipped to the server in one batch once the network recovers. Try it:
Network: online
Turn the network off, do a few actions, then switch it back on
Visit · Neman 70 pharmacy
Bishkek, Aibek-Ata st.
Outbox · local queue
The queue is empty. Perform the actions on the left
Server · /api/sync/push
Waiting for the queue batch
01
An outbox in IndexedDB
Visit actions are stored locally via Dexie and survive even an app restart.
02
Honest offline detection
Network status comes from actual request failures and a public /api/ping, not navigator.onLine: the network-up-server-down case is recognised as offline as well.
03
One batch, FIFO order
When the network returns, the queue ships as a single POST /api/sync/push and is applied atomically per action.
04
An idempotent protocol
Every request carries an X-Idempotency-Key: resending after a dropped connection creates no duplicates and never breaks visit statuses.
Offline mode in the real interface


GPS control
Every visit is confirmed by coordinates
Geo validation blocks starts outside the outlet radius, while Wialon telematics matches vehicle routes against actual visits and flags trip violations.
Neman 70 pharmacy
100 m radius
Outlet radius
A start attempt outside the radius is blocked at the API level with an OutOfRadiusException.
Wialon telematics
Company vehicle tracks are matched against visits, and the analyzer flags violations per trip.
GPS reports
Managers export GPS violations for any period as a single report.

Analytics
Managers see the whole field
Seven analytics sections on top of field data: plan and coverage, trends, outlet and contact LTV, visit quality, presentation effectiveness and forecasting.
Plan & coverage
Plan fulfilment, cumulative progress and coverage of the outlet and contact base.

Activity by hour
A weekly heatmap shows when the team is actually out in the field.

Representatives
Comparing employees: sparkline trends, fulfilment and the GPS confirmation share.

Employee profile
Visits, outlets, contacts and working time of a specific rep.

13 async exports
Excel reports are built in a queue and arrive as a notification: visits, timesheets, GPS, absences, the promo matrix and more.

The system in numbers
permissions
data models
PWA screens
action handlers
Excel exports
visit types
Under the hood
How it works inside
One API for every action
A PolymorphicFormRequest and a factory of 36 handlers: PUT /visits/{id} with an action field instead of a pile of custom routes.
Real-time on Bun.js
A standalone Bun.js WebSocket server next to the PHP monolith pushes notifications to the team instantly.
Search on Meilisearch
Full-text search across doctors, pharmacies and products via Laravel Scout with caching services.
The core as a package
The domain lives in a wires-core Composer library: Finance, Warehouse and HR modules are already wired into the system.
Result
Management got a transparent view of field work, and the number of productive visits grew.
Next project
Trade Assistant — intelligent market analytics