Skip to content

feat(latepoint): add LatePoint booking integration - #197

Open
RishadAlam wants to merge 1 commit into
mainfrom
feat/latepoint
Open

feat(latepoint): add LatePoint booking integration#197
RishadAlam wants to merge 1 commit into
mainfrom
feat/latepoint

Conversation

@RishadAlam

@RishadAlam RishadAlam commented Jul 19, 2026

Copy link
Copy Markdown
Member

Description

Adds LatePoint as an action integration and registers it as a trigger source.
Eight write actions — create/update/cancel booking, create agent, create
customer, create order, and create/update coupon — plus the UI for configuring
them.

The free plugin resolves the field map and fires bit_integrations_latepoint_*;
all LatePoint model work lives in bit-integrations-pro.

Motivation & Context

LatePoint is a widely used WordPress appointment plugin that Bit-Pi already
integrates with, but Bit Integrations had no support for it at all. This closes
that gap so booking flows (create a booking on form submit, sync a customer to a
CRM when a booking is made) can be built here.

Bit-Pi's read methods — findBookingById, findAgentByEmail,
findCustomerByEmail — are deliberately not ported: they are lookups, not
actions. listBundles became the bundle dropdown instead.

Related Links: (if applicable)

  • Pro counterpart: Bit-Apps-Pro/bit-integrations-pro#135 — neither half is functional alone, so the two should merge together

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📚 Documentation update
  • ⚡ Improvement
  • 🔄 Code refactor

Key Changes

Backend

  • Added LatePointController with an activation check, an authorize
    endpoint, and four dropdown endpoints for agents, services, locations and
    bundles
  • Added RecordApiHelper dispatching the eight actions to their Pro hooks,
    logging every path through LogHandler::save
  • Added LatePoint to AllTriggersName and to customFormIntegrations,
    registering the Pro trigger with the flow builder

Frontend

  • Added the LatePoint integration UI — authorization, field map,
    integration layout, Utilities section, create and edit wizards
  • Added validateLatePointConf, shared by the create wizard and the edit
    screen so neither can save a flow that would fail on every run

Field mapping

  • Added fetched dropdowns for agent, service, location and bundle — these
    are per-flow configuration rather than values that vary per run
  • Added optional status enums (booking, order, payment, fulfillment) under
    Utilities, so they only apply when explicitly enabled

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Tests added/updated
  • Documentation updated if needed
  • README updated if needed

Notes for reviewers

  • Not runtime-tested. Neither repo has a test suite and no booking was executed
    against a live install; verification was static only (php -l, php-cs-fixer,
    ESLint, Prettier, plus hook-argument and field-key parity checks).
  • The logo asset is a generated placeholder, not a brand asset.
  • There is no latePoint entry in tutorialLinks.js, so the tutorial box
    renders empty (degrades gracefully, same as FluentCart).

Changelog

  • New Actions
  • LatePoint: 8 new actions added (Pro).
  • New Triggers
  • LatePoint: 5 new events added (Pro).

Ports the LatePoint write actions from Bit-Pi: create/update/cancel booking,
create agent, create customer, create order, and create/update coupon. The
free side only resolves the field map and fires bit_integrations_latepoint_*;
all LatePoint model work lives in the Pro plugin.

Bit-Pi's read methods (findBookingById, findAgentByEmail, findCustomerByEmail)
are not ported — they are lookups rather than actions. listBundles became the
bundle dropdown instead.

Agent, service, location and bundle are per-flow configuration, so they are
fetched dropdowns rather than field-map rows; everything the flow varies per
run stays mappable. The lists are read straight from the latepoint_* tables
because the Os*Model classes only autoload on LatePoint's own screens, and are
held in component state so they are never serialized into flow_details.

