@blackcode_sa/metaestetics-api 1.12.40 → 1.12.42
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 +12 -6
- package/dist/admin/index.d.ts +12 -6
- package/dist/backoffice/index.d.mts +182 -18
- package/dist/backoffice/index.d.ts +182 -18
- package/dist/backoffice/index.js +302 -14
- package/dist/backoffice/index.mjs +318 -27
- package/dist/index.d.mts +174 -10
- package/dist/index.d.ts +174 -10
- package/dist/index.js +329 -25
- package/dist/index.mjs +340 -33
- package/package.json +1 -1
- package/src/backoffice/services/migrate-products.ts +116 -0
- package/src/backoffice/services/product.service.ts +216 -18
- package/src/backoffice/services/technology.service.ts +169 -0
- package/src/backoffice/types/product.types.ts +116 -6
- package/src/services/appointment/appointment.service.ts +29 -5
package/dist/index.mjs
CHANGED
|
@@ -2236,7 +2236,7 @@ var AppointmentService = class extends BaseService {
|
|
|
2236
2236
|
* @returns The updated appointment
|
|
2237
2237
|
*/
|
|
2238
2238
|
async updateAppointment(appointmentId, data) {
|
|
2239
|
-
var _a, _b;
|
|
2239
|
+
var _a, _b, _c, _d;
|
|
2240
2240
|
try {
|
|
2241
2241
|
console.log(`[APPOINTMENT_SERVICE] Updating appointment with ID: ${appointmentId}`);
|
|
2242
2242
|
if ((_a = data.metadata) == null ? void 0 : _a.zonePhotos) {
|
|
@@ -2259,18 +2259,34 @@ var AppointmentService = class extends BaseService {
|
|
|
2259
2259
|
}
|
|
2260
2260
|
data.metadata.zonePhotos = migratedZonePhotos;
|
|
2261
2261
|
}
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2262
|
+
console.log(
|
|
2263
|
+
"[APPOINTMENT_SERVICE] \u{1F50D} BEFORE CLEANUP - recommendedProcedures:",
|
|
2264
|
+
JSON.stringify((_b = data.metadata) == null ? void 0 : _b.recommendedProcedures, null, 2)
|
|
2265
|
+
);
|
|
2266
|
+
if (((_c = data.metadata) == null ? void 0 : _c.recommendedProcedures) && Array.isArray(data.metadata.recommendedProcedures)) {
|
|
2267
|
+
const validRecommendations = data.metadata.recommendedProcedures.filter((rec) => {
|
|
2268
|
+
const isValid = rec.note && typeof rec.note === "string" && rec.note.trim().length > 0;
|
|
2269
|
+
if (!isValid) {
|
|
2270
|
+
console.log("[APPOINTMENT_SERVICE] \u274C INVALID recommendation found:", rec);
|
|
2271
|
+
}
|
|
2272
|
+
return isValid;
|
|
2273
|
+
});
|
|
2266
2274
|
if (validRecommendations.length !== data.metadata.recommendedProcedures.length) {
|
|
2267
2275
|
console.log(
|
|
2268
|
-
`[APPOINTMENT_SERVICE] Removing ${data.metadata.recommendedProcedures.length - validRecommendations.length} invalid recommended procedures with empty notes`
|
|
2276
|
+
`[APPOINTMENT_SERVICE] \u{1F9F9} Removing ${data.metadata.recommendedProcedures.length - validRecommendations.length} invalid recommended procedures with empty notes`
|
|
2269
2277
|
);
|
|
2270
2278
|
data.metadata.recommendedProcedures = validRecommendations;
|
|
2279
|
+
} else {
|
|
2280
|
+
console.log("[APPOINTMENT_SERVICE] \u2705 All recommendedProcedures are valid");
|
|
2271
2281
|
}
|
|
2272
2282
|
}
|
|
2283
|
+
console.log(
|
|
2284
|
+
"[APPOINTMENT_SERVICE] \u{1F50D} AFTER CLEANUP - recommendedProcedures:",
|
|
2285
|
+
JSON.stringify((_d = data.metadata) == null ? void 0 : _d.recommendedProcedures, null, 2)
|
|
2286
|
+
);
|
|
2287
|
+
console.log("[APPOINTMENT_SERVICE] \u{1F50D} Starting Zod validation...");
|
|
2273
2288
|
const validatedData = await updateAppointmentSchema.parseAsync(data);
|
|
2289
|
+
console.log("[APPOINTMENT_SERVICE] \u2705 Zod validation passed!");
|
|
2274
2290
|
const updatedAppointment = await updateAppointmentUtil(this.db, appointmentId, validatedData);
|
|
2275
2291
|
console.log(`[APPOINTMENT_SERVICE] Appointment ${appointmentId} updated successfully`);
|
|
2276
2292
|
return updatedAppointment;
|
|
@@ -18943,8 +18959,14 @@ import {
|
|
|
18943
18959
|
updateDoc as updateDoc38,
|
|
18944
18960
|
where as where36,
|
|
18945
18961
|
arrayUnion as arrayUnion9,
|
|
18946
|
-
arrayRemove as arrayRemove8
|
|
18962
|
+
arrayRemove as arrayRemove8,
|
|
18963
|
+
writeBatch as writeBatch7
|
|
18947
18964
|
} from "firebase/firestore";
|
|
18965
|
+
|
|
18966
|
+
// src/backoffice/types/product.types.ts
|
|
18967
|
+
var PRODUCTS_COLLECTION = "products";
|
|
18968
|
+
|
|
18969
|
+
// src/backoffice/services/technology.service.ts
|
|
18948
18970
|
var DEFAULT_CERTIFICATION_REQUIREMENT = {
|
|
18949
18971
|
minimumLevel: "aesthetician" /* AESTHETICIAN */,
|
|
18950
18972
|
requiredSpecialties: []
|
|
@@ -19106,7 +19128,18 @@ var TechnologyService = class extends BaseService {
|
|
|
19106
19128
|
});
|
|
19107
19129
|
updateData.updatedAt = /* @__PURE__ */ new Date();
|
|
19108
19130
|
const docRef = doc41(this.technologiesRef, id);
|
|
19131
|
+
const beforeTech = await this.getById(id);
|
|
19109
19132
|
await updateDoc38(docRef, updateData);
|
|
19133
|
+
const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
|
|
19134
|
+
const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
|
|
19135
|
+
const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
|
|
19136
|
+
if (categoryChanged || subcategoryChanged || nameChanged) {
|
|
19137
|
+
await this.updateProductsInSubcollection(id, {
|
|
19138
|
+
categoryId: updateData.categoryId,
|
|
19139
|
+
subcategoryId: updateData.subcategoryId,
|
|
19140
|
+
technologyName: updateData.name
|
|
19141
|
+
});
|
|
19142
|
+
}
|
|
19110
19143
|
return this.getById(id);
|
|
19111
19144
|
}
|
|
19112
19145
|
/**
|
|
@@ -19542,6 +19575,125 @@ var TechnologyService = class extends BaseService {
|
|
|
19542
19575
|
})
|
|
19543
19576
|
);
|
|
19544
19577
|
}
|
|
19578
|
+
// ==========================================
|
|
19579
|
+
// NEW METHODS: Product assignment management
|
|
19580
|
+
// ==========================================
|
|
19581
|
+
/**
|
|
19582
|
+
* Assigns multiple products to a technology
|
|
19583
|
+
* Updates each product's assignedTechnologyIds array
|
|
19584
|
+
*/
|
|
19585
|
+
async assignProducts(technologyId, productIds) {
|
|
19586
|
+
const batch = writeBatch7(this.db);
|
|
19587
|
+
for (const productId of productIds) {
|
|
19588
|
+
const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
|
|
19589
|
+
batch.update(productRef, {
|
|
19590
|
+
assignedTechnologyIds: arrayUnion9(technologyId),
|
|
19591
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19592
|
+
});
|
|
19593
|
+
}
|
|
19594
|
+
await batch.commit();
|
|
19595
|
+
}
|
|
19596
|
+
/**
|
|
19597
|
+
* Unassigns multiple products from a technology
|
|
19598
|
+
* Updates each product's assignedTechnologyIds array
|
|
19599
|
+
*/
|
|
19600
|
+
async unassignProducts(technologyId, productIds) {
|
|
19601
|
+
const batch = writeBatch7(this.db);
|
|
19602
|
+
for (const productId of productIds) {
|
|
19603
|
+
const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
|
|
19604
|
+
batch.update(productRef, {
|
|
19605
|
+
assignedTechnologyIds: arrayRemove8(technologyId),
|
|
19606
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19607
|
+
});
|
|
19608
|
+
}
|
|
19609
|
+
await batch.commit();
|
|
19610
|
+
}
|
|
19611
|
+
/**
|
|
19612
|
+
* Gets products assigned to a specific technology
|
|
19613
|
+
* Reads from top-level collection for immediate consistency (Cloud Functions may lag)
|
|
19614
|
+
*/
|
|
19615
|
+
async getAssignedProducts(technologyId) {
|
|
19616
|
+
const q = query36(
|
|
19617
|
+
collection36(this.db, PRODUCTS_COLLECTION),
|
|
19618
|
+
where36("assignedTechnologyIds", "array-contains", technologyId),
|
|
19619
|
+
where36("isActive", "==", true),
|
|
19620
|
+
orderBy22("name")
|
|
19621
|
+
);
|
|
19622
|
+
const snapshot = await getDocs36(q);
|
|
19623
|
+
return snapshot.docs.map(
|
|
19624
|
+
(doc44) => ({
|
|
19625
|
+
id: doc44.id,
|
|
19626
|
+
...doc44.data()
|
|
19627
|
+
})
|
|
19628
|
+
);
|
|
19629
|
+
}
|
|
19630
|
+
/**
|
|
19631
|
+
* Gets products NOT assigned to a specific technology
|
|
19632
|
+
*/
|
|
19633
|
+
async getUnassignedProducts(technologyId) {
|
|
19634
|
+
const q = query36(
|
|
19635
|
+
collection36(this.db, PRODUCTS_COLLECTION),
|
|
19636
|
+
where36("isActive", "==", true),
|
|
19637
|
+
orderBy22("name")
|
|
19638
|
+
);
|
|
19639
|
+
const snapshot = await getDocs36(q);
|
|
19640
|
+
const allProducts = snapshot.docs.map(
|
|
19641
|
+
(doc44) => ({
|
|
19642
|
+
id: doc44.id,
|
|
19643
|
+
...doc44.data()
|
|
19644
|
+
})
|
|
19645
|
+
);
|
|
19646
|
+
return allProducts.filter(
|
|
19647
|
+
(product) => {
|
|
19648
|
+
var _a;
|
|
19649
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
19650
|
+
}
|
|
19651
|
+
);
|
|
19652
|
+
}
|
|
19653
|
+
/**
|
|
19654
|
+
* Gets product assignment statistics for a technology
|
|
19655
|
+
*/
|
|
19656
|
+
async getProductStats(technologyId) {
|
|
19657
|
+
const products = await this.getAssignedProducts(technologyId);
|
|
19658
|
+
const byBrand = {};
|
|
19659
|
+
products.forEach((product) => {
|
|
19660
|
+
byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
|
|
19661
|
+
});
|
|
19662
|
+
return {
|
|
19663
|
+
totalAssigned: products.length,
|
|
19664
|
+
byBrand
|
|
19665
|
+
};
|
|
19666
|
+
}
|
|
19667
|
+
/**
|
|
19668
|
+
* Updates products in technology subcollection when technology metadata changes
|
|
19669
|
+
* @param technologyId - ID of the technology
|
|
19670
|
+
* @param updates - Fields to update (categoryId, subcategoryId, technologyName)
|
|
19671
|
+
*/
|
|
19672
|
+
async updateProductsInSubcollection(technologyId, updates) {
|
|
19673
|
+
const productsRef = collection36(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
19674
|
+
const productsSnapshot = await getDocs36(productsRef);
|
|
19675
|
+
if (productsSnapshot.empty) {
|
|
19676
|
+
return;
|
|
19677
|
+
}
|
|
19678
|
+
const batch = writeBatch7(this.db);
|
|
19679
|
+
for (const productDoc of productsSnapshot.docs) {
|
|
19680
|
+
const productRef = productDoc.ref;
|
|
19681
|
+
const updateFields = {};
|
|
19682
|
+
if (updates.categoryId !== void 0) {
|
|
19683
|
+
updateFields.categoryId = updates.categoryId;
|
|
19684
|
+
}
|
|
19685
|
+
if (updates.subcategoryId !== void 0) {
|
|
19686
|
+
updateFields.subcategoryId = updates.subcategoryId;
|
|
19687
|
+
}
|
|
19688
|
+
if (updates.technologyName !== void 0) {
|
|
19689
|
+
updateFields.technologyName = updates.technologyName;
|
|
19690
|
+
}
|
|
19691
|
+
if (Object.keys(updateFields).length > 0) {
|
|
19692
|
+
batch.update(productRef, updateFields);
|
|
19693
|
+
}
|
|
19694
|
+
}
|
|
19695
|
+
await batch.commit();
|
|
19696
|
+
}
|
|
19545
19697
|
};
|
|
19546
19698
|
|
|
19547
19699
|
// src/backoffice/services/product.service.ts
|
|
@@ -19558,16 +19710,20 @@ import {
|
|
|
19558
19710
|
limit as limit21,
|
|
19559
19711
|
orderBy as orderBy23,
|
|
19560
19712
|
startAfter as startAfter19,
|
|
19561
|
-
getCountFromServer as getCountFromServer7
|
|
19713
|
+
getCountFromServer as getCountFromServer7,
|
|
19714
|
+
arrayUnion as arrayUnion10,
|
|
19715
|
+
arrayRemove as arrayRemove9
|
|
19562
19716
|
} from "firebase/firestore";
|
|
19563
|
-
|
|
19564
|
-
// src/backoffice/types/product.types.ts
|
|
19565
|
-
var PRODUCTS_COLLECTION = "products";
|
|
19566
|
-
|
|
19567
|
-
// src/backoffice/services/product.service.ts
|
|
19568
19717
|
var ProductService = class extends BaseService {
|
|
19569
19718
|
/**
|
|
19570
|
-
* Gets reference to products collection
|
|
19719
|
+
* Gets reference to top-level products collection (source of truth)
|
|
19720
|
+
* @returns Firestore collection reference
|
|
19721
|
+
*/
|
|
19722
|
+
getTopLevelProductsRef() {
|
|
19723
|
+
return collection37(this.db, PRODUCTS_COLLECTION);
|
|
19724
|
+
}
|
|
19725
|
+
/**
|
|
19726
|
+
* Gets reference to products collection under a technology (backward compatibility)
|
|
19571
19727
|
* @param technologyId - ID of the technology
|
|
19572
19728
|
* @returns Firestore collection reference
|
|
19573
19729
|
*/
|
|
@@ -19583,6 +19739,7 @@ var ProductService = class extends BaseService {
|
|
|
19583
19739
|
...product,
|
|
19584
19740
|
brandId,
|
|
19585
19741
|
technologyId,
|
|
19742
|
+
// Required for old subcollection structure
|
|
19586
19743
|
createdAt: now,
|
|
19587
19744
|
updatedAt: now,
|
|
19588
19745
|
isActive: true
|
|
@@ -19642,30 +19799,26 @@ var ProductService = class extends BaseService {
|
|
|
19642
19799
|
}
|
|
19643
19800
|
/**
|
|
19644
19801
|
* Gets counts of active products grouped by category, subcategory, and technology.
|
|
19645
|
-
*
|
|
19802
|
+
* Queries technology subcollections which have the legacy fields synced by Cloud Functions.
|
|
19646
19803
|
*/
|
|
19647
19804
|
async getProductCounts() {
|
|
19648
|
-
const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
|
|
19649
|
-
const snapshot = await getDocs37(q);
|
|
19650
19805
|
const counts = {
|
|
19651
19806
|
byCategory: {},
|
|
19652
19807
|
bySubcategory: {},
|
|
19653
19808
|
byTechnology: {}
|
|
19654
19809
|
};
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
}
|
|
19810
|
+
const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
|
|
19811
|
+
const snapshot = await getDocs37(q);
|
|
19658
19812
|
snapshot.docs.forEach((doc44) => {
|
|
19659
19813
|
const product = doc44.data();
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
|
|
19814
|
+
if (product.categoryId) {
|
|
19815
|
+
counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
|
|
19663
19816
|
}
|
|
19664
|
-
if (subcategoryId) {
|
|
19665
|
-
counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
|
|
19817
|
+
if (product.subcategoryId) {
|
|
19818
|
+
counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
|
|
19666
19819
|
}
|
|
19667
|
-
if (technologyId) {
|
|
19668
|
-
counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
|
|
19820
|
+
if (product.technologyId) {
|
|
19821
|
+
counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
|
|
19669
19822
|
}
|
|
19670
19823
|
});
|
|
19671
19824
|
return counts;
|
|
@@ -19744,12 +19897,166 @@ var ProductService = class extends BaseService {
|
|
|
19744
19897
|
...docSnap.data()
|
|
19745
19898
|
};
|
|
19746
19899
|
}
|
|
19900
|
+
// ==========================================
|
|
19901
|
+
// NEW METHODS: Top-level collection (preferred)
|
|
19902
|
+
// ==========================================
|
|
19903
|
+
/**
|
|
19904
|
+
* Creates a new product in the top-level collection
|
|
19905
|
+
*/
|
|
19906
|
+
async createTopLevel(brandId, product, technologyIds = []) {
|
|
19907
|
+
const now = /* @__PURE__ */ new Date();
|
|
19908
|
+
const newProduct = {
|
|
19909
|
+
...product,
|
|
19910
|
+
brandId,
|
|
19911
|
+
assignedTechnologyIds: technologyIds,
|
|
19912
|
+
createdAt: now,
|
|
19913
|
+
updatedAt: now,
|
|
19914
|
+
isActive: true
|
|
19915
|
+
};
|
|
19916
|
+
const productRef = await addDoc8(this.getTopLevelProductsRef(), newProduct);
|
|
19917
|
+
return { id: productRef.id, ...newProduct };
|
|
19918
|
+
}
|
|
19919
|
+
/**
|
|
19920
|
+
* Gets all products from the top-level collection
|
|
19921
|
+
*/
|
|
19922
|
+
async getAllTopLevel(options) {
|
|
19923
|
+
const { rowsPerPage, lastVisible, brandId } = options;
|
|
19924
|
+
const constraints = [where37("isActive", "==", true), orderBy23("name")];
|
|
19925
|
+
if (brandId) {
|
|
19926
|
+
constraints.push(where37("brandId", "==", brandId));
|
|
19927
|
+
}
|
|
19928
|
+
if (lastVisible) {
|
|
19929
|
+
constraints.push(startAfter19(lastVisible));
|
|
19930
|
+
}
|
|
19931
|
+
constraints.push(limit21(rowsPerPage));
|
|
19932
|
+
const q = query37(this.getTopLevelProductsRef(), ...constraints);
|
|
19933
|
+
const snapshot = await getDocs37(q);
|
|
19934
|
+
const products = snapshot.docs.map(
|
|
19935
|
+
(doc44) => ({
|
|
19936
|
+
id: doc44.id,
|
|
19937
|
+
...doc44.data()
|
|
19938
|
+
})
|
|
19939
|
+
);
|
|
19940
|
+
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
19941
|
+
return { products, lastVisible: newLastVisible };
|
|
19942
|
+
}
|
|
19943
|
+
/**
|
|
19944
|
+
* Gets a product by ID from the top-level collection
|
|
19945
|
+
*/
|
|
19946
|
+
async getByIdTopLevel(productId) {
|
|
19947
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19948
|
+
const docSnap = await getDoc43(docRef);
|
|
19949
|
+
if (!docSnap.exists()) return null;
|
|
19950
|
+
return {
|
|
19951
|
+
id: docSnap.id,
|
|
19952
|
+
...docSnap.data()
|
|
19953
|
+
};
|
|
19954
|
+
}
|
|
19955
|
+
/**
|
|
19956
|
+
* Updates a product in the top-level collection
|
|
19957
|
+
*/
|
|
19958
|
+
async updateTopLevel(productId, product) {
|
|
19959
|
+
const updateData = {
|
|
19960
|
+
...product,
|
|
19961
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19962
|
+
};
|
|
19963
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19964
|
+
await updateDoc39(docRef, updateData);
|
|
19965
|
+
return this.getByIdTopLevel(productId);
|
|
19966
|
+
}
|
|
19967
|
+
/**
|
|
19968
|
+
* Deletes a product from the top-level collection (soft delete)
|
|
19969
|
+
*/
|
|
19970
|
+
async deleteTopLevel(productId) {
|
|
19971
|
+
await this.updateTopLevel(productId, {
|
|
19972
|
+
isActive: false
|
|
19973
|
+
});
|
|
19974
|
+
}
|
|
19975
|
+
/**
|
|
19976
|
+
* Assigns a product to a technology
|
|
19977
|
+
*/
|
|
19978
|
+
async assignToTechnology(productId, technologyId) {
|
|
19979
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19980
|
+
await updateDoc39(docRef, {
|
|
19981
|
+
assignedTechnologyIds: arrayUnion10(technologyId),
|
|
19982
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19983
|
+
});
|
|
19984
|
+
}
|
|
19985
|
+
/**
|
|
19986
|
+
* Unassigns a product from a technology
|
|
19987
|
+
*/
|
|
19988
|
+
async unassignFromTechnology(productId, technologyId) {
|
|
19989
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19990
|
+
await updateDoc39(docRef, {
|
|
19991
|
+
assignedTechnologyIds: arrayRemove9(technologyId),
|
|
19992
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19993
|
+
});
|
|
19994
|
+
}
|
|
19995
|
+
/**
|
|
19996
|
+
* Gets products assigned to a specific technology
|
|
19997
|
+
*/
|
|
19998
|
+
async getAssignedProducts(technologyId) {
|
|
19999
|
+
const q = query37(
|
|
20000
|
+
this.getTopLevelProductsRef(),
|
|
20001
|
+
where37("assignedTechnologyIds", "array-contains", technologyId),
|
|
20002
|
+
where37("isActive", "==", true),
|
|
20003
|
+
orderBy23("name")
|
|
20004
|
+
);
|
|
20005
|
+
const snapshot = await getDocs37(q);
|
|
20006
|
+
return snapshot.docs.map(
|
|
20007
|
+
(doc44) => ({
|
|
20008
|
+
id: doc44.id,
|
|
20009
|
+
...doc44.data()
|
|
20010
|
+
})
|
|
20011
|
+
);
|
|
20012
|
+
}
|
|
20013
|
+
/**
|
|
20014
|
+
* Gets products NOT assigned to a specific technology
|
|
20015
|
+
*/
|
|
20016
|
+
async getUnassignedProducts(technologyId) {
|
|
20017
|
+
const q = query37(
|
|
20018
|
+
this.getTopLevelProductsRef(),
|
|
20019
|
+
where37("isActive", "==", true),
|
|
20020
|
+
orderBy23("name")
|
|
20021
|
+
);
|
|
20022
|
+
const snapshot = await getDocs37(q);
|
|
20023
|
+
const allProducts = snapshot.docs.map(
|
|
20024
|
+
(doc44) => ({
|
|
20025
|
+
id: doc44.id,
|
|
20026
|
+
...doc44.data()
|
|
20027
|
+
})
|
|
20028
|
+
);
|
|
20029
|
+
return allProducts.filter(
|
|
20030
|
+
(product) => {
|
|
20031
|
+
var _a;
|
|
20032
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
20033
|
+
}
|
|
20034
|
+
);
|
|
20035
|
+
}
|
|
20036
|
+
/**
|
|
20037
|
+
* Gets all products for a brand (from top-level collection)
|
|
20038
|
+
*/
|
|
20039
|
+
async getByBrand(brandId) {
|
|
20040
|
+
const q = query37(
|
|
20041
|
+
this.getTopLevelProductsRef(),
|
|
20042
|
+
where37("brandId", "==", brandId),
|
|
20043
|
+
where37("isActive", "==", true),
|
|
20044
|
+
orderBy23("name")
|
|
20045
|
+
);
|
|
20046
|
+
const snapshot = await getDocs37(q);
|
|
20047
|
+
return snapshot.docs.map(
|
|
20048
|
+
(doc44) => ({
|
|
20049
|
+
id: doc44.id,
|
|
20050
|
+
...doc44.data()
|
|
20051
|
+
})
|
|
20052
|
+
);
|
|
20053
|
+
}
|
|
19747
20054
|
};
|
|
19748
20055
|
|
|
19749
20056
|
// src/backoffice/services/constants.service.ts
|
|
19750
20057
|
import {
|
|
19751
|
-
arrayRemove as
|
|
19752
|
-
arrayUnion as
|
|
20058
|
+
arrayRemove as arrayRemove10,
|
|
20059
|
+
arrayUnion as arrayUnion11,
|
|
19753
20060
|
doc as doc43,
|
|
19754
20061
|
getDoc as getDoc44,
|
|
19755
20062
|
setDoc as setDoc30,
|
|
@@ -19817,7 +20124,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19817
20124
|
await setDoc30(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
|
|
19818
20125
|
} else {
|
|
19819
20126
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19820
|
-
benefits:
|
|
20127
|
+
benefits: arrayUnion11(newBenefit)
|
|
19821
20128
|
});
|
|
19822
20129
|
}
|
|
19823
20130
|
return newBenefit;
|
|
@@ -19871,7 +20178,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19871
20178
|
return;
|
|
19872
20179
|
}
|
|
19873
20180
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19874
|
-
benefits:
|
|
20181
|
+
benefits: arrayRemove10(benefitToRemove)
|
|
19875
20182
|
});
|
|
19876
20183
|
}
|
|
19877
20184
|
// =================================================================
|
|
@@ -19924,7 +20231,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19924
20231
|
});
|
|
19925
20232
|
} else {
|
|
19926
20233
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
19927
|
-
contraindications:
|
|
20234
|
+
contraindications: arrayUnion11(newContraindication)
|
|
19928
20235
|
});
|
|
19929
20236
|
}
|
|
19930
20237
|
return newContraindication;
|
|
@@ -19980,7 +20287,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19980
20287
|
return;
|
|
19981
20288
|
}
|
|
19982
20289
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
19983
|
-
contraindications:
|
|
20290
|
+
contraindications: arrayRemove10(toRemove)
|
|
19984
20291
|
});
|
|
19985
20292
|
}
|
|
19986
20293
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as admin from 'firebase-admin';
|
|
2
|
+
import { Product } from '../types/product.types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Migration script to copy existing products from technology subcollections
|
|
6
|
+
* to the new top-level products collection
|
|
7
|
+
*
|
|
8
|
+
* Usage: Run this once to migrate existing data
|
|
9
|
+
*/
|
|
10
|
+
export async function migrateProductsToTopLevel(db: admin.firestore.Firestore) {
|
|
11
|
+
console.log('🚀 Starting product migration...');
|
|
12
|
+
|
|
13
|
+
// Get all technologies
|
|
14
|
+
const technologiesSnapshot = await db.collection('technologies').get();
|
|
15
|
+
|
|
16
|
+
const productMap = new Map<string, {
|
|
17
|
+
product: any;
|
|
18
|
+
technologyIds: string[];
|
|
19
|
+
}>();
|
|
20
|
+
|
|
21
|
+
let totalProcessed = 0;
|
|
22
|
+
|
|
23
|
+
// Step 1: Collect all products from all technology subcollections
|
|
24
|
+
for (const techDoc of technologiesSnapshot.docs) {
|
|
25
|
+
const technologyId = techDoc.id;
|
|
26
|
+
console.log(`📦 Processing technology: ${technologyId}`);
|
|
27
|
+
|
|
28
|
+
const productsSnapshot = await db
|
|
29
|
+
.collection('technologies')
|
|
30
|
+
.doc(technologyId)
|
|
31
|
+
.collection('products')
|
|
32
|
+
.get();
|
|
33
|
+
|
|
34
|
+
for (const productDoc of productsSnapshot.docs) {
|
|
35
|
+
const productId = productDoc.id;
|
|
36
|
+
const productData = productDoc.data();
|
|
37
|
+
|
|
38
|
+
totalProcessed++;
|
|
39
|
+
|
|
40
|
+
// Deduplicate by name + brandId
|
|
41
|
+
const key = `${productData.name}_${productData.brandId}`;
|
|
42
|
+
|
|
43
|
+
if (productMap.has(key)) {
|
|
44
|
+
// Product already exists, just add this technology
|
|
45
|
+
productMap.get(key)!.technologyIds.push(technologyId);
|
|
46
|
+
} else {
|
|
47
|
+
// New product
|
|
48
|
+
productMap.set(key, {
|
|
49
|
+
product: productData,
|
|
50
|
+
technologyIds: [technologyId],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`✅ Found ${productMap.size} unique products from ${totalProcessed} total entries`);
|
|
57
|
+
|
|
58
|
+
// Step 2: Create products in top-level collection
|
|
59
|
+
const batch = db.batch();
|
|
60
|
+
let batchCount = 0;
|
|
61
|
+
const MAX_BATCH_SIZE = 500;
|
|
62
|
+
let createdCount = 0;
|
|
63
|
+
|
|
64
|
+
for (const [key, { product, technologyIds }] of productMap.entries()) {
|
|
65
|
+
const productRef = db.collection('products').doc();
|
|
66
|
+
|
|
67
|
+
const migratedProduct: any = {
|
|
68
|
+
...product,
|
|
69
|
+
assignedTechnologyIds: technologyIds, // Track all assigned technologies
|
|
70
|
+
migratedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
71
|
+
migrationKey: key, // For debugging
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
batch.set(productRef, migratedProduct);
|
|
75
|
+
createdCount++;
|
|
76
|
+
batchCount++;
|
|
77
|
+
|
|
78
|
+
if (batchCount >= MAX_BATCH_SIZE) {
|
|
79
|
+
await batch.commit();
|
|
80
|
+
console.log(`💾 Committed batch of ${batchCount} products (${createdCount}/${productMap.size})`);
|
|
81
|
+
batchCount = 0;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (batchCount > 0) {
|
|
86
|
+
await batch.commit();
|
|
87
|
+
console.log(`💾 Committed final batch of ${batchCount} products`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('✅ Migration complete!');
|
|
91
|
+
console.log(`📊 Summary:`);
|
|
92
|
+
console.log(` - Products processed: ${totalProcessed}`);
|
|
93
|
+
console.log(` - Unique products created: ${createdCount}`);
|
|
94
|
+
console.log(` - Average technologies per product: ${(createdCount > 0 ? totalProcessed / createdCount : 0).toFixed(2)}`);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
totalProcessed,
|
|
98
|
+
uniqueCreated: createdCount,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Run migration (for testing)
|
|
104
|
+
*/
|
|
105
|
+
if (require.main === module) {
|
|
106
|
+
(async () => {
|
|
107
|
+
try {
|
|
108
|
+
await migrateProductsToTopLevel(admin.firestore());
|
|
109
|
+
process.exit(0);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('❌ Migration failed:', error);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
}
|
|
116
|
+
|