@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/index.mjs
CHANGED
|
@@ -18548,6 +18548,73 @@ var BrandService = class extends BaseService {
|
|
|
18548
18548
|
...docSnap.data()
|
|
18549
18549
|
};
|
|
18550
18550
|
}
|
|
18551
|
+
/**
|
|
18552
|
+
* Exports brands to CSV string, suitable for Excel/Sheets.
|
|
18553
|
+
* Includes headers and optional UTF-8 BOM.
|
|
18554
|
+
* By default exports only active brands (set includeInactive to true to export all).
|
|
18555
|
+
*/
|
|
18556
|
+
async exportToCsv(options) {
|
|
18557
|
+
var _a, _b;
|
|
18558
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
18559
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
18560
|
+
const headers = [
|
|
18561
|
+
"id",
|
|
18562
|
+
"name",
|
|
18563
|
+
"manufacturer",
|
|
18564
|
+
"website",
|
|
18565
|
+
"description",
|
|
18566
|
+
"isActive"
|
|
18567
|
+
];
|
|
18568
|
+
const rows = [];
|
|
18569
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
18570
|
+
const PAGE_SIZE = 1e3;
|
|
18571
|
+
let cursor;
|
|
18572
|
+
const baseConstraints = [];
|
|
18573
|
+
if (!includeInactive) {
|
|
18574
|
+
baseConstraints.push(where33("isActive", "==", true));
|
|
18575
|
+
}
|
|
18576
|
+
baseConstraints.push(orderBy19("name_lowercase"));
|
|
18577
|
+
while (true) {
|
|
18578
|
+
const constraints = [...baseConstraints, limit17(PAGE_SIZE)];
|
|
18579
|
+
if (cursor) constraints.push(startAfter15(cursor));
|
|
18580
|
+
const q = query33(this.getBrandsRef(), ...constraints);
|
|
18581
|
+
const snapshot = await getDocs33(q);
|
|
18582
|
+
if (snapshot.empty) break;
|
|
18583
|
+
for (const d of snapshot.docs) {
|
|
18584
|
+
const brand = { id: d.id, ...d.data() };
|
|
18585
|
+
rows.push(this.brandToCsvRow(brand));
|
|
18586
|
+
}
|
|
18587
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
18588
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
18589
|
+
}
|
|
18590
|
+
const csvBody = rows.join("\r\n");
|
|
18591
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
18592
|
+
}
|
|
18593
|
+
brandToCsvRow(brand) {
|
|
18594
|
+
var _a, _b, _c, _d, _e, _f;
|
|
18595
|
+
const values = [
|
|
18596
|
+
(_a = brand.id) != null ? _a : "",
|
|
18597
|
+
(_b = brand.name) != null ? _b : "",
|
|
18598
|
+
(_c = brand.manufacturer) != null ? _c : "",
|
|
18599
|
+
(_d = brand.website) != null ? _d : "",
|
|
18600
|
+
(_e = brand.description) != null ? _e : "",
|
|
18601
|
+
String((_f = brand.isActive) != null ? _f : "")
|
|
18602
|
+
];
|
|
18603
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
18604
|
+
}
|
|
18605
|
+
formatDateIso(value) {
|
|
18606
|
+
if (value instanceof Date) return value.toISOString();
|
|
18607
|
+
if (value && typeof value.toDate === "function") {
|
|
18608
|
+
const d = value.toDate();
|
|
18609
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
18610
|
+
}
|
|
18611
|
+
return String(value != null ? value : "");
|
|
18612
|
+
}
|
|
18613
|
+
formatCsvValue(value) {
|
|
18614
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
18615
|
+
const escaped = str.replace(/"/g, '""');
|
|
18616
|
+
return `"${escaped}"`;
|
|
18617
|
+
}
|
|
18551
18618
|
};
|
|
18552
18619
|
|
|
18553
18620
|
// src/backoffice/services/category.service.ts
|
|
@@ -18739,6 +18806,71 @@ var CategoryService = class extends BaseService {
|
|
|
18739
18806
|
...docSnap.data()
|
|
18740
18807
|
};
|
|
18741
18808
|
}
|
|
18809
|
+
/**
|
|
18810
|
+
* Exports categories to CSV string, suitable for Excel/Sheets.
|
|
18811
|
+
* Includes headers and optional UTF-8 BOM.
|
|
18812
|
+
* By default exports only active categories (set includeInactive to true to export all).
|
|
18813
|
+
*/
|
|
18814
|
+
async exportToCsv(options) {
|
|
18815
|
+
var _a, _b;
|
|
18816
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
18817
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
18818
|
+
const headers = [
|
|
18819
|
+
"id",
|
|
18820
|
+
"name",
|
|
18821
|
+
"description",
|
|
18822
|
+
"family",
|
|
18823
|
+
"isActive"
|
|
18824
|
+
];
|
|
18825
|
+
const rows = [];
|
|
18826
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
18827
|
+
const PAGE_SIZE = 1e3;
|
|
18828
|
+
let cursor;
|
|
18829
|
+
const constraints = [];
|
|
18830
|
+
if (!includeInactive) {
|
|
18831
|
+
constraints.push(where34("isActive", "==", true));
|
|
18832
|
+
}
|
|
18833
|
+
constraints.push(orderBy20("name"));
|
|
18834
|
+
while (true) {
|
|
18835
|
+
const queryConstraints = [...constraints, limit18(PAGE_SIZE)];
|
|
18836
|
+
if (cursor) queryConstraints.push(startAfter16(cursor));
|
|
18837
|
+
const q = query34(this.categoriesRef, ...queryConstraints);
|
|
18838
|
+
const snapshot = await getDocs34(q);
|
|
18839
|
+
if (snapshot.empty) break;
|
|
18840
|
+
for (const d of snapshot.docs) {
|
|
18841
|
+
const category = { id: d.id, ...d.data() };
|
|
18842
|
+
rows.push(this.categoryToCsvRow(category));
|
|
18843
|
+
}
|
|
18844
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
18845
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
18846
|
+
}
|
|
18847
|
+
const csvBody = rows.join("\r\n");
|
|
18848
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
18849
|
+
}
|
|
18850
|
+
categoryToCsvRow(category) {
|
|
18851
|
+
var _a, _b, _c, _d, _e;
|
|
18852
|
+
const values = [
|
|
18853
|
+
(_a = category.id) != null ? _a : "",
|
|
18854
|
+
(_b = category.name) != null ? _b : "",
|
|
18855
|
+
(_c = category.description) != null ? _c : "",
|
|
18856
|
+
(_d = category.family) != null ? _d : "",
|
|
18857
|
+
String((_e = category.isActive) != null ? _e : "")
|
|
18858
|
+
];
|
|
18859
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
18860
|
+
}
|
|
18861
|
+
formatDateIso(value) {
|
|
18862
|
+
if (value instanceof Date) return value.toISOString();
|
|
18863
|
+
if (value && typeof value.toDate === "function") {
|
|
18864
|
+
const d = value.toDate();
|
|
18865
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
18866
|
+
}
|
|
18867
|
+
return String(value != null ? value : "");
|
|
18868
|
+
}
|
|
18869
|
+
formatCsvValue(value) {
|
|
18870
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
18871
|
+
const escaped = str.replace(/"/g, '""');
|
|
18872
|
+
return `"${escaped}"`;
|
|
18873
|
+
}
|
|
18742
18874
|
};
|
|
18743
18875
|
|
|
18744
18876
|
// src/backoffice/services/subcategory.service.ts
|
|
@@ -18982,6 +19114,74 @@ var SubcategoryService = class extends BaseService {
|
|
|
18982
19114
|
...docSnap.data()
|
|
18983
19115
|
};
|
|
18984
19116
|
}
|
|
19117
|
+
/**
|
|
19118
|
+
* Exports subcategories to CSV string, suitable for Excel/Sheets.
|
|
19119
|
+
* Includes headers and optional UTF-8 BOM.
|
|
19120
|
+
* By default exports only active subcategories (set includeInactive to true to export all).
|
|
19121
|
+
*/
|
|
19122
|
+
async exportToCsv(options) {
|
|
19123
|
+
var _a, _b;
|
|
19124
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
19125
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
19126
|
+
const headers = [
|
|
19127
|
+
"id",
|
|
19128
|
+
"name",
|
|
19129
|
+
"categoryId",
|
|
19130
|
+
"description",
|
|
19131
|
+
"isActive"
|
|
19132
|
+
];
|
|
19133
|
+
const rows = [];
|
|
19134
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
19135
|
+
const PAGE_SIZE = 1e3;
|
|
19136
|
+
let cursor;
|
|
19137
|
+
const constraints = [];
|
|
19138
|
+
if (!includeInactive) {
|
|
19139
|
+
constraints.push(where35("isActive", "==", true));
|
|
19140
|
+
}
|
|
19141
|
+
constraints.push(orderBy21("name"));
|
|
19142
|
+
while (true) {
|
|
19143
|
+
const queryConstraints = [...constraints, limit19(PAGE_SIZE)];
|
|
19144
|
+
if (cursor) queryConstraints.push(startAfter17(cursor));
|
|
19145
|
+
const q = query35(
|
|
19146
|
+
collectionGroup2(this.db, SUBCATEGORIES_COLLECTION),
|
|
19147
|
+
...queryConstraints
|
|
19148
|
+
);
|
|
19149
|
+
const snapshot = await getDocs35(q);
|
|
19150
|
+
if (snapshot.empty) break;
|
|
19151
|
+
for (const d of snapshot.docs) {
|
|
19152
|
+
const subcategory = { id: d.id, ...d.data() };
|
|
19153
|
+
rows.push(this.subcategoryToCsvRow(subcategory));
|
|
19154
|
+
}
|
|
19155
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
19156
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
19157
|
+
}
|
|
19158
|
+
const csvBody = rows.join("\r\n");
|
|
19159
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
19160
|
+
}
|
|
19161
|
+
subcategoryToCsvRow(subcategory) {
|
|
19162
|
+
var _a, _b, _c, _d, _e;
|
|
19163
|
+
const values = [
|
|
19164
|
+
(_a = subcategory.id) != null ? _a : "",
|
|
19165
|
+
(_b = subcategory.name) != null ? _b : "",
|
|
19166
|
+
(_c = subcategory.categoryId) != null ? _c : "",
|
|
19167
|
+
(_d = subcategory.description) != null ? _d : "",
|
|
19168
|
+
String((_e = subcategory.isActive) != null ? _e : "")
|
|
19169
|
+
];
|
|
19170
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
19171
|
+
}
|
|
19172
|
+
formatDateIso(value) {
|
|
19173
|
+
if (value instanceof Date) return value.toISOString();
|
|
19174
|
+
if (value && typeof value.toDate === "function") {
|
|
19175
|
+
const d = value.toDate();
|
|
19176
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
19177
|
+
}
|
|
19178
|
+
return String(value != null ? value : "");
|
|
19179
|
+
}
|
|
19180
|
+
formatCsvValue(value) {
|
|
19181
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
19182
|
+
const escaped = str.replace(/"/g, '""');
|
|
19183
|
+
return `"${escaped}"`;
|
|
19184
|
+
}
|
|
18985
19185
|
};
|
|
18986
19186
|
|
|
18987
19187
|
// src/backoffice/services/technology.service.ts
|
|
@@ -18998,8 +19198,14 @@ import {
|
|
|
18998
19198
|
updateDoc as updateDoc38,
|
|
18999
19199
|
where as where36,
|
|
19000
19200
|
arrayUnion as arrayUnion9,
|
|
19001
|
-
arrayRemove as arrayRemove8
|
|
19201
|
+
arrayRemove as arrayRemove8,
|
|
19202
|
+
writeBatch as writeBatch7
|
|
19002
19203
|
} from "firebase/firestore";
|
|
19204
|
+
|
|
19205
|
+
// src/backoffice/types/product.types.ts
|
|
19206
|
+
var PRODUCTS_COLLECTION = "products";
|
|
19207
|
+
|
|
19208
|
+
// src/backoffice/services/technology.service.ts
|
|
19003
19209
|
var DEFAULT_CERTIFICATION_REQUIREMENT = {
|
|
19004
19210
|
minimumLevel: "aesthetician" /* AESTHETICIAN */,
|
|
19005
19211
|
requiredSpecialties: []
|
|
@@ -19161,7 +19367,18 @@ var TechnologyService = class extends BaseService {
|
|
|
19161
19367
|
});
|
|
19162
19368
|
updateData.updatedAt = /* @__PURE__ */ new Date();
|
|
19163
19369
|
const docRef = doc41(this.technologiesRef, id);
|
|
19370
|
+
const beforeTech = await this.getById(id);
|
|
19164
19371
|
await updateDoc38(docRef, updateData);
|
|
19372
|
+
const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
|
|
19373
|
+
const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
|
|
19374
|
+
const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
|
|
19375
|
+
if (categoryChanged || subcategoryChanged || nameChanged) {
|
|
19376
|
+
await this.updateProductsInSubcollection(id, {
|
|
19377
|
+
categoryId: updateData.categoryId,
|
|
19378
|
+
subcategoryId: updateData.subcategoryId,
|
|
19379
|
+
technologyName: updateData.name
|
|
19380
|
+
});
|
|
19381
|
+
}
|
|
19165
19382
|
return this.getById(id);
|
|
19166
19383
|
}
|
|
19167
19384
|
/**
|
|
@@ -19597,6 +19814,225 @@ var TechnologyService = class extends BaseService {
|
|
|
19597
19814
|
})
|
|
19598
19815
|
);
|
|
19599
19816
|
}
|
|
19817
|
+
// ==========================================
|
|
19818
|
+
// NEW METHODS: Product assignment management
|
|
19819
|
+
// ==========================================
|
|
19820
|
+
/**
|
|
19821
|
+
* Assigns multiple products to a technology
|
|
19822
|
+
* Updates each product's assignedTechnologyIds array
|
|
19823
|
+
*/
|
|
19824
|
+
async assignProducts(technologyId, productIds) {
|
|
19825
|
+
const batch = writeBatch7(this.db);
|
|
19826
|
+
for (const productId of productIds) {
|
|
19827
|
+
const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
|
|
19828
|
+
batch.update(productRef, {
|
|
19829
|
+
assignedTechnologyIds: arrayUnion9(technologyId),
|
|
19830
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19831
|
+
});
|
|
19832
|
+
}
|
|
19833
|
+
await batch.commit();
|
|
19834
|
+
}
|
|
19835
|
+
/**
|
|
19836
|
+
* Unassigns multiple products from a technology
|
|
19837
|
+
* Updates each product's assignedTechnologyIds array
|
|
19838
|
+
*/
|
|
19839
|
+
async unassignProducts(technologyId, productIds) {
|
|
19840
|
+
const batch = writeBatch7(this.db);
|
|
19841
|
+
for (const productId of productIds) {
|
|
19842
|
+
const productRef = doc41(this.db, PRODUCTS_COLLECTION, productId);
|
|
19843
|
+
batch.update(productRef, {
|
|
19844
|
+
assignedTechnologyIds: arrayRemove8(technologyId),
|
|
19845
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19846
|
+
});
|
|
19847
|
+
}
|
|
19848
|
+
await batch.commit();
|
|
19849
|
+
}
|
|
19850
|
+
/**
|
|
19851
|
+
* Gets products assigned to a specific technology
|
|
19852
|
+
* Reads from top-level collection for immediate consistency (Cloud Functions may lag)
|
|
19853
|
+
*/
|
|
19854
|
+
async getAssignedProducts(technologyId) {
|
|
19855
|
+
const q = query36(
|
|
19856
|
+
collection36(this.db, PRODUCTS_COLLECTION),
|
|
19857
|
+
where36("assignedTechnologyIds", "array-contains", technologyId),
|
|
19858
|
+
where36("isActive", "==", true),
|
|
19859
|
+
orderBy22("name")
|
|
19860
|
+
);
|
|
19861
|
+
const snapshot = await getDocs36(q);
|
|
19862
|
+
return snapshot.docs.map(
|
|
19863
|
+
(doc44) => ({
|
|
19864
|
+
id: doc44.id,
|
|
19865
|
+
...doc44.data()
|
|
19866
|
+
})
|
|
19867
|
+
);
|
|
19868
|
+
}
|
|
19869
|
+
/**
|
|
19870
|
+
* Gets products NOT assigned to a specific technology
|
|
19871
|
+
*/
|
|
19872
|
+
async getUnassignedProducts(technologyId) {
|
|
19873
|
+
const q = query36(
|
|
19874
|
+
collection36(this.db, PRODUCTS_COLLECTION),
|
|
19875
|
+
where36("isActive", "==", true),
|
|
19876
|
+
orderBy22("name")
|
|
19877
|
+
);
|
|
19878
|
+
const snapshot = await getDocs36(q);
|
|
19879
|
+
const allProducts = snapshot.docs.map(
|
|
19880
|
+
(doc44) => ({
|
|
19881
|
+
id: doc44.id,
|
|
19882
|
+
...doc44.data()
|
|
19883
|
+
})
|
|
19884
|
+
);
|
|
19885
|
+
return allProducts.filter(
|
|
19886
|
+
(product) => {
|
|
19887
|
+
var _a;
|
|
19888
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
19889
|
+
}
|
|
19890
|
+
);
|
|
19891
|
+
}
|
|
19892
|
+
/**
|
|
19893
|
+
* Gets product assignment statistics for a technology
|
|
19894
|
+
*/
|
|
19895
|
+
async getProductStats(technologyId) {
|
|
19896
|
+
const products = await this.getAssignedProducts(technologyId);
|
|
19897
|
+
const byBrand = {};
|
|
19898
|
+
products.forEach((product) => {
|
|
19899
|
+
byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
|
|
19900
|
+
});
|
|
19901
|
+
return {
|
|
19902
|
+
totalAssigned: products.length,
|
|
19903
|
+
byBrand
|
|
19904
|
+
};
|
|
19905
|
+
}
|
|
19906
|
+
/**
|
|
19907
|
+
* Updates products in technology subcollection when technology metadata changes
|
|
19908
|
+
* @param technologyId - ID of the technology
|
|
19909
|
+
* @param updates - Fields to update (categoryId, subcategoryId, technologyName)
|
|
19910
|
+
*/
|
|
19911
|
+
async updateProductsInSubcollection(technologyId, updates) {
|
|
19912
|
+
const productsRef = collection36(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
19913
|
+
const productsSnapshot = await getDocs36(productsRef);
|
|
19914
|
+
if (productsSnapshot.empty) {
|
|
19915
|
+
return;
|
|
19916
|
+
}
|
|
19917
|
+
const batch = writeBatch7(this.db);
|
|
19918
|
+
for (const productDoc of productsSnapshot.docs) {
|
|
19919
|
+
const productRef = productDoc.ref;
|
|
19920
|
+
const updateFields = {};
|
|
19921
|
+
if (updates.categoryId !== void 0) {
|
|
19922
|
+
updateFields.categoryId = updates.categoryId;
|
|
19923
|
+
}
|
|
19924
|
+
if (updates.subcategoryId !== void 0) {
|
|
19925
|
+
updateFields.subcategoryId = updates.subcategoryId;
|
|
19926
|
+
}
|
|
19927
|
+
if (updates.technologyName !== void 0) {
|
|
19928
|
+
updateFields.technologyName = updates.technologyName;
|
|
19929
|
+
}
|
|
19930
|
+
if (Object.keys(updateFields).length > 0) {
|
|
19931
|
+
batch.update(productRef, updateFields);
|
|
19932
|
+
}
|
|
19933
|
+
}
|
|
19934
|
+
await batch.commit();
|
|
19935
|
+
}
|
|
19936
|
+
/**
|
|
19937
|
+
* Exports technologies to CSV string, suitable for Excel/Sheets.
|
|
19938
|
+
* Includes headers and optional UTF-8 BOM.
|
|
19939
|
+
* By default exports only active technologies (set includeInactive to true to export all).
|
|
19940
|
+
* Includes product names from subcollections.
|
|
19941
|
+
*/
|
|
19942
|
+
async exportToCsv(options) {
|
|
19943
|
+
var _a, _b;
|
|
19944
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
19945
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
19946
|
+
const headers = [
|
|
19947
|
+
"id",
|
|
19948
|
+
"name",
|
|
19949
|
+
"description",
|
|
19950
|
+
"family",
|
|
19951
|
+
"categoryId",
|
|
19952
|
+
"subcategoryId",
|
|
19953
|
+
"technicalDetails",
|
|
19954
|
+
"requirements_pre",
|
|
19955
|
+
"requirements_post",
|
|
19956
|
+
"blockingConditions",
|
|
19957
|
+
"contraindications",
|
|
19958
|
+
"benefits",
|
|
19959
|
+
"certificationMinimumLevel",
|
|
19960
|
+
"certificationRequiredSpecialties",
|
|
19961
|
+
"documentationTemplateIds",
|
|
19962
|
+
"productNames",
|
|
19963
|
+
"isActive"
|
|
19964
|
+
];
|
|
19965
|
+
const rows = [];
|
|
19966
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
19967
|
+
const PAGE_SIZE = 1e3;
|
|
19968
|
+
let cursor;
|
|
19969
|
+
const constraints = [];
|
|
19970
|
+
if (!includeInactive) {
|
|
19971
|
+
constraints.push(where36("isActive", "==", true));
|
|
19972
|
+
}
|
|
19973
|
+
constraints.push(orderBy22("name"));
|
|
19974
|
+
while (true) {
|
|
19975
|
+
const queryConstraints = [...constraints, limit20(PAGE_SIZE)];
|
|
19976
|
+
if (cursor) queryConstraints.push(startAfter18(cursor));
|
|
19977
|
+
const q = query36(this.technologiesRef, ...queryConstraints);
|
|
19978
|
+
const snapshot = await getDocs36(q);
|
|
19979
|
+
if (snapshot.empty) break;
|
|
19980
|
+
for (const d of snapshot.docs) {
|
|
19981
|
+
const technology = { id: d.id, ...d.data() };
|
|
19982
|
+
const productNames = await this.getProductNamesForTechnology(technology.id);
|
|
19983
|
+
rows.push(this.technologyToCsvRow(technology, productNames));
|
|
19984
|
+
}
|
|
19985
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
19986
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
19987
|
+
}
|
|
19988
|
+
const csvBody = rows.join("\r\n");
|
|
19989
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
19990
|
+
}
|
|
19991
|
+
/**
|
|
19992
|
+
* Gets product names from the technology's product subcollection
|
|
19993
|
+
*/
|
|
19994
|
+
async getProductNamesForTechnology(technologyId) {
|
|
19995
|
+
try {
|
|
19996
|
+
const productsRef = collection36(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
19997
|
+
const q = query36(productsRef, where36("isActive", "==", true));
|
|
19998
|
+
const snapshot = await getDocs36(q);
|
|
19999
|
+
return snapshot.docs.map((doc44) => {
|
|
20000
|
+
const product = doc44.data();
|
|
20001
|
+
return product.name || "";
|
|
20002
|
+
}).filter((name) => name);
|
|
20003
|
+
} catch (error) {
|
|
20004
|
+
console.error(`Error fetching products for technology ${technologyId}:`, error);
|
|
20005
|
+
return [];
|
|
20006
|
+
}
|
|
20007
|
+
}
|
|
20008
|
+
technologyToCsvRow(technology, productNames = []) {
|
|
20009
|
+
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;
|
|
20010
|
+
const values = [
|
|
20011
|
+
(_a = technology.id) != null ? _a : "",
|
|
20012
|
+
(_b = technology.name) != null ? _b : "",
|
|
20013
|
+
(_c = technology.description) != null ? _c : "",
|
|
20014
|
+
(_d = technology.family) != null ? _d : "",
|
|
20015
|
+
(_e = technology.categoryId) != null ? _e : "",
|
|
20016
|
+
(_f = technology.subcategoryId) != null ? _f : "",
|
|
20017
|
+
(_g = technology.technicalDetails) != null ? _g : "",
|
|
20018
|
+
(_j = (_i = (_h = technology.requirements) == null ? void 0 : _h.pre) == null ? void 0 : _i.map((r) => r.name).join(";")) != null ? _j : "",
|
|
20019
|
+
(_m = (_l = (_k = technology.requirements) == null ? void 0 : _k.post) == null ? void 0 : _l.map((r) => r.name).join(";")) != null ? _m : "",
|
|
20020
|
+
(_o = (_n = technology.blockingConditions) == null ? void 0 : _n.join(";")) != null ? _o : "",
|
|
20021
|
+
(_q = (_p = technology.contraindications) == null ? void 0 : _p.map((c) => c.name).join(";")) != null ? _q : "",
|
|
20022
|
+
(_s = (_r = technology.benefits) == null ? void 0 : _r.map((b) => b.name).join(";")) != null ? _s : "",
|
|
20023
|
+
(_u = (_t = technology.certificationRequirement) == null ? void 0 : _t.minimumLevel) != null ? _u : "",
|
|
20024
|
+
(_x = (_w = (_v = technology.certificationRequirement) == null ? void 0 : _v.requiredSpecialties) == null ? void 0 : _w.join(";")) != null ? _x : "",
|
|
20025
|
+
(_z = (_y = technology.documentationTemplates) == null ? void 0 : _y.map((t) => t.templateId).join(";")) != null ? _z : "",
|
|
20026
|
+
productNames.join(";"),
|
|
20027
|
+
String((_A = technology.isActive) != null ? _A : "")
|
|
20028
|
+
];
|
|
20029
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
20030
|
+
}
|
|
20031
|
+
formatCsvValue(value) {
|
|
20032
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
20033
|
+
const escaped = str.replace(/"/g, '""');
|
|
20034
|
+
return `"${escaped}"`;
|
|
20035
|
+
}
|
|
19600
20036
|
};
|
|
19601
20037
|
|
|
19602
20038
|
// src/backoffice/services/product.service.ts
|
|
@@ -19613,16 +20049,20 @@ import {
|
|
|
19613
20049
|
limit as limit21,
|
|
19614
20050
|
orderBy as orderBy23,
|
|
19615
20051
|
startAfter as startAfter19,
|
|
19616
|
-
getCountFromServer as getCountFromServer7
|
|
20052
|
+
getCountFromServer as getCountFromServer7,
|
|
20053
|
+
arrayUnion as arrayUnion10,
|
|
20054
|
+
arrayRemove as arrayRemove9
|
|
19617
20055
|
} from "firebase/firestore";
|
|
19618
|
-
|
|
19619
|
-
// src/backoffice/types/product.types.ts
|
|
19620
|
-
var PRODUCTS_COLLECTION = "products";
|
|
19621
|
-
|
|
19622
|
-
// src/backoffice/services/product.service.ts
|
|
19623
20056
|
var ProductService = class extends BaseService {
|
|
19624
20057
|
/**
|
|
19625
|
-
* Gets reference to products collection
|
|
20058
|
+
* Gets reference to top-level products collection (source of truth)
|
|
20059
|
+
* @returns Firestore collection reference
|
|
20060
|
+
*/
|
|
20061
|
+
getTopLevelProductsRef() {
|
|
20062
|
+
return collection37(this.db, PRODUCTS_COLLECTION);
|
|
20063
|
+
}
|
|
20064
|
+
/**
|
|
20065
|
+
* Gets reference to products collection under a technology (backward compatibility)
|
|
19626
20066
|
* @param technologyId - ID of the technology
|
|
19627
20067
|
* @returns Firestore collection reference
|
|
19628
20068
|
*/
|
|
@@ -19638,6 +20078,7 @@ var ProductService = class extends BaseService {
|
|
|
19638
20078
|
...product,
|
|
19639
20079
|
brandId,
|
|
19640
20080
|
technologyId,
|
|
20081
|
+
// Required for old subcollection structure
|
|
19641
20082
|
createdAt: now,
|
|
19642
20083
|
updatedAt: now,
|
|
19643
20084
|
isActive: true
|
|
@@ -19697,30 +20138,26 @@ var ProductService = class extends BaseService {
|
|
|
19697
20138
|
}
|
|
19698
20139
|
/**
|
|
19699
20140
|
* Gets counts of active products grouped by category, subcategory, and technology.
|
|
19700
|
-
*
|
|
20141
|
+
* Queries technology subcollections which have the legacy fields synced by Cloud Functions.
|
|
19701
20142
|
*/
|
|
19702
20143
|
async getProductCounts() {
|
|
19703
|
-
const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
|
|
19704
|
-
const snapshot = await getDocs37(q);
|
|
19705
20144
|
const counts = {
|
|
19706
20145
|
byCategory: {},
|
|
19707
20146
|
bySubcategory: {},
|
|
19708
20147
|
byTechnology: {}
|
|
19709
20148
|
};
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
}
|
|
20149
|
+
const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
|
|
20150
|
+
const snapshot = await getDocs37(q);
|
|
19713
20151
|
snapshot.docs.forEach((doc44) => {
|
|
19714
20152
|
const product = doc44.data();
|
|
19715
|
-
|
|
19716
|
-
|
|
19717
|
-
counts.byCategory[categoryId] = (counts.byCategory[categoryId] || 0) + 1;
|
|
20153
|
+
if (product.categoryId) {
|
|
20154
|
+
counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
|
|
19718
20155
|
}
|
|
19719
|
-
if (subcategoryId) {
|
|
19720
|
-
counts.bySubcategory[subcategoryId] = (counts.bySubcategory[subcategoryId] || 0) + 1;
|
|
20156
|
+
if (product.subcategoryId) {
|
|
20157
|
+
counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
|
|
19721
20158
|
}
|
|
19722
|
-
if (technologyId) {
|
|
19723
|
-
counts.byTechnology[technologyId] = (counts.byTechnology[technologyId] || 0) + 1;
|
|
20159
|
+
if (product.technologyId) {
|
|
20160
|
+
counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
|
|
19724
20161
|
}
|
|
19725
20162
|
});
|
|
19726
20163
|
return counts;
|
|
@@ -19799,12 +20236,247 @@ var ProductService = class extends BaseService {
|
|
|
19799
20236
|
...docSnap.data()
|
|
19800
20237
|
};
|
|
19801
20238
|
}
|
|
20239
|
+
// ==========================================
|
|
20240
|
+
// NEW METHODS: Top-level collection (preferred)
|
|
20241
|
+
// ==========================================
|
|
20242
|
+
/**
|
|
20243
|
+
* Creates a new product in the top-level collection
|
|
20244
|
+
*/
|
|
20245
|
+
async createTopLevel(brandId, product, technologyIds = []) {
|
|
20246
|
+
const now = /* @__PURE__ */ new Date();
|
|
20247
|
+
const newProduct = {
|
|
20248
|
+
...product,
|
|
20249
|
+
brandId,
|
|
20250
|
+
assignedTechnologyIds: technologyIds,
|
|
20251
|
+
createdAt: now,
|
|
20252
|
+
updatedAt: now,
|
|
20253
|
+
isActive: true
|
|
20254
|
+
};
|
|
20255
|
+
const productRef = await addDoc8(this.getTopLevelProductsRef(), newProduct);
|
|
20256
|
+
return { id: productRef.id, ...newProduct };
|
|
20257
|
+
}
|
|
20258
|
+
/**
|
|
20259
|
+
* Gets all products from the top-level collection
|
|
20260
|
+
*/
|
|
20261
|
+
async getAllTopLevel(options) {
|
|
20262
|
+
const { rowsPerPage, lastVisible, brandId } = options;
|
|
20263
|
+
const constraints = [where37("isActive", "==", true), orderBy23("name")];
|
|
20264
|
+
if (brandId) {
|
|
20265
|
+
constraints.push(where37("brandId", "==", brandId));
|
|
20266
|
+
}
|
|
20267
|
+
if (lastVisible) {
|
|
20268
|
+
constraints.push(startAfter19(lastVisible));
|
|
20269
|
+
}
|
|
20270
|
+
constraints.push(limit21(rowsPerPage));
|
|
20271
|
+
const q = query37(this.getTopLevelProductsRef(), ...constraints);
|
|
20272
|
+
const snapshot = await getDocs37(q);
|
|
20273
|
+
const products = snapshot.docs.map(
|
|
20274
|
+
(doc44) => ({
|
|
20275
|
+
id: doc44.id,
|
|
20276
|
+
...doc44.data()
|
|
20277
|
+
})
|
|
20278
|
+
);
|
|
20279
|
+
const newLastVisible = snapshot.docs[snapshot.docs.length - 1];
|
|
20280
|
+
return { products, lastVisible: newLastVisible };
|
|
20281
|
+
}
|
|
20282
|
+
/**
|
|
20283
|
+
* Gets a product by ID from the top-level collection
|
|
20284
|
+
*/
|
|
20285
|
+
async getByIdTopLevel(productId) {
|
|
20286
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
20287
|
+
const docSnap = await getDoc43(docRef);
|
|
20288
|
+
if (!docSnap.exists()) return null;
|
|
20289
|
+
return {
|
|
20290
|
+
id: docSnap.id,
|
|
20291
|
+
...docSnap.data()
|
|
20292
|
+
};
|
|
20293
|
+
}
|
|
20294
|
+
/**
|
|
20295
|
+
* Updates a product in the top-level collection
|
|
20296
|
+
*/
|
|
20297
|
+
async updateTopLevel(productId, product) {
|
|
20298
|
+
const updateData = {
|
|
20299
|
+
...product,
|
|
20300
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
20301
|
+
};
|
|
20302
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
20303
|
+
await updateDoc39(docRef, updateData);
|
|
20304
|
+
return this.getByIdTopLevel(productId);
|
|
20305
|
+
}
|
|
20306
|
+
/**
|
|
20307
|
+
* Deletes a product from the top-level collection (soft delete)
|
|
20308
|
+
*/
|
|
20309
|
+
async deleteTopLevel(productId) {
|
|
20310
|
+
await this.updateTopLevel(productId, {
|
|
20311
|
+
isActive: false
|
|
20312
|
+
});
|
|
20313
|
+
}
|
|
20314
|
+
/**
|
|
20315
|
+
* Assigns a product to a technology
|
|
20316
|
+
*/
|
|
20317
|
+
async assignToTechnology(productId, technologyId) {
|
|
20318
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
20319
|
+
await updateDoc39(docRef, {
|
|
20320
|
+
assignedTechnologyIds: arrayUnion10(technologyId),
|
|
20321
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
20322
|
+
});
|
|
20323
|
+
}
|
|
20324
|
+
/**
|
|
20325
|
+
* Unassigns a product from a technology
|
|
20326
|
+
*/
|
|
20327
|
+
async unassignFromTechnology(productId, technologyId) {
|
|
20328
|
+
const docRef = doc42(this.getTopLevelProductsRef(), productId);
|
|
20329
|
+
await updateDoc39(docRef, {
|
|
20330
|
+
assignedTechnologyIds: arrayRemove9(technologyId),
|
|
20331
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
20332
|
+
});
|
|
20333
|
+
}
|
|
20334
|
+
/**
|
|
20335
|
+
* Gets products assigned to a specific technology
|
|
20336
|
+
*/
|
|
20337
|
+
async getAssignedProducts(technologyId) {
|
|
20338
|
+
const q = query37(
|
|
20339
|
+
this.getTopLevelProductsRef(),
|
|
20340
|
+
where37("assignedTechnologyIds", "array-contains", technologyId),
|
|
20341
|
+
where37("isActive", "==", true),
|
|
20342
|
+
orderBy23("name")
|
|
20343
|
+
);
|
|
20344
|
+
const snapshot = await getDocs37(q);
|
|
20345
|
+
return snapshot.docs.map(
|
|
20346
|
+
(doc44) => ({
|
|
20347
|
+
id: doc44.id,
|
|
20348
|
+
...doc44.data()
|
|
20349
|
+
})
|
|
20350
|
+
);
|
|
20351
|
+
}
|
|
20352
|
+
/**
|
|
20353
|
+
* Gets products NOT assigned to a specific technology
|
|
20354
|
+
*/
|
|
20355
|
+
async getUnassignedProducts(technologyId) {
|
|
20356
|
+
const q = query37(
|
|
20357
|
+
this.getTopLevelProductsRef(),
|
|
20358
|
+
where37("isActive", "==", true),
|
|
20359
|
+
orderBy23("name")
|
|
20360
|
+
);
|
|
20361
|
+
const snapshot = await getDocs37(q);
|
|
20362
|
+
const allProducts = snapshot.docs.map(
|
|
20363
|
+
(doc44) => ({
|
|
20364
|
+
id: doc44.id,
|
|
20365
|
+
...doc44.data()
|
|
20366
|
+
})
|
|
20367
|
+
);
|
|
20368
|
+
return allProducts.filter(
|
|
20369
|
+
(product) => {
|
|
20370
|
+
var _a;
|
|
20371
|
+
return !((_a = product.assignedTechnologyIds) == null ? void 0 : _a.includes(technologyId));
|
|
20372
|
+
}
|
|
20373
|
+
);
|
|
20374
|
+
}
|
|
20375
|
+
/**
|
|
20376
|
+
* Gets all products for a brand (from top-level collection)
|
|
20377
|
+
*/
|
|
20378
|
+
async getByBrand(brandId) {
|
|
20379
|
+
const q = query37(
|
|
20380
|
+
this.getTopLevelProductsRef(),
|
|
20381
|
+
where37("brandId", "==", brandId),
|
|
20382
|
+
where37("isActive", "==", true),
|
|
20383
|
+
orderBy23("name")
|
|
20384
|
+
);
|
|
20385
|
+
const snapshot = await getDocs37(q);
|
|
20386
|
+
return snapshot.docs.map(
|
|
20387
|
+
(doc44) => ({
|
|
20388
|
+
id: doc44.id,
|
|
20389
|
+
...doc44.data()
|
|
20390
|
+
})
|
|
20391
|
+
);
|
|
20392
|
+
}
|
|
20393
|
+
/**
|
|
20394
|
+
* Exports products to CSV string, suitable for Excel/Sheets.
|
|
20395
|
+
* Includes headers and optional UTF-8 BOM.
|
|
20396
|
+
* By default exports only active products (set includeInactive to true to export all).
|
|
20397
|
+
*/
|
|
20398
|
+
async exportToCsv(options) {
|
|
20399
|
+
var _a, _b;
|
|
20400
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
20401
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
20402
|
+
const headers = [
|
|
20403
|
+
"id",
|
|
20404
|
+
"name",
|
|
20405
|
+
"brandId",
|
|
20406
|
+
"brandName",
|
|
20407
|
+
"assignedTechnologyIds",
|
|
20408
|
+
"description",
|
|
20409
|
+
"technicalDetails",
|
|
20410
|
+
"dosage",
|
|
20411
|
+
"composition",
|
|
20412
|
+
"indications",
|
|
20413
|
+
"contraindications",
|
|
20414
|
+
"warnings",
|
|
20415
|
+
"isActive"
|
|
20416
|
+
];
|
|
20417
|
+
const rows = [];
|
|
20418
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
20419
|
+
const PAGE_SIZE = 1e3;
|
|
20420
|
+
let cursor;
|
|
20421
|
+
const constraints = [];
|
|
20422
|
+
if (!includeInactive) {
|
|
20423
|
+
constraints.push(where37("isActive", "==", true));
|
|
20424
|
+
}
|
|
20425
|
+
constraints.push(orderBy23("name"));
|
|
20426
|
+
while (true) {
|
|
20427
|
+
const queryConstraints = [...constraints, limit21(PAGE_SIZE)];
|
|
20428
|
+
if (cursor) queryConstraints.push(startAfter19(cursor));
|
|
20429
|
+
const q = query37(this.getTopLevelProductsRef(), ...queryConstraints);
|
|
20430
|
+
const snapshot = await getDocs37(q);
|
|
20431
|
+
if (snapshot.empty) break;
|
|
20432
|
+
for (const d of snapshot.docs) {
|
|
20433
|
+
const product = { id: d.id, ...d.data() };
|
|
20434
|
+
rows.push(this.productToCsvRow(product));
|
|
20435
|
+
}
|
|
20436
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
20437
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
20438
|
+
}
|
|
20439
|
+
const csvBody = rows.join("\r\n");
|
|
20440
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
20441
|
+
}
|
|
20442
|
+
productToCsvRow(product) {
|
|
20443
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
|
|
20444
|
+
const values = [
|
|
20445
|
+
(_a = product.id) != null ? _a : "",
|
|
20446
|
+
(_b = product.name) != null ? _b : "",
|
|
20447
|
+
(_c = product.brandId) != null ? _c : "",
|
|
20448
|
+
(_d = product.brandName) != null ? _d : "",
|
|
20449
|
+
(_f = (_e = product.assignedTechnologyIds) == null ? void 0 : _e.join(";")) != null ? _f : "",
|
|
20450
|
+
(_g = product.description) != null ? _g : "",
|
|
20451
|
+
(_h = product.technicalDetails) != null ? _h : "",
|
|
20452
|
+
(_i = product.dosage) != null ? _i : "",
|
|
20453
|
+
(_j = product.composition) != null ? _j : "",
|
|
20454
|
+
(_l = (_k = product.indications) == null ? void 0 : _k.join(";")) != null ? _l : "",
|
|
20455
|
+
(_n = (_m = product.contraindications) == null ? void 0 : _m.map((c) => c.name).join(";")) != null ? _n : "",
|
|
20456
|
+
(_p = (_o = product.warnings) == null ? void 0 : _o.join(";")) != null ? _p : "",
|
|
20457
|
+
String((_q = product.isActive) != null ? _q : "")
|
|
20458
|
+
];
|
|
20459
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
20460
|
+
}
|
|
20461
|
+
formatDateIso(value) {
|
|
20462
|
+
if (value instanceof Date) return value.toISOString();
|
|
20463
|
+
if (value && typeof value.toDate === "function") {
|
|
20464
|
+
const d = value.toDate();
|
|
20465
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
20466
|
+
}
|
|
20467
|
+
return String(value != null ? value : "");
|
|
20468
|
+
}
|
|
20469
|
+
formatCsvValue(value) {
|
|
20470
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
20471
|
+
const escaped = str.replace(/"/g, '""');
|
|
20472
|
+
return `"${escaped}"`;
|
|
20473
|
+
}
|
|
19802
20474
|
};
|
|
19803
20475
|
|
|
19804
20476
|
// src/backoffice/services/constants.service.ts
|
|
19805
20477
|
import {
|
|
19806
|
-
arrayRemove as
|
|
19807
|
-
arrayUnion as
|
|
20478
|
+
arrayRemove as arrayRemove10,
|
|
20479
|
+
arrayUnion as arrayUnion11,
|
|
19808
20480
|
doc as doc43,
|
|
19809
20481
|
getDoc as getDoc44,
|
|
19810
20482
|
setDoc as setDoc30,
|
|
@@ -19872,7 +20544,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19872
20544
|
await setDoc30(this.treatmentBenefitsDocRef, { benefits: [newBenefit] });
|
|
19873
20545
|
} else {
|
|
19874
20546
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19875
|
-
benefits:
|
|
20547
|
+
benefits: arrayUnion11(newBenefit)
|
|
19876
20548
|
});
|
|
19877
20549
|
}
|
|
19878
20550
|
return newBenefit;
|
|
@@ -19926,7 +20598,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19926
20598
|
return;
|
|
19927
20599
|
}
|
|
19928
20600
|
await updateDoc40(this.treatmentBenefitsDocRef, {
|
|
19929
|
-
benefits:
|
|
20601
|
+
benefits: arrayRemove10(benefitToRemove)
|
|
19930
20602
|
});
|
|
19931
20603
|
}
|
|
19932
20604
|
// =================================================================
|
|
@@ -19979,7 +20651,7 @@ var ConstantsService = class extends BaseService {
|
|
|
19979
20651
|
});
|
|
19980
20652
|
} else {
|
|
19981
20653
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
19982
|
-
contraindications:
|
|
20654
|
+
contraindications: arrayUnion11(newContraindication)
|
|
19983
20655
|
});
|
|
19984
20656
|
}
|
|
19985
20657
|
return newContraindication;
|
|
@@ -20035,9 +20707,69 @@ var ConstantsService = class extends BaseService {
|
|
|
20035
20707
|
return;
|
|
20036
20708
|
}
|
|
20037
20709
|
await updateDoc40(this.contraindicationsDocRef, {
|
|
20038
|
-
contraindications:
|
|
20710
|
+
contraindications: arrayRemove10(toRemove)
|
|
20039
20711
|
});
|
|
20040
20712
|
}
|
|
20713
|
+
// =================================================================
|
|
20714
|
+
// CSV Export Methods
|
|
20715
|
+
// =================================================================
|
|
20716
|
+
/**
|
|
20717
|
+
* Exports treatment benefits to CSV string, suitable for Excel/Sheets.
|
|
20718
|
+
* Includes headers and optional UTF-8 BOM.
|
|
20719
|
+
*/
|
|
20720
|
+
async exportBenefitsToCsv(options) {
|
|
20721
|
+
var _a;
|
|
20722
|
+
const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
|
|
20723
|
+
const headers = ["id", "name", "description"];
|
|
20724
|
+
const rows = [];
|
|
20725
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
20726
|
+
const benefits = await this.getAllBenefitsForFilter();
|
|
20727
|
+
for (const benefit of benefits) {
|
|
20728
|
+
rows.push(this.benefitToCsvRow(benefit));
|
|
20729
|
+
}
|
|
20730
|
+
const csvBody = rows.join("\r\n");
|
|
20731
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
20732
|
+
}
|
|
20733
|
+
/**
|
|
20734
|
+
* Exports contraindications to CSV string, suitable for Excel/Sheets.
|
|
20735
|
+
* Includes headers and optional UTF-8 BOM.
|
|
20736
|
+
*/
|
|
20737
|
+
async exportContraindicationsToCsv(options) {
|
|
20738
|
+
var _a;
|
|
20739
|
+
const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
|
|
20740
|
+
const headers = ["id", "name", "description"];
|
|
20741
|
+
const rows = [];
|
|
20742
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
20743
|
+
const contraindications = await this.getAllContraindicationsForFilter();
|
|
20744
|
+
for (const contraindication of contraindications) {
|
|
20745
|
+
rows.push(this.contraindicationToCsvRow(contraindication));
|
|
20746
|
+
}
|
|
20747
|
+
const csvBody = rows.join("\r\n");
|
|
20748
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
20749
|
+
}
|
|
20750
|
+
benefitToCsvRow(benefit) {
|
|
20751
|
+
var _a, _b, _c;
|
|
20752
|
+
const values = [
|
|
20753
|
+
(_a = benefit.id) != null ? _a : "",
|
|
20754
|
+
(_b = benefit.name) != null ? _b : "",
|
|
20755
|
+
(_c = benefit.description) != null ? _c : ""
|
|
20756
|
+
];
|
|
20757
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
20758
|
+
}
|
|
20759
|
+
contraindicationToCsvRow(contraindication) {
|
|
20760
|
+
var _a, _b, _c;
|
|
20761
|
+
const values = [
|
|
20762
|
+
(_a = contraindication.id) != null ? _a : "",
|
|
20763
|
+
(_b = contraindication.name) != null ? _b : "",
|
|
20764
|
+
(_c = contraindication.description) != null ? _c : ""
|
|
20765
|
+
];
|
|
20766
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
20767
|
+
}
|
|
20768
|
+
formatCsvValue(value) {
|
|
20769
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
20770
|
+
const escaped = str.replace(/"/g, '""');
|
|
20771
|
+
return `"${escaped}"`;
|
|
20772
|
+
}
|
|
20041
20773
|
};
|
|
20042
20774
|
|
|
20043
20775
|
// src/backoffice/types/static/contraindication.types.ts
|