mv.service.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { Injectable } from '@nestjs/common';
  2. import { CreateAdministratorInput, Permission } from '@vendure/common/lib/generated-types';
  3. import { normalizeString } from '@vendure/common/lib/normalize-string';
  4. import {
  5. AdministratorService,
  6. Channel,
  7. ChannelService,
  8. ConfigService,
  9. defaultShippingCalculator,
  10. InternalServerError,
  11. isGraphQlErrorResult,
  12. manualFulfillmentHandler,
  13. RequestContext,
  14. RequestContextService,
  15. RoleService,
  16. SellerService,
  17. ShippingMethod,
  18. ShippingMethodService,
  19. StockLocation,
  20. StockLocationService,
  21. TaxSetting,
  22. TransactionalConnection,
  23. User,
  24. } from '@vendure/core';
  25. import { multivendorShippingEligibilityChecker } from '../config/mv-shipping-eligibility-checker';
  26. import { CreateSellerInput } from '../types';
  27. @Injectable()
  28. export class MultivendorService {
  29. constructor(
  30. private administratorService: AdministratorService,
  31. private sellerService: SellerService,
  32. private roleService: RoleService,
  33. private channelService: ChannelService,
  34. private shippingMethodService: ShippingMethodService,
  35. private configService: ConfigService,
  36. private stockLocationService: StockLocationService,
  37. private requestContextService: RequestContextService,
  38. private connection: TransactionalConnection,
  39. ) {}
  40. async registerNewSeller(ctx: RequestContext, input: { shopName: string; seller: CreateSellerInput }) {
  41. const superAdminCtx = await this.getSuperAdminContext(ctx);
  42. const channel = await this.createSellerChannelRoleAdmin(superAdminCtx, input);
  43. await this.createSellerShippingMethod(superAdminCtx, input.shopName, channel);
  44. await this.createSellerStockLocation(superAdminCtx, input.shopName, channel);
  45. return channel;
  46. }
  47. private async createSellerShippingMethod(ctx: RequestContext, shopName: string, sellerChannel: Channel) {
  48. const defaultChannel = await this.channelService.getDefaultChannel(ctx);
  49. const { shippingEligibilityCheckers, shippingCalculators, fulfillmentHandlers } =
  50. this.configService.shippingOptions;
  51. const shopCode = normalizeString(shopName, '-');
  52. const checker = shippingEligibilityCheckers.find(
  53. c => c.code === multivendorShippingEligibilityChecker.code,
  54. );
  55. const calculator = shippingCalculators.find(c => c.code === defaultShippingCalculator.code);
  56. const fulfillmentHandler = fulfillmentHandlers.find(h => h.code === manualFulfillmentHandler.code);
  57. if (!checker) {
  58. throw new InternalServerError(
  59. 'Could not find a suitable ShippingEligibilityChecker for the seller',
  60. );
  61. }
  62. if (!calculator) {
  63. throw new InternalServerError('Could not find a suitable ShippingCalculator for the seller');
  64. }
  65. if (!fulfillmentHandler) {
  66. throw new InternalServerError('Could not find a suitable FulfillmentHandler for the seller');
  67. }
  68. const shippingMethod = await this.shippingMethodService.create(ctx, {
  69. code: `${shopCode}-shipping`,
  70. checker: {
  71. code: checker.code,
  72. arguments: [],
  73. },
  74. calculator: {
  75. code: calculator.code,
  76. arguments: [
  77. { name: 'rate', value: '500' },
  78. { name: 'includesTax', value: TaxSetting.auto },
  79. { name: 'taxRate', value: '20' },
  80. ],
  81. },
  82. fulfillmentHandler: fulfillmentHandler.code,
  83. translations: [
  84. {
  85. languageCode: defaultChannel.defaultLanguageCode,
  86. name: `Standard Shipping for ${shopName}`,
  87. },
  88. ],
  89. });
  90. await this.channelService.assignToChannels(ctx, ShippingMethod, shippingMethod.id, [
  91. sellerChannel.id,
  92. ]);
  93. }
  94. private async createSellerStockLocation(ctx: RequestContext, shopName: string, sellerChannel: Channel) {
  95. const stockLocation = await this.stockLocationService.create(ctx, {
  96. name: `${shopName} Warehouse`,
  97. });
  98. await this.channelService.assignToChannels(ctx, StockLocation, stockLocation.id, [sellerChannel.id]);
  99. }
  100. private async createSellerChannelRoleAdmin(
  101. ctx: RequestContext,
  102. input: { shopName: string; seller: CreateSellerInput },
  103. ) {
  104. const defaultChannel = await this.channelService.getDefaultChannel(ctx);
  105. const shopCode = normalizeString(input.shopName, '-');
  106. const seller = await this.sellerService.create(ctx, {
  107. name: input.shopName,
  108. customFields: {
  109. // This simulates a connection to a payment provider,
  110. // which would supply the connected account ID.
  111. // In this case we just use a pseudo-random string
  112. connectedAccountId: Math.random().toString(30).substring(3),
  113. },
  114. });
  115. const channel = await this.channelService.create(ctx, {
  116. code: shopCode,
  117. sellerId: seller.id,
  118. token: `${shopCode}-token`,
  119. currencyCode: defaultChannel.defaultCurrencyCode,
  120. defaultLanguageCode: defaultChannel.defaultLanguageCode,
  121. pricesIncludeTax: defaultChannel.pricesIncludeTax,
  122. defaultShippingZoneId: defaultChannel.defaultShippingZone.id,
  123. defaultTaxZoneId: defaultChannel.defaultTaxZone.id,
  124. });
  125. if (isGraphQlErrorResult(channel)) {
  126. throw new InternalServerError(channel.message);
  127. }
  128. const superAdminRole = await this.roleService.getSuperAdminRole(ctx);
  129. const customerRole = await this.roleService.getCustomerRole(ctx);
  130. await this.roleService.assignRoleToChannel(ctx, superAdminRole.id, channel.id);
  131. const role = await this.roleService.create(ctx, {
  132. code: `${shopCode}-admin`,
  133. channelIds: [channel.id],
  134. description: `Administrator of ${input.shopName}`,
  135. permissions: [
  136. Permission.CreateCatalog,
  137. Permission.UpdateCatalog,
  138. Permission.ReadCatalog,
  139. Permission.DeleteCatalog,
  140. Permission.CreateOrder,
  141. Permission.ReadOrder,
  142. Permission.UpdateOrder,
  143. Permission.DeleteOrder,
  144. Permission.ReadCustomer,
  145. Permission.ReadPaymentMethod,
  146. Permission.ReadShippingMethod,
  147. Permission.ReadPromotion,
  148. Permission.ReadCountry,
  149. Permission.ReadZone,
  150. Permission.CreateCustomer,
  151. Permission.UpdateCustomer,
  152. Permission.DeleteCustomer,
  153. Permission.CreateTag,
  154. Permission.ReadTag,
  155. Permission.UpdateTag,
  156. Permission.DeleteTag,
  157. ],
  158. });
  159. const administrator = await this.administratorService.create(ctx, {
  160. firstName: input.seller.firstName,
  161. lastName: input.seller.lastName,
  162. emailAddress: input.seller.emailAddress,
  163. password: input.seller.password,
  164. roleIds: [role.id],
  165. });
  166. return channel;
  167. }
  168. private async getSuperAdminContext(ctx: RequestContext): Promise<RequestContext> {
  169. const { superadminCredentials } = this.configService.authOptions;
  170. const superAdminUser = await this.connection.getRepository(ctx, User).findOne({
  171. where: {
  172. identifier: superadminCredentials.identifier,
  173. },
  174. });
  175. return this.requestContextService.create({
  176. apiType: 'shop',
  177. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  178. user: superAdminUser!,
  179. });
  180. }
  181. }