@blackcode_sa/metaestetics-api 1.12.41 → 1.12.43
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 +4 -4
- package/dist/admin/index.d.ts +4 -4
- package/dist/backoffice/index.d.mts +105 -7
- package/dist/backoffice/index.d.ts +105 -7
- package/dist/backoffice/index.js +552 -8
- package/dist/backoffice/index.mjs +566 -21
- package/dist/index.d.mts +93 -7
- package/dist/index.d.ts +93 -7
- package/dist/index.js +493 -8
- package/dist/index.mjs +493 -8
- 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/product.service.ts +113 -10
- 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 +178 -0
- package/src/backoffice/types/product.types.ts +7 -6
package/dist/index.mjs
CHANGED
|
@@ -18509,6 +18509,73 @@ var BrandService = class extends BaseService {
|
|
|
18509
18509
|
...docSnap.data()
|
|
18510
18510
|
};
|
|
18511
18511
|
}
|
|
18512
|
+
/**
|
|
18513
|
+
* Exports brands to CSV string, suitable for Excel/Sheets.
|
|
18514
|
+
* Includes headers and optional UTF-8 BOM.
|
|
18515
|
+
* By default exports only active brands (set includeInactive to true to export all).
|
|
18516
|
+
*/
|
|
18517
|
+
async exportToCsv(options) {
|
|
18518
|
+
var _a, _b;
|
|
18519
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
18520
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
18521
|
+
const headers = [
|
|
18522
|
+
"id",
|
|
18523
|
+
"name",
|
|
18524
|
+
"manufacturer",
|
|
18525
|
+
"website",
|
|
18526
|
+
"description",
|
|
18527
|
+
"isActive"
|
|
18528
|
+
];
|
|
18529
|
+
const rows = [];
|
|
18530
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
18531
|
+
const PAGE_SIZE = 1e3;
|
|
18532
|
+
let cursor;
|
|
18533
|
+
const baseConstraints = [];
|
|
18534
|
+
if (!includeInactive) {
|
|
18535
|
+
baseConstraints.push(where33("isActive", "==", true));
|
|
18536
|
+
}
|
|
18537
|
+
baseConstraints.push(orderBy19("name_lowercase"));
|
|
18538
|
+
while (true) {
|
|
18539
|
+
const constraints = [...baseConstraints, limit17(PAGE_SIZE)];
|
|
18540
|
+
if (cursor) constraints.push(startAfter15(cursor));
|
|
18541
|
+
const q = query33(this.getBrandsRef(), ...constraints);
|
|
18542
|
+
const snapshot = await getDocs33(q);
|
|
18543
|
+
if (snapshot.empty) break;
|
|
18544
|
+
for (const d of snapshot.docs) {
|
|
18545
|
+
const brand = { id: d.id, ...d.data() };
|
|
18546
|
+
rows.push(this.brandToCsvRow(brand));
|
|
18547
|
+
}
|
|
18548
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
18549
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
18550
|
+
}
|
|
18551
|
+
const csvBody = rows.join("\r\n");
|
|
18552
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
18553
|
+
}
|
|
18554
|
+
brandToCsvRow(brand) {
|
|
18555
|
+
var _a, _b, _c, _d, _e, _f;
|
|
18556
|
+
const values = [
|
|
18557
|
+
(_a = brand.id) != null ? _a : "",
|
|
18558
|
+
(_b = brand.name) != null ? _b : "",
|
|
18559
|
+
(_c = brand.manufacturer) != null ? _c : "",
|
|
18560
|
+
(_d = brand.website) != null ? _d : "",
|
|
18561
|
+
(_e = brand.description) != null ? _e : "",
|
|
18562
|
+
String((_f = brand.isActive) != null ? _f : "")
|
|
18563
|
+
];
|
|
18564
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
18565
|
+
}
|
|
18566
|
+
formatDateIso(value) {
|
|
18567
|
+
if (value instanceof Date) return value.toISOString();
|
|
18568
|
+
if (value && typeof value.toDate === "function") {
|
|
18569
|
+
const d = value.toDate();
|
|
18570
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
18571
|
+
}
|
|
18572
|
+
return String(value != null ? value : "");
|
|
18573
|
+
}
|
|
18574
|
+
formatCsvValue(value) {
|
|
18575
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
18576
|
+
const escaped = str.replace(/"/g, '""');
|
|
18577
|
+
return `"${escaped}"`;
|
|
18578
|
+
}
|
|
18512
18579
|
};
|
|
18513
18580
|
|
|
18514
18581
|
// src/backoffice/services/category.service.ts
|
|
@@ -18700,6 +18767,71 @@ var CategoryService = class extends BaseService {
|
|
|
18700
18767
|
...docSnap.data()
|
|
18701
18768
|
};
|
|
18702
18769
|
}
|
|
18770
|
+
/**
|
|
18771
|
+
* Exports categories to CSV string, suitable for Excel/Sheets.
|
|
18772
|
+
* Includes headers and optional UTF-8 BOM.
|
|
18773
|
+
* By default exports only active categories (set includeInactive to true to export all).
|
|
18774
|
+
*/
|
|
18775
|
+
async exportToCsv(options) {
|
|
18776
|
+
var _a, _b;
|
|
18777
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
18778
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
18779
|
+
const headers = [
|
|
18780
|
+
"id",
|
|
18781
|
+
"name",
|
|
18782
|
+
"description",
|
|
18783
|
+
"family",
|
|
18784
|
+
"isActive"
|
|
18785
|
+
];
|
|
18786
|
+
const rows = [];
|
|
18787
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
18788
|
+
const PAGE_SIZE = 1e3;
|
|
18789
|
+
let cursor;
|
|
18790
|
+
const constraints = [];
|
|
18791
|
+
if (!includeInactive) {
|
|
18792
|
+
constraints.push(where34("isActive", "==", true));
|
|
18793
|
+
}
|
|
18794
|
+
constraints.push(orderBy20("name"));
|
|
18795
|
+
while (true) {
|
|
18796
|
+
const queryConstraints = [...constraints, limit18(PAGE_SIZE)];
|
|
18797
|
+
if (cursor) queryConstraints.push(startAfter16(cursor));
|
|
18798
|
+
const q = query34(this.categoriesRef, ...queryConstraints);
|
|
18799
|
+
const snapshot = await getDocs34(q);
|
|
18800
|
+
if (snapshot.empty) break;
|
|
18801
|
+
for (const d of snapshot.docs) {
|
|
18802
|
+
const category = { id: d.id, ...d.data() };
|
|
18803
|
+
rows.push(this.categoryToCsvRow(category));
|
|
18804
|
+
}
|
|
18805
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
18806
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
18807
|
+
}
|
|
18808
|
+
const csvBody = rows.join("\r\n");
|
|
18809
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
18810
|
+
}
|
|
18811
|
+
categoryToCsvRow(category) {
|
|
18812
|
+
var _a, _b, _c, _d, _e;
|
|
18813
|
+
const values = [
|
|
18814
|
+
(_a = category.id) != null ? _a : "",
|
|
18815
|
+
(_b = category.name) != null ? _b : "",
|
|
18816
|
+
(_c = category.description) != null ? _c : "",
|
|
18817
|
+
(_d = category.family) != null ? _d : "",
|
|
18818
|
+
String((_e = category.isActive) != null ? _e : "")
|
|
18819
|
+
];
|
|
18820
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
18821
|
+
}
|
|
18822
|
+
formatDateIso(value) {
|
|
18823
|
+
if (value instanceof Date) return value.toISOString();
|
|
18824
|
+
if (value && typeof value.toDate === "function") {
|
|
18825
|
+
const d = value.toDate();
|
|
18826
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
18827
|
+
}
|
|
18828
|
+
return String(value != null ? value : "");
|
|
18829
|
+
}
|
|
18830
|
+
formatCsvValue(value) {
|
|
18831
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
18832
|
+
const escaped = str.replace(/"/g, '""');
|
|
18833
|
+
return `"${escaped}"`;
|
|
18834
|
+
}
|
|
18703
18835
|
};
|
|
18704
18836
|
|
|
18705
18837
|
// src/backoffice/services/subcategory.service.ts
|
|
@@ -18943,6 +19075,74 @@ var SubcategoryService = class extends BaseService {
|
|
|
18943
19075
|
...docSnap.data()
|
|
18944
19076
|
};
|
|
18945
19077
|
}
|
|
19078
|
+
/**
|
|
19079
|
+
* Exports subcategories to CSV string, suitable for Excel/Sheets.
|
|
19080
|
+
* Includes headers and optional UTF-8 BOM.
|
|
19081
|
+
* By default exports only active subcategories (set includeInactive to true to export all).
|
|
19082
|
+
*/
|
|
19083
|
+
async exportToCsv(options) {
|
|
19084
|
+
var _a, _b;
|
|
19085
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
19086
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
19087
|
+
const headers = [
|
|
19088
|
+
"id",
|
|
19089
|
+
"name",
|
|
19090
|
+
"categoryId",
|
|
19091
|
+
"description",
|
|
19092
|
+
"isActive"
|
|
19093
|
+
];
|
|
19094
|
+
const rows = [];
|
|
19095
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
19096
|
+
const PAGE_SIZE = 1e3;
|
|
19097
|
+
let cursor;
|
|
19098
|
+
const constraints = [];
|
|
19099
|
+
if (!includeInactive) {
|
|
19100
|
+
constraints.push(where35("isActive", "==", true));
|
|
19101
|
+
}
|
|
19102
|
+
constraints.push(orderBy21("name"));
|
|
19103
|
+
while (true) {
|
|
19104
|
+
const queryConstraints = [...constraints, limit19(PAGE_SIZE)];
|
|
19105
|
+
if (cursor) queryConstraints.push(startAfter17(cursor));
|
|
19106
|
+
const q = query35(
|
|
19107
|
+
collectionGroup2(this.db, SUBCATEGORIES_COLLECTION),
|
|
19108
|
+
...queryConstraints
|
|
19109
|
+
);
|
|
19110
|
+
const snapshot = await getDocs35(q);
|
|
19111
|
+
if (snapshot.empty) break;
|
|
19112
|
+
for (const d of snapshot.docs) {
|
|
19113
|
+
const subcategory = { id: d.id, ...d.data() };
|
|
19114
|
+
rows.push(this.subcategoryToCsvRow(subcategory));
|
|
19115
|
+
}
|
|
19116
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
19117
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
19118
|
+
}
|
|
19119
|
+
const csvBody = rows.join("\r\n");
|
|
19120
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
19121
|
+
}
|
|
19122
|
+
subcategoryToCsvRow(subcategory) {
|
|
19123
|
+
var _a, _b, _c, _d, _e;
|
|
19124
|
+
const values = [
|
|
19125
|
+
(_a = subcategory.id) != null ? _a : "",
|
|
19126
|
+
(_b = subcategory.name) != null ? _b : "",
|
|
19127
|
+
(_c = subcategory.categoryId) != null ? _c : "",
|
|
19128
|
+
(_d = subcategory.description) != null ? _d : "",
|
|
19129
|
+
String((_e = subcategory.isActive) != null ? _e : "")
|
|
19130
|
+
];
|
|
19131
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
19132
|
+
}
|
|
19133
|
+
formatDateIso(value) {
|
|
19134
|
+
if (value instanceof Date) return value.toISOString();
|
|
19135
|
+
if (value && typeof value.toDate === "function") {
|
|
19136
|
+
const d = value.toDate();
|
|
19137
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
19138
|
+
}
|
|
19139
|
+
return String(value != null ? value : "");
|
|
19140
|
+
}
|
|
19141
|
+
formatCsvValue(value) {
|
|
19142
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
19143
|
+
const escaped = str.replace(/"/g, '""');
|
|
19144
|
+
return `"${escaped}"`;
|
|
19145
|
+
}
|
|
18946
19146
|
};
|
|
18947
19147
|
|
|
18948
19148
|
// src/backoffice/services/technology.service.ts
|
|
@@ -19128,7 +19328,18 @@ var TechnologyService = class extends BaseService {
|
|
|
19128
19328
|
});
|
|
19129
19329
|
updateData.updatedAt = /* @__PURE__ */ new Date();
|
|
19130
19330
|
const docRef = doc41(this.technologiesRef, id);
|
|
19331
|
+
const beforeTech = await this.getById(id);
|
|
19131
19332
|
await updateDoc38(docRef, updateData);
|
|
19333
|
+
const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
|
|
19334
|
+
const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
|
|
19335
|
+
const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
|
|
19336
|
+
if (categoryChanged || subcategoryChanged || nameChanged) {
|
|
19337
|
+
await this.updateProductsInSubcollection(id, {
|
|
19338
|
+
categoryId: updateData.categoryId,
|
|
19339
|
+
subcategoryId: updateData.subcategoryId,
|
|
19340
|
+
technologyName: updateData.name
|
|
19341
|
+
});
|
|
19342
|
+
}
|
|
19132
19343
|
return this.getById(id);
|
|
19133
19344
|
}
|
|
19134
19345
|
/**
|
|
@@ -19653,6 +19864,136 @@ var TechnologyService = class extends BaseService {
|
|
|
19653
19864
|
byBrand
|
|
19654
19865
|
};
|
|
19655
19866
|
}
|
|
19867
|
+
/**
|
|
19868
|
+
* Updates products in technology subcollection when technology metadata changes
|
|
19869
|
+
* @param technologyId - ID of the technology
|
|
19870
|
+
* @param updates - Fields to update (categoryId, subcategoryId, technologyName)
|
|
19871
|
+
*/
|
|
19872
|
+
async updateProductsInSubcollection(technologyId, updates) {
|
|
19873
|
+
const productsRef = collection36(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
19874
|
+
const productsSnapshot = await getDocs36(productsRef);
|
|
19875
|
+
if (productsSnapshot.empty) {
|
|
19876
|
+
return;
|
|
19877
|
+
}
|
|
19878
|
+
const batch = writeBatch7(this.db);
|
|
19879
|
+
for (const productDoc of productsSnapshot.docs) {
|
|
19880
|
+
const productRef = productDoc.ref;
|
|
19881
|
+
const updateFields = {};
|
|
19882
|
+
if (updates.categoryId !== void 0) {
|
|
19883
|
+
updateFields.categoryId = updates.categoryId;
|
|
19884
|
+
}
|
|
19885
|
+
if (updates.subcategoryId !== void 0) {
|
|
19886
|
+
updateFields.subcategoryId = updates.subcategoryId;
|
|
19887
|
+
}
|
|
19888
|
+
if (updates.technologyName !== void 0) {
|
|
19889
|
+
updateFields.technologyName = updates.technologyName;
|
|
19890
|
+
}
|
|
19891
|
+
if (Object.keys(updateFields).length > 0) {
|
|
19892
|
+
batch.update(productRef, updateFields);
|
|
19893
|
+
}
|
|
19894
|
+
}
|
|
19895
|
+
await batch.commit();
|
|
19896
|
+
}
|
|
19897
|
+
/**
|
|
19898
|
+
* Exports technologies to CSV string, suitable for Excel/Sheets.
|
|
19899
|
+
* Includes headers and optional UTF-8 BOM.
|
|
19900
|
+
* By default exports only active technologies (set includeInactive to true to export all).
|
|
19901
|
+
* Includes product names from subcollections.
|
|
19902
|
+
*/
|
|
19903
|
+
async exportToCsv(options) {
|
|
19904
|
+
var _a, _b;
|
|
19905
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
19906
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
19907
|
+
const headers = [
|
|
19908
|
+
"id",
|
|
19909
|
+
"name",
|
|
19910
|
+
"description",
|
|
19911
|
+
"family",
|
|
19912
|
+
"categoryId",
|
|
19913
|
+
"subcategoryId",
|
|
19914
|
+
"technicalDetails",
|
|
19915
|
+
"requirements_pre",
|
|
19916
|
+
"requirements_post",
|
|
19917
|
+
"blockingConditions",
|
|
19918
|
+
"contraindications",
|
|
19919
|
+
"benefits",
|
|
19920
|
+
"certificationMinimumLevel",
|
|
19921
|
+
"certificationRequiredSpecialties",
|
|
19922
|
+
"documentationTemplateIds",
|
|
19923
|
+
"productNames",
|
|
19924
|
+
"isActive"
|
|
19925
|
+
];
|
|
19926
|
+
const rows = [];
|
|
19927
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
19928
|
+
const PAGE_SIZE = 1e3;
|
|
19929
|
+
let cursor;
|
|
19930
|
+
const constraints = [];
|
|
19931
|
+
if (!includeInactive) {
|
|
19932
|
+
constraints.push(where36("isActive", "==", true));
|
|
19933
|
+
}
|
|
19934
|
+
constraints.push(orderBy22("name"));
|
|
19935
|
+
while (true) {
|
|
19936
|
+
const queryConstraints = [...constraints, limit20(PAGE_SIZE)];
|
|
19937
|
+
if (cursor) queryConstraints.push(startAfter18(cursor));
|
|
19938
|
+
const q = query36(this.technologiesRef, ...queryConstraints);
|
|
19939
|
+
const snapshot = await getDocs36(q);
|
|
19940
|
+
if (snapshot.empty) break;
|
|
19941
|
+
for (const d of snapshot.docs) {
|
|
19942
|
+
const technology = { id: d.id, ...d.data() };
|
|
19943
|
+
const productNames = await this.getProductNamesForTechnology(technology.id);
|
|
19944
|
+
rows.push(this.technologyToCsvRow(technology, productNames));
|
|
19945
|
+
}
|
|
19946
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
19947
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
19948
|
+
}
|
|
19949
|
+
const csvBody = rows.join("\r\n");
|
|
19950
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
19951
|
+
}
|
|
19952
|
+
/**
|
|
19953
|
+
* Gets product names from the technology's product subcollection
|
|
19954
|
+
*/
|
|
19955
|
+
async getProductNamesForTechnology(technologyId) {
|
|
19956
|
+
try {
|
|
19957
|
+
const productsRef = collection36(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
19958
|
+
const q = query36(productsRef, where36("isActive", "==", true));
|
|
19959
|
+
const snapshot = await getDocs36(q);
|
|
19960
|
+
return snapshot.docs.map((doc44) => {
|
|
19961
|
+
const product = doc44.data();
|
|
19962
|
+
return product.name || "";
|
|
19963
|
+
}).filter((name) => name);
|
|
19964
|
+
} catch (error) {
|
|
19965
|
+
console.error(`Error fetching products for technology ${technologyId}:`, error);
|
|
19966
|
+
return [];
|
|
19967
|
+
}
|
|
19968
|
+
}
|
|
19969
|
+
technologyToCsvRow(technology, productNames = []) {
|
|
19970
|
+
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;
|
|
19971
|
+
const values = [
|
|
19972
|
+
(_a = technology.id) != null ? _a : "",
|
|
19973
|
+
(_b = technology.name) != null ? _b : "",
|
|
19974
|
+
(_c = technology.description) != null ? _c : "",
|
|
19975
|
+
(_d = technology.family) != null ? _d : "",
|
|
19976
|
+
(_e = technology.categoryId) != null ? _e : "",
|
|
19977
|
+
(_f = technology.subcategoryId) != null ? _f : "",
|
|
19978
|
+
(_g = technology.technicalDetails) != null ? _g : "",
|
|
19979
|
+
(_j = (_i = (_h = technology.requirements) == null ? void 0 : _h.pre) == null ? void 0 : _i.map((r) => r.name).join(";")) != null ? _j : "",
|
|
19980
|
+
(_m = (_l = (_k = technology.requirements) == null ? void 0 : _k.post) == null ? void 0 : _l.map((r) => r.name).join(";")) != null ? _m : "",
|
|
19981
|
+
(_o = (_n = technology.blockingConditions) == null ? void 0 : _n.join(";")) != null ? _o : "",
|
|
19982
|
+
(_q = (_p = technology.contraindications) == null ? void 0 : _p.map((c) => c.name).join(";")) != null ? _q : "",
|
|
19983
|
+
(_s = (_r = technology.benefits) == null ? void 0 : _r.map((b) => b.name).join(";")) != null ? _s : "",
|
|
19984
|
+
(_u = (_t = technology.certificationRequirement) == null ? void 0 : _t.minimumLevel) != null ? _u : "",
|
|
19985
|
+
(_x = (_w = (_v = technology.certificationRequirement) == null ? void 0 : _v.requiredSpecialties) == null ? void 0 : _w.join(";")) != null ? _x : "",
|
|
19986
|
+
(_z = (_y = technology.documentationTemplates) == null ? void 0 : _y.map((t) => t.templateId).join(";")) != null ? _z : "",
|
|
19987
|
+
productNames.join(";"),
|
|
19988
|
+
String((_A = technology.isActive) != null ? _A : "")
|
|
19989
|
+
];
|
|
19990
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
19991
|
+
}
|
|
19992
|
+
formatCsvValue(value) {
|
|
19993
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
19994
|
+
const escaped = str.replace(/"/g, '""');
|
|
19995
|
+
return `"${escaped}"`;
|
|
19996
|
+
}
|
|
19656
19997
|
};
|
|
19657
19998
|
|
|
19658
19999
|
// src/backoffice/services/product.service.ts
|
|
@@ -19757,9 +20098,8 @@ var ProductService = class extends BaseService {
|
|
|
19757
20098
|
return snapshot.data().count;
|
|
19758
20099
|
}
|
|
19759
20100
|
/**
|
|
19760
|
-
* Gets counts of active products grouped by technology.
|
|
19761
|
-
*
|
|
19762
|
-
* Categories/subcategories not available in top-level structure.
|
|
20101
|
+
* Gets counts of active products grouped by category, subcategory, and technology.
|
|
20102
|
+
* Queries technology subcollections which have the legacy fields synced by Cloud Functions.
|
|
19763
20103
|
*/
|
|
19764
20104
|
async getProductCounts() {
|
|
19765
20105
|
const counts = {
|
|
@@ -19767,14 +20107,18 @@ var ProductService = class extends BaseService {
|
|
|
19767
20107
|
bySubcategory: {},
|
|
19768
20108
|
byTechnology: {}
|
|
19769
20109
|
};
|
|
19770
|
-
const q = query37(this.
|
|
20110
|
+
const q = query37(collectionGroup3(this.db, PRODUCTS_COLLECTION), where37("isActive", "==", true));
|
|
19771
20111
|
const snapshot = await getDocs37(q);
|
|
19772
20112
|
snapshot.docs.forEach((doc44) => {
|
|
19773
20113
|
const product = doc44.data();
|
|
19774
|
-
if (product.
|
|
19775
|
-
product.
|
|
19776
|
-
|
|
19777
|
-
|
|
20114
|
+
if (product.categoryId) {
|
|
20115
|
+
counts.byCategory[product.categoryId] = (counts.byCategory[product.categoryId] || 0) + 1;
|
|
20116
|
+
}
|
|
20117
|
+
if (product.subcategoryId) {
|
|
20118
|
+
counts.bySubcategory[product.subcategoryId] = (counts.bySubcategory[product.subcategoryId] || 0) + 1;
|
|
20119
|
+
}
|
|
20120
|
+
if (product.technologyId) {
|
|
20121
|
+
counts.byTechnology[product.technologyId] = (counts.byTechnology[product.technologyId] || 0) + 1;
|
|
19778
20122
|
}
|
|
19779
20123
|
});
|
|
19780
20124
|
return counts;
|
|
@@ -20007,6 +20351,87 @@ var ProductService = class extends BaseService {
|
|
|
20007
20351
|
})
|
|
20008
20352
|
);
|
|
20009
20353
|
}
|
|
20354
|
+
/**
|
|
20355
|
+
* Exports products to CSV string, suitable for Excel/Sheets.
|
|
20356
|
+
* Includes headers and optional UTF-8 BOM.
|
|
20357
|
+
* By default exports only active products (set includeInactive to true to export all).
|
|
20358
|
+
*/
|
|
20359
|
+
async exportToCsv(options) {
|
|
20360
|
+
var _a, _b;
|
|
20361
|
+
const includeInactive = (_a = options == null ? void 0 : options.includeInactive) != null ? _a : false;
|
|
20362
|
+
const includeBom = (_b = options == null ? void 0 : options.includeBom) != null ? _b : true;
|
|
20363
|
+
const headers = [
|
|
20364
|
+
"id",
|
|
20365
|
+
"name",
|
|
20366
|
+
"brandId",
|
|
20367
|
+
"brandName",
|
|
20368
|
+
"assignedTechnologyIds",
|
|
20369
|
+
"description",
|
|
20370
|
+
"technicalDetails",
|
|
20371
|
+
"dosage",
|
|
20372
|
+
"composition",
|
|
20373
|
+
"indications",
|
|
20374
|
+
"contraindications",
|
|
20375
|
+
"warnings",
|
|
20376
|
+
"isActive"
|
|
20377
|
+
];
|
|
20378
|
+
const rows = [];
|
|
20379
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
20380
|
+
const PAGE_SIZE = 1e3;
|
|
20381
|
+
let cursor;
|
|
20382
|
+
const constraints = [];
|
|
20383
|
+
if (!includeInactive) {
|
|
20384
|
+
constraints.push(where37("isActive", "==", true));
|
|
20385
|
+
}
|
|
20386
|
+
constraints.push(orderBy23("name"));
|
|
20387
|
+
while (true) {
|
|
20388
|
+
const queryConstraints = [...constraints, limit21(PAGE_SIZE)];
|
|
20389
|
+
if (cursor) queryConstraints.push(startAfter19(cursor));
|
|
20390
|
+
const q = query37(this.getTopLevelProductsRef(), ...queryConstraints);
|
|
20391
|
+
const snapshot = await getDocs37(q);
|
|
20392
|
+
if (snapshot.empty) break;
|
|
20393
|
+
for (const d of snapshot.docs) {
|
|
20394
|
+
const product = { id: d.id, ...d.data() };
|
|
20395
|
+
rows.push(this.productToCsvRow(product));
|
|
20396
|
+
}
|
|
20397
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
20398
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
20399
|
+
}
|
|
20400
|
+
const csvBody = rows.join("\r\n");
|
|
20401
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
20402
|
+
}
|
|
20403
|
+
productToCsvRow(product) {
|
|
20404
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q;
|
|
20405
|
+
const values = [
|
|
20406
|
+
(_a = product.id) != null ? _a : "",
|
|
20407
|
+
(_b = product.name) != null ? _b : "",
|
|
20408
|
+
(_c = product.brandId) != null ? _c : "",
|
|
20409
|
+
(_d = product.brandName) != null ? _d : "",
|
|
20410
|
+
(_f = (_e = product.assignedTechnologyIds) == null ? void 0 : _e.join(";")) != null ? _f : "",
|
|
20411
|
+
(_g = product.description) != null ? _g : "",
|
|
20412
|
+
(_h = product.technicalDetails) != null ? _h : "",
|
|
20413
|
+
(_i = product.dosage) != null ? _i : "",
|
|
20414
|
+
(_j = product.composition) != null ? _j : "",
|
|
20415
|
+
(_l = (_k = product.indications) == null ? void 0 : _k.join(";")) != null ? _l : "",
|
|
20416
|
+
(_n = (_m = product.contraindications) == null ? void 0 : _m.map((c) => c.name).join(";")) != null ? _n : "",
|
|
20417
|
+
(_p = (_o = product.warnings) == null ? void 0 : _o.join(";")) != null ? _p : "",
|
|
20418
|
+
String((_q = product.isActive) != null ? _q : "")
|
|
20419
|
+
];
|
|
20420
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
20421
|
+
}
|
|
20422
|
+
formatDateIso(value) {
|
|
20423
|
+
if (value instanceof Date) return value.toISOString();
|
|
20424
|
+
if (value && typeof value.toDate === "function") {
|
|
20425
|
+
const d = value.toDate();
|
|
20426
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
20427
|
+
}
|
|
20428
|
+
return String(value != null ? value : "");
|
|
20429
|
+
}
|
|
20430
|
+
formatCsvValue(value) {
|
|
20431
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
20432
|
+
const escaped = str.replace(/"/g, '""');
|
|
20433
|
+
return `"${escaped}"`;
|
|
20434
|
+
}
|
|
20010
20435
|
};
|
|
20011
20436
|
|
|
20012
20437
|
// src/backoffice/services/constants.service.ts
|
|
@@ -20246,6 +20671,66 @@ var ConstantsService = class extends BaseService {
|
|
|
20246
20671
|
contraindications: arrayRemove10(toRemove)
|
|
20247
20672
|
});
|
|
20248
20673
|
}
|
|
20674
|
+
// =================================================================
|
|
20675
|
+
// CSV Export Methods
|
|
20676
|
+
// =================================================================
|
|
20677
|
+
/**
|
|
20678
|
+
* Exports treatment benefits to CSV string, suitable for Excel/Sheets.
|
|
20679
|
+
* Includes headers and optional UTF-8 BOM.
|
|
20680
|
+
*/
|
|
20681
|
+
async exportBenefitsToCsv(options) {
|
|
20682
|
+
var _a;
|
|
20683
|
+
const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
|
|
20684
|
+
const headers = ["id", "name", "description"];
|
|
20685
|
+
const rows = [];
|
|
20686
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
20687
|
+
const benefits = await this.getAllBenefitsForFilter();
|
|
20688
|
+
for (const benefit of benefits) {
|
|
20689
|
+
rows.push(this.benefitToCsvRow(benefit));
|
|
20690
|
+
}
|
|
20691
|
+
const csvBody = rows.join("\r\n");
|
|
20692
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
20693
|
+
}
|
|
20694
|
+
/**
|
|
20695
|
+
* Exports contraindications to CSV string, suitable for Excel/Sheets.
|
|
20696
|
+
* Includes headers and optional UTF-8 BOM.
|
|
20697
|
+
*/
|
|
20698
|
+
async exportContraindicationsToCsv(options) {
|
|
20699
|
+
var _a;
|
|
20700
|
+
const includeBom = (_a = options == null ? void 0 : options.includeBom) != null ? _a : true;
|
|
20701
|
+
const headers = ["id", "name", "description"];
|
|
20702
|
+
const rows = [];
|
|
20703
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
20704
|
+
const contraindications = await this.getAllContraindicationsForFilter();
|
|
20705
|
+
for (const contraindication of contraindications) {
|
|
20706
|
+
rows.push(this.contraindicationToCsvRow(contraindication));
|
|
20707
|
+
}
|
|
20708
|
+
const csvBody = rows.join("\r\n");
|
|
20709
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
20710
|
+
}
|
|
20711
|
+
benefitToCsvRow(benefit) {
|
|
20712
|
+
var _a, _b, _c;
|
|
20713
|
+
const values = [
|
|
20714
|
+
(_a = benefit.id) != null ? _a : "",
|
|
20715
|
+
(_b = benefit.name) != null ? _b : "",
|
|
20716
|
+
(_c = benefit.description) != null ? _c : ""
|
|
20717
|
+
];
|
|
20718
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
20719
|
+
}
|
|
20720
|
+
contraindicationToCsvRow(contraindication) {
|
|
20721
|
+
var _a, _b, _c;
|
|
20722
|
+
const values = [
|
|
20723
|
+
(_a = contraindication.id) != null ? _a : "",
|
|
20724
|
+
(_b = contraindication.name) != null ? _b : "",
|
|
20725
|
+
(_c = contraindication.description) != null ? _c : ""
|
|
20726
|
+
];
|
|
20727
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
20728
|
+
}
|
|
20729
|
+
formatCsvValue(value) {
|
|
20730
|
+
const str = value === null || value === void 0 ? "" : String(value);
|
|
20731
|
+
const escaped = str.replace(/"/g, '""');
|
|
20732
|
+
return `"${escaped}"`;
|
|
20733
|
+
}
|
|
20249
20734
|
};
|
|
20250
20735
|
|
|
20251
20736
|
// src/backoffice/types/static/contraindication.types.ts
|
package/package.json
CHANGED
|
@@ -167,4 +167,90 @@ export class BrandService extends BaseService {
|
|
|
167
167
|
...docSnap.data(),
|
|
168
168
|
} as Brand;
|
|
169
169
|
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Exports brands to CSV string, suitable for Excel/Sheets.
|
|
173
|
+
* Includes headers and optional UTF-8 BOM.
|
|
174
|
+
* By default exports only active brands (set includeInactive to true to export all).
|
|
175
|
+
*/
|
|
176
|
+
async exportToCsv(options?: {
|
|
177
|
+
includeInactive?: boolean;
|
|
178
|
+
includeBom?: boolean;
|
|
179
|
+
}): Promise<string> {
|
|
180
|
+
const includeInactive = options?.includeInactive ?? false;
|
|
181
|
+
const includeBom = options?.includeBom ?? true;
|
|
182
|
+
|
|
183
|
+
const headers = [
|
|
184
|
+
"id",
|
|
185
|
+
"name",
|
|
186
|
+
"manufacturer",
|
|
187
|
+
"website",
|
|
188
|
+
"description",
|
|
189
|
+
"isActive",
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const rows: string[] = [];
|
|
193
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
194
|
+
|
|
195
|
+
const PAGE_SIZE = 1000;
|
|
196
|
+
let cursor: any | undefined;
|
|
197
|
+
|
|
198
|
+
// Build base constraints
|
|
199
|
+
const baseConstraints: QueryConstraint[] = [];
|
|
200
|
+
if (!includeInactive) {
|
|
201
|
+
baseConstraints.push(where("isActive", "==", true));
|
|
202
|
+
}
|
|
203
|
+
baseConstraints.push(orderBy("name_lowercase"));
|
|
204
|
+
|
|
205
|
+
// Page through all results
|
|
206
|
+
// eslint-disable-next-line no-constant-condition
|
|
207
|
+
while (true) {
|
|
208
|
+
const constraints: QueryConstraint[] = [...baseConstraints, limit(PAGE_SIZE)];
|
|
209
|
+
if (cursor) constraints.push(startAfter(cursor));
|
|
210
|
+
|
|
211
|
+
const q = query(this.getBrandsRef(), ...constraints);
|
|
212
|
+
const snapshot = await getDocs(q);
|
|
213
|
+
if (snapshot.empty) break;
|
|
214
|
+
|
|
215
|
+
for (const d of snapshot.docs) {
|
|
216
|
+
const brand = ({ id: d.id, ...d.data() } as unknown) as Brand;
|
|
217
|
+
rows.push(this.brandToCsvRow(brand));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
221
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const csvBody = rows.join("\r\n");
|
|
225
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private brandToCsvRow(brand: Brand): string {
|
|
229
|
+
const values = [
|
|
230
|
+
brand.id ?? "",
|
|
231
|
+
brand.name ?? "",
|
|
232
|
+
brand.manufacturer ?? "",
|
|
233
|
+
brand.website ?? "",
|
|
234
|
+
brand.description ?? "",
|
|
235
|
+
String(brand.isActive ?? ""),
|
|
236
|
+
];
|
|
237
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private formatDateIso(value: any): string {
|
|
241
|
+
// Firestore timestamps may come back as Date or Timestamp; handle both
|
|
242
|
+
if (value instanceof Date) return value.toISOString();
|
|
243
|
+
if (value && typeof value.toDate === "function") {
|
|
244
|
+
const d = value.toDate();
|
|
245
|
+
return d instanceof Date ? d.toISOString() : String(value);
|
|
246
|
+
}
|
|
247
|
+
return String(value ?? "");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private formatCsvValue(value: any): string {
|
|
251
|
+
const str = value === null || value === undefined ? "" : String(value);
|
|
252
|
+
// Escape double quotes by doubling them and wrap in quotes
|
|
253
|
+
const escaped = str.replace(/"/g, '""');
|
|
254
|
+
return `"${escaped}"`;
|
|
255
|
+
}
|
|
170
256
|
}
|