← Technical case study / Documentation

BMS: pricing rules, architecture & verification

Methods, implementation notes and reproduction instructions.

BMS Pricing & Quoting Application — Technical Case Study

Microsoft Access · VBA · Business rules · Workflow automation

Real project in context

I independently designed and developed an Access application that combined four supplier catalogues, material and labour estimates, pricing review and quotation/BOQ output. A manager supplied the initial workflow framework and requirements. Typical quotation preparation fell from approximately 2–7 days to half a day, depending on complexity; this is a retrospective estimate.

Version scope: the Notion walkthrough primarily shows the simplified maintenance-pricing application. The pricing rules and interface below concern the expanded project-pricing version. Published code is independently reconstructed with synthetic inputs; the employer database and source are excluded.

Project-pricing interface with blank fields showing the layout

The screenshot documents the interface, not a calculated quotation. A worked example appears in pricing requirements.

Technical evidence

Area Evidence
Architecture Data flow and illustrative model
Requirements Pricing rules, precedence and acceptance scenarios
Implementation Calculation design and input contract
Source Pricing function and test harness
Verification Expected scenarios and execution record
Form dependencies Subform aggregation
Known defect Refresh investigation

What the expanded project-pricing workflow did

I proposed support for both an overall gross margin and category-specific margins. The interface instructed users to choose one method. The inspected implementation used the category result when available and otherwise the overall result. Discount followed margin pricing. See the requirements for exact inputs and fallback behaviour.

Material codes copied from a supplier library populated description and unit price. For applicable libraries, users manually confirmed the exchange rate through an Access form when opening the material library; displayed costs were in EUR.

Run the independent VBA example

  1. Open Microsoft Access or Excel on Windows and press Alt+F11.
  2. Import PricingDemo.bas and TestPricingDemo.bas through File → Import File. When pasting, omit the Attribute VB_Name lines.
  3. Compile if available, then run RunPricingDemoTests and inspect the Immediate window (Ctrl+G).

The function accepts five synthetic costs, gross-margin rates and a discount. It does not implement forms, catalogue lookup, PDF output or email drafting. Test expectations and the recorded execution scope are in test scenarios.

Architecture & Data Flow — Conceptual Reconstruction

This diagram and entity model are independent abstractions, not an export of the original Access database. The exact original table schema, keys and cardinalities are not publicly supplied.

Functional flow

Conceptual diagram for Conceptual architecture
Conceptual diagram · Open full size
View diagram source
flowchart TD
    A[Project information] --> B[Material and labour estimates]
    C[Supplier price catalogue] --> B
    B --> D[Five independent subform summaries]
    D --> D1[TotalSubform aggregation]
    D1 --> E[Refresh action]
    P[Admin / Other / Panel costs] --> E
    E --> F[Update admin and project cost totals]
    F --> G[Calculated form controls]
    H[Five category margin rates] --> G
    I[Project-wide margin rate] --> G
    J[Discount] --> G
    G --> K{Category calculated result available?}
    K -->|Yes| L[Use category selling]
    K -->|No| M[Use project-wide selling]
    L --> N[Store or display Project Selling]
    M --> N
    N --> O[Quotation and BOQ output]

The original TotalSubform expression explicitly added five subform summary controls, each wrapped in Nz(). The original Refresh event assigned point totals, administration cost and ProjectCost before selecting between the two calculated selling controls. It used Me.Refresh at the end. The diagram does not represent Access's internal recalculation schedule or prove the update order is free of stale-value risks.

For the confirmed cost-dependency chain, see subform_aggregation.md. No row-by-row mapping from those five control values to the entities in the illustrative data model is asserted.

Illustrative relational model

