Appearance
Gift Rules Management (Admin)
Flow ID: AD-09 Module(s): eshop, Promotion (modern domain) Complexity: Medium-High Last Updated: 2026-05-19
Business Context
Gift rules allow store administrators to configure promotional logic that automatically grants free products to customers when certain conditions are met during cart evaluation and checkout. The system supports 13 distinct rule types covering product-based, vendor-based, cart-total, and combination triggers. Each gift rule defines what products or vendors trigger it (requirements), what products the customer receives (choices), how many qualifying items are needed per gift (gift_per_count), and how many gifts the customer can pick (gift_user_choice_count). Rules are date-bounded, have optional stock limits, and can be flagged as promotional banners for storefront display.
Entry Points
| Type | Path / Trigger | Controller | Method |
|---|---|---|---|
| Admin listing | /gifts_admin | Adv_gifts_admin | index() |
| Add gift rule | /gifts_admin/add | Adv_gifts_admin | add() |
| Edit gift rule | /gifts_admin/edit/{id} | Adv_gifts_admin | edit($id) |
| Delete gift rule | /gifts_admin/delete/{id} | Adv_gifts_admin | delete($id) |
| Toggle active | /gifts_admin/setGiftActivity/{id}/{0|1} | Adv_gifts_admin | setGiftActivity() |
| Product search | /gifts_admin/search_append_item/{mode} | Adv_gifts_admin | search_append_item() |
| Mass gift creation | /products_admin (batch action) | Adv_products_admin | batch submit |
| REST API | /rest/promotion/gift | Rest\Promotion\Controllers\Gift | CRUD |
| REST Choices | /rest/promotion/gift-choice | Rest\Promotion\Controllers\GiftChoice | CRUD |
| REST Requirements | /rest/promotion/gift-requirement | Rest\Promotion\Controllers\GiftRequirement | CRUD |
| Admin menu | Marketing group | application/config/admin_menu.php | route gifts_admin |
Authorization
Roles: AUTH_ROLE_ADVISABLE, AUTH_ROLE_ADMIN, AUTH_ROLE_MARKETING
Enforced in the controller constructor via allowRole(). The admin menu entry mirrors the same three roles. REST API endpoints are protected under backend auth with ADMIN + MARKETING roles (configured in application/config/rest_policies.php).
Code Path Trace
Step 1: Listing (index)
File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- index($offset) (line 36)
- Search/filter handling (lines 38-63): Stores conditions in session (
giftsSearch). Filters:active(live status),text(internal_name LIKE),giftRules(rule_id IN),maxStock(joinsgift_choicesandshop_productto find gifts whose gift products have stock <= N). - Count and fetch (lines 65-74):
gifts_model->countAdminList($conditions)for pagination,gifts_model->getAdminList($conditions, $limit, $offset)for paginated results. - Active rule calculation (line 112-118):
getActiveGiftRules()determines currently live rules (date range, active flag, remaining stock, valid choices). Results are ordered by live status first (viaFIELD()SQL), then bydate_end DESC. - Data enrichment (lines 132-183): Each row gets its requirements and choices loaded via
gift_requirements_model->getOptions()andgift_choices_model->getOptions(). Product and vendor names/details are resolved for display. - Render: Passes
giftRulesdropdown (rule names),giftRulesHelp(rule descriptions), records withisLiveflag.
View: application/views/admin/gifts/list.php
The listing table columns: ID (with green/red live indicator dot), Internal Name, Date Range, Active status, Prerequisites (collapsible panel showing rule type, amounts, vendors, products with stock badges), Remaining (infinity symbol if NULL), Gift Per Count, Gift Products (product names with stock), and Actions (toggle active, edit, delete with JS confirmation).
Search panel provides: text search on internal_name, multi-select rule type filter, active/inactive/indifferent dropdown, and max stock threshold filter.
Step 2: Create Gift Rule (add)
File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- add() (line 92)
- Validation (line 96): Calls
validation()which sets CodeIgniter form validation rules. - File uploads (lines 99-109): Handles per-language image uploads via
advuploader->uploadFilesSameFolder()tofiles/gifts/. - Rule cleanup (line 113):
gift_rules_model->postDataRuleCleanupHelper($allPost)removes fields inapplicable to the selected rule type (see Rule Type Field Mapping below) and forces defaults. - Collect associations (lines 115-135):
dom_req_ID[]-- requirement product IDs (option_type = 1)req_vendor_ids[]-- requirement vendor IDs (option_type = 2)dom_gift_ID[]-- gift choice product IDs (option_type = 1)
- Build gift data (lines 137-150):
rule_id,amount_from,amount_to,internal_name,date_start,date_end,active,gift_per_count,gift_user_choice_count,is_promo,priority,remaining(NULL = unlimited). - MUI data (lines 153-167): Per language:
description,url,image,promo_image,extra_vendor_image,extra_product_image. - Persist (line 176):
gifts_model->addGift()inserts master, captions, requirements, and choices in a single transaction. - Hook:
afterAdd($giftId)-- empty in base, overridable in client repos.
View: application/views/admin/gifts/create.php
Form structure:
- MUI tab section: Description (text input per language), Badge Image (file per language)
- Promo section:
is_promocheckbox, Promo Image (file per language), Extra Vendor Image, Extra Product Image, Priority (integer), URL (per language) - Rule selection: Dropdown of all 13 rule types with help text panel
- Amount range:
amount_from/amount_to(numeric, step 0.01) -- shown/hidden per rule type - Requirement products: AJAX product search with inline results (
search_append_item('req')) - Requirement vendors: Multi-select dropdown from vendors combo
- Gift products: AJAX product search (
search_append_item('gift')) - Gift user choice count: How many gifts the customer can pick (0 = automatic)
- Date range: Datetime pickers for start/end
- Active: Checkbox
- Internal name: Admin-only label (required in HTML, trim-only in server validation)
- Gift per count: Number 1-10, how many qualifying items needed per gift
- Remaining: Stock counter (empty = unlimited NULL)
Step 3: Edit Gift Rule (edit)
File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- edit($id) (line 211)
Same flow as create with these differences:
- Loads existing gift via
gifts_model->getGift($id)with captions, requirements, and choices - Image handling preserves existing images unless a new file is uploaded or the
del_*checkbox is checked (e.g.,del_image,del_promo_image,del_extra_vendor_image,del_extra_product_image) - Calls
gifts_model->updateGift()which updates master, captions, and diff-updates requirements/choices (adds new, removes deleted) - Hook:
afterEdit($giftId)-- overridable
Step 4: Delete Gift Rule
File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- delete($id) (line 350)
Deletes: requirements (gift_requirements), choices (gift_choices), captions (gifts_mui), master (gifts). No soft-delete -- hard removal. No referential integrity check against past orders (gifts in orders are stored separately).
Hook: afterDelete($giftId) -- overridable
Step 5: Toggle Active Status
File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- setGiftActivity($giftId, $active) (line 366)
Direct update of active flag on the gifts table. Does not affect date range or remaining stock. Redirect back to listing.
Step 6: AJAX Product Search
File: ecommercen/eshop/controllers/Adv_gifts_admin.php -- search_append_item($mode) (line 377)
- Ajax-only, POST
queryString - Searches
shop_productby: name LIKE, vendor_code exact, product_id exact, product_code exact (viaproduct_codessubquery) - Returns active, non-deleted products with name, vendor info, stock, discount, images
- Limited to 40 results, ordered by name ASC then stock DESC
- Cached for 15 seconds via
pscache $modeparameter (reqorgift) determines the JS behavior for adding to requirements vs. gift choices
Step 7: Mass Gift Creation from Product Admin
File: ecommercen/eshop/controllers/Adv_products_admin.php (line 1557-1569)
Batch action on the product listing page. When gift_per_count is submitted, calls gifts_model->addMassGiftsForNoPlusOne($products, $postData). Creates one gift per selected product using Rule 1 (products required), where each product is both the requirement AND the gift choice (buy X, get X free). Internal names auto-incremented as "{name} - 1", "{name} - 2", etc.
The 13 Rule Types
Rule types are defined as a private instance array in Adv_gift_rules_model::$data (not a database table). Each rule entry specifies:
| Property | Purpose |
|---|---|
id | Numeric rule identifier |
name | Translation key for admin display |
help | Translation key for help tooltip |
validator | Method name on Adv_gifts_model called during cart evaluation |
removeFields | POST fields stripped on save (fields not applicable to this rule type) |
setDefaults | Field values forced on save (e.g., gift_per_count = 1 for rules 5, 7) |
Rule Type Reference
| ID | Name | Trigger Condition | Removed Fields | Forced Defaults |
|---|---|---|---|---|
| 1 | Products required | Specific products in cart | amount_from, amount_to, req_vendor_ids | -- |
| 2 | Vendors required | Products from specific vendors in cart | amount_from, amount_to, dom_req_ID | -- |
| 3 | Cart total | Cart total within amount range | req_vendor_ids, dom_req_ID | -- |
| 4 | Products + cart total | Specific products AND cart total in range | req_vendor_ids | -- |
| 5 | Products + min total amount | Required products' total value in range | req_vendor_ids | gift_per_count = 1 |
| 6 | Vendors + cart total | Vendor products AND cart total in range | dom_req_ID | -- |
| 7 | Vendors + min total amount | Vendor products' total value in range | dom_req_ID | gift_per_count = 1 |
| 8 | Vendors + min item price | Individual vendor product prices in range | dom_req_ID | -- |
| 9 | Products + min item price | Individual required product prices in range | req_vendor_ids | -- |
| 10 | Products (combination) | ALL specified products must be in cart | amount_from, amount_to, req_vendor_ids | -- |
| 11 | Products required (one gift) | Specific products in cart; caps at gift_per_count | amount_from, amount_to, req_vendor_ids | -- |
| 12 | Vendors required (one gift) | Vendor products in cart; caps at gift_per_count | amount_from, amount_to, dom_req_ID | -- |
| 13 | Products required -- cheapest free | Specific products in cart; cheapest becomes free | amount_from, amount_to, req_vendor_ids, dom_gift_ID | gift_user_choice_count = 0 |
Rule Type Field Mapping
The postDataRuleCleanupHelper() method (line 192) strips inapplicable POST fields per rule type before saving:
- Product-based rules (1, 10, 11): Remove
amount_from,amount_to,req_vendor_ids-- no amount range or vendor conditions needed - Rule 13: Remove
amount_from,amount_to,req_vendor_ids,dom_gift_ID-- additionally stripsdom_gift_IDbecause Rule 13 never readsgift_choicesat runtime (gift pool is computed dynamically) (Adv_gift_rules_model.php:133) - Vendor-based rules (2, 12): Remove
amount_from,amount_to,dom_req_ID-- no amount range or product conditions needed - Cart total rule (3): Remove
req_vendor_ids,dom_req_ID-- only amount range matters - Hybrid rules (4, 5, 9): Remove
req_vendor_ids-- products + amount, no vendor conditions - Hybrid rules (6, 7, 8): Remove
dom_req_ID-- vendors + amount, no product conditions
Rule 13 Special Behavior
Rule 13 ("Products required -- get cheapest free") is unique:
- No entries in
gift_choicestable -- the gift pool is computed dynamically at cart evaluation time gift_user_choice_countforced to 0 on save (no customer selection presented)getCheapestRequirementProductInCart()identifies the lowest-priced qualifying product in the cart byfinal_price; on tie, lowest product ID wins (deterministic)- The free product's quantity is subtracted from the paid cart row in
baseParseCartContents getActiveGiftRules()explicitly skips thegift_choicesexistence check for rule 13 so these gifts appear as "live"- Admin order page: Vue Vuex getter forces
ruleSatisfiesChoiceSelection = truefor rule 13 - Save-side guard (4.101.0):
postDataRuleCleanupHelper()stripsdom_gift_IDbefore save (Adv_gift_rules_model.php:133), so Rule 13 saves can no longer creategift_choicesrows
Validation Rules
| Field | Rule | Notes |
|---|---|---|
rule_id | required, integer | Must match one of the 13 defined rules |
gift_per_count | required, numeric, 1-10 | How many qualifying items needed per gift |
gift_user_choice_count | required, numeric, >= 0 | 0 = automatic assignment (rule 13 always forced to 0) |
remaining | trim only | NULL = unlimited stock; numeric value decremented on order |
amount_from | numeric | Lower bound (inclusive) of amount range; 0 if not applicable |
amount_to | numeric | Upper bound (exclusive); 0 means no upper limit |
internal_name | trim | Admin-only label; HTML required attribute on form |
date_start | required | Datetime for rule activation |
date_end | required | Datetime for rule expiry |
active | trim | Checkbox flag (0/1) |
description_{lang} | trim (MUI) | Per-language description |
Gift Quantity Calculation Logic
When a gift rule is evaluated at cart time:
validatorResult = validator(...) // How many qualifying units
giftCountToGive = floor(validatorResult / gift_per_count) // How many gifts earned
if remaining is not null:
giftCountToGive = min(giftCountToGive, remaining) // Stock cap
if giftCountToGive <= 0:
skip ruleThe remaining counter is decremented on order creation via gifts_model->updateCounter($giftId, $subtractCount). If remaining is NULL, no limit is applied and no decrement occurs.
Promo Display Feature
Gifts flagged with is_promo = 1 are displayed as promotional banners on the storefront homepage. The Adv_front_controller::getActiveGiftRules() method (line 1336) splits active gift rules into two groups:
activeGiftRules['promo']-- rules withis_promo = 1, rendered as promotional banners with promo_image, extra_vendor_image, extra_product_image, URL, and priority orderingactiveGiftRules['normal']-- standard gift rules
This is loaded on the homepage via Adv_home::index() and provides marketing visibility for active gift promotions. The priority field controls display ordering (higher = first).
Data Model
Database Tables
| Table | Purpose | Key Columns |
|---|---|---|
gifts | Gift rule master | id, rule_id, internal_name, gift_per_count, gift_user_choice_count, amount_from (decimal 9,2), amount_to (decimal 9,2), date_start, date_end, active, is_promo, remaining (NULL = unlimited), priority |
gifts_mui | Multi-language content | gift_id, lang, description, image (badge), promo_image, extra_vendor_image, extra_product_image, url |
gift_requirements | Trigger conditions | gift_id, option_type (1 = product, 2 = vendor), option_type_id |
gift_choices | Gift products offered | gift_id, option_type (1 = product), option_type_id (product ID) |
One-Time Data Migration (4.101.0)
Migration database/migrations/20260511120000_cleanup_rule13_gift_choices.php and patcher patches/CleanupRule13GiftChoices.php purged historical orphan gift_choices rows for rule_id = 13 (DELETE via JOIN) introduced before dom_gift_ID was added to removeFields. These rows were written by the admin form but never read at runtime.
Indexes (gifts table)
date_start, date_end, date_period (composite), active, active_date_start, active_date_end, active_date_period, is_promo, active_period_promo_remaining (composite covering the default active query).
Entity Relationships
gifts (1) ──< gifts_mui (N) -- per language
gifts (1) ──< gift_requirements (N) -- products/vendors that trigger
gifts (1) ──< gift_choices (N) -- products offered as gifts
gift_requirements.option_type_id ──> shop_product.id (when option_type=1)
gift_requirements.option_type_id ──> shop_vendor.id (when option_type=2)
gift_choices.option_type_id ──> shop_product.id (when option_type=1)Model Layer
Legacy Models
| Model | File | Purpose |
|---|---|---|
Adv_gifts_model | ecommercen/eshop/models/Adv_gifts_model.php | Core gift CRUD and all 13 validation strategies |
Adv_gift_rules_model | ecommercen/eshop/models/Adv_gift_rules_model.php | Static rule type registry (in-memory, not DB) |
Adv_gift_options_model | ecommercen/eshop/models/Adv_gift_options_model.php | Base class for option tables (diff-based update) |
Adv_gift_requirements_model | ecommercen/eshop/models/Adv_gift_requirements_model.php | Extends Gift_options_model, table gift_requirements |
Adv_gift_choices_model | ecommercen/eshop/models/Adv_gift_choices_model.php | Extends Gift_options_model, table gift_choices; adds getOptionsWithActiveStockForGiftIds() to filter out-of-stock gift products |
The Adv_gift_options_model uses a diff-based update strategy: on save, it compares current DB entries with the new set, deletes removed IDs and inserts new ones (never doing a full wipe-and-reinsert).
Modern Domain Layer (PSR-4)
| Component | Path | Purpose |
|---|---|---|
| Gift Entity | src/Domains/Promotion/Gift/Repository/Entity.php | Read entity with all gifts columns |
| Gift MUI Entity | src/Domains/Promotion/Gift/Repository/MuiEntity.php | gifts_mui columns |
| Gift Repository | src/Domains/Promotion/Gift/Repository/Repository.php | Specification-based queries |
| Gift RepositoryConfigurator | src/Domains/Promotion/Gift/Repository/RepositoryConfigurator.php | Relations: translations (ONE_TO_MANY MUI), choices (ONE_TO_MANY GiftChoice), requirements (ONE_TO_MANY GiftRequirement) |
| Gift Service | src/Domains/Promotion/Gift/Service.php | Read service |
| Gift WriteService | src/Domains/Promotion/Gift/WriteService.php | Create/update/delete with MUI |
| Gift WriteData | src/Domains/Promotion/Gift/WriteData.php | DTO: ruleId, internalName, giftPerCount, giftUserChoiceCount, amountFrom, amountTo, dateStart, dateEnd, active, isPromo, remaining, priority |
| Gift MuiWriteData | src/Domains/Promotion/Gift/MuiWriteData.php | DTO: lang, description, image, promoImage, extraVendorImage, extraProductImage, url |
| Gift Validator | src/Domains/Promotion/Gift/Validator.php | Create/update validation (currently placeholder) |
| GiftChoice Entity/Repo | src/Domains/Promotion/GiftChoice/ | CRUD for gift_choices table |
| GiftRequirement Entity/Repo | src/Domains/Promotion/GiftRequirement/ | CRUD for gift_requirements table |
| DI Container | src/Domains/Promotion/container.php | Registers all Promotion domain services |
REST API
Full CRUD exposed via modern REST controllers. Auth: backend JWT with ADMIN or MARKETING role.
| Endpoint | Method | Controller | Action |
|---|---|---|---|
/rest/promotion/gift | GET | Gift | List with filters, sorts, pagination |
/rest/promotion/gift/{id} | GET | Gift | Single record |
/rest/promotion/gift/item | GET | Gift | Single by filter |
/rest/promotion/gift | POST | Gift | Create |
/rest/promotion/gift/{id} | POST | Gift | Update |
/rest/promotion/gift/{id} | DELETE | Gift | Delete |
/rest/promotion/gift-choice | GET/POST/DELETE | GiftChoice | CRUD for gift choices |
/rest/promotion/gift-requirement | GET/POST/DELETE | GiftRequirement | CRUD for gift requirements |
Filters: id (exact), ruleId (exact), active (exact), isPromo (exact), internalName (partial), description.{locale} (partial).
Relations (via ?with=): translations, choices, requirements.
Sorts: id, ruleId, amountFrom, amountTo, dateStart, dateEnd, active, isPromo, priority, description.{locale}.
Admin Views
| View | File | Purpose |
|---|---|---|
| List | application/views/admin/gifts/list.php | Paginated table with search/filter panel |
| Create | application/views/admin/gifts/create.php | Full form with MUI tabs, AJAX product search |
| Update | application/views/admin/gifts/update.php | Same as create with pre-populated values and image deletion |
| AJAX search results | application/views/admin/gifts/ajax_search_list.php | Product search dropdown results |
| JS item template | application/views/admin/gifts/js_add_item.php | Client-side template for adding products to requirement/gift lists |
The create/update views feature embedded Scribe How tutorials (scribe.how.marketing.gift.rules and scribe.how.marketing.gift.actions) for admin user guidance.
Admin Order Integration
Gift rules are evaluated on the admin order create/edit page via a dedicated Vue component:
File: assets/admin/js/order-gifts-block.js
The Vuex store manages: giftRulesWithProducts, selectedGiftsWeights, selectedInGroups, orderSelectedGiftIds. The getter getGiftRulesWithProducts enriches each rule with ruleRequiresChoiceSelection and ruleSatisfiesChoiceSelection flags. Rule 13 auto-satisfies choice selection (no admin interaction needed).
The admin AJAX endpoints gifts_for_products and validateProductGiftSelections in Adv_orders_admin.php evaluate gift eligibility for the current order's product set, returning available rules with choices. Rule 13 validation is skipped (auto-assigned). As of 4.101.0 (commit bd187f2db), both Adv_orders_admin.php:1513 and validateProductGiftSelections() at Adv_orders_admin.php:2624 delegate to $this->gifts_model->filterApplicableGiftRules(), eliminating the prior admin-only divergence that dropped Rule 13 gifts from the validated set.
Tests
| Test | File | Coverage |
|---|---|---|
| Legacy validator unit tests | tests/Legacy/Eshop/AdvGiftsModelTest.php | ruleProductsValidator, ruleProductsCombinationValidator, ruleTotalCartValidator, ruleProductsCheapestFreeValidator, getCheapestRequirementProductInCart (greedy allocation, tie-break, cart-qty cap), filterApplicableGiftRules Rule 13 exemption — all via reflection |
| Legacy order-model Rule 13 unit tests | tests/Legacy/Eshop/AdvOrderModelRule13Test.php | getRule13GiftAdjustmentsForProducts, adjustCartForRule13Gifts (qty trim, full-row removal, code-mismatch fallback, exact-code-preference guard) via reflection; 13 tests, all passing after #204 fix |
| Legacy model DB integration (Rule 13) | tests/Integration/Legacy/Eshop/AdvGiftsModelTest.php | getActiveGiftRules Rule 13 bypass — confirms Rule 13 gift is retained when gift_choices is empty, while non-Rule-13 gifts with the same shape are filtered out |
| Integration: Gift Service | tests/Integration/Domains/Promotion/Gift/ServiceTest.php | Modern domain read/write service |
| Integration: Gift Repository | tests/Integration/Domains/Promotion/Gift/RepositoryTest.php | Repository queries |
| Integration: GiftChoice | tests/Integration/Domains/Promotion/GiftChoice/ | Choice CRUD |
| Integration: GiftRequirement | tests/Integration/Domains/Promotion/GiftRequirement/ | Requirement CRUD |
Business Rules Summary
| Rule | Location | Description |
|---|---|---|
| Amount range semantics | ruleTotalCartValidator | amount_from is inclusive (<=), amount_to is exclusive (<); amount_to = 0 means no upper limit |
| Rule 10 = combination | ruleProductsCombinationValidator | ALL required products must be in cart; returns minimum quantity across all (bottleneck product) |
| Rules 11, 12 = single gift cap | Validators | Even with many qualifying products, returns at most gift_per_count gifts |
| Rule 13 = auto-assign cheapest | getCheapestRequirementProductInCart | No gift_choices; dynamic pool from live cart prices |
Stock limit via remaining | getGiftForProductsInCart line 656 | NULL = unlimited; decremented on order creation |
gift_per_count = trigger threshold | Calculation line 654 | floor(validatorResult / gift_per_count) determines number of gifts |
| Date range required | Admin validation | date_start <= NOW < date_end for rule to be live |
File uploads to files/gifts/ | Controller add/edit | 4 image fields per language: image, promo_image, extra_vendor_image, extra_product_image |
| Deletion is hard | deleteGift() | Removes all related rows (requirements, choices, MUI, master) |
| Gift stock filtering | getOptionsWithActiveStockForGiftIds() | Gift choice products with availabilityStock <= 0 are excluded |
| Mass creation | addMassGiftsForNoPlusOne() | Creates Rule 1 gifts where each product is both requirement and gift (buy X get X) |
Client Customization Points
| Override Type | Base File | Override In | What Can Change |
|---|---|---|---|
| Gifts model | ecommercen/eshop/models/Adv_gifts_model.php | application/modules/eshop/models/ | Validator logic, new rule types, custom conditions |
| Gift rules model | ecommercen/eshop/models/Adv_gift_rules_model.php | application/modules/eshop/models/ | Rule definitions, field visibility per type |
| Gift admin controller | ecommercen/eshop/controllers/Adv_gifts_admin.php | application/modules/eshop/controllers/ | Admin form, validation rules, afterAdd/afterEdit/afterDelete hooks |
| Gift views | application/views/admin/gifts/ | Same path in client repo | Form layout, additional fields |
| Modern domain | src/Domains/Promotion/Gift/ | custom/Domains/Promotion/Gift/ with DI alias | Service logic, validation |
Known Issues & Security Gaps
[RESOLVED 4.101.0, commit 879423e21] Rule 13 admin form persisted
dom_gift_IDtogift_choices— the field was written but never read at runtime. Fixed by addingdom_gift_IDtoAdv_gift_rules_model.php:133removeFields. One-time cleanup migration purged existing orphan rows.[RESOLVED 4.101.0, commit bd187f2db]
validateProductGiftSelections()(Adv_orders_admin.php:2624) used an inlinearray_filterthat lacked the Rule 13 carve-out, causing Rule 13 gifts to be dropped from admin order validation. Resolved by delegating to the canonicalAdv_gifts_model::filterApplicableGiftRules()method which carries the Rule 13 exemption at line 524.[OPEN] Admin form still renders the gift-products picker (
dom_gift_IDinput) when Rule 13 is selected. The input is silently discarded bypostDataRuleCleanupHelper()on save — admins can configure gift choices that vanish on submit. Sibling rules 6/7/8 have the same shape fordom_req_ID. Tracked separately (referenced in commit 879423e21 body).[OPEN -- minor] Dangling duplicate
$productReqOptionType = 1;atAdv_orders_admin.php:2626— harmless (used by the subsequentforeachblock) but a cosmetic cleanup candidate left over from the inline closure removal.
Wiki Guides
- Gifts Module Guide -- detailed walkthrough of gift rule configuration and the 13 rule types
Related Flows
- CF-14 Gift Rules & Free Products -- storefront evaluation, all 13 validators
- CF-05 Cart Management -- gifts evaluated on every cart render
- CF-06 Order Preview -- gift selection during checkout
- CF-08 Payment Processing -- gift stock decremented on order creation
- AD-03 Order Management -- order-gifts-block Vue component for admin orders
- AD-02 Product Management -- batch gift creation
- AD-23 Gift Cards -- gift card orders (monetary vouchers; distinct from promotional gift rules)
- SY-23 MUI Translation Pattern --
gifts_muicompanion table stores per-language descriptions and badge/promo image filenames - SY-25 File Upload & Storage --
advuploader->uploadFilesSameFolder()handles 4 per-language image uploads (badge, promo, extra_vendor, extra_product) tofiles/gifts/