@hartl-services/medusa-storefront 0.4.1 → 0.6.0
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 +6 -0
- package/dist/browser/index.d.ts +20 -4
- package/dist/browser/query-options.d.ts +20 -4
- package/dist/core/affiliate/consignment-report-split.d.ts +68 -0
- package/dist/core/affiliate/consignment-report-split.js +151 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/react/use-affiliate.d.ts +10 -12
- package/dist/react/use-affiliate.js +5 -11
- package/dist/registry/account.json +2 -2
- package/dist/registry/affiliate.json +4 -4
- package/dist/registry/cart.json +1 -1
- package/dist/registry/checkout.json +1 -1
- package/dist/registry/form.json +1 -1
- package/dist/registry/registry.json +9 -9
- package/dist/registry/setup.json +2 -2
- package/dist/server/affiliate.d.ts +5 -1
- package/dist/server/catalog.d.ts +7 -1
- package/dist/server/catalog.js +12 -0
- package/dist/server/context.d.ts +7 -0
- package/dist/server/index.d.ts +5 -1
- package/dist/server/index.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -97,6 +97,12 @@ the account with the company address as default address and submits the
|
|
|
97
97
|
application from it; while the e-mail is unconfirmed, both wait until the
|
|
98
98
|
first login.
|
|
99
99
|
|
|
100
|
+
The catalog leaves out partner-only products for everyone but a partner. A
|
|
101
|
+
shop may let others see them too with `canSeePartnerOnlyProducts` in the
|
|
102
|
+
server config, e.g. a CMS editor in preview who checks such a product's page
|
|
103
|
+
(since 0.6.0). It only affects what is shown; the backend still refuses
|
|
104
|
+
partner-only products at checkout for non-partners.
|
|
105
|
+
|
|
100
106
|
## Wiring rules
|
|
101
107
|
|
|
102
108
|
- Create `createStorefrontServer` and `createStorefrontBrowser` once each, at
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -837,7 +837,7 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
837
837
|
affiliate_id: string;
|
|
838
838
|
period_start: string;
|
|
839
839
|
period_end: string;
|
|
840
|
-
status: "rejected" | "
|
|
840
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
841
841
|
draft_order_id: string | null;
|
|
842
842
|
order_id: string | null;
|
|
843
843
|
submitted_at: string | null;
|
|
@@ -851,6 +851,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
851
851
|
confirmed_sale_quantity: number;
|
|
852
852
|
requires_admin_clarification: boolean;
|
|
853
853
|
non_sale_reason: string | null;
|
|
854
|
+
non_sale_quantities: {
|
|
855
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
856
|
+
quantity: number;
|
|
857
|
+
}[];
|
|
854
858
|
resolved_unit_price: number | null;
|
|
855
859
|
discounted_line_total: number | null;
|
|
856
860
|
currency_code: string | null;
|
|
@@ -866,7 +870,7 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
866
870
|
affiliate_id: string;
|
|
867
871
|
period_start: string;
|
|
868
872
|
period_end: string;
|
|
869
|
-
status: "rejected" | "
|
|
873
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
870
874
|
draft_order_id: string | null;
|
|
871
875
|
order_id: string | null;
|
|
872
876
|
submitted_at: string | null;
|
|
@@ -880,6 +884,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
880
884
|
confirmed_sale_quantity: number;
|
|
881
885
|
requires_admin_clarification: boolean;
|
|
882
886
|
non_sale_reason: string | null;
|
|
887
|
+
non_sale_quantities: {
|
|
888
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
889
|
+
quantity: number;
|
|
890
|
+
}[];
|
|
883
891
|
resolved_unit_price: number | null;
|
|
884
892
|
discounted_line_total: number | null;
|
|
885
893
|
currency_code: string | null;
|
|
@@ -896,7 +904,7 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
896
904
|
affiliate_id: string;
|
|
897
905
|
period_start: string;
|
|
898
906
|
period_end: string;
|
|
899
|
-
status: "rejected" | "
|
|
907
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
900
908
|
draft_order_id: string | null;
|
|
901
909
|
order_id: string | null;
|
|
902
910
|
submitted_at: string | null;
|
|
@@ -910,6 +918,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
910
918
|
confirmed_sale_quantity: number;
|
|
911
919
|
requires_admin_clarification: boolean;
|
|
912
920
|
non_sale_reason: string | null;
|
|
921
|
+
non_sale_quantities: {
|
|
922
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
923
|
+
quantity: number;
|
|
924
|
+
}[];
|
|
913
925
|
resolved_unit_price: number | null;
|
|
914
926
|
discounted_line_total: number | null;
|
|
915
927
|
currency_code: string | null;
|
|
@@ -926,7 +938,7 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
926
938
|
affiliate_id: string;
|
|
927
939
|
period_start: string;
|
|
928
940
|
period_end: string;
|
|
929
|
-
status: "rejected" | "
|
|
941
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
930
942
|
draft_order_id: string | null;
|
|
931
943
|
order_id: string | null;
|
|
932
944
|
submitted_at: string | null;
|
|
@@ -940,6 +952,10 @@ export declare function createStorefrontBrowser(config: StorefrontBrowserConfig)
|
|
|
940
952
|
confirmed_sale_quantity: number;
|
|
941
953
|
requires_admin_clarification: boolean;
|
|
942
954
|
non_sale_reason: string | null;
|
|
955
|
+
non_sale_quantities: {
|
|
956
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
957
|
+
quantity: number;
|
|
958
|
+
}[];
|
|
943
959
|
resolved_unit_price: number | null;
|
|
944
960
|
discounted_line_total: number | null;
|
|
945
961
|
currency_code: string | null;
|
|
@@ -695,7 +695,7 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
695
695
|
affiliate_id: string;
|
|
696
696
|
period_start: string;
|
|
697
697
|
period_end: string;
|
|
698
|
-
status: "rejected" | "
|
|
698
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
699
699
|
draft_order_id: string | null;
|
|
700
700
|
order_id: string | null;
|
|
701
701
|
submitted_at: string | null;
|
|
@@ -709,6 +709,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
709
709
|
confirmed_sale_quantity: number;
|
|
710
710
|
requires_admin_clarification: boolean;
|
|
711
711
|
non_sale_reason: string | null;
|
|
712
|
+
non_sale_quantities: {
|
|
713
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
714
|
+
quantity: number;
|
|
715
|
+
}[];
|
|
712
716
|
resolved_unit_price: number | null;
|
|
713
717
|
discounted_line_total: number | null;
|
|
714
718
|
currency_code: string | null;
|
|
@@ -724,7 +728,7 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
724
728
|
affiliate_id: string;
|
|
725
729
|
period_start: string;
|
|
726
730
|
period_end: string;
|
|
727
|
-
status: "rejected" | "
|
|
731
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
728
732
|
draft_order_id: string | null;
|
|
729
733
|
order_id: string | null;
|
|
730
734
|
submitted_at: string | null;
|
|
@@ -738,6 +742,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
738
742
|
confirmed_sale_quantity: number;
|
|
739
743
|
requires_admin_clarification: boolean;
|
|
740
744
|
non_sale_reason: string | null;
|
|
745
|
+
non_sale_quantities: {
|
|
746
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
747
|
+
quantity: number;
|
|
748
|
+
}[];
|
|
741
749
|
resolved_unit_price: number | null;
|
|
742
750
|
discounted_line_total: number | null;
|
|
743
751
|
currency_code: string | null;
|
|
@@ -754,7 +762,7 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
754
762
|
affiliate_id: string;
|
|
755
763
|
period_start: string;
|
|
756
764
|
period_end: string;
|
|
757
|
-
status: "rejected" | "
|
|
765
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
758
766
|
draft_order_id: string | null;
|
|
759
767
|
order_id: string | null;
|
|
760
768
|
submitted_at: string | null;
|
|
@@ -768,6 +776,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
768
776
|
confirmed_sale_quantity: number;
|
|
769
777
|
requires_admin_clarification: boolean;
|
|
770
778
|
non_sale_reason: string | null;
|
|
779
|
+
non_sale_quantities: {
|
|
780
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
781
|
+
quantity: number;
|
|
782
|
+
}[];
|
|
771
783
|
resolved_unit_price: number | null;
|
|
772
784
|
discounted_line_total: number | null;
|
|
773
785
|
currency_code: string | null;
|
|
@@ -784,7 +796,7 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
784
796
|
affiliate_id: string;
|
|
785
797
|
period_start: string;
|
|
786
798
|
period_end: string;
|
|
787
|
-
status: "rejected" | "
|
|
799
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
788
800
|
draft_order_id: string | null;
|
|
789
801
|
order_id: string | null;
|
|
790
802
|
submitted_at: string | null;
|
|
@@ -798,6 +810,10 @@ export declare function createQueryOptions({ actions, affiliateApiPaths, cart, r
|
|
|
798
810
|
confirmed_sale_quantity: number;
|
|
799
811
|
requires_admin_clarification: boolean;
|
|
800
812
|
non_sale_reason: string | null;
|
|
813
|
+
non_sale_quantities: {
|
|
814
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
815
|
+
quantity: number;
|
|
816
|
+
}[];
|
|
801
817
|
resolved_unit_price: number | null;
|
|
802
818
|
discounted_line_total: number | null;
|
|
803
819
|
currency_code: string | null;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ConsignmentNonSaleReasonSchema } from "@hartl-services/medusa-contracts/schemas/affiliate";
|
|
2
|
+
import type { GetConsignmentInventoryResponse, StoreConsignmentReport, SubmitConsignmentReportRequest } from "@hartl-services/medusa-contracts/types/affiliate-store";
|
|
3
|
+
/** A reason a missing quantity was not sold (the contract's list). */
|
|
4
|
+
export type ConsignmentNonSaleReason = (typeof ConsignmentNonSaleReasonSchema.options)[number];
|
|
5
|
+
/** The reason of a split row: "" while the partner has not chosen one yet. */
|
|
6
|
+
export type ConsignmentSplitReason = "" | "sold" | ConsignmentNonSaleReason;
|
|
7
|
+
/** One part of a line's shortage (expected minus counted) with its reason. */
|
|
8
|
+
export interface ConsignmentSplitRow {
|
|
9
|
+
quantity: number;
|
|
10
|
+
reason: ConsignmentSplitReason;
|
|
11
|
+
}
|
|
12
|
+
/** One stock line of the report form. `expected_quantity` is a reference, never sent. */
|
|
13
|
+
export interface ConsignmentReportFormLine {
|
|
14
|
+
inventory_item_id: string;
|
|
15
|
+
batch_id: string | null;
|
|
16
|
+
expected_quantity: number;
|
|
17
|
+
counted_quantity: number;
|
|
18
|
+
split: ConsignmentSplitRow[];
|
|
19
|
+
}
|
|
20
|
+
/** The monthly report form's values; `toSubmitConsignmentReportRequest` turns them into the request. */
|
|
21
|
+
export interface ConsignmentReportFormValues {
|
|
22
|
+
period_start: string;
|
|
23
|
+
submission_idempotency_key: string;
|
|
24
|
+
counted_batches: ConsignmentReportFormLine[];
|
|
25
|
+
}
|
|
26
|
+
type ConsignmentReportLine = StoreConsignmentReport["lines"][number];
|
|
27
|
+
/** Labels for choosing a reason ("sold" included). */
|
|
28
|
+
export declare const consignmentSplitReasonLabels: Record<"sold" | ConsignmentNonSaleReason, string>;
|
|
29
|
+
/** The same reasons as part of a sentence ("1 verkauft, 1 retourniert"). */
|
|
30
|
+
export declare const consignmentSplitReasonSummaryLabels: Record<"sold" | ConsignmentNonSaleReason, string>;
|
|
31
|
+
/** Options for the reason select, "Verkauft" first, then the contract's reasons in their order. */
|
|
32
|
+
export declare function getConsignmentSplitReasonOptions(): {
|
|
33
|
+
label: string;
|
|
34
|
+
value: "sold" | ConsignmentNonSaleReason;
|
|
35
|
+
}[];
|
|
36
|
+
/** What is missing against the expected quantity; an overcount has no shortage. */
|
|
37
|
+
export declare function getConsignmentShortage(expectedQuantity: number, countedQuantity: number): number;
|
|
38
|
+
/** The default split: the whole shortage sold; no rows without a shortage. */
|
|
39
|
+
export declare function createInitialConsignmentSplit(shortage: number): ConsignmentSplitRow[];
|
|
40
|
+
/**
|
|
41
|
+
* Re-balances a line's split after the counted quantity changed: no shortage
|
|
42
|
+
* clears it, a shortage without rows starts the default row, exactly one row
|
|
43
|
+
* follows the new shortage, several rows stay as the partner set them (the
|
|
44
|
+
* validation then asks to fix the sum).
|
|
45
|
+
*/
|
|
46
|
+
export declare function rebalanceConsignmentSplit(split: readonly ConsignmentSplitRow[], shortage: number): ConsignmentSplitRow[];
|
|
47
|
+
/** The row "Weiterer Grund" adds: the part of the shortage not yet assigned, no reason chosen. */
|
|
48
|
+
export declare function createAdditionalConsignmentSplitRow(split: readonly ConsignmentSplitRow[], shortage: number): ConsignmentSplitRow;
|
|
49
|
+
/** The German hint while a line's split is unusable, otherwise null. Lines without a shortage have none. */
|
|
50
|
+
export declare function validateConsignmentLineSplit(line: Pick<ConsignmentReportFormLine, "counted_quantity" | "expected_quantity" | "split">): string | null;
|
|
51
|
+
/** True when every line with a shortage has a usable split. */
|
|
52
|
+
export declare function isConsignmentReportSplitValid(lines: readonly ConsignmentReportFormLine[]): boolean;
|
|
53
|
+
/** The form's starting values: every batch counted as expected, so no shortage and no split. */
|
|
54
|
+
export declare function createConsignmentReportFormValues(inventory: GetConsignmentInventoryResponse, submissionIdempotencyKey: string): ConsignmentReportFormValues;
|
|
55
|
+
/**
|
|
56
|
+
* Maps the form to the request: "Verkauft" rows are not sent (the rest counts
|
|
57
|
+
* as sold), the other rows go merged per reason into `non_sale_quantities`,
|
|
58
|
+
* which is omitted without such rows or without a shortage.
|
|
59
|
+
*/
|
|
60
|
+
export declare function toSubmitConsignmentReportRequest(values: ConsignmentReportFormValues): SubmitConsignmentReportRequest;
|
|
61
|
+
/**
|
|
62
|
+
* "Gemeldet: 1 verkauft, 1 retourniert" (approved reports: "Freigegeben: …")
|
|
63
|
+
* for a report line with a shortage; null when there is nothing to say.
|
|
64
|
+
* A line without `non_sale_quantities` but with one of the reasons in
|
|
65
|
+
* `non_sale_reason` assigns the rest of the shortage to that reason.
|
|
66
|
+
*/
|
|
67
|
+
export declare function summarizeConsignmentReportLine(status: StoreConsignmentReport["status"], line: ConsignmentReportLine): string | null;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { ConsignmentNonSaleReasonSchema } from "@hartl-services/medusa-contracts/schemas/affiliate";
|
|
2
|
+
const nonSaleReasons = ConsignmentNonSaleReasonSchema.options;
|
|
3
|
+
/** Labels for choosing a reason ("sold" included). */
|
|
4
|
+
export const consignmentSplitReasonLabels = {
|
|
5
|
+
sold: "Verkauft",
|
|
6
|
+
damaged: "Beschädigt",
|
|
7
|
+
expired: "Abgelaufen",
|
|
8
|
+
returned: "Retourniert",
|
|
9
|
+
correction: "Korrektur",
|
|
10
|
+
};
|
|
11
|
+
/** The same reasons as part of a sentence ("1 verkauft, 1 retourniert"). */
|
|
12
|
+
export const consignmentSplitReasonSummaryLabels = {
|
|
13
|
+
sold: "verkauft",
|
|
14
|
+
damaged: "beschädigt",
|
|
15
|
+
expired: "abgelaufen",
|
|
16
|
+
returned: "retourniert",
|
|
17
|
+
correction: "korrigiert",
|
|
18
|
+
};
|
|
19
|
+
/** Options for the reason select, "Verkauft" first, then the contract's reasons in their order. */
|
|
20
|
+
export function getConsignmentSplitReasonOptions() {
|
|
21
|
+
return ["sold", ...nonSaleReasons].map((reason) => ({ label: consignmentSplitReasonLabels[reason], value: reason }));
|
|
22
|
+
}
|
|
23
|
+
/** What is missing against the expected quantity; an overcount has no shortage. */
|
|
24
|
+
export function getConsignmentShortage(expectedQuantity, countedQuantity) {
|
|
25
|
+
const shortage = expectedQuantity - countedQuantity;
|
|
26
|
+
return Number.isFinite(shortage) && shortage > 0 ? Math.floor(shortage) : 0;
|
|
27
|
+
}
|
|
28
|
+
/** The default split: the whole shortage sold; no rows without a shortage. */
|
|
29
|
+
export function createInitialConsignmentSplit(shortage) {
|
|
30
|
+
return shortage > 0 ? [{ quantity: shortage, reason: "sold" }] : [];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Re-balances a line's split after the counted quantity changed: no shortage
|
|
34
|
+
* clears it, a shortage without rows starts the default row, exactly one row
|
|
35
|
+
* follows the new shortage, several rows stay as the partner set them (the
|
|
36
|
+
* validation then asks to fix the sum).
|
|
37
|
+
*/
|
|
38
|
+
export function rebalanceConsignmentSplit(split, shortage) {
|
|
39
|
+
if (shortage <= 0) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
if (split.length === 0) {
|
|
43
|
+
return createInitialConsignmentSplit(shortage);
|
|
44
|
+
}
|
|
45
|
+
const [only] = split;
|
|
46
|
+
if (split.length === 1 && only) {
|
|
47
|
+
return [{ ...only, quantity: shortage }];
|
|
48
|
+
}
|
|
49
|
+
return split.map((row) => ({ ...row }));
|
|
50
|
+
}
|
|
51
|
+
/** The row "Weiterer Grund" adds: the part of the shortage not yet assigned, no reason chosen. */
|
|
52
|
+
export function createAdditionalConsignmentSplitRow(split, shortage) {
|
|
53
|
+
const assigned = split.reduce((sum, row) => sum + (Number.isFinite(row.quantity) ? row.quantity : 0), 0);
|
|
54
|
+
return { quantity: Math.max(0, shortage - assigned), reason: "" };
|
|
55
|
+
}
|
|
56
|
+
/** The German hint while a line's split is unusable, otherwise null. Lines without a shortage have none. */
|
|
57
|
+
export function validateConsignmentLineSplit(line) {
|
|
58
|
+
const shortage = getConsignmentShortage(line.expected_quantity, line.counted_quantity);
|
|
59
|
+
if (shortage <= 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (line.split.some((row) => !Number.isInteger(row.quantity) || row.quantity < 1)) {
|
|
63
|
+
return "Jede Teilmenge muss eine ganze Zahl ab 1 sein.";
|
|
64
|
+
}
|
|
65
|
+
if (line.split.some((row) => row.reason === "")) {
|
|
66
|
+
return "Bitte wähle für jede Teilmenge einen Grund.";
|
|
67
|
+
}
|
|
68
|
+
const assigned = line.split.reduce((sum, row) => sum + row.quantity, 0);
|
|
69
|
+
if (assigned !== shortage) {
|
|
70
|
+
return `Die Teilmengen ergeben ${assigned}, die Fehlmenge beträgt ${shortage}. Bitte teile sie genau auf.`;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/** True when every line with a shortage has a usable split. */
|
|
75
|
+
export function isConsignmentReportSplitValid(lines) {
|
|
76
|
+
return lines.every((line) => validateConsignmentLineSplit(line) === null);
|
|
77
|
+
}
|
|
78
|
+
/** The form's starting values: every batch counted as expected, so no shortage and no split. */
|
|
79
|
+
export function createConsignmentReportFormValues(inventory, submissionIdempotencyKey) {
|
|
80
|
+
return {
|
|
81
|
+
period_start: inventory.period_start,
|
|
82
|
+
submission_idempotency_key: submissionIdempotencyKey,
|
|
83
|
+
counted_batches: inventory.inventory.map((line) => ({
|
|
84
|
+
inventory_item_id: line.inventory_item_id,
|
|
85
|
+
batch_id: line.batch_id,
|
|
86
|
+
expected_quantity: line.expected_quantity,
|
|
87
|
+
counted_quantity: line.expected_quantity,
|
|
88
|
+
split: createInitialConsignmentSplit(getConsignmentShortage(line.expected_quantity, line.expected_quantity)),
|
|
89
|
+
})),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Maps the form to the request: "Verkauft" rows are not sent (the rest counts
|
|
94
|
+
* as sold), the other rows go merged per reason into `non_sale_quantities`,
|
|
95
|
+
* which is omitted without such rows or without a shortage.
|
|
96
|
+
*/
|
|
97
|
+
export function toSubmitConsignmentReportRequest(values) {
|
|
98
|
+
return {
|
|
99
|
+
period_start: values.period_start,
|
|
100
|
+
submission_idempotency_key: values.submission_idempotency_key,
|
|
101
|
+
counted_batches: values.counted_batches.map((line) => {
|
|
102
|
+
const base = {
|
|
103
|
+
inventory_item_id: line.inventory_item_id,
|
|
104
|
+
batch_id: line.batch_id,
|
|
105
|
+
counted_quantity: line.counted_quantity,
|
|
106
|
+
};
|
|
107
|
+
if (getConsignmentShortage(line.expected_quantity, line.counted_quantity) <= 0) {
|
|
108
|
+
return base;
|
|
109
|
+
}
|
|
110
|
+
const nonSaleQuantities = nonSaleReasons
|
|
111
|
+
.map((reason) => ({
|
|
112
|
+
reason,
|
|
113
|
+
quantity: line.split.filter((row) => row.reason === reason).reduce((sum, row) => sum + row.quantity, 0),
|
|
114
|
+
}))
|
|
115
|
+
.filter((entry) => entry.quantity > 0);
|
|
116
|
+
return nonSaleQuantities.length > 0 ? { ...base, non_sale_quantities: nonSaleQuantities } : base;
|
|
117
|
+
}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* "Gemeldet: 1 verkauft, 1 retourniert" (approved reports: "Freigegeben: …")
|
|
122
|
+
* for a report line with a shortage; null when there is nothing to say.
|
|
123
|
+
* A line without `non_sale_quantities` but with one of the reasons in
|
|
124
|
+
* `non_sale_reason` assigns the rest of the shortage to that reason.
|
|
125
|
+
*/
|
|
126
|
+
export function summarizeConsignmentReportLine(status, line) {
|
|
127
|
+
const shortage = getConsignmentShortage(line.expected_quantity, line.counted_quantity);
|
|
128
|
+
if (shortage <= 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const sold = line.confirmed_sale_quantity > 0 ? line.confirmed_sale_quantity : 0;
|
|
132
|
+
const nonSale = line.non_sale_quantities.map((entry) => ({ reason: entry.reason, quantity: entry.quantity }));
|
|
133
|
+
const legacyReason = nonSaleReasons.find((reason) => reason === line.non_sale_reason);
|
|
134
|
+
if (nonSale.length === 0 && legacyReason && shortage - sold > 0) {
|
|
135
|
+
nonSale.push({ reason: legacyReason, quantity: shortage - sold });
|
|
136
|
+
}
|
|
137
|
+
// An approved line can carry a corrected sold quantity next to the original
|
|
138
|
+
// non-sold parts; what neither explains is shown as a correction.
|
|
139
|
+
const remainder = shortage - sold - nonSale.reduce((sum, entry) => sum + entry.quantity, 0);
|
|
140
|
+
if (status === "approved" && remainder > 0) {
|
|
141
|
+
nonSale.push({ reason: "correction", quantity: remainder });
|
|
142
|
+
}
|
|
143
|
+
const parts = [
|
|
144
|
+
...(sold > 0 ? [`${sold} ${consignmentSplitReasonSummaryLabels.sold}`] : []),
|
|
145
|
+
...nonSale.map((entry) => `${entry.quantity} ${consignmentSplitReasonSummaryLabels[entry.reason]}`),
|
|
146
|
+
];
|
|
147
|
+
if (parts.length === 0) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return `${status === "approved" ? "Freigegeben" : "Gemeldet"}: ${parts.join(", ")}`;
|
|
151
|
+
}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./affiliate/affiliate-coupon-label.js";
|
|
|
6
6
|
export * from "./affiliate/affiliate-date-range.js";
|
|
7
7
|
export * from "./affiliate/affiliate-rate-presentation.js";
|
|
8
8
|
export * from "./affiliate/consignment-line-labels.js";
|
|
9
|
+
export * from "./affiliate/consignment-report-split.js";
|
|
9
10
|
export * from "./affiliate/partner-company-address.js";
|
|
10
11
|
export * from "./auth/customer-sign-in.js";
|
|
11
12
|
export * from "./auth/link-token.js";
|
package/dist/core/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./affiliate/affiliate-coupon-label.js";
|
|
|
7
7
|
export * from "./affiliate/affiliate-date-range.js";
|
|
8
8
|
export * from "./affiliate/affiliate-rate-presentation.js";
|
|
9
9
|
export * from "./affiliate/consignment-line-labels.js";
|
|
10
|
+
export * from "./affiliate/consignment-report-split.js";
|
|
10
11
|
export * from "./affiliate/partner-company-address.js";
|
|
11
12
|
export * from "./auth/customer-sign-in.js";
|
|
12
13
|
export * from "./auth/link-token.js";
|
|
@@ -2,6 +2,7 @@ import type { AffiliateCouponLifecycleStatus, CreatePartnerPortalSlugRequest, Cr
|
|
|
2
2
|
import type { CustomerAddressTaxIdentification } from "@hartl-services/medusa-contracts/types/tax-compliance-store";
|
|
3
3
|
import type { HttpTypes } from "@medusajs/types";
|
|
4
4
|
import type { AffiliateDateRange } from "../core/affiliate/affiliate-date-range.js";
|
|
5
|
+
import type { ConsignmentReportFormValues } from "../core/affiliate/consignment-report-split.js";
|
|
5
6
|
import type { CustomerAddressValues } from "../core/form-values.js";
|
|
6
7
|
import { useCustomerAddressBook } from "./use-account.js";
|
|
7
8
|
/**
|
|
@@ -527,7 +528,7 @@ export declare function useAffiliateConsignment(): {
|
|
|
527
528
|
affiliate_id: string;
|
|
528
529
|
period_start: string;
|
|
529
530
|
period_end: string;
|
|
530
|
-
status: "rejected" | "
|
|
531
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
531
532
|
draft_order_id: string | null;
|
|
532
533
|
order_id: string | null;
|
|
533
534
|
submitted_at: string | null;
|
|
@@ -541,6 +542,10 @@ export declare function useAffiliateConsignment(): {
|
|
|
541
542
|
confirmed_sale_quantity: number;
|
|
542
543
|
requires_admin_clarification: boolean;
|
|
543
544
|
non_sale_reason: string | null;
|
|
545
|
+
non_sale_quantities: {
|
|
546
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
547
|
+
quantity: number;
|
|
548
|
+
}[];
|
|
544
549
|
resolved_unit_price: number | null;
|
|
545
550
|
discounted_line_total: number | null;
|
|
546
551
|
currency_code: string | null;
|
|
@@ -555,20 +560,13 @@ export declare function useAffiliateConsignment(): {
|
|
|
555
560
|
submitReport(values: SubmitConsignmentReportRequest): Promise<void>;
|
|
556
561
|
};
|
|
557
562
|
/**
|
|
558
|
-
* The monthly report's starting values
|
|
559
|
-
*
|
|
563
|
+
* The monthly report's starting values (form values, see
|
|
564
|
+
* `toSubmitConsignmentReportRequest` for the request): every batch counted as
|
|
565
|
+
* expected, so without a shortage split, with an idempotency key for the submission (null without crypto.randomUUID - the
|
|
560
566
|
* shop then shows a notice instead of the form).
|
|
561
567
|
*/
|
|
562
568
|
export declare function useConsignmentReportDefaults(inventory: GetConsignmentInventoryResponse): {
|
|
563
|
-
defaultValues:
|
|
564
|
-
period_start: string;
|
|
565
|
-
submission_idempotency_key: string;
|
|
566
|
-
counted_batches: {
|
|
567
|
-
inventory_item_id: string;
|
|
568
|
-
counted_quantity: number;
|
|
569
|
-
batch_id?: string | null | undefined;
|
|
570
|
-
}[];
|
|
571
|
-
};
|
|
569
|
+
defaultValues: ConsignmentReportFormValues;
|
|
572
570
|
submissionIdempotencyKey: string | null;
|
|
573
571
|
};
|
|
574
572
|
export {};
|
|
@@ -6,6 +6,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
6
6
|
import { invalidateAffiliateQueries, resetCustomerScopedQueries } from "../browser/query-invalidation.js";
|
|
7
7
|
import { getAffiliateApplicationErrorMessage } from "../core/affiliate/affiliate-application-errors.js";
|
|
8
8
|
import { getCurrentUtcMonthRange } from "../core/affiliate/affiliate-date-range.js";
|
|
9
|
+
import { createConsignmentReportFormValues } from "../core/affiliate/consignment-report-split.js";
|
|
9
10
|
import { getPartnerCompanyAddressKind, isUsablePartnerIdentification } from "../core/affiliate/partner-company-address.js";
|
|
10
11
|
import { getErrorMessage } from "../core/errors.js";
|
|
11
12
|
import { useStorefront } from "./context.js";
|
|
@@ -554,20 +555,13 @@ export function useAffiliateConsignment() {
|
|
|
554
555
|
};
|
|
555
556
|
}
|
|
556
557
|
/**
|
|
557
|
-
* The monthly report's starting values
|
|
558
|
-
*
|
|
558
|
+
* The monthly report's starting values (form values, see
|
|
559
|
+
* `toSubmitConsignmentReportRequest` for the request): every batch counted as
|
|
560
|
+
* expected, so without a shortage split, with an idempotency key for the submission (null without crypto.randomUUID - the
|
|
559
561
|
* shop then shows a notice instead of the form).
|
|
560
562
|
*/
|
|
561
563
|
export function useConsignmentReportDefaults(inventory) {
|
|
562
564
|
const [submissionIdempotencyKey] = useState(createIdempotencyKey);
|
|
563
|
-
const defaultValues =
|
|
564
|
-
period_start: inventory.period_start,
|
|
565
|
-
submission_idempotency_key: submissionIdempotencyKey ?? "",
|
|
566
|
-
counted_batches: inventory.inventory.map((line) => ({
|
|
567
|
-
inventory_item_id: line.inventory_item_id,
|
|
568
|
-
batch_id: line.batch_id,
|
|
569
|
-
counted_quantity: line.expected_quantity,
|
|
570
|
-
})),
|
|
571
|
-
};
|
|
565
|
+
const defaultValues = createConsignmentReportFormValues(inventory, submissionIdempotencyKey ?? "");
|
|
572
566
|
return { defaultValues, submissionIdempotencyKey };
|
|
573
567
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"title": "Customer account",
|
|
5
5
|
"description": "Sign-in with password or e-mail code, password reset, account verification, address book with UID check, profile, orders, the account workspace layout and the document download route.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hartl-services/medusa-storefront@^0.
|
|
8
|
-
"@hartl-services/medusa-contracts@^1.
|
|
7
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
8
|
+
"@hartl-services/medusa-contracts@^1.5.0",
|
|
9
9
|
"@medusajs/types@^2.19.0",
|
|
10
10
|
"lucide-react"
|
|
11
11
|
],
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"title": "Partner portal",
|
|
5
5
|
"description": "Partner portal: gated registration with UID check, links, coupon codes, commissions with date filter, consignment reports, and its Route Handlers.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hartl-services/medusa-storefront@^0.
|
|
8
|
-
"@hartl-services/medusa-contracts@^1.
|
|
7
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
8
|
+
"@hartl-services/medusa-contracts@^1.5.0",
|
|
9
9
|
"@medusajs/types@^2.19.0",
|
|
10
10
|
"lucide-react",
|
|
11
11
|
"react-day-picker"
|
|
@@ -134,13 +134,13 @@
|
|
|
134
134
|
"path": "registry/items/affiliate/components/affiliate/affiliate-consignment-report-form.tsx",
|
|
135
135
|
"target": "@components/affiliate/affiliate-consignment-report-form.tsx",
|
|
136
136
|
"type": "registry:component",
|
|
137
|
-
"content": "\"use client\"\n\nimport type { GetConsignmentInventoryResponse, SubmitConsignmentReportRequest } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\n\nimport { fieldControlClassName, fieldLabelClassName } from \"@/components/form/field-styles\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\nimport { useAppForm } from \"@/hooks/use-app-form\"\nimport { getConsignmentLineDisplay } from \"@hartl-services/medusa-storefront/core\"\nimport { useConsignmentReportDefaults } from \"@hartl-services/medusa-storefront/react\"\n\ninterface AffiliateConsignmentReportFormProps {\n inventory: GetConsignmentInventoryResponse\n isPending?: boolean\n onSubmit: (values: SubmitConsignmentReportRequest) => Promise<void>\n}\n\nexport function AffiliateConsignmentReportForm({ inventory, isPending = false, onSubmit }: AffiliateConsignmentReportFormProps) {\n const { defaultValues, submissionIdempotencyKey } = useConsignmentReportDefaults(inventory)\n const form = useAppForm({\n defaultValues,\n onSubmit: async ({ value }) => {\n if (!submissionIdempotencyKey) {\n return\n }\n\n await onSubmit(value)\n },\n })\n\n if (!submissionIdempotencyKey) {\n return (\n <Alert variant=\"destructive\">\n <AlertTitle>Meldung kann nicht vorbereitet werden</AlertTitle>\n <AlertDescription>Dein Browser unterstützt keine sichere Kennung für diese Einreichung. Bitte aktualisiere deinen Browser und versuche es erneut.</AlertDescription>\n </Alert>\n )\n }\n\n return (\n <WorkspaceCard description=\"Trage nur die gezählte Menge je Lagerposition ein. Die erwartete Menge dient als Referenz.\" title=\"Monatsmeldung\">\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n void form.handleSubmit()\n }}\n >\n <form.AppField mode=\"array\" name=\"counted_batches\">\n {(arrayField) => (\n <fieldset className=\"flex flex-col\">\n <legend className={`${fieldLabelClassName} mb-3`}>Gezählte Lagerbestände</legend>\n {arrayField.state.value.map((line, index) => {\n const quantityId = `counted-quantity-${line.inventory_item_id}-${line.batch_id ?? \"standard\"}`\n const display = getConsignmentLineDisplay(inventory.inventory[index] ?? line, \"de-DE\")\n\n return (\n <div className=\"grid gap-3 border-b py-4 first:pt-0 last:border-b-0 last:pb-0 sm:grid-cols-[minmax(0,1fr)_auto_10rem] sm:items-end sm:gap-6\" key={`${line.inventory_item_id}-${line.batch_id ?? \"\"}`}>\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-sm font-medium\">{display.product ?? `Lagerposition ${line.inventory_item_id}`}</p>\n <p className=\"text-sm text-muted-foreground\">{display.batch ? `Charge ${display.batch}${display.expiryDate ? `, MHD ${display.expiryDate}` : \"\"}` : \"Ohne Charge\"}</p>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n Erwartete Menge: <span className=\"font-medium text-foreground\">{inventory.inventory[index]?.expected_quantity ?? 0}</span>\n </p>\n <form.AppField name={`counted_batches[${index}].counted_quantity`}>\n {(field) => {\n const hasErrors = field.state.meta.errors.length > 0\n\n return (\n <div className=\"flex flex-col gap-2\">\n <label className={fieldLabelClassName} htmlFor={quantityId}>\n Gezählte Menge für {display.product ?? line.inventory_item_id}\n </label>\n <Input\n aria-describedby={hasErrors ? `${quantityId}-error` : undefined}\n aria-invalid={hasErrors || undefined}\n className={fieldControlClassName}\n id={quantityId}\n inputMode=\"numeric\"\n min={0}\n onBlur={field.handleBlur}\n onChange={(event) => {\n const nextValue = event.target.value\n field.handleChange(nextValue === \"\" ? 0 : Number(nextValue))\n }}\n step={1}\n type=\"number\"\n value={field.state.value}\n />\n {hasErrors ? (\n <p className=\"text-sm text-destructive\" id={`${quantityId}-error`}>\n Menge muss eine nicht-negative ganze Zahl sein.\n </p>\n ) : null}\n </div>\n )\n }}\n </form.AppField>\n </div>\n )\n })}\n </fieldset>\n )}\n </form.AppField>\n <form.Subscribe\n selector={(state) => ({\n canSubmit: state.canSubmit,\n isSubmitting: state.isSubmitting,\n })}\n >\n {({ canSubmit, isSubmitting }) => (\n <Button className=\"self-start\" disabled={!canSubmit || isSubmitting || isPending} type=\"submit\">\n {isSubmitting || isPending ? \"Monatsmeldung wird eingereicht…\" : \"Monatsmeldung einreichen\"}\n </Button>\n )}\n </form.Subscribe>\n </form>\n </WorkspaceCard>\n )\n}\n"
|
|
137
|
+
"content": "\"use client\"\n\nimport type { GetConsignmentInventoryResponse, SubmitConsignmentReportRequest } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\n\nimport { fieldControlClassName, fieldLabelClassName } from \"@/components/form/field-styles\"\nimport { Alert, AlertDescription, AlertTitle } from \"@/components/ui/alert\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\nimport { useAppForm } from \"@/hooks/use-app-form\"\nimport {\n createAdditionalConsignmentSplitRow,\n getConsignmentLineDisplay,\n getConsignmentShortage,\n getConsignmentSplitReasonOptions,\n isConsignmentReportSplitValid,\n rebalanceConsignmentSplit,\n toSubmitConsignmentReportRequest,\n validateConsignmentLineSplit,\n} from \"@hartl-services/medusa-storefront/core\"\nimport { useConsignmentReportDefaults } from \"@hartl-services/medusa-storefront/react\"\n\nconst splitReasonOptions = getConsignmentSplitReasonOptions()\n\ninterface AffiliateConsignmentReportFormProps {\n inventory: GetConsignmentInventoryResponse\n isPending?: boolean\n onSubmit: (values: SubmitConsignmentReportRequest) => Promise<void>\n}\n\nexport function AffiliateConsignmentReportForm({ inventory, isPending = false, onSubmit }: AffiliateConsignmentReportFormProps) {\n const { defaultValues, submissionIdempotencyKey } = useConsignmentReportDefaults(inventory)\n const form = useAppForm({\n defaultValues,\n validators: {\n onSubmit: ({ value }) => (isConsignmentReportSplitValid(value.counted_batches) ? undefined : \"Bitte teile jede Fehlmenge genau auf.\"),\n },\n onSubmit: async ({ value }) => {\n if (!submissionIdempotencyKey) {\n return\n }\n\n await onSubmit(toSubmitConsignmentReportRequest(value))\n },\n })\n\n if (!submissionIdempotencyKey) {\n return (\n <Alert variant=\"destructive\">\n <AlertTitle>Meldung kann nicht vorbereitet werden</AlertTitle>\n <AlertDescription>Dein Browser unterstützt keine sichere Kennung für diese Einreichung. Bitte aktualisiere deinen Browser und versuche es erneut.</AlertDescription>\n </Alert>\n )\n }\n\n return (\n <WorkspaceCard description=\"Trage die gezählte Menge je Lagerposition ein. Fehlt etwas gegenüber der erwarteten Menge, teile die Fehlmenge nach Gründen auf.\" title=\"Monatsmeldung\">\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n void form.handleSubmit()\n }}\n >\n <form.AppField mode=\"array\" name=\"counted_batches\">\n {(arrayField) => (\n <fieldset className=\"flex flex-col\">\n <legend className={`${fieldLabelClassName} mb-3`}>Gezählte Lagerbestände</legend>\n {arrayField.state.value.map((line, index) => {\n const quantityId = `counted-quantity-${line.inventory_item_id}-${line.batch_id ?? \"standard\"}`\n const display = getConsignmentLineDisplay(inventory.inventory[index] ?? line, \"de-DE\")\n const splitHintId = `${quantityId}-split-hint`\n\n return (\n <div className=\"flex flex-col gap-4 border-b py-4 first:pt-0 last:border-b-0 last:pb-0\" key={`${line.inventory_item_id}-${line.batch_id ?? \"\"}`}>\n <div className=\"grid gap-3 sm:grid-cols-[minmax(0,1fr)_auto_10rem] sm:items-end sm:gap-6\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-sm font-medium\">{display.product ?? `Lagerposition ${line.inventory_item_id}`}</p>\n <p className=\"text-sm text-muted-foreground\">{display.batch ? `Charge ${display.batch}${display.expiryDate ? `, MHD ${display.expiryDate}` : \"\"}` : \"Ohne Charge\"}</p>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n Erwartete Menge: <span className=\"font-medium text-foreground\">{line.expected_quantity}</span>\n </p>\n <form.AppField\n validators={{\n onChange: ({ value }) => (Number.isInteger(value) && value >= 0 ? undefined : \"Menge muss eine nicht-negative ganze Zahl sein.\"),\n }}\n listeners={{\n onChange: ({ fieldApi }) => {\n const expected = fieldApi.form.getFieldValue(`counted_batches[${index}].expected_quantity`)\n const split = fieldApi.form.getFieldValue(`counted_batches[${index}].split`)\n\n fieldApi.form.setFieldValue(`counted_batches[${index}].split`, rebalanceConsignmentSplit(split, getConsignmentShortage(expected, fieldApi.state.value)))\n },\n }}\n name={`counted_batches[${index}].counted_quantity`}\n >\n {(field) => {\n const hasErrors = field.state.meta.errors.length > 0\n\n return (\n <div className=\"flex flex-col gap-2\">\n <label className={fieldLabelClassName} htmlFor={quantityId}>\n Gezählte Menge für {display.product ?? line.inventory_item_id}\n </label>\n <Input\n aria-describedby={hasErrors ? `${quantityId}-error` : undefined}\n aria-invalid={hasErrors || undefined}\n className={fieldControlClassName}\n id={quantityId}\n inputMode=\"numeric\"\n min={0}\n onBlur={field.handleBlur}\n onChange={(event) => {\n const nextValue = event.target.value\n field.handleChange(nextValue === \"\" ? 0 : Number(nextValue))\n }}\n step={1}\n type=\"number\"\n value={field.state.value}\n />\n {hasErrors ? (\n <p className=\"text-sm text-destructive\" id={`${quantityId}-error`}>\n Menge muss eine nicht-negative ganze Zahl sein.\n </p>\n ) : null}\n </div>\n )\n }}\n </form.AppField>\n </div>\n <form.Subscribe selector={(state) => state.values.counted_batches[index]}>\n {(currentLine) => {\n if (!currentLine) {\n return null\n }\n\n const shortage = getConsignmentShortage(currentLine.expected_quantity, currentLine.counted_quantity)\n const splitHint = validateConsignmentLineSplit(currentLine)\n\n if (shortage <= 0) {\n return null\n }\n\n return (\n <form.AppField mode=\"array\" name={`counted_batches[${index}].split`}>\n {(splitField) => (\n <div aria-describedby={splitHint ? splitHintId : undefined} className=\"flex flex-col gap-3 rounded-lg border p-4\" role=\"group\" aria-label={`Fehlmenge aufteilen für ${display.product ?? line.inventory_item_id}`}>\n <p className=\"text-sm font-medium\">Fehlmenge aufteilen: {shortage} fehlen gegenüber der erwarteten Menge.</p>\n {currentLine.split.map((_row, rowIndex) => (\n <div className=\"grid gap-3 sm:grid-cols-[6rem_minmax(0,1fr)_auto] sm:items-end\" key={rowIndex}>\n <form.AppField name={`counted_batches[${index}].split[${rowIndex}].quantity`}>\n {(field) => (\n <div className=\"flex flex-col gap-2\">\n <label className={fieldLabelClassName} htmlFor={`${quantityId}-split-${rowIndex}`}>\n Menge\n </label>\n <Input\n className={fieldControlClassName}\n id={`${quantityId}-split-${rowIndex}`}\n inputMode=\"numeric\"\n min={1}\n onBlur={field.handleBlur}\n onChange={(event) => {\n const nextValue = event.target.value\n field.handleChange(nextValue === \"\" ? 0 : Number(nextValue))\n }}\n step={1}\n type=\"number\"\n value={field.state.value}\n />\n </div>\n )}\n </form.AppField>\n <form.AppField name={`counted_batches[${index}].split[${rowIndex}].reason`}>\n {(field) => <field.FormSelectField label=\"Grund\" options={splitReasonOptions} placeholder=\"Grund wählen\" />}\n </form.AppField>\n <Button onClick={() => void splitField.removeValue(rowIndex)} size=\"sm\" type=\"button\" variant=\"outline\">\n Entfernen\n </Button>\n </div>\n ))}\n <Button className=\"self-start\" onClick={() => splitField.pushValue(createAdditionalConsignmentSplitRow(currentLine.split, shortage))} size=\"sm\" type=\"button\" variant=\"outline\">\n Weiterer Grund\n </Button>\n {splitHint ? (\n <p className=\"text-sm text-destructive\" id={splitHintId} role=\"alert\">\n {splitHint}\n </p>\n ) : null}\n </div>\n )}\n </form.AppField>\n )\n }}\n </form.Subscribe>\n </div>\n )\n })}\n </fieldset>\n )}\n </form.AppField>\n <form.Subscribe\n selector={(state) => ({\n canSubmit: state.canSubmit && isConsignmentReportSplitValid(state.values.counted_batches),\n isSubmitting: state.isSubmitting,\n })}\n >\n {({ canSubmit, isSubmitting }) => (\n <Button className=\"self-start\" disabled={!canSubmit || isSubmitting || isPending} type=\"submit\">\n {isSubmitting || isPending ? \"Monatsmeldung wird eingereicht…\" : \"Monatsmeldung einreichen\"}\n </Button>\n )}\n </form.Subscribe>\n </form>\n </WorkspaceCard>\n )\n}\n"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"path": "registry/items/affiliate/components/affiliate/affiliate-consignment-reports.tsx",
|
|
141
141
|
"target": "@components/affiliate/affiliate-consignment-reports.tsx",
|
|
142
142
|
"type": "registry:component",
|
|
143
|
-
"content": "import type { StoreConsignmentReport } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\nimport { getConsignmentLineDisplay } from \"@hartl-services/medusa-storefront/core\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\n\ntype ReportStatus = StoreConsignmentReport[\"status\"]\n\nconst reportStatusLabels: Record<ReportStatus, string> = {\n draft: \"Entwurf\",\n submitted: \"Eingereicht\",\n approved: \"Genehmigt\",\n rejected: \"Abgelehnt\",\n}\n\nconst reportStatusVariants: Record<ReportStatus, \"default\" | \"destructive\" | \"outline\"> = {\n draft: \"outline\",\n submitted: \"outline\",\n approved: \"default\",\n rejected: \"destructive\",\n}\n\ninterface AffiliateConsignmentReportsProps {\n reports: StoreConsignmentReport[]\n}\n\nfunction formatDate(value: string | null) {\n if (!value) {\n return \"Noch nicht eingereicht\"\n }\n\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) {\n return value\n }\n\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n }).format(date)\n}\n\nexport function AffiliateConsignmentReports({ reports }: AffiliateConsignmentReportsProps) {\n if (reports.length === 0) {\n return <WorkspaceCard description=\"Für dein Kommissionslager liegen noch keine Monatsmeldungen vor.\" title=\"Bisherige Meldungen\" />\n }\n\n return (\n <section aria-labelledby=\"consignment-reports-heading\" className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h2 className=\"text-lg font-medium\" id=\"consignment-reports-heading\">\n Bisherige Meldungen\n </h2>\n <p className=\"text-sm text-muted-foreground\">Status und die serverseitig aufgelösten Lagerpositionen deiner Monatsmeldungen.</p>\n </div>\n <div className=\"flex flex-col gap-4\">\n {reports.map((report) => (\n <Card key={report.id}>\n <CardContent>\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex flex-col gap-2\">\n <h3 className=\"font-medium\">\n Zeitraum {report.period_start} bis {report.period_end}\n </h3>\n <p className=\"text-sm text-muted-foreground\">Eingereicht: {formatDate(report.submitted_at)}</p>\n </div>\n <Badge variant={reportStatusVariants[report.status]}>{reportStatusLabels[report.status]}</Badge>\n </div>\n <ul aria-label={`Lagerpositionen für ${report.period_start}`} className=\"flex flex-col\">\n {report.lines.map((line, index) => {\n const display = getConsignmentLineDisplay(line, \"de-DE\")\n\n return (\n <li key={line.id}>\n {index > 0 ? <Separator /> : null}\n <div className=\"flex flex-wrap items-center justify-between gap-x-6 gap-y-2 py-3 text-sm\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"font-medium\">{display.product ?? `Lagerposition ${line.inventory_item_id}`}</p>\n <p className=\"text-muted-foreground\">{display.batch ? `Charge ${display.batch}${display.expiryDate ? `, MHD ${display.expiryDate}` : \"\"}` : \"Ohne Charge\"}</p>\n <p className=\"text-muted-foreground\">Erwartet {line.expected_quantity}, gezählt {line.counted_quantity}</p>\n </div>\n {line.requires_admin_clarification ? <Badge variant=\"destructive\">Klärung erforderlich</Badge> : null}\n </div>\n </li>\n )\n })}\n </ul>\n </CardContent>\n </Card>\n ))}\n </div>\n </section>\n )\n}\n"
|
|
143
|
+
"content": "import type { StoreConsignmentReport } from \"@hartl-services/medusa-contracts/types/affiliate-store\"\nimport { getConsignmentLineDisplay, summarizeConsignmentReportLine } from \"@hartl-services/medusa-storefront/core\"\n\nimport { Badge } from \"@/components/ui/badge\"\nimport { Card, CardContent } from \"@/components/ui/card\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { WorkspaceCard } from \"@/components/workspace/workspace-card\"\n\ntype ReportStatus = StoreConsignmentReport[\"status\"]\n\nconst reportStatusLabels: Record<ReportStatus, string> = {\n draft: \"Entwurf\",\n submitted: \"Eingereicht\",\n approved: \"Genehmigt\",\n rejected: \"Abgelehnt\",\n}\n\nconst reportStatusVariants: Record<ReportStatus, \"default\" | \"destructive\" | \"outline\"> = {\n draft: \"outline\",\n submitted: \"outline\",\n approved: \"default\",\n rejected: \"destructive\",\n}\n\ninterface AffiliateConsignmentReportsProps {\n reports: StoreConsignmentReport[]\n}\n\nfunction formatDate(value: string | null) {\n if (!value) {\n return \"Noch nicht eingereicht\"\n }\n\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) {\n return value\n }\n\n return new Intl.DateTimeFormat(\"de-DE\", {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n }).format(date)\n}\n\nexport function AffiliateConsignmentReports({ reports }: AffiliateConsignmentReportsProps) {\n if (reports.length === 0) {\n return <WorkspaceCard description=\"Für dein Kommissionslager liegen noch keine Monatsmeldungen vor.\" title=\"Bisherige Meldungen\" />\n }\n\n return (\n <section aria-labelledby=\"consignment-reports-heading\" className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h2 className=\"text-lg font-medium\" id=\"consignment-reports-heading\">\n Bisherige Meldungen\n </h2>\n <p className=\"text-sm text-muted-foreground\">Status und die serverseitig aufgelösten Lagerpositionen deiner Monatsmeldungen.</p>\n </div>\n <div className=\"flex flex-col gap-4\">\n {reports.map((report) => (\n <Card key={report.id}>\n <CardContent>\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex flex-col gap-2\">\n <h3 className=\"font-medium\">\n Zeitraum {report.period_start} bis {report.period_end}\n </h3>\n <p className=\"text-sm text-muted-foreground\">Eingereicht: {formatDate(report.submitted_at)}</p>\n </div>\n <Badge variant={reportStatusVariants[report.status]}>{reportStatusLabels[report.status]}</Badge>\n </div>\n <ul aria-label={`Lagerpositionen für ${report.period_start}`} className=\"flex flex-col\">\n {report.lines.map((line, index) => {\n const display = getConsignmentLineDisplay(line, \"de-DE\")\n const splitSummary = summarizeConsignmentReportLine(report.status, line)\n\n return (\n <li key={line.id}>\n {index > 0 ? <Separator /> : null}\n <div className=\"flex flex-wrap items-center justify-between gap-x-6 gap-y-2 py-3 text-sm\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"font-medium\">{display.product ?? `Lagerposition ${line.inventory_item_id}`}</p>\n <p className=\"text-muted-foreground\">{display.batch ? `Charge ${display.batch}${display.expiryDate ? `, MHD ${display.expiryDate}` : \"\"}` : \"Ohne Charge\"}</p>\n <p className=\"text-muted-foreground\">Erwartet {line.expected_quantity}, gezählt {line.counted_quantity}</p>\n {splitSummary ? <p className=\"text-muted-foreground\">{splitSummary}</p> : null}\n </div>\n {line.requires_admin_clarification ? <Badge variant=\"destructive\">Klärung erforderlich</Badge> : null}\n </div>\n </li>\n )\n })}\n </ul>\n </CardContent>\n </Card>\n ))}\n </div>\n </section>\n )\n}\n"
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
"path": "registry/items/affiliate/components/affiliate/affiliate-coupon-code-actions.tsx",
|
package/dist/registry/cart.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Cart",
|
|
5
5
|
"description": "Cart widget with popover, line items, totals, cart page, add-to-cart button, variant picker and product price.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hartl-services/medusa-storefront@^0.
|
|
7
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
8
8
|
"@medusajs/types@^2.19.0",
|
|
9
9
|
"lucide-react"
|
|
10
10
|
],
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Checkout",
|
|
5
5
|
"description": "Checkout page (address, login prompt, shipping and Stripe payment, coupon, UID status) and the order confirmation page.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hartl-services/medusa-storefront@^0.
|
|
7
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
8
8
|
"@medusajs/types@^2.19.0",
|
|
9
9
|
"@stripe/react-stripe-js@^5.6.1",
|
|
10
10
|
"@stripe/stripe-js@^8.11.0",
|
package/dist/registry/form.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Storefront form kit",
|
|
5
5
|
"description": "The shop's TanStack form hook (useAppForm) with unstyled field components; the package's hooks hand their formOptions to it.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hartl-services/medusa-storefront@^0.
|
|
7
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
8
8
|
"@tanstack/react-form@^1.33.5",
|
|
9
9
|
"input-otp",
|
|
10
10
|
"lucide-react"
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"title": "Storefront setup",
|
|
8
8
|
"description": "Wires the storefront package into a Next shop: server and browser instances, the Server Action wrappers, texts, error reporting and the provider root for the [countryCode] layout.",
|
|
9
9
|
"dependencies": [
|
|
10
|
-
"@hartl-services/medusa-storefront@^0.
|
|
11
|
-
"@hartl-services/medusa-contracts@^1.
|
|
10
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
11
|
+
"@hartl-services/medusa-contracts@^1.5.0",
|
|
12
12
|
"@medusajs/framework@^2.19.0",
|
|
13
13
|
"@medusajs/js-sdk@^2.19.0",
|
|
14
14
|
"@medusajs/types@^2.19.0",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"title": "Storefront form kit",
|
|
74
74
|
"description": "The shop's TanStack form hook (useAppForm) with unstyled field components; the package's hooks hand their formOptions to it.",
|
|
75
75
|
"dependencies": [
|
|
76
|
-
"@hartl-services/medusa-storefront@^0.
|
|
76
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
77
77
|
"@tanstack/react-form@^1.33.5",
|
|
78
78
|
"input-otp",
|
|
79
79
|
"lucide-react"
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
"title": "Cart",
|
|
198
198
|
"description": "Cart widget with popover, line items, totals, cart page, add-to-cart button, variant picker and product price.",
|
|
199
199
|
"dependencies": [
|
|
200
|
-
"@hartl-services/medusa-storefront@^0.
|
|
200
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
201
201
|
"@medusajs/types@^2.19.0",
|
|
202
202
|
"lucide-react"
|
|
203
203
|
],
|
|
@@ -306,8 +306,8 @@
|
|
|
306
306
|
"title": "Customer account",
|
|
307
307
|
"description": "Sign-in with password or e-mail code, password reset, account verification, address book with UID check, profile, orders, the account workspace layout and the document download route.",
|
|
308
308
|
"dependencies": [
|
|
309
|
-
"@hartl-services/medusa-storefront@^0.
|
|
310
|
-
"@hartl-services/medusa-contracts@^1.
|
|
309
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
310
|
+
"@hartl-services/medusa-contracts@^1.5.0",
|
|
311
311
|
"@medusajs/types@^2.19.0",
|
|
312
312
|
"lucide-react"
|
|
313
313
|
],
|
|
@@ -516,7 +516,7 @@
|
|
|
516
516
|
"title": "Checkout",
|
|
517
517
|
"description": "Checkout page (address, login prompt, shipping and Stripe payment, coupon, UID status) and the order confirmation page.",
|
|
518
518
|
"dependencies": [
|
|
519
|
-
"@hartl-services/medusa-storefront@^0.
|
|
519
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
520
520
|
"@medusajs/types@^2.19.0",
|
|
521
521
|
"@stripe/react-stripe-js@^5.6.1",
|
|
522
522
|
"@stripe/stripe-js@^8.11.0",
|
|
@@ -661,8 +661,8 @@
|
|
|
661
661
|
"title": "Partner portal",
|
|
662
662
|
"description": "Partner portal: gated registration with UID check, links, coupon codes, commissions with date filter, consignment reports, and its Route Handlers.",
|
|
663
663
|
"dependencies": [
|
|
664
|
-
"@hartl-services/medusa-storefront@^0.
|
|
665
|
-
"@hartl-services/medusa-contracts@^1.
|
|
664
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
665
|
+
"@hartl-services/medusa-contracts@^1.5.0",
|
|
666
666
|
"@medusajs/types@^2.19.0",
|
|
667
667
|
"lucide-react",
|
|
668
668
|
"react-day-picker"
|
package/dist/registry/setup.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"title": "Storefront setup",
|
|
5
5
|
"description": "Wires the storefront package into a Next shop: server and browser instances, the Server Action wrappers, texts, error reporting and the provider root for the [countryCode] layout.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"@hartl-services/medusa-storefront@^0.
|
|
8
|
-
"@hartl-services/medusa-contracts@^1.
|
|
7
|
+
"@hartl-services/medusa-storefront@^0.6.0",
|
|
8
|
+
"@hartl-services/medusa-contracts@^1.5.0",
|
|
9
9
|
"@medusajs/framework@^2.19.0",
|
|
10
10
|
"@medusajs/js-sdk@^2.19.0",
|
|
11
11
|
"@medusajs/types@^2.19.0",
|
|
@@ -91,7 +91,7 @@ export declare function createAffiliate(context: ServerContext, pluginFeatures:
|
|
|
91
91
|
affiliate_id: string;
|
|
92
92
|
period_start: string;
|
|
93
93
|
period_end: string;
|
|
94
|
-
status: "rejected" | "
|
|
94
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
95
95
|
draft_order_id: string | null;
|
|
96
96
|
order_id: string | null;
|
|
97
97
|
submitted_at: string | null;
|
|
@@ -105,6 +105,10 @@ export declare function createAffiliate(context: ServerContext, pluginFeatures:
|
|
|
105
105
|
confirmed_sale_quantity: number;
|
|
106
106
|
requires_admin_clarification: boolean;
|
|
107
107
|
non_sale_reason: string | null;
|
|
108
|
+
non_sale_quantities: {
|
|
109
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
110
|
+
quantity: number;
|
|
111
|
+
}[];
|
|
108
112
|
resolved_unit_price: number | null;
|
|
109
113
|
discounted_line_total: number | null;
|
|
110
114
|
currency_code: string | null;
|
package/dist/server/catalog.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpTypes } from "@medusajs/types";
|
|
2
|
-
import type { ServerContext } from "./context.js";
|
|
2
|
+
import type { ServerContext, StorefrontServerConfig } from "./context.js";
|
|
3
3
|
import type { ServerRegion } from "./region.js";
|
|
4
4
|
export interface CommerceProductListInput {
|
|
5
5
|
categoryId?: string;
|
|
@@ -32,3 +32,9 @@ export declare function createCatalog(context: ServerContext, region: ServerRegi
|
|
|
32
32
|
collectionByHandle(handle: string): Promise<HttpTypes.StoreCollection | null>;
|
|
33
33
|
};
|
|
34
34
|
export type ServerCatalog = ReturnType<typeof createCatalog>;
|
|
35
|
+
/**
|
|
36
|
+
* The catalog's partner-only check: the shop's own rule
|
|
37
|
+
* (`canSeePartnerOnlyProducts`) first, then the partner state. A failing
|
|
38
|
+
* shop rule counts as "no", so it never hides anything from a partner.
|
|
39
|
+
*/
|
|
40
|
+
export declare function partnerOnlyVisibility(config: Pick<StorefrontServerConfig, "canSeePartnerOnlyProducts">, isActivePartner: () => Promise<boolean>): () => Promise<boolean>;
|
package/dist/server/catalog.js
CHANGED
|
@@ -112,3 +112,15 @@ export function createCatalog(context, region, isActivePartner = async () => fal
|
|
|
112
112
|
},
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* The catalog's partner-only check: the shop's own rule
|
|
117
|
+
* (`canSeePartnerOnlyProducts`) first, then the partner state. A failing
|
|
118
|
+
* shop rule counts as "no", so it never hides anything from a partner.
|
|
119
|
+
*/
|
|
120
|
+
export function partnerOnlyVisibility(config, isActivePartner) {
|
|
121
|
+
const shopRule = config.canSeePartnerOnlyProducts;
|
|
122
|
+
if (!shopRule) {
|
|
123
|
+
return isActivePartner;
|
|
124
|
+
}
|
|
125
|
+
return async () => (await shopRule().catch(() => false)) || isActivePartner();
|
|
126
|
+
}
|
package/dist/server/context.d.ts
CHANGED
|
@@ -18,6 +18,13 @@ export interface StorefrontServerConfig {
|
|
|
18
18
|
resolveLocale?: (countryCode: string) => string;
|
|
19
19
|
/** Cookies are `Secure` in production. */
|
|
20
20
|
secureCookies?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Who else besides an active partner may see partner-only products in the
|
|
23
|
+
* catalog, e.g. a CMS editor in preview who checks a partner product's page.
|
|
24
|
+
* Display only: the backend still refuses partner-only products at checkout
|
|
25
|
+
* for everyone who is not a partner. Should fail closed.
|
|
26
|
+
*/
|
|
27
|
+
canSeePartnerOnlyProducts?: () => Promise<boolean>;
|
|
21
28
|
}
|
|
22
29
|
export type MedusaServerClient = Medusa;
|
|
23
30
|
export interface ServerContext {
|
package/dist/server/index.d.ts
CHANGED
|
@@ -199,7 +199,7 @@ export declare function createStorefrontServer(config: StorefrontServerConfig):
|
|
|
199
199
|
affiliate_id: string;
|
|
200
200
|
period_start: string;
|
|
201
201
|
period_end: string;
|
|
202
|
-
status: "rejected" | "
|
|
202
|
+
status: "rejected" | "approved" | "draft" | "submitted";
|
|
203
203
|
draft_order_id: string | null;
|
|
204
204
|
order_id: string | null;
|
|
205
205
|
submitted_at: string | null;
|
|
@@ -213,6 +213,10 @@ export declare function createStorefrontServer(config: StorefrontServerConfig):
|
|
|
213
213
|
confirmed_sale_quantity: number;
|
|
214
214
|
requires_admin_clarification: boolean;
|
|
215
215
|
non_sale_reason: string | null;
|
|
216
|
+
non_sale_quantities: {
|
|
217
|
+
reason: "expired" | "damaged" | "returned" | "correction";
|
|
218
|
+
quantity: number;
|
|
219
|
+
}[];
|
|
216
220
|
resolved_unit_price: number | null;
|
|
217
221
|
discounted_line_total: number | null;
|
|
218
222
|
currency_code: string | null;
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "server-only";
|
|
2
2
|
import { createAffiliate } from "./affiliate.js";
|
|
3
3
|
import { createCart } from "./cart.js";
|
|
4
|
-
import { createCatalog } from "./catalog.js";
|
|
4
|
+
import { createCatalog, partnerOnlyVisibility } from "./catalog.js";
|
|
5
5
|
import { createCheckout } from "./checkout.js";
|
|
6
6
|
import { createServerContext } from "./context.js";
|
|
7
7
|
import { createCustomer } from "./customer.js";
|
|
@@ -41,6 +41,6 @@ export function createStorefrontServer(config) {
|
|
|
41
41
|
affiliate,
|
|
42
42
|
orders: createOrders(context),
|
|
43
43
|
documents: createDocuments(context),
|
|
44
|
-
catalog: createCatalog(context, region, affiliate.isActivePartner),
|
|
44
|
+
catalog: createCatalog(context, region, partnerOnlyVisibility(config, affiliate.isActivePartner)),
|
|
45
45
|
};
|
|
46
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hartl-services/medusa-storefront",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Headless Medusa storefront logic, Next.js server/browser adapters and React hooks for shops built from the @hartl-services/medusa-* plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@hartl-services/medusa-contracts": "^1.
|
|
48
|
+
"@hartl-services/medusa-contracts": "^1.5.0",
|
|
49
49
|
"server-only": "^0.0.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|