Skip to content

Add RVO field synchronization package and import workflows#374

Merged
SvenVw merged 118 commits into
developmentfrom
FDM373
Mar 25, 2026
Merged

Add RVO field synchronization package and import workflows#374
SvenVw merged 118 commits into
developmentfrom
FDM373

Conversation

@SvenVw
Copy link
Copy Markdown
Collaborator

@SvenVw SvenVw commented Dec 11, 2025

Summary by CodeRabbit

  • New Features
    • Full "Ophalen bij RVO" flow: OAuth connect, review/import parcels in farm creation and farm pages; new UI cards, review table and error alert; new env vars to enable RVO integration.
  • Bug Fixes
    • Field table cells now display gracefully when numeric values are missing.
  • Documentation
    • Added package README, docs tag and TypeDoc entry for RVO sync.
  • Tests & CI
    • New unit/integration tests for RVO logic; CI updated to run RVO tests and upload coverage.

Closes #373

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Dec 11, 2025

🦋 Changeset detected

Latest commit: 34f2452

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@svenvw/fdm-app Minor
@svenvw/fdm-docs Minor
@svenvw/fdm-rvo Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 11, 2025

Warning

Rate limit exceeded

@SvenVw has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 51 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ef131566-de79-4a1a-b16e-44c40f2aa1bb

📥 Commits

Reviewing files that changed from the base of the PR and between 99c76d0 and d6a9091.

📒 Files selected for processing (2)
  • fdm-rvo/README.md
  • fdm-rvo/src/process.test.ts
📝 Walkthrough

Walkthrough

Adds a new workspace package fdm-rvo and integrates RVO OAuth, field fetching, comparison, review UI, and import processing into fdm-app, plus supporting server helpers, types, tests, TS config changes, workspace updates, and CI workflow updates.

Changes

