Moar quick forms and Quick Form Plans

went on a bit of a bender this week. I showed the quickforms I worked on at the last call, followed up with Serena and Nano (primary partners) to get feedback and of course got feedback on the Dev call, so took a stab at the next steps.

Summary

The short of it is I wanted to create a more effective workflow for entering lots of quickform data at once, adding review steps (which are practically necessary when you’re getting support from technical assistance or agronomists), and allowing people to add / change data before final submission (which requires an intermediate way to store quickform data). It also helps visualize the inputs in a way that is more intuitive (by time, by field, etc.) - this is key to identifying the errors and fixing them before actually creating logs/assets/etc.

Other uses

Anywhere that you need to input lots of data at once, and especially data that generates many repeated or even many different types of logs/assets/entities, this strategy of using Plans w/ Quickforms may have a place. I also think some of the customized UI elements could be handy elsewhere as well. I’m also excited to copy Mike’s intake and approval process (used with the Conservation Planner work) here as a next step.

Review

If you want to take a look:

So here’s the one pager describing the work

Here’s the design brief (written), and here’s screenshots (easier to get the gist).

I also just saved all my conversations from this work and put them here - from the Quick Form creation all the way through this most recent Quick Form Plan work. I know that @Symbioquine and @mstenta have asked to kind of see the process… it’s a lot but anyway, here’s what it looks like.

There were lots and lots of design considerations and details, and I hope the learnings from this can lead me to continue to utilize this pathway of using Plans with Quickforms in other ways in the future. Overall, excluding the quickform creation itself, this last batch of work probably took 15 hours total… so it’s a lot of back and forth and checking, but the outcome I think is pretty nice hopefully.

This is definitely something that’s more useful for the multi-farm use case, or cases where there’s large data entry and active data entry support (TAP, agronomist) compared to direct on farm work.

Would love any thoughts and review on this if it’s of interest!

1 Like

Made a quick walkthrough video, I think that may be easier for some than walking through the notes:

1 Like

This looks really nice @gbathree!

“Drafting” quick form entries is something that we’ve talked about before. I’d love to figure out a way to support that in an official capacity in farmOS core. Glad to see some experimentation happening to explore it. :slight_smile:

2 Likes

This looks great, Greg! I like the changes you made. I don’t have any other suggestions at this time (I think I’d have to actually use it to get more specific).

3 Likes

@mstenta shall I continue? I can do it in a few days, but it will take many hours (which is fine). I can always roll things back if it’s horrible but also don’t want to waste time.

I know no direction is ever perfect, but nice to know if I’m at least not heading way off in a totally wrong direction :slight_smile:

2 Likes

@gbathree Do you mean continue with building your custom quick forms? Or with exploring a generalized farmOS core contribution for draft quick form submissions?

If the former, that’s up to you! If the latter, I would say that deserves some more dedicated discussion about the best design pattern and implementation details by people before sending the bots to work. :slight_smile:

We may find that there are as many requirements as there are possible quick forms… in which case a generalized solution may not be possible… or would be very simple but leave it to individual quick forms to implement most of their own details. This is the kind of discussion I’m thinking of, and a census of all the existing quick forms (and potential future ones) would be part of that.

2 Likes

But don’t let that hold up your custom implementation! IMO it’s not a waste of time to work through a couple of specific implementations without trying to generalize first. Then we have more real world experience to bring to the generalization discussion.

2 Likes

Really awesome to see our conversations transform into real tools :slight_smile:

Can’t wait to start testing and give you feedback

We already did a spanish translation of your video to show tomorrow to a bunch of TAPs

Will love to hear also what @serena.x that is more experienced thinks about it

2 Likes

@mstenta nothing generalized yet, though I’d like to think this moves us forward.

I had it summarize (both to help me understand because it’s been a minute, and to share back to you all), I think this is actually pretty helpful for anyone interested, of course dive into the code more if you have further questions.

The three entities

