@empty-complete-org/medusa-product-attributes 0.14.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +441 -202
- package/.medusa/server/src/admin/index.mjs +442 -203
- package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/[id]/apply/route.js +1 -1
- package/.medusa/server/src/api/admin/attribute-templates/[id]/apply/route.js.map +1 -0
- package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/route.js +6 -6
- package/.medusa/server/src/api/admin/attribute-templates/route.js.map +1 -0
- package/.medusa/server/src/api/admin/category/[categoryId]/custom-attributes/route.js +1 -1
- package/.medusa/server/src/api/admin/category/[categoryId]/custom-attributes/route.js.map +1 -1
- package/.medusa/server/src/api/admin/global-attributes/route.d.ts +4 -0
- package/.medusa/server/src/api/admin/global-attributes/route.js +30 -0
- package/.medusa/server/src/api/admin/global-attributes/route.js.map +1 -0
- package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.d.ts +2 -0
- package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.js +34 -0
- package/.medusa/server/src/api/admin/product/[productId]/attribute-schema/route.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/index.d.ts +5 -5
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.d.ts +5 -0
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.js +58 -0
- package/.medusa/server/src/modules/product-attributes/migrations/Migration20260407120000.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/models/{attribute-preset.d.ts → attribute-template.d.ts} +3 -3
- package/.medusa/server/src/modules/product-attributes/models/{attribute-preset.js → attribute-template.js} +3 -3
- package/.medusa/server/src/modules/product-attributes/models/attribute-template.js.map +1 -0
- package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.d.ts +2 -1
- package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.js +2 -1
- package/.medusa/server/src/modules/product-attributes/models/category-custom-attribute.js.map +1 -1
- package/.medusa/server/src/modules/product-attributes/models/product-custom-attribute.d.ts +2 -1
- package/.medusa/server/src/modules/product-attributes/service.d.ts +56 -17
- package/.medusa/server/src/modules/product-attributes/service.js +44 -18
- package/.medusa/server/src/modules/product-attributes/service.js.map +1 -1
- package/README.md +95 -312
- package/package.json +1 -1
- package/.medusa/server/src/api/admin/attribute-presets/[id]/apply/route.js.map +0 -1
- package/.medusa/server/src/api/admin/attribute-presets/route.js.map +0 -1
- package/.medusa/server/src/modules/product-attributes/models/attribute-preset.js.map +0 -1
- /package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/[id]/apply/route.d.ts +0 -0
- /package/.medusa/server/src/api/admin/{attribute-presets → attribute-templates}/route.d.ts +0 -0
|
@@ -7,11 +7,11 @@ const utils_1 = require("@medusajs/framework/utils");
|
|
|
7
7
|
const transliteration_1 = require("transliteration");
|
|
8
8
|
const category_custom_attribute_1 = __importDefault(require("./models/category-custom-attribute"));
|
|
9
9
|
const product_custom_attribute_1 = __importDefault(require("./models/product-custom-attribute"));
|
|
10
|
-
const
|
|
10
|
+
const attribute_template_1 = __importDefault(require("./models/attribute-template"));
|
|
11
11
|
const models = {
|
|
12
12
|
CategoryCustomAttribute: category_custom_attribute_1.default,
|
|
13
13
|
ProductCustomAttribute: product_custom_attribute_1.default,
|
|
14
|
-
|
|
14
|
+
AttributeTemplate: attribute_template_1.default,
|
|
15
15
|
};
|
|
16
16
|
// @ts-ignore - MedusaService dynamically generates methods based on models
|
|
17
17
|
class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
@@ -20,25 +20,42 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
|
20
20
|
return await this.listCategoryCustomAttributes({
|
|
21
21
|
category_id: categoryId,
|
|
22
22
|
deleted_at: null,
|
|
23
|
+
is_global: false,
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
|
-
async getAttributesByCategoryIds(categoryIds) {
|
|
26
|
+
async getAttributesByCategoryIds(categoryIds, opts = { includeGlobals: true }) {
|
|
27
|
+
// Always include global attributes (applies to every product) unless disabled
|
|
28
|
+
let globalsMapped = [];
|
|
29
|
+
if (opts.includeGlobals) {
|
|
30
|
+
// @ts-ignore - method generated by MedusaService
|
|
31
|
+
const globals = await this.listCategoryCustomAttributes({
|
|
32
|
+
is_global: true,
|
|
33
|
+
deleted_at: null,
|
|
34
|
+
});
|
|
35
|
+
globalsMapped = globals.map((attr) => ({
|
|
36
|
+
...attr,
|
|
37
|
+
inherited: true,
|
|
38
|
+
source_category_id: null,
|
|
39
|
+
source: "global",
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
26
42
|
if (!categoryIds || categoryIds.length === 0) {
|
|
27
|
-
return
|
|
43
|
+
return globalsMapped;
|
|
28
44
|
}
|
|
29
45
|
// @ts-ignore - method generated by MedusaService
|
|
30
46
|
const attributes = await this.listCategoryCustomAttributes({
|
|
31
47
|
category_id: categoryIds,
|
|
48
|
+
is_global: false,
|
|
32
49
|
deleted_at: null,
|
|
33
50
|
});
|
|
34
|
-
// First id in the list is the leaf category — its attributes are "own",
|
|
35
|
-
// everything else is inherited from an ancestor.
|
|
36
51
|
const leafId = categoryIds[0];
|
|
37
|
-
|
|
52
|
+
const categoryMapped = attributes.map((attr) => ({
|
|
38
53
|
...attr,
|
|
39
54
|
inherited: attr.category_id !== leafId,
|
|
40
55
|
source_category_id: attr.category_id,
|
|
56
|
+
source: "category",
|
|
41
57
|
}));
|
|
58
|
+
return [...globalsMapped, ...categoryMapped];
|
|
42
59
|
}
|
|
43
60
|
async createCategoryAttribute(data) {
|
|
44
61
|
const key = this.generateKey(data.label);
|
|
@@ -47,7 +64,9 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
|
47
64
|
...data,
|
|
48
65
|
key,
|
|
49
66
|
unit: data.unit ?? null,
|
|
67
|
+
category_id: data.category_id ?? null,
|
|
50
68
|
is_standard: data.is_standard ?? false,
|
|
69
|
+
is_global: data.is_global ?? false,
|
|
51
70
|
sort_order: data.sort_order ?? 0,
|
|
52
71
|
});
|
|
53
72
|
}
|
|
@@ -59,6 +78,13 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
|
59
78
|
// @ts-ignore - method generated by MedusaService
|
|
60
79
|
return await this.updateCategoryCustomAttributes([{ id, ...updateData }]);
|
|
61
80
|
}
|
|
81
|
+
async listGlobalAttributes() {
|
|
82
|
+
// @ts-ignore - generated
|
|
83
|
+
return await this.listCategoryCustomAttributes({
|
|
84
|
+
is_global: true,
|
|
85
|
+
deleted_at: null,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
62
88
|
async createProductAttribute(data) {
|
|
63
89
|
// @ts-ignore - method generated by MedusaService
|
|
64
90
|
return await this.createProductCustomAttributes(data);
|
|
@@ -76,14 +102,14 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
|
76
102
|
relations: ["category_custom_attribute"],
|
|
77
103
|
});
|
|
78
104
|
}
|
|
79
|
-
async
|
|
105
|
+
async listTemplates() {
|
|
80
106
|
// @ts-ignore - generated
|
|
81
|
-
return await this.
|
|
107
|
+
return await this.listAttributeTemplates({ deleted_at: null });
|
|
82
108
|
}
|
|
83
|
-
async
|
|
109
|
+
async createTemplate(data) {
|
|
84
110
|
const key = this.generateKey(data.label);
|
|
85
111
|
// @ts-ignore - generated
|
|
86
|
-
return await this.
|
|
112
|
+
return await this.createAttributeTemplates({
|
|
87
113
|
label: data.label,
|
|
88
114
|
key,
|
|
89
115
|
type: data.type || "text",
|
|
@@ -91,21 +117,21 @@ class CustomAttributeService extends (0, utils_1.MedusaService)(models) {
|
|
|
91
117
|
description: data.description ?? null,
|
|
92
118
|
});
|
|
93
119
|
}
|
|
94
|
-
async
|
|
120
|
+
async updateTemplate(id, data) {
|
|
95
121
|
const updateData = { ...data };
|
|
96
122
|
if (data.label) {
|
|
97
123
|
updateData.key = this.generateKey(data.label);
|
|
98
124
|
}
|
|
99
125
|
// @ts-ignore - generated
|
|
100
|
-
return await this.
|
|
126
|
+
return await this.updateAttributeTemplates([{ id, ...updateData }]);
|
|
101
127
|
}
|
|
102
|
-
async
|
|
128
|
+
async applyTemplateToCategory(templateId, categoryId) {
|
|
103
129
|
// @ts-ignore - generated
|
|
104
|
-
const
|
|
130
|
+
const template = await this.retrieveAttributeTemplate(templateId);
|
|
105
131
|
return await this.createCategoryAttribute({
|
|
106
|
-
label:
|
|
107
|
-
type:
|
|
108
|
-
unit:
|
|
132
|
+
label: template.label,
|
|
133
|
+
type: template.type,
|
|
134
|
+
unit: template.unit,
|
|
109
135
|
category_id: categoryId,
|
|
110
136
|
is_standard: true,
|
|
111
137
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../../src/modules/product-attributes/service.ts"],"names":[],"mappings":";;;;;AAAA,qDAAyD;AACzD,qDAAyC;AACzC,mGAAwE;AACxE,iGAAsE;AACtE,
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../../../../src/modules/product-attributes/service.ts"],"names":[],"mappings":"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"}
|
package/README.md
CHANGED
|
@@ -1,367 +1,150 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="
|
|
3
|
-
<h1>Product Attributes
|
|
4
|
-
<p><i>
|
|
5
|
-
|
|
2
|
+
<img src="./assets/logo.png" alt="medusa-product-attributes logo" width="120">
|
|
3
|
+
<h1>Medusa Product Attributes</h1>
|
|
4
|
+
<p><i>A flexible attribute system for Medusa v2 — text, number, file, boolean, with units and category inheritance</i></p>
|
|
5
|
+
|
|
6
6
|
<p>
|
|
7
7
|
<a href="https://github.com/empty-complete/medusa-product-attributes/blob/main/LICENSE">
|
|
8
|
-
<img src="https://img.shields.io/
|
|
8
|
+
<img src="https://img.shields.io/npm/l/@empty-complete-org/medusa-product-attributes" alt="License">
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/@empty-complete-org/medusa-product-attributes">
|
|
11
|
+
<img src="https://img.shields.io/npm/v/@empty-complete-org/medusa-product-attributes" alt="npm version">
|
|
9
12
|
</a>
|
|
10
|
-
<a href="https://www.npmjs.com/package/@empty-complete/medusa-product-attributes">
|
|
11
|
-
<img src="https://img.shields.io/npm/
|
|
13
|
+
<a href="https://www.npmjs.com/package/@empty-complete-org/medusa-product-attributes">
|
|
14
|
+
<img src="https://img.shields.io/npm/dm/@empty-complete-org/medusa-product-attributes" alt="npm downloads">
|
|
12
15
|
</a>
|
|
13
16
|
<a href="https://github.com/empty-complete/medusa-product-attributes/actions/workflows/ci.yml">
|
|
14
17
|
<img src="https://github.com/empty-complete/medusa-product-attributes/actions/workflows/ci.yml/badge.svg" alt="CI Status">
|
|
15
18
|
</a>
|
|
16
|
-
<a href="https://github.com/empty-complete/medusa-product-attributes/commits/main">
|
|
17
|
-
<img src="https://img.shields.io/github/commit-activity/m/empty-complete/medusa-product-attributes" alt="GitHub commit activity">
|
|
18
|
-
</a>
|
|
19
|
-
<a href="https://www.npmjs.com/package/@empty-complete/medusa-product-attributes">
|
|
20
|
-
<img src="https://img.shields.io/npm/dm/@empty-complete/medusa-product-attributes" alt="npm downloads">
|
|
21
|
-
</a>
|
|
22
|
-
</p>
|
|
23
|
-
|
|
24
|
-
<p>
|
|
25
|
-
<a href="https://medusajs.com/">
|
|
26
|
-
<img src="https://img.shields.io/badge/Medusa-v2-000000" alt="Medusa v2">
|
|
27
|
-
</a>
|
|
28
|
-
<a href="https://www.typescriptlang.org/">
|
|
29
|
-
<img src="https://img.shields.io/badge/TypeScript-5.0+-3178C6" alt="TypeScript">
|
|
30
|
-
</a>
|
|
31
|
-
<a href="https://nodejs.org/">
|
|
32
|
-
<img src="https://img.shields.io/badge/Node.js-%3E=20-339933" alt="Node.js">
|
|
33
|
-
</a>
|
|
34
19
|
</p>
|
|
35
|
-
|
|
20
|
+
|
|
36
21
|
<p>
|
|
37
|
-
<a href="https://
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
<a href="https://medusajs.com/"><img src="https://img.shields.io/badge/Medusa-v2-000000" alt="Medusa v2"></a>
|
|
23
|
+
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.0+-3178C6" alt="TypeScript"></a>
|
|
24
|
+
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/Node.js-%3E=20-339933" alt="Node.js"></a>
|
|
40
25
|
</p>
|
|
26
|
+
|
|
41
27
|
</div>
|
|
42
28
|
|
|
43
29
|
---
|
|
44
30
|
|
|
45
|
-
##
|
|
31
|
+
## Features
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
33
|
+
- **4 attribute types**: text, number (with units), file, boolean
|
|
34
|
+
- **Category inheritance**: attributes defined on a parent category are automatically inherited by all subcategories
|
|
35
|
+
- **Global attributes**: applied to every product in the store
|
|
36
|
+
- **Templates**: reusable attribute blueprints — apply to any category in one click
|
|
37
|
+
- **Automatic slug generation**: labels are transliterated (works with Russian, Chinese, any script)
|
|
38
|
+
- **Smart file naming**: uploaded images are renamed to `{product_handle}_{attr_key}.ext`
|
|
39
|
+
- **Admin UI included**: category widget, product widget, two Settings pages
|
|
40
|
+
- **Ships with migrations**: just run `npx medusa db:migrate`
|
|
55
41
|
|
|
56
|
-
|
|
42
|
+
## Screenshots
|
|
57
43
|
|
|
58
|
-
###
|
|
44
|
+
### Category widget with inheritance
|
|
45
|
+

|
|
59
46
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| Medusa | ^2.0.0 |
|
|
63
|
-
| Node.js | >=20 |
|
|
64
|
-
| TypeScript | ^5.0.0 |
|
|
47
|
+
### Product attribute values
|
|
48
|
+

|
|
65
49
|
|
|
66
|
-
|
|
50
|
+
### Global attributes settings
|
|
51
|
+

|
|
67
52
|
|
|
68
|
-
|
|
53
|
+
### Attribute templates settings
|
|
54
|
+

|
|
69
55
|
|
|
70
|
-
|
|
56
|
+
## Installation
|
|
71
57
|
|
|
72
58
|
```bash
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# Via yarn
|
|
80
|
-
yarn add @empty-complete/medusa-product-attributes
|
|
81
|
-
|
|
82
|
-
# Via GitHub Packages (requires authentication)
|
|
83
|
-
npm install @empty-complete/medusa-product-attributes --registry=https://npm.pkg.github.com
|
|
59
|
+
pnpm add @empty-complete-org/medusa-product-attributes
|
|
60
|
+
# or
|
|
61
|
+
npm install @empty-complete-org/medusa-product-attributes
|
|
62
|
+
# or
|
|
63
|
+
yarn add @empty-complete-org/medusa-product-attributes
|
|
84
64
|
```
|
|
85
65
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
// medusa-config.ts
|
|
90
|
-
import { defineConfig } from '@medusajs/framework/utils'
|
|
66
|
+
Add to `medusa-config.ts`:
|
|
91
67
|
|
|
92
|
-
|
|
93
|
-
|
|
68
|
+
```ts
|
|
69
|
+
module.exports = defineConfig({
|
|
70
|
+
plugins: [
|
|
94
71
|
{
|
|
95
|
-
resolve:
|
|
72
|
+
resolve: "@empty-complete-org/medusa-product-attributes",
|
|
73
|
+
options: {},
|
|
96
74
|
},
|
|
97
75
|
],
|
|
98
76
|
})
|
|
99
77
|
```
|
|
100
78
|
|
|
101
|
-
|
|
79
|
+
Run migrations:
|
|
102
80
|
|
|
103
81
|
```bash
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### 3. Done!
|
|
108
|
-
|
|
109
|
-
The module will automatically create the necessary tables and register services.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## 💡 Usage
|
|
114
|
-
|
|
115
|
-
### Creating a category attribute
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
import { CUSTOM_ATTRIBUTE_MODULE } from '@empty-complete/medusa-product-attributes'
|
|
119
|
-
|
|
120
|
-
const module = container.resolve(CUSTOM_ATTRIBUTE_MODULE)
|
|
121
|
-
|
|
122
|
-
// Create a "Weight" attribute with unit
|
|
123
|
-
await module.createCategoryAttribute({
|
|
124
|
-
label: 'Weight',
|
|
125
|
-
type: 'number',
|
|
126
|
-
unit: 'kg',
|
|
127
|
-
category_id: 'pcat_123',
|
|
128
|
-
sort_order: 0,
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
// Create a 3D model attribute
|
|
132
|
-
await module.createCategoryAttribute({
|
|
133
|
-
label: '3D Model',
|
|
134
|
-
type: 'file',
|
|
135
|
-
category_id: 'pcat_123',
|
|
136
|
-
is_standard: true,
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Adding attributes to a product
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
// Get the attribute template
|
|
144
|
-
const attributes = await module.getCategoryAttributes('pcat_123')
|
|
145
|
-
|
|
146
|
-
// Add values to the product
|
|
147
|
-
await module.createProductAttribute({
|
|
148
|
-
product_id: 'prod_456',
|
|
149
|
-
category_custom_attribute_id: attributes[0].id,
|
|
150
|
-
value_numeric: 5.5,
|
|
151
|
-
})
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### Getting product attributes
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
const productAttributes = await module.getProductAttributes('prod_456')
|
|
158
|
-
|
|
159
|
-
console.log(productAttributes)
|
|
160
|
-
// [
|
|
161
|
-
// {
|
|
162
|
-
// id: 'pat_...',
|
|
163
|
-
// value: 'Premium quality',
|
|
164
|
-
// value_numeric: 5.5,
|
|
165
|
-
// value_file: null,
|
|
166
|
-
// category_custom_attribute: {
|
|
167
|
-
// label: 'Weight',
|
|
168
|
-
// unit: 'kg',
|
|
169
|
-
// type: 'number'
|
|
170
|
-
// }
|
|
171
|
-
// }
|
|
172
|
-
// ]
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
---
|
|
176
|
-
|
|
177
|
-
## 📚 API Reference
|
|
178
|
-
|
|
179
|
-
### CategoryCustomAttribute
|
|
180
|
-
|
|
181
|
-
| Method | Description | Parameters |
|
|
182
|
-
|--------|-------------|------------|
|
|
183
|
-
| `getCategoryAttributes(categoryId)` | Get all category attributes | `categoryId: string` |
|
|
184
|
-
| `createCategoryAttribute(data)` | Create a new attribute | See below |
|
|
185
|
-
| `updateCategoryAttribute(id, data)` | Update an attribute | `id: string`, `data: Partial<Attribute>` |
|
|
186
|
-
|
|
187
|
-
#### createCategoryAttribute
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
{
|
|
191
|
-
label: string // Display name
|
|
192
|
-
type: 'text' | 'number' | 'file' | 'boolean'
|
|
193
|
-
unit?: string | null // Unit of measurement (optional)
|
|
194
|
-
category_id: string // Category ID
|
|
195
|
-
is_standard?: boolean // Standard attribute flag
|
|
196
|
-
sort_order?: number // Sort order
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### ProductCustomAttribute
|
|
201
|
-
|
|
202
|
-
| Method | Description | Parameters |
|
|
203
|
-
|--------|-------------|------------|
|
|
204
|
-
| `getProductAttributes(productId)` | Get product attributes | `productId: string` |
|
|
205
|
-
| `createProductAttribute(data)` | Create attribute value | See below |
|
|
206
|
-
| `updateProductAttribute(id, data)` | Update value | `id: string`, `data: Partial<Value>` |
|
|
207
|
-
|
|
208
|
-
#### createProductAttribute
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
{
|
|
212
|
-
product_id: string
|
|
213
|
-
category_custom_attribute_id: string
|
|
214
|
-
value?: string
|
|
215
|
-
value_numeric?: number | null
|
|
216
|
-
value_file?: string | null
|
|
217
|
-
}
|
|
82
|
+
npx medusa db:migrate
|
|
218
83
|
```
|
|
219
84
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
## 🗄️ Data Structure
|
|
223
|
-
|
|
224
|
-
### CategoryCustomAttribute
|
|
225
|
-
|
|
226
|
-
```
|
|
227
|
-
┌─────────────────────────────────────────┐
|
|
228
|
-
│ CategoryCustomAttribute │
|
|
229
|
-
├─────────────────────────────────────────┤
|
|
230
|
-
│ id: string (PK) │
|
|
231
|
-
│ key: string │
|
|
232
|
-
│ label: string │
|
|
233
|
-
│ type: 'text' | 'number' | 'file' │
|
|
234
|
-
│ unit: string | null │
|
|
235
|
-
│ sort_order: number │
|
|
236
|
-
│ is_standard: boolean │
|
|
237
|
-
│ category_id: string │
|
|
238
|
-
│ created_at: timestamp │
|
|
239
|
-
│ updated_at: timestamp │
|
|
240
|
-
│ deleted_at: timestamp | null │
|
|
241
|
-
└─────────────────────────────────────────┘
|
|
242
|
-
```
|
|
85
|
+
## Usage
|
|
243
86
|
|
|
244
|
-
###
|
|
87
|
+
### Admin
|
|
245
88
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
│ id: string (PK) │
|
|
251
|
-
│ product_id: string │
|
|
252
|
-
│ value: string | null │
|
|
253
|
-
│ value_numeric: number | null │
|
|
254
|
-
│ value_file: string | null │
|
|
255
|
-
│ category_custom_attribute_id: FK │
|
|
256
|
-
│ created_at: timestamp │
|
|
257
|
-
│ updated_at: timestamp │
|
|
258
|
-
│ deleted_at: timestamp | null │
|
|
259
|
-
└─────────────────────────────────────────┘
|
|
260
|
-
```
|
|
89
|
+
1. **Category attributes** — open any category in admin, the "Атрибуты" widget appears below the details. Add custom attributes (inherited by all subcategories), or pick one from a template.
|
|
90
|
+
2. **Global attributes** — Settings → *Global Attributes*. Applied to every product automatically.
|
|
91
|
+
3. **Templates** — Settings → *Attribute Templates*. Create reusable blueprints.
|
|
92
|
+
4. **Product values** — open any product, the "Характеристики" widget shows all inherited + global attributes with inputs for their values.
|
|
261
93
|
|
|
262
|
-
|
|
94
|
+
### API
|
|
263
95
|
|
|
264
|
-
|
|
96
|
+
```ts
|
|
97
|
+
// List all attributes for a product (globals + category chain)
|
|
98
|
+
GET /admin/product/:productId/attribute-schema
|
|
265
99
|
|
|
266
|
-
|
|
100
|
+
// List/create/update attributes for a category
|
|
101
|
+
GET /admin/category/:categoryId/custom-attributes
|
|
102
|
+
POST /admin/category/:categoryId/custom-attributes
|
|
103
|
+
PATCH /admin/category/:categoryId/custom-attributes
|
|
267
104
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
type: 'number',
|
|
273
|
-
unit: 'inch',
|
|
274
|
-
category_id: 'pcat_phones',
|
|
275
|
-
sort_order: 1,
|
|
276
|
-
})
|
|
105
|
+
// Global attributes
|
|
106
|
+
GET /admin/global-attributes
|
|
107
|
+
POST /admin/global-attributes
|
|
108
|
+
PATCH /admin/global-attributes
|
|
277
109
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
category_id
|
|
283
|
-
sort_order: 2,
|
|
284
|
-
})
|
|
110
|
+
// Templates
|
|
111
|
+
GET /admin/attribute-templates
|
|
112
|
+
POST /admin/attribute-templates
|
|
113
|
+
PATCH /admin/attribute-templates
|
|
114
|
+
POST /admin/attribute-templates/:id/apply { category_id }
|
|
285
115
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
category_id: 'pcat_phones',
|
|
290
|
-
is_standard: true,
|
|
291
|
-
sort_order: 3,
|
|
292
|
-
})
|
|
116
|
+
// Product values
|
|
117
|
+
GET /admin/product/:productId/custom-attributes
|
|
118
|
+
POST /admin/product/:productId/custom-attributes
|
|
293
119
|
```
|
|
294
120
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
```typescript
|
|
298
|
-
// iPhone 15 Pro
|
|
299
|
-
await module.createProductAttribute({
|
|
300
|
-
product_id: 'prod_iphone15pro',
|
|
301
|
-
category_custom_attribute_id: 'attr_screen_size',
|
|
302
|
-
value_numeric: 6.1,
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
await module.createProductAttribute({
|
|
306
|
-
product_id: 'prod_iphone15pro',
|
|
307
|
-
category_custom_attribute_id: 'attr_storage',
|
|
308
|
-
value_numeric: 256,
|
|
309
|
-
})
|
|
310
|
-
```
|
|
121
|
+
## Attribute types
|
|
311
122
|
|
|
312
|
-
|
|
123
|
+
| Type | Admin input | Stored in |
|
|
124
|
+
|------|-------------|-----------|
|
|
125
|
+
| `text` | text input | `value` |
|
|
126
|
+
| `number` | number input + unit badge | `value`, `value_numeric` |
|
|
127
|
+
| `file` | upload button | `value` (URL), `value_file` |
|
|
128
|
+
| `boolean` | select Yes/No | `value` |
|
|
313
129
|
|
|
314
|
-
|
|
315
|
-
// GET /store/attribute-values?product_id=prod_iphone15pro
|
|
316
|
-
// Response:
|
|
317
|
-
{
|
|
318
|
-
"attribute_values": [
|
|
319
|
-
{
|
|
320
|
-
"id": "pat_...",
|
|
321
|
-
"name": "Screen Size",
|
|
322
|
-
"type": "number",
|
|
323
|
-
"unit": "inch",
|
|
324
|
-
"numeric_value": 6.1,
|
|
325
|
-
"is_standard": false
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
"id": "pat_...",
|
|
329
|
-
"name": "Certificate",
|
|
330
|
-
"type": "file",
|
|
331
|
-
"file_value": "/files/cert_iphone15pro.pdf",
|
|
332
|
-
"is_standard": true
|
|
333
|
-
}
|
|
334
|
-
]
|
|
335
|
-
}
|
|
336
|
-
```
|
|
130
|
+
## Peer dependencies
|
|
337
131
|
|
|
338
|
-
|
|
132
|
+
- `@medusajs/framework` ^2.13
|
|
133
|
+
- `@medusajs/medusa` ^2.13
|
|
134
|
+
- `@medusajs/admin-sdk` ^2.13
|
|
135
|
+
- `@medusajs/ui` ^4
|
|
136
|
+
- `@tanstack/react-query` ^5
|
|
137
|
+
- `react` ^18 || ^19
|
|
339
138
|
|
|
340
|
-
##
|
|
139
|
+
## Development
|
|
341
140
|
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
│ │ └── product-custom-attribute.ts
|
|
348
|
-
│ ├── index.ts
|
|
349
|
-
│ └── service.ts
|
|
350
|
-
├── assets/
|
|
351
|
-
│ └── *.png
|
|
352
|
-
├── dist/
|
|
353
|
-
├── package.json
|
|
354
|
-
├── tsconfig.json
|
|
355
|
-
├── LICENSE
|
|
356
|
-
└── README.md
|
|
141
|
+
```bash
|
|
142
|
+
pnpm install
|
|
143
|
+
pnpm test
|
|
144
|
+
pnpm run build # builds to .medusa/server/
|
|
145
|
+
pnpm run dev # medusa plugin:develop
|
|
357
146
|
```
|
|
358
147
|
|
|
359
|
-
|
|
148
|
+
## License
|
|
360
149
|
|
|
361
|
-
|
|
362
|
-
<h2>🤝 Contributing</h2>
|
|
363
|
-
<p>Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.</p>
|
|
364
|
-
<p>
|
|
365
|
-
<sub>Made with ❤️ for the Medusa community</sub>
|
|
366
|
-
</p>
|
|
367
|
-
</div>
|
|
150
|
+
MIT © empty-complete
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empty-complete-org/medusa-product-attributes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Custom attributes module for Medusa v2 with support for text, number, file types and units of measurement",
|
|
5
5
|
"author": "empty-complete",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../../../../../src/api/admin/attribute-presets/[id]/apply/route.ts"],"names":[],"mappings":";;AAIA,oBAMC;AATD,kFAAmF;AAG5E,KAAK,UAAU,IAAI,CAAC,GAAkB,EAAE,GAAmB;IAChE,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAM,CAAA;IACzB,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,IAA+B,CAAA;IAC3D,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,yBAAyB,GAAG,MAAM,OAAO,CAAC,qBAAqB,CAAC,EAAE,EAAE,WAAW,CAAC,CAAA;IACtF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,yBAAyB,EAAE,CAAC,CAAA;AACrD,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../../../../../src/api/admin/attribute-presets/route.ts"],"names":[],"mappings":";;AAIA,kBAIC;AAED,oBAUC;AAED,sBAYC;AAjCD,4EAA6E;AAGtE,KAAK,UAAU,GAAG,CAAC,GAAkB,EAAE,GAAmB;IAC/D,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,iBAAiB,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAA;IACrD,GAAG,CAAC,IAAI,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAA;AACjC,CAAC;AAEM,KAAK,UAAU,IAAI,CAAC,GAAkB,EAAE,GAAmB;IAChE,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,GAAG,CAAC,IAKhB,CAAA;IACD,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;IACzD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAA;AAC5C,CAAC;AAEM,KAAK,UAAU,KAAK,CAAC,GAAkB,EAAE,GAAmB;IACjE,MAAM,OAAO,GAA2B,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,4CAAuB,CAAC,CAAA;IAClF,MAAM,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC,IAO3B,CAAA;IACD,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;IAC7D,GAAG,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAA;AAChC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"attribute-preset.js","sourceRoot":"","sources":["../../../../../../src/modules/product-attributes/models/attribute-preset.ts"],"names":[],"mappings":";;AAAA,qDAAiD;AAEjD,MAAM,eAAe,GAAG,aAAK,CAAC,MAAM,CAAC,kBAAkB,EAAE;IACvD,EAAE,EAAE,aAAK,CAAC,EAAE,EAAE,CAAC,UAAU,EAAE;IAC3B,GAAG,EAAE,aAAK,CAAC,IAAI,EAAE;IACjB,KAAK,EAAE,aAAK,CAAC,IAAI,EAAE;IACnB,IAAI,EAAE,aAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,aAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;IAC7B,WAAW,EAAE,aAAK,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAA;AAEF,kBAAe,eAAe,CAAA"}
|
|
File without changes
|
|
File without changes
|