Cohort / File(s) Summary
New package: fdm-rvo
fdm-rvo/package.json, fdm-rvo/tsconfig.json, fdm-rvo/tsconfig.build.json, fdm-rvo/rollup.config.js, fdm-rvo/typedoc.json, fdm-rvo/README.md, fdm-rvo/src/index.ts, fdm-rvo/src/*, fdm-rvo/src/**/*.test.ts
Adds a new workspace package exposing RVO helpers: auth, data fetch, comparison, import processing, types, utils, tests, build and docs config, and public exports.
App RVO routes & pages
fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx, fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx, fdm-app/app/routes/.../rvo.tsx, fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx, fdm-app/app/routes/farm.$b_id_farm._index.tsx
New route modules implementing RVO start/callback flows, loaders/actions to produce review datasets and apply/save imports; loader return shapes updated where applicable.
RVO UI components
fdm-app/app/components/blocks/rvo/connect-card.tsx, fdm-app/app/components/blocks/rvo/import-review-table.tsx, fdm-app/app/components/blocks/rvo/error-alert.tsx
Adds connect card, import-review table (TanStack), and formatted error alert components used by import pages.
Server integration & config
fdm-app/app/integrations/rvo.server.ts, fdm-app/app/lib/rvo.server.ts, fdm-app/app/lib/config.server.ts, fdm-app/.env.example, fdm-app/app/types/config.d.ts
Adds server-side RVO state cookie, state verification, credentials loader, configured client creation, environment variables, and ServerConfig typings for RVO.
RVO data & comparison logic (core package + app glue)
fdm-rvo/src/data.ts, fdm-rvo/src/compare.ts, fdm-rvo/src/process.ts, fdm-rvo/src/types.ts, fdm-app/app/lib/rvo.server.ts, fdm-core/src/index.ts
Implements fetch/merge of RVO GeoJSON (MEST merging), two-tier matching (ID then spatial IoU), diff detection, import processing, and exposes types; fdm-core re-exports option constants.
NMI integration imports switched to server-only
multiple fdm-app/app/routes/*, fdm-app/app/integrations/calculator.ts
Replaces client/shared imports with .server entry points so sensitive NMI logic runs server-side.
Fertilizer mapping moved server-side
fdm-app/app/components/blocks/fertilizer/utils.server.ts, removed fdm-app/app/components/blocks/fertilizer/utils.ts export, updated routes
Moves getRvoMappings to server-only implementation and updates routes to import it.
Field table & constants
fdm-app/app/components/blocks/fields/columns.tsx, fdm-app/app/lib/constants.ts
Adds null-safe formatting for a_som_loi and introduces acquiringMethodOptions constant for cultivation mapping.
App loader/UI updates
fdm-app/app/routes/farm.$b_id_farm._index.tsx, fdm-app/app/routes/farm._index.tsx, farm-create routes
Loaders now include isRvoConfigured and farmWritePermission; UI shows "Ophalen bij RVO" action when configured; page title pluralized.
TypeScript config consolidation
tsconfig.base.json, tsconfig.packages.json, tsconfig.apps.json, tsconfig.typedoc.json, per-package tsconfig*.json
Adds shared base/package/app Typed config files and updates per-package configs to extend the shared settings; adds build-specific tsconfigs.
Workspace & package updates
package.json, pnpm-workspace.yaml, fdm-app/package.json, fdm-app/vite.config.ts, fdm-app/tsconfig.json
Adds fdm-rvo to workspace, app dependency and TS path alias, excludes @nmi-agro/fdm-rvo from Vite optimizeDeps, and updates pnpm workspace entries.
CI / Workflows
.github/workflows/tests.yml, .github/workflows/release.yml, .github/workflows/typecheck.yml
Adds test-rvo job with PostGIS service and coverage upload; writes npm auth in release jobs; removes registry-url from some setup-node steps.
Docs & changelogs
.changeset/*.md, fdm-docs/blog/tags.yml, fdm-docs/typedoc.json, fdm-rvo/README.md
Adds changesets, registers fdm-rvo tag and Typedoc entry, and adds package README.
Tests, packaging, ignore rules
fdm-rvo/*.gitignore, fdm-rvo/tsconfig.build.json, many fdm-rvo/src/*.test.ts
Adds unit and integration tests for auth/data/compare/process/utils, build tsconfig, gitignore and packaging metadata.
Error helper
fdm-app/app/lib/error.ts
Adds extractErrorMessage(e: unknown): Promise<string> to normalize errors for display.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Browser
    participant AppServer
    participant RVO_API
    participant DB

    User->>Browser: Click "Import from RVO"
    Browser->>AppServer: POST /start_import (b_id_farm)
    AppServer->>AppServer: createRvoState(farmId, returnUrl) / set-state cookie
    AppServer->>RVO_API: generateAuthUrl(state, redirect_uri)
    AppServer-->>Browser: 302 Redirect to RVO auth URL (Set-Cookie: rvo_state)
    Browser->>RVO_API: User authenticates, RVO redirects back with ?code&state
    Browser->>AppServer: GET /rvo?code=...&state=...
    AppServer->>AppServer: verifyRvoState(cookie, state)
    AppServer->>RVO_API: exchangeToken(code) => accessToken
    AppServer->>RVO_API: fetchRvoFields(accessToken, year, kvk)
    AppServer->>DB: loadLocalFields(farm)
    AppServer->>AppServer: compareFields(local, rvo) => reviewData
    AppServer-->>Browser: 200 Page with review UI (reviewData)
    Browser->>AppServer: POST apply_changes (userChoices + reviewData)
    AppServer->>DB: processRvoImport(...) (add/update/remove)
    AppServer-->>Browser: Redirect to farm page (success)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, fdm-app, fdm-core, fdm-data, branch:development

Suggested reviewers

  • gerardhros

Poem

🐰 I hopped through code with a tiny cheer,
OAuth carrots and IoU clear.
Parcels matched and conflicts shown,
From RVO fields a garden grown.
Hooray — imports done, let’s munch a carrot 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR successfully implements all core objectives from issue #373: fdm-rvo package created with auth, fetch, compare, and process functions; fdm-app integration with RVO auth flow, environment variables, and reconciliation UI routes.
Out of Scope Changes check ✅ Passed The PR includes some scope creep in TypeScript configuration consolidation and RVO-unrelated import path updates (NMI integrations), but these are reasonable infrastructure improvements supporting the RVO feature.
Title Check ✅ Passed Title check skipped as CodeRabbit has written the PR title.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch FDM373

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 11, 2025

Codecov Report

❌ Patch coverage is 90.56604% with 30 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
fdm-rvo/src/compare.ts 91.54% 12 Missing ⚠️
fdm-rvo/src/data.ts 90.00% 5 Missing ⚠️
fdm-rvo/src/process.ts 92.06% 5 Missing ⚠️
fdm-rvo/src/auth.ts 78.94% 3 Missing and 1 partial ⚠️
fdm-rvo/src/utils.ts 85.71% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

@SvenVw SvenVw self-assigned this Dec 12, 2025
@SvenVw
Copy link
Copy Markdown
Collaborator Author

SvenVw commented Mar 24, 2026

Looks good. I only have a few suggestions.

Oh also the rvo state cookie uses samesite: lax. You could change it to samesite: strict if you don't need to forward this cookie to the authorization provider or something.

Using 'strict' would break the CSRF verification as the user is redirected back from the RVO site

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx (1)

99-144: ⚠️ Potential issue | 🟡 Minor

OAuth authorization code reuse on page refresh.

After the OAuth callback, code and state remain in the URL. If the user refreshes, exchangeToken(rvoClient, code) will be called again with a single-use code that's already been consumed, causing an invalid_grant error. While the error is caught and displayed, this creates a confusing UX.

Consider persisting the review data in a session/flash store after successful token exchange, then redirecting to the same route without query params. This prevents accidental code resubmission.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx around lines 99
- 144, After successfully exchanging the OAuth code (exchangeToken called on the
rvoClient created by createConfiguredRvoClient) and building RvoImportReviewData
via compareFields, persist the review payload (or a flag indicating successful
exchange) into the session/flash store tied to the current principal/farm and
then perform a server-side redirect to the same route without query parameters
to clear code/state; on subsequent loads read review data from the session/flash
instead of re-calling exchangeToken (and clear the persisted data once
consumed). Ensure verifyRvoState still runs before exchangeToken and use the
session key to skip exchangeToken when present so refreshing the page won’t
resend the single-use code.
fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx (1)

109-170: ⚠️ Potential issue | 🟡 Minor

OAuth authorization code reuse on page refresh.

Same issue as the creation wizard route: after the OAuth callback, the code and state remain in the URL. A page refresh will attempt to reuse the single-use authorization code, resulting in an invalid_grant error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.rvo.tsx around lines 109 - 170,
The OAuth authorization code and state are left in the URL so a page refresh
re-uses the single-use code; after you successfully call
exchangeToken(rvoClient, code) (and any further RVO calls like
fetchRvoFields/compareFields), clear those query params by returning a redirect
to the same route without code and state (i.e. rebuild the URL excluding
code/state or use the framework's redirect helper) so the browser receives a 3xx
and the URL no longer contains the one-time code; ensure this redirect happens
only after a successful exchange and keep verifyRvoState, exchangeToken,
createConfiguredRvoClient and subsequent logic intact before issuing the
redirect.
🧹 Nitpick comments (3)
fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx (1)

224-250: Useless initial assignment to defaultAction.

Line 228 assigns "NO_ACTION" to defaultAction, but the switch statement on lines 230-246 covers all cases and always assigns a value before use. Remove the initial assignment to fix the CodeQL warning.

♻️ Suggested fix
-            let defaultAction: ImportReviewAction = "NO_ACTION"
+            let defaultAction: ImportReviewAction

Note: The switch comprehensively handles all five RvoImportReviewStatus values (NEW_REMOTE, NEW_LOCAL, EXPIRED_LOCAL, CONFLICT, MATCH), so the initial assignment is never used.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.rvo.tsx around lines 224 - 250,
Remove the unnecessary initialization of defaultAction in the useEffect: declare
let defaultAction: ImportReviewAction; (no initial value) inside the loop that
processes rvoImportReviewData, keep the existing switch on RvoImportReviewStatus
to assign the value, and then use initialChoices[id] = defaultAction before
calling setUserChoices; reference symbols: useEffect, initialChoices,
defaultAction, rvoImportReviewData, RvoImportReviewStatus, getItemId,
setUserChoices.
fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx (1)

495-496: Consider returning an error for unrecognized intents.

Returning an empty object for unrecognized intent values could mask form submission errors. While intents are controlled via hidden fields, explicit error handling would improve debuggability.

♻️ Suggested improvement
-    return {}
+    return {
+        success: false,
+        message: `Unknown intent: ${intent}`,
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx around lines 495
- 496, The handler currently returns an empty object for unknown "intent" cases
(the line with "return {}"); replace that silent fallback with an explicit error
response: locate the branch handling unrecognized intent values and either throw
a Response or return a JSON error payload with a 4xx status (e.g., 400) and a
clear message like "Unrecognized intent" so form submission errors are surfaced
and debuggable; keep the check that reads the "intent" hidden field and ensure
the error path is hit when no known intent matches.
fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx (1)

88-92: Simplify conditional rendering.

The RVO card uses a hidden class when !isRvoConfigured, but it would be cleaner to conditionally render the card only when needed, consistent with how the grid column count is already conditionally set.

♻️ Suggested simplification
-                    <Card
-                            className={cn(
-                                "flex flex-col",
-                                isRvoConfigured ? "" : "hidden",
-                            )}
-                        >
+                    {isRvoConfigured && (
+                        <Card className="flex flex-col">

And close with </Card>)} instead of just </Card>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar._index.tsx around lines
88 - 92, The Card currently toggles visibility by adding a "hidden" class via
cn; instead, render it only when needed by wrapping the Card JSX with a
conditional expression (e.g. {isRvoConfigured && ( ... )}) and remove the
isRvoConfigured ? "" : "hidden" branch from the cn call; ensure the wrapper
closes with </Card>)} so the component returns the Card only when
isRvoConfigured.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.rvo.tsx:
- Line 530: The action handler currently does const year = Number(yearString)
without validation, which can produce NaN and lead to bad input being passed to
processRvoImport; update the action to validate yearString the same way as the
loader: parse the value (e.g., parseInt or Number), check Number.isInteger(year)
and that it is within any expected range, and if invalid return or throw an
appropriate response (mirror the loader's error handling) before calling
processRvoImport; specifically adjust the code around the yearString -> const
year = Number(yearString) conversion in the action handler to include these
checks and early-return on invalid input.

---

Duplicate comments:
In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.rvo.tsx:
- Around line 109-170: The OAuth authorization code and state are left in the
URL so a page refresh re-uses the single-use code; after you successfully call
exchangeToken(rvoClient, code) (and any further RVO calls like
fetchRvoFields/compareFields), clear those query params by returning a redirect
to the same route without code and state (i.e. rebuild the URL excluding
code/state or use the framework's redirect helper) so the browser receives a 3xx
and the URL no longer contains the one-time code; ensure this redirect happens
only after a successful exchange and keep verifyRvoState, exchangeToken,
createConfiguredRvoClient and subsequent logic intact before issuing the
redirect.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx:
- Around line 99-144: After successfully exchanging the OAuth code
(exchangeToken called on the rvoClient created by createConfiguredRvoClient) and
building RvoImportReviewData via compareFields, persist the review payload (or a
flag indicating successful exchange) into the session/flash store tied to the
current principal/farm and then perform a server-side redirect to the same route
without query parameters to clear code/state; on subsequent loads read review
data from the session/flash instead of re-calling exchangeToken (and clear the
persisted data once consumed). Ensure verifyRvoState still runs before
exchangeToken and use the session key to skip exchangeToken when present so
refreshing the page won’t resend the single-use code.

---

Nitpick comments:
In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.rvo.tsx:
- Around line 224-250: Remove the unnecessary initialization of defaultAction in
the useEffect: declare let defaultAction: ImportReviewAction; (no initial value)
inside the loop that processes rvoImportReviewData, keep the existing switch on
RvoImportReviewStatus to assign the value, and then use initialChoices[id] =
defaultAction before calling setUserChoices; reference symbols: useEffect,
initialChoices, defaultAction, rvoImportReviewData, RvoImportReviewStatus,
getItemId, setUserChoices.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar._index.tsx:
- Around line 88-92: The Card currently toggles visibility by adding a "hidden"
class via cn; instead, render it only when needed by wrapping the Card JSX with
a conditional expression (e.g. {isRvoConfigured && ( ... )}) and remove the
isRvoConfigured ? "" : "hidden" branch from the cn call; ensure the wrapper
closes with </Card>)} so the component returns the Card only when
isRvoConfigured.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx:
- Around line 495-496: The handler currently returns an empty object for unknown
"intent" cases (the line with "return {}"); replace that silent fallback with an
explicit error response: locate the branch handling unrecognized intent values
and either throw a Response or return a JSON error payload with a 4xx status
(e.g., 400) and a clear message like "Unrecognized intent" so form submission
errors are surfaced and debuggable; keep the check that reads the "intent"
hidden field and ensure the error path is hit when no known intent matches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 17eaed40-0e20-4f06-a14d-92325ca8fb3e

📥 Commits

Reviewing files that changed from the base of the PR and between d53a9ac and 82a13e5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • fdm-app/app/components/blocks/fertilizer/utils.server.ts
  • fdm-app/app/components/blocks/fertilizer/utils.ts
  • fdm-app/app/integrations/rvo.server.ts
  • fdm-app/app/lib/error.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx
  • fdm-app/app/routes/farm.$b_id_farm._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx
  • fdm-app/package.json
  • fdm-rvo/src/compare.test.ts
  • fdm-rvo/src/process.test.ts
  • fdm-rvo/src/process.ts
💤 Files with no reviewable changes (1)
  • fdm-app/app/components/blocks/fertilizer/utils.ts
✅ Files skipped from review due to trivial changes (3)
  • fdm-app/package.json
  • fdm-rvo/src/process.test.ts
  • fdm-rvo/src/compare.test.ts

Comment thread fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fdm-app/.env.example`:
- Around line 73-76: Reorder the RVO environment variable lines so they are
alphabetically ordered to satisfy dotenv-linter's UnorderedKey rule: change the
block with RVO_CLIENT_ID, RVO_CLIENT_NAME, RVO_REDIRECT_URI,
RVO_PKIO_PRIVATE_KEY to the order RVO_CLIENT_ID, RVO_CLIENT_NAME,
RVO_PKIO_PRIVATE_KEY, RVO_REDIRECT_URI so the identifier RVO_PKIO_PRIVATE_KEY no
longer breaks the linter.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx:
- Around line 430-451: Before initiating the OAuth flow in the start_import
branch, verify the farm's KvK number (farm.b_businessid_farm) and reject the
request if missing; specifically, inside the intent === "start_import" block
(where getRvoCredentials, createConfiguredRvoClient, createRvoState,
generateAuthUrl, b_id_farm and request.url are used) add a guard that reads the
farm object and throws a Response (e.g., 400 or 422) when farm.b_businessid_farm
is falsy so you never call createConfiguredRvoClient / createRvoState / redirect
for farms that cannot complete the import.

In `@fdm-rvo/README.md`:
- Around line 40-76: The examples expose confidential PKIO/private credentials
and perform the token exchange in the browser (see createRvoClient,
generateAuthUrl, exchangeToken, pkioPrivateKey, handleAuthInitiation,
handleRvoCallback, and serverConfig.integrations.rvo); move any use of
pkioPrivateKey and the exchangeToken step to a backend-only handler
(loader/action/server endpoint) so the client only builds the initial redirect
URL (generateAuthUrl) and sends the user to the RVO auth page, then the server
handles the callback (reads code, uses pkioPrivateKey to call exchange token,
validates state) and returns or stores the resulting access token securely
(e.g., HTTP-only cookie or server session); remove pkioPrivateKey from
client-side config and update the README snippets to show client-side only
initiating redirect and backend-only callback/ token exchange flow.
- Around line 14-15: Update the README to match the current API: in the config
block replace the documented RVO_ENVIRONMENT with RVO_CLIENT_NAME; in the
examples remove destructured clientSecret and any environment variable passed to
createRvoClient so the call matches createRvoClient(...) signature; update the
compareFields invocation to pass the calendar argument (i.e., compareFields(...,
calendar, ...)) so the example matches the real call; and add the EXPIRED_LOCAL
member to the documented RvoImportReviewStatus enum to reflect the actual import
flow.

In `@fdm-rvo/src/process.ts`:
- Around line 137-169: The current sync only updates cultivation when
item.localCultivation exists and differs; for matched fields that have no local
cultivation we must also create one from RVO: in the UPDATE_FROM_REMOTE handling
(where item.localCultivation is checked) add an else branch for when
item.localCultivation is falsy that constructs b_lu_catalogue =
`nl_${item.rvoField.properties.CropTypeCode}`, calls
getDefaultDatesOfCultivation(tx, principal_id, b_id_farm, b_lu_catalogue, year)
and then calls addCultivation(tx, principal_id, b_lu_catalogue,
item.localField.b_id, defaultDates.b_lu_start, defaultDates.b_lu_end) so the RVO
cultivation is created for fields without an existing local cultivation (use the
same helper functions
removeCultivation/getDefaultDatesOfCultivation/addCultivation used elsewhere).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a8ad0140-0be2-42f1-b99e-590db6a7bd81

📥 Commits

Reviewing files that changed from the base of the PR and between d53a9ac and 89f208c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • fdm-app/.env.example
  • fdm-app/app/components/blocks/fertilizer/utils.server.ts
  • fdm-app/app/components/blocks/fertilizer/utils.ts
  • fdm-app/app/integrations/rvo.server.ts
  • fdm-app/app/lib/config.server.ts
  • fdm-app/app/lib/error.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx
  • fdm-app/app/routes/farm.$b_id_farm._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new._index.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx
  • fdm-app/app/types/config.d.ts
  • fdm-app/package.json
  • fdm-calculator/package.json
  • fdm-rvo/README.md
  • fdm-rvo/src/compare.test.ts
  • fdm-rvo/src/process.test.ts
  • fdm-rvo/src/process.ts
💤 Files with no reviewable changes (1)
  • fdm-app/app/components/blocks/fertilizer/utils.ts
✅ Files skipped from review due to trivial changes (7)
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new._index.tsx
  • fdm-app/app/lib/error.ts
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.$p_id.tsx
  • fdm-app/package.json
  • fdm-calculator/package.json
  • fdm-rvo/src/process.test.ts
  • fdm-rvo/src/compare.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.$p_id.tsx
  • fdm-app/app/routes/farm.$b_id_farm.fertilizers.new.custom.tsx
  • fdm-app/app/components/blocks/fertilizer/utils.server.ts
  • fdm-app/app/routes/farm.$b_id_farm._index.tsx
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar._index.tsx
  • fdm-app/app/lib/config.server.ts
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx

Comment thread fdm-app/.env.example
Comment thread fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx
Comment thread fdm-rvo/README.md Outdated
Comment thread fdm-rvo/README.md
Comment thread fdm-rvo/src/process.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx (1)

85-86: Consider consistent variable naming with the sibling route.

This file uses RvoImportReviewData (PascalCase) while farm.$b_id_farm.$calendar.rvo.tsx uses rvoImportReviewData (camelCase). Aligning naming conventions between these similar routes would improve consistency and reduce confusion when maintaining both.

Also applies to: 166-166, 180-180, 194-194, 217-217, 385-385, 469-469, 472-472, 484-484, 519-519, 523-523

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx around lines 85
- 86, The variable RvoImportReviewData is using PascalCase while the sibling
route uses camelCase; rename all occurrences of RvoImportReviewData to
rvoImportReviewData (including declarations and all references) to match the
sibling route naming, and ensure any related handlers or setters that reference
it are updated accordingly (keep type RvoImportReviewItem as-is). Update every
instance in this file where RvoImportReviewData appears so the variable name is
consistently camelCase (rvoImportReviewData) throughout.
fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx (1)

109-185: Consider redirecting away after successful OAuth code exchange.

After exchangeToken() succeeds, the code and state query params remain in the URL. OAuth authorization codes are single-use per RFC 6749, so a page refresh will fail at token exchange. While the error handler (lines 128-142) catches invalid_grant/expired errors with a user-friendly message, persisting the review data in session/flash storage and redirecting to a clean URL would provide a smoother experience.

The current approach works but may confuse users who refresh and see an error despite having successfully loaded data moments before.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.rvo.tsx around lines 109 - 185,
After a successful exchangeToken() call, persist the minimal post-exchange state
(e.g., a flash/session flag or necessary rvoCredentials) and then redirect the
user to the same route without the code and state query params so refreshes
won't attempt a reused authorization code; implement this by setting a session
flash (or session value) immediately after exchangeToken() and issuing a
redirect (throw redirect) to the current route with code/state removed, and then
in the subsequent loader/run path detect that flash/session flag and continue
the work (createConfiguredRvoClient, fetchRvoFields, compareFields to produce
rvoImportReviewData) so the token exchange happens only once and the UI is
loaded from the clean URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx:
- Around line 483-485: After parsing RvoImportReviewDataJson and
userChoicesJson, validate their shapes before proceeding: ensure
RvoImportReviewData (variable RvoImportReviewData) is an array (Array.isArray)
and that userChoices has the expected type/shape (e.g., object or array as
required by downstream code), and if validation fails throw or return a clear
error message indicating malformed input (include which payload failed). Update
the parse block that currently sets RvoImportReviewData =
JSON.parse(String(RvoImportReviewDataJson)) and userChoices =
JSON.parse(String(userChoicesJson)) to perform these checks and short-circuit
with a descriptive error when the structure is invalid so downstream code does
not assume a correct shape.

In `@fdm-rvo/README.md`:
- Line 5: Update the README package name to match the actual import used in the
codebase: replace the incorrect `@svenvw/rvo-connector` reference with
`@nmi-agro/rvo-connector` (the package imported in fdm-rvo/src/auth.ts). Make
sure the README description and any dependency mentions consistently reference
`@nmi-agro/rvo-connector` to avoid confusion.
- Line 64: Replace the non-standard Crypto.randomUUID() example with the correct
Node.js API by using crypto.randomUUID(); ensure the README example also shows
importing or requiring the crypto module (e.g., referencing the crypto
identifier used) so the call is valid — update the example line where
Crypto.randomUUID() appears and confirm the surrounding text/demo uses the
lowercase crypto symbol consistently.

---

Nitpick comments:
In `@fdm-app/app/routes/farm`.$b_id_farm.$calendar.rvo.tsx:
- Around line 109-185: After a successful exchangeToken() call, persist the
minimal post-exchange state (e.g., a flash/session flag or necessary
rvoCredentials) and then redirect the user to the same route without the code
and state query params so refreshes won't attempt a reused authorization code;
implement this by setting a session flash (or session value) immediately after
exchangeToken() and issuing a redirect (throw redirect) to the current route
with code/state removed, and then in the subsequent loader/run path detect that
flash/session flag and continue the work (createConfiguredRvoClient,
fetchRvoFields, compareFields to produce rvoImportReviewData) so the token
exchange happens only once and the UI is loaded from the clean URL.

In `@fdm-app/app/routes/farm.create`.$b_id_farm.$calendar.rvo.tsx:
- Around line 85-86: The variable RvoImportReviewData is using PascalCase while
the sibling route uses camelCase; rename all occurrences of RvoImportReviewData
to rvoImportReviewData (including declarations and all references) to match the
sibling route naming, and ensure any related handlers or setters that reference
it are updated accordingly (keep type RvoImportReviewItem as-is). Update every
instance in this file where RvoImportReviewData appears so the variable name is
consistently camelCase (rvoImportReviewData) throughout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0ec2c76e-3b60-4216-bc65-b6fb19f16c84

📥 Commits

Reviewing files that changed from the base of the PR and between 89f208c and eb0fa45.

📒 Files selected for processing (5)
  • fdm-app/.env.example
  • fdm-app/app/routes/farm.$b_id_farm.$calendar.rvo.tsx
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx
  • fdm-rvo/README.md
  • fdm-rvo/src/process.ts
✅ Files skipped from review due to trivial changes (2)
  • fdm-app/.env.example
  • fdm-rvo/src/process.ts

Comment thread fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx
Comment thread fdm-rvo/README.md Outdated
Comment thread fdm-rvo/README.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@fdm-rvo/README.md`:
- Line 15: The README's conflict diff key list is incomplete; update the
documentation to list the full set of FieldDiff keys by adding
b_acquiring_method, b_lu_catalogue, and b_bufferstrip alongside b_name,
b_geometry, b_start, and b_end so consumers and the UI see the complete expected
diff keys; reference the FieldDiff structure and ensure the README's "diff keys"
or "conflicting properties" section enumerates all seven keys exactly as named.
- Around line 127-129: The README example incorrectly passes accessToken as a
4th argument to fetchRvoFields even though the function signature is
fetchRvoFields(rvoClient, year, kvkNumber); update the example to call
fetchRvoFields(rvoClient, year, kvkNumber) (removing accessToken) OR, if
accessToken must be used, modify the function signature of fetchRvoFields to
accept and handle an accessToken parameter and update its internal usage
accordingly; ensure references to rvoClient, year, kvkNumber, and accessToken
are consistent after the change.
- Around line 63-65: Persist the generated state (the state variable created via
crypto.randomUUID() when calling generateAuthUrl(rvoClient, state)) in a
server-side session or a signed, HttpOnly cookie tied to the user, then verify
the incoming state parameter in your OAuth callback handler by comparing it to
the stored value and rejecting the flow if it differs or is missing; update the
README examples to show storing state before redirect and checking the
callback's state parameter (and clearing it after verification) so
generateAuthUrl, the state variable, and the callback logic are clearly linked
and validated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ac79ec89-a852-48b6-8ae4-8c762b99394e

📥 Commits

Reviewing files that changed from the base of the PR and between eb0fa45 and 99c76d0.

📒 Files selected for processing (2)
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx
  • fdm-rvo/README.md
✅ Files skipped from review due to trivial changes (1)
  • fdm-app/app/routes/farm.create.$b_id_farm.$calendar.rvo.tsx

Comment thread fdm-rvo/README.md Outdated
Comment thread fdm-rvo/README.md Outdated
Comment thread fdm-rvo/README.md
@SvenVw SvenVw merged commit 8ad4813 into development Mar 25, 2026
12 checks passed
Copy link
Copy Markdown
Collaborator

@gerardhros gerardhros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add option in fdm-app to retrieve fields from RVO

3 participants