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.

Maintainers

Package info

git.modehaus.de/inspeya/inspeya-combo-box

Type:magento2-module

pkg:composer/mhinspeya/module-combodeal

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

1.0.1 2026-08-11 13:35 UTC

This package is not auto-updated.

Last update: 2026-08-12 05:27:00 UTC


README

mhinspeya/module-combodeal

Module Information

ItemValue
ModuleMHinspeya_Combodeal
Composer packagemhinspeya/module-combodeal
Version1.0.0
LicenseGPL-3.0
AuthorMHinspeya / Inspeya
Path (manual install)app/code/MHinspeya/Combodeal
Admin routemhinspeya_combinationdiscount
Storefront routemhcombinationdiscount

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 (see composer.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)

  1. Copy the module to:

    app/code/MHinspeya/Combodeal
    
  2. Ensure mhinspeya/backend-mod is installed and enabled.

  3. 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:flush
    
  4. Confirm module status:

    bin/magento module:status MHinspeya_Combodeal
    

    Expected: 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_discountcombination
  • mhinspeya_combinationdiscount_mainproducts
  • mhinspeya_combinationdiscount_childprodcuts (spelling as in schema)
  • Extra columns on quote, quote_item, sales_order, sales_order_item for 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
    or Stores → Configuration → MHinspeya.de → Combo deal

ACL resources (Roles)

Assign under System → Permissions → User Roles:

Resource IDPurpose
MHinspeya_Combodeal::DiscountcombinationCombo deal root
MHinspeya_Combodeal::Discountcombination_viewView / manage list & forms
MHinspeya_Combodeal::Discountcombination_saveSave
MHinspeya_Combodeal::Discountcombination_updateUpdate
MHinspeya_Combodeal::Discountcombination_deleteDelete
MHinspeya_Combodeal::Discountcombination_configSystem configuration

System configuration

Path: Stores → Configuration → MHinspeya.de → Combo deal
Section ID: mhinspeya_combinationdiscount

General Settings

FieldConfig pathDefaultDescription
Enable Combo dealmhinspeya_combinationdiscount/general/enableYes (1)Master storefront switch
Combo Deal URL Keymhinspeya_combinationdiscount/general/url_keycombo-offersList 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.

FieldDefault
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 Width0px
Add to Cart Border Radius4px
Add to Cart Font Size16px
Add to Cart Line Height1.25
Add to Cart Vertical Padding16px
Add to Cart Horizontal Padding19px

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:

FieldConfig pathDefault
Combo Discountsales/totals_sort/customcombo_discount25

Typical order:

CodeSortLine
subtotal10Subtotal
discount20Magento cart rule / coupon
customcombo_discount25Combo Discount
shipping30Shipping
tax40Tax
grand_total100Grand 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)

  1. Go to Combo deal → Manage Combo deal → Add New Combo deal.
  2. 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 (%)150
    • Discount Amount (step %) — optional stepped increase per extra child
    • Stop Discount Count13 max children that count toward the stepped %
  3. Package Main Product
    • Select exactly one product (grid enforces single selection).
  4. Package Child Products
    • Select 1 to 3 children.
  5. Click Save.

Validation rules (admin save)

RuleBehavior
Required fieldsTitle, dates, stores, customer groups, main, children
Main countExactly one Main Product
Child countAt least 1, maximum 3
Same Main + Child in one comboBlocked (error lists conflicting IDs)
Product exclusivity across combosA 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 errorGeneral 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}.html from 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:

  1. Magento Discount runs first (sort_order ~300).
  2. Combo Discount runs after (sort_order 420).
  3. Both can appear in the summary and stack.
  4. “Discard subsequent rules” on a cart rule only affects other cart rules, not Combo Discount.
  5. Combo % is based on item row totals (not after subtracting the cart-rule amount).

Testing with a coupon

  1. Create/enable a Cart Price Rule (Marketing → Cart Price Rules).
  2. Use Specific Coupon, turn off auto generation if you need a stable manual code.
  3. 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:

TablePurpose
mhinspeya_combinationdiscount_discountcombinationCombo header (title, status, dates, stores, groups, discount fields, JSON product maps)
mhinspeya_combinationdiscount_mainproductsMain product relations
mhinspeya_combinationdiscount_childprodcutsChild 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.

BehaviorDetail
StatusDisabled (0) by default
ProductsNone — Main/Child are empty
TitlesPrefixed with [Sample]
IdempotentSkips titles that already exist
RevertPatch 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

  1. [ ] Module enabled (module:status)
  2. [ ] setup:upgrade completed without schema errors
  3. [ ] ACL role can access Combo deal menu
  4. [ ] Config: Enable = Yes; URL key set
  5. [ ] Create a combo with Main + 1–3 Children, dates covering now, correct store & customer group
  6. [ ] Open Main Product PDP → combo block loads
  7. [ ] Add combo via combo Add to Cart → cart shows Combo Discount under Subtotal
  8. [ ] Open {url_key}.html list page
  9. [ ] (Optional) Apply a Magento coupon → both Discount and Combo Discount visible
  10. [ ] Flush caches after design/URL changes

Troubleshooting

IssueWhat to check
Combo not on PDPProduct is Main? Status Yes? Dates include now? Store view + customer group match? Module enabled?
No Combo Discount in cartItems added via combo Add to Cart (customcombo_product)? Main + child present? Qty remaining?
Admin: product already usedProduct exists on another combo for overlapping stores (as Main or Child)
Admin: same Main and ChildDo not select the same product in both grids of one combo
Coupon invalidRule active; specific coupon exists; auto-generation off if using a manual code; website + customer group
Combo Discount above SubtotalEnsure sales/totals_sort/customcombo_discount = 25 and cache flushed (Hyvä StoreConfig plugin also forces 25 when missing/0)
Dates “required” while filledUse native datetime fields (legacy jQuery calendar was removed); hard-refresh admin
Form empty after validation errorFixed via DataProvider + form-part product maps; flush caches / open a fresh New after upgrading
Cache flush permission deniedRun Magento CLI as www-data or flush from Admin

Support

For questions, issues, or feature requests, contact MHinspeya / Inspeya.