Search by

augustash / mna_offers

DanePete

Schedules the offers on Minnesota Air dealer sites from one screen and imports the coupons already on their pages.

Package info

github.com/augustash/drupal-mna-offers

Type:drupal-module

pkg:composer/augustash/mna_offers

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.3 2026-11-05 16:54 UTC

This package is auto-updated.

Last update: 2026-11-05 16:57:48 UTC


README

Scheduled offers for Minnesota Air dealer sites. Dealer staff manage every discount and promotion from Content › Offers, with start and end dates that put each one up and take it down. August Ash migrates the offers already typed into a site's pages from Configuration › Content › Offers setup, and the pages look exactly as they did.

How it works

An offer does not store its own wording, image or button. It owns one of the site's existing offer components and adds what those never had: dates, one list, and a record of which pages show it.

Site generation An offer's content is Placed on a page by
Paragraphs (37 sites) a coupon paragraph an "Offers" paragraph (mna_offers_slot)
eXo / Layout Builder (11 sites) a mna_theme_offer block an "Offers" block (mna_offers_slot)

Rendering through the site's own component is what makes an offer identical to the coupon it replaced: the theme's own template, the hardcoded dealer phone number, the Schedule Service webform modal, eXo's wrapper, modifiers and first/last markers. There are no offer templates in this module to drift out of step with 48 themes.

The offer form is built the same way: the component's own fields, with the site's own widgets, and on eXo sites the component's Appearance choices (Background Color) from eXo's property manager, exactly as Layout Builder offers them. Paragraphs coupons have no appearance settings to carry; their theme colours every coupon alike.

On Paragraphs sites the "Offers" paragraph is never rendered itself: before a page is built, it is swapped in memory for the coupons of the offers running today, in its position in the field. Themes that read the field directly (one sizes a Coupons section with a with-2-coupons class counted from its items) see exactly what they saw before migration. The swap is undone as soon as the page has rendered, and before any save, so it is never stored. On eXo sites the "Offers" block is replaced in the layout build in hook_node_view(), which core runs before eXo's hook_entity_view() marks each region's first and last component.

The generation is detected, not configured. mna_offers.adapter returns the ParagraphCouponAdapter or ExoOfferAdapter, and everything generation specific (form, rendering, finding and replacing components on a page) lives behind ComponentAdapterInterface.

Migration

OfferImporter walks each page's current revision for components. Parent pointers are not trusted: on the surveyed fleet 1 in 10 coupon paragraphs was still on a page, and the rest were leftovers from old revisions that must not come back as live offers.

  • Adjacent components on a page become one placement; an offer remembers its placement, so offers on the homepage stay on the homepage.
  • Printed expiry lines ("Ends November 20th, 2026", "EXP. 7/31/17") become end dates. A line with no year uses the year it was written. A line with no date keeps showing and is flagged. Sites without an expiry field are read for a sentence like "Offer ends 11/20/2026" in the description.
  • Components are copied, not moved. The page is saved as a new revision and "Return offers to this page" puts components back from the offers as they are now. Offers not showing today go back unpublished, and offers whose spot is no longer on the page are left alone rather than deleted.
  • Offers already past their printed date keep showing, as they do today, and are flagged for the dealer, unless the reviewer ticks "Take down" for them. Migrating never changes what visitors see on its own.
  • A component hidden on its page becomes an offer that is turned off, so "Turn on" shows it. Identical components on several pages share one offer only when they would also show the same way.
  • Migrating a page again, after someone types a new coupon into it, adds to its record, so "Return" still puts back everything.
  • Components are copied without File (Field) Paths moving their files, so image URLs stay where they are.

Migration creates content only, so it runs on live through the UI even though these sites lock configuration for web requests.

Visibility and caching

PlacementRenderer loads the running offers for a placement and gives the build a max-age of seconds until the site's midnight plus the mna_offer_list tag. hook_cron() clears that tag once per day for the page cache and the Pantheon edge, which only respond to tags. End dates are inclusive and use the site timezone; the setup screen warns when a site is on UTC.

Screens

Dealer staff reach the Offers screen from the site's admin bar: Manage Offers in Escort on Paragraphs sites, and in the eXo toolbar's Content group on eXo sites. The module ships both items as optional config, so each site gets the one for the admin bar it has, and only users who may open the screen see it.

Path Who What
/admin/content/offers Dealer staff (manage mna offers) Season strip, offers grouped by status, turn on/off
/admin/content/offers/add, /{id}/edit Dealer staff The site's own component fields plus when, where and on/off, with a live preview
/admin/config/content/offers August Ash (administer mna offers) What is particular about this site
/admin/config/content/offers/migrate August Ash Review and migrate pages, return migrated pages

Install

Needs Drupal 10.3 or 11 and either a coupon paragraph type or the eXo Offer component. The module refuses to install without one.

  • Add the package, on a local copy of the site with dev's database: composer require augustash/mna_offers:^1.0. It comes from Packagist and lands in web/modules/contrib/mna_offers, which every site's .gitignore already covers.
  • Export its config locally: drush cim -y first, so the export holds only what the module adds, then drush en mna_offers -y and drush cex -y. Commit composer.json, composer.lock and config/. These sites lock config on the web and Pantheon stays in git mode, so config only reaches an environment through git.
  • Deploy: push to dev, then deploy to test and live, and on each run drush updb -y && drush cim -y && drush cr.
  • Migrate the offers: on each environment open Configuration › Content › Offers setup › Migrate, check the ticked pages and migrate them.
  • Hand over: dealer staff manage offers from Manage Offers in the admin bar.

On Pantheon, run drush as terminus remote:drush <site>.<env> -- <command>.

Tests

From web/ on a Paragraphs site (the kernel tests need paragraphs), with SQLite so the site's own database is never touched:

SIMPLETEST_DB=sqlite://localhost//tmp/mna-offers.sqlite \
  ../vendor/bin/phpunit -c core modules/contrib/mna_offers/tests

The parser tests include every expiration line found on a live page across the fleet in September 2026. The kernel tests cover dates, migrating and returning a page, and a page keeping its Offers spot through a render and a save.