Build Hive CPQ

KINDAback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

Configure, price and quote for manufactured products, where the core is a constraint solver over option rules that must explain why something is unavailable, and the discipline is that a quote is a priced snapshot that cannot silently change after it is sent.

estimated effort multi-daythe files for this build are in the project pack

RulesDeclarative constraints over options, evaluated by a solverExplanationEvery exclusion traceable to the rules that caused itPricingExact decimal with a priced snapshot per quoteVersioningRules and price lists versioned, quotes pinned to a versionRuntimeTypeScript with PostgreSQL

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server, the solver and the document renderer.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree locally

    Why Versioned rules, price lists and immutable quotes want real constraints.

    Get it Docker or your package manager. open ↗

    Verify psql connects and an insert-only table can be enforced

  3. installfree

    Why Percentage discounts over rounded line items produce arithmetic a customer will dispute.

    Get it Any well-tested decimal library for your runtime.

    Verify Line items sum exactly to a discounted total

  4. have readyfree, and it takes a conversation with whoever knows the product

    Why Contradiction detection, explanation quality and solver performance are all uninteresting below a few dozen rules and become the whole problem above a hundred.

    Get it Take a real configurable product and write out its rules, including the ones the sales team keep in their heads.

    Verify Fifty rules exist, including at least one pair that turns out to contradict

  5. decidefree

    Why Where rounding happens and whether discounts apply per line or to the total are decisions that change the number, and reconstructing them later from disputed quotes is worse.

    Get it Decide the rounding point, the discount order and the approval threshold, and write them down before pricing anything.

    Verify A written note states the rounding point, discount order and approval threshold

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- products(id, name, active_from, active_to)
- options(id, product_id, group_id, code, name, active)
- rules(id, product_id, version, kind, expression_json, message) · kind is requires, excludes, implies or constrains
- rule_versions(product_id, version, published_at, published_by)
- price_lists(id, product_id, version, currency, valid_from, valid_to)
- prices(price_list_id, option_id, amount_minor, kind) · kind is base, per_unit or surcharge
- quotes(id, customer_ref, product_id, rule_version, price_list_id, configuration_json, total_minor, currency, valid_until, status, sent_at)
- A quote pins the rule version and price list it was built from, because both will have changed by the time the customer replies.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/cpqRules, prices and customer quotes.
ROUNDING_POINTrequiredline_itemWhere rounding happens. Applied once, stated on the quote.
DISCOUNT_APPROVAL_THRESHOLD_BPSrequired1000Above this a quote needs approval before sending.
QUOTE_VALIDITY_DAYSrequired30How long a quote holds its price. Pinned data makes this enforceable.
SOLVER_TIMEOUT_MSrequired200Configuration must stay interactive. A slow solver is an abandoned configurator.
ALLOW_EDIT_SENT_QUOTESrequiredneverPresent as a constant so the answer is visible. Revisions, not edits.

The build, in order

  1. Rules as data, not code

    Option constraints are declarative and can be changed without deploying.

    1. Four kinds cover most of it. Code means every change is a deployment.

      Files src/rules.ts

    2. Before a customer finds them, which is the alternative.

      Files src/analyse.tssrc/analyse.test.ts

    3. An option nothing can select is a rule mistake, not a product decision.

      Files src/analyse.ts

    done when · tick each as it passes
    watch out
    • Expressing configuration rules as conditional code means every catalogue change is a deployment, and after two hundred rules nobody can tell whether a new one contradicts an existing one. Keep them as data, and check the rule set for contradictions and unreachable options when it is published.
  2. Saying why an option is unavailable

    Every disabled option can explain which choice disabled it.

    1. Not afterwards. A separate explanation path drifts from the real one.

      Files src/evaluate.ts

    2. Both. The rule alone does not tell the user what to change.

      Files src/explain.ts

    3. And make applying it actually work.

      Files src/suggest.ts

    done when · tick each as it passes
    watch out
    • Greying out an option without a reason is the single most common complaint about configurators, because the user cannot tell whether it is genuinely unavailable or whether they made a wrong choice three steps earlier. Track the excluding rules during evaluation rather than recomputing an explanation afterwards, since the second version drifts from the first.
  3. Pricing a configuration exactly

    A configured product prices correctly including the awkward parts.

    1. Percentages on rounded lines produce arithmetic customers dispute.

      Files src/money.ts

    2. The threshold itself is where off-by-one pricing lives.

      Files src/pricing.test.ts

    3. And assert lines sum exactly to the total.

      Files src/pricing.ts

    done when · tick each as it passes
    watch out
    • Applying a percentage discount to a total computed from rounded line items gives a different answer from discounting each line, and a customer checking your arithmetic will find the discrepancy. Decide where rounding happens, apply it once, and state the rule.
  4. Quotes that do not change after sending

    What the customer received is what you can still see months later.

    1. At creation. Referencing current means the quote changes itself.

      Files src/quotes.ts

    2. Revisions, not edits. The customer has a copy.

      Files src/quotes.tstests/immutable.test.ts

    3. An expired quote accepted at the old price is a loss you chose by accident.

      Files src/validity.ts

    done when · tick each as it passes
    watch out
    • A quote that references the current price list rather than a pinned one reprices itself when the catalogue is updated, and the customer accepts a figure that no longer matches what you sent. Pin everything at creation, and make a sent quote a snapshot rather than a view.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more sales outreach.