=== Bundle Quantity Discount ===
Requires at least: 6.8
Tested up to: 7.0.2
Requires PHP: 7.4
Stable tag: 1.1.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Tags: woocommerce, quantity discount, bundle, cart, discount tiers

Automatic quantity discounts on eligible products, with live progress banners. Shipping and fees are excluded.

== Description ==
Create percentage or fixed-per-item offers with multiple quantity thresholds. Show shoppers how many eligible items they need to unlock the next tier. Includes progress bars, exclusions, category/product targeting, schedules, customer roles, caps, coupon comparison/stacking, suggestions, preview, and reports.

WooCommerce 9.0 or newer is required. Verified with WooCommerce 10.6.2 and WordPress 7.0.2; see VALIDATION.md for the exact verification scope.

== Installation ==
1. In WordPress, open Plugins > Add New > Upload Plugin.
2. Upload bundle-quantity-discount.zip, install, and activate.
3. Open WooCommerce > Bundle discounts.
4. Enter an internal offer name and customer-facing title.
5. Choose percentage discounts and add tiers 3 / 10 and 5 / 20.
6. Select the desired eligibility, enable the offer, and save.
7. Configure banner locations under Appearance & settings.

No offers are automatically enabled on installation. Creating and saving an enabled offer starts it immediately unless a future start date is supplied.

== Updating the existing plugin ==
1. Keep the existing plugin installed.
2. Open Plugins > Add New > Upload Plugin and upload bundle-quantity-discount.zip.
3. When WordPress detects the existing plugin, choose “Replace current with uploaded”.
4. The existing offers and settings remain in place. Do not uninstall the plugin to update it.

Every release uses the same folder (bundle-quantity-discount) and main file (bundle-quantity-discount.php). The version is stored in the plugin header, not in the installation folder name. If a previous copy was manually installed under a different folder name, WordPress may not recognize it as the same installation; use the canonical folder for future updates.

== Usage ==
Under Appearance & settings, “Show Shop eligible products button” explicitly enables/disables the shop link across all banner placements, including the live preview. “Show eligible product suggestions” separately controls product recommendation cards. Disabling either preserves the button label and offer settings. Existing installations keep their previous button visibility until changed.

Appearance & settings includes separate text/title/savings/small-print colors, typography, alignment, spacing, borders, shadows, button colors/hover/padding/width, and progress colors/height. The live preview uses the same styling as the storefront, supports a narrow mini-cart width, warns about low text contrast, and can reset the design before saving. Existing offers and saved accent/background colors are preserved on upgrade.

Default count mode sums eligible quantities. Distinct mode counts parent products: two variations of one parent count as one distinct product. Every eligible unit receives the unlocked discount in either mode.

Category rules include descendants. Product scope accepts parent product IDs and variation IDs. Exclusions win. Sale items are excluded unless enabled. Find product IDs in Products and category IDs in their edit URLs.

If the cart has one item but the banner still asks for three, check whether that item is excluded by sale/category/product settings. “All products” does not override these exclusions. Enable “Include sale products” if sale items should count. Banners now explain excluded items, and classic mini-carts receive current banners through WooCommerce's own add-to-cart fragments. The banner always counts eligible items rather than blindly subtracting the total cart quantity.

Use [bundle_quantity_discount] for custom placement, or [bundle_quantity_discount offer="OFFER-ID"] for a particular offer. The offer ID is the edit= value in the offer's Edit URL. The Gutenberg block is named Bundle discount banner. Duplicate automatic banners are hidden when the same offer is placed manually in the page; the separate mini-cart retains its own banner.

Banner placeholders: {remaining_quantity}, {next_discount}, {current_discount}, {savings_amount}, {offer_title}. Automatic messages adapt to the next tier; custom templates should account for blank next/current discounts. The savings amount is the actual bundle saving excluding tax, not a projected saving.