Plan (plan entity, bundle = plan_type)

  • One plan per workflow instance: e.g. a single yearly_fields_plan for “Smith
    Farm 2026 Fields”.
  • Holds top-level metadata + workflow state (planning / active / done /
    abandoned).
  • The bundle config declares allowed_forms, empty_state_text, next_flow_id.

Plan Record (plan_record entity — yes, that’s the term)

  • farmOS’s generic join entity. It exists to attach other things to a plan
    without bloating the plan schema.
  • A plan_record always has: a reference to a plan, and a reference to some
    other entity (asset, log, quantity, or — in our case — raw form data).
  • We add one new plan_record bundle: quickform_data. Each row stores one
    quickform submission: raw $form_state->getValues(), summary text,
    touched_locations, created_entity_uuids.
  • So when a user fills out a quickform inside a plan, the result is one
    plan_record of bundle quickform_data attached to that plan. The actual
    assets/logs created when the form is finalized are referenced by UUID inside
    that plan_record (not directly on the plan).

Quickform (existing QuickFormBase subclasses, 13 of them)

  • These don’t change shape. We add a non-abstract default summarize($values)
    and an optional QuickFormSummaryInterface.
  • A quickform doesn’t know about plans. The plan context is injected by the
    controller wrapping it.

How it actually flows

┌─────────────────────────────────────────────────────────────┐
│ Plan (yearly_fields_plan, state=planning) │
│ ─ name, year, state, allowed_forms (from bundle config) │
└───────────────┬─────────────────────────────────────────────┘
│ 1..n

┌─────────────────────────────────────────────────────────────┐
│ plan_record (bundle: quickform_data) │
│ ─ form_id: “farm_field” │
│ ─ raw_values: {…$form_state->getValues()…} │
│ ─ summary: “Added 4 fields, 12.3 ac” │
│ ─ touched_locations: [uuid, uuid] │
│ ─ created_entity_uuids: [uuid, …] ← idempotency key │
└───────────────┬─────────────────────────────────────────────┘
│ on Finalize: replay raw_values through QuickFormBase

┌─────────────────────────────────────────────────────────────┐
│ Real farmOS entities: asset–land, log–activity, … │
│ (created via the existing QuickFormBase submit path) │
└─────────────────────────────────────────────────────────────┘

Two phases:

  1. Draft phase — user opens a quickform from inside the plan page. The form
    renders normally; on submit, instead of creating assets/logs, the controller
    serializes values into a quickform_data plan_record. Plan state = planning.
  2. Finalize — user clicks Finalize on the plan. The Finalizer service iterates
    plan_records, builds a synthetic FormStateInterface for each, replays the
    quickform’s submit handler inside a Drupal transaction. The UUID short-circuit
    (Plantings + Fields) makes replays idempotent. Plan state → active (or done).

How the page/components connect to the plan

This is the part where your intuition is right: they’re bundled in the module,
not modeled on the plan entity.

  • plan–quickform-plan.html.twig — a Twig template override in
    quickform_plans/templates/. Drupal picks it for any plan whose bundle is one
    of our three. It’s not referenced by the plan; it’s selected for the plan by
    Drupal’s theme suggestion machinery.
  • Extra-field pseudo-fields — declared in quickform_plans.module via
    hook_entity_extra_field_info(). They’re things like “Forms Completed sidebar”,
    “Add a form” picker button, “Activity-by-Field” custom component, “Next: …”
    hint card. They render on the plan page because the template asks for them,
    but they live in the module — they aren’t real fields on the plan bundle.
  • Add-form picker — core use-ajax off-canvas modal, controller in module
    (AddRecordController).
  • Forms Completed sidebar — a Drupal Views block filtered by current plan id,
    contextual to the page.

So the connection is purely: bundle name → template suggestion →
module-supplied render pieces. The plan entity itself has no field pointing at
any of these components. Swap the module out and the plan still exists as a
bare Drupal plan; the rich page just disappears.

