Skip to content

Commit 30ddb56

Browse files
authored
Merge pull request #2073 from shopware/integrate-subscription-to-b2b-employee-management
chore: Integrate Subscription to B2B Employee Management
2 parents 30ae2da + 2a357cb commit 30ddb56

4 files changed

Lines changed: 422 additions & 0 deletions

File tree

.wordlist.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,9 @@ Subcommands
913913
SubjectAware
914914
SubjectStorer
915915
Subprocessor
916+
SubscriptionCartConvertedSubscriber
917+
SubscriptionOrderPlacedSubscriber
918+
SubscriptionTransformedSubscriber
916919
SuggestPage
917920
SwagAdvDevBundle
918921
SwagB
Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
---
2+
nav:
3+
title: Subscription Integration
4+
position: 60
5+
6+
---
7+
8+
# B2B Employee Integration for Subscriptions
9+
10+
This guide describes how B2B Employees are integrated with the Subscription module, enabling employee-based subscription management and tracking within a B2B context.
11+
12+
## Overview
13+
14+
The B2B Employee Integration for Subscriptions extends subscription functionality to support B2B employee workflows without modifying the core Subscription module. All B2B-specific logic is contained within the B2B Employee Management module using decorators, extensions, and event subscribers.
15+
16+
This integration enables:
17+
18+
- **Permission-based subscription access** - Control which subscriptions employees can view
19+
- **Employee tracking** - Track which employee created each subscription
20+
- **Organization context preservation** - Maintain organization data throughout the subscription lifecycle
21+
22+
## Prerequisites
23+
24+
- Shopware 6.7 with Subscriptions extension
25+
- B2B Components with Employee Management module
26+
- Understanding of [Subscription concepts](../../../../extensions/subscriptions/concept.md)
27+
- Understanding of [B2B Employee Management concepts](../concepts/index.md)
28+
29+
## Key Features
30+
31+
### Employee-Based Subscription Permissions
32+
33+
Employees can view subscriptions based on their assigned permissions:
34+
35+
| Permission | Access Level |
36+
|---------------------------------------|------------------------------------------------------------------------|
37+
| `subscription.read.all` | View all subscriptions in the system |
38+
| `organization_unit.subscription.read` | View subscriptions from assigned organization unit + own subscriptions |
39+
| (no permission) | View only own subscriptions |
40+
41+
These permissions are checked when employees access subscription lists, ensuring data isolation and security in B2B contexts.
42+
43+
### Employee Tracking
44+
45+
Subscriptions track which employee created them through the `b2b_components_subscription_employee` association table. This enables:
46+
47+
- **Permission-based filtering** - Filter subscriptions by employee and organization
48+
- **Employee context in orders** - Both initial and renewal orders maintain employee context
49+
- **Audit trails** - Track which employee initiated each subscription
50+
51+
### Organization Context
52+
53+
Organization data is preserved throughout the subscription lifecycle:
54+
55+
- **Initial orders** - Organization data is added when a subscription is created during checkout
56+
- **Renewal orders** - Organization data is maintained in later automated orders
57+
- **Context preservation** - Organization information flows through all subscription-related processes
58+
59+
## Architecture
60+
61+
### Core Components
62+
63+
The integration is built using several key components in the B2B Employee Management module:
64+
65+
**Decorators:**
66+
67+
- `SubscriptionRouteDecorator` - Decorates `SubscriptionRoute` to apply permission-based filtering
68+
- `SalesChannelContextServiceDecorator` - Adds employee context to subscription sales channel contexts
69+
70+
**Event Subscribers:**
71+
72+
- `SubscriptionTransformedSubscriber` - Adds employee and organization data during subscription creation
73+
- `SubscriptionCartConvertedSubscriber` - Adds employee and organization data to initial orders
74+
- `SubscriptionOrderPlacedSubscriber` - Maintains employee context in renewal orders
75+
76+
**Entity Extension:**
77+
78+
- `SubscriptionExtension` - Extends `SubscriptionDefinition` with `subscriptionEmployee` association
79+
- `SubscriptionEmployeeDefinition` - Defines the subscription-employee relationship
80+
81+
**Filter Service:**
82+
83+
- `SubscriptionEmployeeFilter` - Implements permission-based subscription filtering logic
84+
85+
### Integration Patterns
86+
87+
The integration follows Shopware best practices:
88+
89+
**1. Decorator Pattern**
90+
91+
Instead of modifying core Subscription code, the integration uses decorators to extend functionality:
92+
93+
```php
94+
// SubscriptionRouteDecorator wraps the core SubscriptionRoute
95+
$this->decorated->load($request, $context, $criteria);
96+
// Then applies employee-based filtering
97+
$this->subscriptionEmployeeFilter->applyEmployeeFilter($criteria, $employee);
98+
```
99+
100+
**2. Event-Based Integration**
101+
102+
Key subscription events are used to inject employee data:
103+
104+
- `SubscriptionTransformedEvent` - Fired when subscription is created from cart
105+
- `SUBSCRIPTION_CART_CONVERTED` - Fired when subscription cart is converted to order
106+
- `CheckoutOrderPlacedEvent` - Fired when renewal order is placed
107+
108+
**3. Entity Extension Pattern**
109+
110+
The `SubscriptionExtension` adds the employee association to subscriptions without modifying the core entity:
111+
112+
```php
113+
new OneToOneAssociationField(
114+
'subscriptionEmployee',
115+
'id',
116+
'subscription_id',
117+
SubscriptionEmployeeDefinition::class,
118+
false
119+
);
120+
```
121+
122+
## Database Schema
123+
124+
### b2b_components_subscription_employee Table
125+
126+
This table links subscriptions to the employees who created them:
127+
128+
| Column | Type | Description |
129+
|-------------------|-------------|----------------------------------------|
130+
| `id` | BINARY(16) | Primary key |
131+
| `subscription_id` | BINARY(16) | Foreign key to `subscription` (UNIQUE) |
132+
| `employee_id` | BINARY(16) | Foreign key to `b2b_employee` |
133+
| `created_at` | DATETIME(3) | Creation timestamp |
134+
| `updated_at` | DATETIME(3) | Update timestamp |
135+
136+
**Key characteristics:**
137+
138+
- One-to-one relationship between subscription and subscription_employee
139+
- `subscription_id` has a UNIQUE constraint ensuring one employee per subscription
140+
- Foreign keys maintain referential integrity
141+
142+
## Technical Flows
143+
144+
### Initial Subscription Order Flow
145+
146+
When an employee checks out with a subscription product, the following flow occurs:
147+
148+
```mermaid
149+
sequenceDiagram
150+
participant Customer as Employee Customer
151+
participant Checkout as Checkout Process
152+
participant Transformer as SubscriptionTransformer
153+
participant TransformedSub as SubscriptionTransformedSubscriber
154+
participant CartSub as SubscriptionCartConvertedSubscriber
155+
participant Order as Initial Order
156+
157+
Customer->>Checkout: Checkout with subscription product
158+
Checkout->>Transformer: transform() subscription cart
159+
Transformer->>TransformedSub: dispatch SubscriptionTransformedEvent
160+
TransformedSub->>TransformedSub: Add subscriptionEmployee data
161+
TransformedSub->>TransformedSub: Add orderEmployee to convertedOrder
162+
TransformedSub->>TransformedSub: Add organization to convertedOrder
163+
Transformer->>CartSub: dispatch SUBSCRIPTION_CART_CONVERTED
164+
CartSub->>CartSub: Add orderEmployee to cart
165+
CartSub->>CartSub: Add organization to cart
166+
CartSub->>Order: Create initial order with employee + org
167+
168+
Note over Order: Initial Order has:<br/>- orderEmployee ✓<br/>- organization ✓
169+
Note over Transformer: Subscription has:<br/>- subscriptionEmployee ✓<br/>- convertedOrder with employee ✓
170+
```
171+
172+
**Key Points:**
173+
174+
1. `SubscriptionTransformedSubscriber` adds employee and organization data to the subscription being created
175+
2. `SubscriptionCartConvertedSubscriber` ensures the initial order contains employee and organization data
176+
3. The subscription's `convertedOrder` field stores this data for future renewal orders
177+
178+
### Permission-Based Subscription Filtering
179+
180+
When an employee views their subscriptions, filtering is applied based on permissions:
181+
182+
```mermaid
183+
flowchart TD
184+
Start[Employee loads subscriptions]
185+
Start --> Decorator[SubscriptionRouteDecorator]
186+
Decorator --> Filter[SubscriptionEmployeeFilter]
187+
Filter --> CheckAll{Has subscription.read.all?}
188+
189+
CheckAll -->|Yes| NoFilter[No filter applied]
190+
NoFilter --> ResultAll[View ALL subscriptions]
191+
192+
CheckAll -->|No| CheckOrg{Has organization_unit<br/>.subscription.read?}
193+
194+
CheckOrg -->|Yes| OrgFilter[Apply OR filter]
195+
OrgFilter --> OwnSubs[Own subscriptions]
196+
OrgFilter --> OrgSubs[Organization subscriptions]
197+
OrgSubs --> ResultOrg[View own + org subscriptions]
198+
OwnSubs --> ResultOrg
199+
200+
CheckOrg -->|No| EmpFilter[Filter by employeeId]
201+
EmpFilter --> ResultOwn[View ONLY own subscriptions]
202+
```
203+
204+
**Permission Logic:**
205+
206+
- **No filter** (`subscription.read.all`) - Employee sees all subscriptions
207+
- **OR filter** (`organization_unit.subscription.read`) - Employee sees their own subscriptions or subscriptions from their organization unit
208+
- **Default filter** - Employee sees only subscriptions they created
209+
210+
## Developer Integration Points
211+
212+
### Accessing Employee Data from Subscriptions
213+
214+
To access the employee who created a subscription:
215+
216+
```php
217+
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
218+
219+
// Add association when loading subscriptions
220+
$criteria = new Criteria();
221+
$criteria->addAssociation('subscriptionEmployee.employee');
222+
223+
$subscription = $subscriptionRepository->search($criteria, $context)->first();
224+
225+
if ($subscription->getSubscriptionEmployee()) {
226+
$employee = $subscription->getSubscriptionEmployee()->getEmployee();
227+
// Access employee data
228+
}
229+
```
230+
231+
### Accessing Employee Data from Orders
232+
233+
Employee data is stored in order extensions:
234+
235+
```php
236+
// For initial or renewal orders
237+
$order = $orderRepository->search($criteria, $context)->first();
238+
239+
// Check if the order has employee context
240+
$orderEmployee = $order->getExtension('orderEmployee');
241+
if ($orderEmployee) {
242+
$employeeId = $orderEmployee->getEmployeeId();
243+
}
244+
245+
// Check if the order has organization context
246+
$organization = $order->getExtension('organization');
247+
if ($organization) {
248+
$organizationId = $organization->getId();
249+
}
250+
```
251+
252+
### Event Subscribers
253+
254+
The integration provides several event subscribers you can use as reference:
255+
256+
**SubscriptionTransformedSubscriber** - Priority: 0
257+
258+
Listens to: `SubscriptionTransformedEvent`
259+
260+
Use case: Add custom data when a subscription is created from a cart
261+
262+
**SubscriptionCartConvertedSubscriber** - Priority: 0
263+
264+
Listens to: `SUBSCRIPTION_CART_CONVERTED` event
265+
266+
Use case: Modify the initial order data during subscription checkout
267+
268+
**SubscriptionOrderPlacedSubscriber** - Priority: 0
269+
270+
Listens to: `CheckoutOrderPlacedCriteriaEvent` and `CheckoutOrderPlacedEvent`
271+
272+
Use case: Add employee context to renewal orders
273+
274+
### Adding Custom Logic
275+
276+
To add custom B2B logic to subscriptions:
277+
278+
1. **Create a decorator** for subscription services (follow `SubscriptionRouteDecorator` pattern)
279+
2. **Subscribe to subscription events** to inject your data
280+
3. **Use entity extensions** to add custom associations without modifying core entities
281+
4. **Check context state** instead of relying on event priorities for more robust integration
282+
283+
Example decorator pattern:
284+
285+
```php
286+
use Shopware\Core\System\SalesChannel\SalesChannelContext;
287+
288+
class CustomSubscriptionServiceDecorator extends AbstractSubscriptionService
289+
{
290+
public function __construct(
291+
private readonly AbstractSubscriptionService $decorated,
292+
private readonly CustomLogicService $customService
293+
) {}
294+
295+
public function getDecorated(): AbstractService
296+
{
297+
return $this->decorated;
298+
}
299+
300+
public function someMethod(SalesChannelContext $context): void
301+
{
302+
// Your custom logic before
303+
$this->customService->doSomething($context);
304+
305+
// Call decorated service
306+
$this->decorated->someMethod($context);
307+
308+
// Your custom logic after
309+
}
310+
}
311+
```
312+
313+
## Related Documentation
314+
315+
- [Subscription Concept](../../../../extensions/subscriptions/concept.md) - Core subscription concepts
316+
- [Mixed Checkout](../../../../extensions/subscriptions/guides/mixed-checkout.md) - Mixed cart checkout flow
317+
- [Separate Checkout](../../../../extensions/subscriptions/guides/separate-checkout.md) - Separate subscription checkout
318+
- [B2B Employee Management Concepts](../concepts/index.md) - Employee and role concepts
319+
- [Creating Permissions via Plugin](./creating-own-permissions-via-plugin.md) - How to extend permissions
320+
- [API Route Restriction](./api-route-restriction-for-employees.md) - Route-level permission control
321+
322+
## Summary
323+
324+
The B2B Employee Integration for Subscriptions provides a clean, maintainable way to add employee context to subscriptions without modifying core code. By using decorators, event subscribers, and entity extensions, the integration:
325+
326+
- Maintains separation of concerns between modules
327+
- Follows Shopware architectural patterns
328+
- Enables permission-based subscription access
329+
- Preserves employee and organization context throughout the subscription lifecycle
330+
- Provides clear extension points for custom logic

0 commit comments

Comments
 (0)