== Calculation rules ==
* Highest quantity threshold wins within an offer.
* The single offer with the greatest product discount wins across offers. Equal offers use their saved order.
* A native virtual coupon (bqd-auto-bundle) allocates the discount to product lines. No negative fees or product price mutations are used.
* Amounts follow the product price basis: fixed discounts/caps include tax when product prices include tax, and exclude tax otherwise. Native WooCommerce tax handling calculates the reduced tax. Discount allocation uses currency minor units and largest remainders to preserve the cap.
* Shipping, shipping tax, and fees are outside the discount base.
* Without stacking, WooCommerce calculates the customer coupons with their native eligibility and restrictions and compares their product discount to the best bundle. Coupons win ties. If the bundle wins, customer coupon product amounts become zero during that calculation; their codes and free-shipping eligibility remain. No customer coupon is silently removed by this plugin.
* With stacking, customer coupons are calculated first. The bundle applies to remaining eligible product value, even when WooCommerce's sequential-discount option is off. Threshold quantities still count eligible items that a coupon has fully discounted.
* WordPress store timezone controls schedules. Boundaries are inclusive to the minute. Rules re-evaluate on cart calculation; visible banners also refresh every minute.
* Customer coupon usage restrictions remain native. A retained zero-discount coupon can still be recorded as used by WooCommerce when an order is placed. This avoids silently removing coupons or rewriting WooCommerce usage accounting.

== Orders and refunds ==
Offer ID/name/title, tier, amount, and tax are copied to order metadata. Line items retain their allocated discount basis and net amount. Native line totals and taxes support WooCommerce partial refunds. Offer edits/deletion do not rewrite existing orders. Manually recalculating an existing order is a WooCommerce editing action and may change its totals; the historical metadata is an audit snapshot, not an order recalculation engine.

== Reporting ==
Reports use order creation dates and include processing, completed, on-hold, and refunded orders. Grouping separates currencies and offers. Revenue means net product revenue from the entire order after discounts and allocated line refunds; it excludes tax, shipping, and fees. Discount refund reductions are proportional estimates based on line refunds. Fully refunded orders contribute zero monetary totals. Unallocated manual refunds cannot be attributed to products. Order counts include refunded orders. No incremental-revenue claim is made. Reports scan matching orders in batches of 100; use narrow date ranges for large stores.

== Compatibility and limitations ==
The plugin declares HPOS and Cart/Checkout Blocks compatibility and supports classic cart/checkout. See VALIDATION.md for tests performed and pending visual/browser checks. Product suggestions examine up to 30 recent candidates and show up to three eligible, visible, purchasable products; they may be empty for restrictive offers. Variable-product suggestions link to product pages for option selection. Offer-filtered shop links work on standard product archive/product queries; custom themes or headless catalogs may need the custom button URL.

Third-party coupon engines, subscriptions, multi-currency conversion, bundles/composite product plugins, and themes replacing WooCommerce cart markup need store-specific testing. No external service is required.

== Privacy and uninstall ==
No analytics, tracking, or external requests are added. Session storage remembers banner dismissal for the current tab. Deactivation preserves settings. Uninstall removes settings only if explicitly enabled; order history remains intact.

== Development ==
Run php tests/engine.php for independent calculation checks. For actual WooCommerce integration, point BQD_WP_BOOTSTRAP to a disposable WordPress bootstrap with WooCommerce and this plugin loaded, then run php tests/woocommerce.php. Never run integration tests against a live store: they create/delete test products, coupons, orders, refunds, taxes, and a shipping method.

== Changelog ==
= 1.1.2 =
Added an explicit on/off control for the Shop eligible products button, independent of product suggestions. Applies to all banners and the live preview, preserving the configured label when hidden.

= 1.1.1 =
Fixed storefront theme styles overriding the savings amount/currency color. Nested WooCommerce price elements now follow the selected Savings text color, matching the admin preview. Regular product prices outside the banner are unaffected.

= 1.1.0 =
Added comprehensive banner appearance controls, a live/narrow preview, contrast warnings, and reset-to-defaults. Preserves previous styles on upgrade. Added native mini-cart fragment refresh, exclusion explanations, and sale-item counting regressions. Retains MAK IT Solutions attribution.

= 1.0.2 =
Added developer attribution: Developed by MAK IT Solutions, https://maktisol.com/.

= 1.0.1 =
Fixed native Checkout Block hydration by rendering banners after WooCommerce adds mount attributes. Fixed mini-cart banner mounting when footer content loads later. Banner selection now follows the actual offer applied after coupons. Product-page eligibility no longer hides global mini-cart offers. Rejects non-finite discount inputs. Verified complete test purchases through classic and block checkout.

= 1.0.0 =
Initial release.
