@pinelab/vendure-plugin-customer-managed-groups 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -0
- package/dist/api-extensions.d.ts +16 -0
- package/dist/api-extensions.js +256 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +5 -0
- package/dist/custom-fields.d.ts +11 -0
- package/dist/custom-fields.js +39 -0
- package/dist/customer-managed-groups.plugin.d.ts +7 -0
- package/dist/customer-managed-groups.plugin.js +48 -0
- package/dist/customer-managed-groups.service.d.ts +41 -0
- package/dist/customer-managed-groups.service.js +380 -0
- package/dist/generated/graphql.d.ts +137 -0
- package/dist/generated/graphql.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Vendure Customer Group Extensions
|
|
2
|
+
|
|
3
|
+
This plugin allows customers to manage their own groups, called Customer Managed Groups. Customer Managed Groups can have Group Admins and Group members.
|
|
4
|
+
|
|
5
|
+
- Group admins and members are stored as Vendure's built-in customers, and the groups are the built-in Customer Group entities.
|
|
6
|
+
- Customers can only be in one group, and they are either group participant or group admin. (Customers can still be in multiple)
|
|
7
|
+
- Customers can create groups and add other non-admin customers to their group. Adding others to your group automatically makes you administrator of the group.
|
|
8
|
+
- Group Admins can fetch placed orders for everyone in the group.
|
|
9
|
+
- Group Admins can update profile details of members of their group.
|
|
10
|
+
|
|
11
|
+
## Getting started
|
|
12
|
+
|
|
13
|
+
Add the plugin to your config:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { CustomerManagedGroupsPlugin } from 'vendure-plugin-customer-managed-group';
|
|
17
|
+
plugins: [CustomerManagedGroupsPlugin];
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
You can create your own group via the Shop API, if you are logged in as a customer, by inviting another customer by email:
|
|
21
|
+
|
|
22
|
+
```graphql
|
|
23
|
+
mutation {
|
|
24
|
+
addCustomerToMyCustomerManagedGroup('invitee@gmail.com') {
|
|
25
|
+
id
|
|
26
|
+
createdAt
|
|
27
|
+
updatedAt
|
|
28
|
+
name
|
|
29
|
+
administrators {
|
|
30
|
+
id
|
|
31
|
+
title
|
|
32
|
+
firstName
|
|
33
|
+
lastName
|
|
34
|
+
emailAddress
|
|
35
|
+
}
|
|
36
|
+
participants {
|
|
37
|
+
id
|
|
38
|
+
title
|
|
39
|
+
firstName
|
|
40
|
+
lastName
|
|
41
|
+
emailAddress
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You can also remove participants again, by passing in the customer id that should be removed:
|
|
48
|
+
|
|
49
|
+
```graphql
|
|
50
|
+
mutation {
|
|
51
|
+
removeCustomerToMyCustomerManagedGroup('2') {
|
|
52
|
+
id
|
|
53
|
+
createdAt
|
|
54
|
+
updatedAt
|
|
55
|
+
name
|
|
56
|
+
administrators {
|
|
57
|
+
id
|
|
58
|
+
title
|
|
59
|
+
firstName
|
|
60
|
+
lastName
|
|
61
|
+
emailAddress
|
|
62
|
+
}
|
|
63
|
+
participants {
|
|
64
|
+
id
|
|
65
|
+
title
|
|
66
|
+
firstName
|
|
67
|
+
lastName
|
|
68
|
+
emailAddress
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Admins are allowed to fetch orders of all customers in a group. You can do this with the following query:
|
|
75
|
+
|
|
76
|
+
```graphql
|
|
77
|
+
query {
|
|
78
|
+
ordersForMyCustomerManagedGroup {
|
|
79
|
+
items {
|
|
80
|
+
id
|
|
81
|
+
code
|
|
82
|
+
customer {
|
|
83
|
+
emailAddress
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
totalItems
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
These are all the GraphQL queries and mutations exposed by this plugin:
|
|
92
|
+
|
|
93
|
+
```graphql
|
|
94
|
+
extend type Mutation {
|
|
95
|
+
"""
|
|
96
|
+
Creates a group with the current logged in user as administrator of the group
|
|
97
|
+
"""
|
|
98
|
+
addCustomerToMyCustomerManagedGroup(
|
|
99
|
+
input: AddCustomerToMyCustomerManagedGroupInput
|
|
100
|
+
): CustomerManagedGroup!
|
|
101
|
+
"""
|
|
102
|
+
Create an empty group with the current user as Administrator
|
|
103
|
+
"""
|
|
104
|
+
createCustomerManagedGroup: CustomerManagedGroup!
|
|
105
|
+
|
|
106
|
+
removeCustomerFromMyCustomerManagedGroup(
|
|
107
|
+
customerId: ID!
|
|
108
|
+
): CustomerManagedGroup!
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
extend type Query {
|
|
112
|
+
"""
|
|
113
|
+
Fetch placed orders for each member of the group
|
|
114
|
+
"""
|
|
115
|
+
ordersForMyCustomerManagedGroup: OrderList!
|
|
116
|
+
"""
|
|
117
|
+
Fetch the current logged in group member
|
|
118
|
+
"""
|
|
119
|
+
activeCustomerManagedGroupMember: CustomerManagedGroupMember
|
|
120
|
+
|
|
121
|
+
myCustomerManagedGroup: CustomerManagedGroup
|
|
122
|
+
}
|
|
123
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ID, Order, PaginatedList, RequestContext } from '@vendure/core';
|
|
2
|
+
import { CustomerManagedGroupsService } from './customer-managed-groups.service';
|
|
3
|
+
import { AddCustomerToMyCustomerManagedGroupInput, CustomerManagedGroup, CustomerManagedGroupMember, UpdateCustomerManagedGroupMemberInput } from './generated/graphql';
|
|
4
|
+
export declare const shopSchema: import("graphql").DocumentNode;
|
|
5
|
+
export declare class CustomerManagedGroupsResolver {
|
|
6
|
+
private service;
|
|
7
|
+
constructor(service: CustomerManagedGroupsService);
|
|
8
|
+
ordersForMyCustomerManagedGroup(ctx: RequestContext): Promise<PaginatedList<Order>>;
|
|
9
|
+
activeCustomerManagedGroupMember(ctx: RequestContext): Promise<CustomerManagedGroupMember | undefined>;
|
|
10
|
+
myCustomerManagedGroup(ctx: RequestContext): Promise<CustomerManagedGroup | undefined>;
|
|
11
|
+
createCustomerManagedGroup(ctx: RequestContext): Promise<CustomerManagedGroup | undefined>;
|
|
12
|
+
makeCustomerAdminOfCustomerManagedGroup(ctx: RequestContext, groupId: ID, customerId: ID): Promise<CustomerManagedGroup>;
|
|
13
|
+
addCustomerToMyCustomerManagedGroup(ctx: RequestContext, input: AddCustomerToMyCustomerManagedGroupInput): Promise<CustomerManagedGroup>;
|
|
14
|
+
updateCustomerManagedGroupMember(ctx: RequestContext, input: UpdateCustomerManagedGroupMemberInput): Promise<CustomerManagedGroup>;
|
|
15
|
+
removeCustomerFromMyCustomerManagedGroup(ctx: RequestContext, customerId: ID): Promise<CustomerManagedGroup>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.CustomerManagedGroupsResolver = exports.shopSchema = void 0;
|
|
16
|
+
const graphql_1 = require("@nestjs/graphql");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const graphql_tag_1 = require("graphql-tag");
|
|
19
|
+
const customer_managed_groups_service_1 = require("./customer-managed-groups.service");
|
|
20
|
+
// This is just to enable static codegen, without starting a server
|
|
21
|
+
const scalars = (0, graphql_tag_1.gql) `
|
|
22
|
+
scalar DateTime
|
|
23
|
+
scalar OrderList
|
|
24
|
+
scalar LanguageCode
|
|
25
|
+
scalar JSON
|
|
26
|
+
`;
|
|
27
|
+
exports.shopSchema = (0, graphql_tag_1.gql) `
|
|
28
|
+
input AddCustomerToMyCustomerManagedGroupInput {
|
|
29
|
+
emailAddress: String!
|
|
30
|
+
isGroupAdmin: Boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
input UpdateCustomerManagedGroupMemberInput {
|
|
34
|
+
title: String
|
|
35
|
+
firstName: String
|
|
36
|
+
lastName: String
|
|
37
|
+
emailAddress: String
|
|
38
|
+
addresses: [CustomerManagedGroupAddressInput!]
|
|
39
|
+
customerId: ID!
|
|
40
|
+
customFields: JSON
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
When no ID is given, a new address will be created
|
|
45
|
+
"""
|
|
46
|
+
input CustomerManagedGroupAddressInput {
|
|
47
|
+
id: ID
|
|
48
|
+
fullName: String
|
|
49
|
+
company: String
|
|
50
|
+
streetLine1: String
|
|
51
|
+
streetLine2: String
|
|
52
|
+
city: String
|
|
53
|
+
province: String
|
|
54
|
+
postalCode: String
|
|
55
|
+
countryCode: String
|
|
56
|
+
phoneNumber: String
|
|
57
|
+
defaultShippingAddress: Boolean
|
|
58
|
+
defaultBillingAddress: Boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type CustomerManagedGroupMember {
|
|
62
|
+
customerId: ID!
|
|
63
|
+
title: String
|
|
64
|
+
addresses: [CustomerManagedGroupAddress!]
|
|
65
|
+
firstName: String!
|
|
66
|
+
lastName: String!
|
|
67
|
+
emailAddress: String!
|
|
68
|
+
isGroupAdministrator: Boolean!
|
|
69
|
+
customFields: JSON
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type CustomerManagedGroupAddress {
|
|
73
|
+
id: ID!
|
|
74
|
+
createdAt: DateTime!
|
|
75
|
+
updatedAt: DateTime!
|
|
76
|
+
fullName: String
|
|
77
|
+
company: String
|
|
78
|
+
streetLine1: String!
|
|
79
|
+
streetLine2: String
|
|
80
|
+
city: String
|
|
81
|
+
province: String
|
|
82
|
+
postalCode: String
|
|
83
|
+
country: CustomerManagedGroupCountry!
|
|
84
|
+
phoneNumber: String
|
|
85
|
+
defaultShippingAddress: Boolean
|
|
86
|
+
defaultBillingAddress: Boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type CustomerManagedGroupCountry {
|
|
90
|
+
id: ID!
|
|
91
|
+
createdAt: DateTime!
|
|
92
|
+
updatedAt: DateTime!
|
|
93
|
+
code: String!
|
|
94
|
+
name: String!
|
|
95
|
+
enabled: Boolean!
|
|
96
|
+
translations: [CustomerManagedGroupCountryTranslation!]!
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type CustomerManagedGroupCountryTranslation {
|
|
100
|
+
id: ID!
|
|
101
|
+
languageCode: LanguageCode!
|
|
102
|
+
name: String!
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
type CustomerManagedGroup {
|
|
106
|
+
id: ID!
|
|
107
|
+
createdAt: DateTime!
|
|
108
|
+
updatedAt: DateTime!
|
|
109
|
+
name: String!
|
|
110
|
+
customers: [CustomerManagedGroupMember!]!
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
extend type Mutation {
|
|
114
|
+
"""
|
|
115
|
+
Creates a group with the current logged in user as administrator of the group
|
|
116
|
+
"""
|
|
117
|
+
addCustomerToMyCustomerManagedGroup(
|
|
118
|
+
input: AddCustomerToMyCustomerManagedGroupInput
|
|
119
|
+
): CustomerManagedGroup!
|
|
120
|
+
"""
|
|
121
|
+
Create an empty group with the current user as Administrator
|
|
122
|
+
"""
|
|
123
|
+
createCustomerManagedGroup: CustomerManagedGroup!
|
|
124
|
+
|
|
125
|
+
removeCustomerFromMyCustomerManagedGroup(
|
|
126
|
+
customerId: ID!
|
|
127
|
+
): CustomerManagedGroup!
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
Update a member of one's group
|
|
131
|
+
"""
|
|
132
|
+
updateCustomerManagedGroupMember(
|
|
133
|
+
input: UpdateCustomerManagedGroupMemberInput!
|
|
134
|
+
): CustomerManagedGroup!
|
|
135
|
+
makeCustomerAdminOfCustomerManagedGroup(
|
|
136
|
+
groupId: ID!
|
|
137
|
+
customerId: ID!
|
|
138
|
+
): CustomerManagedGroup!
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
extend type Query {
|
|
142
|
+
"""
|
|
143
|
+
Fetch placed orders for each member of the group
|
|
144
|
+
"""
|
|
145
|
+
ordersForMyCustomerManagedGroup: OrderList!
|
|
146
|
+
"""
|
|
147
|
+
Fetch the current logged in group member
|
|
148
|
+
"""
|
|
149
|
+
activeCustomerManagedGroupMember: CustomerManagedGroupMember
|
|
150
|
+
|
|
151
|
+
myCustomerManagedGroup: CustomerManagedGroup
|
|
152
|
+
}
|
|
153
|
+
`;
|
|
154
|
+
let CustomerManagedGroupsResolver = class CustomerManagedGroupsResolver {
|
|
155
|
+
constructor(service) {
|
|
156
|
+
this.service = service;
|
|
157
|
+
}
|
|
158
|
+
async ordersForMyCustomerManagedGroup(ctx) {
|
|
159
|
+
return this.service.getOrdersForCustomer(ctx);
|
|
160
|
+
}
|
|
161
|
+
async activeCustomerManagedGroupMember(ctx) {
|
|
162
|
+
return this.service.getActiveMember(ctx);
|
|
163
|
+
}
|
|
164
|
+
async myCustomerManagedGroup(ctx) {
|
|
165
|
+
return this.service.myCustomerManagedGroup(ctx);
|
|
166
|
+
}
|
|
167
|
+
async createCustomerManagedGroup(ctx) {
|
|
168
|
+
return this.service.createCustomerManagedGroup(ctx);
|
|
169
|
+
}
|
|
170
|
+
async makeCustomerAdminOfCustomerManagedGroup(ctx, groupId, customerId) {
|
|
171
|
+
return this.service.makeAdminOfGroup(ctx, groupId, customerId);
|
|
172
|
+
}
|
|
173
|
+
async addCustomerToMyCustomerManagedGroup(ctx, input) {
|
|
174
|
+
return this.service.addToGroup(ctx, input);
|
|
175
|
+
}
|
|
176
|
+
async updateCustomerManagedGroupMember(ctx, input) {
|
|
177
|
+
return this.service.updateGroupMember(ctx, input);
|
|
178
|
+
}
|
|
179
|
+
async removeCustomerFromMyCustomerManagedGroup(ctx, customerId) {
|
|
180
|
+
return this.service.removeFromGroup(ctx, customerId);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
__decorate([
|
|
184
|
+
(0, graphql_1.Query)(),
|
|
185
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
186
|
+
__param(0, (0, core_1.Ctx)()),
|
|
187
|
+
__metadata("design:type", Function),
|
|
188
|
+
__metadata("design:paramtypes", [core_1.RequestContext]),
|
|
189
|
+
__metadata("design:returntype", Promise)
|
|
190
|
+
], CustomerManagedGroupsResolver.prototype, "ordersForMyCustomerManagedGroup", null);
|
|
191
|
+
__decorate([
|
|
192
|
+
(0, graphql_1.Query)(),
|
|
193
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
194
|
+
__param(0, (0, core_1.Ctx)()),
|
|
195
|
+
__metadata("design:type", Function),
|
|
196
|
+
__metadata("design:paramtypes", [core_1.RequestContext]),
|
|
197
|
+
__metadata("design:returntype", Promise)
|
|
198
|
+
], CustomerManagedGroupsResolver.prototype, "activeCustomerManagedGroupMember", null);
|
|
199
|
+
__decorate([
|
|
200
|
+
(0, graphql_1.Query)(),
|
|
201
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
202
|
+
__param(0, (0, core_1.Ctx)()),
|
|
203
|
+
__metadata("design:type", Function),
|
|
204
|
+
__metadata("design:paramtypes", [core_1.RequestContext]),
|
|
205
|
+
__metadata("design:returntype", Promise)
|
|
206
|
+
], CustomerManagedGroupsResolver.prototype, "myCustomerManagedGroup", null);
|
|
207
|
+
__decorate([
|
|
208
|
+
(0, graphql_1.Mutation)(),
|
|
209
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
210
|
+
__param(0, (0, core_1.Ctx)()),
|
|
211
|
+
__metadata("design:type", Function),
|
|
212
|
+
__metadata("design:paramtypes", [core_1.RequestContext]),
|
|
213
|
+
__metadata("design:returntype", Promise)
|
|
214
|
+
], CustomerManagedGroupsResolver.prototype, "createCustomerManagedGroup", null);
|
|
215
|
+
__decorate([
|
|
216
|
+
(0, graphql_1.Mutation)(),
|
|
217
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
218
|
+
__param(0, (0, core_1.Ctx)()),
|
|
219
|
+
__param(1, (0, graphql_1.Args)('groupId')),
|
|
220
|
+
__param(2, (0, graphql_1.Args)('customerId')),
|
|
221
|
+
__metadata("design:type", Function),
|
|
222
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object, Object]),
|
|
223
|
+
__metadata("design:returntype", Promise)
|
|
224
|
+
], CustomerManagedGroupsResolver.prototype, "makeCustomerAdminOfCustomerManagedGroup", null);
|
|
225
|
+
__decorate([
|
|
226
|
+
(0, graphql_1.Mutation)(),
|
|
227
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
228
|
+
__param(0, (0, core_1.Ctx)()),
|
|
229
|
+
__param(1, (0, graphql_1.Args)('input')),
|
|
230
|
+
__metadata("design:type", Function),
|
|
231
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
232
|
+
__metadata("design:returntype", Promise)
|
|
233
|
+
], CustomerManagedGroupsResolver.prototype, "addCustomerToMyCustomerManagedGroup", null);
|
|
234
|
+
__decorate([
|
|
235
|
+
(0, graphql_1.Mutation)(),
|
|
236
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
237
|
+
__param(0, (0, core_1.Ctx)()),
|
|
238
|
+
__param(1, (0, graphql_1.Args)('input')),
|
|
239
|
+
__metadata("design:type", Function),
|
|
240
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
241
|
+
__metadata("design:returntype", Promise)
|
|
242
|
+
], CustomerManagedGroupsResolver.prototype, "updateCustomerManagedGroupMember", null);
|
|
243
|
+
__decorate([
|
|
244
|
+
(0, graphql_1.Mutation)(),
|
|
245
|
+
(0, core_1.Allow)(core_1.Permission.Authenticated),
|
|
246
|
+
__param(0, (0, core_1.Ctx)()),
|
|
247
|
+
__param(1, (0, graphql_1.Args)('customerId')),
|
|
248
|
+
__metadata("design:type", Function),
|
|
249
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Object]),
|
|
250
|
+
__metadata("design:returntype", Promise)
|
|
251
|
+
], CustomerManagedGroupsResolver.prototype, "removeCustomerFromMyCustomerManagedGroup", null);
|
|
252
|
+
CustomerManagedGroupsResolver = __decorate([
|
|
253
|
+
(0, graphql_1.Resolver)(),
|
|
254
|
+
__metadata("design:paramtypes", [customer_managed_groups_service_1.CustomerManagedGroupsService])
|
|
255
|
+
], CustomerManagedGroupsResolver);
|
|
256
|
+
exports.CustomerManagedGroupsResolver = CustomerManagedGroupsResolver;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CustomerGroup, Customer, CustomFields } from '@vendure/core';
|
|
2
|
+
export interface CustomerGroupWithCustomFields extends CustomerGroup {
|
|
3
|
+
customFields: {
|
|
4
|
+
isCustomerManaged?: boolean;
|
|
5
|
+
groupAdmins?: Customer[];
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface CustomerWithCustomFields extends Customer {
|
|
9
|
+
groups: CustomerGroupWithCustomFields[];
|
|
10
|
+
}
|
|
11
|
+
export declare const customFields: CustomFields;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.customFields = void 0;
|
|
4
|
+
const core_1 = require("@vendure/core");
|
|
5
|
+
exports.customFields = {
|
|
6
|
+
CustomerGroup: [
|
|
7
|
+
{
|
|
8
|
+
name: 'groupAdmins',
|
|
9
|
+
list: true,
|
|
10
|
+
type: 'relation',
|
|
11
|
+
entity: core_1.Customer,
|
|
12
|
+
graphQLType: 'Customer',
|
|
13
|
+
public: true,
|
|
14
|
+
nullable: true,
|
|
15
|
+
eager: true,
|
|
16
|
+
readonly: false,
|
|
17
|
+
label: [
|
|
18
|
+
{
|
|
19
|
+
languageCode: core_1.LanguageCode.en,
|
|
20
|
+
value: 'Administrators of this group',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
// ui: { component: 'customer-group-admin-selector' },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'isCustomerManaged',
|
|
27
|
+
type: 'boolean',
|
|
28
|
+
public: false,
|
|
29
|
+
nullable: true,
|
|
30
|
+
readonly: true,
|
|
31
|
+
label: [
|
|
32
|
+
{
|
|
33
|
+
languageCode: core_1.LanguageCode.en,
|
|
34
|
+
value: 'Is customer managed',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CustomerManagedGroupsPlugin = void 0;
|
|
13
|
+
const core_1 = require("@vendure/core");
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const api_extensions_1 = require("./api-extensions");
|
|
16
|
+
const custom_fields_1 = require("./custom-fields");
|
|
17
|
+
const customer_managed_groups_service_1 = require("./customer-managed-groups.service");
|
|
18
|
+
let CustomerManagedGroupsPlugin = class CustomerManagedGroupsPlugin {
|
|
19
|
+
};
|
|
20
|
+
CustomerManagedGroupsPlugin.ui = {
|
|
21
|
+
extensionPath: path_1.default.join(__dirname, 'ui'),
|
|
22
|
+
ngModules: [
|
|
23
|
+
{
|
|
24
|
+
type: 'shared',
|
|
25
|
+
ngModuleFileName: 'customer-group-extension.shared-module.ts',
|
|
26
|
+
ngModuleName: 'CustomerGroupExtensionSharedModule',
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
CustomerManagedGroupsPlugin = __decorate([
|
|
31
|
+
(0, core_1.VendurePlugin)({
|
|
32
|
+
imports: [core_1.PluginCommonModule],
|
|
33
|
+
providers: [customer_managed_groups_service_1.CustomerManagedGroupsService],
|
|
34
|
+
shopApiExtensions: {
|
|
35
|
+
resolvers: [api_extensions_1.CustomerManagedGroupsResolver],
|
|
36
|
+
schema: api_extensions_1.shopSchema,
|
|
37
|
+
},
|
|
38
|
+
configuration: (config) => {
|
|
39
|
+
config.customFields = {
|
|
40
|
+
...config.customFields,
|
|
41
|
+
...custom_fields_1.customFields,
|
|
42
|
+
};
|
|
43
|
+
return config;
|
|
44
|
+
},
|
|
45
|
+
compatibility: '^2.0.0',
|
|
46
|
+
})
|
|
47
|
+
], CustomerManagedGroupsPlugin);
|
|
48
|
+
exports.CustomerManagedGroupsPlugin = CustomerManagedGroupsPlugin;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Customer, CustomerGroupService, CustomerService, EntityHydrator, ID, Order, OrderService, PaginatedList, RequestContext, TransactionalConnection } from '@vendure/core';
|
|
2
|
+
import { CustomerGroupWithCustomFields, CustomerWithCustomFields } from './custom-fields';
|
|
3
|
+
import { AddCustomerToMyCustomerManagedGroupInput, CustomerManagedGroup, CustomerManagedGroupMember, UpdateCustomerManagedGroupMemberInput } from './generated/graphql';
|
|
4
|
+
export declare class CustomerManagedGroupsService {
|
|
5
|
+
private orderService;
|
|
6
|
+
private customerService;
|
|
7
|
+
private customerGroupService;
|
|
8
|
+
private hydrator;
|
|
9
|
+
private transactionalConnection;
|
|
10
|
+
constructor(orderService: OrderService, customerService: CustomerService, customerGroupService: CustomerGroupService, hydrator: EntityHydrator, transactionalConnection: TransactionalConnection);
|
|
11
|
+
getOrdersForCustomer(ctx: RequestContext): Promise<PaginatedList<Order>>;
|
|
12
|
+
addToGroup(ctx: RequestContext, { emailAddress: inviteeEmailAddress, isGroupAdmin: inviteeIsAdmin, }: AddCustomerToMyCustomerManagedGroupInput): Promise<CustomerManagedGroup>;
|
|
13
|
+
removeFromGroup(ctx: RequestContext, customerIdToRemove: ID): Promise<CustomerManagedGroup>;
|
|
14
|
+
getOrThrowCustomerByUserId(ctx: RequestContext, userId: ID): Promise<CustomerWithCustomFields>;
|
|
15
|
+
getOrThrowCustomerByEmail(ctx: RequestContext, emailAddress: string): Promise<Customer>;
|
|
16
|
+
/**
|
|
17
|
+
* Get userId from RequestContext or throw a ForbiddenError if not logged in
|
|
18
|
+
*/
|
|
19
|
+
getOrThrowUserId(ctx: RequestContext): ID;
|
|
20
|
+
throwIfNotAdministratorOfGroup(userId: ID, group: CustomerGroupWithCustomFields): void;
|
|
21
|
+
isAdministratorOfGroup(userId: ID, group: CustomerGroupWithCustomFields): boolean;
|
|
22
|
+
getCustomerManagedGroup(customer: CustomerWithCustomFields): CustomerGroupWithCustomFields | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Get current logged in member, or undefined if not in a group
|
|
25
|
+
*/
|
|
26
|
+
getActiveMember(ctx: RequestContext): Promise<CustomerManagedGroupMember | undefined>;
|
|
27
|
+
myCustomerManagedGroup(ctx: RequestContext): Promise<CustomerManagedGroup | undefined>;
|
|
28
|
+
myCustomerManagedGroupWithCustomFields(ctx: RequestContext): Promise<CustomerGroupWithCustomFields | undefined>;
|
|
29
|
+
createCustomerManagedGroup(ctx: RequestContext): Promise<CustomerManagedGroup>;
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @param ctx Internal function to create a customer managed group based
|
|
33
|
+
* @param groupAdmin
|
|
34
|
+
* @param additionalMembers add addtional members to the group. Group admin is already added as member
|
|
35
|
+
*/
|
|
36
|
+
private createGroup;
|
|
37
|
+
mapToCustomerManagedGroup(group: CustomerGroupWithCustomFields): CustomerManagedGroup;
|
|
38
|
+
mapToCustomerManagedGroupMember(customer: Customer, isGroupAdministrator: boolean): CustomerManagedGroupMember;
|
|
39
|
+
updateGroupMember(ctx: RequestContext, input: UpdateCustomerManagedGroupMemberInput): Promise<CustomerManagedGroup>;
|
|
40
|
+
makeAdminOfGroup(ctx: RequestContext, groupId: ID, customerId: ID): Promise<CustomerManagedGroup>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.CustomerManagedGroupsService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const core_1 = require("@vendure/core");
|
|
15
|
+
const constants_1 = require("./constants");
|
|
16
|
+
let CustomerManagedGroupsService = class CustomerManagedGroupsService {
|
|
17
|
+
constructor(orderService, customerService, customerGroupService, hydrator, transactionalConnection) {
|
|
18
|
+
this.orderService = orderService;
|
|
19
|
+
this.customerService = customerService;
|
|
20
|
+
this.customerGroupService = customerGroupService;
|
|
21
|
+
this.hydrator = hydrator;
|
|
22
|
+
this.transactionalConnection = transactionalConnection;
|
|
23
|
+
}
|
|
24
|
+
async getOrdersForCustomer(ctx) {
|
|
25
|
+
const userId = this.getOrThrowUserId(ctx);
|
|
26
|
+
const customer = await this.getOrThrowCustomerByUserId(ctx, userId);
|
|
27
|
+
const customerManagedGroup = this.getCustomerManagedGroup(customer);
|
|
28
|
+
if (!customerManagedGroup) {
|
|
29
|
+
throw new core_1.UserInputError(`You are not in a customer managed group`);
|
|
30
|
+
}
|
|
31
|
+
const isAdmin = this.isAdministratorOfGroup(userId, customerManagedGroup);
|
|
32
|
+
const orders = [];
|
|
33
|
+
// If not admin, only fetch orders for the current user
|
|
34
|
+
const customers = isAdmin ? customerManagedGroup.customers : [customer];
|
|
35
|
+
for (const customer of customers) {
|
|
36
|
+
const ordersForCustomer = await this.orderService.findByCustomerId(ctx, customer.id);
|
|
37
|
+
core_1.Logger.info(`Found ${ordersForCustomer.items.length} orders for customer ${customer.emailAddress}`, constants_1.loggerCtx);
|
|
38
|
+
orders.push(...ordersForCustomer.items);
|
|
39
|
+
if (ordersForCustomer.totalItems > ordersForCustomer.items.length) {
|
|
40
|
+
throw Error(`Too many orders for customer ${customer.emailAddress}, pagination is not implemented yet.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
items: orders,
|
|
45
|
+
totalItems: orders.length,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async addToGroup(ctx, { emailAddress: inviteeEmailAddress, isGroupAdmin: inviteeIsAdmin, }) {
|
|
49
|
+
const userId = this.getOrThrowUserId(ctx);
|
|
50
|
+
let [currentCustomer, invitee] = await Promise.all([
|
|
51
|
+
this.getOrThrowCustomerByUserId(ctx, userId),
|
|
52
|
+
this.getOrThrowCustomerByEmail(ctx, inviteeEmailAddress),
|
|
53
|
+
]);
|
|
54
|
+
let customerManagedGroup = this.getCustomerManagedGroup(currentCustomer);
|
|
55
|
+
if (customerManagedGroup) {
|
|
56
|
+
this.throwIfNotAdministratorOfGroup(userId, customerManagedGroup);
|
|
57
|
+
}
|
|
58
|
+
if (!customerManagedGroup) {
|
|
59
|
+
customerManagedGroup = await this.createGroup(ctx, currentCustomer, [
|
|
60
|
+
invitee.id,
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
const existingAdminIds = (customerManagedGroup.customFields.groupAdmins || []).map((admin) => admin.id);
|
|
64
|
+
// Add current customer as admin
|
|
65
|
+
const adminIds = [currentCustomer.id, ...existingAdminIds];
|
|
66
|
+
core_1.Logger.info(`Adding ${currentCustomer.emailAddress} as administrator of group`, constants_1.loggerCtx);
|
|
67
|
+
if (inviteeIsAdmin) {
|
|
68
|
+
// Add invitee as admin
|
|
69
|
+
adminIds.push(invitee.id);
|
|
70
|
+
core_1.Logger.info(`Adding ${invitee.emailAddress} as administrator of group`, constants_1.loggerCtx);
|
|
71
|
+
}
|
|
72
|
+
// Set group admins
|
|
73
|
+
customerManagedGroup = await this.customerGroupService.update(ctx, {
|
|
74
|
+
id: customerManagedGroup.id,
|
|
75
|
+
customFields: {
|
|
76
|
+
groupAdmins: adminIds.map((id) => ({ id })),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
await this.hydrator.hydrate(ctx, customerManagedGroup, {
|
|
80
|
+
relations: ['customers'],
|
|
81
|
+
});
|
|
82
|
+
const existingCustomersIds = customerManagedGroup.customers.map((customer) => customer.id);
|
|
83
|
+
if (!existingCustomersIds.includes(invitee.id)) {
|
|
84
|
+
// Add invitee to group
|
|
85
|
+
customerManagedGroup =
|
|
86
|
+
await this.customerGroupService.addCustomersToGroup(ctx, {
|
|
87
|
+
customerGroupId: customerManagedGroup.id,
|
|
88
|
+
customerIds: [invitee.id, ...existingCustomersIds],
|
|
89
|
+
});
|
|
90
|
+
core_1.Logger.info(`Added ${invitee.emailAddress} as participants of group`, constants_1.loggerCtx);
|
|
91
|
+
}
|
|
92
|
+
// Refetch customer and group
|
|
93
|
+
currentCustomer = await this.getOrThrowCustomerByUserId(ctx, userId);
|
|
94
|
+
customerManagedGroup = this.getCustomerManagedGroup(currentCustomer);
|
|
95
|
+
return this.mapToCustomerManagedGroup(customerManagedGroup);
|
|
96
|
+
}
|
|
97
|
+
async removeFromGroup(ctx, customerIdToRemove) {
|
|
98
|
+
const userId = this.getOrThrowUserId(ctx);
|
|
99
|
+
let customer = await this.getOrThrowCustomerByUserId(ctx, userId);
|
|
100
|
+
let customerManagedGroup = this.getCustomerManagedGroup(customer);
|
|
101
|
+
if (!customerManagedGroup) {
|
|
102
|
+
throw new core_1.UserInputError(`You are not in a customer managed group`);
|
|
103
|
+
}
|
|
104
|
+
this.throwIfNotAdministratorOfGroup(userId, customerManagedGroup);
|
|
105
|
+
const customerToRemove = customerManagedGroup.customers.find((c) => c.id == customerIdToRemove);
|
|
106
|
+
if (!customerToRemove) {
|
|
107
|
+
throw new core_1.UserInputError(`Customer '${customerIdToRemove}' is not in your group`);
|
|
108
|
+
}
|
|
109
|
+
if (customer.id === customerIdToRemove) {
|
|
110
|
+
throw new core_1.UserInputError(`You cannot remove yourself from your group`);
|
|
111
|
+
}
|
|
112
|
+
customerManagedGroup =
|
|
113
|
+
await this.customerGroupService.removeCustomersFromGroup(ctx, {
|
|
114
|
+
customerGroupId: customerManagedGroup.id,
|
|
115
|
+
customerIds: [customerIdToRemove],
|
|
116
|
+
});
|
|
117
|
+
core_1.Logger.info(`Removed customer ${customerToRemove.emailAddress} from group`, constants_1.loggerCtx);
|
|
118
|
+
const existingAdminIds = customerManagedGroup.customFields.groupAdmins?.map((a) => a.id) || [];
|
|
119
|
+
if (existingAdminIds.includes(customerToRemove.id)) {
|
|
120
|
+
const newAdminIds = existingAdminIds.filter((id) => id != customerToRemove.id);
|
|
121
|
+
customerManagedGroup = await this.customerGroupService.update(ctx, {
|
|
122
|
+
id: customerManagedGroup.id,
|
|
123
|
+
customFields: {
|
|
124
|
+
groupAdmins: newAdminIds.map((id) => ({ id })),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
core_1.Logger.info(`Removed ${customerToRemove.emailAddress} as group administrator`, constants_1.loggerCtx);
|
|
128
|
+
}
|
|
129
|
+
// Refetch customer and group
|
|
130
|
+
customer = await this.getOrThrowCustomerByUserId(ctx, userId);
|
|
131
|
+
customerManagedGroup = this.getCustomerManagedGroup(customer);
|
|
132
|
+
return this.mapToCustomerManagedGroup(customerManagedGroup);
|
|
133
|
+
}
|
|
134
|
+
async getOrThrowCustomerByUserId(ctx, userId) {
|
|
135
|
+
const customerRepo = this.transactionalConnection.getRepository(ctx, core_1.Customer);
|
|
136
|
+
const customerWithGroupsData = await customerRepo
|
|
137
|
+
.createQueryBuilder('customer')
|
|
138
|
+
.leftJoin('customer.channels', 'customer_channel')
|
|
139
|
+
.leftJoin('customer.user', 'user')
|
|
140
|
+
.leftJoinAndSelect('customer.addresses', 'customerAddress')
|
|
141
|
+
.leftJoinAndSelect('customerAddress.country', 'customerCountry')
|
|
142
|
+
.leftJoinAndSelect('customer.groups', 'groups')
|
|
143
|
+
.leftJoinAndSelect('groups.customers', 'customers')
|
|
144
|
+
.leftJoinAndSelect('groups.customFields.groupAdmins', 'groupAdmins')
|
|
145
|
+
.leftJoinAndSelect('groupAdmins.user', 'groupAdminsUser')
|
|
146
|
+
.leftJoinAndSelect('groupAdmins.addresses', 'groupAdminAddresses')
|
|
147
|
+
.leftJoinAndSelect('groupAdminAddresses.country', 'groupAdminCountries')
|
|
148
|
+
.leftJoinAndSelect('customers.addresses', 'addresses')
|
|
149
|
+
.leftJoinAndSelect('addresses.country', 'country')
|
|
150
|
+
.where('user.id = :userId', { userId: userId })
|
|
151
|
+
.andWhere('customer_channel.id = :customerChannelId', {
|
|
152
|
+
customerChannelId: ctx.channelId,
|
|
153
|
+
})
|
|
154
|
+
.getOne();
|
|
155
|
+
if (!customerWithGroupsData) {
|
|
156
|
+
throw new core_1.UserInputError(`No customer found for user with id ${userId}`);
|
|
157
|
+
}
|
|
158
|
+
return customerWithGroupsData;
|
|
159
|
+
}
|
|
160
|
+
async getOrThrowCustomerByEmail(ctx, emailAddress) {
|
|
161
|
+
const customers = await this.customerService.findAll(ctx, {
|
|
162
|
+
filter: {
|
|
163
|
+
emailAddress: {
|
|
164
|
+
eq: emailAddress,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
if (!customers.items[0]) {
|
|
169
|
+
throw new core_1.UserInputError(`No customer found for email adress ${emailAddress}`);
|
|
170
|
+
}
|
|
171
|
+
return customers.items[0];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get userId from RequestContext or throw a ForbiddenError if not logged in
|
|
175
|
+
*/
|
|
176
|
+
getOrThrowUserId(ctx) {
|
|
177
|
+
if (!ctx.activeUserId) {
|
|
178
|
+
throw new core_1.ForbiddenError();
|
|
179
|
+
}
|
|
180
|
+
return ctx.activeUserId;
|
|
181
|
+
}
|
|
182
|
+
throwIfNotAdministratorOfGroup(userId, group) {
|
|
183
|
+
if (!this.isAdministratorOfGroup(userId, group)) {
|
|
184
|
+
throw new core_1.UserInputError('You are not administrator of your group');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
isAdministratorOfGroup(userId, group) {
|
|
188
|
+
return !!group.customFields.groupAdmins?.find((admin) => admin.user.id == userId);
|
|
189
|
+
}
|
|
190
|
+
getCustomerManagedGroup(customer) {
|
|
191
|
+
if (!customer.groups) {
|
|
192
|
+
throw Error(`Make sure to include groups in the customer query. Can not find customer managed group for customer ${customer.emailAddress}`);
|
|
193
|
+
}
|
|
194
|
+
return customer.groups.find((group) => group.customFields.isCustomerManaged);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get current logged in member, or undefined if not in a group
|
|
198
|
+
*/
|
|
199
|
+
async getActiveMember(ctx) {
|
|
200
|
+
const userId = this.getOrThrowUserId(ctx);
|
|
201
|
+
const customer = await this.getOrThrowCustomerByUserId(ctx, userId);
|
|
202
|
+
const customerManagedGroup = this.getCustomerManagedGroup(customer);
|
|
203
|
+
if (!customerManagedGroup) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const isAdministrator = this.isAdministratorOfGroup(userId, customerManagedGroup);
|
|
207
|
+
return this.mapToCustomerManagedGroupMember(customer, isAdministrator);
|
|
208
|
+
}
|
|
209
|
+
async myCustomerManagedGroup(ctx) {
|
|
210
|
+
let customerManagedGroup = await this.myCustomerManagedGroupWithCustomFields(ctx);
|
|
211
|
+
if (!customerManagedGroup) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
return this.mapToCustomerManagedGroup(customerManagedGroup);
|
|
215
|
+
}
|
|
216
|
+
async myCustomerManagedGroupWithCustomFields(ctx) {
|
|
217
|
+
const userId = this.getOrThrowUserId(ctx);
|
|
218
|
+
const customer = await this.getOrThrowCustomerByUserId(ctx, userId);
|
|
219
|
+
let customerManagedGroup = this.getCustomerManagedGroup(customer);
|
|
220
|
+
if (!customerManagedGroup) {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
return customerManagedGroup;
|
|
224
|
+
}
|
|
225
|
+
async createCustomerManagedGroup(ctx) {
|
|
226
|
+
const userId = this.getOrThrowUserId(ctx);
|
|
227
|
+
const currentCustomer = await this.getOrThrowCustomerByUserId(ctx, userId);
|
|
228
|
+
let customerManagedGroup = this.getCustomerManagedGroup(currentCustomer);
|
|
229
|
+
if (customerManagedGroup) {
|
|
230
|
+
throw new core_1.UserInputError(`You are already in a customer managed group`);
|
|
231
|
+
}
|
|
232
|
+
customerManagedGroup = await this.createGroup(ctx, currentCustomer);
|
|
233
|
+
await this.hydrator.hydrate(ctx, customerManagedGroup, {
|
|
234
|
+
relations: ['customers'],
|
|
235
|
+
});
|
|
236
|
+
return this.mapToCustomerManagedGroup(customerManagedGroup);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
*
|
|
240
|
+
* @param ctx Internal function to create a customer managed group based
|
|
241
|
+
* @param groupAdmin
|
|
242
|
+
* @param additionalMembers add addtional members to the group. Group admin is already added as member
|
|
243
|
+
*/
|
|
244
|
+
createGroup(ctx, groupAdmin, additionalMembers) {
|
|
245
|
+
const members = [groupAdmin.id];
|
|
246
|
+
if (additionalMembers) {
|
|
247
|
+
members.push(...additionalMembers);
|
|
248
|
+
}
|
|
249
|
+
return this.customerGroupService.create(ctx, {
|
|
250
|
+
name: `${groupAdmin.lastName}'s Group`,
|
|
251
|
+
customerIds: members,
|
|
252
|
+
customFields: {
|
|
253
|
+
isCustomerManaged: true,
|
|
254
|
+
groupAdmins: [groupAdmin],
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
mapToCustomerManagedGroup(group) {
|
|
259
|
+
const adminIds = group.customFields.groupAdmins?.map((admin) => admin.id) || [];
|
|
260
|
+
// Filter out group administrators
|
|
261
|
+
const participants = group.customers
|
|
262
|
+
.filter((customer) => !adminIds.includes(customer.id))
|
|
263
|
+
.map((c) => this.mapToCustomerManagedGroupMember(c, false));
|
|
264
|
+
const administrators = (group.customFields.groupAdmins || []).map((a) => this.mapToCustomerManagedGroupMember(a, true));
|
|
265
|
+
return {
|
|
266
|
+
...group,
|
|
267
|
+
customers: [...administrators, ...participants],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
mapToCustomerManagedGroupMember(customer, isGroupAdministrator) {
|
|
271
|
+
return {
|
|
272
|
+
...customer,
|
|
273
|
+
addresses: customer.addresses,
|
|
274
|
+
customFields: customer.customFields,
|
|
275
|
+
customerId: customer.id,
|
|
276
|
+
isGroupAdministrator,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
async updateGroupMember(ctx, input) {
|
|
280
|
+
if (!input.title &&
|
|
281
|
+
!input.firstName &&
|
|
282
|
+
!input.lastName &&
|
|
283
|
+
!input.emailAddress &&
|
|
284
|
+
!input.addresses?.length &&
|
|
285
|
+
!input.customFields) {
|
|
286
|
+
throw new core_1.UserInputError(`Make sure to include fields to be updated`);
|
|
287
|
+
}
|
|
288
|
+
if (!ctx.activeUserId) {
|
|
289
|
+
throw new core_1.ForbiddenError();
|
|
290
|
+
}
|
|
291
|
+
const myGroup = await this.myCustomerManagedGroupWithCustomFields(ctx);
|
|
292
|
+
if (!myGroup) {
|
|
293
|
+
throw new core_1.UserInputError(`No customer managed group exists for the authenticated customer`);
|
|
294
|
+
}
|
|
295
|
+
const member = myGroup.customers.find((customer) => customer.id === input.customerId);
|
|
296
|
+
if (!member) {
|
|
297
|
+
throw new core_1.UserInputError(`No customer with id ${input.customerId} exists in '${myGroup.name}' customer managed group`);
|
|
298
|
+
}
|
|
299
|
+
const customer = await this.customerService.findOne(ctx, member.id, [
|
|
300
|
+
'user',
|
|
301
|
+
]);
|
|
302
|
+
if (!customer || !customer.user) {
|
|
303
|
+
throw new core_1.UserInputError(`No customer with id ${input.customerId} exists`);
|
|
304
|
+
}
|
|
305
|
+
if (!this.isAdministratorOfGroup(ctx.activeUserId, myGroup) &&
|
|
306
|
+
customer.user.id !== ctx.activeUserId) {
|
|
307
|
+
throw new core_1.UserInputError(`You are not allowed to update other member's details`);
|
|
308
|
+
}
|
|
309
|
+
const userRepo = this.transactionalConnection.getRepository(ctx, core_1.User);
|
|
310
|
+
if (input.emailAddress &&
|
|
311
|
+
(await userRepo.count({
|
|
312
|
+
where: { identifier: input.emailAddress },
|
|
313
|
+
}))) {
|
|
314
|
+
throw new core_1.UserInputError('User with this email already exists');
|
|
315
|
+
}
|
|
316
|
+
const updateCustomerData = {
|
|
317
|
+
id: customer.id,
|
|
318
|
+
...(input.title ? { title: input.title } : []),
|
|
319
|
+
...(input.firstName ? { firstName: input.firstName } : []),
|
|
320
|
+
...(input.lastName ? { lastName: input.lastName } : []),
|
|
321
|
+
...(input.emailAddress ? { emailAddress: input.emailAddress } : []),
|
|
322
|
+
...(input.customFields ? { customFields: input.customFields } : []),
|
|
323
|
+
};
|
|
324
|
+
await this.customerService.update(ctx, updateCustomerData);
|
|
325
|
+
if (input.addresses?.length) {
|
|
326
|
+
for (let addressInput of input.addresses) {
|
|
327
|
+
if (addressInput?.id) {
|
|
328
|
+
await this.customerService.updateAddress(ctx, addressInput);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
await this.customerService.createAddress(ctx, customer.id, addressInput);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const newGroupData = await this.myCustomerManagedGroup(ctx);
|
|
336
|
+
if (!newGroupData) {
|
|
337
|
+
throw Error(`Group with id ${myGroup.id} not found`); // Should never happen
|
|
338
|
+
}
|
|
339
|
+
return newGroupData;
|
|
340
|
+
}
|
|
341
|
+
async makeAdminOfGroup(ctx, groupId, customerId) {
|
|
342
|
+
const customerGroup = await this.customerGroupService.findOne(ctx, groupId, ['customers', 'customers.user']);
|
|
343
|
+
if (!customerGroup ||
|
|
344
|
+
!customerGroup.customFields ||
|
|
345
|
+
!customerGroup.customFields.isCustomerManaged) {
|
|
346
|
+
throw new core_1.UserInputError(`No customer managed group with id ${groupId} exists`);
|
|
347
|
+
}
|
|
348
|
+
if (!customerGroup.customFields.groupAdmins.find((admin) => admin.user?.id === ctx.activeUserId)) {
|
|
349
|
+
throw new core_1.UserInputError(`You are not admin of this customer managed group`);
|
|
350
|
+
}
|
|
351
|
+
const customerInQuestion = await customerGroup.customers.find((c) => c.id === customerId);
|
|
352
|
+
if (!customerInQuestion) {
|
|
353
|
+
throw new core_1.UserInputError(`Customer with id ${customerId} is not part of this customer managed group`);
|
|
354
|
+
}
|
|
355
|
+
if (customerGroup.customFields.groupAdmins.find((admin) => admin.id === customerId)) {
|
|
356
|
+
throw new core_1.UserInputError('Customer is already admin of this customer managed group');
|
|
357
|
+
}
|
|
358
|
+
const customerGroupRepo = this.transactionalConnection.getRepository(ctx, core_1.CustomerGroup);
|
|
359
|
+
const partialValue = {
|
|
360
|
+
id: customerGroup.id,
|
|
361
|
+
customFields: {
|
|
362
|
+
groupAdmins: [
|
|
363
|
+
...customerGroup.customFields.groupAdmins,
|
|
364
|
+
customerInQuestion,
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
await customerGroupRepo.save(partialValue);
|
|
369
|
+
return this.mapToCustomerManagedGroup((await this.customerGroupService.findOne(ctx, groupId, ['customers'])));
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
CustomerManagedGroupsService = __decorate([
|
|
373
|
+
(0, common_1.Injectable)(),
|
|
374
|
+
__metadata("design:paramtypes", [core_1.OrderService,
|
|
375
|
+
core_1.CustomerService,
|
|
376
|
+
core_1.CustomerGroupService,
|
|
377
|
+
core_1.EntityHydrator,
|
|
378
|
+
core_1.TransactionalConnection])
|
|
379
|
+
], CustomerManagedGroupsService);
|
|
380
|
+
exports.CustomerManagedGroupsService = CustomerManagedGroupsService;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export type Maybe<T> = T | null;
|
|
2
|
+
export type InputMaybe<T> = Maybe<T>;
|
|
3
|
+
export type Exact<T extends {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}> = {
|
|
6
|
+
[K in keyof T]: T[K];
|
|
7
|
+
};
|
|
8
|
+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & {
|
|
9
|
+
[SubKey in K]?: Maybe<T[SubKey]>;
|
|
10
|
+
};
|
|
11
|
+
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & {
|
|
12
|
+
[SubKey in K]: Maybe<T[SubKey]>;
|
|
13
|
+
};
|
|
14
|
+
/** All built-in and custom scalars, mapped to their actual values */
|
|
15
|
+
export type Scalars = {
|
|
16
|
+
ID: number | string;
|
|
17
|
+
String: string;
|
|
18
|
+
Boolean: boolean;
|
|
19
|
+
Int: number;
|
|
20
|
+
Float: number;
|
|
21
|
+
DateTime: any;
|
|
22
|
+
JSON: any;
|
|
23
|
+
LanguageCode: any;
|
|
24
|
+
OrderList: any;
|
|
25
|
+
};
|
|
26
|
+
export type AddCustomerToMyCustomerManagedGroupInput = {
|
|
27
|
+
emailAddress: Scalars['String'];
|
|
28
|
+
isGroupAdmin?: InputMaybe<Scalars['Boolean']>;
|
|
29
|
+
};
|
|
30
|
+
export type CustomerManagedGroup = {
|
|
31
|
+
__typename?: 'CustomerManagedGroup';
|
|
32
|
+
createdAt: Scalars['DateTime'];
|
|
33
|
+
customers: Array<CustomerManagedGroupMember>;
|
|
34
|
+
id: Scalars['ID'];
|
|
35
|
+
name: Scalars['String'];
|
|
36
|
+
updatedAt: Scalars['DateTime'];
|
|
37
|
+
};
|
|
38
|
+
export type CustomerManagedGroupAddress = {
|
|
39
|
+
__typename?: 'CustomerManagedGroupAddress';
|
|
40
|
+
city?: Maybe<Scalars['String']>;
|
|
41
|
+
company?: Maybe<Scalars['String']>;
|
|
42
|
+
country: CustomerManagedGroupCountry;
|
|
43
|
+
createdAt: Scalars['DateTime'];
|
|
44
|
+
defaultBillingAddress?: Maybe<Scalars['Boolean']>;
|
|
45
|
+
defaultShippingAddress?: Maybe<Scalars['Boolean']>;
|
|
46
|
+
fullName?: Maybe<Scalars['String']>;
|
|
47
|
+
id: Scalars['ID'];
|
|
48
|
+
phoneNumber?: Maybe<Scalars['String']>;
|
|
49
|
+
postalCode?: Maybe<Scalars['String']>;
|
|
50
|
+
province?: Maybe<Scalars['String']>;
|
|
51
|
+
streetLine1: Scalars['String'];
|
|
52
|
+
streetLine2?: Maybe<Scalars['String']>;
|
|
53
|
+
updatedAt: Scalars['DateTime'];
|
|
54
|
+
};
|
|
55
|
+
/** When no ID is given, a new address will be created */
|
|
56
|
+
export type CustomerManagedGroupAddressInput = {
|
|
57
|
+
city?: InputMaybe<Scalars['String']>;
|
|
58
|
+
company?: InputMaybe<Scalars['String']>;
|
|
59
|
+
countryCode?: InputMaybe<Scalars['String']>;
|
|
60
|
+
defaultBillingAddress?: InputMaybe<Scalars['Boolean']>;
|
|
61
|
+
defaultShippingAddress?: InputMaybe<Scalars['Boolean']>;
|
|
62
|
+
fullName?: InputMaybe<Scalars['String']>;
|
|
63
|
+
id?: InputMaybe<Scalars['ID']>;
|
|
64
|
+
phoneNumber?: InputMaybe<Scalars['String']>;
|
|
65
|
+
postalCode?: InputMaybe<Scalars['String']>;
|
|
66
|
+
province?: InputMaybe<Scalars['String']>;
|
|
67
|
+
streetLine1?: InputMaybe<Scalars['String']>;
|
|
68
|
+
streetLine2?: InputMaybe<Scalars['String']>;
|
|
69
|
+
};
|
|
70
|
+
export type CustomerManagedGroupCountry = {
|
|
71
|
+
__typename?: 'CustomerManagedGroupCountry';
|
|
72
|
+
code: Scalars['String'];
|
|
73
|
+
createdAt: Scalars['DateTime'];
|
|
74
|
+
enabled: Scalars['Boolean'];
|
|
75
|
+
id: Scalars['ID'];
|
|
76
|
+
name: Scalars['String'];
|
|
77
|
+
translations: Array<CustomerManagedGroupCountryTranslation>;
|
|
78
|
+
updatedAt: Scalars['DateTime'];
|
|
79
|
+
};
|
|
80
|
+
export type CustomerManagedGroupCountryTranslation = {
|
|
81
|
+
__typename?: 'CustomerManagedGroupCountryTranslation';
|
|
82
|
+
id: Scalars['ID'];
|
|
83
|
+
languageCode: Scalars['LanguageCode'];
|
|
84
|
+
name: Scalars['String'];
|
|
85
|
+
};
|
|
86
|
+
export type CustomerManagedGroupMember = {
|
|
87
|
+
__typename?: 'CustomerManagedGroupMember';
|
|
88
|
+
addresses?: Maybe<Array<CustomerManagedGroupAddress>>;
|
|
89
|
+
customFields?: Maybe<Scalars['JSON']>;
|
|
90
|
+
customerId: Scalars['ID'];
|
|
91
|
+
emailAddress: Scalars['String'];
|
|
92
|
+
firstName: Scalars['String'];
|
|
93
|
+
isGroupAdministrator: Scalars['Boolean'];
|
|
94
|
+
lastName: Scalars['String'];
|
|
95
|
+
title?: Maybe<Scalars['String']>;
|
|
96
|
+
};
|
|
97
|
+
export type Mutation = {
|
|
98
|
+
__typename?: 'Mutation';
|
|
99
|
+
/** Creates a group with the current logged in user as administrator of the group */
|
|
100
|
+
addCustomerToMyCustomerManagedGroup: CustomerManagedGroup;
|
|
101
|
+
/** Create an empty group with the current user as Administrator */
|
|
102
|
+
createCustomerManagedGroup: CustomerManagedGroup;
|
|
103
|
+
makeCustomerAdminOfCustomerManagedGroup: CustomerManagedGroup;
|
|
104
|
+
removeCustomerFromMyCustomerManagedGroup: CustomerManagedGroup;
|
|
105
|
+
/** Update a member of one's group */
|
|
106
|
+
updateCustomerManagedGroupMember: CustomerManagedGroup;
|
|
107
|
+
};
|
|
108
|
+
export type MutationAddCustomerToMyCustomerManagedGroupArgs = {
|
|
109
|
+
input?: InputMaybe<AddCustomerToMyCustomerManagedGroupInput>;
|
|
110
|
+
};
|
|
111
|
+
export type MutationMakeCustomerAdminOfCustomerManagedGroupArgs = {
|
|
112
|
+
customerId: Scalars['ID'];
|
|
113
|
+
groupId: Scalars['ID'];
|
|
114
|
+
};
|
|
115
|
+
export type MutationRemoveCustomerFromMyCustomerManagedGroupArgs = {
|
|
116
|
+
customerId: Scalars['ID'];
|
|
117
|
+
};
|
|
118
|
+
export type MutationUpdateCustomerManagedGroupMemberArgs = {
|
|
119
|
+
input: UpdateCustomerManagedGroupMemberInput;
|
|
120
|
+
};
|
|
121
|
+
export type Query = {
|
|
122
|
+
__typename?: 'Query';
|
|
123
|
+
/** Fetch the current logged in group member */
|
|
124
|
+
activeCustomerManagedGroupMember?: Maybe<CustomerManagedGroupMember>;
|
|
125
|
+
myCustomerManagedGroup?: Maybe<CustomerManagedGroup>;
|
|
126
|
+
/** Fetch placed orders for each member of the group */
|
|
127
|
+
ordersForMyCustomerManagedGroup: Scalars['OrderList'];
|
|
128
|
+
};
|
|
129
|
+
export type UpdateCustomerManagedGroupMemberInput = {
|
|
130
|
+
addresses?: InputMaybe<Array<CustomerManagedGroupAddressInput>>;
|
|
131
|
+
customFields?: InputMaybe<Scalars['JSON']>;
|
|
132
|
+
customerId: Scalars['ID'];
|
|
133
|
+
emailAddress?: InputMaybe<Scalars['String']>;
|
|
134
|
+
firstName?: InputMaybe<Scalars['String']>;
|
|
135
|
+
lastName?: InputMaybe<Scalars['String']>;
|
|
136
|
+
title?: InputMaybe<Scalars['String']>;
|
|
137
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./customer-managed-groups.plugin"), exports);
|
|
18
|
+
__exportStar(require("./customer-managed-groups.service"), exports);
|
|
19
|
+
__exportStar(require("./api-extensions"), exports);
|
|
20
|
+
__exportStar(require("./custom-fields"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pinelab/vendure-plugin-customer-managed-groups",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "This plugin allows customer groups to have 'Group admins', that are allowed to fetch placed orders for everyone in the group.",
|
|
5
|
+
"author": "Martijn van de Brug <martijn@pinelab.studio>",
|
|
6
|
+
"homepage": "https://pinelab-plugins.com/",
|
|
7
|
+
"repository": "https://github.com/Pinelab-studio/pinelab-vendure-plugins",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"private": false,
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=16.0.0"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "yarn ts-node test/dev-server.ts",
|
|
24
|
+
"build": "rimraf dist && yarn graphql-codegen && tsc",
|
|
25
|
+
"test": "vitest run"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@graphql-codegen/cli": "^2.4.0",
|
|
29
|
+
"@graphql-codegen/typescript": "^2.4.8",
|
|
30
|
+
"@graphql-codegen/typescript-operations": "^2.3.5",
|
|
31
|
+
"@swc/core": "^1.3.59",
|
|
32
|
+
"@vendure/admin-ui-plugin": "2.0.4",
|
|
33
|
+
"@vendure/asset-server-plugin": "2.0.4",
|
|
34
|
+
"@vendure/core": "2.0.4",
|
|
35
|
+
"@vendure/testing": "2.0.4",
|
|
36
|
+
"@vendure/ui-devkit": "2.0.4",
|
|
37
|
+
"rimraf": "^4.1.2",
|
|
38
|
+
"ts-node": "10.9.1",
|
|
39
|
+
"typescript": "5.0.4",
|
|
40
|
+
"unplugin-swc": "^1.3.2",
|
|
41
|
+
"vitest": "^0.32.0"
|
|
42
|
+
},
|
|
43
|
+
"gitHead": "5673faf91c029dc4716226965ce3531931b434b6"
|
|
44
|
+
}
|