@evenicanpm/storefront-core 2.2.0 → 2.3.1

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.
Files changed (80) hide show
  1. package/package.json +7 -3
  2. package/src/api-manager/README.md +52 -27
  3. package/src/api-manager/datasources/d365/d365-cart.datasource.ts +166 -17
  4. package/src/api-manager/datasources/d365/d365-invoice.datasource.ts +100 -0
  5. package/src/api-manager/datasources/d365/d365-order.datasource.ts +75 -18
  6. package/src/api-manager/datasources/d365/d365-product.datasource.ts +19 -1
  7. package/src/api-manager/datasources/d365/d365-user.datasource.ts +125 -36
  8. package/src/api-manager/datasources/d365/d365.datasource.ts +3 -0
  9. package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +10 -3
  10. package/src/api-manager/datasources/e4/e4.datasource.ts +9 -0
  11. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +7 -0
  12. package/src/api-manager/datasources/e4/order/e4-order.datasource.ts +7 -4
  13. package/src/api-manager/datasources/e4/user/e4-user.datasource.ts +4 -0
  14. package/src/api-manager/index.ts +54 -3
  15. package/src/api-manager/lib/get-graphql-client.ts +11 -5
  16. package/src/api-manager/schemas/cart.schema.ts +10 -1
  17. package/src/api-manager/schemas/invoice.schema.ts +55 -0
  18. package/src/api-manager/schemas/order.schema.ts +13 -1
  19. package/src/api-manager/schemas/user.schema.ts +8 -0
  20. package/src/api-manager/services/create-query.ts +4 -10
  21. package/src/api-manager/services/invoice/queries/get-invoice-details.ts +18 -0
  22. package/src/api-manager/services/invoice/queries/get-invoices.ts +18 -0
  23. package/src/api-manager/services/order/queries/get-order-details.ts +1 -1
  24. package/src/api-manager/services/order/queries/get-orders.ts +6 -3
  25. package/src/api-manager/services/user/queries/get-customer-balance.ts +20 -0
  26. package/src/api-manager/types/Datasource.ts +21 -2
  27. package/src/auth/better-auth.ts +1 -1
  28. package/src/cms/blocks/block-manager.tsx +18 -3
  29. package/src/cms/blocks/components/cta-banner.tsx +65 -0
  30. package/src/cms/blocks/components/footer/footer-content.tsx +61 -0
  31. package/src/cms/blocks/components/footer/index.tsx +9 -71
  32. package/src/cms/blocks/components/footer/interfaces.ts +4 -0
  33. package/src/cms/blocks/components/footer/sections/footer-contact.tsx +18 -9
  34. package/src/cms/blocks/components/footer/sections/footer-logo.tsx +17 -2
  35. package/src/cms/blocks/components/hero-image.tsx +83 -0
  36. package/src/cms/blocks/index.tsx +2 -0
  37. package/src/cms/blocks/interfaces.ts +20 -0
  38. package/src/cms/draft-mode-badge.tsx +26 -0
  39. package/src/cms/queries.ts +81 -0
  40. package/src/components/_tests_/site-logo.test.tsx +1 -1
  41. package/src/components/flex-box/flex-box.tsx +2 -0
  42. package/src/components/header/components/user.tsx +35 -11
  43. package/src/components/header/sticky-header.tsx +1 -0
  44. package/src/components/mini-cart/mini-cart.tsx +3 -3
  45. package/src/components/product-cards/product-card-plp/product-card.tsx +4 -12
  46. package/src/components/product-cards/product-card-plp/styles/index.ts +15 -0
  47. package/src/components/site-logo.tsx +1 -1
  48. package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-header.tsx +1 -0
  49. package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-item.tsx +3 -1
  50. package/src/pages/account/account-navigation.tsx +88 -60
  51. package/src/pages/account/account-routes.ts +86 -0
  52. package/src/pages/account/orders/order-history-filter-types.tsx +26 -0
  53. package/src/pages/account/orders/order-history-filters-panel.tsx +375 -0
  54. package/src/pages/account/orders/order-history-header.tsx +54 -0
  55. package/src/pages/account/orders/order-history-pagination.tsx +55 -0
  56. package/src/pages/account/orders/order-history-root.tsx +125 -0
  57. package/src/pages/account/orders/order-history-row.tsx +110 -0
  58. package/src/pages/account/orders/order-history-search-and-filter.tsx +449 -0
  59. package/src/pages/account/orders/order-history-search-bar.tsx +84 -0
  60. package/src/pages/account/orders/order-history-search-dropdown.tsx +53 -0
  61. package/src/pages/account/orders/order-history-sort.tsx +90 -0
  62. package/src/pages/account/orders/order-history-table.tsx +54 -0
  63. package/src/pages/account/orders/order-row.tsx +2 -2
  64. package/src/pages/account/orders/orders-drop-down-handler.tsx +19 -0
  65. package/src/pages/account/table-header.tsx +20 -0
  66. package/src/pages/account/table-row-skeleton.tsx +31 -0
  67. package/src/pages/account/wishlist/wishlist-item.tsx +4 -1
  68. package/src/pages/blog/blog-card.tsx +9 -3
  69. package/src/pages/blog/blog-detail-view.tsx +152 -0
  70. package/src/pages/blog/blog-list-view.tsx +59 -0
  71. package/src/pages/cart/estimate-shipping.tsx +6 -5
  72. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +43 -21
  73. package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +144 -19
  74. package/src/pages/cms-page-view.tsx +53 -0
  75. package/src/pages/product-details/product-description.tsx +25 -13
  76. package/src/pages/product-details/product-intro/compound/product-intro-images.tsx +26 -8
  77. package/src/pages/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +17 -21
  78. package/src/pages/product-details/product-tabs.tsx +20 -4
  79. package/src/pages/quickorder/order-upload.tsx +12 -6
  80. package/src/pages/quickorder/quick-order.tsx +25 -20
