@blackcode_sa/metaestetics-api 1.12.40 → 1.12.41
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 +178 -19
- package/dist/backoffice/index.d.ts +178 -19
- package/dist/backoffice/index.js +261 -17
- package/dist/backoffice/index.mjs +277 -30
- package/dist/index.d.mts +170 -11
- package/dist/index.d.ts +170 -11
- package/dist/index.js +288 -28
- package/dist/index.mjs +299 -36
- package/package.json +1 -1
- package/src/backoffice/services/migrate-products.ts +116 -0
- package/src/backoffice/services/product.service.ts +216 -21
- package/src/backoffice/services/technology.service.ts +111 -0
- package/src/backoffice/types/product.types.ts +115 -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: []
|
|
@@ -19542,6 +19564,95 @@ var TechnologyService = class extends BaseService {
|
|
|
19542
19564
|
})
|
|
19543
19565
|
);
|
|
19544
19566
|
}
|
|
19567
|
+
// ==========================================
|
|
19568
|
+
// NEW METHODS: Product assignment management
|
|
19569
|
+
// ==========================================
|
|
19570
|
+
/**
|
|
19571
|
+
* Assigns multiple products to a technology
|
|
19572
|
+
* Updates each product's assignedTechnologyIds array
|
|
19573
|
+
*/
|
|
19574
|
+
async assignProducts(technologyId, productIds) {
|
|
19575
|
+
const batch = writeBatch7(this.db);
|
|
19576
|
+
for (const productId of productIds) {
|
|
19577
|
+
const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
|
|
19578
|
+
batch.update(productRef, {
|
|
19579
|
+
assignedTechnologyIds: arrayUnion9(technologyId),
|
|
19580
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19581
|
+
});
|
|
19582
|
+
}
|
|
19583
|
+
await batch.commit();
|
|
19584
|
+
}
|
|
19585
|
+
/**
|
|
19586
|
+
* Unassigns multiple products from a technology
|
|
19587
|
+
* Updates each product's assignedTechnologyIds array
|
|
19588
|
+
*/
|
|
19589
|
+
async unassignProducts(technologyId, productIds) {
|
|
19590
|
+
const batch = writeBatch7(this.db);
|
|
19591
|
+
for (const productId of productIds) {
|
|
19592
|
+
const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
|
|
19593
|
+
batch.update(productRef, {
|
|
19594
|
+
assignedTechnologyIds: arrayRemove8(technologyId),
|
|
19595
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19596
|
+
});
|
|
19597
|
+
}
|
|
19598
|
+
await batch.commit();
|
|
19599
|
+
}
|
|
19600
|
+
/**
|
|
19601
|
+
* Gets products assigned to a specific technology
|
|
19602
|
+
* Reads from top-level collection for immediate consistency (Cloud Functions may lag)
|
|
19603
|
+
*/
|
|
19604
|
+
async getAssignedProducts(technologyId) {
|
|
19605
|
+
const q = query36(
|
|
19606
|
+
collection36(this.db, PRODUCTS_COLLECTION),
|
|
19607
|
+
where36("assignedTechnologyIds", "array-contains", technologyId),
|
|
19608
|
+
where36("isActive", "==", true),
|
|
19609
|
+
orderBy22("name")
|
|
19610
|
+
);
|
|
19611
|
+
const snapshot = await getDocs36(q);
|
|
19612
|
+
return snapshot.docs.map(
|
|
19613
|
+
(doc44) => ({
|
|
19614
|
+
id: doc44.id,
|
|
19615
|
+
...doc44.data()
|
|
19616
|
+
})
|
|
19617
|
+
);
|
|
19618
|
+
}
|
|
19619
|
+
/**
|
|
19620
|
+
* Gets products NOT assigned to a specific technology
|
|
19621
|
+
*/
|
|
19622
|
+
async getUnassignedProducts(technologyId) {
|
|
19623
|
+
const q = query36(
|
|
19624
|
+
collection36(this.db, PRODUCTS_COLLECTION),
|
|
19625
|
+
where36("isActive", "==", true),
|
|
19626
|
+
orderBy22("name")
|
|
19627
|
+
);
|
|
19628
|
+
const snapshot = await getDocs36(q);
|
|
19629
|
+
const allProducts = snapshot.docs.map(
|
|
19630
|
+
(doc44) => ({
|
|
19631
|
+
id: doc44.id,
|
|
19632
|
+
...doc44.data()
|
|
19633
|
+
})
|
|
19634
|
+
);
|
|
19635
|
+
return allProducts.filter(
|
|
19636
|
+
(product) => {
|
|
19637
|
+
var _a;
|
|
19638
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
19639
|
+
}
|
|
19640
|
+
);
|
|
19641
|
+
}
|
|
19642
|
+
/**
|
|
19643
|
+
* Gets product assignment statistics for a technology
|
|
19644
|
+
*/
|
|
19645
|
+
async getProductStats(technologyId) {
|
|
19646
|
+
const products = await this.getAssignedProducts(technologyId);
|
|
19647
|
+
const byBrand = {};
|
|
19648
|
+
products.forEach((product) => {
|
|
19649
|
+
byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
|
|
19650
|
+
});
|
|
19651
|
+
return {
|
|
19652
|
+
totalAssigned: products.length,
|
|
19653
|
+
byBrand
|
|
19654
|
+
};
|
|
19655
|
+
}
|
|
19545
19656
|
};
|
|
19546
19657
|
|
|
19547
19658
|
// src/backoffice/services/product.service.ts
|
|
@@ -19558,16 +19669,20 @@ import {
|
|
|
19558
19669
|
limit as limit21,
|
|
19559
19670
|
orderBy as orderBy23,
|
|
19560
19671
|
startAfter as startAfter19,
|
|
19561
|
-
getCountFromServer as getCountFromServer7
|
|
19672
|
+
getCountFromServer as getCountFromServer7,
|
|
19673
|
+
arrayUnion as arrayUnion10,
|
|
19674
|
+
arrayRemove as arrayRemove9
|
|
19562
19675
|
} 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
19676
|
var ProductService = class extends BaseService {
|
|
19569
19677
|
/**
|
|
19570
|
-
* Gets reference to products collection
|
|
19678
|
+
* Gets reference to top-level products collection (source of truth)
|
|
19679
|
+
* @returns Firestore collection reference
|
|
19680
|
+
*/
|
|
19681
|
+
getTopLevelProductsRef() {
|
|
19682
|
+
return collection37(this.db, PRODUCTS_COLLECTION);
|
|
19683
|
+
}
|
|
19684
|
+
/**
|
|
19685
|
+
* Gets reference to products collection under a technology (backward compatibility)
|
|
19571
19686
|
* @param technologyId - ID of the technology
|
|
19572
19687
|
* @returns Firestore collection reference
|
|
19573
19688
|
*/
|
|
@@ -19583,6 +19698,7 @@ var ProductService = class extends BaseService {
|
|
|
19583
19698
|
...product,
|
|
19584
19699
|
brandId,
|
|
19585
19700
|
technologyId,
|
|
19701
|
+
// Required for old subcollection structure
|
|
19586
19702
|
createdAt: now,
|
|
19587
19703
|
updatedAt: now,
|
|
19588
19704
|
isActive: true
|
|
@@ -19641,31 +19757,24 @@ var ProductService = class extends BaseService {
|
|
|
19641
19757
|
return snapshot.data().count;
|
|
19642
19758
|
}
|
|
19643
19759
|
/**
|
|
19644
|
-
* Gets counts of active products grouped by
|
|
19645
|
-
*
|
|
19760
|
+
* Gets counts of active products grouped by technology.
|
|
19761
|
+
* NOTE: Only counts top-level collection to avoid duplication during migration.
|
|
19762
|
+
* Categories/subcategories not available in top-level structure.
|
|
19646
19763
|
*/
|
|
19647
19764
|
async getProductCounts() {
|
|
19648
|
-
const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
|
|
19649
|
-
const snapshot = await getDocs37(q);
|
|
19650
19765
|
const counts = {
|
|
19651
19766
|
byCategory: {},
|
|
19652
19767
|
bySubcategory: {},
|
|
19653
19768
|
byTechnology: {}
|
|
19654
19769
|
};
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
}
|
|
19770
|
+
const q = query37(this.getTopLevelProductsRef(), where37("isActive", "==", true));
|
|
19771
|
+
const snapshot = await getDocs37(q);
|
|
19658
19772
|
snapshot.docs.forEach((doc44) => {
|
|
19659
19773
|
const product = doc44.data();
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
|
|
19663
|
-
|
|
19664
|
-
if (subcategoryId) {
|
|
19665
|
-
counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
|
|
19666
|
-
}
|
|
19667
|
-
if (technologyId) {
|
|
19668
|
-
counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
|
|
19774
|
+
if (product.assignedTechnologyIds && Array.isArray(product.assignedTechnologyIds)) {
|
|
19775
|
+
product.assignedTechnologyIds.forEach((techId) => {
|
|
19776
|
+
counts.byTechnology[techId] = (counts.byTechnology[techId] || 0) + 1;
|
|
19777
|
+
});
|
|
19669
19778
|
}
|
|
19670
19779
|
});
|
|
19671
19780
|
return counts;
|
|
@@ -19744,12 +19853,166 @@ var ProductService = class extends BaseService {
|
|
|
19744
19853
|
...docSnap.data()
|
|
19745
19854
|
};
|
|
19746
19855
|
}
|
|
19856
|
+
// ==========================================
|
|
19857
|
+
// NEW METHODS: Top-level collection (preferred)
|
|
19858
|
+
// ==========================================
|
|
19859
|
+
/**
|
|
19860
|
+
* Creates a new product in the top-level collection
|
|
19861
|
+
*/
|
|
19862
|
+
async createTopLevel(brandId, product, technologyIds = []) {
|
|
19863
|
+
const now = /* @__PURE__ */ new Date();
|
|
19864
|
+
const newProduct = {
|
|
19865
|
+
...product,
|
|
19866
|
+
brandId,
|
|
19867
|
+
assignedTechnologyIds: technologyIds,
|
|
19868
|
+
createdAt: now,
|
|
19869
|
+
updatedAt: now,
|
|
19870
|
+
isActive: true
|
|
19871
|
+
};
|
|
19872
|
+
const productRef = await addDoc8(this.getTopLevelProductsRef(), newProduct);
|
|
19873
|
+
return { id: productRef.id, ...newProduct };
|
|
19874
|
+
}
|
|
19875
|
+
/**
|
|
19876
|
+
* Gets all products from the top-level collection
|
|
19877
|
+
*/
|
|
19878
|
+
async getAllTopLevel(options) {
|
|
19879
|
+
const { rowsPerPage, lastVisible, brandId } = options;
|
|
19880
|
+
const constraints = [where37("isActive", "==", true), orderBy23("name")];
|
|
19881
|
+
if (brandId) {
|
|
19882
|
+
constraints.push(where37("brandId", "==", brandId));
|
|
19883
|
+
}
|
|
19884
|
+
if (lastVisible) {
|
|
19885
|
+
constraints.push(startAfter19(lastVisible));
|
|
19886
|
+
}
|
|
19887
|
+
constraints.push(limit21(rowsPerPage));
|
|
19888
|
+
const q = query37(this.getTopLevelProductsRef(), ...constraints);
|
|
19889
|
+
const snapshot = await getDocs37(q);
|
|
19890
|
+
const products = snapshot.docs.map(
|
|
19891
|
+
(doc44) => ({
|
|
19892
|
+
id: doc44.id,
|
|
19893
|
+
...doc44.data()
|
|
19894
|
+
})
|
|
19895
|
+
);
|
|
19896
|
+
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
19897
|
+
return { products, lastVisible: newLastVisible };
|
|
19898
|
+
}
|
|
19899
|
+
/**
|
|
19900
|
+
* Gets a product by ID from the top-level collection
|
|
19901
|
+
*/
|
|
19902
|
+
async getByIdTopLevel(productId) {
|
|
19903
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19904
|
+
const docSnap = await getDoc43(docRef);
|
|
19905
|
+
if (!docSnap.exists()) return null;
|
|
19906
|
+
return {
|
|
19907
|
+
id: docSnap.id,
|
|
19908
|
+
...docSnap.data()
|
|
19909
|
+
};
|
|
19910
|
+
}
|
|
19911
|
+
/**
|
|
19912
|
+
* Updates a product in the top-level collection
|
|
19913
|
+
*/
|
|
19914
|
+
async updateTopLevel(productId, product) {
|
|
19915
|
+
const updateData = {
|
|
19916
|
+
...product,
|
|
19917
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19918
|
+
};
|
|
19919
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19920
|
+
await updateDoc39(docRef, updateData);
|
|
19921
|
+
return this.getByIdTopLevel(productId);
|
|
19922
|
+
}
|
|
19923
|
+
/**
|
|
19924
|
+
* Deletes a product from the top-level collection (soft delete)
|
|
19925
|
+
*/
|
|
19926
|
+
async deleteTopLevel(productId) {
|
|
19927
|
+
await this.updateTopLevel(productId, {
|
|
19928
|
+
isActive: false
|
|
19929
|
+
});
|
|
19930
|
+
}
|
|
19931
|
+
/**
|
|
19932
|
+
* Assigns a product to a technology
|
|
19933
|
+
*/
|
|
19934
|
+
async assignToTechnology(productId, technologyId) {
|
|
19935
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19936
|
+
await updateDoc39(docRef, {
|
|
19937
|
+
assignedTechnologyIds: arrayUnion10(technologyId),
|
|
19938
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19939
|
+
});
|
|
19940
|
+
}
|
|
19941
|
+
/**
|
|
19942
|
+
* Unassigns a product from a technology
|
|
19943
|
+
*/
|
|
19944
|
+
async unassignFromTechnology(productId, technologyId) {
|
|
19945
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
19946
|
+
await updateDoc39(docRef, {
|
|
19947
|
+
assignedTechnologyIds: arrayRemove9(technologyId),
|
|
19948
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19949
|
+
});
|
|
19950
|
+
}
|
|
19951
|
+
/**
|
|
19952
|
+
* Gets products assigned to a specific technology
|
|
19953
|
+
*/
|
|
19954
|
+
async getAssignedProducts(technologyId) {
|
|
19955
|
+
const q = query37(
|
|
19956
|
+
this.getTopLevelProductsRef(),
|
|
19957
|
+
where37("assignedTechnologyIds", "array-contains", technologyId),
|
|
19958
|
+
where37("isActive", "==", true),
|
|
19959
|
+
orderBy23("name")
|
|
19960
|
+
);
|
|
19961
|
+
const snapshot = await getDocs37(q);
|
|
19962
|
+
return snapshot.docs.map(
|
|
19963
|
+
(doc44) => ({
|
|
19964
|
+
id: doc44.id,
|
|
19965
|
+
...doc44.data()
|
|
19966
|
+
})
|
|
19967
|
+
);
|
|
19968
|
+
}
|
|
19969
|
+
/**
|
|
19970
|
+
* Gets products NOT assigned to a specific technology
|
|
19971
|
+
*/
|
|
19972
|
+
async getUnassignedProducts(technologyId) {
|
|
19973
|
+
const q = query37(
|
|
19974
|
+
this.getTopLevelProductsRef(),
|
|
19975
|
+
where37("isActive", "==", true),
|
|
19976
|
+
orderBy23("name")
|
|
19977
|
+
);
|
|
19978
|
+
const snapshot = await getDocs37(q);
|
|
19979
|
+
const allProducts = snapshot.docs.map(
|
|
19980
|
+
(doc44) => ({
|
|
19981
|
+
id: doc44.id,
|
|
19982
|
+
...doc44.data()
|
|
19983
|
+
})
|
|
19984
|
+
);
|
|
19985
|
+
return allProducts.filter(
|
|
19986
|
+
(product) => {
|
|
19987
|
+
var _a;
|
|
19988
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
19989
|
+
}
|
|
19990
|
+
);
|
|
19991
|
+
}
|
|
19992
|
+
/**
|
|
19993
|
+
* Gets all products for a brand (from top-level collection)
|
|
19994
|
+
*/
|
|
19995
|
+
async getByBrand(brandId) {
|
|
19996
|
+
const q = query37(
|
|
19997
|
+
this.getTopLevelProductsRef(),
|
|
19998
|
+
where37("brandId", "==", brandId),
|
|
19999
|
+
where37("isActive", "==", true),
|
|
20000
|
+
orderBy23("name")
|
|
20001
|
+
);
|
|
20002
|
+
const snapshot = await getDocs37(q);
|
|
20003
|
+
return snapshot.docs.map(
|
|
20004
|
+
(doc44) => ({
|
|
20005
|
+
id: doc44.id,
|
|
20006
|
+
...doc44.data()
|
|
20007
|
+
})
|
|
20008
|
+
);
|
|
20009
|
+
}
|
|
19747
20010
|
};
|
|
19748
20011
|
|
|
19749
20012
|
// src/backoffice/services/constants.service.ts
|
|
19750
20013
|
import {
|
|
19751
|
-
arrayRemove as
|
|
19752
|
-
arrayUnion as
|
|
20014
|
+
arrayRemove as arrayRemove10,
|
|
20015
|
+
arrayUnion as arrayUnion11,
|
|
19753
20016
|
doc as doc43,
|
|
19754
20017
|
getDoc as getDoc44,
|
|
19755
20018
|
setDoc as setDoc30,
|
|
@@ -19817,7 +20080,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19817
20080
|
await setDoc30(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
|
|
19818
20081
|
} else {
|
|
19819
20082
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19820
|
-
benefits:
|
|
20083
|
+
benefits: arrayUnion11(newBenefit)
|
|
19821
20084
|
});
|
|
19822
20085
|
}
|
|
19823
20086
|
return newBenefit;
|
|
@@ -19871,7 +20134,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19871
20134
|
return;
|
|
19872
20135
|
}
|
|
19873
20136
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19874
|
-
benefits:
|
|
20137
|
+
benefits: arrayRemove10(benefitToRemove)
|
|
19875
20138
|
});
|
|
19876
20139
|
}
|
|
19877
20140
|
// =================================================================
|
|
@@ -19924,7 +20187,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19924
20187
|
});
|
|
19925
20188
|
} else {
|
|
19926
20189
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
19927
|
-
contraindications:
|
|
20190
|
+
contraindications: arrayUnion11(newContraindication)
|
|
19928
20191
|
});
|
|
19929
20192
|
}
|
|
19930
20193
|
return newContraindication;
|
|
@@ -19980,7 +20243,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19980
20243
|
return;
|
|
19981
20244
|
}
|
|
19982
20245
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
19983
|
-
contraindications:
|
|
20246
|
+
contraindications: arrayRemove10(toRemove)
|
|
19984
20247
|
});
|
|
19985
20248
|
}
|
|
19986
20249
|
};
|
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
|
+
|