As a supplier, I will be able to adjust the priority level of my price profiles (with an additional UI for priority selection). When a customer is connected to multiple price profiles, the system will first rank them by priority from high to low. If multiple price profiles share the same priority level, the lowest price among them will be selected.
git clone https://github.com/linklin830912/FOBOH_Engineering_Challenge.git
cd FOBOH_Engineering_Challenge/frontend
npm install
npm run dev
cd FOBOH_Engineering_Challenge/backend
npm install
npm run dev
http://localhost:3001/api-docs/#/
- The supplier is able to define the priority of each pricing profile, which directly affects the final price a customer receives.
- A multi-choice UI component is added in the frontend to allow suppliers to select a priority level from High to Low.
- This value is stored in the
PricingProfileand used as a key input in the pricing resolver.
- The UI provides a simple and explicit way for suppliers to control pricing precedence.
- Priority is treated as a configurable abstraction rather than a hardcoded rule, allowing future flexibility (e.g. time-based priority: profiles created before a certain date should higher or lower the priority).
- The current priority system is represented as discrete levels (0–3), which may be ambiguous without clear business definitions.
- Additional documentation or a stricter enum definition is required to ensure consistent interpretation across frontend and backend.
- The project currently uses an in-memory mock database for simplicity and rapid prototyping instead of a relational database such as PostgreSQL
- Temporary mock relational fields are introduced to emulate bidirectional relationships. These fields act as a lightweight in-memory solution that mimics foreign key relationships, allowing queries to traverse associations in both directions without a real ORM or relational database engine. They are intended to be removed once a proper relational database and ORM layer are implemented:
Customer.groupIdsCustomerGroup.priceProfileIds
- Faster implementation without requiring a real database or ORM setup.
- Easier bidirectional querying in the mock environment.
- Relationship consistency must be synchronized manually.
- Complex cascading logic is required when updating or deleting
PricingProfile, since relatedCustomerGroupandCustomerreferences must also be updated manually. - Query performance temporarily relies on array traversal rather than indexed relational joins, making the current approach less efficient.
- Maintaining both a direct
PricingProfile-Customerrelation and aPricingProfile-CustomerGrouprelation simultaneously introduces multiple relationship paths and creates two sources of truth. - A direct
PricingProfile-Customerrelation also reduces flexibility for future operations such as group-level precedence, bulk assignment/removal, and reusable customer segmentation.
CustomerGroupacts as the single association layer betweenCustomerandPricingProfile.- Individually added customers are automatically placed into a CustomerGroup with
type="auto", distinct from manually managed groups withtype="custom".
- Relationship traversal becomes normalized and predictable:
PricingProfile→CustomerGroup→Customer - PricingProfile remains the single source of truth for pricing intent, including:
- priority
- adjustment mode and adjustment increment mode
- product scope (All Product)
- Customer segmentation logic becomes reusable and extensible.
- Additional logic is required to create and maintain
CustomerGroupwithtype="auto" - CRUD operations become more complex due to cascading relationship synchronization between:
PricingProfile→CustomerGroup→Customer
-
Precedence Rule Upgrade
- The price resolver is currently implemented for simplicity as a single
resolvePricefunction within the service layer, following a sequential flow: matching → sorting → price calculation. These responsibilities are currently coupled together, but should be modularized so that each step can be independently tested and maintained. - In a more scalable design, the pricing flow can be structured as a pipeline separating the matching, sorting, and price calculation stages. This would allow each stage of the pricing pipeline to be independently replaced or extended (ex: alternative matching strategies or sorting rules) without modifying the core resolver logic.
- The price resolver is currently implemented for simplicity as a single
-
Database Layer
- Connecting to a real relational database to replace the in-memory store is the essential next step. The schema relationships between Product, PricingProfile, CustomerGroup, and Customer map naturally to foreign keys and inner joins, so the migration path is straightforward.
-
API Layer
- Currently the routes are quite heavy, with validation, error handling, and logging. We should introduce a middleware layer to separate core API logic from cross-cutting concerns such as validation and logging.
-
Profile-first vs Customer Group-first
- So far, the Precedence Rule applies a Profile-first approach, where priority is defined at the
PricingProfilelevel. This means pricing decisions are ranked across different profiles based on theirpriorityfield. - Alternatively, priority could be moved to the
CustomerGrouplevel. Given that individually added customers are automatically assigned to aCustomerGroupwithtype="auto", this would allow different priority semantics for group-level vs individual-level assignments, enabling more granular control over pricing precedence.
- So far, the Precedence Rule applies a Profile-first approach, where priority is defined at the
-
Interesting scenarios for future feature The current design handles a few non-obvious scenarios cleanly and opens the door to several product extensions:
- A supplier adds multiple customer groups to a pricing profile. If a group grows later, no changes are needed to the profile — it is linked to the group, not to individual customers.
- A supplier wants to remove all individually-added customers from a profile without affecting customers who belong via a group. Deleting the auto-generated group is sufficient — group members are untouched.
- The
typefield onCustomerGroupdistinguishes auto-generated groups (individually added customers) from manually created ones. If the precedence rule ever evolves — for example, giving individually added customers higher priority than group-based matches — the data model already supports this without schema changes. - To remove a customer from all pricing profiles, you only need to remove them from their customer groups — no changes to the profiles themselves.
- The
CustomerGroupconcept could be extended to products as well — introducing aProductGroup(by category, brand, or expiry date) linked to pricing profiles instead of individual products. This would decouple product selection from profiles in the same way customer segmentation is decoupled, making bulk pricing rules more scalable and maintainable.