@blackcode_sa/metaestetics-api 1.11.3 → 1.12.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/dist/admin/index.d.mts +329 -318
- package/dist/admin/index.d.ts +329 -318
- package/dist/backoffice/index.d.mts +1166 -430
- package/dist/backoffice/index.d.ts +1166 -430
- package/dist/backoffice/index.js +1128 -245
- package/dist/backoffice/index.mjs +1119 -209
- package/dist/index.d.mts +4428 -4035
- package/dist/index.d.ts +4428 -4035
- package/dist/index.js +1642 -665
- package/dist/index.mjs +1406 -401
- package/package.json +1 -1
- package/src/backoffice/expo-safe/index.ts +3 -0
- package/src/backoffice/services/README.md +40 -0
- package/src/backoffice/services/brand.service.ts +85 -6
- package/src/backoffice/services/category.service.ts +92 -10
- package/src/backoffice/services/constants.service.ts +308 -0
- package/src/backoffice/services/documentation-template.service.ts +56 -2
- package/src/backoffice/services/index.ts +1 -0
- package/src/backoffice/services/product.service.ts +126 -5
- package/src/backoffice/services/requirement.service.ts +13 -0
- package/src/backoffice/services/subcategory.service.ts +184 -13
- package/src/backoffice/services/technology.service.ts +344 -129
- package/src/backoffice/types/admin-constants.types.ts +69 -0
- package/src/backoffice/types/brand.types.ts +1 -0
- package/src/backoffice/types/index.ts +1 -0
- package/src/backoffice/types/product.types.ts +31 -4
- package/src/backoffice/types/static/contraindication.types.ts +1 -0
- package/src/backoffice/types/static/treatment-benefit.types.ts +1 -0
- package/src/backoffice/types/technology.types.ts +113 -4
- package/src/backoffice/validations/schemas.ts +35 -9
- package/src/services/appointment/appointment.service.ts +0 -5
- package/src/services/appointment/utils/appointment.utils.ts +124 -113
- package/src/services/base.service.ts +10 -3
- package/src/services/documentation-templates/documentation-template.service.ts +116 -0
- package/src/services/media/media.service.ts +2 -2
- package/src/services/procedure/procedure.service.ts +436 -234
- package/src/types/appointment/index.ts +2 -3
- package/src/types/clinic/index.ts +1 -6
- package/src/types/patient/medical-info.types.ts +3 -3
- package/src/types/procedure/index.ts +20 -17
- package/src/validations/clinic.schema.ts +1 -6
- package/src/validations/patient/medical-info.schema.ts +7 -2
- package/src/backoffice/services/__tests__/brand.service.test.ts +0 -196
- package/src/backoffice/services/__tests__/category.service.test.ts +0 -201
- package/src/backoffice/services/__tests__/product.service.test.ts +0 -358
- package/src/backoffice/services/__tests__/requirement.service.test.ts +0 -226
- package/src/backoffice/services/__tests__/subcategory.service.test.ts +0 -181
- package/src/backoffice/services/__tests__/technology.service.test.ts +0 -1097
|
@@ -21,8 +21,10 @@ export class DocumentationTemplateServiceBackoffice {
|
|
|
21
21
|
* @param auth - Firebase Auth instance
|
|
22
22
|
* @param app - Firebase App instance
|
|
23
23
|
*/
|
|
24
|
-
constructor(
|
|
25
|
-
|
|
24
|
+
constructor(
|
|
25
|
+
...args: ConstructorParameters<typeof ApiDocumentationTemplateService>
|
|
26
|
+
) {
|
|
27
|
+
this.apiService = new ApiDocumentationTemplateService(...args);
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -88,6 +90,58 @@ export class DocumentationTemplateServiceBackoffice {
|
|
|
88
90
|
return this.apiService.getActiveTemplates(pageSize, lastDoc);
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Get all active templates with optional filters and pagination.
|
|
95
|
+
* @param options - Options for filtering and pagination.
|
|
96
|
+
* @returns A promise that resolves to the templates and the last visible document.
|
|
97
|
+
*/
|
|
98
|
+
async getTemplates(options: {
|
|
99
|
+
pageSize?: number;
|
|
100
|
+
lastDoc?: QueryDocumentSnapshot<DocumentTemplate>;
|
|
101
|
+
isUserForm?: boolean;
|
|
102
|
+
isRequired?: boolean;
|
|
103
|
+
sortingOrder?: number;
|
|
104
|
+
}): Promise<{
|
|
105
|
+
templates: DocumentTemplate[];
|
|
106
|
+
lastDoc: QueryDocumentSnapshot<DocumentTemplate> | null;
|
|
107
|
+
}> {
|
|
108
|
+
return this.apiService.getTemplates(options);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the total count of active templates with optional filters.
|
|
113
|
+
* @param options - Options for filtering.
|
|
114
|
+
* @returns A promise that resolves to the total count of templates.
|
|
115
|
+
*/
|
|
116
|
+
async getTemplatesCount(options: {
|
|
117
|
+
isUserForm?: boolean;
|
|
118
|
+
isRequired?: boolean;
|
|
119
|
+
sortingOrder?: number;
|
|
120
|
+
search?: string;
|
|
121
|
+
}): Promise<number> {
|
|
122
|
+
return this.apiService.getTemplatesCount(options);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get all active templates without pagination for filtering purposes.
|
|
127
|
+
* @returns A promise that resolves to an array of all active templates.
|
|
128
|
+
*/
|
|
129
|
+
async getAllActiveTemplates(): Promise<DocumentTemplate[]> {
|
|
130
|
+
return this.apiService.getAllActiveTemplates();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Searches for active templates by title.
|
|
135
|
+
* @param title - The title to search for.
|
|
136
|
+
* @returns A list of templates that match the search criteria.
|
|
137
|
+
*/
|
|
138
|
+
async search(title: string): Promise<DocumentTemplate[]> {
|
|
139
|
+
const { templates } = await this.apiService.getActiveTemplates(1000); // A large number to get all templates
|
|
140
|
+
return templates.filter((t) =>
|
|
141
|
+
t.title.toLowerCase().includes(title.toLowerCase())
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
91
145
|
/**
|
|
92
146
|
* Get templates by tags
|
|
93
147
|
* @param tags - Tags to filter by
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addDoc,
|
|
3
3
|
collection,
|
|
4
|
+
collectionGroup,
|
|
4
5
|
doc,
|
|
5
6
|
getDoc,
|
|
6
7
|
getDocs,
|
|
7
8
|
query,
|
|
8
9
|
updateDoc,
|
|
9
10
|
where,
|
|
11
|
+
limit,
|
|
12
|
+
orderBy,
|
|
13
|
+
startAfter,
|
|
14
|
+
getCountFromServer,
|
|
15
|
+
QueryConstraint,
|
|
10
16
|
} from "firebase/firestore";
|
|
11
17
|
import {
|
|
12
18
|
Product,
|
|
@@ -43,6 +49,7 @@ export class ProductService extends BaseService implements IProductService {
|
|
|
43
49
|
>
|
|
44
50
|
): Promise<Product> {
|
|
45
51
|
const now = new Date();
|
|
52
|
+
// categoryId and subcategoryId are now expected to be part of the product object
|
|
46
53
|
const newProduct: Omit<Product, "id"> = {
|
|
47
54
|
...product,
|
|
48
55
|
brandId,
|
|
@@ -61,21 +68,135 @@ export class ProductService extends BaseService implements IProductService {
|
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
|
-
* Gets all products
|
|
71
|
+
* Gets a paginated list of all products, with optional filters.
|
|
72
|
+
* This uses a collectionGroup query to search across all technologies.
|
|
65
73
|
*/
|
|
66
|
-
async
|
|
74
|
+
async getAll(options: {
|
|
75
|
+
rowsPerPage: number;
|
|
76
|
+
lastVisible?: any;
|
|
77
|
+
categoryId?: string;
|
|
78
|
+
subcategoryId?: string;
|
|
79
|
+
technologyId?: string;
|
|
80
|
+
}): Promise<{ products: Product[]; lastVisible: any }> {
|
|
81
|
+
const {
|
|
82
|
+
rowsPerPage,
|
|
83
|
+
lastVisible,
|
|
84
|
+
categoryId,
|
|
85
|
+
subcategoryId,
|
|
86
|
+
technologyId,
|
|
87
|
+
} = options;
|
|
88
|
+
|
|
89
|
+
const constraints: QueryConstraint[] = [
|
|
90
|
+
where("isActive", "==", true),
|
|
91
|
+
orderBy("name"),
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
if (categoryId) {
|
|
95
|
+
constraints.push(where("categoryId", "==", categoryId));
|
|
96
|
+
}
|
|
97
|
+
if (subcategoryId) {
|
|
98
|
+
constraints.push(where("subcategoryId", "==", subcategoryId));
|
|
99
|
+
}
|
|
100
|
+
if (technologyId) {
|
|
101
|
+
constraints.push(where("technologyId", "==", technologyId));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (lastVisible) {
|
|
105
|
+
constraints.push(startAfter(lastVisible));
|
|
106
|
+
}
|
|
107
|
+
constraints.push(limit(rowsPerPage));
|
|
108
|
+
|
|
67
109
|
const q = query(
|
|
68
|
-
this.
|
|
69
|
-
|
|
110
|
+
collectionGroup(this.db, PRODUCTS_COLLECTION),
|
|
111
|
+
...constraints
|
|
70
112
|
);
|
|
71
113
|
const snapshot = await getDocs(q);
|
|
72
|
-
|
|
114
|
+
|
|
115
|
+
const products = snapshot.docs.map(
|
|
73
116
|
(doc) =>
|
|
74
117
|
({
|
|
75
118
|
id: doc.id,
|
|
76
119
|
...doc.data(),
|
|
77
120
|
} as Product)
|
|
78
121
|
);
|
|
122
|
+
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
123
|
+
|
|
124
|
+
return { products, lastVisible: newLastVisible };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Gets the total count of active products, with optional filters.
|
|
129
|
+
*/
|
|
130
|
+
async getProductsCount(options: {
|
|
131
|
+
categoryId?: string;
|
|
132
|
+
subcategoryId?: string;
|
|
133
|
+
technologyId?: string;
|
|
134
|
+
}): Promise<number> {
|
|
135
|
+
const { categoryId, subcategoryId, technologyId } = options;
|
|
136
|
+
const constraints: QueryConstraint[] = [where("isActive", "==", true)];
|
|
137
|
+
|
|
138
|
+
if (categoryId) {
|
|
139
|
+
constraints.push(where("categoryId", "==", categoryId));
|
|
140
|
+
}
|
|
141
|
+
if (subcategoryId) {
|
|
142
|
+
constraints.push(where("subcategoryId", "==", subcategoryId));
|
|
143
|
+
}
|
|
144
|
+
if (technologyId) {
|
|
145
|
+
constraints.push(where("technologyId", "==", technologyId));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const q = query(
|
|
149
|
+
collectionGroup(this.db, PRODUCTS_COLLECTION),
|
|
150
|
+
...constraints
|
|
151
|
+
);
|
|
152
|
+
const snapshot = await getCountFromServer(q);
|
|
153
|
+
return snapshot.data().count;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Gets counts of active products grouped by category, subcategory, and technology.
|
|
158
|
+
* This uses a single collectionGroup query for efficiency.
|
|
159
|
+
*/
|
|
160
|
+
async getProductCounts(): Promise<{
|
|
161
|
+
byCategory: Record<string, number>;
|
|
162
|
+
bySubcategory: Record<string, number>;
|
|
163
|
+
byTechnology: Record<string, number>;
|
|
164
|
+
}> {
|
|
165
|
+
const q = query(
|
|
166
|
+
collectionGroup(this.db, PRODUCTS_COLLECTION),
|
|
167
|
+
where("isActive", "==", true)
|
|
168
|
+
);
|
|
169
|
+
const snapshot = await getDocs(q);
|
|
170
|
+
|
|
171
|
+
const counts = {
|
|
172
|
+
byCategory: {} as Record<string, number>,
|
|
173
|
+
bySubcategory: {} as Record<string, number>,
|
|
174
|
+
byTechnology: {} as Record<string, number>,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
if (snapshot.empty) {
|
|
178
|
+
return counts;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
snapshot.docs.forEach((doc) => {
|
|
182
|
+
const product = doc.data() as Product;
|
|
183
|
+
const { categoryId, subcategoryId, technologyId } = product;
|
|
184
|
+
|
|
185
|
+
if (categoryId) {
|
|
186
|
+
counts.byCategory[categoryId] =
|
|
187
|
+
(counts.byCategory[categoryId] || 0) + 1;
|
|
188
|
+
}
|
|
189
|
+
if (subcategoryId) {
|
|
190
|
+
counts.bySubcategory[subcategoryId] =
|
|
191
|
+
(counts.bySubcategory[subcategoryId] || 0) + 1;
|
|
192
|
+
}
|
|
193
|
+
if (technologyId) {
|
|
194
|
+
counts.byTechnology[technologyId] =
|
|
195
|
+
(counts.byTechnology[technologyId] || 0) + 1;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return counts;
|
|
79
200
|
}
|
|
80
201
|
|
|
81
202
|
/**
|
|
@@ -101,6 +101,19 @@ export class RequirementService extends BaseService {
|
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Searches for requirements by name.
|
|
106
|
+
* @param name - The name to search for.
|
|
107
|
+
* @param type - The type of requirement (pre/post).
|
|
108
|
+
* @returns A list of requirements that match the search criteria.
|
|
109
|
+
*/
|
|
110
|
+
async search(name: string, type: RequirementType) {
|
|
111
|
+
const requirements = await this.getAllByType(type);
|
|
112
|
+
return requirements.filter((r) =>
|
|
113
|
+
r.name.toLowerCase().includes(name.toLowerCase())
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
/**
|
|
105
118
|
* Ažurira postojeći zahtev
|
|
106
119
|
* @param id - ID zahteva koji se ažurira
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
addDoc,
|
|
3
3
|
collection,
|
|
4
|
+
collectionGroup,
|
|
5
|
+
deleteDoc,
|
|
4
6
|
doc,
|
|
7
|
+
DocumentData,
|
|
8
|
+
getCountFromServer,
|
|
5
9
|
getDoc,
|
|
6
10
|
getDocs,
|
|
11
|
+
limit,
|
|
12
|
+
orderBy,
|
|
7
13
|
query,
|
|
14
|
+
setDoc,
|
|
15
|
+
startAfter,
|
|
8
16
|
updateDoc,
|
|
9
17
|
where,
|
|
10
18
|
} from "firebase/firestore";
|
|
@@ -69,17 +77,133 @@ export class SubcategoryService extends BaseService {
|
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
/**
|
|
72
|
-
*
|
|
80
|
+
* Returns counts of subcategories for all categories.
|
|
81
|
+
* @param active - Whether to count active or inactive subcategories.
|
|
82
|
+
* @returns A record mapping category ID to subcategory count.
|
|
83
|
+
*/
|
|
84
|
+
async getSubcategoryCounts(active = true) {
|
|
85
|
+
const categoriesRef = collection(this.db, CATEGORIES_COLLECTION);
|
|
86
|
+
const categoriesSnapshot = await getDocs(categoriesRef);
|
|
87
|
+
const counts: Record<string, number> = {};
|
|
88
|
+
|
|
89
|
+
for (const categoryDoc of categoriesSnapshot.docs) {
|
|
90
|
+
const categoryId = categoryDoc.id;
|
|
91
|
+
const subcategoriesRef = this.getSubcategoriesRef(categoryId);
|
|
92
|
+
const q = query(subcategoriesRef, where("isActive", "==", active));
|
|
93
|
+
const snapshot = await getCountFromServer(q);
|
|
94
|
+
counts[categoryId] = snapshot.data().count;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return counts;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Vraća sve aktivne podkategorije za određenu kategoriju sa paginacijom
|
|
73
102
|
* @param categoryId - ID kategorije čije podkategorije tražimo
|
|
74
|
-
* @
|
|
103
|
+
* @param options - Pagination options
|
|
104
|
+
* @returns Lista aktivnih podkategorija i poslednji vidljiv dokument
|
|
75
105
|
*/
|
|
76
|
-
async getAllByCategoryId(
|
|
106
|
+
async getAllByCategoryId(
|
|
107
|
+
categoryId: string,
|
|
108
|
+
options: {
|
|
109
|
+
active?: boolean;
|
|
110
|
+
limit?: number;
|
|
111
|
+
lastVisible?: DocumentData;
|
|
112
|
+
} = {}
|
|
113
|
+
) {
|
|
114
|
+
const { active = true, limit: queryLimit = 10, lastVisible } = options;
|
|
115
|
+
const constraints = [
|
|
116
|
+
where("isActive", "==", active),
|
|
117
|
+
orderBy("name"),
|
|
118
|
+
queryLimit ? limit(queryLimit) : undefined,
|
|
119
|
+
lastVisible ? startAfter(lastVisible) : undefined,
|
|
120
|
+
].filter((c): c is NonNullable<typeof c> => !!c);
|
|
121
|
+
|
|
122
|
+
const q = query(this.getSubcategoriesRef(categoryId), ...constraints);
|
|
123
|
+
|
|
124
|
+
const querySnapshot = await getDocs(q);
|
|
125
|
+
const subcategories = querySnapshot.docs.map(
|
|
126
|
+
(doc) =>
|
|
127
|
+
({
|
|
128
|
+
id: doc.id,
|
|
129
|
+
...doc.data(),
|
|
130
|
+
} as Subcategory)
|
|
131
|
+
);
|
|
132
|
+
const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
|
|
133
|
+
return { subcategories, lastVisible: newLastVisible };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Vraća sve podkategorije sa paginacijom koristeći collection group query.
|
|
138
|
+
* NOTE: This query requires a composite index in Firestore on the 'subcategories' collection group.
|
|
139
|
+
* The index should be on 'isActive' (ascending) and 'name' (ascending).
|
|
140
|
+
* Firestore will provide a link to create this index in the console error if it's missing.
|
|
141
|
+
* @param options - Pagination options
|
|
142
|
+
* @returns Lista podkategorija i poslednji vidljiv dokument
|
|
143
|
+
*/
|
|
144
|
+
async getAll(
|
|
145
|
+
options: {
|
|
146
|
+
active?: boolean;
|
|
147
|
+
limit?: number;
|
|
148
|
+
lastVisible?: DocumentData;
|
|
149
|
+
} = {}
|
|
150
|
+
) {
|
|
151
|
+
const { active = true, limit: queryLimit = 10, lastVisible } = options;
|
|
152
|
+
const constraints = [
|
|
153
|
+
where("isActive", "==", active),
|
|
154
|
+
orderBy("name"),
|
|
155
|
+
queryLimit ? limit(queryLimit) : undefined,
|
|
156
|
+
lastVisible ? startAfter(lastVisible) : undefined,
|
|
157
|
+
].filter((c): c is NonNullable<typeof c> => !!c);
|
|
158
|
+
|
|
159
|
+
const q = query(
|
|
160
|
+
collectionGroup(this.db, SUBCATEGORIES_COLLECTION),
|
|
161
|
+
...constraints
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const querySnapshot = await getDocs(q);
|
|
165
|
+
const subcategories = querySnapshot.docs.map(
|
|
166
|
+
(doc) =>
|
|
167
|
+
({
|
|
168
|
+
id: doc.id,
|
|
169
|
+
...doc.data(),
|
|
170
|
+
} as Subcategory)
|
|
171
|
+
);
|
|
172
|
+
const newLastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
|
|
173
|
+
return { subcategories, lastVisible: newLastVisible };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Vraća sve subkategorije za određenu kategoriju za potrebe filtera (bez paginacije)
|
|
178
|
+
* @param categoryId - ID kategorije čije subkategorije tražimo
|
|
179
|
+
* @returns Lista svih aktivnih subkategorija
|
|
180
|
+
*/
|
|
181
|
+
async getAllForFilterByCategoryId(categoryId: string) {
|
|
77
182
|
const q = query(
|
|
78
183
|
this.getSubcategoriesRef(categoryId),
|
|
79
184
|
where("isActive", "==", true)
|
|
80
185
|
);
|
|
81
|
-
const
|
|
82
|
-
return
|
|
186
|
+
const querySnapshot = await getDocs(q);
|
|
187
|
+
return querySnapshot.docs.map(
|
|
188
|
+
(doc) =>
|
|
189
|
+
({
|
|
190
|
+
id: doc.id,
|
|
191
|
+
...doc.data(),
|
|
192
|
+
} as Subcategory)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Vraća sve subkategorije za potrebe filtera (bez paginacije)
|
|
198
|
+
* @returns Lista svih aktivnih subkategorija
|
|
199
|
+
*/
|
|
200
|
+
async getAllForFilter() {
|
|
201
|
+
const q = query(
|
|
202
|
+
collectionGroup(this.db, SUBCATEGORIES_COLLECTION),
|
|
203
|
+
where("isActive", "==", true)
|
|
204
|
+
);
|
|
205
|
+
const querySnapshot = await getDocs(q);
|
|
206
|
+
return querySnapshot.docs.map(
|
|
83
207
|
(doc) =>
|
|
84
208
|
({
|
|
85
209
|
id: doc.id,
|
|
@@ -98,16 +222,54 @@ export class SubcategoryService extends BaseService {
|
|
|
98
222
|
async update(
|
|
99
223
|
categoryId: string,
|
|
100
224
|
subcategoryId: string,
|
|
101
|
-
subcategory: Partial<Omit<Subcategory, "id" | "createdAt"
|
|
225
|
+
subcategory: Partial<Omit<Subcategory, "id" | "createdAt">>
|
|
102
226
|
) {
|
|
103
|
-
const
|
|
104
|
-
...subcategory,
|
|
105
|
-
updatedAt: new Date(),
|
|
106
|
-
};
|
|
227
|
+
const newCategoryId = subcategory.categoryId;
|
|
107
228
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
229
|
+
if (newCategoryId && newCategoryId !== categoryId) {
|
|
230
|
+
// Category has changed, move the document
|
|
231
|
+
const oldDocRef = doc(
|
|
232
|
+
this.getSubcategoriesRef(categoryId),
|
|
233
|
+
subcategoryId
|
|
234
|
+
);
|
|
235
|
+
const docSnap = await getDoc(oldDocRef);
|
|
236
|
+
|
|
237
|
+
if (!docSnap.exists()) {
|
|
238
|
+
throw new Error("Subcategory to update does not exist.");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const existingData = docSnap.data();
|
|
242
|
+
const newData: Omit<Subcategory, "id"> = {
|
|
243
|
+
...(existingData as Omit<
|
|
244
|
+
Subcategory,
|
|
245
|
+
"id" | "createdAt" | "updatedAt"
|
|
246
|
+
>),
|
|
247
|
+
...subcategory,
|
|
248
|
+
categoryId: newCategoryId, // Ensure categoryId is updated
|
|
249
|
+
createdAt: existingData.createdAt, // Preserve original creation date
|
|
250
|
+
updatedAt: new Date(),
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const newDocRef = doc(
|
|
254
|
+
this.getSubcategoriesRef(newCategoryId),
|
|
255
|
+
subcategoryId
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
await setDoc(newDocRef, newData);
|
|
259
|
+
await deleteDoc(oldDocRef);
|
|
260
|
+
|
|
261
|
+
return { id: subcategoryId, ...newData };
|
|
262
|
+
} else {
|
|
263
|
+
// Category has not changed, just update the document
|
|
264
|
+
const updateData = {
|
|
265
|
+
...subcategory,
|
|
266
|
+
updatedAt: new Date(),
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const docRef = doc(this.getSubcategoriesRef(categoryId), subcategoryId);
|
|
270
|
+
await updateDoc(docRef, updateData);
|
|
271
|
+
return this.getById(categoryId, subcategoryId);
|
|
272
|
+
}
|
|
111
273
|
}
|
|
112
274
|
|
|
113
275
|
/**
|
|
@@ -119,6 +281,15 @@ export class SubcategoryService extends BaseService {
|
|
|
119
281
|
await this.update(categoryId, subcategoryId, { isActive: false });
|
|
120
282
|
}
|
|
121
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Reactivates a subcategory by setting its isActive flag to true.
|
|
286
|
+
* @param categoryId - The ID of the category to which the subcategory belongs.
|
|
287
|
+
* @param subcategoryId - The ID of the subcategory to reactivate.
|
|
288
|
+
*/
|
|
289
|
+
async reactivate(categoryId: string, subcategoryId: string) {
|
|
290
|
+
await this.update(categoryId, subcategoryId, { isActive: true });
|
|
291
|
+
}
|
|
292
|
+
|
|
122
293
|
/**
|
|
123
294
|
* Vraća podkategoriju po ID-u
|
|
124
295
|
* @param categoryId - ID kategorije kojoj pripada podkategorija
|