@blackcode_sa/metaestetics-api 1.12.50 → 1.12.51
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 +275 -18
- package/dist/backoffice/index.d.ts +275 -18
- package/dist/backoffice/index.js +802 -14
- package/dist/backoffice/index.mjs +830 -38
- package/dist/index.d.mts +255 -10
- package/dist/index.d.ts +255 -10
- package/dist/index.js +748 -19
- package/dist/index.mjs +759 -27
- package/package.json +1 -1
- package/src/backoffice/services/brand.service.ts +86 -0
- package/src/backoffice/services/category.service.ts +84 -0
- package/src/backoffice/services/constants.service.ts +77 -0
- package/src/backoffice/services/migrate-products.ts +116 -0
- package/src/backoffice/services/product.service.ts +316 -18
- package/src/backoffice/services/requirement.service.ts +76 -0
- package/src/backoffice/services/subcategory.service.ts +87 -0
- package/src/backoffice/services/technology.service.ts +289 -0
- package/src/backoffice/types/product.types.ts +116 -6
- package/src/services/procedure/procedure.service.ts +2 -2
package/dist/backoffice/index.js
CHANGED
|
@@ -261,6 +261,73 @@ var BrandService = class extends BaseService {
|
|
|
261
261
|
...docSnap.data()
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Exports brands to CSV string, suitable for Excel/Sheets.
|
|
266
|
+
* Includes headers and optional UTF-8 BOM.
|
|
267
|
+
* By default exports only active brands (set includeInactive to true to export all).
|
|
268
|
+
*/
|
|
269
|
+
async exportToCsv(options) {
|
|
270
|
+
var _a, _b;
|
|
271
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
272
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
273
|
+
const headers = [
|
|
274
|
+
"id",
|
|
275
|
+
"name",
|
|
276
|
+
"manufacturer",
|
|
277
|
+
"website",
|
|
278
|
+
"description",
|
|
279
|
+
"isActive"
|
|
280
|
+
];
|
|
281
|
+
const rows = [];
|
|
282
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
283
|
+
const PAGE_SIZE = 1e3;
|
|
284
|
+
let cursor;
|
|
285
|
+
const baseConstraints = [];
|
|
286
|
+
if (!includeInactive) {
|
|
287
|
+
baseConstraints.push((0, import_firestore.where)("isActive", "==", true));
|
|
288
|
+
}
|
|
289
|
+
baseConstraints.push((0, import_firestore.orderBy)("name_lowercase"));
|
|
290
|
+
while (true) {
|
|
291
|
+
const constraints = [...baseConstraints, (0, import_firestore.limit)(PAGE_SIZE)];
|
|
292
|
+
if (cursor) constraints.push((0, import_firestore.startAfter)(cursor));
|
|
293
|
+
const q = (0, import_firestore.query)(this.getBrandsRef(), ...constraints);
|
|
294
|
+
const snapshot = await (0, import_firestore.getDocs)(q);
|
|
295
|
+
if (snapshot.empty) break;
|
|
296
|
+
for (const d of snapshot.docs) {
|
|
297
|
+
const brand = { id: d.id, ...d.data() };
|
|
298
|
+
rows.push(this.brandToCsvRow(brand));
|
|
299
|
+
}
|
|
300
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
301
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
302
|
+
}
|
|
303
|
+
const csvBody = rows.join("\r\n");
|
|
304
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
305
|
+
}
|
|
306
|
+
brandToCsvRow(brand) {
|
|
307
|
+
var _a, _b, _c, _d, _e, _f;
|
|
308
|
+
const values = [
|
|
309
|
+
(_a = brand.id) != null ? _a : "",
|
|
310
|
+
(_b = brand.name) != null ? _b : "",
|
|
311
|
+
(_c = brand.manufacturer) != null ? _c : "",
|
|
312
|
+
(_d = brand.website) != null ? _d : "",
|
|
313
|
+
(_e = brand.description) != null ? _e : "",
|
|
314
|
+
String((_f = brand.isActive) != null ? _f : "")
|
|
315
|
+
];
|
|
316
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
317
|
+
}
|
|
318
|
+
formatDateIso(value) {
|
|
319
|
+
if (value instanceof Date) return value.toISOString();
|
|
320
|
+
if (value && typeof value.toDate === "function") {
|
|
321
|
+
const d = value.toDate();
|
|
322
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
323
|
+
}
|
|
324
|
+
return String(value != null ? value : "");
|
|
325
|
+
}
|
|
326
|
+
formatCsvValue(value) {
|
|
327
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
328
|
+
const escaped = str.replace(/"/g, '""');
|
|
329
|
+
return `"${escaped}"`;
|
|
330
|
+
}
|
|
264
331
|
};
|
|
265
332
|
|
|
266
333
|
// src/backoffice/services/category.service.ts
|
|
@@ -446,6 +513,71 @@ var CategoryService = class extends BaseService {
|
|
|
446
513
|
...docSnap.data()
|
|
447
514
|
};
|
|
448
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Exports categories to CSV string, suitable for Excel/Sheets.
|
|
518
|
+
* Includes headers and optional UTF-8 BOM.
|
|
519
|
+
* By default exports only active categories (set includeInactive to true to export all).
|
|
520
|
+
*/
|
|
521
|
+
async exportToCsv(options) {
|
|
522
|
+
var _a, _b;
|
|
523
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
524
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
525
|
+
const headers = [
|
|
526
|
+
"id",
|
|
527
|
+
"name",
|
|
528
|
+
"description",
|
|
529
|
+
"family",
|
|
530
|
+
"isActive"
|
|
531
|
+
];
|
|
532
|
+
const rows = [];
|
|
533
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
534
|
+
const PAGE_SIZE = 1e3;
|
|
535
|
+
let cursor;
|
|
536
|
+
const constraints = [];
|
|
537
|
+
if (!includeInactive) {
|
|
538
|
+
constraints.push((0, import_firestore2.where)("isActive", "==", true));
|
|
539
|
+
}
|
|
540
|
+
constraints.push((0, import_firestore2.orderBy)("name"));
|
|
541
|
+
while (true) {
|
|
542
|
+
const queryConstraints = [...constraints, (0, import_firestore2.limit)(PAGE_SIZE)];
|
|
543
|
+
if (cursor) queryConstraints.push((0, import_firestore2.startAfter)(cursor));
|
|
544
|
+
const q = (0, import_firestore2.query)(this.categoriesRef, ...queryConstraints);
|
|
545
|
+
const snapshot = await (0, import_firestore2.getDocs)(q);
|
|
546
|
+
if (snapshot.empty) break;
|
|
547
|
+
for (const d of snapshot.docs) {
|
|
548
|
+
const category = { id: d.id, ...d.data() };
|
|
549
|
+
rows.push(this.categoryToCsvRow(category));
|
|
550
|
+
}
|
|
551
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
552
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
553
|
+
}
|
|
554
|
+
const csvBody = rows.join("\r\n");
|
|
555
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
556
|
+
}
|
|
557
|
+
categoryToCsvRow(category) {
|
|
558
|
+
var _a, _b, _c, _d, _e;
|
|
559
|
+
const values = [
|
|
560
|
+
(_a = category.id) != null ? _a : "",
|
|
561
|
+
(_b = category.name) != null ? _b : "",
|
|
562
|
+
(_c = category.description) != null ? _c : "",
|
|
563
|
+
(_d = category.family) != null ? _d : "",
|
|
564
|
+
String((_e = category.isActive) != null ? _e : "")
|
|
565
|
+
];
|
|
566
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
567
|
+
}
|
|
568
|
+
formatDateIso(value) {
|
|
569
|
+
if (value instanceof Date) return value.toISOString();
|
|
570
|
+
if (value && typeof value.toDate === "function") {
|
|
571
|
+
const d = value.toDate();
|
|
572
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
573
|
+
}
|
|
574
|
+
return String(value != null ? value : "");
|
|
575
|
+
}
|
|
576
|
+
formatCsvValue(value) {
|
|
577
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
578
|
+
const escaped = str.replace(/"/g, '""');
|
|
579
|
+
return `"${escaped}"`;
|
|
580
|
+
}
|
|
449
581
|
};
|
|
450
582
|
|
|
451
583
|
// src/services/documentation-templates/documentation-template.service.ts
|
|
@@ -1193,7 +1325,14 @@ var TECHNOLOGIES_COLLECTION = "technologies";
|
|
|
1193
1325
|
// src/backoffice/services/product.service.ts
|
|
1194
1326
|
var ProductService = class extends BaseService {
|
|
1195
1327
|
/**
|
|
1196
|
-
* Gets reference to products collection
|
|
1328
|
+
* Gets reference to top-level products collection (source of truth)
|
|
1329
|
+
* @returns Firestore collection reference
|
|
1330
|
+
*/
|
|
1331
|
+
getTopLevelProductsRef() {
|
|
1332
|
+
return (0, import_firestore8.collection)(this.db, PRODUCTS_COLLECTION);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Gets reference to products collection under a technology (backward compatibility)
|
|
1197
1336
|
* @param technologyId - ID of the technology
|
|
1198
1337
|
* @returns Firestore collection reference
|
|
1199
1338
|
*/
|
|
@@ -1209,6 +1348,7 @@ var ProductService = class extends BaseService {
|
|
|
1209
1348
|
...product,
|
|
1210
1349
|
brandId,
|
|
1211
1350
|
technologyId,
|
|
1351
|
+
// Required for old subcollection structure
|
|
1212
1352
|
createdAt: now,
|
|
1213
1353
|
updatedAt: now,
|
|
1214
1354
|
isActive: true
|
|
@@ -1268,30 +1408,26 @@ var ProductService = class extends BaseService {
|
|
|
1268
1408
|
}
|
|
1269
1409
|
/**
|
|
1270
1410
|
* Gets counts of active products grouped by category, subcategory, and technology.
|
|
1271
|
-
*
|
|
1411
|
+
* Queries technology subcollections which have the legacy fields synced by Cloud Functions.
|
|
1272
1412
|
*/
|
|
1273
1413
|
async getProductCounts() {
|
|
1274
|
-
const q = (0, import_firestore8.query)((0, import_firestore8.collectionGroup)(this.db, PRODUCTS_COLLECTION), (0, import_firestore8.where)("isActive", "==", true));
|
|
1275
|
-
const snapshot = await (0, import_firestore8.getDocs)(q);
|
|
1276
1414
|
const counts = {
|
|
1277
1415
|
byCategory: {},
|
|
1278
1416
|
bySubcategory: {},
|
|
1279
1417
|
byTechnology: {}
|
|
1280
1418
|
};
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
}
|
|
1419
|
+
const q = (0, import_firestore8.query)((0, import_firestore8.collectionGroup)(this.db, PRODUCTS_COLLECTION), (0, import_firestore8.where)("isActive", "==", true));
|
|
1420
|
+
const snapshot = await (0, import_firestore8.getDocs)(q);
|
|
1284
1421
|
snapshot.docs.forEach((doc11) => {
|
|
1285
1422
|
const product = doc11.data();
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
|
|
1423
|
+
if (product.categoryId) {
|
|
1424
|
+
counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
|
|
1289
1425
|
}
|
|
1290
|
-
if (subcategoryId) {
|
|
1291
|
-
counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
|
|
1426
|
+
if (product.subcategoryId) {
|
|
1427
|
+
counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
|
|
1292
1428
|
}
|
|
1293
|
-
if (technologyId) {
|
|
1294
|
-
counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
|
|
1429
|
+
if (product.technologyId) {
|
|
1430
|
+
counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
|
|
1295
1431
|
}
|
|
1296
1432
|
});
|
|
1297
1433
|
return counts;
|
|
@@ -1370,6 +1506,241 @@ var ProductService = class extends BaseService {
|
|
|
1370
1506
|
...docSnap.data()
|
|
1371
1507
|
};
|
|
1372
1508
|
}
|
|
1509
|
+
// ==========================================
|
|
1510
|
+
// NEW METHODS: Top-level collection (preferred)
|
|
1511
|
+
// ==========================================
|
|
1512
|
+
/**
|
|
1513
|
+
* Creates a new product in the top-level collection
|
|
1514
|
+
*/
|
|
1515
|
+
async createTopLevel(brandId, product, technologyIds = []) {
|
|
1516
|
+
const now = /* @__PURE__ */ new Date();
|
|
1517
|
+
const newProduct = {
|
|
1518
|
+
...product,
|
|
1519
|
+
brandId,
|
|
1520
|
+
assignedTechnologyIds: technologyIds,
|
|
1521
|
+
createdAt: now,
|
|
1522
|
+
updatedAt: now,
|
|
1523
|
+
isActive: true
|
|
1524
|
+
};
|
|
1525
|
+
const productRef = await (0, import_firestore8.addDoc)(this.getTopLevelProductsRef(), newProduct);
|
|
1526
|
+
return { id: productRef.id, ...newProduct };
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Gets all products from the top-level collection
|
|
1530
|
+
*/
|
|
1531
|
+
async getAllTopLevel(options) {
|
|
1532
|
+
const { rowsPerPage, lastVisible, brandId } = options;
|
|
1533
|
+
const constraints = [(0, import_firestore8.where)("isActive", "==", true), (0, import_firestore8.orderBy)("name")];
|
|
1534
|
+
if (brandId) {
|
|
1535
|
+
constraints.push((0, import_firestore8.where)("brandId", "==", brandId));
|
|
1536
|
+
}
|
|
1537
|
+
if (lastVisible) {
|
|
1538
|
+
constraints.push((0, import_firestore8.startAfter)(lastVisible));
|
|
1539
|
+
}
|
|
1540
|
+
constraints.push((0, import_firestore8.limit)(rowsPerPage));
|
|
1541
|
+
const q = (0, import_firestore8.query)(this.getTopLevelProductsRef(), ...constraints);
|
|
1542
|
+
const snapshot = await (0, import_firestore8.getDocs)(q);
|
|
1543
|
+
const products = snapshot.docs.map(
|
|
1544
|
+
(doc11) => ({
|
|
1545
|
+
id: doc11.id,
|
|
1546
|
+
...doc11.data()
|
|
1547
|
+
})
|
|
1548
|
+
);
|
|
1549
|
+
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
1550
|
+
return { products, lastVisible: newLastVisible };
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Gets a product by ID from the top-level collection
|
|
1554
|
+
*/
|
|
1555
|
+
async getByIdTopLevel(productId) {
|
|
1556
|
+
const docRef = (0, import_firestore8.doc)(this.getTopLevelProductsRef(), productId);
|
|
1557
|
+
const docSnap = await (0, import_firestore8.getDoc)(docRef);
|
|
1558
|
+
if (!docSnap.exists()) return null;
|
|
1559
|
+
return {
|
|
1560
|
+
id: docSnap.id,
|
|
1561
|
+
...docSnap.data()
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Updates a product in the top-level collection
|
|
1566
|
+
*/
|
|
1567
|
+
async updateTopLevel(productId, product) {
|
|
1568
|
+
const updateData = {
|
|
1569
|
+
...product,
|
|
1570
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1571
|
+
};
|
|
1572
|
+
const docRef = (0, import_firestore8.doc)(this.getTopLevelProductsRef(), productId);
|
|
1573
|
+
await (0, import_firestore8.updateDoc)(docRef, updateData);
|
|
1574
|
+
return this.getByIdTopLevel(productId);
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Deletes a product from the top-level collection (soft delete)
|
|
1578
|
+
*/
|
|
1579
|
+
async deleteTopLevel(productId) {
|
|
1580
|
+
await this.updateTopLevel(productId, {
|
|
1581
|
+
isActive: false
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Assigns a product to a technology
|
|
1586
|
+
*/
|
|
1587
|
+
async assignToTechnology(productId, technologyId) {
|
|
1588
|
+
const docRef = (0, import_firestore8.doc)(this.getTopLevelProductsRef(), productId);
|
|
1589
|
+
await (0, import_firestore8.updateDoc)(docRef, {
|
|
1590
|
+
assignedTechnologyIds: (0, import_firestore8.arrayUnion)(technologyId),
|
|
1591
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Unassigns a product from a technology
|
|
1596
|
+
*/
|
|
1597
|
+
async unassignFromTechnology(productId, technologyId) {
|
|
1598
|
+
const docRef = (0, import_firestore8.doc)(this.getTopLevelProductsRef(), productId);
|
|
1599
|
+
await (0, import_firestore8.updateDoc)(docRef, {
|
|
1600
|
+
assignedTechnologyIds: (0, import_firestore8.arrayRemove)(technologyId),
|
|
1601
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Gets products assigned to a specific technology
|
|
1606
|
+
*/
|
|
1607
|
+
async getAssignedProducts(technologyId) {
|
|
1608
|
+
const q = (0, import_firestore8.query)(
|
|
1609
|
+
this.getTopLevelProductsRef(),
|
|
1610
|
+
(0, import_firestore8.where)("assignedTechnologyIds", "array-contains", technologyId),
|
|
1611
|
+
(0, import_firestore8.where)("isActive", "==", true),
|
|
1612
|
+
(0, import_firestore8.orderBy)("name")
|
|
1613
|
+
);
|
|
1614
|
+
const snapshot = await (0, import_firestore8.getDocs)(q);
|
|
1615
|
+
return snapshot.docs.map(
|
|
1616
|
+
(doc11) => ({
|
|
1617
|
+
id: doc11.id,
|
|
1618
|
+
...doc11.data()
|
|
1619
|
+
})
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Gets products NOT assigned to a specific technology
|
|
1624
|
+
*/
|
|
1625
|
+
async getUnassignedProducts(technologyId) {
|
|
1626
|
+
const q = (0, import_firestore8.query)(
|
|
1627
|
+
this.getTopLevelProductsRef(),
|
|
1628
|
+
(0, import_firestore8.where)("isActive", "==", true),
|
|
1629
|
+
(0, import_firestore8.orderBy)("name")
|
|
1630
|
+
);
|
|
1631
|
+
const snapshot = await (0, import_firestore8.getDocs)(q);
|
|
1632
|
+
const allProducts = snapshot.docs.map(
|
|
1633
|
+
(doc11) => ({
|
|
1634
|
+
id: doc11.id,
|
|
1635
|
+
...doc11.data()
|
|
1636
|
+
})
|
|
1637
|
+
);
|
|
1638
|
+
return allProducts.filter(
|
|
1639
|
+
(product) => {
|
|
1640
|
+
var _a;
|
|
1641
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
1642
|
+
}
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Gets all products for a brand (from top-level collection)
|
|
1647
|
+
*/
|
|
1648
|
+
async getByBrand(brandId) {
|
|
1649
|
+
const q = (0, import_firestore8.query)(
|
|
1650
|
+
this.getTopLevelProductsRef(),
|
|
1651
|
+
(0, import_firestore8.where)("brandId", "==", brandId),
|
|
1652
|
+
(0, import_firestore8.where)("isActive", "==", true),
|
|
1653
|
+
(0, import_firestore8.orderBy)("name")
|
|
1654
|
+
);
|
|
1655
|
+
const snapshot = await (0, import_firestore8.getDocs)(q);
|
|
1656
|
+
return snapshot.docs.map(
|
|
1657
|
+
(doc11) => ({
|
|
1658
|
+
id: doc11.id,
|
|
1659
|
+
...doc11.data()
|
|
1660
|
+
})
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Exports products to CSV string, suitable for Excel/Sheets.
|
|
1665
|
+
* Includes headers and optional UTF-8 BOM.
|
|
1666
|
+
* By default exports only active products (set includeInactive to true to export all).
|
|
1667
|
+
*/
|
|
1668
|
+
async exportToCsv(options) {
|
|
1669
|
+
var _a, _b;
|
|
1670
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
1671
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
1672
|
+
const headers = [
|
|
1673
|
+
"id",
|
|
1674
|
+
"name",
|
|
1675
|
+
"brandId",
|
|
1676
|
+
"brandName",
|
|
1677
|
+
"assignedTechnologyIds",
|
|
1678
|
+
"description",
|
|
1679
|
+
"technicalDetails",
|
|
1680
|
+
"dosage",
|
|
1681
|
+
"composition",
|
|
1682
|
+
"indications",
|
|
1683
|
+
"contraindications",
|
|
1684
|
+
"warnings",
|
|
1685
|
+
"isActive"
|
|
1686
|
+
];
|
|
1687
|
+
const rows = [];
|
|
1688
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
1689
|
+
const PAGE_SIZE = 1e3;
|
|
1690
|
+
let cursor;
|
|
1691
|
+
const constraints = [];
|
|
1692
|
+
if (!includeInactive) {
|
|
1693
|
+
constraints.push((0, import_firestore8.where)("isActive", "==", true));
|
|
1694
|
+
}
|
|
1695
|
+
constraints.push((0, import_firestore8.orderBy)("name"));
|
|
1696
|
+
while (true) {
|
|
1697
|
+
const queryConstraints = [...constraints, (0, import_firestore8.limit)(PAGE_SIZE)];
|
|
1698
|
+
if (cursor) queryConstraints.push((0, import_firestore8.startAfter)(cursor));
|
|
1699
|
+
const q = (0, import_firestore8.query)(this.getTopLevelProductsRef(), ...queryConstraints);
|
|
1700
|
+
const snapshot = await (0, import_firestore8.getDocs)(q);
|
|
1701
|
+
if (snapshot.empty) break;
|
|
1702
|
+
for (const d of snapshot.docs) {
|
|
1703
|
+
const product = { id: d.id, ...d.data() };
|
|
1704
|
+
rows.push(this.productToCsvRow(product));
|
|
1705
|
+
}
|
|
1706
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
1707
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
1708
|
+
}
|
|
1709
|
+
const csvBody = rows.join("\r\n");
|
|
1710
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
1711
|
+
}
|
|
1712
|
+
productToCsvRow(product) {
|
|
1713
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
|
|
1714
|
+
const values = [
|
|
1715
|
+
(_a = product.id) != null ? _a : "",
|
|
1716
|
+
(_b = product.name) != null ? _b : "",
|
|
1717
|
+
(_c = product.brandId) != null ? _c : "",
|
|
1718
|
+
(_d = product.brandName) != null ? _d : "",
|
|
1719
|
+
(_f = (_e = product.assignedTechnologyIds) == null ? void 0 : _e.join(";")) != null ? _f : "",
|
|
1720
|
+
(_g = product.description) != null ? _g : "",
|
|
1721
|
+
(_h = product.technicalDetails) != null ? _h : "",
|
|
1722
|
+
(_i = product.dosage) != null ? _i : "",
|
|
1723
|
+
(_j = product.composition) != null ? _j : "",
|
|
1724
|
+
(_l = (_k = product.indications) == null ? void 0 : _k.join(";")) != null ? _l : "",
|
|
1725
|
+
(_n = (_m = product.contraindications) == null ? void 0 : _m.map((c) => c.name).join(";")) != null ? _n : "",
|
|
1726
|
+
(_p = (_o = product.warnings) == null ? void 0 : _o.join(";")) != null ? _p : "",
|
|
1727
|
+
String((_q = product.isActive) != null ? _q : "")
|
|
1728
|
+
];
|
|
1729
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
1730
|
+
}
|
|
1731
|
+
formatDateIso(value) {
|
|
1732
|
+
if (value instanceof Date) return value.toISOString();
|
|
1733
|
+
if (value && typeof value.toDate === "function") {
|
|
1734
|
+
const d = value.toDate();
|
|
1735
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
1736
|
+
}
|
|
1737
|
+
return String(value != null ? value : "");
|
|
1738
|
+
}
|
|
1739
|
+
formatCsvValue(value) {
|
|
1740
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
1741
|
+
const escaped = str.replace(/"/g, '""');
|
|
1742
|
+
return `"${escaped}"`;
|
|
1743
|
+
}
|
|
1373
1744
|
};
|
|
1374
1745
|
|
|
1375
1746
|
// src/backoffice/services/requirement.service.ts
|
|
@@ -1493,6 +1864,65 @@ var RequirementService = class extends BaseService {
|
|
|
1493
1864
|
...docSnap.data()
|
|
1494
1865
|
};
|
|
1495
1866
|
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Exports requirements to CSV string, suitable for Excel/Sheets.
|
|
1869
|
+
* Includes headers and optional UTF-8 BOM.
|
|
1870
|
+
* By default exports only active requirements (set includeInactive to true to export all).
|
|
1871
|
+
*/
|
|
1872
|
+
async exportToCsv(options) {
|
|
1873
|
+
var _a, _b;
|
|
1874
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
1875
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
1876
|
+
const headers = [
|
|
1877
|
+
"id",
|
|
1878
|
+
"type",
|
|
1879
|
+
"name",
|
|
1880
|
+
"description",
|
|
1881
|
+
"timeframe_duration",
|
|
1882
|
+
"timeframe_unit",
|
|
1883
|
+
"timeframe_notifyAt",
|
|
1884
|
+
"importance",
|
|
1885
|
+
"isActive"
|
|
1886
|
+
];
|
|
1887
|
+
const rows = [];
|
|
1888
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
1889
|
+
const q = includeInactive ? (0, import_firestore9.query)(this.requirementsRef, (0, import_firestore9.orderBy)("name")) : (0, import_firestore9.query)(this.requirementsRef, (0, import_firestore9.where)("isActive", "==", true), (0, import_firestore9.orderBy)("name"));
|
|
1890
|
+
const snapshot = await (0, import_firestore9.getDocs)(q);
|
|
1891
|
+
for (const d of snapshot.docs) {
|
|
1892
|
+
const requirement = { id: d.id, ...d.data() };
|
|
1893
|
+
rows.push(this.requirementToCsvRow(requirement));
|
|
1894
|
+
}
|
|
1895
|
+
const csvBody = rows.join("\r\n");
|
|
1896
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
1897
|
+
}
|
|
1898
|
+
requirementToCsvRow(requirement) {
|
|
1899
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
1900
|
+
const values = [
|
|
1901
|
+
(_a = requirement.id) != null ? _a : "",
|
|
1902
|
+
(_b = requirement.type) != null ? _b : "",
|
|
1903
|
+
(_c = requirement.name) != null ? _c : "",
|
|
1904
|
+
(_d = requirement.description) != null ? _d : "",
|
|
1905
|
+
(_g = (_f = (_e = requirement.timeframe) == null ? void 0 : _e.duration) == null ? void 0 : _f.toString()) != null ? _g : "",
|
|
1906
|
+
(_i = (_h = requirement.timeframe) == null ? void 0 : _h.unit) != null ? _i : "",
|
|
1907
|
+
(_l = (_k = (_j = requirement.timeframe) == null ? void 0 : _j.notifyAt) == null ? void 0 : _k.join(";")) != null ? _l : "",
|
|
1908
|
+
(_m = requirement.importance) != null ? _m : "",
|
|
1909
|
+
String((_n = requirement.isActive) != null ? _n : "")
|
|
1910
|
+
];
|
|
1911
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
1912
|
+
}
|
|
1913
|
+
formatDateIso(value) {
|
|
1914
|
+
if (value instanceof Date) return value.toISOString();
|
|
1915
|
+
if (value && typeof value.toDate === "function") {
|
|
1916
|
+
const d = value.toDate();
|
|
1917
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
1918
|
+
}
|
|
1919
|
+
return String(value != null ? value : "");
|
|
1920
|
+
}
|
|
1921
|
+
formatCsvValue(value) {
|
|
1922
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
1923
|
+
const escaped = str.replace(/"/g, '""');
|
|
1924
|
+
return `"${escaped}"`;
|
|
1925
|
+
}
|
|
1496
1926
|
};
|
|
1497
1927
|
|
|
1498
1928
|
// src/backoffice/services/subcategory.service.ts
|
|
@@ -1720,6 +2150,74 @@ var SubcategoryService = class extends BaseService {
|
|
|
1720
2150
|
...docSnap.data()
|
|
1721
2151
|
};
|
|
1722
2152
|
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Exports subcategories to CSV string, suitable for Excel/Sheets.
|
|
2155
|
+
* Includes headers and optional UTF-8 BOM.
|
|
2156
|
+
* By default exports only active subcategories (set includeInactive to true to export all).
|
|
2157
|
+
*/
|
|
2158
|
+
async exportToCsv(options) {
|
|
2159
|
+
var _a, _b;
|
|
2160
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
2161
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
2162
|
+
const headers = [
|
|
2163
|
+
"id",
|
|
2164
|
+
"name",
|
|
2165
|
+
"categoryId",
|
|
2166
|
+
"description",
|
|
2167
|
+
"isActive"
|
|
2168
|
+
];
|
|
2169
|
+
const rows = [];
|
|
2170
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
2171
|
+
const PAGE_SIZE = 1e3;
|
|
2172
|
+
let cursor;
|
|
2173
|
+
const constraints = [];
|
|
2174
|
+
if (!includeInactive) {
|
|
2175
|
+
constraints.push((0, import_firestore10.where)("isActive", "==", true));
|
|
2176
|
+
}
|
|
2177
|
+
constraints.push((0, import_firestore10.orderBy)("name"));
|
|
2178
|
+
while (true) {
|
|
2179
|
+
const queryConstraints = [...constraints, (0, import_firestore10.limit)(PAGE_SIZE)];
|
|
2180
|
+
if (cursor) queryConstraints.push((0, import_firestore10.startAfter)(cursor));
|
|
2181
|
+
const q = (0, import_firestore10.query)(
|
|
2182
|
+
(0, import_firestore10.collectionGroup)(this.db, SUBCATEGORIES_COLLECTION),
|
|
2183
|
+
...queryConstraints
|
|
2184
|
+
);
|
|
2185
|
+
const snapshot = await (0, import_firestore10.getDocs)(q);
|
|
2186
|
+
if (snapshot.empty) break;
|
|
2187
|
+
for (const d of snapshot.docs) {
|
|
2188
|
+
const subcategory = { id: d.id, ...d.data() };
|
|
2189
|
+
rows.push(this.subcategoryToCsvRow(subcategory));
|
|
2190
|
+
}
|
|
2191
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
2192
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
2193
|
+
}
|
|
2194
|
+
const csvBody = rows.join("\r\n");
|
|
2195
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
2196
|
+
}
|
|
2197
|
+
subcategoryToCsvRow(subcategory) {
|
|
2198
|
+
var _a, _b, _c, _d, _e;
|
|
2199
|
+
const values = [
|
|
2200
|
+
(_a = subcategory.id) != null ? _a : "",
|
|
2201
|
+
(_b = subcategory.name) != null ? _b : "",
|
|
2202
|
+
(_c = subcategory.categoryId) != null ? _c : "",
|
|
2203
|
+
(_d = subcategory.description) != null ? _d : "",
|
|
2204
|
+
String((_e = subcategory.isActive) != null ? _e : "")
|
|
2205
|
+
];
|
|
2206
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
2207
|
+
}
|
|
2208
|
+
formatDateIso(value) {
|
|
2209
|
+
if (value instanceof Date) return value.toISOString();
|
|
2210
|
+
if (value && typeof value.toDate === "function") {
|
|
2211
|
+
const d = value.toDate();
|
|
2212
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
2213
|
+
}
|
|
2214
|
+
return String(value != null ? value : "");
|
|
2215
|
+
}
|
|
2216
|
+
formatCsvValue(value) {
|
|
2217
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
2218
|
+
const escaped = str.replace(/"/g, '""');
|
|
2219
|
+
return `"${escaped}"`;
|
|
2220
|
+
}
|
|
1723
2221
|
};
|
|
1724
2222
|
|
|
1725
2223
|
// src/backoffice/services/technology.service.ts
|
|
@@ -1911,7 +2409,18 @@ var TechnologyService = class extends BaseService {
|
|
|
1911
2409
|
});
|
|
1912
2410
|
updateData.updatedAt = /* @__PURE__ */ new Date();
|
|
1913
2411
|
const docRef = (0, import_firestore11.doc)(this.technologiesRef, id);
|
|
2412
|
+
const beforeTech = await this.getById(id);
|
|
1914
2413
|
await (0, import_firestore11.updateDoc)(docRef, updateData);
|
|
2414
|
+
const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
|
|
2415
|
+
const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
|
|
2416
|
+
const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
|
|
2417
|
+
if (categoryChanged || subcategoryChanged || nameChanged) {
|
|
2418
|
+
await this.updateProductsInSubcollection(id, {
|
|
2419
|
+
categoryId: updateData.categoryId,
|
|
2420
|
+
subcategoryId: updateData.subcategoryId,
|
|
2421
|
+
technologyName: updateData.name
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
1915
2424
|
return this.getById(id);
|
|
1916
2425
|
}
|
|
1917
2426
|
/**
|
|
@@ -2347,6 +2856,225 @@ var TechnologyService = class extends BaseService {
|
|
|
2347
2856
|
})
|
|
2348
2857
|
);
|
|
2349
2858
|
}
|
|
2859
|
+
// ==========================================
|
|
2860
|
+
// NEW METHODS: Product assignment management
|
|
2861
|
+
// ==========================================
|
|
2862
|
+
/**
|
|
2863
|
+
* Assigns multiple products to a technology
|
|
2864
|
+
* Updates each product's assignedTechnologyIds array
|
|
2865
|
+
*/
|
|
2866
|
+
async assignProducts(technologyId, productIds) {
|
|
2867
|
+
const batch = (0, import_firestore11.writeBatch)(this.db);
|
|
2868
|
+
for (const productId of productIds) {
|
|
2869
|
+
const productRef = (0, import_firestore11.doc)(this.db, PRODUCTS_COLLECTION, productId);
|
|
2870
|
+
batch.update(productRef, {
|
|
2871
|
+
assignedTechnologyIds: (0, import_firestore11.arrayUnion)(technologyId),
|
|
2872
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
await batch.commit();
|
|
2876
|
+
}
|
|
2877
|
+
/**
|
|
2878
|
+
* Unassigns multiple products from a technology
|
|
2879
|
+
* Updates each product's assignedTechnologyIds array
|
|
2880
|
+
*/
|
|
2881
|
+
async unassignProducts(technologyId, productIds) {
|
|
2882
|
+
const batch = (0, import_firestore11.writeBatch)(this.db);
|
|
2883
|
+
for (const productId of productIds) {
|
|
2884
|
+
const productRef = (0, import_firestore11.doc)(this.db, PRODUCTS_COLLECTION, productId);
|
|
2885
|
+
batch.update(productRef, {
|
|
2886
|
+
assignedTechnologyIds: (0, import_firestore11.arrayRemove)(technologyId),
|
|
2887
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
await batch.commit();
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Gets products assigned to a specific technology
|
|
2894
|
+
* Reads from top-level collection for immediate consistency (Cloud Functions may lag)
|
|
2895
|
+
*/
|
|
2896
|
+
async getAssignedProducts(technologyId) {
|
|
2897
|
+
const q = (0, import_firestore11.query)(
|
|
2898
|
+
(0, import_firestore11.collection)(this.db, PRODUCTS_COLLECTION),
|
|
2899
|
+
(0, import_firestore11.where)("assignedTechnologyIds", "array-contains", technologyId),
|
|
2900
|
+
(0, import_firestore11.where)("isActive", "==", true),
|
|
2901
|
+
(0, import_firestore11.orderBy)("name")
|
|
2902
|
+
);
|
|
2903
|
+
const snapshot = await (0, import_firestore11.getDocs)(q);
|
|
2904
|
+
return snapshot.docs.map(
|
|
2905
|
+
(doc11) => ({
|
|
2906
|
+
id: doc11.id,
|
|
2907
|
+
...doc11.data()
|
|
2908
|
+
})
|
|
2909
|
+
);
|
|
2910
|
+
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Gets products NOT assigned to a specific technology
|
|
2913
|
+
*/
|
|
2914
|
+
async getUnassignedProducts(technologyId) {
|
|
2915
|
+
const q = (0, import_firestore11.query)(
|
|
2916
|
+
(0, import_firestore11.collection)(this.db, PRODUCTS_COLLECTION),
|
|
2917
|
+
(0, import_firestore11.where)("isActive", "==", true),
|
|
2918
|
+
(0, import_firestore11.orderBy)("name")
|
|
2919
|
+
);
|
|
2920
|
+
const snapshot = await (0, import_firestore11.getDocs)(q);
|
|
2921
|
+
const allProducts = snapshot.docs.map(
|
|
2922
|
+
(doc11) => ({
|
|
2923
|
+
id: doc11.id,
|
|
2924
|
+
...doc11.data()
|
|
2925
|
+
})
|
|
2926
|
+
);
|
|
2927
|
+
return allProducts.filter(
|
|
2928
|
+
(product) => {
|
|
2929
|
+
var _a;
|
|
2930
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
2931
|
+
}
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* Gets product assignment statistics for a technology
|
|
2936
|
+
*/
|
|
2937
|
+
async getProductStats(technologyId) {
|
|
2938
|
+
const products = await this.getAssignedProducts(technologyId);
|
|
2939
|
+
const byBrand = {};
|
|
2940
|
+
products.forEach((product) => {
|
|
2941
|
+
byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
|
|
2942
|
+
});
|
|
2943
|
+
return {
|
|
2944
|
+
totalAssigned: products.length,
|
|
2945
|
+
byBrand
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
/**
|
|
2949
|
+
* Updates products in technology subcollection when technology metadata changes
|
|
2950
|
+
* @param technologyId - ID of the technology
|
|
2951
|
+
* @param updates - Fields to update (categoryId, subcategoryId, technologyName)
|
|
2952
|
+
*/
|
|
2953
|
+
async updateProductsInSubcollection(technologyId, updates) {
|
|
2954
|
+
const productsRef = (0, import_firestore11.collection)(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
2955
|
+
const productsSnapshot = await (0, import_firestore11.getDocs)(productsRef);
|
|
2956
|
+
if (productsSnapshot.empty) {
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
const batch = (0, import_firestore11.writeBatch)(this.db);
|
|
2960
|
+
for (const productDoc of productsSnapshot.docs) {
|
|
2961
|
+
const productRef = productDoc.ref;
|
|
2962
|
+
const updateFields = {};
|
|
2963
|
+
if (updates.categoryId !== void 0) {
|
|
2964
|
+
updateFields.categoryId = updates.categoryId;
|
|
2965
|
+
}
|
|
2966
|
+
if (updates.subcategoryId !== void 0) {
|
|
2967
|
+
updateFields.subcategoryId = updates.subcategoryId;
|
|
2968
|
+
}
|
|
2969
|
+
if (updates.technologyName !== void 0) {
|
|
2970
|
+
updateFields.technologyName = updates.technologyName;
|
|
2971
|
+
}
|
|
2972
|
+
if (Object.keys(updateFields).length > 0) {
|
|
2973
|
+
batch.update(productRef, updateFields);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
await batch.commit();
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Exports technologies to CSV string, suitable for Excel/Sheets.
|
|
2980
|
+
* Includes headers and optional UTF-8 BOM.
|
|
2981
|
+
* By default exports only active technologies (set includeInactive to true to export all).
|
|
2982
|
+
* Includes product names from subcollections.
|
|
2983
|
+
*/
|
|
2984
|
+
async exportToCsv(options) {
|
|
2985
|
+
var _a, _b;
|
|
2986
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
2987
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
2988
|
+
const headers = [
|
|
2989
|
+
"id",
|
|
2990
|
+
"name",
|
|
2991
|
+
"description",
|
|
2992
|
+
"family",
|
|
2993
|
+
"categoryId",
|
|
2994
|
+
"subcategoryId",
|
|
2995
|
+
"technicalDetails",
|
|
2996
|
+
"requirements_pre",
|
|
2997
|
+
"requirements_post",
|
|
2998
|
+
"blockingConditions",
|
|
2999
|
+
"contraindications",
|
|
3000
|
+
"benefits",
|
|
3001
|
+
"certificationMinimumLevel",
|
|
3002
|
+
"certificationRequiredSpecialties",
|
|
3003
|
+
"documentationTemplateIds",
|
|
3004
|
+
"productNames",
|
|
3005
|
+
"isActive"
|
|
3006
|
+
];
|
|
3007
|
+
const rows = [];
|
|
3008
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
3009
|
+
const PAGE_SIZE = 1e3;
|
|
3010
|
+
let cursor;
|
|
3011
|
+
const constraints = [];
|
|
3012
|
+
if (!includeInactive) {
|
|
3013
|
+
constraints.push((0, import_firestore11.where)("isActive", "==", true));
|
|
3014
|
+
}
|
|
3015
|
+
constraints.push((0, import_firestore11.orderBy)("name"));
|
|
3016
|
+
while (true) {
|
|
3017
|
+
const queryConstraints = [...constraints, (0, import_firestore11.limit)(PAGE_SIZE)];
|
|
3018
|
+
if (cursor) queryConstraints.push((0, import_firestore11.startAfter)(cursor));
|
|
3019
|
+
const q = (0, import_firestore11.query)(this.technologiesRef, ...queryConstraints);
|
|
3020
|
+
const snapshot = await (0, import_firestore11.getDocs)(q);
|
|
3021
|
+
if (snapshot.empty) break;
|
|
3022
|
+
for (const d of snapshot.docs) {
|
|
3023
|
+
const technology = { id: d.id, ...d.data() };
|
|
3024
|
+
const productNames = await this.getProductNamesForTechnology(technology.id);
|
|
3025
|
+
rows.push(this.technologyToCsvRow(technology, productNames));
|
|
3026
|
+
}
|
|
3027
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
3028
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
3029
|
+
}
|
|
3030
|
+
const csvBody = rows.join("\r\n");
|
|
3031
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
3032
|
+
}
|
|
3033
|
+
/**
|
|
3034
|
+
* Gets product names from the technology's product subcollection
|
|
3035
|
+
*/
|
|
3036
|
+
async getProductNamesForTechnology(technologyId) {
|
|
3037
|
+
try {
|
|
3038
|
+
const productsRef = (0, import_firestore11.collection)(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
3039
|
+
const q = (0, import_firestore11.query)(productsRef, (0, import_firestore11.where)("isActive", "==", true));
|
|
3040
|
+
const snapshot = await (0, import_firestore11.getDocs)(q);
|
|
3041
|
+
return snapshot.docs.map((doc11) => {
|
|
3042
|
+
const product = doc11.data();
|
|
3043
|
+
return product.name || "";
|
|
3044
|
+
}).filter((name) => name);
|
|
3045
|
+
} catch (error) {
|
|
3046
|
+
console.error(`Error fetching products for technology ${technologyId}:`, error);
|
|
3047
|
+
return [];
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
technologyToCsvRow(technology, productNames = []) {
|
|
3051
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A;
|
|
3052
|
+
const values = [
|
|
3053
|
+
(_a = technology.id) != null ? _a : "",
|
|
3054
|
+
(_b = technology.name) != null ? _b : "",
|
|
3055
|
+
(_c = technology.description) != null ? _c : "",
|
|
3056
|
+
(_d = technology.family) != null ? _d : "",
|
|
3057
|
+
(_e = technology.categoryId) != null ? _e : "",
|
|
3058
|
+
(_f = technology.subcategoryId) != null ? _f : "",
|
|
3059
|
+
(_g = technology.technicalDetails) != null ? _g : "",
|
|
3060
|
+
(_j = (_i = (_h = technology.requirements) == null ? void 0 : _h.pre) == null ? void 0 : _i.map((r) => r.name).join(";")) != null ? _j : "",
|
|
3061
|
+
(_m = (_l = (_k = technology.requirements) == null ? void 0 : _k.post) == null ? void 0 : _l.map((r) => r.name).join(";")) != null ? _m : "",
|
|
3062
|
+
(_o = (_n = technology.blockingConditions) == null ? void 0 : _n.join(";")) != null ? _o : "",
|
|
3063
|
+
(_q = (_p = technology.contraindications) == null ? void 0 : _p.map((c) => c.name).join(";")) != null ? _q : "",
|
|
3064
|
+
(_s = (_r = technology.benefits) == null ? void 0 : _r.map((b) => b.name).join(";")) != null ? _s : "",
|
|
3065
|
+
(_u = (_t = technology.certificationRequirement) == null ? void 0 : _t.minimumLevel) != null ? _u : "",
|
|
3066
|
+
(_x = (_w = (_v = technology.certificationRequirement) == null ? void 0 : _v.requiredSpecialties) == null ? void 0 : _w.join(";")) != null ? _x : "",
|
|
3067
|
+
(_z = (_y = technology.documentationTemplates) == null ? void 0 : _y.map((t) => t.templateId).join(";")) != null ? _z : "",
|
|
3068
|
+
productNames.join(";"),
|
|
3069
|
+
String((_A = technology.isActive) != null ? _A : "")
|
|
3070
|
+
];
|
|
3071
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
3072
|
+
}
|
|
3073
|
+
formatCsvValue(value) {
|
|
3074
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
3075
|
+
const escaped = str.replace(/"/g, '""');
|
|
3076
|
+
return `"${escaped}"`;
|
|
3077
|
+
}
|
|
2350
3078
|
};
|
|
2351
3079
|
|
|
2352
3080
|
// src/backoffice/services/constants.service.ts
|
|
@@ -2579,6 +3307,66 @@ var ConstantsService = class extends BaseService {
|
|
|
2579
3307
|
contraindications: (0, import_firestore12.arrayRemove)(toRemove)
|
|
2580
3308
|
});
|
|
2581
3309
|
}
|
|
3310
|
+
// =================================================================
|
|
3311
|
+
// CSV Export Methods
|
|
3312
|
+
// =================================================================
|
|
3313
|
+
/**
|
|
3314
|
+
* Exports treatment benefits to CSV string, suitable for Excel/Sheets.
|
|
3315
|
+
* Includes headers and optional UTF-8 BOM.
|
|
3316
|
+
*/
|
|
3317
|
+
async exportBenefitsToCsv(options) {
|
|
3318
|
+
var _a;
|
|
3319
|
+
const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
|
|
3320
|
+
const headers = ["id", "name", "description"];
|
|
3321
|
+
const rows = [];
|
|
3322
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
3323
|
+
const benefits = await this.getAllBenefitsForFilter();
|
|
3324
|
+
for (const benefit of benefits) {
|
|
3325
|
+
rows.push(this.benefitToCsvRow(benefit));
|
|
3326
|
+
}
|
|
3327
|
+
const csvBody = rows.join("\r\n");
|
|
3328
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
3329
|
+
}
|
|
3330
|
+
/**
|
|
3331
|
+
* Exports contraindications to CSV string, suitable for Excel/Sheets.
|
|
3332
|
+
* Includes headers and optional UTF-8 BOM.
|
|
3333
|
+
*/
|
|
3334
|
+
async exportContraindicationsToCsv(options) {
|
|
3335
|
+
var _a;
|
|
3336
|
+
const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
|
|
3337
|
+
const headers = ["id", "name", "description"];
|
|
3338
|
+
const rows = [];
|
|
3339
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
3340
|
+
const contraindications = await this.getAllContraindicationsForFilter();
|
|
3341
|
+
for (const contraindication of contraindications) {
|
|
3342
|
+
rows.push(this.contraindicationToCsvRow(contraindication));
|
|
3343
|
+
}
|
|
3344
|
+
const csvBody = rows.join("\r\n");
|
|
3345
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
3346
|
+
}
|
|
3347
|
+
benefitToCsvRow(benefit) {
|
|
3348
|
+
var _a, _b, _c;
|
|
3349
|
+
const values = [
|
|
3350
|
+
(_a = benefit.id) != null ? _a : "",
|
|
3351
|
+
(_b = benefit.name) != null ? _b : "",
|
|
3352
|
+
(_c = benefit.description) != null ? _c : ""
|
|
3353
|
+
];
|
|
3354
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
3355
|
+
}
|
|
3356
|
+
contraindicationToCsvRow(contraindication) {
|
|
3357
|
+
var _a, _b, _c;
|
|
3358
|
+
const values = [
|
|
3359
|
+
(_a = contraindication.id) != null ? _a : "",
|
|
3360
|
+
(_b = contraindication.name) != null ? _b : "",
|
|
3361
|
+
(_c = contraindication.description) != null ? _c : ""
|
|
3362
|
+
];
|
|
3363
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
3364
|
+
}
|
|
3365
|
+
formatCsvValue(value) {
|
|
3366
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
3367
|
+
const escaped = str.replace(/"/g, '""');
|
|
3368
|
+
return `"${escaped}"`;
|
|
3369
|
+
}
|
|
2582
3370
|
};
|
|
2583
3371
|
|
|
2584
3372
|
// src/backoffice/types/static/blocking-condition.types.ts
|