mar-pod-b2b / module-b2b-quote-template
Reusable and recurring negotiated quote templates for Marpod B2B Advanced.
Package info
gitlab.com/mar-pod-b2b/module-b2b-quote-template
Type:magento2-module
pkg:composer/mar-pod-b2b/module-b2b-quote-template
Requires
- php: ^8.5
- magento/framework: ^103.0
- magento/module-catalog: ^104.0
- magento/module-customer: ^103.0
- magento/module-quote: ^101.2
- mar-pod-b2b/module-b2b-company: ^1.0
- mar-pod-b2b/module-b2b-core: ^1.0
- mar-pod-b2b/module-b2b-quote: ^1.0
This package is auto-updated.
Last update: 2026-08-22 08:36:57 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 runningtimes_usedcounter — no limit set means unlimited use); QuoteTemplateManagementInterface::createQuoteFromTemplate()— the entry point that turns a template into a realMarpod_B2bQuote. This is a new, additive contract: it does not modify the frozenMarpod_B2bQuote\Api\QuoteManagementInterface(seeB2B_LIGHT_PLAN.mdsection 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 existingQuoteManagementInterface::createFromCart()to snapshot it into a draft quote — so quote creation itself stays on one, single code path. Blocked outside the validity window or onceusage_limitis reached, and every successful call appends amarpod_b2b_quote_template_usagerow.
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).