Debugging FNP pricing that did not load for an enquiry
Use this page when a supplier reports missing FNP menus, no price, a wrong price, or a package that cannot be selected. Work through the stages in order: live supplier eligibility → saved enquiry data → provider retrieval → price filtering → jobsheet selection. Finding the first failed stage prevents unnecessary imports or configuration changes.
Opening an enquiry reads saved FNP descriptions. Refreshing the browser or choosing View More does not fetch fresh provider menus. A logged succeeded message can also follow an empty result or a caught provider error.
1. Capture the incident context
Record:
- Environment/project, enquiry number and enquiry document ID, supplier account ID/service-unit ID and customer/lease ID.
- Enquiry source (
WALK_INor another source), creation time with timezone, and any reassignment history/new enquiry ID. - VRM, stock ID, stored VIN,
car.vrmLookup.vin, make and vehicle type. Record the values actually used in requests, not only those displayed in the header. rateCardType, enquiryfnpandfleetLevel, and the current supplier terms for that network.- Exact symptom: no FNP icon, empty menu, one description missing, package disabled, no fixed price, wrong amount or unsaved parts selection.
- Whether the problem affects one VIN, one supplier, one customer or an entire manufacturer; whether a new enquiry for known-good data works.
Use the supplier-side enquiry when reproducing. Save in-progress jobsheet work before reloading. Browser network responses, a package ID/description and a screenshot of the exact symptom are more useful than “FNP missing” alone.
2. Triage from the browser
In developer tools, preserve the network log and reopen the enquiry. Locate these callable requests:
| Request | Expected behaviour | What a failure means |
|---|---|---|
POST /api/v1/supplier/checkIfSupplierHasFnpEnabled | Uses lease ID, service-unit ID, enquiry ID and manufacturer; account context is derived by the backend. Returns current FNP/franchise eligibility. | false points to current network terms or franchise mismatch. An error is different from a valid false. |
POST /api/v1/enquiry/getFnpJobDescriptions | Reads saved descriptions, with enquiry ID, manufacturer, fleetLevel and fnp in the body. | A successful empty result can mean missing cache or OEC filtering. An HTTP/API error can leave the same empty UI state. |
If the description request never happens, start with eligibility and the page's fetch state. The eligibility check runs on mount, and the jobsheet's FNP fetch effect does not depend on every eligibility/configuration change. A reload can resolve stale UI state, but cannot populate an empty provider cache.
Inspect the actual description request body. In particular, verify fnp.isEnabled, the exact string fnp.fnpBand, fleetLevel.type and manufacturer. For Ford, the later filter checks the exact uppercase string FORD; provider lookup's case normalization does not make that later comparison case-insensitive.
3. Check the correct records and database
These business records and the FNP description cache are in the default Firestore database. ancillary in the last path is a collection name, not the named database used for API logs.
| Record | Inspect |
|---|---|
accounts/{accountId}/serviceUnits/{serviceUnitId}/enquiries/{enquiryId} | rateCardType; enquiry.enquirySource, enquiry.car, enquiry.fnp, enquiry.fleetLevel, enquiry.leaseCompanyId. |
accounts/{accountId}/serviceUnits/{serviceUnitId} | For DI network terms: diNetworkTerms.fnp and diNetworkTerms.franchises. For VWG: top-level ppsoPartnerKey. |
leasecompany/{leaseId}/suppliers/{accountId}/serviceUnits/{serviceUnitId} | Customer network fnp and franchises. Collection name is lowercase leasecompany. |
leasecompany/{leaseId} | fleetLevel, config.integrations, customer status where relevant to exports. |
config/settings | kiaFnpLabourRate for Kia's dedicated FNP labour charge. |
ancillary/enquiries/descriptions/{enquiryId}/{provider}/descriptions | Field descriptions; provider collection is lowercase modulus, oec or oem. |
Use the rate-card table to select the correct supplier terms. The UI eligibility check uses current terms, while the saved-menu request uses the enquiry's copied pricing inputs. Both can be individually valid yet disagree after a settings change.
Check the cache before rerunning anything:
| Cache state | Interpretation and next step |
|---|---|
| No provider document | Loader may not have run, may have skipped eligibility/VIN, or may have failed before writing. Also check the provider and enquiry ID, especially after reassignment. |
descriptions: [] | Lookup may have legitimately returned no rows, or the background loader may have caught a provider error. Read creation-time logs. |
| Nonempty descriptions; UI API empty | Investigate provider selection and OEC fleet/band filtering before upstream imports. |
| Nonempty API response; menu absent/disabled | Investigate UI search/selection and incomplete-package flags. |
| Menu selectable but wrong/no amount | Inspect selected cost/parts, copied terms and pricing calculation separately from loading. |
Eligibility checks
Confirm supplier fnp.isEnabled and a normalized matching franchise on the selected terms. Manufacturer matching trims and ignores case; supported LCV suffixes are normalized, but a different group brand is not interchangeable. Compare lookup make with stored make because lookup make is preferred.
The current saver bypasses the customer integration-enabled check for the supported OEC, Modulus, Kia and VWG make/provider pairs. Do not diagnose an absent FNP_* integration entry as the sole cause for those runtime lookups. Integration configuration still matters to processes such as Kia VIN exports. See Configuration.
4. Trace the creation-time loader
For non-walk-in enquiries, locate onEnquiryCreatedHandler around the creation time. It is a Firestore create trigger on the supplier enquiry. For walk-ins, inspect the callable creation request logs; the background trigger's Returning as the enquiry is of type WALK_IN is expected for ordinary walk-in creation.
Start Cloud Logging with the correct project, time window, service/function and an enquiry ID or number. After locating the invocation, follow its trace/execution and nearby messages: individual FNP helper logs do not all contain the enquiry ID. A useful broad message filter within that window is:
("Saving FNP provider job descriptions" OR "save-fnp-job-description" OR "fetch-fnp-" OR "getPackagesByVin")
Do not restrict only to error severity; several failure/skip paths log at ordinary log level.
| Search text | Meaning / next check |
|---|---|
Saving FNP provider job descriptions for enquiryId: | Background handler reached the FNP step. |
[save-fnp-job-description] : supplier details not found from lease | No applicable supplier terms. Check network choice and IDs. |
suppliers FNP is disabled | FNP not enabled on the terms used by that provider check. |
suppliers franchises doesn't match with vehicle manufacturer | Make/franchise mismatch. |
car manufacturer ... is not available | That candidate provider did not support this make. The loader tries multiple providers, so this message alone does not prove the matching provider failed. |
integration is not enabled for lease | Generic integration gate failed for that candidate. Interpret alongside the explicit make/provider exceptions. |
skip saving descriptions for ... as vin is not found | VIN guard skipped loading. In the inline saver, no matched provider can also reach this broadly worded log; inspect preceding eligibility logs. |
[save-fnp-job-description] saving descriptions for ... | Provider selected and retrieval starting. |
[fetch-fnp-...-job-description] : ... descriptions received | Provider response count. Zero is not evidence of an exception; compare SQL/API result. |
error while getting descriptions | Background provider call failed; loader substitutes [] and can still write/log success. |
error while running | Outer background save failed and rethrew. Inspect the invocation's actual outcome/retry configuration. |
[save-fnp-job-description] succeeded | Saver completed; inspect the saved array and earlier errors before calling this a successful price load. |
If the handler fails before reaching FNP, inspect the preceding VRM lookup/update. If no invocation exists, confirm the deployed trigger, project and document path. Editing an existing enquiry does not fire a create trigger.
Reassigned walk-in enquiries
Reassignment creates a new enquiry ID and preserves the original source. In the reviewed code, a reassigned walk-in can be skipped by the background handler while the reassignment helper neither invokes the inline FNP saver nor copies the previous cache. Check both enquiry IDs and sources and raise this path for engineering review. Reassigning repeatedly is not a refresh procedure.
5. Verify provider data with read-only checks
Use the SQL database configured on the callable deployment, not merely a database with a familiar FNP name. Confirm the importer writes to that same environment. Replace the placeholder values below with the incident's exact keys. Use read-only access; these queries do not repair data or refresh an enquiry.
Modulus: Peugeot/Citroen
SELECT * FROM modulus_vehicle WHERE vrm = '<VRM>' LIMIT 20;
SELECT * FROM modulus_menu_price WHERE vehicle_id = '<vehicle_id>' LIMIT 20;
If no VRM was supplied in the provider request, the runtime instead uses:
SELECT * FROM modulus_vehicle WHERE vin = '<VIN>' LIMIT 20;
Check both files and the join key. When a VRM was supplied but not found, the code does not automatically retry VIN. Multiple vehicle matches also matter because it uses the first returned vehicle ID. Inspect duplicate imports, formatting and menu labour/part fields if the result is incomplete.
Vauxhall/OEC
SELECT * FROM oec_menu_price WHERE vin = '<VIN>' LIMIT 20;
Inspect the expected idJob/menu and relevant price columns as well as row presence. Check the OEC import's date-specific filename and persisted batch counts if rows are missing. A VIN export only sends a request for data; it does not prove the returned file was received and imported.
Ford/OEC
SELECT * FROM get_vehicle_descriptions_and_parts('<VIN>') LIMIT 50;
If the function is missing, inspect SQL deployment. If it returns no rows, inspect the Ford VIN/applicability, description, labour, parts and price datasets listed in Providers and data. Record region code, price and complete_code when rows do exist. Ford is not read from Vauxhall's oec_menu_price table.
Kia/OEM
SELECT * FROM vehicle WHERE vin = '<VIN>' LIMIT 20;
SELECT mp.*
FROM vehicle AS v
JOIN menu_price AS mp ON mp.vehicle_id = v.vehicle_id
WHERE v.vin = '<VIN>'
LIMIT 20;
Compare vehicle_id construction from vehicle miocn/model year and menu model/model year. A receivedMenuPrice stock flag or successful VIN export is not a substitute for this join. If returned data never arrived, inspect FNP_KIA export eligibility and the Typesense stock state.
If menus exist but Kia labour pricing is wrong, check config/settings.kiaFnpLabourRate and partsAndLabourConfig.kiaFnpLabourRate in the aggregated enquiry response. This configuration lookup tests the enquiry's stored car.make.label, so also compare it with the preferred VRM lookup make. See Kia labour-rate setup.
VWG/OEM
Check these in order:
- The enquiry's VRM lookup VIN exists. A stock-only VIN does not supply the VWG request, even if an earlier generic VIN guard passed.
- The supplier service unit has
ppsoPartnerKey. VWG_CLIENT_CREDENTIALScan be read/decoded and includes an exact uppercase manufacturer/vehicle-type match, with client ID, secret and wholesaler key. Verify presence/matching without copying credentials into the incident.- The make/type has a PPSO brand code. The outgoing partner key combines supplier partner key and brand code.
- The VIN is not blacklisted:
SELECT vin FROM vwg_blacklisted_vin WHERE vin = '<VIN>';
- The API returns
carPackageSet.packageSet.package, with usablebrandPackageIdvalues. - For a package that loads but cannot be selected, compare its exact part/fluid codes with reference data:
SELECT part_number, part_price
FROM vwg_parts_price WHERE part_number = '<PART CODE>';
SELECT fluid_code, description
FROM vwg_fluid_details WHERE fluid_code = '<FLUID CODE>';
hasIncompletePartInfo: true means at least one non-fluid price is missing. Fluid prices are deliberately absent and entered by the supplier. If enrichment failed, packages may remain present with missing non-fluid prices; repairing the import alone does not update the already saved enquiry descriptions.
VWG API logs
VWG API logs use the named ancillary Firestore database, under:
logs/vwgApi/repair-serve-callable/{year}/{monthIndex}/{day}/log/{entryId}
monthIndex is zero-based: January is 0, September is 8. Entries include api (getAccessToken or getPackagesByVIN), request, response/error, timestamps and response time. Find the package request by VIN and time; correlate token failures in the same period. Check status and response shape, and confirm the deployed endpoint: the reviewed source's VWG base URL is a QA host.
Search Cloud Logging for:
Error parsing VWG client secretsgetPackagesByVin: VIN is blacklistedgetPackagesByVin: VWG API errorgetVwgDescriptions: failed to fetch VWG parts/fluid catalogue from Cloud SQL
No package-call log can be consistent with an early return for missing VIN, supplier partner key, credentials/brand or blacklist; it does not automatically mean logging failed.
6. When the cache has data but prices are filtered out
For OEC/Ford, compare the request's enquiry snapshot values with the saved cost arrays:
| Check | Expected value |
|---|---|
| Fleet level | SERVICE_4_FLEET, PLATINUM_SERVICE_4_FLEET or LEX_AUTO. |
| Cost group | Respectively service4Fleet, service4FleetPlatinumArval, lexAutoBand. |
| Band | Allowed string, e.g. Band 2 LA3, not an old number. NONE returns no OEC menus. |
| Ordinary Lex match | Both numeric band and LA number match a cost entry. |
| Service4Fleet/Platinum match | Numeric band matches; importer uses LA1 source columns for these groups. |
| Ford region | Region 22 or 23 as agreed, populated cost group, exact FORD manufacturer in the request. |
The service discards any description without a matching cost. For example, nonempty costs.service4Fleet cannot satisfy an enquiry requesting LEX_AUTO.
For Ford + Lex Auto, check the documented group-name mismatch: Ford shaping asks for lexAutoBand, while band definitions use lexAuto. If that creates an empty saved group despite valid source prices, this needs an implementation fix rather than another supplier-band change. See Enquiry pricing flow.
For OEM/Modulus, do not apply these OEC band rules to explain an empty result. Check the selected provider document, ID and retrieval error instead.
7. When data reached the browser
| Symptom | Checks |
|---|---|
| No FNP icon despite data | Confirm current eligibility state and fetched descriptions in the same page session. Reopen after configuration correction. |
| Package appears in the full list but not search | Clear search; check search threshold and hasIncompletePartInfo. Incomplete packages are excluded from autocomplete. |
| “This cannot be added because there is missing information in this package.” | Inspect missing non-fluid VWG part prices and the cached package flag. |
| Ford description but no fixed price | Multiple options need selection; inspect fixedPrices, completeCode and enquiry fnp.isEnabled. |
| Price differs from expected | Compare the selected band/fleet group, the enquiry snapshot, cache age and actual selected jobline. For non-aggregate menus, inspect labour rate/time, selected parts, quantities and discounts. |
| Parts validation prevents save | Complete/save the parts dialog and duplicate-part or fixed-price selection. isFnpPartLinesNotSaved is a selection-state issue. |
| View More does not fix it | Expected: this exposes Haynes/ViSN tabs, not an FNP retry. |
Do not infer a loading failure merely because the displayed label is not “FNP”. Kia uses Business Service Promise, Vauxhall Fleet Service, Peugeot/Citroen Fleet Charter Pricing, Ford Fleet National Pricing, and VWG uses its branded icon/PPSO configuration.
8. Recovery and verification
Choose the repair from the failed stage:
| Finding | Recovery |
|---|---|
| Wrong live supplier/network configuration | Correct the applicable terms/franchise and reopen to recheck eligibility. Then inspect the old enquiry's snapshot/cache separately. |
| Missing provider dataset | Validate/import the correct data into the callable's environment, or resolve the upstream return-data issue. Verify the SQL lookup. |
| VWG credentials, partner key, blacklist or API issue | Resolve the specific configuration/provider issue and verify a valid response for the vehicle. Do not remove a legitimate blacklist entry merely to force pricing. |
| Empty/stale enquiry cache after the cause is fixed | Engineering should regenerate descriptions for that specific enquiry through the appropriate saver with its correct supplier, vehicle and network context, then verify the saved array. There is no dedicated supplier-facing FNP regenerate action in the reviewed flow. |
Outdated enquiry fnp/fleetLevel | Review the enquiry's intended commercial terms and correct through the appropriate support/data workflow; changing the master supplier/customer does not itself update the snapshot. |
| Descriptions returned but jobline incomplete | Complete the selection or fix missing parts data, then verify the saved jobline. |
For engineering isolation, POST /api/v1/fnp/getFnpDescriptions performs the upstream lookup using vrm, vin, fnpProvider, manufacturer and, for VWG, vehicleType, accountId, serviceUnitId. It does not execute all enquiry eligibility checks and does not save the result to the enquiry. It can issue a live VWG request. Successful use of this endpoint proves retrieval only, not successful recovery of the enquiry.
Do not delete/recreate an operational enquiry, mass-edit caches, or rerun creation with side effects just to test loading. Imports and regeneration have different write behaviour; preserve the original cache/request evidence and target the actual failed stage.
After remediation, confirm:
- The correct enquiry ID has nonempty provider descriptions.
- Its supplier eligibility passes and saved-menu response includes the expected description and cost.
- The supplier can select the intended menu and complete parts/fixed-price selection.
- The saved jobline's source, parts, labour and subtotal match the intended terms. Existing saved joblines are not automatically repriced by a cache refresh.
Escalation record
Environment and deployed revisions:
Enquiry number / ID / creation time / source:
Previous/new IDs if reassigned:
Customer / supplier account / service unit / rate card:
VRM / provider / manufacturer / vehicle type:
Stored VIN vs VRM lookup VIN:
Live FNP + franchises:
Enquiry FNP + fleet level:
Eligibility request result:
Cache path / missing, empty or description count:
Saved-menu request result and expected menu/package:
Provider SQL/API result and relevant import run:
Logs / timestamps / trace IDs:
First failed stage / attempted correction / verification result:
Keep provider credentials and authorization headers out of the record.