@aglyn/plugins-commerce 1.0.0-beta.221 → 1.0.0-beta.222
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 +4 -2
- package/package.json +14 -13
- package/src/lib/components/console/catalog-organization-card.component.js +9 -0
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/discounts-card.component.js +8 -0
- package/src/lib/components/console/discounts-card.component.js.map +1 -1
- package/src/lib/components/console/gift-cards-card.component.js +8 -0
- package/src/lib/components/console/gift-cards-card.component.js.map +1 -1
- package/src/lib/components/console/host-coupons-card.component.js +8 -0
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +34 -70
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/product-zones.d.ts +7 -6
- package/src/lib/components/console/product-zones.js +5 -4
- package/src/lib/components/console/product-zones.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +86 -284
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +38 -0
- package/src/lib/declarations.console-server.js +67 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/model/commerce-gift-cards.d.ts +26 -0
- package/src/lib/model/commerce-gift-cards.js +42 -0
- package/src/lib/model/commerce-gift-cards.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +0 -57
- package/src/lib/model/commerce-orders.js +0 -80
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/index.d.ts +0 -1
- package/src/lib/model/index.js +0 -1
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/plugin.js +5 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +27 -0
- package/src/lib/server/api-v1/openapi.js +219 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -0
- package/src/lib/server/api-v1/orders-and-products.d.ts +19 -0
- package/src/lib/server/api-v1/orders-and-products.js +419 -0
- package/src/lib/server/api-v1/orders-and-products.js.map +1 -0
- package/src/lib/server/billing-webhook.js +10 -13
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/download.js +4 -1
- package/src/lib/server/download.js.map +1 -1
- package/src/lib/server/fulfill-order.d.ts +52 -11
- package/src/lib/server/fulfill-order.js +6 -9
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/gift-card-issue.d.ts +57 -0
- package/src/lib/server/gift-card-issue.js +155 -0
- package/src/lib/server/gift-card-issue.js.map +1 -0
- package/src/lib/server/gift-cards.js +25 -84
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/membership-register.js +9 -8
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/newsletter.js +7 -5
- package/src/lib/server/newsletter.js.map +1 -1
- package/src/lib/server/paid-media-delivery.d.ts +21 -0
- package/src/lib/server/paid-media-delivery.js +23 -5
- package/src/lib/server/paid-media-delivery.js.map +1 -1
- package/src/lib/server/paid-media-serve-count.d.ts +28 -0
- package/src/lib/server/paid-media-serve-count.js +46 -0
- package/src/lib/server/paid-media-serve-count.js.map +1 -0
- package/src/lib/server/person-eraser.d.ts +45 -0
- package/src/lib/server/person-eraser.js +57 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/refund.js +15 -15
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/restock-flag.d.ts +2 -2
- package/src/lib/server/restock-flag.js +1 -1
- package/src/lib/server/restock-flag.js.map +1 -1
- package/src/lib/server/stream.js +4 -1
- package/src/lib/server/stream.js.map +1 -1
- package/src/lib/server.js +5 -21
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-person-records.d.ts +43 -0
- package/src/lib/testing/stand-in-person-records.js +63 -0
- package/src/lib/testing/stand-in-person-records.js.map +1 -0
- package/src/lib/transfer/gift-card-confirm-step.component.d.ts +22 -0
- package/src/lib/transfer/gift-card-confirm-step.component.js +259 -0
- package/src/lib/transfer/gift-card-confirm-step.component.js.map +1 -0
- package/src/lib/transfer/gift-cards-plan.d.ts +29 -0
- package/src/lib/transfer/gift-cards-plan.js +40 -0
- package/src/lib/transfer/gift-cards-plan.js.map +1 -0
- package/src/lib/transfer/gift-cards.server.d.ts +34 -0
- package/src/lib/transfer/gift-cards.server.js +296 -0
- package/src/lib/transfer/gift-cards.server.js.map +1 -0
- package/src/lib/transfer/list-filter.d.ts +29 -0
- package/src/lib/transfer/list-filter.js +112 -0
- package/src/lib/transfer/list-filter.js.map +1 -0
- package/src/lib/transfer/product-import-results.d.ts +29 -0
- package/src/lib/transfer/product-import-results.js +49 -0
- package/src/lib/transfer/product-import-results.js.map +1 -0
- package/src/lib/transfer/product-import-step.component.d.ts +30 -0
- package/src/lib/transfer/product-import-step.component.js +99 -0
- package/src/lib/transfer/product-import-step.component.js.map +1 -0
- package/src/lib/transfer/product-transfer.d.ts +211 -0
- package/src/lib/transfer/product-transfer.js +1594 -0
- package/src/lib/transfer/product-transfer.js.map +1 -0
- package/src/lib/transfer/products.server.d.ts +44 -0
- package/src/lib/transfer/products.server.js +550 -0
- package/src/lib/transfer/products.server.js.map +1 -0
- package/src/lib/transfer/records-transfer.d.ts +182 -0
- package/src/lib/transfer/records-transfer.js +1153 -0
- package/src/lib/transfer/records-transfer.js.map +1 -0
- package/src/lib/transfer/records.server.d.ts +68 -0
- package/src/lib/transfer/records.server.js +455 -0
- package/src/lib/transfer/records.server.js.map +1 -0
- package/src/lib/transfer/register-transfer-resources.d.ts +17 -0
- package/src/lib/transfer/register-transfer-resources.js +171 -0
- package/src/lib/transfer/register-transfer-resources.js.map +1 -0
- package/src/lib/transfer/register-transfer-ui.d.ts +23 -0
- package/src/lib/transfer/register-transfer-ui.js +78 -0
- package/src/lib/transfer/register-transfer-ui.js.map +1 -0
- package/src/lib/transfer/server-common.d.ts +76 -0
- package/src/lib/transfer/server-common.js +165 -0
- package/src/lib/transfer/server-common.js.map +1 -0
- package/src/lib/transfer/transfer-header-actions.component.d.ts +39 -0
- package/src/lib/transfer/transfer-header-actions.component.js +79 -0
- package/src/lib/transfer/transfer-header-actions.component.js.map +1 -0
- package/src/lib/transfer/transfer-keys.d.ts +31 -0
- package/src/lib/transfer/transfer-keys.js +56 -0
- package/src/lib/transfer/transfer-keys.js.map +1 -0
- package/src/lib/model/commerce-io.d.ts +0 -36
- package/src/lib/model/commerce-io.js +0 -245
- package/src/lib/model/commerce-io.js.map +0 -1
- package/src/lib/server/contact-refund.d.ts +0 -117
- package/src/lib/server/contact-refund.js +0 -196
- package/src/lib/server/contact-refund.js.map +0 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { TransferResumeImport } from "@aglyn/aglyn/app-utils/transfer-resume-import";
|
|
20
|
+
import { useTransferLauncher } from "@aglyn/aglyn/app-utils/transfer-launcher-context";
|
|
21
|
+
import { Button, Stack } from "@mui/material";
|
|
22
|
+
import { COMMERCE_IMPORTABLE_TRANSFERS } from "./transfer-keys.js";
|
|
23
|
+
/**
|
|
24
|
+
* A commerce card's Import and Export, for its card header (AGL-3531): the
|
|
25
|
+
* console's import wizard and export dialog on the card's resource, through
|
|
26
|
+
* the core launcher. Each shows only when the launcher's `can` says the
|
|
27
|
+
* person may (AGL-3554) — their access on the site, the resource's
|
|
28
|
+
* `importRoles` (gift cards: a site's admins) and the workspace's plan —
|
|
29
|
+
* and Import only for a resource a file may write. Off the console shell
|
|
30
|
+
* there is no launcher, and nothing renders.
|
|
31
|
+
*/ export function TransferHeaderActions(props) {
|
|
32
|
+
const { resource, hostId, noun, onImported } = props;
|
|
33
|
+
const transfer = useTransferLauncher();
|
|
34
|
+
if (!transfer) return null;
|
|
35
|
+
const target = {
|
|
36
|
+
resource,
|
|
37
|
+
scope: 'host',
|
|
38
|
+
hostId
|
|
39
|
+
};
|
|
40
|
+
const canImport = COMMERCE_IMPORTABLE_TRANSFERS.has(resource) && transfer.can('import', target);
|
|
41
|
+
const canExport = transfer.can('export', target);
|
|
42
|
+
if (!canImport && !canExport) return null;
|
|
43
|
+
const suffix = noun ? ` ${noun}` : '';
|
|
44
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
45
|
+
direction: "row",
|
|
46
|
+
spacing: 1,
|
|
47
|
+
children: [
|
|
48
|
+
canImport ? /*#__PURE__*/ _jsx(TransferResumeImport, _extends({
|
|
49
|
+
target: target
|
|
50
|
+
}, onImported ? {
|
|
51
|
+
onFinished: onImported
|
|
52
|
+
} : {})) : null,
|
|
53
|
+
canImport ? /*#__PURE__*/ _jsx(Button, {
|
|
54
|
+
size: "small",
|
|
55
|
+
onClick: ()=>transfer.openImport(_extends({
|
|
56
|
+
resource,
|
|
57
|
+
scope: 'host',
|
|
58
|
+
hostId
|
|
59
|
+
}, onImported ? {
|
|
60
|
+
onFinished: onImported
|
|
61
|
+
} : {})),
|
|
62
|
+
children: `Import${suffix}`
|
|
63
|
+
}) : null,
|
|
64
|
+
canExport ? /*#__PURE__*/ _jsx(Button, {
|
|
65
|
+
size: "small",
|
|
66
|
+
onClick: ()=>transfer.openExport({
|
|
67
|
+
resource,
|
|
68
|
+
scope: 'host',
|
|
69
|
+
hostId
|
|
70
|
+
}),
|
|
71
|
+
children: `Export${suffix}`
|
|
72
|
+
}) : null
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
TransferHeaderActions.displayName = 'TransferHeaderActions';
|
|
77
|
+
export default TransferHeaderActions;
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=transfer-header-actions.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/transfer-header-actions.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { TransferResumeImport } from '@aglyn/aglyn/app-utils/transfer-resume-import'\nimport { useTransferLauncher } from '@aglyn/aglyn/app-utils/transfer-launcher-context'\nimport { Button, Stack } from '@mui/material'\nimport { COMMERCE_IMPORTABLE_TRANSFERS } from './transfer-keys'\n\nexport interface TransferHeaderActionsProps {\n /** The commerce resource key (`transfer-keys.ts`). */\n resource: string\n hostId: string\n /** What the buttons name, when the card holds more than this resource (\"categories\"). */\n noun?: string\n /** Called when the person leaves the import wizard from its results. */\n onImported?(): void\n}\n\n/**\n * A commerce card's Import and Export, for its card header (AGL-3531): the\n * console's import wizard and export dialog on the card's resource, through\n * the core launcher. Each shows only when the launcher's `can` says the\n * person may (AGL-3554) — their access on the site, the resource's\n * `importRoles` (gift cards: a site's admins) and the workspace's plan —\n * and Import only for a resource a file may write. Off the console shell\n * there is no launcher, and nothing renders.\n */\nexport function TransferHeaderActions(props: TransferHeaderActionsProps) {\n const { resource, hostId, noun, onImported } = props\n const transfer = useTransferLauncher()\n if (!transfer) return null\n const target = { resource, scope: 'host' as const, hostId }\n const canImport = COMMERCE_IMPORTABLE_TRANSFERS.has(resource) && transfer.can('import', target)\n const canExport = transfer.can('export', target)\n if (!canImport && !canExport) return null\n const suffix = noun ? ` ${noun}` : ''\n return (\n <Stack direction=\"row\" spacing={1}>\n {/* An import left unfinished, reopened where it stopped (AGL-3549). */}\n {canImport ? <TransferResumeImport target={target} {...(onImported ? { onFinished: onImported } : {})} /> : null}\n {canImport ? (\n <Button\n size=\"small\"\n onClick={() =>\n transfer.openImport({ resource, scope: 'host', hostId, ...(onImported ? { onFinished: onImported } : {}) })\n }\n >\n {`Import${suffix}`}\n </Button>\n ) : null}\n {canExport ? (\n <Button size=\"small\" onClick={() => transfer.openExport({ resource, scope: 'host', hostId })}>\n {`Export${suffix}`}\n </Button>\n ) : null}\n </Stack>\n )\n}\nTransferHeaderActions.displayName = 'TransferHeaderActions'\n\nexport default TransferHeaderActions\n"],"names":["TransferResumeImport","useTransferLauncher","Button","Stack","COMMERCE_IMPORTABLE_TRANSFERS","TransferHeaderActions","props","resource","hostId","noun","onImported","transfer","target","scope","canImport","has","can","canExport","suffix","direction","spacing","onFinished","size","onClick","openImport","openExport","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;AAEA,SAASA,oBAAoB,QAAQ,gDAA+C;AACpF,SAASC,mBAAmB,QAAQ,mDAAkD;AACtF,SAASC,MAAM,EAAEC,KAAK,QAAQ,gBAAe;AAC7C,SAASC,6BAA6B,QAAQ,qBAAiB;AAY/D;;;;;;;;CAQC,GACD,OAAO,SAASC,sBAAsBC,KAAiC;IACrE,MAAM,EAAEC,QAAQ,EAAEC,MAAM,EAAEC,IAAI,EAAEC,UAAU,EAAE,GAAGJ;IAC/C,MAAMK,WAAWV;IACjB,IAAI,CAACU,UAAU,OAAO;IACtB,MAAMC,SAAS;QAAEL;QAAUM,OAAO;QAAiBL;IAAO;IAC1D,MAAMM,YAAYV,8BAA8BW,GAAG,CAACR,aAAaI,SAASK,GAAG,CAAC,UAAUJ;IACxF,MAAMK,YAAYN,SAASK,GAAG,CAAC,UAAUJ;IACzC,IAAI,CAACE,aAAa,CAACG,WAAW,OAAO;IACrC,MAAMC,SAAST,OAAO,CAAC,CAAC,EAAEA,MAAM,GAAG;IACnC,qBACE,MAACN;QAAMgB,WAAU;QAAMC,SAAS;;YAE7BN,0BAAY,KAACd;gBAAqBY,QAAQA;eAAaF,aAAa;gBAAEW,YAAYX;YAAW,IAAI,CAAC,MAAS;YAC3GI,0BACC,KAACZ;gBACCoB,MAAK;gBACLC,SAAS,IACPZ,SAASa,UAAU,CAAC;wBAAEjB;wBAAUM,OAAO;wBAAQL;uBAAYE,aAAa;wBAAEW,YAAYX;oBAAW,IAAI,CAAC;0BAGvG,CAAC,MAAM,EAAEQ,QAAQ;iBAElB;YACHD,0BACC,KAACf;gBAAOoB,MAAK;gBAAQC,SAAS,IAAMZ,SAASc,UAAU,CAAC;wBAAElB;wBAAUM,OAAO;wBAAQL;oBAAO;0BACvF,CAAC,MAAM,EAAEU,QAAQ;iBAElB;;;AAGV;AACAb,sBAAsBqB,WAAW,GAAG;AAEpC,eAAerB,sBAAqB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
export declare const COMMERCE_PRODUCTS_TRANSFER = "commerce.products";
|
|
18
|
+
export declare const COMMERCE_ORDERS_TRANSFER = "commerce.orders";
|
|
19
|
+
export declare const COMMERCE_DISCOUNTS_TRANSFER = "commerce.discounts";
|
|
20
|
+
export declare const COMMERCE_COUPONS_TRANSFER = "commerce.coupons";
|
|
21
|
+
export declare const COMMERCE_GIFT_CARDS_TRANSFER = "commerce.gift-cards";
|
|
22
|
+
export declare const COMMERCE_CATEGORIES_TRANSFER = "commerce.categories";
|
|
23
|
+
/** Every commerce resource key, in the order the hub lists them. */
|
|
24
|
+
export declare const COMMERCE_TRANSFER_RESOURCES: readonly ["commerce.products", "commerce.categories", "commerce.orders", "commerce.discounts", "commerce.coupons", "commerce.gift-cards"];
|
|
25
|
+
/** The resources a file may write; the others are exported only. */
|
|
26
|
+
export declare const COMMERCE_IMPORTABLE_TRANSFERS: ReadonlySet<string>;
|
|
27
|
+
/**
|
|
28
|
+
* The gift card import's Confirm step (AGL-3551), and the key its answer
|
|
29
|
+
* rides under in the wizard's `extras`: the total value typed back.
|
|
30
|
+
*/
|
|
31
|
+
export declare const GIFT_CARD_CONFIRM_STEP_ID = "gift-card-total";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /*
|
|
17
|
+
* THE COMMERCE TRANSFER RESOURCES (AGL-3531), by the key each is declared
|
|
18
|
+
* under in `plugins.config.json` (`transferResources`). Every one is a site's
|
|
19
|
+
* records (`scope: host`); the console's lists open the import wizard and the
|
|
20
|
+
* export dialog on these keys through the core launcher.
|
|
21
|
+
*
|
|
22
|
+
* | key | import | why |
|
|
23
|
+
* | -- | -- | -- |
|
|
24
|
+
* | products | yes | matched by handle, then SKU, then ID; a match is updated |
|
|
25
|
+
* | categories | yes | matched by slug, then name, then ID |
|
|
26
|
+
* | discounts | yes | matched by code, then name, then ID; new ones start switched off |
|
|
27
|
+
* | coupons | yes | matched by code; new ones start switched off |
|
|
28
|
+
* | orders | no | an order is the record of a sale, written by checkout alone |
|
|
29
|
+
* | gift cards | yes, by issuing | each row issues a card through the issue path, confirmed by its total; owners and admins only |
|
|
30
|
+
*/ export const COMMERCE_PRODUCTS_TRANSFER = 'commerce.products';
|
|
31
|
+
export const COMMERCE_ORDERS_TRANSFER = 'commerce.orders';
|
|
32
|
+
export const COMMERCE_DISCOUNTS_TRANSFER = 'commerce.discounts';
|
|
33
|
+
export const COMMERCE_COUPONS_TRANSFER = 'commerce.coupons';
|
|
34
|
+
export const COMMERCE_GIFT_CARDS_TRANSFER = 'commerce.gift-cards';
|
|
35
|
+
export const COMMERCE_CATEGORIES_TRANSFER = 'commerce.categories';
|
|
36
|
+
/** Every commerce resource key, in the order the hub lists them. */ export const COMMERCE_TRANSFER_RESOURCES = [
|
|
37
|
+
COMMERCE_PRODUCTS_TRANSFER,
|
|
38
|
+
COMMERCE_CATEGORIES_TRANSFER,
|
|
39
|
+
COMMERCE_ORDERS_TRANSFER,
|
|
40
|
+
COMMERCE_DISCOUNTS_TRANSFER,
|
|
41
|
+
COMMERCE_COUPONS_TRANSFER,
|
|
42
|
+
COMMERCE_GIFT_CARDS_TRANSFER
|
|
43
|
+
];
|
|
44
|
+
/** The resources a file may write; the others are exported only. */ export const COMMERCE_IMPORTABLE_TRANSFERS = new Set([
|
|
45
|
+
COMMERCE_PRODUCTS_TRANSFER,
|
|
46
|
+
COMMERCE_CATEGORIES_TRANSFER,
|
|
47
|
+
COMMERCE_DISCOUNTS_TRANSFER,
|
|
48
|
+
COMMERCE_COUPONS_TRANSFER,
|
|
49
|
+
COMMERCE_GIFT_CARDS_TRANSFER
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* The gift card import's Confirm step (AGL-3551), and the key its answer
|
|
53
|
+
* rides under in the wizard's `extras`: the total value typed back.
|
|
54
|
+
*/ export const GIFT_CARD_CONFIRM_STEP_ID = 'gift-card-total';
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=transfer-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/transfer-keys.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * THE COMMERCE TRANSFER RESOURCES (AGL-3531), by the key each is declared\n * under in `plugins.config.json` (`transferResources`). Every one is a site's\n * records (`scope: host`); the console's lists open the import wizard and the\n * export dialog on these keys through the core launcher.\n *\n * | key | import | why |\n * | -- | -- | -- |\n * | products | yes | matched by handle, then SKU, then ID; a match is updated |\n * | categories | yes | matched by slug, then name, then ID |\n * | discounts | yes | matched by code, then name, then ID; new ones start switched off |\n * | coupons | yes | matched by code; new ones start switched off |\n * | orders | no | an order is the record of a sale, written by checkout alone |\n * | gift cards | yes, by issuing | each row issues a card through the issue path, confirmed by its total; owners and admins only |\n */\n\nexport const COMMERCE_PRODUCTS_TRANSFER = 'commerce.products'\nexport const COMMERCE_ORDERS_TRANSFER = 'commerce.orders'\nexport const COMMERCE_DISCOUNTS_TRANSFER = 'commerce.discounts'\nexport const COMMERCE_COUPONS_TRANSFER = 'commerce.coupons'\nexport const COMMERCE_GIFT_CARDS_TRANSFER = 'commerce.gift-cards'\nexport const COMMERCE_CATEGORIES_TRANSFER = 'commerce.categories'\n\n/** Every commerce resource key, in the order the hub lists them. */\nexport const COMMERCE_TRANSFER_RESOURCES = [\n COMMERCE_PRODUCTS_TRANSFER,\n COMMERCE_CATEGORIES_TRANSFER,\n COMMERCE_ORDERS_TRANSFER,\n COMMERCE_DISCOUNTS_TRANSFER,\n COMMERCE_COUPONS_TRANSFER,\n COMMERCE_GIFT_CARDS_TRANSFER,\n] as const\n\n/** The resources a file may write; the others are exported only. */\nexport const COMMERCE_IMPORTABLE_TRANSFERS: ReadonlySet<string> = new Set([\n COMMERCE_PRODUCTS_TRANSFER,\n COMMERCE_CATEGORIES_TRANSFER,\n COMMERCE_DISCOUNTS_TRANSFER,\n COMMERCE_COUPONS_TRANSFER,\n COMMERCE_GIFT_CARDS_TRANSFER,\n])\n\n/**\n * The gift card import's Confirm step (AGL-3551), and the key its answer\n * rides under in the wizard's `extras`: the total value typed back.\n */\nexport const GIFT_CARD_CONFIRM_STEP_ID = 'gift-card-total'\n"],"names":["COMMERCE_PRODUCTS_TRANSFER","COMMERCE_ORDERS_TRANSFER","COMMERCE_DISCOUNTS_TRANSFER","COMMERCE_COUPONS_TRANSFER","COMMERCE_GIFT_CARDS_TRANSFER","COMMERCE_CATEGORIES_TRANSFER","COMMERCE_TRANSFER_RESOURCES","COMMERCE_IMPORTABLE_TRANSFERS","Set","GIFT_CARD_CONFIRM_STEP_ID"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;CAcC,GAED,OAAO,MAAMA,6BAA6B,oBAAmB;AAC7D,OAAO,MAAMC,2BAA2B,kBAAiB;AACzD,OAAO,MAAMC,8BAA8B,qBAAoB;AAC/D,OAAO,MAAMC,4BAA4B,mBAAkB;AAC3D,OAAO,MAAMC,+BAA+B,sBAAqB;AACjE,OAAO,MAAMC,+BAA+B,sBAAqB;AAEjE,kEAAkE,GAClE,OAAO,MAAMC,8BAA8B;IACzCN;IACAK;IACAJ;IACAC;IACAC;IACAC;CACD,CAAS;AAEV,kEAAkE,GAClE,OAAO,MAAMG,gCAAqD,IAAIC,IAAI;IACxER;IACAK;IACAH;IACAC;IACAC;CACD,EAAC;AAEF;;;CAGC,GACD,OAAO,MAAMK,4BAA4B,kBAAiB"}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Catalog CSV import/export (AGL-282) speaking Shopify's product CSV
|
|
19
|
-
* dialect — the migration wedge for switchers: `Handle` groups variant
|
|
20
|
-
* rows into one product, `Option1..3 Name/Value` build the option axes,
|
|
21
|
-
* `Variant *` columns carry per-variant data, and repeated-handle rows
|
|
22
|
-
* with only `Image Src` add media. Export mirrors the same columns so
|
|
23
|
-
* round-trips are lossless for the fields both sides share.
|
|
24
|
-
*/
|
|
25
|
-
import { type HostProduct } from './commerce';
|
|
26
|
-
export declare const PRODUCT_CSV_HEADER: readonly ["Handle", "Title", "Body (HTML)", "Type", "Tags", "Published", "Option1 Name", "Option1 Value", "Option2 Name", "Option2 Value", "Option3 Name", "Option3 Value", "Variant SKU", "Variant Barcode", "Variant Price", "Variant Compare At Price", "Variant Inventory Qty", "Variant Grams", "Image Src"];
|
|
27
|
-
/** One row per variant; extra image rows follow Shopify's convention. */
|
|
28
|
-
export declare function productsToCsv(products: Array<HostProduct & {
|
|
29
|
-
$id?: string;
|
|
30
|
-
}>): string;
|
|
31
|
-
export interface ProductCsvImport {
|
|
32
|
-
products: HostProduct[];
|
|
33
|
-
/** Row-anchored problems; importable products still parse. */
|
|
34
|
-
errors: string[];
|
|
35
|
-
}
|
|
36
|
-
export declare function parseProductsCsv(text: string): ProductCsvImport;
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
-
/**
|
|
3
|
-
* @license
|
|
4
|
-
* Copyright 2026 Aglyn LLC
|
|
5
|
-
*
|
|
6
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
-
* you may not use this file except in compliance with the License.
|
|
8
|
-
* You may obtain a copy of the License at
|
|
9
|
-
*
|
|
10
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
-
*
|
|
12
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
-
* See the License for the specific language governing permissions and
|
|
16
|
-
* limitations under the License.
|
|
17
|
-
*/ /**
|
|
18
|
-
* Catalog CSV import/export (AGL-282) speaking Shopify's product CSV
|
|
19
|
-
* dialect — the migration wedge for switchers: `Handle` groups variant
|
|
20
|
-
* rows into one product, `Option1..3 Name/Value` build the option axes,
|
|
21
|
-
* `Variant *` columns carry per-variant data, and repeated-handle rows
|
|
22
|
-
* with only `Image Src` add media. Export mirrors the same columns so
|
|
23
|
-
* round-trips are lossless for the fields both sides share.
|
|
24
|
-
*/ import { commerceSlug, validateProduct } from "./commerce.js";
|
|
25
|
-
import { parseCsv } from "@aglyn/aglyn/app-utils/csv";
|
|
26
|
-
export const PRODUCT_CSV_HEADER = [
|
|
27
|
-
'Handle',
|
|
28
|
-
'Title',
|
|
29
|
-
'Body (HTML)',
|
|
30
|
-
'Type',
|
|
31
|
-
'Tags',
|
|
32
|
-
'Published',
|
|
33
|
-
'Option1 Name',
|
|
34
|
-
'Option1 Value',
|
|
35
|
-
'Option2 Name',
|
|
36
|
-
'Option2 Value',
|
|
37
|
-
'Option3 Name',
|
|
38
|
-
'Option3 Value',
|
|
39
|
-
'Variant SKU',
|
|
40
|
-
'Variant Barcode',
|
|
41
|
-
'Variant Price',
|
|
42
|
-
'Variant Compare At Price',
|
|
43
|
-
'Variant Inventory Qty',
|
|
44
|
-
'Variant Grams',
|
|
45
|
-
'Image Src'
|
|
46
|
-
];
|
|
47
|
-
function csvCell(value) {
|
|
48
|
-
const text = value == null ? '' : String(value);
|
|
49
|
-
return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
50
|
-
}
|
|
51
|
-
/** One row per variant; extra image rows follow Shopify's convention. */ export function productsToCsv(products) {
|
|
52
|
-
const lines = [
|
|
53
|
-
PRODUCT_CSV_HEADER.join(',')
|
|
54
|
-
];
|
|
55
|
-
for (const product of products){
|
|
56
|
-
var _product_options, _product_mediaUrls;
|
|
57
|
-
const options = (_product_options = product.options) != null ? _product_options : [];
|
|
58
|
-
product.variants.forEach((variant, index)=>{
|
|
59
|
-
var _product_description, _product_tags, _variant_sku, _variant_barcode, _variant_compareAtPriceUsd, _variant_inventory, _variant_weightGrams, _ref;
|
|
60
|
-
var _product_mediaUrls;
|
|
61
|
-
const optionCells = [];
|
|
62
|
-
for(let axis = 0; axis < 3; axis += 1){
|
|
63
|
-
var _ref1, _ref2;
|
|
64
|
-
var _variant_options;
|
|
65
|
-
const option = options[axis];
|
|
66
|
-
optionCells.push(index === 0 ? (_ref1 = option == null ? void 0 : option.name) != null ? _ref1 : '' : '', option ? (_ref2 = (_variant_options = variant.options) == null ? void 0 : _variant_options[option.name]) != null ? _ref2 : '' : '');
|
|
67
|
-
}
|
|
68
|
-
lines.push([
|
|
69
|
-
csvCell(product.slug),
|
|
70
|
-
csvCell(index === 0 ? product.name : ''),
|
|
71
|
-
csvCell(index === 0 ? (_product_description = product.description) != null ? _product_description : '' : ''),
|
|
72
|
-
csvCell(index === 0 ? product.type : ''),
|
|
73
|
-
csvCell(index === 0 ? ((_product_tags = product.tags) != null ? _product_tags : []).join(', ') : ''),
|
|
74
|
-
csvCell(index === 0 ? String(product.status === 'active') : ''),
|
|
75
|
-
...optionCells.map(csvCell),
|
|
76
|
-
csvCell((_variant_sku = variant.sku) != null ? _variant_sku : ''),
|
|
77
|
-
csvCell((_variant_barcode = variant.barcode) != null ? _variant_barcode : ''),
|
|
78
|
-
csvCell(variant.priceUsd),
|
|
79
|
-
csvCell((_variant_compareAtPriceUsd = variant.compareAtPriceUsd) != null ? _variant_compareAtPriceUsd : ''),
|
|
80
|
-
csvCell((_variant_inventory = variant.inventory) != null ? _variant_inventory : ''),
|
|
81
|
-
csvCell((_variant_weightGrams = variant.weightGrams) != null ? _variant_weightGrams : ''),
|
|
82
|
-
csvCell(index === 0 ? (_ref = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls[0]) != null ? _ref : '' : '')
|
|
83
|
-
].join(','));
|
|
84
|
-
});
|
|
85
|
-
for (const url of ((_product_mediaUrls = product.mediaUrls) != null ? _product_mediaUrls : []).slice(1)){
|
|
86
|
-
lines.push([
|
|
87
|
-
csvCell(product.slug),
|
|
88
|
-
...Array(PRODUCT_CSV_HEADER.length - 2).fill(''),
|
|
89
|
-
csvCell(url)
|
|
90
|
-
].join(','));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return lines.join('\n');
|
|
94
|
-
}
|
|
95
|
-
/** Case-insensitive column lookup so hand-edited headers still map. */ function headerIndex(header) {
|
|
96
|
-
const map = new Map();
|
|
97
|
-
header.forEach((cell, index)=>map.set(cell.trim().toLowerCase(), index));
|
|
98
|
-
return map;
|
|
99
|
-
}
|
|
100
|
-
export function parseProductsCsv(text) {
|
|
101
|
-
const rows = parseCsv(text);
|
|
102
|
-
if (rows.length < 2) {
|
|
103
|
-
return {
|
|
104
|
-
products: [],
|
|
105
|
-
errors: [
|
|
106
|
-
'No data rows found'
|
|
107
|
-
]
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const columns = headerIndex(rows[0]);
|
|
111
|
-
const cell = (row, name)=>{
|
|
112
|
-
var _row_index;
|
|
113
|
-
const index = columns.get(name.toLowerCase());
|
|
114
|
-
return index == null ? '' : ((_row_index = row[index]) != null ? _row_index : '').trim();
|
|
115
|
-
};
|
|
116
|
-
if (!columns.has('handle') || !columns.has('title')) {
|
|
117
|
-
return {
|
|
118
|
-
products: [],
|
|
119
|
-
errors: [
|
|
120
|
-
'Missing required columns: Handle, Title'
|
|
121
|
-
]
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const byHandle = new Map();
|
|
125
|
-
const errors = [];
|
|
126
|
-
rows.slice(1).forEach((row, rowIndex)=>{
|
|
127
|
-
var _product_mediaUrls;
|
|
128
|
-
const line = rowIndex + 2;
|
|
129
|
-
const handle = commerceSlug(cell(row, 'handle'));
|
|
130
|
-
if (!handle) {
|
|
131
|
-
if (row.some((value)=>value.trim())) {
|
|
132
|
-
errors.push(`Row ${line}: missing Handle`);
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
let product = byHandle.get(handle);
|
|
137
|
-
const title = cell(row, 'title');
|
|
138
|
-
if (!product) {
|
|
139
|
-
if (!title) {
|
|
140
|
-
// Image-only continuation row for a product we never saw.
|
|
141
|
-
errors.push(`Row ${line}: first row for "${handle}" needs a Title`);
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const published = cell(row, 'published').toLowerCase();
|
|
145
|
-
const type = cell(row, 'type').toLowerCase();
|
|
146
|
-
product = {
|
|
147
|
-
name: title,
|
|
148
|
-
slug: handle,
|
|
149
|
-
description: cell(row, 'body (html)') || undefined,
|
|
150
|
-
type: [
|
|
151
|
-
'physical',
|
|
152
|
-
'digital',
|
|
153
|
-
'service'
|
|
154
|
-
].includes(type) ? type : 'physical',
|
|
155
|
-
status: published === 'false' ? 'draft' : 'active',
|
|
156
|
-
tags: cell(row, 'tags') ? cell(row, 'tags').split(',').map((tag)=>tag.trim()).filter(Boolean) : undefined,
|
|
157
|
-
options: [],
|
|
158
|
-
variants: [],
|
|
159
|
-
mediaUrls: []
|
|
160
|
-
};
|
|
161
|
-
byHandle.set(handle, product);
|
|
162
|
-
}
|
|
163
|
-
const imageSrc = cell(row, 'image src');
|
|
164
|
-
if (imageSrc && !((_product_mediaUrls = product.mediaUrls) != null ? _product_mediaUrls : []).includes(imageSrc)) {
|
|
165
|
-
var _product_mediaUrls1;
|
|
166
|
-
product.mediaUrls = [
|
|
167
|
-
...(_product_mediaUrls1 = product.mediaUrls) != null ? _product_mediaUrls1 : [],
|
|
168
|
-
imageSrc
|
|
169
|
-
];
|
|
170
|
-
}
|
|
171
|
-
const priceRaw = cell(row, 'variant price');
|
|
172
|
-
const hasVariant = priceRaw !== '' || cell(row, 'variant sku') !== '';
|
|
173
|
-
if (!hasVariant) return;
|
|
174
|
-
const options = {};
|
|
175
|
-
for(let axis = 1; axis <= 3; axis += 1){
|
|
176
|
-
var _product_options_;
|
|
177
|
-
const name = cell(row, `option${axis} name`);
|
|
178
|
-
const value = cell(row, `option${axis} value`);
|
|
179
|
-
if (name && product.options[axis - 1] == null) {
|
|
180
|
-
product.options[axis - 1] = {
|
|
181
|
-
name,
|
|
182
|
-
values: []
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
const optionName = (_product_options_ = product.options[axis - 1]) == null ? void 0 : _product_options_.name;
|
|
186
|
-
if (optionName && value) {
|
|
187
|
-
options[optionName] = value;
|
|
188
|
-
const axisOption = product.options[axis - 1];
|
|
189
|
-
if (!axisOption.values.includes(value)) {
|
|
190
|
-
axisOption.values.push(value);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
const price = Number(priceRaw);
|
|
195
|
-
if (!Number.isFinite(price) || price < 0) {
|
|
196
|
-
errors.push(`Row ${line}: invalid Variant Price "${priceRaw}"`);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const qtyRaw = cell(row, 'variant inventory qty');
|
|
200
|
-
const compareRaw = cell(row, 'variant compare at price');
|
|
201
|
-
const gramsRaw = cell(row, 'variant grams');
|
|
202
|
-
const variant = _extends({
|
|
203
|
-
id: `v${product.variants.length + 1}`
|
|
204
|
-
}, Object.keys(options).length ? {
|
|
205
|
-
options
|
|
206
|
-
} : {}, {
|
|
207
|
-
priceUsd: price
|
|
208
|
-
}, cell(row, 'variant sku') ? {
|
|
209
|
-
sku: cell(row, 'variant sku')
|
|
210
|
-
} : {}, cell(row, 'variant barcode') ? {
|
|
211
|
-
barcode: cell(row, 'variant barcode')
|
|
212
|
-
} : {}, compareRaw && Number.isFinite(Number(compareRaw)) ? {
|
|
213
|
-
compareAtPriceUsd: Number(compareRaw)
|
|
214
|
-
} : {}, {
|
|
215
|
-
inventory: qtyRaw === '' ? null : Math.max(0, Math.round(Number(qtyRaw)))
|
|
216
|
-
}, gramsRaw && Number.isFinite(Number(gramsRaw)) ? {
|
|
217
|
-
weightGrams: Number(gramsRaw)
|
|
218
|
-
} : {});
|
|
219
|
-
product.variants.push(variant);
|
|
220
|
-
});
|
|
221
|
-
const products = [];
|
|
222
|
-
for (const [handle, product] of byHandle){
|
|
223
|
-
var _product_options, _ref;
|
|
224
|
-
var _product_mediaUrls;
|
|
225
|
-
product.options = ((_product_options = product.options) != null ? _product_options : []).filter(Boolean);
|
|
226
|
-
if (product.options.length === 0) delete product.options;
|
|
227
|
-
if (((_ref = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls.length) != null ? _ref : 0) === 0) delete product.mediaUrls;
|
|
228
|
-
if (product.variants.length === 0) {
|
|
229
|
-
errors.push(`"${handle}": no variant rows (needs Variant Price)`);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
const problem = validateProduct(product);
|
|
233
|
-
if (problem) {
|
|
234
|
-
errors.push(`"${handle}": ${problem}`);
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
products.push(product);
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
products,
|
|
241
|
-
errors
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
//# sourceMappingURL=commerce-io.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-io.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Catalog CSV import/export (AGL-282) speaking Shopify's product CSV\n * dialect — the migration wedge for switchers: `Handle` groups variant\n * rows into one product, `Option1..3 Name/Value` build the option axes,\n * `Variant *` columns carry per-variant data, and repeated-handle rows\n * with only `Image Src` add media. Export mirrors the same columns so\n * round-trips are lossless for the fields both sides share.\n */\n\nimport {\n commerceSlug,\n validateProduct,\n type HostProduct,\n type ProductOption,\n type ProductStatus,\n type ProductVariant,\n} from './commerce'\nimport { parseCsv } from '@aglyn/aglyn/app-utils/csv'\n\nexport const PRODUCT_CSV_HEADER = [\n 'Handle',\n 'Title',\n 'Body (HTML)',\n 'Type',\n 'Tags',\n 'Published',\n 'Option1 Name',\n 'Option1 Value',\n 'Option2 Name',\n 'Option2 Value',\n 'Option3 Name',\n 'Option3 Value',\n 'Variant SKU',\n 'Variant Barcode',\n 'Variant Price',\n 'Variant Compare At Price',\n 'Variant Inventory Qty',\n 'Variant Grams',\n 'Image Src',\n] as const\n\nfunction csvCell(value: unknown): string {\n const text = value == null ? '' : String(value)\n return /[\",\\n]/.test(text) ? `\"${text.replace(/\"/g, '\"\"')}\"` : text\n}\n\n/** One row per variant; extra image rows follow Shopify's convention. */\nexport function productsToCsv(\n products: Array<HostProduct & { $id?: string }>,\n): string {\n const lines = [PRODUCT_CSV_HEADER.join(',')]\n for (const product of products) {\n const options = product.options ?? []\n product.variants.forEach((variant, index) => {\n const optionCells: string[] = []\n for (let axis = 0; axis < 3; axis += 1) {\n const option = options[axis]\n optionCells.push(\n index === 0 ? (option?.name ?? '') : '',\n option ? (variant.options?.[option.name] ?? '') : '',\n )\n }\n lines.push(\n [\n csvCell(product.slug),\n csvCell(index === 0 ? product.name : ''),\n csvCell(index === 0 ? (product.description ?? '') : ''),\n csvCell(index === 0 ? product.type : ''),\n csvCell(index === 0 ? (product.tags ?? []).join(', ') : ''),\n csvCell(index === 0 ? String(product.status === 'active') : ''),\n ...optionCells.map(csvCell),\n csvCell(variant.sku ?? ''),\n csvCell(variant.barcode ?? ''),\n csvCell(variant.priceUsd),\n csvCell(variant.compareAtPriceUsd ?? ''),\n csvCell(variant.inventory ?? ''),\n csvCell(variant.weightGrams ?? ''),\n csvCell(index === 0 ? (product.mediaUrls?.[0] ?? '') : ''),\n ].join(','),\n )\n })\n for (const url of (product.mediaUrls ?? []).slice(1)) {\n lines.push(\n [\n csvCell(product.slug),\n ...Array(PRODUCT_CSV_HEADER.length - 2).fill(''),\n csvCell(url),\n ].join(','),\n )\n }\n }\n return lines.join('\\n')\n}\n\nexport interface ProductCsvImport {\n products: HostProduct[]\n /** Row-anchored problems; importable products still parse. */\n errors: string[]\n}\n\n/** Case-insensitive column lookup so hand-edited headers still map. */\nfunction headerIndex(header: string[]): Map<string, number> {\n const map = new Map<string, number>()\n header.forEach((cell, index) =>\n map.set(cell.trim().toLowerCase(), index),\n )\n return map\n}\n\nexport function parseProductsCsv(text: string): ProductCsvImport {\n const rows = parseCsv(text)\n if (rows.length < 2) {\n return { products: [], errors: ['No data rows found'] }\n }\n const columns = headerIndex(rows[0])\n const cell = (row: string[], name: string): string => {\n const index = columns.get(name.toLowerCase())\n return index == null ? '' : (row[index] ?? '').trim()\n }\n if (!columns.has('handle') || !columns.has('title')) {\n return {\n products: [],\n errors: ['Missing required columns: Handle, Title'],\n }\n }\n\n const byHandle = new Map<string, HostProduct>()\n const errors: string[] = []\n rows.slice(1).forEach((row, rowIndex) => {\n const line = rowIndex + 2\n const handle = commerceSlug(cell(row, 'handle'))\n if (!handle) {\n if (row.some((value) => value.trim())) {\n errors.push(`Row ${line}: missing Handle`)\n }\n return\n }\n let product = byHandle.get(handle)\n const title = cell(row, 'title')\n if (!product) {\n if (!title) {\n // Image-only continuation row for a product we never saw.\n errors.push(`Row ${line}: first row for \"${handle}\" needs a Title`)\n return\n }\n const published = cell(row, 'published').toLowerCase()\n const type = cell(row, 'type').toLowerCase()\n product = {\n name: title,\n slug: handle,\n description: cell(row, 'body (html)') || undefined,\n type: ['physical', 'digital', 'service'].includes(type)\n ? (type as HostProduct['type'])\n : 'physical',\n status: (published === 'false'\n ? 'draft'\n : 'active') as ProductStatus,\n tags: cell(row, 'tags')\n ? cell(row, 'tags')\n .split(',')\n .map((tag) => tag.trim())\n .filter(Boolean)\n : undefined,\n options: [],\n variants: [],\n mediaUrls: [],\n }\n byHandle.set(handle, product)\n }\n\n const imageSrc = cell(row, 'image src')\n if (imageSrc && !(product.mediaUrls ?? []).includes(imageSrc)) {\n product.mediaUrls = [...(product.mediaUrls ?? []), imageSrc]\n }\n\n const priceRaw = cell(row, 'variant price')\n const hasVariant = priceRaw !== '' || cell(row, 'variant sku') !== ''\n if (!hasVariant) return\n\n const options: Record<string, string> = {}\n for (let axis = 1; axis <= 3; axis += 1) {\n const name = cell(row, `option${axis} name`)\n const value = cell(row, `option${axis} value`)\n if (name && product.options![axis - 1] == null) {\n product.options![axis - 1] = { name, values: [] }\n }\n const optionName = product.options![axis - 1]?.name\n if (optionName && value) {\n options[optionName] = value\n const axisOption = product.options![axis - 1] as ProductOption\n if (!axisOption.values.includes(value)) {\n axisOption.values.push(value)\n }\n }\n }\n\n const price = Number(priceRaw)\n if (!Number.isFinite(price) || price < 0) {\n errors.push(`Row ${line}: invalid Variant Price \"${priceRaw}\"`)\n return\n }\n const qtyRaw = cell(row, 'variant inventory qty')\n const compareRaw = cell(row, 'variant compare at price')\n const gramsRaw = cell(row, 'variant grams')\n const variant: ProductVariant = {\n id: `v${product.variants.length + 1}`,\n ...(Object.keys(options).length ? { options } : {}),\n priceUsd: price,\n ...(cell(row, 'variant sku') ? { sku: cell(row, 'variant sku') } : {}),\n ...(cell(row, 'variant barcode')\n ? { barcode: cell(row, 'variant barcode') }\n : {}),\n ...(compareRaw && Number.isFinite(Number(compareRaw))\n ? { compareAtPriceUsd: Number(compareRaw) }\n : {}),\n inventory: qtyRaw === '' ? null : Math.max(0, Math.round(Number(qtyRaw))),\n ...(gramsRaw && Number.isFinite(Number(gramsRaw))\n ? { weightGrams: Number(gramsRaw) }\n : {}),\n }\n product.variants.push(variant)\n })\n\n const products: HostProduct[] = []\n for (const [handle, product] of byHandle) {\n product.options = (product.options ?? []).filter(Boolean)\n if (product.options.length === 0) delete product.options\n if ((product.mediaUrls?.length ?? 0) === 0) delete product.mediaUrls\n if (product.variants.length === 0) {\n errors.push(`\"${handle}\": no variant rows (needs Variant Price)`)\n continue\n }\n const problem = validateProduct(product)\n if (problem) {\n errors.push(`\"${handle}\": ${problem}`)\n continue\n }\n products.push(product)\n }\n return { products, errors }\n}\n"],"names":["commerceSlug","validateProduct","parseCsv","PRODUCT_CSV_HEADER","csvCell","value","text","String","test","replace","productsToCsv","products","lines","join","product","options","variants","forEach","variant","index","optionCells","axis","option","push","name","slug","description","type","tags","status","map","sku","barcode","priceUsd","compareAtPriceUsd","inventory","weightGrams","mediaUrls","url","slice","Array","length","fill","headerIndex","header","Map","cell","set","trim","toLowerCase","parseProductsCsv","rows","errors","columns","row","get","has","byHandle","rowIndex","line","handle","some","title","published","undefined","includes","split","tag","filter","Boolean","imageSrc","priceRaw","hasVariant","values","optionName","axisOption","price","Number","isFinite","qtyRaw","compareRaw","gramsRaw","id","Object","keys","Math","max","round","problem"],"mappings":"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"}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Aglyn LLC
|
|
4
|
-
*
|
|
5
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
-
* you may not use this file except in compliance with the License.
|
|
7
|
-
* You may obtain a copy of the License at
|
|
8
|
-
*
|
|
9
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
-
*
|
|
11
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
-
* See the License for the specific language governing permissions and
|
|
15
|
-
* limitations under the License.
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Money going BACK to a customer, recorded on their contact (AGL-1754).
|
|
19
|
-
*
|
|
20
|
-
* `upsertHostContact` accumulates `ltvCents` and nothing anywhere decremented
|
|
21
|
-
* it, so a customer who bought $500 and returned all of it read identically to
|
|
22
|
-
* one who bought $500 and kept it — and the first surface built on the field
|
|
23
|
-
* (RFM segmentation, a lifetime-value column, a best-customers list) would have
|
|
24
|
-
* ranked a serial returner among the merchant's best customers.
|
|
25
|
-
*
|
|
26
|
-
* WHAT THIS WRITES, AND WHY IT IS NOT A DECREMENT
|
|
27
|
-
*
|
|
28
|
-
* `ltvCents` is left exactly as it is and the reversal is recorded BESIDE it:
|
|
29
|
-
*
|
|
30
|
-
* | field | meaning |
|
|
31
|
-
* | -- | -- |
|
|
32
|
-
* | `ltvCents` | gross money handed over, unchanged (AGL-1748) |
|
|
33
|
-
* | `refundedCents` | gross money handed back |
|
|
34
|
-
* | `ordersCount` | orders placed, unchanged |
|
|
35
|
-
* | `refundedOrdersCount` | of those, how many ended fully reversed |
|
|
36
|
-
* | `lastRefundAtMs` | when money last went back |
|
|
37
|
-
*
|
|
38
|
-
* This is the shape AGL-1747 chose two days earlier for the same question on
|
|
39
|
-
* the orders CSV — `amountUsd` stayed gross under its existing name and
|
|
40
|
-
* `refundedUsd`/`netUsd` were appended — and it is the shape AGL-1748 named
|
|
41
|
-
* when it recorded the gross semantics in `upsert-contact.ts`. Netting in
|
|
42
|
-
* place would make a stored number mean one thing for rows written before this
|
|
43
|
-
* commit and another for rows written after, with nothing on the document to
|
|
44
|
-
* tell the two apart. Two fields cost one number's worth of storage and keep
|
|
45
|
-
* both facts, and a reader that wants the net has `ltvCents - refundedCents`.
|
|
46
|
-
*
|
|
47
|
-
* It also answers "can it go negative?" by moving the question off storage.
|
|
48
|
-
* Both figures are monotonic counters of money that actually moved in one
|
|
49
|
-
* direction, so neither can go negative. The DERIVED net can, for exactly one
|
|
50
|
-
* reason: an order placed before AGL-1748 taught POS, drafts, reservations and
|
|
51
|
-
* bookings to pass `purchaseCents` at all, whose sale was never added and whose
|
|
52
|
-
* refund is subtracted here. That is a true statement about the data, and
|
|
53
|
-
* clamping it away at write time would erase the only evidence that a purchase
|
|
54
|
-
* is missing. AGL-1753 is the backfill that reconciles it, by SETTING both
|
|
55
|
-
* fields from the orders — which already carry `refundedCents`, so whatever a
|
|
56
|
-
* reader decides here costs the rebuild nothing. A merchant-facing reader
|
|
57
|
-
* should show gross and refunded, and clamp only the net it ranks on.
|
|
58
|
-
*
|
|
59
|
-
* `ordersCount` is deliberately NOT decremented. A fully refunded order still
|
|
60
|
-
* happened, and a decrement cannot be undone by a reader that would rather
|
|
61
|
-
* count it — while `refundedOrdersCount` beside it lets RFM's frequency term
|
|
62
|
-
* net the two, or not, at the point of display. Same argument as the amount,
|
|
63
|
-
* one field along.
|
|
64
|
-
*
|
|
65
|
-
* WHEN THE CONTACT IS MISSING, THIS REFUSES — and records the refusal.
|
|
66
|
-
*
|
|
67
|
-
* Refusing is right on AGL-1732's test, "does refusing discard money or work
|
|
68
|
-
* that already happened?", because it does not: the refund is durable on the
|
|
69
|
-
* order (`refundedCents`, the timeline entry, Stripe), and the contact is a
|
|
70
|
-
* derived aggregate that AGL-1753's rebuild reads back out of those same
|
|
71
|
-
* orders. Creating one here would be worse three ways — creation is audience
|
|
72
|
-
* BAND gated, so it hard-bands a Free org (raising the AGL-891 dropped-contacts
|
|
73
|
-
* alert for a record the merchant never had) or meters onto a paid invoice; the
|
|
74
|
-
* contact it created would hold a refund and no purchase, which is the negative
|
|
75
|
-
* lifetime value above with nothing to reconcile it against; and it is the
|
|
76
|
-
* phantom-document shape the `updateExisting` sweep spent yesterday closing.
|
|
77
|
-
*
|
|
78
|
-
* So the write is an `update()`, never `set(…, { merge: true })` — the contact
|
|
79
|
-
* may have been deleted between the query and the write, and a merge-set would
|
|
80
|
-
* resurrect it as a document holding nothing but a refund. Every refusal
|
|
81
|
-
* increments `hosts/{hostId}/counters/contactRefundsUnmatched`, the counter
|
|
82
|
-
* shape `contactsDropped` already uses, so a refund that reached no contact is
|
|
83
|
-
* countable rather than silent.
|
|
84
|
-
*
|
|
85
|
-
* CHARGEBACKS (AGL-1554) are a separate decision and are NOT handled here.
|
|
86
|
-
* This takes `kind` so the handler that eventually answers
|
|
87
|
-
* `charge.dispute.closed` can record a lost dispute through the same writer
|
|
88
|
-
* with the same fields: money reversed is money reversed, whichever door it
|
|
89
|
-
* left by, and only the wording of the timeline entry differs.
|
|
90
|
-
*/
|
|
91
|
-
export declare function recordContactRefund(options: {
|
|
92
|
-
hostId: string;
|
|
93
|
-
/** The order the money went back on — the contact timeline's `refId`. */
|
|
94
|
-
orderId: string;
|
|
95
|
-
/**
|
|
96
|
-
* The buyer as the ORDER recorded them, raw. Normalized here, because that
|
|
97
|
-
* is the key the contacts collection is stored under and orders are not
|
|
98
|
-
* consistent about it (AGL-1753 item 3). No `name` rides along on purpose: a
|
|
99
|
-
* refund is not a capture point and must not rewrite who the contact is.
|
|
100
|
-
*/
|
|
101
|
-
email: unknown;
|
|
102
|
-
/**
|
|
103
|
-
* Cents reversed by THIS attempt, never the order total. `refund.ts` caps
|
|
104
|
-
* each attempt against what is left, so several partials on one order sum to
|
|
105
|
-
* at most the order total; this follows that number rather than recomputing
|
|
106
|
-
* it.
|
|
107
|
-
*/
|
|
108
|
-
amountCents: number;
|
|
109
|
-
/**
|
|
110
|
-
* True only for the write that moved the order INTO `refunded`. Two partials
|
|
111
|
-
* settling concurrently can both observe a fully refunded total, so the
|
|
112
|
-
* caller decides this from the status transition it made, not from the
|
|
113
|
-
* total it read.
|
|
114
|
-
*/
|
|
115
|
-
closedTheOrder: boolean;
|
|
116
|
-
kind?: 'refund' | 'chargeback';
|
|
117
|
-
}): Promise<void>;
|