@@ -26,11 +26,15 @@ import {
26
26
  updateProductListLinesAsync,
27
27
  readAsync as wishlistReadAsync,
28
28
  } from "@msdyn365-commerce/retail-proxy/dist/DataActions/ProductListsDataActions.g";
29
+ import { getCustomerBalanceAsync } from "@msdyn365-commerce/retail-proxy/dist/DataActions/StoreOperationsDataActions.g";
30
+ import { SpanStatusCode, trace } from "@opentelemetry/api";
29
31
  import jwt from "jsonwebtoken";
30
32
  import { uniq } from "lodash";
31
33
  import type {
32
34
  CopyCartToWishlistInput,
35
+ CustomerBalances,
33
36
  ExtendedWishlist,
37
+ GetCustomerBalanceInput,
34
38
  User,
35
39
  UserCounts,
36
40
  Wishlist,
@@ -51,6 +55,8 @@ function decodeIdTokenClaims(token: string): IdTokenClaims {
51
55
  return decoded as IdTokenClaims;
52
56
  }
53
57
 
58
+ const tracer = trace.getTracer("d365-user-datasource");
59
+
54
60
  class D365UserDatasource extends D365DatasourceBase implements UserApi {
55
61
  private getRetailContext(context: ICallerContext): IContext {
56
62
  return context as unknown as IContext;
@@ -93,34 +99,102 @@ class D365UserDatasource extends D365DatasourceBase implements UserApi {
93
99
  }
94
100
 
95
101
  async getUser(): Promise<User | null> {
96
- const context = this.context;
97
- if (!context.requestContext.user.isAuthenticated) return null;
98
- let customer: Customer;
99
- try {
100
- customer = await readAsync(this.getRetailContext(context), "");
101
- } catch {
102
- const token = decodeIdTokenClaims(context.requestContext.user.token);
103
- customer = await createCustomerAsync(this.getRetailContext(context), {
104
- AccountNumber: "",
105
- Email: Array.isArray(token.emails) ? token.emails[0] : token.email,
106
- FirstName: token.given_name,
107
- LastName: token.family_name || ".",
108
- });
109
- }
110
- const mappedAddresses = (customer.Addresses ?? []).map((a) => ({
111
- ...a,
112
- Id: a.RecordId?.toString() ?? "",
113
- }));
114
- return {
115
- ...customer,
116
- Addresses: mappedAddresses,
117
- } as User;
102
+ return tracer.startActiveSpan("d365.getUser", async (span) => {
103
+ try {
104
+ const context = this.context;
105
+ if (!context.requestContext.user.isAuthenticated) {
106
+ span.setAttributes({ "d365.user.authenticated": false });
107
+ span.setStatus({ code: SpanStatusCode.OK });
108
+ return null;
109
+ }
110
+
111
+ let customer: Customer;
112
+ try {
113
+ customer = await readAsync(this.getRetailContext(context), "");
114
+ } catch {
115
+ const token = decodeIdTokenClaims(context.requestContext.user.token);
116
+ customer = await createCustomerAsync(this.getRetailContext(context), {
117
+ AccountNumber: "",
118
+ Email: Array.isArray(token.emails) ? token.emails[0] : token.email,
119
+ FirstName: token.given_name,
120
+ LastName: token.family_name || ".",
121
+ });
122
+ }
123
+
124
+ const mappedAddresses = (customer.Addresses ?? []).map((a) => ({
125
+ ...a,
126
+ Id: a.RecordId?.toString() ?? "",
127
+ }));
128
+
129
+ span.setAttributes({
130
+ "d365.user.authenticated": true,
131
+ "d365.user.account_number": customer.AccountNumber ?? "",
132
+ "d365.user.email": customer.Email ?? "",
133
+ "d365.user.first_name": customer.FirstName ?? "",
134
+ "d365.user.last_name": customer.LastName ?? "",
135
+ "d365.user.customer_type": customer.CustomerTypeValue ?? "",
136
+ "d365.user.record_id": customer.RecordId?.toString() ?? "",
137
+ "d365.user.address_count": mappedAddresses.length,
138
+ });
139
+ span.setStatus({ code: SpanStatusCode.OK });
140
+
141
+ return {
142
+ ...customer,
143
+ Addresses: mappedAddresses,
144
+ } as User;
145
+ } catch (error) {
146
+ const message =
147
+ error instanceof Error ? error.message : JSON.stringify(error);
148
+ span.setStatus({ code: SpanStatusCode.ERROR, message });
149
+ span.recordException(
150
+ error instanceof Error ? error : new Error(message),
151
+ );
152
+ throw new Error(`getUser failed: ${message}`);
153
+ } finally {
154
+ span.end();
155
+ }
156
+ });
118
157
  }
119
158
 
120
159
  async getUserCounts(): Promise<UserCounts> {
121
160
  throw new Error("Method not implemented.");
122
161
  }
123
162
 
163
+ async getCustomerBalance(
164
+ input: GetCustomerBalanceInput,
165
+ ): Promise<CustomerBalances> {
166
+ return tracer.startActiveSpan("d365.getCustomerBalance", async (span) => {
167
+ try {
168
+ const context = this.context;
169
+ const balances = await getCustomerBalanceAsync(
170
+ this.getRetailContext(context),
171
+ input.accountNumber,
172
+ input.invoiceAccountNumber,
173
+ );
174
+ span.setAttributes({
175
+ "d365.balance.account_number": input.accountNumber,
176
+ "d365.balance.credit_limit": balances.CreditLimit,
177
+ "d365.balance.invoice_account_balance":
178
+ balances.InvoiceAccountBalance,
179
+ "d365.balance.invoice_account_credit_limit":
180
+ balances.InvoiceAccountCreditLimit,
181
+ });
182
+ span.setStatus({ code: SpanStatusCode.OK });
183
+ return balances;
184
+ } catch (error) {
185
+ const message =
186
+ error instanceof Error ? error.message : JSON.stringify(error);
187
+ span.setStatus({ code: SpanStatusCode.ERROR, message });
188
+ span.recordException(
189
+ error instanceof Error ? error : new Error(message),
190
+ );
191
+ throw new Error(`getCustomerBalance failed: ${message}`);
192
+ } finally {
193
+ span.end();
194
+ }
195
+ });
196
+ }
197
+
124
198
  async getWishlists(): Promise<Wishlist[]> {
125
199
  const context = this.context;
126
200
  const productListType = ProductListType.WishList;
@@ -246,22 +320,37 @@ class D365UserDatasource extends D365DatasourceBase implements UserApi {
246
320
  }
247
321
 
248
322
  async addProductsToWishlist(input: WishlistLinesInput): Promise<Wishlist> {
249
- const context = this.context;
250
- const id = input.Id;
251
- const productListLines = input.ProductListLines;
323
+ return tracer.startActiveSpan(
324
+ "d365.addProductsToWishlist",
325
+ async (span) => {
326
+ const context = this.context;
327
+ const id = input.Id;
328
+ const catalogId = this.context.requestContext.apiSettings.catalogId;
252
329
 
253
- try {
254
- const updatedProductListLines = await addProductListLinesAsync(
255
- this.getRetailContext(context),
256
- id,
257
- productListLines,
258
- );
330
+ const productListLines = input.ProductListLines.map((line) => ({
331
+ ...line,
332
+ CatalogId: catalogId,
333
+ }));
334
+ console.log(productListLines);
335
+ try {
336
+ const updatedProductListLines = await addProductListLinesAsync(
337
+ this.getRetailContext(context),
338
+ id,
339
+ productListLines,
340
+ );
259
341
 
260
- const wishlist = { Id: id, productListLines: updatedProductListLines };
261
- return wishlist;
262
- } catch (error) {
263
- throw new Error(`Error adding to wishlist: ${error}`);
264
- }
342
+ const wishlist = {
343
+ Id: id,
344
+ productListLines: updatedProductListLines,
345
+ };
346
+ return wishlist;
347
+ } catch (error) {
348
+ throw new Error(`Error adding to wishlist: ${error}`);
349
+ } finally {
350
+ span.end();
351
+ }
352
+ },
353
+ );
265
354
  }
266
355
 
267
356
  async removeProductsFromWishlist(input: WishlistLinesInput): Promise<void> {
@@ -18,6 +18,7 @@ import {
18
18
  } from "@evenicanpm/storefront-core/src/api-manager/types/Datasource";
19
19
  import type { ICallerContext } from "@msdyn365-commerce/retail-proxy";
20
20
  import { D365ProductDatasource } from ".";
21
+ import D365InvoiceDatasource from "./d365-invoice.datasource";
21
22
 
22
23
  export class D365Datasource extends Datasource implements PrimaryDatasource {
23
24
  user: UserApi;
@@ -28,6 +29,7 @@ export class D365Datasource extends Datasource implements PrimaryDatasource {
28
29
  organization: OrganizationApi;
29
30
  session: SessionApi;
30
31
  cart: D365CartDatasource;
32
+ invoice: D365InvoiceDatasource;
31
33
  constructor(name: string, context: ICallerContext) {
32
34
  super(name);
33
35
  this.cart = new D365CartDatasource(context);
@@ -38,6 +40,7 @@ export class D365Datasource extends Datasource implements PrimaryDatasource {
38
40
  this.organization = new D365OrganizationDatasource(context);
39
41
  this.session = new D365SessionDatasource(context);
40
42
  this.user = new D365UserDatasource(context);
43
+ this.invoice = new D365InvoiceDatasource(context);
41
44
  }
42
45
  // Datasources
43
46
  }
@@ -21,9 +21,16 @@ import { auth } from "@/auth/better-auth";
21
21
  export async function getContextFromCookie(): Promise<ICallerContext> {
22
22
  const cookieStore = await cookies();
23
23
  const d365Context = cookieStore.get("D365Context");
24
- const session = await auth.api.getSession({ headers: await getHeaders() });
25
- const accessToken =
26
- (session?.session as { accessToken?: string })?.accessToken || "";
24
+ let accessToken = "";
25
+ try {
26
+ const tokens = await auth.api.getAccessToken({
27
+ headers: await getHeaders(),
28
+ body: { providerId: "entra-external-id" },
29
+ });
30
+ accessToken = tokens?.idToken || tokens?.accessToken || "";
31
+ } catch {
32
+ // User may not be authenticated — continue with empty token
33
+ }
27
34
  const locale = await getLocale();
28
35
  if (d365Context) {
29
36
  const context = JSON.parse(d365Context.value) as ICallerContext;
@@ -19,6 +19,15 @@ const E4Datasource: PrimaryDatasource = {
19
19
  product,
20
20
  organization,
21
21
  categories,
22
+ // To be implemented in future iteration
23
+ invoice: {
24
+ getInvoices: async () => {
25
+ throw new Error("Method not implemented.");
26
+ },
27
+ getInvoiceDetails: async () => {
28
+ throw new Error("Method not implemented.");
29
+ },
30
+ },
22
31
  };
23
32
 
24
33
  export default E4Datasource;
@@ -3428,6 +3428,7 @@ export type Mutation = {
3428
3428
  reindex: IndexAttemptResponse;
3429
3429
  relatedAccountCreate?: Maybe<RelatedAccountCreateMutationResponse>;
3430
3430
  relatedAccountDelete?: Maybe<RelatedAccountDeleteMutationResponse>;
3431
+ removeIndexHistory?: Maybe<Scalars['ID']['output']>;
3431
3432
  /** Removes role and returns removed role */
3432
3433
  removeRole?: Maybe<Role>;
3433
3434
  /**
@@ -4290,6 +4291,12 @@ export type MutationRelatedAccountDeleteArgs = {
4290
4291
  };
4291
4292
 
4292
4293
 
4294
+ export type MutationRemoveIndexHistoryArgs = {
4295
+ id: Scalars['ID']['input'];
4296
+ index?: InputMaybe<Scalars['String']['input']>;
4297
+ };
4298
+
4299
+
4293
4300
  export type MutationRemoveRoleArgs = {
4294
4301
  input: RemoveRoleInput;
4295
4302
  };
@@ -5,17 +5,19 @@ import {
5
5
  import getGraphqlClient from "@evenicanpm/storefront-core/src/api-manager/lib/get-graphql-client";
6
6
  import type {
7
7
  GetSalesOrderDetailsInput,
8
+ GetSalesOrdersInput,
8
9
  SalesOrder,
9
- SalesOrderInput,
10
10
  } from "@evenicanpm/storefront-core/src/api-manager/schemas/order.schema";
11
11
  import type { OrderApi } from "@evenicanpm/storefront-core/src/api-manager/types/Datasource";
12
12
  import { merge } from "object-mapper";
13
13
 
14
14
  const order: OrderApi = {
15
- async getSalesOrders(input: SalesOrderInput): Promise<SalesOrder[]> {
15
+ async getSalesOrders(input: GetSalesOrdersInput): Promise<SalesOrder[]> {
16
16
  const e4Api = await getGraphqlClient(true);
17
17
 
18
- const { data, errors } = await e4Api.e4OrderListSM({ id: input?.id });
18
+ const { data, errors } = await e4Api.e4OrderListSM({
19
+ id: input?.orderSearchCriteria?.CustomerAccountNumber ?? "",
20
+ });
19
21
 
20
22
  if (errors) {
21
23
  throw new Error("Error getting orders");
@@ -30,9 +32,10 @@ const order: OrderApi = {
30
32
  input: GetSalesOrderDetailsInput,
31
33
  ): Promise<SalesOrder> {
32
34
  const e4Api = await getGraphqlClient(true);
35
+ if (!input?.transactionId) throw new Error("transactionId is required");
33
36
 
34
37
  const { data, errors } = await e4Api.e4OrderReadMD({
35
- id: input?.transactionId,
38
+ id: input.transactionId,
36
39
  });
37
40
 
38
41
  if (errors) {
@@ -138,6 +138,10 @@ const user: UserApi = {
138
138
  async copyCartToWishlist(_input: CopyCartToWishlistInput): Promise<Wishlist> {
139
139
  throw new Error("copyCartToWishlist not implemented");
140
140
  },
141
+
142
+ async getCustomerBalance(_input) {
143
+ throw new Error("getCustomerBalance not implemented");
144
+ },
141
145
  };
142
146
 
143
147
  export default user;
@@ -1,23 +1,74 @@
1
1
  import { D365Datasource } from "@evenicanpm/storefront-core/src/api-manager/datasources/d365/index";
2
2
  import { getContextFromCookie } from "@evenicanpm/storefront-core/src/api-manager/datasources/d365/utils/get-context-cookie";
3
3
  import E4Datasource from "@evenicanpm/storefront-core/src/api-manager/datasources/e4/e4.datasource";
4
- import type { Datasource } from "@evenicanpm/storefront-core/src/api-manager/types/Datasource";
4
+ import type {
5
+ Datasource,
6
+ DatasourceApis,
7
+ SecondaryDatasource,
8
+ } from "@evenicanpm/storefront-core/src/api-manager/types/Datasource";
9
+
10
+ const OVERRIDES_FILE = "./overrides.datasource.ts";
5
11
 
6
12
  /**
7
13
  * Returns the active commerce datasource based on the API_MANAGER_PRIMARY
8
14
  * environment variable. Defaults to "e4" when the variable is not set.
9
15
  *
16
+ * Supports project-level function overrides via
17
+ * src/extensions/api-manager-overrides/overrides.datasource.ts
18
+ *
10
19
  * Supported values: "e4" | "d365"
11
20
  */
12
21
  async function createApiManager(): Promise<Datasource> {
13
22
  const primary = process.env.API_MANAGER_PRIMARY ?? "e4";
14
23
 
24
+ let datasource: Datasource;
15
25
  if (primary === "d365") {
16
26
  const context = await getContextFromCookie();
17
- return new D365Datasource("d365", context);
27
+ datasource = new D365Datasource("d365", context);
28
+ } else {
29
+ datasource = E4Datasource;
30
+ }
31
+
32
+ const storefrontOverrides: { [key: string]: any } = {};
33
+
34
+ // @ts-expect-error
35
+ function importAll(r) {
36
+ r.keys().forEach((key: string) => {
37
+ storefrontOverrides[key] = r(key);
38
+ });
39
+ }
40
+
41
+ // Arguments must be literals not variables.
42
+ importAll(
43
+ // @ts-expect-error
44
+ require.context(
45
+ "@storefront/extensions/api-manager-overrides",
46
+ true,
47
+ /overrides\.datasource\.ts/,
48
+ ),
49
+ );
50
+
51
+ const projectOverrides: SecondaryDatasource | undefined =
52
+ storefrontOverrides[OVERRIDES_FILE]?.default;
53
+
54
+ if (projectOverrides) {
55
+ for (const entityName of Object.keys(projectOverrides) as Array<
56
+ keyof DatasourceApis
57
+ >) {
58
+ const entityOverrides = projectOverrides[entityName];
59
+ if (!entityOverrides) continue;
60
+
61
+ if (datasource[entityName]) {
62
+ // Merge override methods into the existing entity
63
+ Object.assign(datasource[entityName] as object, entityOverrides);
64
+ } else {
65
+ // Non-core entity added entirely from overrides
66
+ (datasource as any)[entityName] = entityOverrides;
67
+ }
68
+ }
18
69
  }
19
70
 
20
- return E4Datasource;
71
+ return datasource;
21
72
  }
22
73
 
23
74
  export default createApiManager;
@@ -27,11 +27,17 @@ const getGraphQLClient = async (withAuth = false) => {
27
27
  }
28
28
 
29
29
  if (withAuth) {
30
- const session = await auth.api.getSession({ headers: await getHeaders() });
31
- const accessToken = (session?.session as { accessToken?: string })
32
- ?.accessToken;
33
- if (accessToken) {
34
- headers.Authorization = `Bearer ${accessToken}`;
30
+ try {
31
+ const tokens = await auth.api.getAccessToken({
32
+ headers: await getHeaders(),
33
+ body: { providerId: "entra-external-id" },
34
+ });
35
+ const accessToken = tokens?.idToken || tokens?.accessToken;
36
+ if (accessToken) {
37
+ headers.Authorization = `Bearer ${accessToken}`;
38
+ }
39
+ } catch {
40
+ // User may not be authenticated — continue without auth header
35
41
  }
36
42
  }
37
43
 
@@ -54,9 +54,10 @@ export const getCardAcceptPointInput = z.object({
54
54
  export const checkoutInput = z.object({
55
55
  id: z.string(),
56
56
  receiptEmail: z.string(),
57
- tokenizedPaymentCard: tokenizedPaymentCardSchema,
57
+ tokenizedPaymentCard: tokenizedPaymentCardSchema.optional(),
58
58
  currency: z.string(),
59
59
  amount: z.number(),
60
+ paymentMethod: z.enum(["card", "on-account"]).default("card"),
60
61
  });
61
62
 
62
63
  export const updateCartLineDeliveryModeInput = z.object({
@@ -82,6 +83,13 @@ const InventorySearchCartLinesCriteriaInput = z.object({
82
83
  allDimensionsSelected: z.boolean(),
83
84
  });
84
85
 
86
+ export const updateInvoiceLineInput = z.object({
87
+ cartId: z.string(),
88
+ cartLineId: z.string(),
89
+ price: z.number(),
90
+ version: z.number().optional(),
91
+ });
92
+
85
93
  // Typescript Types
86
94
  // export type Cart = z.infer<typeof cartSchema>;
87
95
  // export type CartLine = z.infer<typeof cartLineSchema>;
@@ -108,3 +116,4 @@ export type CheckoutInput = z.infer<typeof checkoutInput>;
108
116
  export type InventorySearchCartLinesCriteriaInput = z.infer<
109
117
  typeof InventorySearchCartLinesCriteriaInput
110
118
  >;
119
+ export type UpdateInvoiceLineInput = z.infer<typeof updateInvoiceLineInput>;
@@ -0,0 +1,55 @@
1
+ export interface InvoiceSearchCriteria {
2
+ InvoiceIds?: string[];
3
+ CustomerId?: string;
4
+ BusinessPartnerId?: string;
5
+ InvoiceTypeValues?: number[];
6
+ InvoicePaidStatusValues?: number[];
7
+ }
8
+
9
+ export interface GetInvoicesInput {
10
+ InvoiceSearchCriteria: InvoiceSearchCriteria;
11
+ }
12
+
13
+ export interface InvoiceDetailsSearchCriteria {
14
+ InvoiceId?: string;
15
+ }
16
+
17
+ export interface GetInvoicesDetailsInput {
18
+ InvoiceSearchCriteria: InvoiceDetailsSearchCriteria;
19
+ }
20
+
21
+ export interface SalesInvoiceLine {
22
+ ItemId?: string;
23
+ Description?: string;
24
+ Quantity?: number;
25
+ Price?: number;
26
+ NetAmount?: number;
27
+ LineDiscount?: number;
28
+ }
29
+
30
+ export interface Invoice {
31
+ Id?: string;
32
+ CustomerName?: string;
33
+ CustomerAccountNumber?: string;
34
+ TotalAmount?: number;
35
+ InvoiceDate?: Date;
36
+ InvoiceDueDate?: Date;
37
+ InvoicePaidStatusValue?: number;
38
+ AmountPaid?: number;
39
+ AmountBalance?: number;
40
+ CurrencyCode?: string;
41
+ }
42
+
43
+ export interface InvoiceDetails {
44
+ Id?: string;
45
+ AmountBalance?: number;
46
+ AmountPaid?: number;
47
+ DueDate?: Date;
48
+ SalesInvoiceLine?: SalesInvoiceLine[];
49
+ OrderedByCustomerName?: string;
50
+ Name?: string;
51
+ InvoiceDate?: Date;
52
+ InvoiceSubtotal?: number;
53
+ TotalTaxAmount?: number;
54
+ Amount?: number;
55
+ }
@@ -1,13 +1,17 @@
1
1
  import {
2
+ orderSearchCriteriaSchema,
3
+ pagingInfoSchema,
2
4
  salesLineSchema,
3
5
  salesOrderSchema,
4
6
  searchLocationSchema,
5
7
  simpleProductSchema,
8
+ sortColumnSchema,
6
9
  } from "@evenicanpm/storefront-core/src/api-manager/schemas/d365.schema";
7
10
  import z from "zod";
8
11
 
9
12
  const getSalesOrderDetailsInput = z.object({
10
- transactionId: z.string(),
13
+ transactionId: z.string().optional(),
14
+ salesId: z.string().optional(),
11
15
  searchLocation: searchLocationSchema.optional(),
12
16
  });
13
17
 
@@ -22,9 +26,17 @@ const extendedSalesOrderSchema = salesOrderSchema
22
26
  .omit({ SalesLines: true })
23
27
  .extend({ SalesLines: extendedSalesLineSchema.array().optional() });
24
28
 
29
+ const getSalesOrdersInput = z.object({
30
+ orderSearchCriteria: orderSearchCriteriaSchema,
31
+ sort: sortColumnSchema.optional(),
32
+ paging: pagingInfoSchema.optional(),
33
+ });
34
+
25
35
  export type GetSalesOrderDetailsInput = z.infer<
26
36
  typeof getSalesOrderDetailsInput
27
37
  >;
38
+ export type GetSalesOrdersInput = z.infer<typeof getSalesOrdersInput>;
39
+
28
40
  export type SalesOrderInput = z.infer<typeof salesOrderInput>;
29
41
  export type SalesOrder = z.infer<typeof extendedSalesOrderSchema>;
30
42
  export type SalesLine = z.infer<typeof extendedSalesLineSchema>;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ type customerBalancesSchema,
2
3
  customerSchema,
3
4
  productListLineSchema,
4
5
  productListSchema,
@@ -17,6 +18,13 @@ export const extendedCustomerSchema = customerSchema.extend({
17
18
 
18
19
  export type User = z.infer<typeof extendedCustomerSchema>;
19
20
  export type UserCounts = z.infer<typeof userCountsSchema>;
21
+ export type CustomerBalances = z.infer<typeof customerBalancesSchema>;
22
+
23
+ export const getCustomerBalanceInput = z.object({
24
+ accountNumber: z.string(),
25
+ invoiceAccountNumber: z.string().optional(),
26
+ });
27
+ export type GetCustomerBalanceInput = z.infer<typeof getCustomerBalanceInput>;
20
28
 
21
29
  // Wishlist
22
30
 
@@ -73,7 +73,10 @@ export const createQuery = <TInput, TOutput>(
73
73
  input?: TInput,
74
74
  options?: { enabled: boolean },
75
75
  ): UseQueryResult<TOutput> =>
76
- useQuery({ ...getQueryOptions(input), enabled: options?.enabled }),
76
+ useQuery({
77
+ ...getQueryOptions(input),
78
+ ...(options?.enabled !== undefined && { enabled: options.enabled }),
79
+ }),
77
80
  useSuspenseData: (input?: TInput): UseSuspenseQueryResult<TOutput> =>
78
81
  useSuspenseQuery(getQueryOptions(input)),
79
82
  fetchData: (queryClient: QueryClient, input?: TInput) =>
@@ -97,15 +100,6 @@ export function getApiFromPath<TInput, TOutput>(
97
100
  const [entity, method] = path;
98
101
  const result = apiManager[entity][method];
99
102
 
100
- if (process.env.NODE_ENV === "development") {
101
- // eslint-disable-next-line no-console
102
- console.log(`[CREATE QUERY] Accessing API Path:`, {
103
- entity,
104
- method,
105
- result,
106
- });
107
- }
108
-
109
103
  // Function reference loses 'this' context — re-bind for d365 classes.
110
104
  if (typeof result === "function") {
111
105
  return (result as (input?: TInput) => Promise<TOutput>).bind(
@@ -0,0 +1,18 @@
1
+ import type { GetInvoicesDetailsInput } from "@evenicanpm/storefront-core/src/api-manager/schemas/invoice.schema";
2
+ import {
3
+ createApiPath,
4
+ createQuery,
5
+ } from "@evenicanpm/storefront-core/src/api-manager/services/create-query";
6
+ import type { SalesInvoice } from "@msdyn365-commerce/retail-proxy/dist";
7
+
8
+ const apiPath = createApiPath("invoice", "getInvoiceDetails");
9
+ const config = {
10
+ queryKey: "invoices",
11
+ };
12
+
13
+ const getInvoices = createQuery<GetInvoicesDetailsInput, SalesInvoice>(
14
+ apiPath,
15
+ config,
16
+ );
17
+
18
+ export default getInvoices;
@@ -0,0 +1,18 @@
1
+ import {
2
+ createApiPath,
3
+ createQuery,
4
+ } from "@evenicanpm/storefront-core/src/api-manager/services/create-query";
5
+ import type { OrderInvoice } from "@msdyn365-commerce/retail-proxy/dist";
6
+ import type { GetInvoicesInput } from "../../../schemas/invoice.schema";
7
+
8
+ const apiPath = createApiPath("invoice", "getInvoices");
9
+ const config = {
10
+ queryKey: "invoices",
11
+ };
12
+
13
+ const getInvoices = createQuery<GetInvoicesInput, OrderInvoice[]>(
14
+ apiPath,
15
+ config,
16
+ );
17
+
18
+ export default getInvoices;
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  const apiPath = createApiPath("order", "getSalesOrderDetails");
11
11
  const config = {
12
- queryKey: "salesOrderDetails",
12
+ queryKey: "salesOrders",
13
13
  };
14
14
 
15
15
  const getOrderDetails = createQuery<GetSalesOrderDetailsInput, SalesOrder>(
@@ -1,6 +1,6 @@
1
1
  import type {
2
+ GetSalesOrdersInput,
2
3
  SalesOrder,
3
- SalesOrderInput,
4
4
  } from "@evenicanpm/storefront-core/src/api-manager/schemas/order.schema";
5
5
  import {
6
6
  createApiPath,
@@ -9,9 +9,12 @@ import {
9
9
 
10
10
  const apiPath = createApiPath("order", "getSalesOrders");
11
11
  const config = {
12
- queryKey: "salesOrderDetails",
12
+ queryKey: "SalesOrders",
13
13
  };
14
14
 
15
- const getOrders = createQuery<SalesOrderInput, SalesOrder[]>(apiPath, config);
15
+ const getOrders = createQuery<GetSalesOrdersInput, SalesOrder[]>(
16
+ apiPath,
17
+ config,
18
+ );
16
19
 
17
20
  export default getOrders;