@blackcode_sa/metaestetics-api 1.12.49 → 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 +754 -25
- package/dist/index.mjs +765 -33
- 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/practitioner/practitioner.service.ts +1 -1
- package/src/services/procedure/procedure.service.ts +5 -5
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
arrayUnion,
|
|
16
16
|
arrayRemove,
|
|
17
17
|
Firestore,
|
|
18
|
+
writeBatch,
|
|
19
|
+
QueryConstraint,
|
|
18
20
|
} from 'firebase/firestore';
|
|
19
21
|
import { Technology, TECHNOLOGIES_COLLECTION, ITechnologyService } from '../types/technology.types';
|
|
20
22
|
import { Requirement, RequirementType } from '../types/requirement.types';
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
import { BaseService } from '../../services/base.service';
|
|
30
32
|
import { ProcedureFamily } from '../types/static/procedure-family.types';
|
|
31
33
|
import { Practitioner, PractitionerCertification } from '../../types/practitioner';
|
|
34
|
+
import { Product, PRODUCTS_COLLECTION } from '../types/product.types';
|
|
32
35
|
|
|
33
36
|
/**
|
|
34
37
|
* Default vrednosti za sertifikaciju
|
|
@@ -240,7 +243,25 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
240
243
|
updateData.updatedAt = new Date();
|
|
241
244
|
|
|
242
245
|
const docRef = doc(this.technologiesRef, id);
|
|
246
|
+
|
|
247
|
+
// Get the technology before update to check what changed
|
|
248
|
+
const beforeTech = await this.getById(id);
|
|
249
|
+
|
|
243
250
|
await updateDoc(docRef, updateData);
|
|
251
|
+
|
|
252
|
+
// If categoryId, subcategoryId, or name changed, update all products in subcollection
|
|
253
|
+
const categoryChanged = beforeTech && updateData.categoryId && beforeTech.categoryId !== updateData.categoryId;
|
|
254
|
+
const subcategoryChanged = beforeTech && updateData.subcategoryId && beforeTech.subcategoryId !== updateData.subcategoryId;
|
|
255
|
+
const nameChanged = beforeTech && updateData.name && beforeTech.name !== updateData.name;
|
|
256
|
+
|
|
257
|
+
if (categoryChanged || subcategoryChanged || nameChanged) {
|
|
258
|
+
await this.updateProductsInSubcollection(id, {
|
|
259
|
+
categoryId: updateData.categoryId,
|
|
260
|
+
subcategoryId: updateData.subcategoryId,
|
|
261
|
+
technologyName: updateData.name,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
244
265
|
return this.getById(id);
|
|
245
266
|
}
|
|
246
267
|
|
|
@@ -778,4 +799,272 @@ export class TechnologyService extends BaseService implements ITechnologyService
|
|
|
778
799
|
} as Technology),
|
|
779
800
|
);
|
|
780
801
|
}
|
|
802
|
+
|
|
803
|
+
// ==========================================
|
|
804
|
+
// NEW METHODS: Product assignment management
|
|
805
|
+
// ==========================================
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Assigns multiple products to a technology
|
|
809
|
+
* Updates each product's assignedTechnologyIds array
|
|
810
|
+
*/
|
|
811
|
+
async assignProducts(technologyId: string, productIds: string[]): Promise<void> {
|
|
812
|
+
const batch = writeBatch(this.db);
|
|
813
|
+
|
|
814
|
+
for (const productId of productIds) {
|
|
815
|
+
const productRef = doc(this.db, PRODUCTS_COLLECTION, productId);
|
|
816
|
+
batch.update(productRef, {
|
|
817
|
+
assignedTechnologyIds: arrayUnion(technologyId),
|
|
818
|
+
updatedAt: new Date(),
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
await batch.commit();
|
|
823
|
+
// Cloud Function will handle syncing to subcollections
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Unassigns multiple products from a technology
|
|
828
|
+
* Updates each product's assignedTechnologyIds array
|
|
829
|
+
*/
|
|
830
|
+
async unassignProducts(technologyId: string, productIds: string[]): Promise<void> {
|
|
831
|
+
const batch = writeBatch(this.db);
|
|
832
|
+
|
|
833
|
+
for (const productId of productIds) {
|
|
834
|
+
const productRef = doc(this.db, PRODUCTS_COLLECTION, productId);
|
|
835
|
+
batch.update(productRef, {
|
|
836
|
+
assignedTechnologyIds: arrayRemove(technologyId),
|
|
837
|
+
updatedAt: new Date(),
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
await batch.commit();
|
|
842
|
+
// Cloud Function will handle removing from subcollections
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Gets products assigned to a specific technology
|
|
847
|
+
* Reads from top-level collection for immediate consistency (Cloud Functions may lag)
|
|
848
|
+
*/
|
|
849
|
+
async getAssignedProducts(technologyId: string): Promise<Product[]> {
|
|
850
|
+
const q = query(
|
|
851
|
+
collection(this.db, PRODUCTS_COLLECTION),
|
|
852
|
+
where('assignedTechnologyIds', 'array-contains', technologyId),
|
|
853
|
+
where('isActive', '==', true),
|
|
854
|
+
orderBy('name'),
|
|
855
|
+
);
|
|
856
|
+
const snapshot = await getDocs(q);
|
|
857
|
+
|
|
858
|
+
return snapshot.docs.map(
|
|
859
|
+
doc =>
|
|
860
|
+
({
|
|
861
|
+
id: doc.id,
|
|
862
|
+
...doc.data(),
|
|
863
|
+
} as Product),
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Gets products NOT assigned to a specific technology
|
|
869
|
+
*/
|
|
870
|
+
async getUnassignedProducts(technologyId: string): Promise<Product[]> {
|
|
871
|
+
const q = query(
|
|
872
|
+
collection(this.db, PRODUCTS_COLLECTION),
|
|
873
|
+
where('isActive', '==', true),
|
|
874
|
+
orderBy('name'),
|
|
875
|
+
);
|
|
876
|
+
const snapshot = await getDocs(q);
|
|
877
|
+
|
|
878
|
+
const allProducts = snapshot.docs.map(
|
|
879
|
+
doc =>
|
|
880
|
+
({
|
|
881
|
+
id: doc.id,
|
|
882
|
+
...doc.data(),
|
|
883
|
+
} as Product),
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
// Filter out products already assigned to this technology
|
|
887
|
+
return allProducts.filter(product =>
|
|
888
|
+
!product.assignedTechnologyIds?.includes(technologyId)
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Gets product assignment statistics for a technology
|
|
894
|
+
*/
|
|
895
|
+
async getProductStats(technologyId: string): Promise<{
|
|
896
|
+
totalAssigned: number;
|
|
897
|
+
byBrand: Record<string, number>;
|
|
898
|
+
}> {
|
|
899
|
+
const products = await this.getAssignedProducts(technologyId);
|
|
900
|
+
|
|
901
|
+
const byBrand: Record<string, number> = {};
|
|
902
|
+
products.forEach(product => {
|
|
903
|
+
byBrand[product.brandName] = (byBrand[product.brandName] || 0) + 1;
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
return {
|
|
907
|
+
totalAssigned: products.length,
|
|
908
|
+
byBrand,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Updates products in technology subcollection when technology metadata changes
|
|
914
|
+
* @param technologyId - ID of the technology
|
|
915
|
+
* @param updates - Fields to update (categoryId, subcategoryId, technologyName)
|
|
916
|
+
*/
|
|
917
|
+
private async updateProductsInSubcollection(
|
|
918
|
+
technologyId: string,
|
|
919
|
+
updates: { categoryId?: string; subcategoryId?: string; technologyName?: string }
|
|
920
|
+
): Promise<void> {
|
|
921
|
+
const productsRef = collection(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
922
|
+
const productsSnapshot = await getDocs(productsRef);
|
|
923
|
+
|
|
924
|
+
if (productsSnapshot.empty) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const batch = writeBatch(this.db);
|
|
929
|
+
|
|
930
|
+
for (const productDoc of productsSnapshot.docs) {
|
|
931
|
+
const productRef = productDoc.ref;
|
|
932
|
+
const updateFields: any = {};
|
|
933
|
+
|
|
934
|
+
if (updates.categoryId !== undefined) {
|
|
935
|
+
updateFields.categoryId = updates.categoryId;
|
|
936
|
+
}
|
|
937
|
+
if (updates.subcategoryId !== undefined) {
|
|
938
|
+
updateFields.subcategoryId = updates.subcategoryId;
|
|
939
|
+
}
|
|
940
|
+
if (updates.technologyName !== undefined) {
|
|
941
|
+
updateFields.technologyName = updates.technologyName;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (Object.keys(updateFields).length > 0) {
|
|
945
|
+
batch.update(productRef, updateFields);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
await batch.commit();
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Exports technologies to CSV string, suitable for Excel/Sheets.
|
|
954
|
+
* Includes headers and optional UTF-8 BOM.
|
|
955
|
+
* By default exports only active technologies (set includeInactive to true to export all).
|
|
956
|
+
* Includes product names from subcollections.
|
|
957
|
+
*/
|
|
958
|
+
async exportToCsv(options?: {
|
|
959
|
+
includeInactive?: boolean;
|
|
960
|
+
includeBom?: boolean;
|
|
961
|
+
}): Promise<string> {
|
|
962
|
+
const includeInactive = options?.includeInactive ?? false;
|
|
963
|
+
const includeBom = options?.includeBom ?? true;
|
|
964
|
+
|
|
965
|
+
const headers = [
|
|
966
|
+
"id",
|
|
967
|
+
"name",
|
|
968
|
+
"description",
|
|
969
|
+
"family",
|
|
970
|
+
"categoryId",
|
|
971
|
+
"subcategoryId",
|
|
972
|
+
"technicalDetails",
|
|
973
|
+
"requirements_pre",
|
|
974
|
+
"requirements_post",
|
|
975
|
+
"blockingConditions",
|
|
976
|
+
"contraindications",
|
|
977
|
+
"benefits",
|
|
978
|
+
"certificationMinimumLevel",
|
|
979
|
+
"certificationRequiredSpecialties",
|
|
980
|
+
"documentationTemplateIds",
|
|
981
|
+
"productNames",
|
|
982
|
+
"isActive",
|
|
983
|
+
];
|
|
984
|
+
|
|
985
|
+
const rows: string[] = [];
|
|
986
|
+
rows.push(headers.map((h) => this.formatCsvValue(h)).join(","));
|
|
987
|
+
|
|
988
|
+
const PAGE_SIZE = 1000;
|
|
989
|
+
let cursor: any | undefined;
|
|
990
|
+
|
|
991
|
+
// Build base constraints
|
|
992
|
+
const constraints: QueryConstraint[] = [];
|
|
993
|
+
if (!includeInactive) {
|
|
994
|
+
constraints.push(where("isActive", "==", true));
|
|
995
|
+
}
|
|
996
|
+
constraints.push(orderBy("name"));
|
|
997
|
+
|
|
998
|
+
// Page through all results
|
|
999
|
+
// eslint-disable-next-line no-constant-condition
|
|
1000
|
+
while (true) {
|
|
1001
|
+
const queryConstraints: QueryConstraint[] = [...constraints, limit(PAGE_SIZE)];
|
|
1002
|
+
if (cursor) queryConstraints.push(startAfter(cursor));
|
|
1003
|
+
|
|
1004
|
+
const q = query(this.technologiesRef, ...queryConstraints);
|
|
1005
|
+
const snapshot = await getDocs(q);
|
|
1006
|
+
if (snapshot.empty) break;
|
|
1007
|
+
|
|
1008
|
+
for (const d of snapshot.docs) {
|
|
1009
|
+
const technology = ({ id: d.id, ...d.data() } as unknown) as Technology;
|
|
1010
|
+
// Fetch products for this technology
|
|
1011
|
+
const productNames = await this.getProductNamesForTechnology(technology.id!);
|
|
1012
|
+
rows.push(this.technologyToCsvRow(technology, productNames));
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
cursor = snapshot.docs[snapshot.docs.length - 1];
|
|
1016
|
+
if (snapshot.size < PAGE_SIZE) break;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const csvBody = rows.join("\r\n");
|
|
1020
|
+
return includeBom ? "\uFEFF" + csvBody : csvBody;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Gets product names from the technology's product subcollection
|
|
1025
|
+
*/
|
|
1026
|
+
private async getProductNamesForTechnology(technologyId: string): Promise<string[]> {
|
|
1027
|
+
try {
|
|
1028
|
+
const productsRef = collection(this.db, TECHNOLOGIES_COLLECTION, technologyId, PRODUCTS_COLLECTION);
|
|
1029
|
+
const q = query(productsRef, where("isActive", "==", true));
|
|
1030
|
+
const snapshot = await getDocs(q);
|
|
1031
|
+
return snapshot.docs.map(doc => {
|
|
1032
|
+
const product = doc.data() as Product;
|
|
1033
|
+
return product.name || "";
|
|
1034
|
+
}).filter(name => name); // Filter out empty names
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
console.error(`Error fetching products for technology ${technologyId}:`, error);
|
|
1037
|
+
return [];
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
private technologyToCsvRow(technology: Technology, productNames: string[] = []): string {
|
|
1042
|
+
const values = [
|
|
1043
|
+
technology.id ?? "",
|
|
1044
|
+
technology.name ?? "",
|
|
1045
|
+
technology.description ?? "",
|
|
1046
|
+
technology.family ?? "",
|
|
1047
|
+
technology.categoryId ?? "",
|
|
1048
|
+
technology.subcategoryId ?? "",
|
|
1049
|
+
technology.technicalDetails ?? "",
|
|
1050
|
+
technology.requirements?.pre?.map(r => r.name).join(";") ?? "",
|
|
1051
|
+
technology.requirements?.post?.map(r => r.name).join(";") ?? "",
|
|
1052
|
+
technology.blockingConditions?.join(";") ?? "",
|
|
1053
|
+
technology.contraindications?.map(c => c.name).join(";") ?? "",
|
|
1054
|
+
technology.benefits?.map(b => b.name).join(";") ?? "",
|
|
1055
|
+
technology.certificationRequirement?.minimumLevel ?? "",
|
|
1056
|
+
technology.certificationRequirement?.requiredSpecialties?.join(";") ?? "",
|
|
1057
|
+
technology.documentationTemplates?.map(t => t.templateId).join(";") ?? "",
|
|
1058
|
+
productNames.join(";"),
|
|
1059
|
+
String(technology.isActive ?? ""),
|
|
1060
|
+
];
|
|
1061
|
+
return values.map((v) => this.formatCsvValue(v)).join(",");
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private formatCsvValue(value: any): string {
|
|
1065
|
+
const str = value === null || value === undefined ? "" : String(value);
|
|
1066
|
+
// Escape double quotes by doubling them and wrap in quotes
|
|
1067
|
+
const escaped = str.replace(/"/g, '""');
|
|
1068
|
+
return `"${escaped}"`;
|
|
1069
|
+
}
|
|
781
1070
|
}
|
|
@@ -6,9 +6,10 @@ import type { ContraindicationDynamic } from './admin-constants.types';
|
|
|
6
6
|
*
|
|
7
7
|
* @property id - Unique identifier of the product
|
|
8
8
|
* @property name - Name of the product
|
|
9
|
-
* @property description - Detailed description of the product and its purpose
|
|
10
9
|
* @property brandId - ID of the brand that manufactures this product
|
|
11
|
-
* @property
|
|
10
|
+
* @property brandName - Name of the brand (denormalized for display)
|
|
11
|
+
* @property assignedTechnologyIds - Array of technology IDs this product is assigned to
|
|
12
|
+
* @property description - Detailed description of the product and its purpose
|
|
12
13
|
* @property technicalDetails - Technical details and specifications
|
|
13
14
|
* @property warnings - List of warnings related to product use
|
|
14
15
|
* @property dosage - Dosage information (if applicable)
|
|
@@ -24,10 +25,11 @@ export interface Product {
|
|
|
24
25
|
name: string;
|
|
25
26
|
brandId: string;
|
|
26
27
|
brandName: string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
|
|
29
|
+
// NEW: Technology assignment tracking
|
|
30
|
+
assignedTechnologyIds?: string[];
|
|
31
|
+
|
|
32
|
+
// Product details
|
|
31
33
|
createdAt: Date;
|
|
32
34
|
updatedAt: Date;
|
|
33
35
|
isActive: boolean;
|
|
@@ -38,6 +40,18 @@ export interface Product {
|
|
|
38
40
|
composition?: string;
|
|
39
41
|
indications?: string[];
|
|
40
42
|
contraindications?: ContraindicationDynamic[];
|
|
43
|
+
|
|
44
|
+
// LEGACY FIELDS: Only present in technology subcollections (/technologies/{id}/products/)
|
|
45
|
+
// These fields are synced by Cloud Functions for backward compatibility
|
|
46
|
+
// NOT stored in top-level /products collection
|
|
47
|
+
/** Present only in subcollections - synced from technology metadata */
|
|
48
|
+
technologyId?: string;
|
|
49
|
+
/** Present only in subcollections - synced from technology name */
|
|
50
|
+
technologyName?: string;
|
|
51
|
+
/** Present only in subcollections - synced from technology categoryId */
|
|
52
|
+
categoryId?: string;
|
|
53
|
+
/** Present only in subcollections - synced from technology subcategoryId */
|
|
54
|
+
subcategoryId?: string;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
/**
|
|
@@ -47,9 +61,97 @@ export const PRODUCTS_COLLECTION = 'products';
|
|
|
47
61
|
|
|
48
62
|
/**
|
|
49
63
|
* Interface for the ProductService class
|
|
64
|
+
*
|
|
65
|
+
* NOTE: This interface maintains backward compatibility while adding new top-level collection methods.
|
|
66
|
+
* Old methods using technologyId are kept for existing code, new methods work with top-level collection.
|
|
50
67
|
*/
|
|
51
68
|
export interface IProductService {
|
|
69
|
+
// ==========================================
|
|
70
|
+
// NEW METHODS: Top-level collection (preferred)
|
|
71
|
+
// ==========================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates a new product in the top-level collection
|
|
75
|
+
* @param brandId - ID of the brand that manufactures this product
|
|
76
|
+
* @param product - Product data
|
|
77
|
+
* @param technologyIds - Optional array of technology IDs to assign this product to
|
|
78
|
+
*/
|
|
79
|
+
createTopLevel(
|
|
80
|
+
brandId: string,
|
|
81
|
+
product: Omit<Product, 'id' | 'createdAt' | 'updatedAt' | 'brandId' | 'assignedTechnologyIds'>,
|
|
82
|
+
technologyIds?: string[],
|
|
83
|
+
): Promise<Product>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Gets all products from the top-level collection
|
|
87
|
+
* @param options - Query options
|
|
88
|
+
*/
|
|
89
|
+
getAllTopLevel(options: {
|
|
90
|
+
rowsPerPage: number;
|
|
91
|
+
lastVisible?: any;
|
|
92
|
+
brandId?: string;
|
|
93
|
+
}): Promise<{ products: Product[]; lastVisible: any }>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Gets a product by ID from the top-level collection
|
|
97
|
+
* @param productId - ID of the product
|
|
98
|
+
*/
|
|
99
|
+
getByIdTopLevel(productId: string): Promise<Product | null>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Updates a product in the top-level collection
|
|
103
|
+
* @param productId - ID of the product to update
|
|
104
|
+
* @param product - Updated product data
|
|
105
|
+
*/
|
|
106
|
+
updateTopLevel(
|
|
107
|
+
productId: string,
|
|
108
|
+
product: Partial<Omit<Product, 'id' | 'createdAt' | 'brandId'>>,
|
|
109
|
+
): Promise<Product | null>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Deletes a product from the top-level collection (soft delete)
|
|
113
|
+
* @param productId - ID of the product to delete
|
|
114
|
+
*/
|
|
115
|
+
deleteTopLevel(productId: string): Promise<void>;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Assigns a product to a technology
|
|
119
|
+
* @param productId - ID of the product
|
|
120
|
+
* @param technologyId - ID of the technology
|
|
121
|
+
*/
|
|
122
|
+
assignToTechnology(productId: string, technologyId: string): Promise<void>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Unassigns a product from a technology
|
|
126
|
+
* @param productId - ID of the product
|
|
127
|
+
* @param technologyId - ID of the technology
|
|
128
|
+
*/
|
|
129
|
+
unassignFromTechnology(productId: string, technologyId: string): Promise<void>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Gets products assigned to a specific technology
|
|
133
|
+
* @param technologyId - ID of the technology
|
|
134
|
+
*/
|
|
135
|
+
getAssignedProducts(technologyId: string): Promise<Product[]>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Gets products NOT assigned to a specific technology
|
|
139
|
+
* @param technologyId - ID of the technology
|
|
140
|
+
*/
|
|
141
|
+
getUnassignedProducts(technologyId: string): Promise<Product[]>;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Gets all products for a brand
|
|
145
|
+
* @param brandId - ID of the brand
|
|
146
|
+
*/
|
|
147
|
+
getByBrand(brandId: string): Promise<Product[]>;
|
|
148
|
+
|
|
149
|
+
// ==========================================
|
|
150
|
+
// DEPRECATED METHODS: Kept for backward compatibility
|
|
151
|
+
// ==========================================
|
|
152
|
+
|
|
52
153
|
/**
|
|
154
|
+
* @deprecated Use createTopLevel instead
|
|
53
155
|
* Creates a new product
|
|
54
156
|
* @param technologyId - ID of the technology this product is used with
|
|
55
157
|
* @param brandId - ID of the brand that manufactures this product
|
|
@@ -62,6 +164,7 @@ export interface IProductService {
|
|
|
62
164
|
): Promise<Product>;
|
|
63
165
|
|
|
64
166
|
/**
|
|
167
|
+
* @deprecated Use getAllTopLevel instead
|
|
65
168
|
* Gets a paginated list of all products, with optional filters.
|
|
66
169
|
*/
|
|
67
170
|
getAll(options: {
|
|
@@ -73,6 +176,7 @@ export interface IProductService {
|
|
|
73
176
|
}): Promise<{ products: Product[]; lastVisible: any }>;
|
|
74
177
|
|
|
75
178
|
/**
|
|
179
|
+
* @deprecated Use alternative counting methods
|
|
76
180
|
* Gets the total count of active products, with optional filters.
|
|
77
181
|
*/
|
|
78
182
|
getProductsCount(options: {
|
|
@@ -82,6 +186,7 @@ export interface IProductService {
|
|
|
82
186
|
}): Promise<number>;
|
|
83
187
|
|
|
84
188
|
/**
|
|
189
|
+
* @deprecated Use alternative counting methods
|
|
85
190
|
* Gets counts of active products grouped by category, subcategory, and technology.
|
|
86
191
|
*/
|
|
87
192
|
getProductCounts(): Promise<{
|
|
@@ -91,18 +196,21 @@ export interface IProductService {
|
|
|
91
196
|
}>;
|
|
92
197
|
|
|
93
198
|
/**
|
|
199
|
+
* @deprecated Use getAssignedProducts instead
|
|
94
200
|
* Gets all products for a specific technology (non-paginated, for filters/dropdowns)
|
|
95
201
|
* @param technologyId - ID of the technology
|
|
96
202
|
*/
|
|
97
203
|
getAllByTechnology(technologyId: string): Promise<Product[]>;
|
|
98
204
|
|
|
99
205
|
/**
|
|
206
|
+
* @deprecated Use getByBrand instead
|
|
100
207
|
* Gets all products for a brand
|
|
101
208
|
* @param brandId - ID of the brand
|
|
102
209
|
*/
|
|
103
210
|
getAllByBrand(brandId: string): Promise<Product[]>;
|
|
104
211
|
|
|
105
212
|
/**
|
|
213
|
+
* @deprecated Use updateTopLevel instead
|
|
106
214
|
* Updates a product
|
|
107
215
|
* @param technologyId - ID of the technology
|
|
108
216
|
* @param productId - ID of the product to update
|
|
@@ -115,6 +223,7 @@ export interface IProductService {
|
|
|
115
223
|
): Promise<Product | null>;
|
|
116
224
|
|
|
117
225
|
/**
|
|
226
|
+
* @deprecated Use deleteTopLevel instead
|
|
118
227
|
* Deletes a product (soft delete)
|
|
119
228
|
* @param technologyId - ID of the technology
|
|
120
229
|
* @param productId - ID of the product to delete
|
|
@@ -122,6 +231,7 @@ export interface IProductService {
|
|
|
122
231
|
delete(technologyId: string, productId: string): Promise<void>;
|
|
123
232
|
|
|
124
233
|
/**
|
|
234
|
+
* @deprecated Use getByIdTopLevel instead
|
|
125
235
|
* Gets a product by ID
|
|
126
236
|
* @param technologyId - ID of the technology
|
|
127
237
|
* @param productId - ID of the product
|
|
@@ -1566,7 +1566,7 @@ export class PractitionerService extends BaseService {
|
|
|
1566
1566
|
price: 0,
|
|
1567
1567
|
currency: Currency.EUR,
|
|
1568
1568
|
pricingMeasure: PricingMeasure.PER_SESSION,
|
|
1569
|
-
productsMetadata
|
|
1569
|
+
// productsMetadata omitted - no products needed for consultations
|
|
1570
1570
|
duration: 30, // 30 minutes consultation
|
|
1571
1571
|
practitionerId: practitionerId,
|
|
1572
1572
|
clinicBranchId: clinicId,
|
|
@@ -201,7 +201,7 @@ export class ProcedureService extends BaseService {
|
|
|
201
201
|
const procedureId = this.generateId();
|
|
202
202
|
|
|
203
203
|
// Get references to related entities (Category, Subcategory, Technology, and optionally Product)
|
|
204
|
-
const baseEntitiesPromises = [
|
|
204
|
+
const baseEntitiesPromises: Promise<Category | Subcategory | Technology | Product | null>[] = [
|
|
205
205
|
this.categoryService.getById(validatedData.categoryId),
|
|
206
206
|
this.subcategoryService.getById(validatedData.categoryId, validatedData.subcategoryId),
|
|
207
207
|
this.technologyService.getById(validatedData.technologyId),
|
|
@@ -301,7 +301,7 @@ export class ProcedureService extends BaseService {
|
|
|
301
301
|
category, // Embed full objects
|
|
302
302
|
subcategory,
|
|
303
303
|
technology,
|
|
304
|
-
product,
|
|
304
|
+
...(product && { product }), // Only include product field if it exists (Firestore doesn't allow undefined)
|
|
305
305
|
productsMetadata: transformedProductsMetadata, // Use transformed data, not original
|
|
306
306
|
blockingConditions: technology.blockingConditions,
|
|
307
307
|
contraindications: technology.contraindications || [],
|
|
@@ -366,7 +366,7 @@ export class ProcedureService extends BaseService {
|
|
|
366
366
|
const validatedData = createProcedureSchema.parse(validationData);
|
|
367
367
|
|
|
368
368
|
// 2. Fetch common data once to avoid redundant reads
|
|
369
|
-
const baseEntitiesPromises = [
|
|
369
|
+
const baseEntitiesPromises: Promise<Category | Subcategory | Technology | Product | null>[] = [
|
|
370
370
|
this.categoryService.getById(validatedData.categoryId),
|
|
371
371
|
this.subcategoryService.getById(validatedData.categoryId, validatedData.subcategoryId),
|
|
372
372
|
this.technologyService.getById(validatedData.technologyId),
|
|
@@ -494,7 +494,7 @@ export class ProcedureService extends BaseService {
|
|
|
494
494
|
category,
|
|
495
495
|
subcategory,
|
|
496
496
|
technology,
|
|
497
|
-
product,
|
|
497
|
+
...(product && { product }), // Only include product field if it exists (Firestore doesn't allow undefined)
|
|
498
498
|
productsMetadata: transformedProductsMetadata, // Use transformed data, not original
|
|
499
499
|
blockingConditions: technology.blockingConditions,
|
|
500
500
|
contraindications: technology.contraindications || [],
|
|
@@ -1514,7 +1514,7 @@ export class ProcedureService extends BaseService {
|
|
|
1514
1514
|
category,
|
|
1515
1515
|
subcategory,
|
|
1516
1516
|
technology,
|
|
1517
|
-
|
|
1517
|
+
// No product field for consultations (Firestore doesn't allow undefined, so we omit it entirely)
|
|
1518
1518
|
productsMetadata: transformedProductsMetadata, // Empty array for consultations
|
|
1519
1519
|
blockingConditions: technology.blockingConditions,
|
|
1520
1520
|
contraindications: technology.contraindications || [],
|