validateLatePointConf covers what the field map cannot express: the required
dropdown ids, and the customer fields whose necessity depends on the selected
customer option. Both the create wizard and the edit screen call it, so
neither can save a flow that would fail on every run.
Copilot AI review requested due to automatic review settings July 19, 2026 04:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new integration for the LatePoint booking plugin, adding backend controllers, API helpers, and routes, alongside frontend configuration screens, action layouts, and field mapping components. The review feedback suggests ensuring that non-persisted configuration fields (such as latePointFields) are rebuilt on mount when editing an integration to prevent empty options or crashes. Additionally, several controller methods in LatePointController (refreshAgents, refreshServices, refreshLocations, and refreshBundles) should be declared as static since they do not access instance state and are used as static route callbacks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +76 to +83
useEffect(() => {
const keys = listsForAction(mainAction)

if (keys.length > 0) {
refreshLatePointLists(setLists, setIsLoading, keys)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mainAction])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

When editing an integration, non-persisted configuration fields like latePointFields must be rebuilt on mount from the saved action to prevent empty options and component crashes. Rebuilding latePointFields here ensures that the field mapping section renders correctly when editing an existing flow.

Suggested change
useEffect(() => {
const keys = listsForAction(mainAction)
if (keys.length > 0) {
refreshLatePointLists(setLists, setIsLoading, keys)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mainAction])
useEffect(() => {
if (mainAction) {
const keys = listsForAction(mainAction)
if (keys.length > 0) {
refreshLatePointLists(setLists, setIsLoading, keys)
}
if (!latePointConf.latePointFields) {
setLatePointConf(prevConf =>
create(prevConf, draftConf => {
draftConf.latePointFields = fieldsByAction[mainAction] || []
})
)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mainAction])
References
  1. When editing an integration, ensure that non-persisted configuration fields (like static fields) are rebuilt on mount from the saved action without modifying the saved field mappings, to prevent empty options and component crashes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not applying — the premise does not hold here, though there is a real point underneath it.

latePointFields is persisted. It is written into conf when the action is chosen:

draftConf.latePointFields = fieldsByAction[value] || []

conf is serialized into btcbi_flow.flow_details on save, so the edit screen gets it back from the database and the field-map section renders. FluentCartIntegLayout stores fluentCartFields the same way — it is the established pattern for this component family.

There is also no crash path. Both read sites are guarded:

{mainAction && latePointConf.latePointFields && (  )}
latePointConf?.latePointFields?.filter(fld => fld.required === true) || []

The genuine issue your suggestion gestures at is staleness, not absence: because the field definitions are persisted, a future release that adds a field to CreateBookingFields will not surface it for flows saved before that release. Deriving latePointFields from mainAction at render time would fix that and drop the copy from flow_details — but it changes the saved config shape and applies equally to FluentCart and the rest of the family, so it belongs in its own change rather than this PR.

wp_send_json_success(true);
}

public function refreshAgents()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since refreshAgents does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshAgents()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not applying — this is a style preference rather than a defect, and it applies to all four refresh* methods on this controller.

Route::action() dispatches through reflection and supports both forms explicitly:

$response = $reflectionMethod->invoke($reflectionMethod->isStatic() ? null : new $invokeable[0](), $data);

A non-static callback is instantiated and invoked correctly, so nothing is broken. There are 30 non-static refresh* methods across backend/Actions/ today, and this controller was modelled on FluentCartController, whose refresh methods are also non-static.

Static is the wider majority (64 vs 30), so this is a fair thing to standardise — but as a single sweep across the directory rather than only on the newest integration. The same suggestion was raised and declined for the same reason on #196, and I would rather LatePoint and BadgeOS stay consistent with each other than have one of each.

wp_send_json_success($response, 200);
}

public function refreshServices()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since refreshServices does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshServices()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same as the refreshAgents thread above — not applying, for the same reason. Route::action() invokes non-static callbacks correctly via reflection, and this controller follows FluentCartController. Worth standardising across backend/Actions/ as one sweep rather than on this integration alone.

wp_send_json_success($response, 200);
}

public function refreshLocations()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since refreshLocations does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshLocations()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same as the refreshAgents thread above — not applying, for the same reason. Route::action() invokes non-static callbacks correctly via reflection, and this controller follows FluentCartController. Worth standardising across backend/Actions/ as one sweep rather than on this integration alone.

wp_send_json_success($response, 200);
}

public function refreshBundles()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since refreshBundles does not access any instance properties ($this) and is registered as a static route callback, it should be explicitly declared as static to maintain consistency and adhere to coding standards.

    public static function refreshBundles()
References
  1. In PHP, controller methods that do not access instance state ($this) and are registered as static route callbacks should be explicitly declared as static to maintain consistency and adhere to coding standards.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same as the refreshAgents thread above — not applying, for the same reason. Route::action() invokes non-static callbacks correctly via reflection, and this controller follows FluentCartController. Worth standardising across backend/Actions/ as one sweep rather than on this integration alone.

@github-actions

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

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.

2 participants