# etechflow/module-supplier-autoflow

> Magento 2 module that auto-toggles supplier active flags based on stock and reprices products from the first-active supplier's cost × markup. Built for stores with multi-supplier products and dynamic supplier-driven pricing. Pairs cleanly with ETechFlow Next Day Eligibility — when a supplier flips, NDE re-evaluates next-day eligibility automatically.

`composer require etechflow/module-supplier-autoflow`

Canonical URL: https://packagento.com/etechflow/module-supplier-autoflow

## At a glance

- **Vendor**: etechflow (https://packagento.com/etechflow.md)
- **Latest version**: 1.2.2 — released 2026-06-22
- **Pricing**: Free
- **Package type**: Magento 2 module
- **Status**: active, accepting new buyers

## Installation

Packagento is licence-gated, so even free packages need a licence on a project before Composer can resolve them.

1. **Sign in or create an account** at https://packagento.com/customer/account/.

2. **Add the package to your account.** Open https://packagento.com/etechflow/module-supplier-autoflow and complete the free checkout. A licence is minted automatically.

3. **Create or pick a project, then activate the licence on it.**
   - Projects represent the Magento installs you deploy to. Manage them at https://packagento.com/projects/.
   - Activate the new licence on the project you'll deploy this package to. Activation is what generates the Composer credentials scoped to that project.

4. **Add the project credentials to your Magento codebase.**

   Grab the project's public + private key from https://packagento.com/projects/ (open the project, then its Credentials tab), and add them to `auth.json`:

   ```json
   {
     "http-basic": {
       "packagento.com": {
         "username": "ppk_live_...",
         "password": "psk_live_..."
       }
     }
   }
   ```

   Add the Packagento Composer repository to `composer.json`:

   ```json
   {
     "repositories": [
       { "type": "composer", "url": "https://packagento.com" }
     ]
   }
   ```

5. **Install and apply.**

   ```bash
   composer require etechflow/module-supplier-autoflow:*
   bin/magento setup:upgrade
   bin/magento setup:di:compile
   bin/magento cache:flush
   ```

## What it does

Magento 2 module that auto-toggles supplier active flags based on stock and reprices products from the first-active supplier's cost × markup. Built for stores with multi-supplier products and dynamic supplier-driven pricing. Pairs cleanly with ETechFlow Next Day Eligibility — when a supplier flips, NDE re-evaluates next-day eligibility automatically.

## README

Auto-toggle supplier active flags based on stock, then reprice products from
the first-active supplier's `cost × markup`. For Magento 2 stores with
multi-supplier products and dynamic supplier-driven pricing.

### What it does

You sell the same product through multiple suppliers in priority order:

```
S1 = Onlyda          (your manufacturer; cheap; only ships from your own stock)
S2 = Auto Remote     (drop-ship; more expensive; ships when Onlyda is out)
S3 = Remkeys         (drop-ship fallback)
```

This module makes that workflow automatic:

1. **Auto-toggle**: when Onlyda stock hits 0, flip S1's `active` flag off.
   The product now fulfills from S2 = Auto Remote.
2. **Reprice**: with S2 now first-active, recompute the customer-facing price
   as `S2.cost × (1 + S2.markup / 100)`. Apply your configured rounding +
   anchor strategy. Write to `price` and/or `special_price` per your output
   target.
3. **Audit**: every flip + reprice is logged to `etechflow_supplier_autoflow_log`
   so finance can trace why a price changed.
4. **NDE integration**: if you also run `etechflow/module-next-day-eligibility`,
   the module synchronously triggers NDE's evaluator after every change.
   `next_day_eligible` stays in sync with the active supplier — no event-bus
   relay required.

### Everything is merchant-configurable

No supplier names, no attribute codes, no markup percentages are hardcoded.
All configuration lives in
`Stores → Configuration → eTechFlow → Supplier Autoflow`.

#### Supplier slot definition

One slot per line, in priority order (top = highest priority):

```
S1|s1_active|s1|s1_cost|s1_markup
S2|s2_active|s2|s2_cost|s2_markup
S3|s3_active|s3|s3_cost|s3_markup
```

Format: `label|active_attr|name_attr|cost_attr|markup_attr[|stock_source_or_qty_attr]`.

#### Stock-dependent suppliers

Name-based, not slot-position-based. Lists which supplier *names* get the
auto-toggle behaviour:

```
Onlyda
OurOwnWarehouse
```

Other slots (drop-ship suppliers) stay manual-only. The merchant decides
when to disable them.

#### Stock trigger source

Pick from: `magento_qty`, `msi_default`, `msi_per_slot`, `per_slot_qty_attr`,
`disabled`. MSI modes soft-detected — module installs and works on non-MSI
builds.

#### Price output target

- `price` — write to regular price only.
- `special_price` — write to special price only.
- `special_price_with_anchor` (recommended) — write computed value to
  `special_price`, write `price = special_price × anchor_multiplier`.
  Renders strikethrough on storefront + sale-pricing on Google Shopping.

#### Rounding

`2dp`, `5p`, `10p`, `99p_ending`, or `none`.

#### No-active-supplier fallback

When every slot is inactive (all suppliers out): set product to
out-of-stock, disable it entirely, or leave unchanged with a warning.

### How it stays accurate

1. **Legacy stock event observer** — `cataloginventory_stock_item_save_after`.
2. **MSI source-items plugin** — `Magento\InventoryApi\Api\SourceItemsSaveInterface`,
   soft-detected so non-MSI installs skip it cleanly.
3. **Product-save observer** — re-runs the pricing engine when a merchant
   manually flips a slot's active flag or changes cost/markup.
4. **Hourly cron** — belt-and-braces safety net for any propagation hole.
5. **CLI** — `bin/magento etechflow:autoflow:resync [--sku=...]` for
   manual full-catalogue evaluation.
6. **FPC tag invalidation** — `cat_p_<id>` clean after every price write,
   so customers see fresh HTML without a manual `cache:flush`.

### Reverse toggle

When stock comes back (Onlyda restocked), the previously auto-toggled slot
flips back to active and the product reprices from THAT slot's current
cost — handles supplier cost changes on restock.

Configurable on/off — some merchants want one-way toggles only.

### Audit log

Every event writes to `etechflow_supplier_autoflow_log` with:

- Product ID + SKU
- Event type: `auto_toggle` / `reprice` / `no_active_supplier` / `error`
- Trigger source: `stock_save` / `msi_source_items_save` / `product_save` / `cron` / `cli_resync`
- Before/after active slot
- Before/after price + special_price
- Human-readable message

Read via the admin grid (v0.1.1+ — coming next) or directly via SQL.

### Companion modules

- **`etechflow/module-next-day-eligibility`** — when installed, Autoflow's
  active-flag changes automatically trigger NDE's eligibility recompute.
  Recommended together for stores running dynamic next-day rules.

### Versioning

v0.1.0 — initial release. Engine complete; admin audit-log grid lands in
v0.1.1.

## Changelog

All notable changes to this module. Adheres to [Semantic Versioning](https://semver.org/).

---

### [0.1.0] — 2026-05-23 — Initial release

First release of `etechflow/module-supplier-autoflow`. Engine complete; admin audit-log grid lands in v0.1.1.

#### What it does

You sell the same product through multiple suppliers in priority order:

```
S1 = Onlyda          (manufacturer; cheap; ships from own stock)
S2 = Auto Remote     (drop-ship; more expensive; ships when S1 is out)
S3 = Remkeys         (drop-ship fallback)
```

This module makes that workflow automatic:

1. **Auto-toggle**: when the configured "stock-dependent" supplier's stock hits 0, flip that slot's `active` flag off. Other slots (drop-ship) stay manual-only.
2. **Reprice**: with the next slot now first-active, recompute the customer-facing price as `cost × (1 + markup/100)`. Apply configured rounding + (optional) anchor strategy for strikethrough pricing. Write to `price` and/or `special_price` per the configured output target.
3. **Audit**: every flip + reprice writes to `etechflow_supplier_autoflow_log` so finance can trace price changes.
4. **NDE integration** (when `etechflow/module-next-day-eligibility` is installed): synchronously calls NDE's `EligibilityEvaluator` after every change so `next_day_eligible` reflects the new active supplier. Pairs cleanly with NDE v1.6.4's first-active-wins mode.

#### Everything is merchant-configurable

No supplier names, no attribute codes, no markup percentages are hardcoded. All configuration lives in `Stores → Configuration → eTechFlow → Supplier Autoflow`:

- **Supplier slot definitions** — pipe-separated lines, priority-ordered (top = highest)
  Format: `label|active_attr|name_attr|cost_attr|markup_attr[|stock_source_or_qty_attr]`
- **Stock-dependent supplier names** — case-insensitive list of slots that get auto-toggle behaviour. Other slots stay manual.
- **Stock trigger source** — five modes: `magento_qty` / `msi_default` / `msi_per_slot` / `per_slot_qty_attr` / `disabled`. MSI modes soft-detected.
- **Price output target** — `price` / `special_price` / `special_price_with_anchor` (recommended for retail strikethrough pricing).
- **Anchor multiplier** — for `special_price_with_anchor` mode. Default 1.40 (regular price renders 40% higher than special). Optional per-product attribute override.
- **Rounding** — `2dp` / `5p` / `10p` / `99p_ending` / `none`.
- **No-active-supplier fallback** — `out_of_stock` / `disable_product` / `leave_with_warning`.
- **Reverse toggle on restock** — when stock returns, re-activate the slot and reprice from its current cost.

#### Architecture

Six service classes + two observers + one plugin + one cron + one CLI:

- `Model/Config.php` — typed config reader (every knob)
- `Model/Slot.php` — immutable struct for one supplier slot
- `Model/SlotResolver.php` — first-active iteration with per-request memoization
- `Model/PricingEngine.php` — `cost × markup`, rounding, output target
- `Model/PriceResult.php` — immutable return value
- `Model/StockTrigger.php` — 5 trigger modes (legacy / 3 MSI variants / disabled)
- `Model/AutoToggleService.php` — flips active flags on stock change
- `Model/RepriceService.php` — walks slots → first-active → reprice → audit → notify NDE
- `Model/NdeIntegration.php` — soft-detect NDE, synchronously call evaluator
- `Model/AuditLogger.php` — writes to `etechflow_supplier_autoflow_log`
- `Observer/AutoToggleOnStockChange.php` — legacy `cataloginventory_stock_item_save_after` hook
- `Observer/RepriceOnProductSave.php` — `catalog_product_save_after` hook (manual merchant edits)
- `Plugin/AutoToggleOnMsiSourceItemsSave.php` — MSI source-items save hook (soft-detect)
- `Cron/RecomputePrices.php` — hourly safety net (15-past every hour)
- `Console/Command/ResyncCommand.php` — `bin/magento etechflow:autoflow:resync [--sku=...]`

#### Audit log table

_(Changelog truncated for .md surface. Full history on https://packagento.com/etechflow/module-supplier-autoflow.)_

## Recent Versions

| Version | Released |
|---|---|
| 1.2.2 | 2026-06-22 |
| 1.2.1 | 2026-06-22 |
| 1.2.0 | 2026-06-22 |
| 1.1.0 | 2026-06-06 |
| 1.0.0 | 2026-06-04 |
| 0.1.0 | 2026-05-23 |

## Dependencies

### Require

| Package | Constraint |
|---|---|
| magento/framework | ^103.0\|\|^104.0 |
| magento/module-backend | ^102.0\|\|^103.0 |
| magento/module-catalog | ^104.0\|\|^105.0 |
| magento/module-catalog-inventory | ^100.4\|\|^101.0 |
| magento/module-config | ^101.2\|\|^102.0 |
| magento/module-cron | ^100.4\|\|^101.0 |
| magento/module-eav | ^102.1\|\|^103.0 |
| magento/module-store | ^101.1\|\|^102.0 |
| magento/module-ui | ^101.2\|\|^102.0 |
| php | ~8.1.0\|\|~8.2.0\|\|~8.3.0\|\|~8.4.0 |

### Suggest

| Package | Constraint |
|---|---|
| etechflow/module-next-day-eligibility | When installed, Autoflow's supplier active-flag changes automatically trigger NDE to recompute next_day_eligible — keeps shipping eligibility in sync with the supplier that's actually fulfilling the order. |
| magento/module-inventory-api | When MSI is enabled, Autoflow can use per-source stock as the auto-toggle trigger. Soft-detected — module installs and works without MSI. |

## Quality

Latest release (1.2.2) fails the Packagento QA pipeline. Verdicts below are per-cell (Magento line × PHP version) for the matrixed tools, and run-once for the static / security tiers.


### Compatibility

Each Magento line is installed on its supported PHP versions, then the module is built (DI compile + static-content deploy). Cells show passed / failed / untested; staircase gaps render as `–`.

| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | Pass | Pass | – | – |
| 2.4.8 | – | Pass | Pass | – |
| 2.4.9 | – | – | Pass | not tested |


### Code Quality

Advisory checks against the module's source. Never affect the Compatibility verdict — a phpcs finding can't make a module incompatible.

#### Static Analysis

Coding standards (phpcs), mess detection (phpmd), copy-pasted code (cpd), PHP cross-version compatibility, composer.json validity. Each runs once for the whole module.

| Tool | Status | Findings | Summary |
|---|---|---|---|
| PHPCS | Fail | 142 | 5 errors, 137 warnings (ruleset: Magento2) — 65 auto-fixable with phpcbf |
| PHPMD | Warning | 24 | 24 rule violations (CyclomaticComplexity:7, NPathComplexity:7, UnusedLocalVariable:3, EmptyCatchBlock:3, MissingImport:2) |
| Cpd | Warning | 1 | 1 duplicated chunk spanning 41 total lines (min-lines=5, min-tokens=70) |
| Composer validate | Info | 1 | valid; 1 advisory note (composer validate --strict) |

#### PHPStan

Type-checks the module against a real Magento install. Re-runs per Magento + PHP version because resolvable symbols differ between releases.

| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | 35 | 35 | – | – |
| 2.4.8 | – | 35 | 35 | – |
| 2.4.9 | – | – | 35 | N/A |


### Tests

Unit and integration suites run per Magento + PHP cell. Test failures speak to the module's behaviour, not its compatibility with a line, so they're reported here separately.

#### Unit Tests

| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | N/A | N/A | – | – |
| 2.4.8 | – | N/A | N/A | – |
| 2.4.9 | – | – | N/A | N/A |

#### Integration Tests

| Magento | PHP 8.2 | PHP 8.3 | PHP 8.4 | PHP 8.5 |
|---|---|---|---|---|
| 2.4.7 | N/A | N/A | – | – |
| 2.4.8 | – | N/A | N/A | – |
| 2.4.9 | – | – | N/A | N/A |


### Security

Dependency-advisory audit (composer audit) plus a source malware scan. A malware detection fails the version outright.

| Tool | Status | Findings | Summary |
|---|---|---|---|
| Composer audit | Pass | 0 |  |
| Malware scan | Pass | 0 |  |

## Licence and pricing

Free. A licence is still minted on checkout and bound to your project for Composer access — no payment step.

Refundable within 14 days of first purchase via https://packagento.com/account/refunds/.

## Install via Claude Code or any MCP client

The Packagento MCP server can run the licence + project + Composer steps above in one tool call:

```
purchase_and_install_packages(
  composer_names=["etechflow/module-supplier-autoflow"],
  project_id="proj_xxx"
)
```

This handles cart, checkout, licence minting, project activation, and writes auth.json credentials. Connect a client with `claude mcp add packagento https://mcp.packagento.com`. Full setup at https://packagento.com/docs/mcp-setup.

## Vendor

etechflow is a Magento 2 vendor on Packagento. See https://packagento.com/etechflow.md for their full catalogue.

