mar-pod-b2b/module-b2b-quote-template

Reusable and recurring negotiated quote templates for Marpod B2B Advanced.

Maintainers

Package info

gitlab.com/mar-pod-b2b/module-b2b-quote-template

Issues

Type:magento2-module

pkg:composer/mar-pod-b2b/module-b2b-quote-template

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

1.1.5 2026-08-21 07:54 UTC

README

Reusable, per-company templates for recurring quote requests.

Status

Full template engine and Admin UI shipped.

Implemented foundation

  • declarative schema: marpod_b2b_quote_template (per company), marpod_b2b_quote_template_item (SKU, quantity, optional agreed price — no price means "use the current price at the time the quote is created"), marpod_b2b_quote_template_usage (audit trail: which customer used the template, the quote it produced, and when);
  • validity window (valid_from/valid_to), an active flag, and a usage cap (usage_limit + a running times_used counter — no limit set means unlimited use);
  • QuoteTemplateManagementInterface::createQuoteFromTemplate() — the entry point that turns a template into a real Marpod_B2bQuote. This is a new, additive contract: it does not modify the frozen Marpod_B2bQuote\Api\QuoteManagementInterface (see B2B_LIGHT_PLAN.md section 6a). It clears and repopulates the logged-in customer's active cart with the template's items (using the template's agreed price where set, current price otherwise), then calls the existing QuoteManagementInterface::createFromCart() to snapshot it into a draft quote — so quote creation itself stays on one, single code path. Blocked outside the validity window or once usage_limit is reached, and every successful call appends a marpod_b2b_quote_template_usage row.

Admin UI

Marpod_B2bQuoteTemplate::manage (marpod_b2b_quote_template/template/index) provides template management including line-item add/remove. The edit screen uses persistent Magento Admin tabs and a native action bar. Its Items grid includes product identity/status, filtering, record counts, page-size selection, and pagination; the add form is separated below the grid.

Requirements

  • PHP 8.5
  • Magento 2.4.9-compatible framework packages
  • mar-pod-b2b/module-b2b-core, mar-pod-b2b/module-b2b-company, mar-pod-b2b/module-b2b-quote

Installation

Run from the Magento project root:

composer require mar-pod-b2b/module-b2b-quote-template:^1.1
bin/magento module:enable Marpod_B2bQuoteTemplate
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:clean

Development

The planned scope, dependency rules, cache boundaries, security requirements, and test strategy are documented in packages/B2B_LIGHT_PLAN.md in the development Magento project (section 9.4).

Unit tests:

vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist packages/module-b2b-quote-template/Test/Unit

Admin smoke test:

cd packages/module-b2b-quote-template/Test/Playwright
npm install
npm test

REST/GraphQL API tests (real HTTP against a running instance — routing, authentication, ACL enforcement, and cross-company isolation; see mar-pod-b2b/module-b2b-core's README for the shared Test/Api base classes these build on):

vendor/bin/phpunit -c dev/tests/api-functional/phpunit_rest.xml \
    packages/module-b2b-quote-template/Test/Api/Rest/QuoteTemplateAdminRestTest.php

vendor/bin/phpunit -c dev/tests/api-functional/phpunit_graphql.xml \
    packages/module-b2b-quote-template/Test/Api/GraphQl/QuoteTemplateGraphQlTest.php

Integration surfaces

Admin REST routes below /rest/V1/marpod-b2b/quote-template require the existing Marpod_B2bQuoteTemplate::manage ACL resource and cover template/item CRUD — the repositories have no company-scoping of their own, so these routes must stay admin-only, never customer-facing.

Authenticated customers get GraphQL: marpodB2bQuoteTemplate/marpodB2bQuoteTemplates queries (company ID always derived from the customer token, never accepted as an argument — the repository itself has no scoping, so the resolvers add it) and one mutation, marpodB2bCreateQuoteFromTemplate(templateId), a thin wrapper around QuoteTemplateManagementInterface::createQuoteFromTemplate() — already fully authorized internally (logged-in customer, quote.create permission, company-ownership match), so the resolver adds no extra checks of its own. The mutation returns module-b2b-quote's own MarpodB2bQuote GraphQL type (reused, not redeclared) via that module's QuoteMapperTrait directly. A template item's agreed_price lands on the resulting quote item's original_price — applied as the cart's custom_price before QuoteManagementInterface::createFromCart() snapshots it — not on offered_price, which stays null on a freshly created quote.

Limitations

  • creating a quote from a template always replaces the customer's current cart contents — there is no "add to existing cart" merge mode;
  • no scheduled/cron-driven recurring quote creation — a customer or admin must trigger each use.

Uninstallation

Do not remove the package while another installed module requires it. The marpod_b2b_quote_template* tables contain business data and are retained when the module is disabled or its package is removed; removing it does not affect quotes already created from a template (they are ordinary Marpod_B2bQuote records with no back-reference dependency).