Conceptual diagram for Conceptual architecture
Conceptual diagram · Open full size
View diagram source
erDiagram
    PROJECT ||--o{ MATERIAL_ESTIMATE : contains
    PROJECT ||--o{ LABOUR_ESTIMATE : contains
    PROJECT ||--o| PRICING_REVIEW : receives
    SUPPLIER ||--o{ CATALOG_ITEM : provides
    CATALOG_ITEM o|--o{ MATERIAL_ESTIMATE : referenced_by

    PROJECT {
      int project_id PK
      string reference
      string status
    }
    MATERIAL_ESTIMATE {
      int line_id PK
      int project_id FK
      int catalog_item_id FK
      decimal quantity
      decimal cost
    }
    LABOUR_ESTIMATE {
      int line_id PK
      int project_id FK
      decimal hours
      decimal cost
    }
    SUPPLIER {
      int supplier_id PK
      string supplier_name
    }
    CATALOG_ITEM {
      int catalog_item_id PK
      int supplier_id FK
      string product_code
      decimal unit_cost
    }
    PRICING_REVIEW {
      int review_id PK
      int project_id FK
      decimal project_margin
      decimal discount
      decimal calculated_selling
    }

Illustrative only: All entity names, foreign keys, data types and cardinalities are proposed generic labels. In particular, the actual pricing mode was not stored as a verified pricing_mode field; the provided form event selected a non-Null category result first.

Design boundaries

  • Original reported application: supplier catalogues, Access relational records, forms/subforms, VBA actions, quotation/BOQ generation and Outlook email drafting.
  • Reconstructed public code: a pure VBA calculation function on synthetic arrays. It does not read/write tables or implement Access form events.
  • System limitation reported: double-clicking the original Refresh button could yield incorrect costs/selling; exact cause is unknown. See refresh investigation.
  • Potential redesigns (not historical deliverables): explicit mode choice, input completeness guidance, calculation in one deterministic routine, guarded refresh events, and a front-end/back-end deployment architecture.

Requirements-to-Implementation Mapping (Retrospective)

Why two pricing routes?

Original request: one gross-margin rate on the entire quote.

Refinement: variable project complexity could justify distinct margins for Material, Labour, Administration, Other Costs and Panels. The manager agreed to support both pricing approaches in the more detailed review form.

Confirmed rules and implementation evidence

ID Requirement or behaviour Evidence / qualification
BR-01 Allow one manually entered project-wide gross-margin rate CalSelling control expression; screenshot value 30% was test input, not a default
BR-02 Allow five category-specific gross-margin rates NewCalSell form expression
BR-03 Apply discount after gross-margin-based selling price Both provided expressions multiply by (1 - Nz(Discount)); intentional client discount
BR-04 Give category pricing precedence when its result is non-Null If IsNull(NewCalSell) Then ... Else ... in BtnRefresh_Click
BR-05 Fall back to project-wide result when category result is Null Same branch; if no project rate is entered either, selling may remain blank
BR-06 Category pricing requires all five rate fields; Other/Panel cost boxes must contain values, including explicit zero when absent Reported usage requirement; category expression omits Nz() for those two costs
BR-07 Tell users not to use both margin sections at once Interface instruction; no blocking check shown in the provided event procedure
BR-08 Recompute/store project totals and update selling using Refresh Event assignments to point/admin totals, ProjectCost, and ProjectSelling, followed by Me.Refresh
BR-09 Use calculated price for quotation/BOQ; no manual final-price override Those output functions not included here

If a category rate is missing, the category result becomes unavailable. Selling can still use a valid project-wide result; otherwise it remains blank.

Calculation contract inferred from expressions

Project-wide gross margin:

S_project = ProjectCost / (1 - ProjectMargin) * (1 - Discount)

Category-specific gross margin:

S_category = (1 - Discount) * (Material/(1-MaterialMargin) + Labour/(1-LabourMargin) + Admin/(1-AdminMargin) + Other/(1-OtherMargin) + Panel/(1-PanelMargin))

Selection (pseudocode, not original VBA):

refresh point totals
refresh administration cost
refresh ProjectCost from calculated aggregate
if CategorySelling is Null:
    ProjectSelling = ProjectWideSelling
else:
    ProjectSelling = CategorySelling
refresh displayed record

ProjectCost in the original was calculated from Nz(TotalSubform) + Nz(TotalAdminCost) + Nz(TotalOtherCost) + Nz(PanelAllowance). The synthetic VBA demo instead sums the five cost categories for standalone verification. The inspected TotalSubform formula sums five distinct subform summary controls via Nz() (controller, BMS points/material and three labour-related summaries). Their individual formulas and runtime values remain unverified. See subform aggregation.

The original category expression used Nz() for Material, Labour and Administration, but not for Other Cost and Panel Allowance. Discount used Nz() in both routes. Rates were supplied via form controls (typically shown as percentages). Historical division-by-zero handling, field precision and rounding were not provided.

Retrospectively drafted acceptance scenarios

Scenario Given When Expected
AC-01 Valid project margin; category result unavailable Refresh Project-wide result after discount
AC-02 Five category rates and corresponding costs available Refresh Category result after discount, regardless of whether a project margin is also present
AC-03 At least one category margin missing; valid project margin available Refresh Fall back to project-wide result
AC-04 Category calculation unavailable; project margin also missing Refresh No calculated selling amount (Null / blank)
AC-05 No Other or Panel cost User explicitly enters zero in these fields Category calculation can proceed if rates and other inputs are valid
AC-06 Existing price and project costs Discount is entered, then Refresh Discount is applied after margin-based price
AC-07 Refresh button activated twice quickly User double-clicks Desired future criterion: remain correct; observed original behaviour violates it (defect)

These are retrospective analysis artifacts. AC-07 is a target for a corrected implementation, not a claim of passing historical UAT.

Applied discount ≠ preserved gross margin

Example: cost 100, gross-margin rate 20%, discount 10% gives (100/0.8)*0.9 = 112.50. Achieved gross margin is (112.50-100)/112.50 = 11.11% (rounded). This was an intentional discount-after-pricing workflow, not evidence that the organisation required a post-discount 20% gross margin.

Implementation Notes, Evidence Boundaries & Choices

Original vs demonstration

Aspect Original application Independent VBA example
Route selection Category result when non-Null, otherwise overall result Same precedence for supplied arrays
Cost aggregation Five subform summaries plus administration, other costs and panel allowance Sum of five category cost inputs
Missing inputs Nz() protected some terms; Other/Panel needed explicit zero for category pricing Missing Material/Labour/Admin costs act as zero; missing Other/Panel blocks the category route
Discount Applied after gross-margin pricing Same order
Rounding Original storage precision not established Decimal intermediates; one final nonnegative half-up cent rounding
Validation Complete original validation rules unavailable Reject negative costs, invalid discounts and invalid margins on the selected route
UI behaviour Form events, calculated controls and output actions Stateless calculation only

The example accepts costs already expressed in EUR. In the application, users manually confirmed an exchange rate through an Access form for applicable supplier libraries. That catalogue process is outside the standalone function.

Design choices

I use Decimal intermediates to reduce binary midpoint rounding risk, distinguish explicit zero from missing input, and keep discount placement visible. Unused-route margins are not comprehensively validated. Verification evidence is centralised in test scenarios.

Possible future improvements

  • Make the pricing mode explicit and reject simultaneous conflicting inputs.
  • Show the specific missing fields rather than an unexplained blank selling amount.
  • Calculate from stable input values in one routine before updating the form.
  • Define rounding and discount policy with worked examples.
  • Investigate the actual form dependency chain before changing event behaviour; see Refresh investigation.

Pricing Verification Scenarios — Independent Examples

Execution record: an earlier version of the standalone pricing demo passed 14 synthetic scenarios in Access. The current Decimal version and its 19-scenario harness have not been executed in Windows Access. The amounts below are independent arithmetic expectations. Neither version tests the original Access form or its Refresh defect.

Common synthetic inputs

Cost order: Material €100, Labour €200, Administration €50, Other €20 and Panel €80; total €450. Category margins: 20%, 25%, 10%, 5% and 15%. Rates are decimal fractions in VBA.

Automated demonstration test matrix

ID Input / behaviour Expected
T-01 Overall margin 20%, category route unavailable, discount 10% €506.25
T-02 All category margins, no overall margin, discount 10% €506.15
T-03 Both methods available Category result €506.15
T-04 Missing category margin, overall margin 20% Overall result €506.25
T-05 Missing category margin and overall margin Null
T-06 Overall margin 20%, blank discount €562.50
T-07 Other/Panel missing, overall margin 20%, discount 10% €393.75
T-08 Other/Panel missing and no overall margin Null
T-09 Other/Panel costs zero; all margins and discount zero €350.00
T-10 Missing Material cost; other costs present; all rates zero €350.00
T-11 Overall margin 100% Validation error
T-12 Negative cost Validation error
T-13 Discount 120% Validation error
T-14 Two calls with identical inputs €506.25 on each call
T-15 €2.01 cost, zero margin, 50% discount €1.01
T-16 €2.03 cost, zero margin, 50% discount €1.02
T-17 €2.0098 cost, zero margin, 50% discount €1.00
T-18 €2.0102 cost, zero margin, 50% discount €1.01
T-19 Two €0.005 costs, zero margins and discount €0.01 after summing

Independent arithmetic check

Overall: 450 / 0.8 × 0.9 = 506.25.

Category: (100/0.8 + 200/0.75 + 50/0.9 + 20/0.95 + 80/0.85) × 0.9 ≈ 506.1532508, rounded once to €506.15.

How to run

Import PricingDemo.bas and TestPricingDemo.bas as standard modules. Compile if available and run RunPricingDemoTests. The harness stops at a failed assertion or prints its pass message after every current assertion succeeds. Record the actual platform, date and output when running it.

T-14 checks repeatability of a pure function. The form regression requires the separate Refresh investigation protocol.

Subform-to-Project Cost Aggregation

Read the detailed notes

Confirmed dependency

The inspected TotalSubform expression added five subform summary controls, each wrapped in Nz(): controller, BMS points/material, panel labour, hardware/software labour and commissioning labour. The cost calculation then added administration, other costs and panel allowance. These labels describe the control names; the underlying formulas and one-to-one mapping to cost categories were not independently established.

Conceptual diagram for Subform aggregation
Conceptual diagram · Open full size
View diagram source
flowchart TD
    A[Controller summary] --> T[Five-subform aggregate]
    B[BMS points / material summary] --> T
    C[Panel labour summary] --> T
    D[Hardware / software labour summary] --> T
    E[Commissioning labour summary] --> T
    T --> C1[Project cost calculation]
    F[Administration cost] --> C1
    G[Other costs] --> C1
    H[Panel allowance] --> C1
    C1 --> R[Refresh writes ProjectCost]
    R --> S[Calculated selling controls / route selection]

What Nz() establishes

In this numeric expression, a Null term contributes zero. This prevents Null propagation but does not prove that the underlying subtotal is genuinely zero or that a form reference is valid. If subtotals were temporarily Null, zero substitution could conceal incomplete inputs; no runtime trace has confirmed that mechanism.

Diagnostic checkpoints

Use distinguishable synthetic subtotals, such as 10, 20, 30, 40 and 50, plus separate administration, other and panel values. Capture the five source controls, TotalSubform, CalCost, ProjectCost and both selling expressions before the first Click, after it and after the second Click.

The first point of divergence would help distinguish aggregation, recalculation and persistence problems. The full protocol and observed symptoms are in Refresh investigation.

Refresh Double-Click Defect — Investigation Plan

Read the detailed notes

Observed behaviour

Single clicks generally updated the original form correctly in day-to-day use. Repeated double-clicks produced either zero cost/selling or a cost containing only Panel Allowance and Other Cost. I inspected the Refresh procedure and the five-subform aggregate, but did not establish or fix the root cause. The standalone calculation does not reproduce form state.

Why event order matters

Microsoft documents that double-clicking an Access command button can trigger Click, DblClick and a second Click. This supports tracing repeated execution; it does not establish which subtotal or stored value first becomes wrong.

Reproduction protocol

Use synthetic inputs in a disposable copy. Give every subform subtotal a distinct nonzero value and calculate the expected total independently.

Check What to capture
Single Click Edited detail values, five subtotals, aggregate, cost and selling
Double Click The same values after each Click, including event count and order
Zero Other/Panel Whether remaining component costs are retained
Nonzero Other/Panel Whether any components disappear while these remain
Reopen the record Whether displayed and saved amounts agree
Missing category input Whether fallback follows the documented pricing rule

Record the active record, focus, unsaved edits and relevant form/subform events alongside the values. A passing pure-function test is not evidence of a passing form regression.

Hypotheses and candidate improvements

Possible mechanisms include incomplete subform summaries, stale calculated controls, unsaved edits or other event side effects. Nz() could mask a Null subtotal as zero, but an observed trace is needed before attributing the defect to that mechanism.

Investigate cancelling the second Click through DblClick handling, explicit recalculation and calculating from stable values before writing totals. An in-progress guard alone does not establish protection from two sequential completed Click events. Test any change against the protocol above.