mhinspeya / module-combodeal
Enhance your Magento 2 store with the Combo Deal module, allowing you to bundle multiple products, offer special combo pricing, and boost sales through flexible, customizable deal configurations.
Package info
git.modehaus.de/inspeya/inspeya-combo-box
Type:magento2-module
pkg:composer/mhinspeya/module-combodeal
Requires
- mhinspeya/backend-mod: ^1.0
This package is not auto-updated.
Last update: 2026-08-12 05:27:00 UTC
README
mhinspeya/module-combodeal
Module Information
| Item | Value |
|---|---|
| Module | MHinspeya_Combodeal |
| Composer package | mhinspeya/module-combodeal |
| Version | 1.0.0 |
| License | GPL-3.0 |
| Author | MHinspeya / Inspeya |
| Path (manual install) | app/code/MHinspeya/Combodeal |
| Admin route | mhinspeya_combinationdiscount |
| Storefront route | mhcombinationdiscount |
Overview
MHinspeya_Combodeal lets merchants create combo deals: one Main Product plus up to three Child Products, with percentage discounts (base + stepped extra), schedule, store view, and customer group targeting.
On the storefront it shows:
- Combo block on the Main Product PDP (AJAX-loaded)
- SEO combo list page (default
combo-offers.html) - Combo Discount line in cart / checkout (Luma + Hyvä)
- Add-to-cart via the combo UI (flags items with
customcombo_product)
Features
- Admin CRUD grid for combo deals (enable / disable / delete / mass actions)
- Preview button (opens storefront PDP of the Main Product)
- Exactly one Main Product per combo
- Up to 3 Child Products per combo
- Product exclusivity: a product used as Main or Child in one combo cannot be used in another combo on an overlapping store view
- Discount: Main Discount % (1–50), optional step % and Stop Discount Count (1–3)
- Store view + customer group targeting (including NOT LOGGED IN / group
0) - Display From / Display To datetime scheduling
- Design & Colors (admin color pickers + CSS variables on frontend)
- Configurable SEO list URL key
- German translations (
i18n/de_DE.csv) - Works with Magento Cart Price Rules (separate totals; they stack)
- Frontend exclusive assignment safety (legacy overlapping DB rows: lowest combo ID wins)
- Hyvä cart total segment for Combo Discount (sorted after Subtotal / Magento Discount)
Requirements
- Magento 2.4.x (tested with Magento 2.4.8)
- PHP 8.1+ (match your Magento version)
- Composer (if using Composer install)
- Dependency:
mhinspeya/backend-mod^1.0(seecomposer.json) - Recommended storefront: Hyvä theme (Luma-compatible layouts also included)
- Writable Magento dirs:
var/,generated/,pub/static/
Installation
Run commands from the Magento root (e.g. /var/www/html/magento248).
In production mode, prefer:
bin/magento setup:upgrade --keep-generated
only when you intentionally keep generated code; otherwise use the full upgrade + compile steps below.
Option 1: Composer (recommended)
# 1) Install dependency first (if not already present)
composer require mhinspeya/backend-mod:^1.0
# 2) Install combo module
composer require mhinspeya/module-combodeal
# 3) Enable and upgrade
bin/magento module:enable MHinspeya_Combodeal
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush
Option 2: Manual (app/code)
Copy the module to:
app/code/MHinspeya/CombodealEnsure
mhinspeya/backend-modis installed and enabled.Run:
bin/magento module:enable MHinspeya_Combodeal bin/magento setup:upgrade bin/magento setup:di:compile bin/magento setup:static-content:deploy -f bin/magento cache:flushConfirm module status:
bin/magento module:status MHinspeya_CombodealExpected: Module is enabled.
File / cache permissions note
If bin/magento cache:flush cannot clear files owned by the web server (www-data), flush as that user, for example:
sudo -u www-data bin/magento cache:flush
Or clear from Admin → System → Cache Management.
Uninstall / Disable
bin/magento module:disable MHinspeya_Combodeal
bin/magento setup:upgrade
bin/magento cache:flush
Database tables created by db_schema.xml are not removed automatically on disable. Remove manually only if you intentionally want data purged:
mhinspeya_combinationdiscount_discountcombinationmhinspeya_combinationdiscount_mainproductsmhinspeya_combinationdiscount_childprodcuts(spelling as in schema)- Extra columns on
quote,quote_item,sales_order,sales_order_itemfor combo metadata
Admin menu & ACL
Menu
- Combo deal (root)
- Manage Combo deal → grid / create / edit
- Configuration → system config
Paths:
- Grid:
Admin → Combo deal → Manage Combo deal
URL:…/admin/mhinspeya_combinationdiscount/discountcombination/index - Config:
Admin → Combo deal → Configuration
orStores → Configuration → MHinspeya.de → Combo deal
ACL resources (Roles)
Assign under System → Permissions → User Roles:
| Resource ID | Purpose |
|---|---|
MHinspeya_Combodeal::Discountcombination | Combo deal root |
MHinspeya_Combodeal::Discountcombination_view | View / manage list & forms |
MHinspeya_Combodeal::Discountcombination_save | Save |
MHinspeya_Combodeal::Discountcombination_update | Update |
MHinspeya_Combodeal::Discountcombination_delete | Delete |
MHinspeya_Combodeal::Discountcombination_config | System configuration |
System configuration
Path: Stores → Configuration → MHinspeya.de → Combo deal
Section ID: mhinspeya_combinationdiscount
General Settings
| Field | Config path | Default | Description |
|---|---|---|---|
| Enable Combo deal | mhinspeya_combinationdiscount/general/enable | Yes (1) | Master storefront switch |
| Combo Deal URL Key | mhinspeya_combinationdiscount/general/url_key | combo-offers | List page path without leading slash. Opens as {url_key}.html (e.g. combo-offers.html). Example custom: my-combo-deals |
After changing the URL key, flush cache. The custom router maps {url_key}.html to the combo list.
Design & Colors
Color fields use a native admin color picker. Defaults match the live combo UI. Use “Use system value” to restore a field.
| Field | Default |
|---|---|
| Section Background | #f7f7f7 |
| Heading Color | #1a1a1a |
| Discount Label Color | #d32f2f |
| Timer Background | #d32f2f |
| Timer Text Color | #ffffff |
| Discount Badge Background | #d32f2f |
| Discount Badge Text | #ffffff |
| Product Card Background | #ffffff |
| Product Card Border | #dddddd |
| Option Border Color | #cccccc |
| Selected Option Background | #ffffff |
| Selected Option Outline | #ff5501 |
| Selected Option Text | #000000 |
| Old / Strikethrough Price | #999999 |
| Final Price Color | #d32f2f |
| Savings Message Color | #388e3c |
| View All Button Background | #ffffff |
| View All Button Text | #000000 |
| View All Button Border | #dddddd |
| Add to Cart Background | #388e3c |
| Add to Cart Hover Background | #2e7d32 |
| Add to Cart Text Color | #ffffff |
| Add to Cart Border Color | #388e3c |
| Add to Cart Border Width | 0px |
| Add to Cart Border Radius | 4px |
| Add to Cart Font Size | 16px |
| Add to Cart Line Height | 1.25 |
| Add to Cart Vertical Padding | 16px |
| Add to Cart Horizontal Padding | 19px |
Config path prefix: mhinspeya_combinationdiscount/design/{field_id}
Checkout totals sort order (Combo Discount position)
Hyvä sorts cart summary lines using sales/totals_sort. Combo Discount is configured as:
| Field | Config path | Default |
|---|---|---|
| Combo Discount | sales/totals_sort/customcombo_discount | 25 |
Typical order:
| Code | Sort | Line |
|---|---|---|
subtotal | 10 | Subtotal |
discount | 20 | Magento cart rule / coupon |
customcombo_discount | 25 | Combo Discount |
shipping | 30 | Shipping |
tax | 40 | Tax |
grand_total | 100 | Grand Total |
Also available under Magento’s sales totals sort admin fields when the module’s sales system config is merged.
Visual user guide
Step images (illustrated + real admin/storefront captures):
docs/user-guide/USER_GUIDE.md
Creating a Combo Deal (step by step)
- Go to Combo deal → Manage Combo deal → Add New Combo deal.
- Fill General:
- Status — Yes / No
- Package Title — required
- Display From / Display To — required (native Magento datetime fields)
- Select Views — at least one store view (or All Store Views)
- Customer Groups — at least one (include NOT LOGGED IN for guests)
- Main Discount (%) —
1–50 - Discount Amount (step %) — optional stepped increase per extra child
- Stop Discount Count —
1–3max children that count toward the stepped %
- Package Main Product
- Select exactly one product (grid enforces single selection).
- Package Child Products
- Select 1 to 3 children.
- Click Save.
Validation rules (admin save)
| Rule | Behavior |
|---|---|
| Required fields | Title, dates, stores, customer groups, main, children |
| Main count | Exactly one Main Product |
| Child count | At least 1, maximum 3 |
| Same Main + Child in one combo | Blocked (error lists conflicting IDs) |
| Product exclusivity across combos | A product already used as Main or Child in another combo on an overlapping store view cannot be reused (clear error message with product, role, combo ID, stores) |
| Form refill after error | General fields + product grids are restored via data persistor / registry |
Discount calculation (cart)
For a matched combo (Main + at least one Child, items flagged customcombo_product = 1):
finalPercent = basePercent + max(0, childCount - 1) * stepPercent
(childCount capped by Stop Discount Count when set)
comboTotal = sum of matched item row totals (excl. tax preferred)
discount = comboTotal * finalPercent / 100
Shown as cart total code customcombo_discount (title: Combo Discount).
Order / Invoice / Credit Memo / PDF / Email
Combo Discount is stored on the quote (customcombo_discountprice, customcombo_item), copied to the order on place-order (fieldset.xml + CopyComboTotalsToOrder observer), then applied on invoice / credit memo collectors.
It is displayed as a Combo Discount totals line on:
- Admin + customer account: order, invoice, credit memo (and print/email layouts)
- PDF invoice / credit memo (
etc/pdf.xml)
Shipment documents do not include financial totals (qty/fulfillment only) — Combo Discount is not shown there by design.
Collector: MHinspeya\Combodeal\Model\Total\Quote\Discount
sales.xml sort_order: 420 (runs after Magento sales-rule discount 300).
Storefront usage
Product page (PDP)
- Combo UI loads via AJAX when the current product is the exclusive Main of an active combo (status, dates, store, customer group).
- Out-of-stock / unavailable children are skipped.
- Countdown uses Display To.
- View All links to the combo list URL.
Layout: view/frontend/layout/catalog_product_view.xml
Combo list page
- Default URL:
https://{store}/combo-offers.html
(or{url_key}.htmlfrom config) - Router:
Controller/Router.php - Controllers under frontName
mhcombinationdiscount
Add to cart
- Use the combo Add to Cart so items get
customcombo_product = 1. - Only those items participate in Combo Discount collection.
Cart / Checkout
- Luma: KO totals under
before_grandtotal(checkout_cart_index.xml,checkout_index_index.xml) - Hyvä: PHP cart segment template
view/frontend/templates/hyva/cart/totals/combo-discount.phtml
Layout:hyva_checkout_cart_index.xml - Order, invoice, and creditmemo totals are also registered in
etc/sales.xml
Interaction with Magento Cart Price Rules
Combo Discount and Magento Cart Price Rules are independent:
- Magento Discount runs first (
sort_order~300). - Combo Discount runs after (
sort_order420). - Both can appear in the summary and stack.
- “Discard subsequent rules” on a cart rule only affects other cart rules, not Combo Discount.
- Combo % is based on item row totals (not after subtracting the cart-rule amount).
Testing with a coupon
- Create/enable a Cart Price Rule (Marketing → Cart Price Rules).
- Use Specific Coupon, turn off auto generation if you need a stable manual code.
- Apply coupon in cart; confirm both:
- Discount (rule name)
- Combo Discount
Frontend exclusivity (legacy / overlapping data)
Admin blocks shared products across combos. If older data still shares a product, storefront/cart use:
Helper/ComboAssignment.php
- Active combos only (status + schedule + store + customer group)
- Each product ID owned by at most one combo
- Lowest combo ID wins when overlaps exist
- Used by PDP, list page, and quote discount collector
Database schema (declaration)
Defined in etc/db_schema.xml:
| Table | Purpose |
|---|---|
mhinspeya_combinationdiscount_discountcombination | Combo header (title, status, dates, stores, groups, discount fields, JSON product maps) |
mhinspeya_combinationdiscount_mainproducts | Main product relations |
mhinspeya_combinationdiscount_childprodcuts | Child product relations (table name spelling is historical) |
Extra attributes on quote / order / items:
customcombo_product,customcombo_discount,customcombo_discountprice,applied_combo_ids,customcombo_item(where applicable)
Applied via bin/magento setup:upgrade.
Web API (optional)
Routes in etc/webapi.xml under /V1/mhinspeya-combodeal/discountcombination (CRUD + search). Use Magento integration / admin tokens as usual.
Translations
- Locale file:
i18n/de_DE.csv Deploy / flush cache after changes:
bin/magento cache:flush translate
Important file map
app/code/MHinspeya/Combodeal/
├── Block/
│ ├── Product/Comboproducts.php # PDP combo data
│ ├── Comolist.php # List page data
│ └── Adminhtml/ # Assign grids, buttons, config color picker
├── Controller/
│ ├── Adminhtml/Discountcombination/ # Save, grid mass actions, preview
│ ├── Index/Ajaxcombo.php # PDP AJAX
│ ├── List/Comolist.php # List controller
│ └── Router.php # SEO list URL
├── Helper/
│ ├── ComboAssignment.php # Exclusive Main/Child ownership
│ └── Design.php # CSS variables from config
├── Model/Total/Quote/Discount.php # Cart Combo Discount collector
├── etc/
│ ├── module.xml, db_schema.xml, acl.xml, sales.xml, config.xml
│ ├── adminhtml/system.xml, menu.xml, routes.xml
│ └── frontend/routes.xml, di.xml, sections.xml
├── view/adminhtml/ # UI components, assign product JS/templates
├── view/frontend/ # PDP/list templates, Hyvä cart total, CSS
├── i18n/de_DE.csv
└── README.md
Sample data (auto-install)
On bin/magento setup:upgrade, the data patch
Setup/Patch/Data/InstallSampleComboDeals.php reads
Setup/sample_data/combo_deals.json and creates 3 sample combo deals.
| Behavior | Detail |
|---|---|
| Status | Disabled (0) by default |
| Products | None — Main/Child are empty |
| Titles | Prefixed with [Sample] |
| Idempotent | Skips titles that already exist |
| Revert | Patch implements revert() to remove [Sample]% rows |
Admin next steps: open each sample → assign 1 Main + 1–3 Children → set Status = Yes → Save.
To customize samples before install, edit Setup/sample_data/combo_deals.json.
Post-install checklist
- [ ] Module enabled (
module:status) - [ ]
setup:upgradecompleted without schema errors - [ ] ACL role can access Combo deal menu
- [ ] Config: Enable = Yes; URL key set
- [ ] Create a combo with Main + 1–3 Children, dates covering now, correct store & customer group
- [ ] Open Main Product PDP → combo block loads
- [ ] Add combo via combo Add to Cart → cart shows Combo Discount under Subtotal
- [ ] Open
{url_key}.htmllist page - [ ] (Optional) Apply a Magento coupon → both Discount and Combo Discount visible
- [ ] Flush caches after design/URL changes
Troubleshooting
| Issue | What to check |
|---|---|
| Combo not on PDP | Product is Main? Status Yes? Dates include now? Store view + customer group match? Module enabled? |
| No Combo Discount in cart | Items added via combo Add to Cart (customcombo_product)? Main + child present? Qty remaining? |
| Admin: product already used | Product exists on another combo for overlapping stores (as Main or Child) |
| Admin: same Main and Child | Do not select the same product in both grids of one combo |
| Coupon invalid | Rule active; specific coupon exists; auto-generation off if using a manual code; website + customer group |
| Combo Discount above Subtotal | Ensure sales/totals_sort/customcombo_discount = 25 and cache flushed (Hyvä StoreConfig plugin also forces 25 when missing/0) |
| Dates “required” while filled | Use native datetime fields (legacy jQuery calendar was removed); hard-refresh admin |
| Form empty after validation error | Fixed via DataProvider + form-part product maps; flush caches / open a fresh New after upgrading |
| Cache flush permission denied | Run Magento CLI as www-data or flush from Admin |
Support
For questions, issues, or feature requests, contact MHinspeya / Inspeya.