diff --git a/CHANGELOG.md b/CHANGELOG.md index b0401d3..a56004f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Fixed + +- Only apply one regionalization behaviour at a time. + +### Added + +- Setting to control what regionalization type the store has. + ## [1.33.1] - 2023-04-21 ### Fixed @@ -26,15 +34,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added the x-b2b-senderapp header to fix problems with the new B2B API - ## [1.31.5] - 2023-04-11 ### Fixed -- Fixed clear call async calls - +- Fixed clear call async calls ### Removed + - [ENGINEERS-1247] - Disable cypress tests in PR level ### Changed @@ -48,7 +55,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed the `setProfile` to clear the cart properly - Improved calls on set profile in order to get faster response - ## [1.31.3] - 2023-03-01 ### Fixed @@ -58,6 +64,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [1.31.2] - 2023-02-27 ### Fixed + - `setProfile` adding sku 1 to the cart to set sales channel when the cart is empty - `setProfile` losing item attachments after login @@ -73,7 +80,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added a feature when the user logs in or changes the current organization. - ## [1.30.0] - 2023-02-09 ### Added @@ -137,7 +143,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - getOrganizationsByEmail is returning all users instead of only first 50 records - ## [1.29.2] - 2022-11-28 ### Fixed @@ -156,7 +161,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [1.29.0] - 2022-11-08 - ### Added - Added the functionality to the storefront permissions to change the sales channel according to the Organization @@ -173,7 +177,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Force the business document and state registration on setProfile method - ## [1.27.0] - 2022-10-19 ### Changed @@ -210,7 +213,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - - Changed the validation of the add user +- Changed the validation of the add user ### Added diff --git a/docs/README.md b/docs/README.md index 8ffb2f4..33ce3c5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,25 +10,23 @@ When navigating a B2B store as a customer, it is common for a main user from an organization to have other people under their structure, each with their own information and access privileges. -Within an organization, each user can have different roles, such as a professional buyer that places orders with budget limits from a predefined cost center or a manager in charge of reviewing and approving orders. These roles can be associated with multiple permissions, depending on the actions this user needs to perform. +Within an organization, each user can have different roles, such as a professional buyer that places orders with budget limits from a predefined cost center or a manager in charge of reviewing and approving orders. These roles can be associated with multiple permissions, depending on the actions this user needs to perform. The **Storefront Permissions** app stores a predefined set of roles and app permissions related to what B2B users can access and do in your storefront, making this information available for other integrated apps to check. This is useful for stores who want to set specific app permissions for users with different roles in an organization. - ## Available storefront roles In the following table, you can see the available storefront roles, their key used for identification in the app’s code, and their description. -| **Role** | **Key** | **Description** | -|---|---|---| -| Store Admin | `store-admin` | Store administrator, that is, a user who has access to the VTEX Admin. | -| Sales Admin | `sales-admin` | Sales administrator user who can manage all sales users. | -| Sales Manager | `sales-manager` | Sales manager user who can manage sales users in the same organization, as well as assist or impersonate buyers during navigation or purchase. | -| Sales Representative | `sales-representative` | Sales representative user who can assist or impersonate buyers in the same cost center during navigation or purchase. | -| Organization Admin | `customer-admin` | Main organization user who manages the organization information, as well as its members and cost centers. | -| Organization Approver | `customer-approver` | Organization user who can take a saved cart or quote that was created by an **Organization Buyer** and use it to place an order. | -| Organization Buyer | `customer-buyer` | Organization user who has the ability to add items to cart. If the **B2B Quotes** app is installed, they are also able to save their cart for future use or create a quote. | - +| **Role** | **Key** | **Description** | +| --------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Store Admin | `store-admin` | Store administrator, that is, a user who has access to the VTEX Admin. | +| Sales Admin | `sales-admin` | Sales administrator user who can manage all sales users. | +| Sales Manager | `sales-manager` | Sales manager user who can manage sales users in the same organization, as well as assist or impersonate buyers during navigation or purchase. | +| Sales Representative | `sales-representative` | Sales representative user who can assist or impersonate buyers in the same cost center during navigation or purchase. | +| Organization Admin | `customer-admin` | Main organization user who manages the organization information, as well as its members and cost centers. | +| Organization Approver | `customer-approver` | Organization user who can take a saved cart or quote that was created by an **Organization Buyer** and use it to place an order. | +| Organization Buyer | `customer-buyer` | Organization user who has the ability to add items to cart. If the **B2B Quotes** app is installed, they are also able to save their cart for future use or create a quote. | ## How it works @@ -38,19 +36,16 @@ It also allows you to configure available permissions when developing your own a The **Storefront Permissions** app does not contain an interface – it operates “backstage”, storing the predefined roles and serving as a bridge to communicate with other apps in order to check user permissions. If you would like to manage roles and app permissions using the VTEX Admin interface, you must also install the [Storefront Permissions UI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-storefront-permissions-ui) app. As an optional feature, you can install the [Admin Customers](https://developers.vtex.com/vtex-developer-docs/docs/vtex-admin-customers) app for additional customer management capabilities. - ## Before you start Make sure you have the [VTEX IO CLI (Command Line Interface)](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-vtex-io-cli-install) installed in your machine. If you opt to develop your own app and [integrate](#advanced-app-integration-optional) it with **Storefront Permissions**, read our documentation on [Developing an app](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-developing-an-app). - ## Installation You can install the app by running `vtex install vtex.storefront-permissions` in your terminal, using the [VTEX IO CLI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-vtex-io-cli-installation-and-command-reference). - ## Advanced app integration [optional] If you would like to develop your own app and integrate it with **Storefront Permissions**, follow the steps below. @@ -58,72 +53,88 @@ If you would like to develop your own app and integrate it with **Storefront Per 1. Open your app’s repository. 2. In the `manifest.json` file, add `vtex.storefront-permissions` under the `builders` property, as follows. - ```json - "builders": { - "vtex.storefront-permissions": "1.x" - } - ``` + ```json + "builders": { + "vtex.storefront-permissions": "1.x" + } + ``` 3. In the root folder, create a new folder named `vtex.storefront-permissions`. 4. In the `vtex.storefront-permissions` folder, create a `configuration.json` file. The content of this file should follow the format below. Keep in mind that you must replace the example information with the `name` of your app and its `features`. - ```json - { - "name": "My awesome app", - "features": [ - { - "label": "View", - "value": "view-awesome-things", - "roles": ["store-admin","sales-admin","sales-manager","sales-representative","customer-admin","customer-approver","customer-buyer"] - }, - { - "label": "Create", - "value": "create-awesome-things", - "roles": ["store-admin","sales-admin","sales-manager","sales-representative"] - }, - { - "label": "Delete", - "value": "delete-awesome-things", - "roles": ["store-admin","sales-admin","sales-manager","sales-representative"] - }, - { - "label": "Special Access", - "value": "allow-special-access", - "roles": ["store-admin","sales-admin"] - } - ] - } - ``` - - Below you can find a description of each property. - - | **Property** | **Type** | **Description** | - |---|---|---| - | `name` | string | Name of your app. | - | `features` | array of objects | List of features related to your app – the functionalities of your app that you want to set permissions for users to be able to access or not. | - | ⤷ | object | Object containing information about each feature of your app. | - | ⤷ `label` | string | Name or description of the feature. | - | ⤷ `value` | string | Identifier key you want to use for the feature, as used in your app’s [custom storefront components](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-developing-custom-storefront-components). Do not use space or special characters in this field. | - | ⤷ `roles` | array of strings | List with the keys for all the roles you want to associate with the permission to use the feature by default. You can find more information about each role and its key in the [Available Storefront Roles section](#available-storefront-roles) of this documentation. | + ```json + { + "name": "My awesome app", + "features": [ + { + "label": "View", + "value": "view-awesome-things", + "roles": [ + "store-admin", + "sales-admin", + "sales-manager", + "sales-representative", + "customer-admin", + "customer-approver", + "customer-buyer" + ] + }, + { + "label": "Create", + "value": "create-awesome-things", + "roles": [ + "store-admin", + "sales-admin", + "sales-manager", + "sales-representative" + ] + }, + { + "label": "Delete", + "value": "delete-awesome-things", + "roles": [ + "store-admin", + "sales-admin", + "sales-manager", + "sales-representative" + ] + }, + { + "label": "Special Access", + "value": "allow-special-access", + "roles": ["store-admin", "sales-admin"] + } + ] + } + ``` + + Below you can find a description of each property. + + | **Property** | **Type** | **Description** | + | ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `name` | string | Name of your app. | + | `features` | array of objects | List of features related to your app – the functionalities of your app that you want to set permissions for users to be able to access or not. | + | ⤷ | object | Object containing information about each feature of your app. | + | ⤷ `label` | string | Name or description of the feature. | + | ⤷ `value` | string | Identifier key you want to use for the feature, as used in your app’s [custom storefront components](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-developing-custom-storefront-components). Do not use space or special characters in this field. | + | ⤷ `roles` | array of strings | List with the keys for all the roles you want to associate with the permission to use the feature by default. You can find more information about each role and its key in the [Available Storefront Roles section](#available-storefront-roles) of this documentation. | 5. Make sure you refer to the `value` – the identifier key – for each feature you want to set permissions for in your [custom storefront components](https://developers.vtex.com/vtex-developer-docs/docs/vtex-io-documentation-developing-custom-storefront-components)’ code, when developing your app. - Example: in the B2B Organizations app, one of the permissions created is represented by the following object. + Example: in the B2B Organizations app, one of the permissions created is represented by the following object. - ```json - { - "label": "Create Cost Center", - "value": "create-cost-center-organization", - "roles": ["store-admin","sales-admin","customer-admin"] - } - ``` - - Therefore, in a [custom storefront component’s code](https://github.com/vtex-apps/b2b-organizations/blob/366a28add226eac5d9b104bb13a9f2cd1d574f02/react/components/CostCenterDetails.tsx) in TypeScript, whenever referring to this feature, it uses `create-cost-center-organization`. + ```json + { + "label": "Create Cost Center", + "value": "create-cost-center-organization", + "roles": ["store-admin", "sales-admin", "customer-admin"] + } + ``` + Therefore, in a [custom storefront component’s code](https://github.com/vtex-apps/b2b-organizations/blob/366a28add226eac5d9b104bb13a9f2cd1d574f02/react/components/CostCenterDetails.tsx) in TypeScript, whenever referring to this feature, it uses `create-cost-center-organization`. Once you are done developing and installing your own app, if you have [Storefront Permissions UI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-storefront-permissions-ui), the features of your app associated with each role will be automatically loaded on the **Storefront Permissions** page. For more details on this, read our documentation on the [Storefront Permissions UI](https://developers.vtex.com/vtex-developer-docs/docs/vtex-storefront-permissions-ui) app. - ### GraphQL queries Now that your app is integrated, you can write a GraphQL query on your app to check the current user's permission within the context of your app. @@ -132,7 +143,6 @@ First, you need to associate your test user to a Role containing your app's perm It is not necessary to declare your app name nor user credentials, the query will take care of these details. - #### checkUserPermission This query allows you to check which permissions a user has in the context of your app. When the query is performed, **Storefront Permissions** checks which app the request is coming from, and returns the current user's permissions for that specific app. @@ -154,10 +164,8 @@ query permissions { } ``` - Sample response: - ```graphql { "data":{ @@ -173,15 +181,12 @@ Sample response: } ``` - - #### hasUsers This query allows you to check if there are any users associated with a specific role. The response will be a boolean, with `"hasUsers": true` if there are users with this role or `false` if there are not. Sample query: - ```graphql query hasUsers { hasUsers(slug: "sales-admin") @@ -189,10 +194,8 @@ query hasUsers { } ``` - Sample response: - ```graphql { "data": { @@ -201,15 +204,12 @@ Sample response: } ``` - - #### checkImpersonation This query allows you to check if the current user is [impersonating](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-organizations#impersonate-users) another user, and retrieve information on the impersonated user. Sample query: - ```graphql query checkImpersonation { checkImpersonation { @@ -222,10 +222,8 @@ query checkImpersonation { } ``` - Sample response: - ```graphql { "data": { @@ -244,43 +242,50 @@ Sample response: Using Session Watcher, **Storefront Permissions** detects changes to the user authentication to trigger changes to its cart and navigation, based on associated price tables, collections, profile information and shipping options. -This query fetches Session Watcher’s status, that is, whether it is active or not. The response will be a boolean, with `true` meaning that it is enabled or `false` meaning it is disabled. +This query fetches Session Watcher’s status, that is, whether it is active or not. +And it will query the Regionalization Type set for the store. +The response has a a boolean, with `true` meaning that it is enabled or `false` meaning it is disabled. +And the three values: + +- `DEFAULTV2` which is the default v2 regionalization from VTEX and the default for the application. +- `DEFAULTV1` which uses the VTEX regionalization v1. +- `PRIVATESELLER` which uses the private-seller facet filter, this should only be used in sellerWhiteLabel sellers. Sample query: ```graphql -query getSessionWatcher { - getSessionWatcher +query { + getSessionWatcher { + active + regionalizationType + } } ``` - Sample response: - ```graphql { "data": { - "getSessionWatcher": false + "getSessionWatcher": { + "active": true, + "regionalizationType": "DEFAULTV2" + } } } ``` - - ### GraphQL mutation - #### impersonateUser Using this mutation, you can inform the `userId` to [impersonate an user](https://developers.vtex.com/vtex-developer-docs/docs/vtex-b2b-organizations#impersonate-users). To remove impersonation, send an empty `userId` instead. Sample mutation: - ```graphql mutation impersonateUser($userId: ID) -@context(provider: "vtex.storefront-permissions") { + @context(provider: "vtex.storefront-permissions") { impersonateUser(userId: $userId) { status message @@ -292,13 +297,21 @@ mutation impersonateUser($userId: ID) If your account is not using `vtex.b2b-organizations` you may want to disable the Session Watcher to avoid unnecessary operations. To do so, set the `active` property to `false` in the mutation exemplified below. +Based on the type of business, the regionalization behaviour need to change. +The property `regionalizationType`controls how the session or search will be patched. +The possible values are: + +- `DEFAULTV2` which is the default v2 regionalization from VTEX and the default for the application. +- `DEFAULTV1` which uses the VTEX regionalization v1. +- `PRIVATESELLER` which uses the private-seller facet filter, this should only be used in sellerWhiteLabel sellers. + The response will be a boolean, with `true` for a successful operation or `false` for a failure. Sample mutation: ```graphql mutation setSessionWatcher { - sessionWatcher(active: false) + sessionWatcher(active: false, regionalizationType: DEFAULTV2) } ``` diff --git a/graphql/schema.graphql b/graphql/schema.graphql index a35c59a..67edfe4 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -57,7 +57,7 @@ type Query { @withSender @cacheControl(scope: PRIVATE) - getSessionWatcher: Boolean @cacheControl(scope: PRIVATE) + getSessionWatcher: sessionWatcherData @cacheControl(scope: PRIVATE) getUsersByEmail(email: String!): [User] @cacheControl(scope: PRIVATE) @@ -68,7 +68,10 @@ type Query { } type Mutation { - sessionWatcher(active: Boolean!): Boolean @cacheControl(scope: PRIVATE) + sessionWatcher( + active: Boolean! + regionalizationType: regionalizationTypeInput + ): Boolean @cacheControl(scope: PRIVATE) saveRole( id: ID @@ -141,6 +144,10 @@ type Mutation { @withSession @cacheControl(scope: PRIVATE) } +type sessionWatcherData { + active: Boolean + regionalizationType: regionalizationTypeInput +} type UserImpersonation { firstName: String @@ -194,6 +201,11 @@ type Profile { roleId: ID! scoped: Boolean } +enum regionalizationTypeInput { + DEFAULTV2 + PRIVATESELLER + DEFAULTV1 +} input FeatureInput { module: String! diff --git a/node/resolvers/Mutations/Settings.ts b/node/resolvers/Mutations/Settings.ts index e815331..ed48483 100644 --- a/node/resolvers/Mutations/Settings.ts +++ b/node/resolvers/Mutations/Settings.ts @@ -13,9 +13,9 @@ export const sessionWatcher = async (_: any, params: any, ctx: Context) => { return {} }) - const { active } = params + const { active, regionalizationType } = params - settings.sessionWatcher = { active } + settings.sessionWatcher = { active, regionalizationType } return vbase .saveJSON('b2b_settings', app, settings) diff --git a/node/resolvers/Queries/Settings.ts b/node/resolvers/Queries/Settings.ts index 7da5ba1..2abecf1 100644 --- a/node/resolvers/Queries/Settings.ts +++ b/node/resolvers/Queries/Settings.ts @@ -90,13 +90,19 @@ export const getSessionWatcher = async (_: any, __: any, ctx: Context) => { } = ctx const app: string = getAppId() + // applies when settings?.sessionWatcher == undefined for new accounts + + const defaultSettingsSessionWatcher = { + active: true, + regionalizationType: 'DEFAULTV2', + } const settings: any = await vbase.getJSON('b2b_settings', app).catch(() => { return {} }) try { - return settings?.sessionWatcher?.active ?? true + return settings?.sessionWatcher ?? defaultSettingsSessionWatcher } catch (error) { logger.error({ error, diff --git a/node/resolvers/Routes/index.ts b/node/resolvers/Routes/index.ts index cfda7f8..dc0fabc 100644 --- a/node/resolvers/Routes/index.ts +++ b/node/resolvers/Routes/index.ts @@ -133,7 +133,7 @@ export const Routes = { const isWatchActive = await getSessionWatcher(null, null, ctx) - if (!isWatchActive) { + if (!isWatchActive?.active) { ctx.response.body = response ctx.response.status = 200 @@ -196,6 +196,10 @@ export const Routes = { } if (!email) { + if (orderFormId) { + await checkout.clearCart(orderFormId) + } + ctx.response.body = response ctx.response.status = 200 @@ -369,19 +373,16 @@ export const Routes = { facets = [...facets, ...collections] } - if (organization.sellers?.length) { - const sellersName = organization.sellers.map( - (seller: any) => - `sellername=${seller.name - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '')}` - ) + if (isWatchActive.regionalizationType === 'PRIVATESELLER') { + if (organization.sellers?.length) { + // DISCLAIMER PRIVATE-SELLER only works for SWL sellers also leaving this commented as there might be a way to use it for traditional sellers later on. - const sellersId = organization.sellers.map( - (seller: any) => `private-seller=${seller.id}` - ) + const sellersId = organization.sellers.map( + (seller: any) => `private-seller=${seller.id}` + ) - facets = [...facets, ...sellersName, ...sellersId] + facets = [...facets, ...sellersId] + } } response.public.facets.value = facets ? `${facets.join(';')};` : null @@ -457,15 +458,32 @@ export const Routes = { marketingTagsResponse?.data?.getMarketingTags?.tags try { - const [regionId] = await checkout.getRegionId( - address.country, - address.postalCode, - salesChannel.toString() - ) + if ( + isWatchActive.regionalizationType === 'DEFAULTV2' || + !isWatchActive.regionalizationType + ) { + const [regionId] = await checkout.getRegionId( + address.country, + address.postalCode, + salesChannel.toString() + ) + + if (regionId?.id) { + response.public.regionId = { + value: regionId.id, + } + } + } else if (isWatchActive.regionalizationType === 'DEFAULTV1') { + const sellerIds = organization.sellers.map((seller: any) => seller.id) + const sellerString = `SW#${sellerIds.join(',')}` + + const encodedSellerString = + Buffer.from(sellerString).toString('base64') - if (regionId?.id) { - response.public.regionId = { - value: regionId.id, + if (encodedSellerString) { + response.public.regionId = { + value: encodedSellerString, + } } } } catch (error) {