@blackcode_sa/metaestetics-api 1.12.39 → 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 +346 -25
- package/dist/index.mjs +357 -33
- 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 +120 -16
package/dist/index.mjs
CHANGED
|
@@ -2236,9 +2236,57 @@ var AppointmentService = class extends BaseService {
|
|
|
2236
2236
|
* @returns The updated appointment
|
|
2237
2237
|
*/
|
|
2238
2238
|
async updateAppointment(appointmentId, data) {
|
|
2239
|
+
var _a, _b, _c, _d;
|
|
2239
2240
|
try {
|
|
2240
2241
|
console.log(`[APPOINTMENT_SERVICE] Updating appointment with ID: ${appointmentId}`);
|
|
2242
|
+
if ((_a = data.metadata) == null ? void 0 : _a.zonePhotos) {
|
|
2243
|
+
const migratedZonePhotos = {};
|
|
2244
|
+
for (const [key, value] of Object.entries(data.metadata.zonePhotos)) {
|
|
2245
|
+
if (Array.isArray(value)) {
|
|
2246
|
+
migratedZonePhotos[key] = value;
|
|
2247
|
+
} else {
|
|
2248
|
+
console.log(`[APPOINTMENT_SERVICE] Auto-migrating ${key} from object to array format`);
|
|
2249
|
+
const oldData = value;
|
|
2250
|
+
migratedZonePhotos[key] = [
|
|
2251
|
+
{
|
|
2252
|
+
before: oldData.before || null,
|
|
2253
|
+
after: oldData.after || null,
|
|
2254
|
+
beforeNote: null,
|
|
2255
|
+
afterNote: null
|
|
2256
|
+
}
|
|
2257
|
+
];
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
data.metadata.zonePhotos = migratedZonePhotos;
|
|
2261
|
+
}
|
|
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
|
+
});
|
|
2274
|
+
if (validRecommendations.length !== data.metadata.recommendedProcedures.length) {
|
|
2275
|
+
console.log(
|
|
2276
|
+
`[APPOINTMENT_SERVICE] \u{1F9F9} Removing ${data.metadata.recommendedProcedures.length - validRecommendations.length} invalid recommended procedures with empty notes`
|
|
2277
|
+
);
|
|
2278
|
+
data.metadata.recommendedProcedures = validRecommendations;
|
|
2279
|
+
} else {
|
|
2280
|
+
console.log("[APPOINTMENT_SERVICE] \u2705 All recommendedProcedures are valid");
|
|
2281
|
+
}
|
|
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...");
|
|
2241
2288
|
const validatedData = await updateAppointmentSchema.parseAsync(data);
|
|
2289
|
+
console.log("[APPOINTMENT_SERVICE] \u2705 Zod validation passed!");
|
|
2242
2290
|
const updatedAppointment = await updateAppointmentUtil(this.db, appointmentId, validatedData);
|
|
2243
2291
|
console.log(`[APPOINTMENT_SERVICE] Appointment ${appointmentId} updated successfully`);
|
|
2244
2292
|
return updatedAppointment;
|
|
@@ -2876,7 +2924,25 @@ var AppointmentService = class extends BaseService {
|
|
|
2876
2924
|
finalbilling: null,
|
|
2877
2925
|
finalizationNotes: null
|
|
2878
2926
|
};
|
|
2879
|
-
|
|
2927
|
+
let currentZonePhotos = {};
|
|
2928
|
+
if (currentMetadata.zonePhotos) {
|
|
2929
|
+
for (const [key, value] of Object.entries(currentMetadata.zonePhotos)) {
|
|
2930
|
+
if (Array.isArray(value)) {
|
|
2931
|
+
currentZonePhotos[key] = value;
|
|
2932
|
+
} else {
|
|
2933
|
+
console.log(`[APPOINTMENT_SERVICE] Auto-migrating ${key} from object to array format`);
|
|
2934
|
+
const oldData = value;
|
|
2935
|
+
currentZonePhotos[key] = [
|
|
2936
|
+
{
|
|
2937
|
+
before: oldData.before || null,
|
|
2938
|
+
after: oldData.after || null,
|
|
2939
|
+
beforeNote: null,
|
|
2940
|
+
afterNote: null
|
|
2941
|
+
}
|
|
2942
|
+
];
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2880
2946
|
if (!currentZonePhotos[zoneId]) {
|
|
2881
2947
|
currentZonePhotos[zoneId] = [];
|
|
2882
2948
|
}
|
|
@@ -3275,7 +3341,13 @@ var AppointmentService = class extends BaseService {
|
|
|
3275
3341
|
console.log(
|
|
3276
3342
|
`[APPOINTMENT_SERVICE] Adding recommended procedure ${procedureId} to appointment ${appointmentId}`
|
|
3277
3343
|
);
|
|
3278
|
-
return await addRecommendedProcedureUtil(
|
|
3344
|
+
return await addRecommendedProcedureUtil(
|
|
3345
|
+
this.db,
|
|
3346
|
+
appointmentId,
|
|
3347
|
+
procedureId,
|
|
3348
|
+
note,
|
|
3349
|
+
timeframe
|
|
3350
|
+
);
|
|
3279
3351
|
} catch (error) {
|
|
3280
3352
|
console.error(`[APPOINTMENT_SERVICE] Error adding recommended procedure:`, error);
|
|
3281
3353
|
throw error;
|
|
@@ -3312,7 +3384,12 @@ var AppointmentService = class extends BaseService {
|
|
|
3312
3384
|
console.log(
|
|
3313
3385
|
`[APPOINTMENT_SERVICE] Updating recommended procedure at index ${recommendationIndex} in appointment ${appointmentId}`
|
|
3314
3386
|
);
|
|
3315
|
-
return await updateRecommendedProcedureUtil(
|
|
3387
|
+
return await updateRecommendedProcedureUtil(
|
|
3388
|
+
this.db,
|
|
3389
|
+
appointmentId,
|
|
3390
|
+
recommendationIndex,
|
|
3391
|
+
updates
|
|
3392
|
+
);
|
|
3316
3393
|
} catch (error) {
|
|
3317
3394
|
console.error(`[APPOINTMENT_SERVICE] Error updating recommended procedure:`, error);
|
|
3318
3395
|
throw error;
|
|
@@ -18882,8 +18959,14 @@ import {
|
|
|
18882
18959
|
updateDoc as updateDoc38,
|
|
18883
18960
|
where as where36,
|
|
18884
18961
|
arrayUnion as arrayUnion9,
|
|
18885
|
-
arrayRemove as arrayRemove8
|
|
18962
|
+
arrayRemove as arrayRemove8,
|
|
18963
|
+
writeBatch as writeBatch7
|
|
18886
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
|
|
18887
18970
|
var DEFAULT_CERTIFICATION_REQUIREMENT = {
|
|
18888
18971
|
minimumLevel: "aesthetician" /* AESTHETICIAN */,
|
|
18889
18972
|
requiredSpecialties: []
|
|
@@ -19481,6 +19564,95 @@ var TechnologyService = class extends BaseService {
|
|
|
19481
19564
|
})
|
|
19482
19565
|
);
|
|
19483
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
|
+
}
|
|
19484
19656
|
};
|
|
19485
19657
|
|
|
19486
19658
|
// src/backoffice/services/product.service.ts
|
|
@@ -19497,16 +19669,20 @@ import {
|
|
|
19497
19669
|
limit as limit21,
|
|
19498
19670
|
orderBy as orderBy23,
|
|
19499
19671
|
startAfter as startAfter19,
|
|
19500
|
-
getCountFromServer as getCountFromServer7
|
|
19672
|
+
getCountFromServer as getCountFromServer7,
|
|
19673
|
+
arrayUnion as arrayUnion10,
|
|
19674
|
+
arrayRemove as arrayRemove9
|
|
19501
19675
|
} from "firebase/firestore";
|
|
19502
|
-
|
|
19503
|
-
// src/backoffice/types/product.types.ts
|
|
19504
|
-
var PRODUCTS_COLLECTION = "products";
|
|
19505
|
-
|
|
19506
|
-
// src/backoffice/services/product.service.ts
|
|
19507
19676
|
var ProductService = class extends BaseService {
|
|
19508
19677
|
/**
|
|
19509
|
-
* 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)
|
|
19510
19686
|
* @param technologyId - ID of the technology
|
|
19511
19687
|
* @returns Firestore collection reference
|
|
19512
19688
|
*/
|
|
@@ -19522,6 +19698,7 @@ var ProductService = class extends BaseService {
|
|
|
19522
19698
|
...product,
|
|
19523
19699
|
brandId,
|
|
19524
19700
|
technologyId,
|
|
19701
|
+
// Required for old subcollection structure
|
|
19525
19702
|
createdAt: now,
|
|
19526
19703
|
updatedAt: now,
|
|
19527
19704
|
isActive: true
|
|
@@ -19580,31 +19757,24 @@ var ProductService = class extends BaseService {
|
|
|
19580
19757
|
return snapshot.data().count;
|
|
19581
19758
|
}
|
|
19582
19759
|
/**
|
|
19583
|
-
* Gets counts of active products grouped by
|
|
19584
|
-
*
|
|
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.
|
|
19585
19763
|
*/
|
|
19586
19764
|
async getProductCounts() {
|
|
19587
|
-
const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
|
|
19588
|
-
const snapshot = await getDocs37(q);
|
|
19589
19765
|
const counts = {
|
|
19590
19766
|
byCategory: {},
|
|
19591
19767
|
bySubcategory: {},
|
|
19592
19768
|
byTechnology: {}
|
|
19593
19769
|
};
|
|
19594
|
-
|
|
19595
|
-
|
|
19596
|
-
}
|
|
19770
|
+
const q = query37(this.getTopLevelProductsRef(), where37("isActive", "==", true));
|
|
19771
|
+
const snapshot = await getDocs37(q);
|
|
19597
19772
|
snapshot.docs.forEach((doc44) => {
|
|
19598
19773
|
const product = doc44.data();
|
|
19599
|
-
|
|
19600
|
-
|
|
19601
|
-
|
|
19602
|
-
|
|
19603
|
-
if (subcategoryId) {
|
|
19604
|
-
counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
|
|
19605
|
-
}
|
|
19606
|
-
if (technologyId) {
|
|
19607
|
-
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
|
+
});
|
|
19608
19778
|
}
|
|
19609
19779
|
});
|
|
19610
19780
|
return counts;
|
|
@@ -19683,12 +19853,166 @@ var ProductService = class extends BaseService {
|
|
|
19683
19853
|
...docSnap.data()
|
|
19684
19854
|
};
|
|
19685
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
|
+
}
|
|
19686
20010
|
};
|
|
19687
20011
|
|
|
19688
20012
|
// src/backoffice/services/constants.service.ts
|
|
19689
20013
|
import {
|
|
19690
|
-
arrayRemove as
|
|
19691
|
-
arrayUnion as
|
|
20014
|
+
arrayRemove as arrayRemove10,
|
|
20015
|
+
arrayUnion as arrayUnion11,
|
|
19692
20016
|
doc as doc43,
|
|
19693
20017
|
getDoc as getDoc44,
|
|
19694
20018
|
setDoc as setDoc30,
|
|
@@ -19756,7 +20080,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19756
20080
|
await setDoc30(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
|
|
19757
20081
|
} else {
|
|
19758
20082
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19759
|
-
benefits:
|
|
20083
|
+
benefits: arrayUnion11(newBenefit)
|
|
19760
20084
|
});
|
|
19761
20085
|
}
|
|
19762
20086
|
return newBenefit;
|
|
@@ -19810,7 +20134,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19810
20134
|
return;
|
|
19811
20135
|
}
|
|
19812
20136
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19813
|
-
benefits:
|
|
20137
|
+
benefits: arrayRemove10(benefitToRemove)
|
|
19814
20138
|
});
|
|
19815
20139
|
}
|
|
19816
20140
|
// =================================================================
|
|
@@ -19863,7 +20187,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19863
20187
|
});
|
|
19864
20188
|
} else {
|
|
19865
20189
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
19866
|
-
contraindications:
|
|
20190
|
+
contraindications: arrayUnion11(newContraindication)
|
|
19867
20191
|
});
|
|
19868
20192
|
}
|
|
19869
20193
|
return newContraindication;
|
|
@@ -19919,7 +20243,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19919
20243
|
return;
|
|
19920
20244
|
}
|
|
19921
20245
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
19922
|
-
contraindications:
|
|
20246
|
+
contraindications: arrayRemove10(toRemove)
|
|
19923
20247
|
});
|
|
19924
20248
|
}
|
|
19925
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
|
+
|