That separation is intentional — it’s why the design avoided a custom Plan
controller and why plan_type config bundles (not a plugin type) are the
contribution point. New flows are YAML, not PHP.

TL;DR for sharing

  • Plan = the workflow container.
  • Plan Record (bundle = quickform_data) = the join row that stores one
    quickform submission, draft-style, before it becomes real entities.
  • Quickforms = unchanged; replayed at finalize time against synthetic form
    state.
  • Page UI = bundled in the quickform_plans module via twig override +
    extra-field pseudo-fields. The plan entity has no direct knowledge of those
    components.

Oh I see - you’re talking about using the existing plan and plan_record entities to represent drafts… (I only skimmed earlier info and didn’t digest that bit)… that’s interesting. It feels like a bit of an odd fit to me, but it could work. :thinking:

The plan and plan_record entities are permanent records… whereas a very simple “draft submission” concept only needs to be a temporary record (of the form state values).

Creating plan and plan_record entities would be overkill for most quick forms, I think. But your use-case is a lot more complex than most quick forms. In fact, I might suggest that you are no longer creating a “quick form”. :wink:

My suggestion might be to flip/reframe the way you’re approaching your requirements. Instead of thinking “I need a quick form that can do all of this”, maybe it’s “I need a multi-person workflow with custom forms that create and edit records”. Subtle distinction, and ultimately the code and logic might end up being very similar… but you let go of the need to shoehorn this into a “quick form” plugin.

The farmOS Conservation Planner incorporates many custom forms into the farm_rcp planning workflow, and none of them are “quick forms”. They also reload entities back into the forms so they can be edited after the fact. It’s a great example of a complex workflow that generates (and allows editing) multiple disparate records, all under the umbrella of a “plan”. In your case, the “plan” is something like “a workflow for gathering records from growers”, with its own status options potentially.

This is not a general solution to “drafting quick form submissions” though, but it doesn’t need to be. Quick forms are meant to be small, one-time forms for quickly creating a few simple records. “Drafts” in that context might be useful for little things, but your case sounds more like a longer process, potentially involving multiple people reviewing, etc. You probably don’t even need to save “drafts”… you can save the actual asset/log entities and add the “Needs review” flag to them, but allow editing them inside the plan forms, and have a final step that removes that flag when everything is reviewed.

That feels like a good way to achieve what you need.

2 Likes

Thank you Mike! That’s super helpful… it’s easy to work your way down a rabbit hole and even if agents are fast… they do come with a cost of time, money, effort and rework.

I’ve plugged you response back and requested a review - here’s the response. Overall it agrees (I told it disagreeing is totally kosher btw… it’s not a total sycophant when appropriately prompted).

I’m going to re-run based on what we learned in our initial investgation, plus your comments and thoughts (feel free to add any more) and rework the plan.

If others have thoughts also before I get working, I’m all ears

mike’s response.txt (6.5 KB)

1 Like

I’ll be curious to see if you can actually get database transactions to do the heavy lifting for you here.

In particular, I think you need to figure out whether you can lock the draft quickform_data DB row(s) so no additional changes to it can happen concurrently with the transaction that is attempting to finalize that draft.

2 Likes

Ok - updated based on feedback.

I hope this addresses some of those concerns by using a more natural farmOS approach for the draft. It saves the draft state from the quickform as logs/assets directly (marked status: pending and flag: ‘in review’). For edit actions on the quickform, it just cascade deletes and then recreates the entities. Only when the quickform is submitted in it’s final form (ie status: done and removed ‘in review’ flag), then the quickform is no longer an editable reference to those entities.

Side note: Quantities are deleted with their logs, though taxonomy terms remain (a plus sometimes, a minus sometimes).

Full plan attached w/ more details. I think I’m going to give this a go but welcome any other thoughts of feedback.

2026-05-20-quickform-plans-overview-rev2.txt (11.5 KB)

Hopefully this is a better approach!

Any reason it doesn’t edit the existing assets/log? The benefit of editing (over deleting and recreating) is you keep all the history of edits in asset/log revisions.