@blackcode_sa/metaestetics-api 1.12.46 → 1.12.47
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 +5 -4
- package/dist/admin/index.d.ts +5 -4
- package/dist/admin/index.js +3 -26
- package/dist/admin/index.mjs +3 -26
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +49 -44
- package/dist/index.mjs +49 -44
- package/package.json +1 -1
- package/src/admin/booking/booking.admin.ts +21 -17
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +4 -31
- package/src/services/appointment/utils/appointment.utils.ts +2 -2
- package/src/services/appointment/utils/extended-procedure.utils.ts +82 -53
- package/src/services/appointment/utils/recommended-procedure.utils.ts +8 -6
- package/src/services/practitioner/practitioner.service.ts +2 -10
- package/src/services/procedure/procedure.service.ts +22 -22
- package/src/types/procedure/index.ts +5 -5
- package/src/validations/appointment.schema.ts +60 -53
- package/src/validations/procedure.schema.ts +4 -4
package/dist/index.mjs
CHANGED
|
@@ -529,7 +529,8 @@ var recommendedProcedureTimeframeSchema = z3.object({
|
|
|
529
529
|
});
|
|
530
530
|
var recommendedProcedureSchema = z3.object({
|
|
531
531
|
procedure: extendedProcedureInfoSchema,
|
|
532
|
-
note: z3.string().
|
|
532
|
+
note: z3.string().max(MAX_STRING_LENGTH_LONG, "Note too long"),
|
|
533
|
+
// Note is now optional (no min length)
|
|
533
534
|
timeframe: recommendedProcedureTimeframeSchema
|
|
534
535
|
});
|
|
535
536
|
var appointmentMetadataSchema = z3.object({
|
|
@@ -1739,7 +1740,7 @@ async function aggregateProductsFromProcedure(db, procedureId, existingProducts)
|
|
|
1739
1740
|
}
|
|
1740
1741
|
const procedureData = procedureSnap.data();
|
|
1741
1742
|
const productsMetadata = procedureData.productsMetadata || [];
|
|
1742
|
-
const newProducts = productsMetadata.map((pp) => {
|
|
1743
|
+
const newProducts = productsMetadata.filter((pp) => pp && pp.product).map((pp) => {
|
|
1743
1744
|
const product = pp.product;
|
|
1744
1745
|
return {
|
|
1745
1746
|
productId: product.id,
|
|
@@ -1792,7 +1793,7 @@ async function createExtendedProcedureInfo(db, procedureId) {
|
|
|
1792
1793
|
// Access embedded technology
|
|
1793
1794
|
procedureTechnologyName: data.technology.name,
|
|
1794
1795
|
// Access embedded technology
|
|
1795
|
-
procedureProducts: (data.productsMetadata || []).map((pp) => ({
|
|
1796
|
+
procedureProducts: (data.productsMetadata || []).filter((pp) => pp && pp.product).map((pp) => ({
|
|
1796
1797
|
productId: pp.product.id,
|
|
1797
1798
|
// Access embedded product
|
|
1798
1799
|
productName: pp.product.name,
|
|
@@ -1808,9 +1809,7 @@ async function addExtendedProcedureUtil(db, appointmentId, procedureId) {
|
|
|
1808
1809
|
var _a;
|
|
1809
1810
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
1810
1811
|
const metadata = initializeMetadata(appointment);
|
|
1811
|
-
const existingProcedure = (_a = metadata.extendedProcedures) == null ? void 0 : _a.find(
|
|
1812
|
-
(p) => p.procedureId === procedureId
|
|
1813
|
-
);
|
|
1812
|
+
const existingProcedure = (_a = metadata.extendedProcedures) == null ? void 0 : _a.find((p) => p.procedureId === procedureId);
|
|
1814
1813
|
if (existingProcedure) {
|
|
1815
1814
|
throw new Error(`Procedure ${procedureId} is already added to this appointment`);
|
|
1816
1815
|
}
|
|
@@ -1841,7 +1840,10 @@ async function addExtendedProcedureUtil(db, appointmentId, procedureId) {
|
|
|
1841
1840
|
);
|
|
1842
1841
|
updatedLinkedFormIds = [...updatedLinkedFormIds, ...formInitResult.allLinkedFormIds];
|
|
1843
1842
|
updatedLinkedForms = [...updatedLinkedForms, ...formInitResult.initializedFormsInfo];
|
|
1844
|
-
updatedPendingUserFormsIds = [
|
|
1843
|
+
updatedPendingUserFormsIds = [
|
|
1844
|
+
...updatedPendingUserFormsIds,
|
|
1845
|
+
...formInitResult.pendingUserFormsIds
|
|
1846
|
+
];
|
|
1845
1847
|
}
|
|
1846
1848
|
const extendedProcedures = [...metadata.extendedProcedures || [], extendedProcedureInfo];
|
|
1847
1849
|
const appointmentRef = doc5(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
@@ -1861,9 +1863,7 @@ async function removeExtendedProcedureUtil(db, appointmentId, procedureId) {
|
|
|
1861
1863
|
if (!metadata.extendedProcedures || metadata.extendedProcedures.length === 0) {
|
|
1862
1864
|
throw new Error("No extended procedures found for this appointment");
|
|
1863
1865
|
}
|
|
1864
|
-
const procedureIndex = metadata.extendedProcedures.findIndex(
|
|
1865
|
-
(p) => p.procedureId === procedureId
|
|
1866
|
-
);
|
|
1866
|
+
const procedureIndex = metadata.extendedProcedures.findIndex((p) => p.procedureId === procedureId);
|
|
1867
1867
|
if (procedureIndex === -1) {
|
|
1868
1868
|
throw new Error(`Extended procedure ${procedureId} not found in this appointment`);
|
|
1869
1869
|
}
|
|
@@ -1871,6 +1871,20 @@ async function removeExtendedProcedureUtil(db, appointmentId, procedureId) {
|
|
|
1871
1871
|
const updatedProducts = (metadata.appointmentProducts || []).filter(
|
|
1872
1872
|
(p) => p.procedureId !== procedureId
|
|
1873
1873
|
);
|
|
1874
|
+
const updatedZonesData = { ...metadata.zonesData || {} };
|
|
1875
|
+
let productsRemovedFromZones = 0;
|
|
1876
|
+
Object.keys(updatedZonesData).forEach((zoneId) => {
|
|
1877
|
+
const originalLength = updatedZonesData[zoneId].length;
|
|
1878
|
+
updatedZonesData[zoneId] = updatedZonesData[zoneId].filter((item) => {
|
|
1879
|
+
if (item.type === "note") return true;
|
|
1880
|
+
if (item.type === "item" && item.belongingProcedureId !== procedureId) return true;
|
|
1881
|
+
return false;
|
|
1882
|
+
});
|
|
1883
|
+
productsRemovedFromZones += originalLength - updatedZonesData[zoneId].length;
|
|
1884
|
+
});
|
|
1885
|
+
console.log(
|
|
1886
|
+
`\u{1F5D1}\uFE0F [removeExtendedProcedure] Removed ${productsRemovedFromZones} products from zones for procedure ${procedureId}`
|
|
1887
|
+
);
|
|
1874
1888
|
const removedFormIds = await removeFormsForExtendedProcedure(db, appointmentId, procedureId);
|
|
1875
1889
|
const updatedLinkedFormIds = (appointment.linkedFormIds || []).filter(
|
|
1876
1890
|
(formId) => !removedFormIds.includes(formId)
|
|
@@ -1885,6 +1899,7 @@ async function removeExtendedProcedureUtil(db, appointmentId, procedureId) {
|
|
|
1885
1899
|
await updateDoc4(appointmentRef, {
|
|
1886
1900
|
"metadata.extendedProcedures": metadata.extendedProcedures,
|
|
1887
1901
|
"metadata.appointmentProducts": updatedProducts,
|
|
1902
|
+
"metadata.zonesData": updatedZonesData,
|
|
1888
1903
|
linkedFormIds: updatedLinkedFormIds,
|
|
1889
1904
|
linkedForms: updatedLinkedForms,
|
|
1890
1905
|
pendingUserFormsIds: updatedPendingUserFormsIds,
|
|
@@ -1923,7 +1938,7 @@ async function createExtendedProcedureInfoForRecommended(db, procedureId) {
|
|
|
1923
1938
|
procedureSubCategoryName: data.subcategory.name,
|
|
1924
1939
|
procedureTechnologyId: data.technology.id,
|
|
1925
1940
|
procedureTechnologyName: data.technology.name,
|
|
1926
|
-
procedureProducts: (data.productsMetadata || []).map((pp) => ({
|
|
1941
|
+
procedureProducts: (data.productsMetadata || []).filter((pp) => pp && pp.product).map((pp) => ({
|
|
1927
1942
|
productId: pp.product.id,
|
|
1928
1943
|
productName: pp.product.name,
|
|
1929
1944
|
brandId: pp.product.brandId,
|
|
@@ -8747,15 +8762,8 @@ var PractitionerService = class extends BaseService {
|
|
|
8747
8762
|
price: 0,
|
|
8748
8763
|
currency: "EUR" /* EUR */,
|
|
8749
8764
|
pricingMeasure: "per_session" /* PER_SESSION */,
|
|
8750
|
-
productsMetadata:
|
|
8751
|
-
|
|
8752
|
-
productId: "free-consultation-product",
|
|
8753
|
-
price: 0,
|
|
8754
|
-
currency: "EUR" /* EUR */,
|
|
8755
|
-
pricingMeasure: "per_session" /* PER_SESSION */,
|
|
8756
|
-
isDefault: true
|
|
8757
|
-
}
|
|
8758
|
-
],
|
|
8765
|
+
productsMetadata: void 0,
|
|
8766
|
+
// No products needed for consultations
|
|
8759
8767
|
duration: 30,
|
|
8760
8768
|
// 30 minutes consultation
|
|
8761
8769
|
practitionerId,
|
|
@@ -16562,11 +16570,11 @@ var createProcedureSchema = z26.object({
|
|
|
16562
16570
|
categoryId: z26.string().min(1),
|
|
16563
16571
|
subcategoryId: z26.string().min(1),
|
|
16564
16572
|
technologyId: z26.string().min(1),
|
|
16565
|
-
productId: z26.string().min(1),
|
|
16573
|
+
productId: z26.string().min(1).optional(),
|
|
16566
16574
|
price: z26.number().min(0),
|
|
16567
16575
|
currency: z26.nativeEnum(Currency),
|
|
16568
16576
|
pricingMeasure: z26.nativeEnum(PricingMeasure),
|
|
16569
|
-
productsMetadata: z26.array(procedureProductDataSchema).min(1),
|
|
16577
|
+
productsMetadata: z26.array(procedureProductDataSchema).min(1).optional(),
|
|
16570
16578
|
duration: z26.number().min(1).max(480),
|
|
16571
16579
|
// Max 8 hours
|
|
16572
16580
|
practitionerId: z26.string().min(1),
|
|
@@ -16603,10 +16611,10 @@ var procedureSchema = z26.object({
|
|
|
16603
16611
|
// We'll validate the full subcategory object separately
|
|
16604
16612
|
technology: z26.any(),
|
|
16605
16613
|
// We'll validate the full technology object separately
|
|
16606
|
-
product: z26.any(),
|
|
16607
|
-
// We'll validate the full product object separately
|
|
16608
|
-
productsMetadata: z26.array(storedProcedureProductSchema).
|
|
16609
|
-
// Use stored format schema
|
|
16614
|
+
product: z26.any().optional(),
|
|
16615
|
+
// We'll validate the full product object separately (optional for consultations)
|
|
16616
|
+
productsMetadata: z26.array(storedProcedureProductSchema).optional(),
|
|
16617
|
+
// Use stored format schema (optional for consultations)
|
|
16610
16618
|
price: z26.number().min(0),
|
|
16611
16619
|
currency: z26.nativeEnum(Currency),
|
|
16612
16620
|
pricingMeasure: z26.nativeEnum(PricingMeasure),
|
|
@@ -16699,11 +16707,14 @@ var ProcedureService = class extends BaseService {
|
|
|
16699
16707
|
}
|
|
16700
16708
|
/**
|
|
16701
16709
|
* Transforms validated procedure product data (with productId) to ProcedureProduct objects (with full product)
|
|
16702
|
-
* @param productsMetadata Array of validated procedure product data
|
|
16710
|
+
* @param productsMetadata Array of validated procedure product data (optional)
|
|
16703
16711
|
* @param technologyId Technology ID to fetch products from
|
|
16704
16712
|
* @returns Array of ProcedureProduct objects with full product information
|
|
16705
16713
|
*/
|
|
16706
16714
|
async transformProductsMetadata(productsMetadata, technologyId) {
|
|
16715
|
+
if (!productsMetadata || productsMetadata.length === 0) {
|
|
16716
|
+
return [];
|
|
16717
|
+
}
|
|
16707
16718
|
const transformedProducts = [];
|
|
16708
16719
|
for (const productData of productsMetadata) {
|
|
16709
16720
|
const product = await this.productService.getById(technologyId, productData.productId);
|
|
@@ -16730,12 +16741,16 @@ var ProcedureService = class extends BaseService {
|
|
|
16730
16741
|
async createProcedure(data) {
|
|
16731
16742
|
var _a, _b, _c;
|
|
16732
16743
|
const validatedData = createProcedureSchema.parse(data);
|
|
16744
|
+
if (!validatedData.productId) {
|
|
16745
|
+
throw new Error("productId is required for regular procedures. Use createConsultationProcedure for product-free procedures.");
|
|
16746
|
+
}
|
|
16733
16747
|
const procedureId = this.generateId();
|
|
16734
16748
|
const [category, subcategory, technology, product] = await Promise.all([
|
|
16735
16749
|
this.categoryService.getById(validatedData.categoryId),
|
|
16736
16750
|
this.subcategoryService.getById(validatedData.categoryId, validatedData.subcategoryId),
|
|
16737
16751
|
this.technologyService.getById(validatedData.technologyId),
|
|
16738
16752
|
this.productService.getById(validatedData.technologyId, validatedData.productId)
|
|
16753
|
+
// Safe: validated above
|
|
16739
16754
|
]);
|
|
16740
16755
|
if (!category || !subcategory || !technology || !product) {
|
|
16741
16756
|
throw new Error("One or more required base entities not found");
|
|
@@ -16844,6 +16859,9 @@ var ProcedureService = class extends BaseService {
|
|
|
16844
16859
|
if (!practitionerIds || practitionerIds.length === 0) {
|
|
16845
16860
|
throw new Error("Practitioner IDs array cannot be empty.");
|
|
16846
16861
|
}
|
|
16862
|
+
if (!baseData.productId) {
|
|
16863
|
+
throw new Error("productId is required for regular procedures. Use createConsultationProcedure for product-free procedures.");
|
|
16864
|
+
}
|
|
16847
16865
|
const validationData = { ...baseData, practitionerId: practitionerIds[0] };
|
|
16848
16866
|
const validatedData = createProcedureSchema.parse(validationData);
|
|
16849
16867
|
const [category, subcategory, technology, product, clinicSnapshot] = await Promise.all([
|
|
@@ -16851,6 +16869,7 @@ var ProcedureService = class extends BaseService {
|
|
|
16851
16869
|
this.subcategoryService.getById(validatedData.categoryId, validatedData.subcategoryId),
|
|
16852
16870
|
this.technologyService.getById(validatedData.technologyId),
|
|
16853
16871
|
this.productService.getById(validatedData.technologyId, validatedData.productId),
|
|
16872
|
+
// Safe: validated above
|
|
16854
16873
|
getDoc37(doc36(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId))
|
|
16855
16874
|
]);
|
|
16856
16875
|
if (!category || !subcategory || !technology || !product) {
|
|
@@ -17711,20 +17730,6 @@ var ProcedureService = class extends BaseService {
|
|
|
17711
17730
|
rating: ((_a = practitioner.reviewInfo) == null ? void 0 : _a.averageRating) || 0,
|
|
17712
17731
|
services: practitioner.procedures || []
|
|
17713
17732
|
};
|
|
17714
|
-
const consultationProduct = {
|
|
17715
|
-
id: "consultation-no-product",
|
|
17716
|
-
name: "No Product Required",
|
|
17717
|
-
description: "Consultation procedures do not require specific products",
|
|
17718
|
-
brandId: "consultation-brand",
|
|
17719
|
-
brandName: "Consultation",
|
|
17720
|
-
technologyId: data.technologyId,
|
|
17721
|
-
technologyName: technology.name,
|
|
17722
|
-
categoryId: technology.categoryId,
|
|
17723
|
-
subcategoryId: technology.subcategoryId,
|
|
17724
|
-
isActive: true,
|
|
17725
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
17726
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
17727
|
-
};
|
|
17728
17733
|
const { productsMetadata: _, ...dataWithoutProductsMetadata } = data;
|
|
17729
17734
|
const newProcedure = {
|
|
17730
17735
|
id: procedureId,
|
|
@@ -17734,10 +17739,10 @@ var ProcedureService = class extends BaseService {
|
|
|
17734
17739
|
category,
|
|
17735
17740
|
subcategory,
|
|
17736
17741
|
technology,
|
|
17737
|
-
product:
|
|
17738
|
-
//
|
|
17742
|
+
product: void 0,
|
|
17743
|
+
// No product needed for consultations
|
|
17739
17744
|
productsMetadata: transformedProductsMetadata,
|
|
17740
|
-
//
|
|
17745
|
+
// Empty array for consultations
|
|
17741
17746
|
blockingConditions: technology.blockingConditions,
|
|
17742
17747
|
contraindications: technology.contraindications || [],
|
|
17743
17748
|
contraindicationIds: ((_b = technology.contraindications) == null ? void 0 : _b.map((c) => c.id)) || [],
|
package/package.json
CHANGED
|
@@ -1004,29 +1004,33 @@ export class BookingAdmin {
|
|
|
1004
1004
|
procedureTechnologyName: procedureTechnology?.name || "",
|
|
1005
1005
|
procedureProductBrandId: procedureProduct?.brandId || "",
|
|
1006
1006
|
procedureProductBrandName: procedureProduct?.brandName || "",
|
|
1007
|
-
procedureProducts: productsMetadata
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1007
|
+
procedureProducts: productsMetadata
|
|
1008
|
+
.filter((pp: any) => pp && pp.product) // Safety check for product-free procedures
|
|
1009
|
+
.map((pp: any) => ({
|
|
1010
|
+
productId: pp.product.id,
|
|
1011
|
+
productName: pp.product.name,
|
|
1012
|
+
brandId: pp.product.brandId,
|
|
1013
|
+
brandName: pp.product.brandName,
|
|
1014
|
+
})),
|
|
1013
1015
|
};
|
|
1014
1016
|
}
|
|
1015
1017
|
|
|
1016
1018
|
private _generateAppointmentProductsFromProcedure(procedure: Procedure): any[] {
|
|
1017
1019
|
const productsMetadata = procedure.productsMetadata || [];
|
|
1018
1020
|
|
|
1019
|
-
return productsMetadata
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1021
|
+
return productsMetadata
|
|
1022
|
+
.filter((pp: any) => pp && pp.product) // Safety check for product-free procedures
|
|
1023
|
+
.map((pp: any) => {
|
|
1024
|
+
const product = pp.product;
|
|
1025
|
+
return {
|
|
1026
|
+
productId: product.id,
|
|
1027
|
+
productName: product.name,
|
|
1028
|
+
brandId: product.brandId,
|
|
1029
|
+
brandName: product.brandName,
|
|
1030
|
+
procedureId: procedure.id,
|
|
1031
|
+
price: pp.price,
|
|
1032
|
+
currency: pp.currency,
|
|
1033
|
+
unitOfMeasurement: pp.pricingMeasure,
|
|
1030
1034
|
};
|
|
1031
1035
|
});
|
|
1032
1036
|
}
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
import { CATEGORIES_COLLECTION } from '../../backoffice/types/category.types';
|
|
8
8
|
import { SUBCATEGORIES_COLLECTION } from '../../backoffice/types/subcategory.types';
|
|
9
9
|
import { TECHNOLOGIES_COLLECTION } from '../../backoffice/types/technology.types';
|
|
10
|
-
import { PRODUCTS_COLLECTION } from '../../backoffice/types/product.types';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Ensures that the free consultation infrastructure exists in the database
|
|
14
|
-
* Creates category, subcategory,
|
|
13
|
+
* Creates category, subcategory, and technology if they don't exist
|
|
14
|
+
* Note: Consultations are product-free procedures, so no product is created
|
|
15
15
|
* @param db - Firestore database instance (optional, defaults to admin.firestore())
|
|
16
16
|
* @returns Promise<boolean> - Always returns true after ensuring infrastructure exists
|
|
17
17
|
*/
|
|
@@ -54,7 +54,7 @@ export async function freeConsultationInfrastructure(
|
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
56
|
* Creates the complete free consultation infrastructure
|
|
57
|
-
* Creates category, subcategory, technology
|
|
57
|
+
* Creates category, subcategory, and technology (no product needed)
|
|
58
58
|
* @param db - Firestore database instance
|
|
59
59
|
*/
|
|
60
60
|
async function createFreeConsultationInfrastructure(db: admin.firestore.Firestore): Promise<void> {
|
|
@@ -125,33 +125,6 @@ async function createFreeConsultationInfrastructure(db: admin.firestore.Firestor
|
|
|
125
125
|
};
|
|
126
126
|
batch.set(technologyRef, technologyData);
|
|
127
127
|
|
|
128
|
-
// 4. Create Product: "free-consultation-product"
|
|
129
|
-
const productRef = db
|
|
130
|
-
.collection(TECHNOLOGIES_COLLECTION)
|
|
131
|
-
.doc('free-consultation-tech')
|
|
132
|
-
.collection(PRODUCTS_COLLECTION)
|
|
133
|
-
.doc('free-consultation-product');
|
|
134
|
-
|
|
135
|
-
const productData = {
|
|
136
|
-
id: 'free-consultation-product',
|
|
137
|
-
name: 'Free Consultation Service',
|
|
138
|
-
description: 'No physical product required for consultation services',
|
|
139
|
-
brandId: 'consultation-brand',
|
|
140
|
-
brandName: 'Consultation Services',
|
|
141
|
-
technologyId: 'free-consultation-tech',
|
|
142
|
-
technologyName: 'Free Consultation Technology',
|
|
143
|
-
categoryId: 'consultation',
|
|
144
|
-
subcategoryId: 'free-consultation',
|
|
145
|
-
isActive: true,
|
|
146
|
-
createdAt: now,
|
|
147
|
-
updatedAt: now,
|
|
148
|
-
technicalDetails: 'Virtual consultation service - no physical product required',
|
|
149
|
-
warnings: [],
|
|
150
|
-
indications: ['Initial patient assessment', 'Treatment planning', 'Patient education'],
|
|
151
|
-
contraindications: [],
|
|
152
|
-
};
|
|
153
|
-
batch.set(productRef, productData);
|
|
154
|
-
|
|
155
128
|
// Commit all changes atomically
|
|
156
129
|
await batch.commit();
|
|
157
130
|
|
|
@@ -159,5 +132,5 @@ async function createFreeConsultationInfrastructure(db: admin.firestore.Firestor
|
|
|
159
132
|
console.log(' ✓ Category: consultation');
|
|
160
133
|
console.log(' ✓ Subcategory: free-consultation');
|
|
161
134
|
console.log(' ✓ Technology: free-consultation-tech');
|
|
162
|
-
console.log('
|
|
135
|
+
console.log(' ℹ No product needed - consultations are product-free procedures');
|
|
163
136
|
}
|
|
@@ -135,8 +135,8 @@ export async function fetchAggregatedInfoUtil(
|
|
|
135
135
|
categoryName: procedureData.category?.name || '',
|
|
136
136
|
subcategoryName: procedureData.subcategory?.name || '',
|
|
137
137
|
technologyName: procedureData.technology?.name || '',
|
|
138
|
-
brandName: procedureData.product?.
|
|
139
|
-
productName: procedureData.product?.name || '',
|
|
138
|
+
brandName: procedureData.product?.brandName || '', // Safe: optional chaining
|
|
139
|
+
productName: procedureData.product?.name || '', // Safe: optional chaining
|
|
140
140
|
price: procedureData.price || 0,
|
|
141
141
|
pricingMeasure: procedureData.pricingMeasure,
|
|
142
142
|
currency: procedureData.currency,
|
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
} from '../../../types/appointment';
|
|
8
8
|
import { getAppointmentOrThrow, initializeMetadata } from './zone-management.utils';
|
|
9
9
|
import { PROCEDURES_COLLECTION } from '../../../types/procedure';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
initializeFormsForExtendedProcedure,
|
|
12
|
+
removeFormsForExtendedProcedure,
|
|
13
|
+
} from './form-initialization.utils';
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Aggregates products from a procedure into appointmentProducts
|
|
@@ -19,7 +22,7 @@ import { initializeFormsForExtendedProcedure, removeFormsForExtendedProcedure }
|
|
|
19
22
|
async function aggregateProductsFromProcedure(
|
|
20
23
|
db: Firestore,
|
|
21
24
|
procedureId: string,
|
|
22
|
-
existingProducts: AppointmentProductMetadata[]
|
|
25
|
+
existingProducts: AppointmentProductMetadata[],
|
|
23
26
|
): Promise<AppointmentProductMetadata[]> {
|
|
24
27
|
const procedureRef = doc(db, PROCEDURES_COLLECTION, procedureId);
|
|
25
28
|
const procedureSnap = await getDoc(procedureRef);
|
|
@@ -34,21 +37,24 @@ async function aggregateProductsFromProcedure(
|
|
|
34
37
|
const productsMetadata = procedureData.productsMetadata || [];
|
|
35
38
|
|
|
36
39
|
// Map procedure products to AppointmentProductMetadata
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
// Filter out any entries without products (safety check for product-free procedures like consultations)
|
|
41
|
+
const newProducts: AppointmentProductMetadata[] = productsMetadata
|
|
42
|
+
.filter((pp: any) => pp && pp.product)
|
|
43
|
+
.map((pp: any) => {
|
|
44
|
+
// Each item in productsMetadata is a ProcedureProduct with embedded Product
|
|
45
|
+
const product = pp.product;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
productId: product.id,
|
|
49
|
+
productName: product.name,
|
|
50
|
+
brandId: product.brandId,
|
|
51
|
+
brandName: product.brandName,
|
|
52
|
+
procedureId: procedureId,
|
|
53
|
+
price: pp.price, // Price from ProcedureProduct
|
|
54
|
+
currency: pp.currency, // Currency from ProcedureProduct
|
|
55
|
+
unitOfMeasurement: pp.pricingMeasure, // PricingMeasure from ProcedureProduct
|
|
56
|
+
};
|
|
57
|
+
});
|
|
52
58
|
|
|
53
59
|
// Merge with existing products, avoiding duplicates
|
|
54
60
|
const productMap = new Map<string, AppointmentProductMetadata>();
|
|
@@ -78,7 +84,7 @@ async function aggregateProductsFromProcedure(
|
|
|
78
84
|
*/
|
|
79
85
|
async function createExtendedProcedureInfo(
|
|
80
86
|
db: Firestore,
|
|
81
|
-
procedureId: string
|
|
87
|
+
procedureId: string,
|
|
82
88
|
): Promise<ExtendedProcedureInfo> {
|
|
83
89
|
const procedureRef = doc(db, PROCEDURES_COLLECTION, procedureId);
|
|
84
90
|
const procedureSnap = await getDoc(procedureRef);
|
|
@@ -92,19 +98,21 @@ async function createExtendedProcedureInfo(
|
|
|
92
98
|
return {
|
|
93
99
|
procedureId: procedureId,
|
|
94
100
|
procedureName: data.name,
|
|
95
|
-
procedureFamily: data.family,
|
|
96
|
-
procedureCategoryId: data.category.id,
|
|
97
|
-
procedureCategoryName: data.category.name,
|
|
98
|
-
procedureSubCategoryId: data.subcategory.id,
|
|
101
|
+
procedureFamily: data.family, // Use embedded family object
|
|
102
|
+
procedureCategoryId: data.category.id, // Access embedded category
|
|
103
|
+
procedureCategoryName: data.category.name, // Access embedded category
|
|
104
|
+
procedureSubCategoryId: data.subcategory.id, // Access embedded subcategory
|
|
99
105
|
procedureSubCategoryName: data.subcategory.name, // Access embedded subcategory
|
|
100
|
-
procedureTechnologyId: data.technology.id,
|
|
101
|
-
procedureTechnologyName: data.technology.name,
|
|
102
|
-
procedureProducts: (data.productsMetadata || [])
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
procedureTechnologyId: data.technology.id, // Access embedded technology
|
|
107
|
+
procedureTechnologyName: data.technology.name, // Access embedded technology
|
|
108
|
+
procedureProducts: (data.productsMetadata || [])
|
|
109
|
+
.filter((pp: any) => pp && pp.product) // Safety check for product-free procedures
|
|
110
|
+
.map((pp: any) => ({
|
|
111
|
+
productId: pp.product.id, // Access embedded product
|
|
112
|
+
productName: pp.product.name, // Access embedded product
|
|
113
|
+
brandId: pp.product.brandId, // Access embedded product
|
|
114
|
+
brandName: pp.product.brandName, // Access embedded product
|
|
115
|
+
})),
|
|
108
116
|
};
|
|
109
117
|
}
|
|
110
118
|
|
|
@@ -119,15 +127,13 @@ async function createExtendedProcedureInfo(
|
|
|
119
127
|
export async function addExtendedProcedureUtil(
|
|
120
128
|
db: Firestore,
|
|
121
129
|
appointmentId: string,
|
|
122
|
-
procedureId: string
|
|
130
|
+
procedureId: string,
|
|
123
131
|
): Promise<Appointment> {
|
|
124
132
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
125
133
|
const metadata = initializeMetadata(appointment);
|
|
126
134
|
|
|
127
135
|
// Check if procedure is already added
|
|
128
|
-
const existingProcedure = metadata.extendedProcedures?.find(
|
|
129
|
-
p => p.procedureId === procedureId
|
|
130
|
-
);
|
|
136
|
+
const existingProcedure = metadata.extendedProcedures?.find(p => p.procedureId === procedureId);
|
|
131
137
|
if (existingProcedure) {
|
|
132
138
|
throw new Error(`Procedure ${procedureId} is already added to this appointment`);
|
|
133
139
|
}
|
|
@@ -138,18 +144,18 @@ export async function addExtendedProcedureUtil(
|
|
|
138
144
|
// Get procedure data for forms and products
|
|
139
145
|
const procedureRef = doc(db, PROCEDURES_COLLECTION, procedureId);
|
|
140
146
|
const procedureSnap = await getDoc(procedureRef);
|
|
141
|
-
|
|
147
|
+
|
|
142
148
|
if (!procedureSnap.exists()) {
|
|
143
149
|
throw new Error(`Procedure with ID ${procedureId} not found`);
|
|
144
150
|
}
|
|
145
|
-
|
|
151
|
+
|
|
146
152
|
const procedureData = procedureSnap.data();
|
|
147
153
|
|
|
148
154
|
// Aggregate products
|
|
149
155
|
const updatedProducts = await aggregateProductsFromProcedure(
|
|
150
156
|
db,
|
|
151
157
|
procedureId,
|
|
152
|
-
metadata.appointmentProducts || []
|
|
158
|
+
metadata.appointmentProducts || [],
|
|
153
159
|
);
|
|
154
160
|
|
|
155
161
|
// Initialize forms for extended procedure
|
|
@@ -165,13 +171,16 @@ export async function addExtendedProcedureUtil(
|
|
|
165
171
|
procedureData.documentationTemplates,
|
|
166
172
|
appointment.patientId,
|
|
167
173
|
appointment.practitionerId,
|
|
168
|
-
appointment.clinicBranchId
|
|
174
|
+
appointment.clinicBranchId,
|
|
169
175
|
);
|
|
170
176
|
|
|
171
177
|
// Merge form IDs and info
|
|
172
178
|
updatedLinkedFormIds = [...updatedLinkedFormIds, ...formInitResult.allLinkedFormIds];
|
|
173
179
|
updatedLinkedForms = [...updatedLinkedForms, ...formInitResult.initializedFormsInfo];
|
|
174
|
-
updatedPendingUserFormsIds = [
|
|
180
|
+
updatedPendingUserFormsIds = [
|
|
181
|
+
...updatedPendingUserFormsIds,
|
|
182
|
+
...formInitResult.pendingUserFormsIds,
|
|
183
|
+
];
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
// Add extended procedure
|
|
@@ -193,7 +202,10 @@ export async function addExtendedProcedureUtil(
|
|
|
193
202
|
|
|
194
203
|
/**
|
|
195
204
|
* Removes an extended procedure from an appointment
|
|
196
|
-
* Also removes
|
|
205
|
+
* Also removes:
|
|
206
|
+
* - Associated products from appointmentProducts
|
|
207
|
+
* - Associated products from zonesData (all zones)
|
|
208
|
+
* - Associated forms
|
|
197
209
|
* @param db Firestore instance
|
|
198
210
|
* @param appointmentId Appointment ID
|
|
199
211
|
* @param procedureId Procedure ID to remove
|
|
@@ -202,7 +214,7 @@ export async function addExtendedProcedureUtil(
|
|
|
202
214
|
export async function removeExtendedProcedureUtil(
|
|
203
215
|
db: Firestore,
|
|
204
216
|
appointmentId: string,
|
|
205
|
-
procedureId: string
|
|
217
|
+
procedureId: string,
|
|
206
218
|
): Promise<Appointment> {
|
|
207
219
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
208
220
|
const metadata = initializeMetadata(appointment);
|
|
@@ -212,9 +224,7 @@ export async function removeExtendedProcedureUtil(
|
|
|
212
224
|
}
|
|
213
225
|
|
|
214
226
|
// Find and remove the procedure
|
|
215
|
-
const procedureIndex = metadata.extendedProcedures.findIndex(
|
|
216
|
-
p => p.procedureId === procedureId
|
|
217
|
-
);
|
|
227
|
+
const procedureIndex = metadata.extendedProcedures.findIndex(p => p.procedureId === procedureId);
|
|
218
228
|
if (procedureIndex === -1) {
|
|
219
229
|
throw new Error(`Extended procedure ${procedureId} not found in this appointment`);
|
|
220
230
|
}
|
|
@@ -222,23 +232,42 @@ export async function removeExtendedProcedureUtil(
|
|
|
222
232
|
// Remove procedure
|
|
223
233
|
metadata.extendedProcedures.splice(procedureIndex, 1);
|
|
224
234
|
|
|
225
|
-
// Remove products associated with this procedure
|
|
235
|
+
// Remove products associated with this procedure from appointmentProducts
|
|
226
236
|
const updatedProducts = (metadata.appointmentProducts || []).filter(
|
|
227
|
-
p => p.procedureId !== procedureId
|
|
237
|
+
p => p.procedureId !== procedureId,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Remove products from zonesData that belong to this procedure
|
|
241
|
+
const updatedZonesData = { ...(metadata.zonesData || {}) };
|
|
242
|
+
let productsRemovedFromZones = 0;
|
|
243
|
+
|
|
244
|
+
Object.keys(updatedZonesData).forEach(zoneId => {
|
|
245
|
+
const originalLength = updatedZonesData[zoneId].length;
|
|
246
|
+
updatedZonesData[zoneId] = updatedZonesData[zoneId].filter(item => {
|
|
247
|
+
// Keep notes and items that don't belong to this procedure
|
|
248
|
+
if (item.type === 'note') return true;
|
|
249
|
+
if (item.type === 'item' && item.belongingProcedureId !== procedureId) return true;
|
|
250
|
+
return false;
|
|
251
|
+
});
|
|
252
|
+
productsRemovedFromZones += originalLength - updatedZonesData[zoneId].length;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
console.log(
|
|
256
|
+
`🗑️ [removeExtendedProcedure] Removed ${productsRemovedFromZones} products from zones for procedure ${procedureId}`,
|
|
228
257
|
);
|
|
229
258
|
|
|
230
259
|
// Remove forms associated with this procedure
|
|
231
260
|
const removedFormIds = await removeFormsForExtendedProcedure(db, appointmentId, procedureId);
|
|
232
|
-
|
|
261
|
+
|
|
233
262
|
// Update appointment form arrays
|
|
234
263
|
const updatedLinkedFormIds = (appointment.linkedFormIds || []).filter(
|
|
235
|
-
formId => !removedFormIds.includes(formId)
|
|
264
|
+
formId => !removedFormIds.includes(formId),
|
|
236
265
|
);
|
|
237
266
|
const updatedLinkedForms = (appointment.linkedForms || []).filter(
|
|
238
|
-
form => !removedFormIds.includes(form.formId)
|
|
267
|
+
form => !removedFormIds.includes(form.formId),
|
|
239
268
|
);
|
|
240
269
|
const updatedPendingUserFormsIds = (appointment.pendingUserFormsIds || []).filter(
|
|
241
|
-
formId => !removedFormIds.includes(formId)
|
|
270
|
+
formId => !removedFormIds.includes(formId),
|
|
242
271
|
);
|
|
243
272
|
|
|
244
273
|
// Update appointment
|
|
@@ -246,6 +275,7 @@ export async function removeExtendedProcedureUtil(
|
|
|
246
275
|
await updateDoc(appointmentRef, {
|
|
247
276
|
'metadata.extendedProcedures': metadata.extendedProcedures,
|
|
248
277
|
'metadata.appointmentProducts': updatedProducts,
|
|
278
|
+
'metadata.zonesData': updatedZonesData,
|
|
249
279
|
linkedFormIds: updatedLinkedFormIds,
|
|
250
280
|
linkedForms: updatedLinkedForms,
|
|
251
281
|
pendingUserFormsIds: updatedPendingUserFormsIds,
|
|
@@ -263,7 +293,7 @@ export async function removeExtendedProcedureUtil(
|
|
|
263
293
|
*/
|
|
264
294
|
export async function getExtendedProceduresUtil(
|
|
265
295
|
db: Firestore,
|
|
266
|
-
appointmentId: string
|
|
296
|
+
appointmentId: string,
|
|
267
297
|
): Promise<ExtendedProcedureInfo[]> {
|
|
268
298
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
269
299
|
return appointment.metadata?.extendedProcedures || [];
|
|
@@ -277,9 +307,8 @@ export async function getExtendedProceduresUtil(
|
|
|
277
307
|
*/
|
|
278
308
|
export async function getAppointmentProductsUtil(
|
|
279
309
|
db: Firestore,
|
|
280
|
-
appointmentId: string
|
|
310
|
+
appointmentId: string,
|
|
281
311
|
): Promise<AppointmentProductMetadata[]> {
|
|
282
312
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
283
313
|
return appointment.metadata?.appointmentProducts || [];
|
|
284
314
|
}
|
|
285
|
-
|
|
@@ -38,12 +38,14 @@ async function createExtendedProcedureInfoForRecommended(
|
|
|
38
38
|
procedureSubCategoryName: data.subcategory.name,
|
|
39
39
|
procedureTechnologyId: data.technology.id,
|
|
40
40
|
procedureTechnologyName: data.technology.name,
|
|
41
|
-
procedureProducts: (data.productsMetadata || [])
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
procedureProducts: (data.productsMetadata || [])
|
|
42
|
+
.filter((pp: any) => pp && pp.product) // Safety check for product-free procedures
|
|
43
|
+
.map((pp: any) => ({
|
|
44
|
+
productId: pp.product.id,
|
|
45
|
+
productName: pp.product.name,
|
|
46
|
+
brandId: pp.product.brandId,
|
|
47
|
+
brandName: pp.product.brandName,
|
|
48
|
+
})),
|
|
47
49
|
};
|
|
48
50
|
}
|
|
49
51
|
|