feat(core-flows,dashboard,js-sdk,promotion,medusa,types,utils): limit promotion usage per customer (#13451)

**What**
- implement promotion usage limits per customer/email
- fix registering spend usage over the limit
- fix type errors in promotion module tests

**How**
- introduce a new type of campaign budget that can be defined by an attribute such as customer id or email
- add `CampaignBudgetUsage` entity to keep track of the number of uses per attribute value
- update `registerUsage` and `computeActions` in the promotion module to work with the new type
- update `core-flows` to pass context needed for usage calculation to the promotion module

**Breaking**
- registering promotion usage now throws (and cart complete fails) if the budget limit is exceeded or if the cart completion would result in a breached limit

---

CLOSES CORE-1172
CLOSES CORE-1173
CLOSES CORE-1174
CLOSES CORE-1175


Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
Frane Polić
2025-10-09 14:35:54 +02:00
committed by GitHub
parent 924564bee5
commit 7dc3b0c5ff
36 changed files with 2390 additions and 190 deletions

View File

@@ -15,26 +15,26 @@ export class Campaign {
}
/**
* This method retrieves a campaign by its ID. It sends a request to the
* This method retrieves a campaign by its ID. It sends a request to the
* [Get Campaign](https://docs.medusajs.com/api/admin#campaigns_getcampaignsid) API route.
*
*
* @param id - The campaign's ID.
* @param query - Configure the fields to retrieve in the campaign.
* @param headers - Headers to pass in the request
* @returns The campaign's details.
*
*
* @example
* To retrieve a campaign by its ID:
*
*
* ```ts
* sdk.admin.campaign.retrieve("procamp_123")
* .then(({ campaign }) => {
* console.log(campaign)
* })
* ```
*
*
* To specify the fields and relations to retrieve:
*
*
* ```ts
* sdk.admin.campaign.retrieve("procamp_123", {
* fields: "id,*budget"
@@ -43,7 +43,7 @@ export class Campaign {
* console.log(campaign)
* })
* ```
*
*
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
*/
async retrieve(
@@ -61,27 +61,27 @@ export class Campaign {
}
/**
* This method retrieves a paginated list of campaigns. It sends a request to the
* This method retrieves a paginated list of campaigns. It sends a request to the
* [List Campaigns](https://docs.medusajs.com/api/admin#campaigns_getcampaigns) API route.
*
*
* @param query - Filters and pagination configurations.
* @param headers - Headers to pass in the request.
* @returns The paginated list of campaigns.
*
*
* @example
* To retrieve the list of campaigns:
*
*
* ```ts
* sdk.admin.campaign.list()
* .then(({ campaigns, count, limit, offset }) => {
* console.log(campaigns)
* })
* ```
*
*
* To configure the pagination, pass the `limit` and `offset` query parameters.
*
*
* For example, to retrieve only 10 items and skip 10 items:
*
*
* ```ts
* sdk.admin.campaign.list({
* limit: 10,
@@ -91,10 +91,10 @@ export class Campaign {
* console.log(campaigns)
* })
* ```
*
*
* Using the `fields` query parameter, you can specify the fields and relations to retrieve
* in each campaign:
*
*
* ```ts
* sdk.admin.campaign.list({
* fields: "id,*budget"
@@ -103,7 +103,7 @@ export class Campaign {
* console.log(campaigns)
* })
* ```
*
*
* Learn more about the `fields` property in the [API reference](https://docs.medusajs.com/api/store#select-fields-and-relations).
*/
async list(
@@ -120,13 +120,13 @@ export class Campaign {
}
/**
* This method creates a campaign. It sends a request to the
* This method creates a campaign. It sends a request to the
* [Create Campaign](https://docs.medusajs.com/api/admin#campaigns_postcampaigns) API route.
*
*
* @param payload - The details of the campaign to create.
* @param headers - Headers to pass in the request
* @returns The campaign's details.
*
*
* @example
* sdk.admin.campaign.create({
* name: "Summer Campaign"
@@ -150,14 +150,14 @@ export class Campaign {
}
/**
* This method updates a campaign. It sends a request to the
* This method updates a campaign. It sends a request to the
* [Update Campaign](https://docs.medusajs.com/api/admin#campaigns_postcampaignsid) API route.
*
*
* @param id - The campaign's ID.
* @param payload - The data to update in the campaign.
* @param headers - Headers to pass in the request
* @returns The campaign's details.
*
*
* @example
* sdk.admin.campaign.update("procamp_123", {
* name: "Summer Campaign"
@@ -184,11 +184,11 @@ export class Campaign {
/**
* This method deletes a campaign by its ID. It sends a request to the
* [Delete Campaign](https://docs.medusajs.com/api/admin#campaigns_deletecampaignsid) API route.
*
*
* @param id - The campaign's ID.
* @param headers - Headers to pass in the request
* @returns The deletion's details.
*
*
* @example
* sdk.admin.campaign.delete("procamp_123")
* .then(({ deleted }) => {
@@ -209,12 +209,12 @@ export class Campaign {
* This method manages the promotions of a campaign to either add or remove the association between them.
* It sends a request to the [Manage Promotions](https://docs.medusajs.com/api/admin#campaigns_postcampaignsidpromotions)
* API route.
*
*
* @param id - The campaign's ID.
* @param payload - The promotions to add or remove associations to them.
* @param headers - Headers to pass in the request
* @returns The campaign's details.
*
*
* @example
* sdk.admin.campaign.batchPromotions("procamp_123", {
* add: ["prom_123", "prom_456"],