@blackcode_sa/metaestetics-api 1.14.51 → 1.14.53
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/index.js
CHANGED
|
@@ -4843,7 +4843,6 @@ function initializeMetadata(appointment) {
|
|
|
4843
4843
|
};
|
|
4844
4844
|
}
|
|
4845
4845
|
async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
4846
|
-
var _a;
|
|
4847
4846
|
validateZoneKeyFormat(zoneId);
|
|
4848
4847
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
4849
4848
|
const metadata = initializeMetadata(appointment);
|
|
@@ -4852,15 +4851,19 @@ async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
|
4852
4851
|
zonesData[zoneId] = [];
|
|
4853
4852
|
}
|
|
4854
4853
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4854
|
+
const cleanItem = Object.fromEntries(
|
|
4855
|
+
Object.entries(item).filter(([_, value]) => value !== void 0)
|
|
4856
|
+
);
|
|
4857
|
+
const notesVisibleToPatientValue = cleanItem.notesVisibleToPatient !== void 0 ? cleanItem.notesVisibleToPatient : cleanItem.notes ? false : void 0;
|
|
4855
4858
|
const itemWithSubtotal = {
|
|
4856
|
-
...
|
|
4859
|
+
...cleanItem,
|
|
4857
4860
|
parentZone: zoneId,
|
|
4858
4861
|
// Set parentZone to the zone key
|
|
4859
|
-
subtotal: calculateItemSubtotal(
|
|
4860
|
-
// Set default visibility to false (privacy-first) if notes exist and visibility not explicitly set
|
|
4861
|
-
notesVisibleToPatient: (_a = item.notesVisibleToPatient) != null ? _a : item.notes ? false : void 0,
|
|
4862
|
+
subtotal: calculateItemSubtotal(cleanItem),
|
|
4862
4863
|
createdAt: now,
|
|
4863
|
-
updatedAt: now
|
|
4864
|
+
updatedAt: now,
|
|
4865
|
+
// Only include notesVisibleToPatient if it has a defined boolean value
|
|
4866
|
+
...typeof notesVisibleToPatientValue === "boolean" && { notesVisibleToPatient: notesVisibleToPatientValue }
|
|
4864
4867
|
};
|
|
4865
4868
|
zonesData[zoneId].push(itemWithSubtotal);
|
|
4866
4869
|
const finalbilling = calculateFinalBilling(zonesData, 0.081);
|
|
@@ -4957,6 +4960,45 @@ var import_firestore10 = require("firebase/firestore");
|
|
|
4957
4960
|
|
|
4958
4961
|
// src/services/appointment/utils/form-initialization.utils.ts
|
|
4959
4962
|
var import_firestore9 = require("firebase/firestore");
|
|
4963
|
+
function isProcedureSpecificForm(template) {
|
|
4964
|
+
const tags = template.tags || [];
|
|
4965
|
+
const titleLower = template.title.toLowerCase();
|
|
4966
|
+
if (tags.includes("procedure-specific")) {
|
|
4967
|
+
return true;
|
|
4968
|
+
}
|
|
4969
|
+
if (tags.includes("shared")) {
|
|
4970
|
+
return false;
|
|
4971
|
+
}
|
|
4972
|
+
if (tags.some((tag) => {
|
|
4973
|
+
const tagLower = tag.toLowerCase();
|
|
4974
|
+
return tagLower.includes("consent") || tagLower === "consent-form";
|
|
4975
|
+
})) {
|
|
4976
|
+
return true;
|
|
4977
|
+
}
|
|
4978
|
+
if (titleLower.includes("consent")) {
|
|
4979
|
+
return true;
|
|
4980
|
+
}
|
|
4981
|
+
return false;
|
|
4982
|
+
}
|
|
4983
|
+
async function findExistingFormByTemplate(db, appointmentId, templateId, isUserForm) {
|
|
4984
|
+
const formSubcollection = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
|
|
4985
|
+
const appointmentRef = (0, import_firestore9.doc)(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
4986
|
+
const formsCollectionRef = (0, import_firestore9.collection)(appointmentRef, formSubcollection);
|
|
4987
|
+
const q = (0, import_firestore9.query)(
|
|
4988
|
+
formsCollectionRef,
|
|
4989
|
+
(0, import_firestore9.where)("templateId", "==", templateId)
|
|
4990
|
+
);
|
|
4991
|
+
const querySnapshot = await (0, import_firestore9.getDocs)(q);
|
|
4992
|
+
if (querySnapshot.empty) {
|
|
4993
|
+
return null;
|
|
4994
|
+
}
|
|
4995
|
+
const docSnap = querySnapshot.docs[0];
|
|
4996
|
+
const data = docSnap.data();
|
|
4997
|
+
if (!data.id) {
|
|
4998
|
+
data.id = docSnap.id;
|
|
4999
|
+
}
|
|
5000
|
+
return data;
|
|
5001
|
+
}
|
|
4960
5002
|
async function initializeFormsForExtendedProcedure(db, appointmentId, procedureId, technologyTemplates, patientId, practitionerId, clinicId) {
|
|
4961
5003
|
const initializedFormsInfo = [];
|
|
4962
5004
|
const pendingUserFormsIds = [];
|
|
@@ -4999,6 +5041,49 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
|
|
|
4999
5041
|
const isRequired = templateRef.isRequired;
|
|
5000
5042
|
const isUserForm = templateRef.isUserForm || false;
|
|
5001
5043
|
const formSubcollectionPath = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
|
|
5044
|
+
const isProcedureSpecific = isProcedureSpecificForm(template);
|
|
5045
|
+
let existingForm = null;
|
|
5046
|
+
if (!isProcedureSpecific) {
|
|
5047
|
+
try {
|
|
5048
|
+
existingForm = await findExistingFormByTemplate(
|
|
5049
|
+
db,
|
|
5050
|
+
appointmentId,
|
|
5051
|
+
templateRef.templateId,
|
|
5052
|
+
isUserForm
|
|
5053
|
+
);
|
|
5054
|
+
if (existingForm) {
|
|
5055
|
+
console.log(
|
|
5056
|
+
`[FormInit] Found existing shared form ${existingForm.id} (template: ${template.id}) for appointment ${appointmentId}. Reusing instead of creating duplicate.`
|
|
5057
|
+
);
|
|
5058
|
+
const linkedForm = {
|
|
5059
|
+
formId: existingForm.id,
|
|
5060
|
+
templateId: template.id,
|
|
5061
|
+
templateVersion: template.version,
|
|
5062
|
+
title: template.title,
|
|
5063
|
+
isUserForm,
|
|
5064
|
+
isRequired,
|
|
5065
|
+
sortingOrder: templateRef.sortingOrder,
|
|
5066
|
+
status: existingForm.status || "pending" /* PENDING */,
|
|
5067
|
+
path: `${APPOINTMENTS_COLLECTION}/${appointmentId}/${formSubcollectionPath}/${existingForm.id}`
|
|
5068
|
+
};
|
|
5069
|
+
initializedFormsInfo.push(linkedForm);
|
|
5070
|
+
if (!allLinkedFormIds.includes(existingForm.id)) {
|
|
5071
|
+
allLinkedFormIds.push(existingForm.id);
|
|
5072
|
+
}
|
|
5073
|
+
if (isUserForm && isRequired && existingForm.status === "pending" /* PENDING */) {
|
|
5074
|
+
if (!pendingUserFormsIds.includes(existingForm.id)) {
|
|
5075
|
+
pendingUserFormsIds.push(existingForm.id);
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
continue;
|
|
5079
|
+
}
|
|
5080
|
+
} catch (error) {
|
|
5081
|
+
console.warn(
|
|
5082
|
+
`[FormInit] Error checking for existing form (template: ${templateRef.templateId}):`,
|
|
5083
|
+
error
|
|
5084
|
+
);
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5002
5087
|
const appointmentRef = (0, import_firestore9.doc)(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
5003
5088
|
const formsCollectionRef = (0, import_firestore9.collection)(appointmentRef, formSubcollectionPath);
|
|
5004
5089
|
const filledDocumentData = {
|
|
@@ -5036,8 +5121,9 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
|
|
|
5036
5121
|
path: docRef.path
|
|
5037
5122
|
};
|
|
5038
5123
|
initializedFormsInfo.push(linkedForm);
|
|
5124
|
+
const formType = isProcedureSpecific ? "procedure-specific" : "general/shared";
|
|
5039
5125
|
console.log(
|
|
5040
|
-
`[FormInit] Created FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
5126
|
+
`[FormInit] Created ${formType} FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
5041
5127
|
);
|
|
5042
5128
|
} catch (error) {
|
|
5043
5129
|
console.error(
|
|
@@ -5049,8 +5135,28 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
|
|
|
5049
5135
|
return { initializedFormsInfo, pendingUserFormsIds, allLinkedFormIds };
|
|
5050
5136
|
}
|
|
5051
5137
|
async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
|
|
5138
|
+
var _a, _b;
|
|
5052
5139
|
const removedFormIds = [];
|
|
5053
5140
|
const appointmentRef = (0, import_firestore9.doc)(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
5141
|
+
const appointmentSnap = await (0, import_firestore9.getDoc)(appointmentRef);
|
|
5142
|
+
if (!appointmentSnap.exists()) {
|
|
5143
|
+
console.warn(
|
|
5144
|
+
`[FormInit] Appointment ${appointmentId} not found when removing forms for procedure ${procedureId}.`
|
|
5145
|
+
);
|
|
5146
|
+
return removedFormIds;
|
|
5147
|
+
}
|
|
5148
|
+
const appointment = appointmentSnap.data();
|
|
5149
|
+
const linkedForms = appointment.linkedForms || [];
|
|
5150
|
+
const mainProcedureId = appointment.procedureId;
|
|
5151
|
+
const extendedProcedureIds = ((_b = (_a = appointment.metadata) == null ? void 0 : _a.extendedProcedures) == null ? void 0 : _b.map(
|
|
5152
|
+
(ep) => ep.procedureId
|
|
5153
|
+
)) || [];
|
|
5154
|
+
const allProcedureIds = [mainProcedureId, ...extendedProcedureIds].filter(Boolean);
|
|
5155
|
+
const remainingProcedureIds = allProcedureIds.filter((id) => id !== procedureId);
|
|
5156
|
+
const isFormSharedAndReferenced = (formId) => {
|
|
5157
|
+
const formEntries = linkedForms.filter((form) => form.formId === formId);
|
|
5158
|
+
return formEntries.length > 1;
|
|
5159
|
+
};
|
|
5054
5160
|
const doctorFormsRef = (0, import_firestore9.collection)(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
|
|
5055
5161
|
const doctorFormsQuery = (0, import_firestore9.query)(
|
|
5056
5162
|
doctorFormsRef,
|
|
@@ -5059,11 +5165,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
|
|
|
5059
5165
|
const doctorFormsSnap = await (0, import_firestore9.getDocs)(doctorFormsQuery);
|
|
5060
5166
|
for (const formDoc of doctorFormsSnap.docs) {
|
|
5061
5167
|
try {
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5168
|
+
const formData = formDoc.data();
|
|
5169
|
+
let isShared = false;
|
|
5170
|
+
if (formData.templateId) {
|
|
5171
|
+
try {
|
|
5172
|
+
const templateDoc = (0, import_firestore9.doc)(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
5173
|
+
const templateSnap = await (0, import_firestore9.getDoc)(templateDoc);
|
|
5174
|
+
if (templateSnap.exists()) {
|
|
5175
|
+
const template = templateSnap.data();
|
|
5176
|
+
isShared = !isProcedureSpecificForm(template);
|
|
5177
|
+
}
|
|
5178
|
+
} catch (error) {
|
|
5179
|
+
console.warn(
|
|
5180
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
5181
|
+
error
|
|
5182
|
+
);
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
if (!isShared) {
|
|
5186
|
+
await (0, import_firestore9.deleteDoc)(formDoc.ref);
|
|
5187
|
+
removedFormIds.push(formDoc.id);
|
|
5188
|
+
console.log(
|
|
5189
|
+
`[FormInit] Removed procedure-specific doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
5190
|
+
);
|
|
5191
|
+
} else {
|
|
5192
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
5193
|
+
console.log(
|
|
5194
|
+
`[FormInit] Skipped deletion of shared doctor form ${formDoc.id} - still referenced by other procedures.`
|
|
5195
|
+
);
|
|
5196
|
+
} else {
|
|
5197
|
+
await (0, import_firestore9.deleteDoc)(formDoc.ref);
|
|
5198
|
+
removedFormIds.push(formDoc.id);
|
|
5199
|
+
console.log(
|
|
5200
|
+
`[FormInit] Removed shared doctor form ${formDoc.id} - no longer referenced by other procedures.`
|
|
5201
|
+
);
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5067
5204
|
} catch (error) {
|
|
5068
5205
|
console.error(
|
|
5069
5206
|
`[FormInit] Error removing doctor form ${formDoc.id}:`,
|
|
@@ -5079,11 +5216,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
|
|
|
5079
5216
|
const userFormsSnap = await (0, import_firestore9.getDocs)(userFormsQuery);
|
|
5080
5217
|
for (const formDoc of userFormsSnap.docs) {
|
|
5081
5218
|
try {
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5219
|
+
const formData = formDoc.data();
|
|
5220
|
+
let isShared = false;
|
|
5221
|
+
if (formData.templateId) {
|
|
5222
|
+
try {
|
|
5223
|
+
const templateDoc = (0, import_firestore9.doc)(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
5224
|
+
const templateSnap = await (0, import_firestore9.getDoc)(templateDoc);
|
|
5225
|
+
if (templateSnap.exists()) {
|
|
5226
|
+
const template = templateSnap.data();
|
|
5227
|
+
isShared = !isProcedureSpecificForm(template);
|
|
5228
|
+
}
|
|
5229
|
+
} catch (error) {
|
|
5230
|
+
console.warn(
|
|
5231
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
5232
|
+
error
|
|
5233
|
+
);
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
if (!isShared) {
|
|
5237
|
+
await (0, import_firestore9.deleteDoc)(formDoc.ref);
|
|
5238
|
+
removedFormIds.push(formDoc.id);
|
|
5239
|
+
console.log(
|
|
5240
|
+
`[FormInit] Removed procedure-specific user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
5241
|
+
);
|
|
5242
|
+
} else {
|
|
5243
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
5244
|
+
console.log(
|
|
5245
|
+
`[FormInit] Skipped deletion of shared user form ${formDoc.id} - still referenced by other procedures.`
|
|
5246
|
+
);
|
|
5247
|
+
} else {
|
|
5248
|
+
await (0, import_firestore9.deleteDoc)(formDoc.ref);
|
|
5249
|
+
removedFormIds.push(formDoc.id);
|
|
5250
|
+
console.log(
|
|
5251
|
+
`[FormInit] Removed shared user form ${formDoc.id} - no longer referenced by other procedures.`
|
|
5252
|
+
);
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5087
5255
|
} catch (error) {
|
|
5088
5256
|
console.error(
|
|
5089
5257
|
`[FormInit] Error removing user form ${formDoc.id}:`,
|
package/dist/index.mjs
CHANGED
|
@@ -4729,7 +4729,6 @@ function initializeMetadata(appointment) {
|
|
|
4729
4729
|
};
|
|
4730
4730
|
}
|
|
4731
4731
|
async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
4732
|
-
var _a;
|
|
4733
4732
|
validateZoneKeyFormat(zoneId);
|
|
4734
4733
|
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
4735
4734
|
const metadata = initializeMetadata(appointment);
|
|
@@ -4738,15 +4737,19 @@ async function addItemToZoneUtil(db, appointmentId, zoneId, item) {
|
|
|
4738
4737
|
zonesData[zoneId] = [];
|
|
4739
4738
|
}
|
|
4740
4739
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4740
|
+
const cleanItem = Object.fromEntries(
|
|
4741
|
+
Object.entries(item).filter(([_, value]) => value !== void 0)
|
|
4742
|
+
);
|
|
4743
|
+
const notesVisibleToPatientValue = cleanItem.notesVisibleToPatient !== void 0 ? cleanItem.notesVisibleToPatient : cleanItem.notes ? false : void 0;
|
|
4741
4744
|
const itemWithSubtotal = {
|
|
4742
|
-
...
|
|
4745
|
+
...cleanItem,
|
|
4743
4746
|
parentZone: zoneId,
|
|
4744
4747
|
// Set parentZone to the zone key
|
|
4745
|
-
subtotal: calculateItemSubtotal(
|
|
4746
|
-
// Set default visibility to false (privacy-first) if notes exist and visibility not explicitly set
|
|
4747
|
-
notesVisibleToPatient: (_a = item.notesVisibleToPatient) != null ? _a : item.notes ? false : void 0,
|
|
4748
|
+
subtotal: calculateItemSubtotal(cleanItem),
|
|
4748
4749
|
createdAt: now,
|
|
4749
|
-
updatedAt: now
|
|
4750
|
+
updatedAt: now,
|
|
4751
|
+
// Only include notesVisibleToPatient if it has a defined boolean value
|
|
4752
|
+
...typeof notesVisibleToPatientValue === "boolean" && { notesVisibleToPatient: notesVisibleToPatientValue }
|
|
4750
4753
|
};
|
|
4751
4754
|
zonesData[zoneId].push(itemWithSubtotal);
|
|
4752
4755
|
const finalbilling = calculateFinalBilling(zonesData, 0.081);
|
|
@@ -4843,6 +4846,45 @@ import { updateDoc as updateDoc5, serverTimestamp as serverTimestamp4, doc as do
|
|
|
4843
4846
|
|
|
4844
4847
|
// src/services/appointment/utils/form-initialization.utils.ts
|
|
4845
4848
|
import { collection as collection5, doc as doc6, addDoc, deleteDoc as deleteDoc2, getDocs as getDocs5, query as query5, where as where5, serverTimestamp as serverTimestamp3, getDoc as getDoc6, updateDoc as updateDoc4 } from "firebase/firestore";
|
|
4849
|
+
function isProcedureSpecificForm(template) {
|
|
4850
|
+
const tags = template.tags || [];
|
|
4851
|
+
const titleLower = template.title.toLowerCase();
|
|
4852
|
+
if (tags.includes("procedure-specific")) {
|
|
4853
|
+
return true;
|
|
4854
|
+
}
|
|
4855
|
+
if (tags.includes("shared")) {
|
|
4856
|
+
return false;
|
|
4857
|
+
}
|
|
4858
|
+
if (tags.some((tag) => {
|
|
4859
|
+
const tagLower = tag.toLowerCase();
|
|
4860
|
+
return tagLower.includes("consent") || tagLower === "consent-form";
|
|
4861
|
+
})) {
|
|
4862
|
+
return true;
|
|
4863
|
+
}
|
|
4864
|
+
if (titleLower.includes("consent")) {
|
|
4865
|
+
return true;
|
|
4866
|
+
}
|
|
4867
|
+
return false;
|
|
4868
|
+
}
|
|
4869
|
+
async function findExistingFormByTemplate(db, appointmentId, templateId, isUserForm) {
|
|
4870
|
+
const formSubcollection = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
|
|
4871
|
+
const appointmentRef = doc6(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
4872
|
+
const formsCollectionRef = collection5(appointmentRef, formSubcollection);
|
|
4873
|
+
const q = query5(
|
|
4874
|
+
formsCollectionRef,
|
|
4875
|
+
where5("templateId", "==", templateId)
|
|
4876
|
+
);
|
|
4877
|
+
const querySnapshot = await getDocs5(q);
|
|
4878
|
+
if (querySnapshot.empty) {
|
|
4879
|
+
return null;
|
|
4880
|
+
}
|
|
4881
|
+
const docSnap = querySnapshot.docs[0];
|
|
4882
|
+
const data = docSnap.data();
|
|
4883
|
+
if (!data.id) {
|
|
4884
|
+
data.id = docSnap.id;
|
|
4885
|
+
}
|
|
4886
|
+
return data;
|
|
4887
|
+
}
|
|
4846
4888
|
async function initializeFormsForExtendedProcedure(db, appointmentId, procedureId, technologyTemplates, patientId, practitionerId, clinicId) {
|
|
4847
4889
|
const initializedFormsInfo = [];
|
|
4848
4890
|
const pendingUserFormsIds = [];
|
|
@@ -4885,6 +4927,49 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
|
|
|
4885
4927
|
const isRequired = templateRef.isRequired;
|
|
4886
4928
|
const isUserForm = templateRef.isUserForm || false;
|
|
4887
4929
|
const formSubcollectionPath = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
|
|
4930
|
+
const isProcedureSpecific = isProcedureSpecificForm(template);
|
|
4931
|
+
let existingForm = null;
|
|
4932
|
+
if (!isProcedureSpecific) {
|
|
4933
|
+
try {
|
|
4934
|
+
existingForm = await findExistingFormByTemplate(
|
|
4935
|
+
db,
|
|
4936
|
+
appointmentId,
|
|
4937
|
+
templateRef.templateId,
|
|
4938
|
+
isUserForm
|
|
4939
|
+
);
|
|
4940
|
+
if (existingForm) {
|
|
4941
|
+
console.log(
|
|
4942
|
+
`[FormInit] Found existing shared form ${existingForm.id} (template: ${template.id}) for appointment ${appointmentId}. Reusing instead of creating duplicate.`
|
|
4943
|
+
);
|
|
4944
|
+
const linkedForm = {
|
|
4945
|
+
formId: existingForm.id,
|
|
4946
|
+
templateId: template.id,
|
|
4947
|
+
templateVersion: template.version,
|
|
4948
|
+
title: template.title,
|
|
4949
|
+
isUserForm,
|
|
4950
|
+
isRequired,
|
|
4951
|
+
sortingOrder: templateRef.sortingOrder,
|
|
4952
|
+
status: existingForm.status || "pending" /* PENDING */,
|
|
4953
|
+
path: `${APPOINTMENTS_COLLECTION}/${appointmentId}/${formSubcollectionPath}/${existingForm.id}`
|
|
4954
|
+
};
|
|
4955
|
+
initializedFormsInfo.push(linkedForm);
|
|
4956
|
+
if (!allLinkedFormIds.includes(existingForm.id)) {
|
|
4957
|
+
allLinkedFormIds.push(existingForm.id);
|
|
4958
|
+
}
|
|
4959
|
+
if (isUserForm && isRequired && existingForm.status === "pending" /* PENDING */) {
|
|
4960
|
+
if (!pendingUserFormsIds.includes(existingForm.id)) {
|
|
4961
|
+
pendingUserFormsIds.push(existingForm.id);
|
|
4962
|
+
}
|
|
4963
|
+
}
|
|
4964
|
+
continue;
|
|
4965
|
+
}
|
|
4966
|
+
} catch (error) {
|
|
4967
|
+
console.warn(
|
|
4968
|
+
`[FormInit] Error checking for existing form (template: ${templateRef.templateId}):`,
|
|
4969
|
+
error
|
|
4970
|
+
);
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4888
4973
|
const appointmentRef = doc6(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
4889
4974
|
const formsCollectionRef = collection5(appointmentRef, formSubcollectionPath);
|
|
4890
4975
|
const filledDocumentData = {
|
|
@@ -4922,8 +5007,9 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
|
|
|
4922
5007
|
path: docRef.path
|
|
4923
5008
|
};
|
|
4924
5009
|
initializedFormsInfo.push(linkedForm);
|
|
5010
|
+
const formType = isProcedureSpecific ? "procedure-specific" : "general/shared";
|
|
4925
5011
|
console.log(
|
|
4926
|
-
`[FormInit] Created FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
5012
|
+
`[FormInit] Created ${formType} FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
4927
5013
|
);
|
|
4928
5014
|
} catch (error) {
|
|
4929
5015
|
console.error(
|
|
@@ -4935,8 +5021,28 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
|
|
|
4935
5021
|
return { initializedFormsInfo, pendingUserFormsIds, allLinkedFormIds };
|
|
4936
5022
|
}
|
|
4937
5023
|
async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
|
|
5024
|
+
var _a, _b;
|
|
4938
5025
|
const removedFormIds = [];
|
|
4939
5026
|
const appointmentRef = doc6(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
5027
|
+
const appointmentSnap = await getDoc6(appointmentRef);
|
|
5028
|
+
if (!appointmentSnap.exists()) {
|
|
5029
|
+
console.warn(
|
|
5030
|
+
`[FormInit] Appointment ${appointmentId} not found when removing forms for procedure ${procedureId}.`
|
|
5031
|
+
);
|
|
5032
|
+
return removedFormIds;
|
|
5033
|
+
}
|
|
5034
|
+
const appointment = appointmentSnap.data();
|
|
5035
|
+
const linkedForms = appointment.linkedForms || [];
|
|
5036
|
+
const mainProcedureId = appointment.procedureId;
|
|
5037
|
+
const extendedProcedureIds = ((_b = (_a = appointment.metadata) == null ? void 0 : _a.extendedProcedures) == null ? void 0 : _b.map(
|
|
5038
|
+
(ep) => ep.procedureId
|
|
5039
|
+
)) || [];
|
|
5040
|
+
const allProcedureIds = [mainProcedureId, ...extendedProcedureIds].filter(Boolean);
|
|
5041
|
+
const remainingProcedureIds = allProcedureIds.filter((id) => id !== procedureId);
|
|
5042
|
+
const isFormSharedAndReferenced = (formId) => {
|
|
5043
|
+
const formEntries = linkedForms.filter((form) => form.formId === formId);
|
|
5044
|
+
return formEntries.length > 1;
|
|
5045
|
+
};
|
|
4940
5046
|
const doctorFormsRef = collection5(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
|
|
4941
5047
|
const doctorFormsQuery = query5(
|
|
4942
5048
|
doctorFormsRef,
|
|
@@ -4945,11 +5051,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
|
|
|
4945
5051
|
const doctorFormsSnap = await getDocs5(doctorFormsQuery);
|
|
4946
5052
|
for (const formDoc of doctorFormsSnap.docs) {
|
|
4947
5053
|
try {
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
5054
|
+
const formData = formDoc.data();
|
|
5055
|
+
let isShared = false;
|
|
5056
|
+
if (formData.templateId) {
|
|
5057
|
+
try {
|
|
5058
|
+
const templateDoc = doc6(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
5059
|
+
const templateSnap = await getDoc6(templateDoc);
|
|
5060
|
+
if (templateSnap.exists()) {
|
|
5061
|
+
const template = templateSnap.data();
|
|
5062
|
+
isShared = !isProcedureSpecificForm(template);
|
|
5063
|
+
}
|
|
5064
|
+
} catch (error) {
|
|
5065
|
+
console.warn(
|
|
5066
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
5067
|
+
error
|
|
5068
|
+
);
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
if (!isShared) {
|
|
5072
|
+
await deleteDoc2(formDoc.ref);
|
|
5073
|
+
removedFormIds.push(formDoc.id);
|
|
5074
|
+
console.log(
|
|
5075
|
+
`[FormInit] Removed procedure-specific doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
5076
|
+
);
|
|
5077
|
+
} else {
|
|
5078
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
5079
|
+
console.log(
|
|
5080
|
+
`[FormInit] Skipped deletion of shared doctor form ${formDoc.id} - still referenced by other procedures.`
|
|
5081
|
+
);
|
|
5082
|
+
} else {
|
|
5083
|
+
await deleteDoc2(formDoc.ref);
|
|
5084
|
+
removedFormIds.push(formDoc.id);
|
|
5085
|
+
console.log(
|
|
5086
|
+
`[FormInit] Removed shared doctor form ${formDoc.id} - no longer referenced by other procedures.`
|
|
5087
|
+
);
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
4953
5090
|
} catch (error) {
|
|
4954
5091
|
console.error(
|
|
4955
5092
|
`[FormInit] Error removing doctor form ${formDoc.id}:`,
|
|
@@ -4965,11 +5102,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
|
|
|
4965
5102
|
const userFormsSnap = await getDocs5(userFormsQuery);
|
|
4966
5103
|
for (const formDoc of userFormsSnap.docs) {
|
|
4967
5104
|
try {
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
5105
|
+
const formData = formDoc.data();
|
|
5106
|
+
let isShared = false;
|
|
5107
|
+
if (formData.templateId) {
|
|
5108
|
+
try {
|
|
5109
|
+
const templateDoc = doc6(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
5110
|
+
const templateSnap = await getDoc6(templateDoc);
|
|
5111
|
+
if (templateSnap.exists()) {
|
|
5112
|
+
const template = templateSnap.data();
|
|
5113
|
+
isShared = !isProcedureSpecificForm(template);
|
|
5114
|
+
}
|
|
5115
|
+
} catch (error) {
|
|
5116
|
+
console.warn(
|
|
5117
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
5118
|
+
error
|
|
5119
|
+
);
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
if (!isShared) {
|
|
5123
|
+
await deleteDoc2(formDoc.ref);
|
|
5124
|
+
removedFormIds.push(formDoc.id);
|
|
5125
|
+
console.log(
|
|
5126
|
+
`[FormInit] Removed procedure-specific user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
5127
|
+
);
|
|
5128
|
+
} else {
|
|
5129
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
5130
|
+
console.log(
|
|
5131
|
+
`[FormInit] Skipped deletion of shared user form ${formDoc.id} - still referenced by other procedures.`
|
|
5132
|
+
);
|
|
5133
|
+
} else {
|
|
5134
|
+
await deleteDoc2(formDoc.ref);
|
|
5135
|
+
removedFormIds.push(formDoc.id);
|
|
5136
|
+
console.log(
|
|
5137
|
+
`[FormInit] Removed shared user form ${formDoc.id} - no longer referenced by other procedures.`
|
|
5138
|
+
);
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
4973
5141
|
} catch (error) {
|
|
4974
5142
|
console.error(
|
|
4975
5143
|
`[FormInit] Error removing user form ${formDoc.id}:`,
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
DOCTOR_FORMS_SUBCOLLECTION,
|
|
6
6
|
USER_FORMS_SUBCOLLECTION,
|
|
7
7
|
DOCUMENTATION_TEMPLATES_COLLECTION,
|
|
8
|
+
FilledDocument,
|
|
8
9
|
} from '../../../types/documentation-templates';
|
|
9
10
|
import {
|
|
10
11
|
APPOINTMENTS_COLLECTION,
|
|
@@ -18,6 +19,95 @@ export interface InitializeExtendedProcedureFormsResult {
|
|
|
18
19
|
allLinkedFormIds: string[];
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Determines if a form template is procedure-specific or general/shared
|
|
24
|
+
* Uses explicit tags first, then falls back to heuristics
|
|
25
|
+
*
|
|
26
|
+
* Priority order:
|
|
27
|
+
* 1. "procedure-specific" tag → always procedure-specific
|
|
28
|
+
* 2. "shared" tag → always shared
|
|
29
|
+
* 3. Consent-related tags ("consent", "consent-form") → procedure-specific
|
|
30
|
+
* 4. Title contains "consent" → procedure-specific
|
|
31
|
+
* 5. Default → shared (general forms)
|
|
32
|
+
*
|
|
33
|
+
* @param template DocumentTemplate to check
|
|
34
|
+
* @returns true if procedure-specific, false if general/shared
|
|
35
|
+
*/
|
|
36
|
+
function isProcedureSpecificForm(template: DocumentTemplate): boolean {
|
|
37
|
+
const tags = template.tags || [];
|
|
38
|
+
const titleLower = template.title.toLowerCase();
|
|
39
|
+
|
|
40
|
+
// Priority 1: Explicit "procedure-specific" tag → always procedure-specific
|
|
41
|
+
if (tags.includes('procedure-specific')) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Priority 2: Explicit "shared" tag → always shared
|
|
46
|
+
if (tags.includes('shared')) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Priority 3: Consent-related tags → procedure-specific
|
|
51
|
+
if (tags.some(tag => {
|
|
52
|
+
const tagLower = tag.toLowerCase();
|
|
53
|
+
return tagLower.includes('consent') || tagLower === 'consent-form';
|
|
54
|
+
})) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Priority 4: Check title for "consent" → procedure-specific
|
|
59
|
+
if (titleLower.includes('consent')) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Priority 5: Default → shared (general forms)
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checks if a form with the given templateId already exists in the appointment
|
|
69
|
+
* @param db Firestore instance
|
|
70
|
+
* @param appointmentId Appointment ID
|
|
71
|
+
* @param templateId Template ID to check
|
|
72
|
+
* @param isUserForm Whether to check user-forms or doctor-forms subcollection
|
|
73
|
+
* @returns Existing FilledDocument or null if not found
|
|
74
|
+
*/
|
|
75
|
+
async function findExistingFormByTemplate(
|
|
76
|
+
db: Firestore,
|
|
77
|
+
appointmentId: string,
|
|
78
|
+
templateId: string,
|
|
79
|
+
isUserForm: boolean
|
|
80
|
+
): Promise<FilledDocument | null> {
|
|
81
|
+
const formSubcollection = isUserForm
|
|
82
|
+
? USER_FORMS_SUBCOLLECTION
|
|
83
|
+
: DOCTOR_FORMS_SUBCOLLECTION;
|
|
84
|
+
|
|
85
|
+
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
86
|
+
const formsCollectionRef = collection(appointmentRef, formSubcollection);
|
|
87
|
+
|
|
88
|
+
const q = query(
|
|
89
|
+
formsCollectionRef,
|
|
90
|
+
where('templateId', '==', templateId)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const querySnapshot = await getDocs(q);
|
|
94
|
+
|
|
95
|
+
if (querySnapshot.empty) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Return the first matching form (should only be one for general forms)
|
|
100
|
+
const docSnap = querySnapshot.docs[0];
|
|
101
|
+
const data = docSnap.data() as FilledDocument;
|
|
102
|
+
|
|
103
|
+
// Ensure id is populated from Firestore document ID if not in data
|
|
104
|
+
if (!data.id) {
|
|
105
|
+
data.id = docSnap.id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
|
|
21
111
|
/**
|
|
22
112
|
* Initializes forms for an extended procedure using client-side Firestore
|
|
23
113
|
* Similar to DocumentManagerAdminService but for client-side usage
|
|
@@ -94,7 +184,67 @@ export async function initializeFormsForExtendedProcedure(
|
|
|
94
184
|
? USER_FORMS_SUBCOLLECTION
|
|
95
185
|
: DOCTOR_FORMS_SUBCOLLECTION;
|
|
96
186
|
|
|
97
|
-
//
|
|
187
|
+
// Check if form is procedure-specific or general/shared
|
|
188
|
+
const isProcedureSpecific = isProcedureSpecificForm(template);
|
|
189
|
+
|
|
190
|
+
// For general/shared forms, check if form already exists in appointment
|
|
191
|
+
let existingForm: FilledDocument | null = null;
|
|
192
|
+
if (!isProcedureSpecific) {
|
|
193
|
+
try {
|
|
194
|
+
existingForm = await findExistingFormByTemplate(
|
|
195
|
+
db,
|
|
196
|
+
appointmentId,
|
|
197
|
+
templateRef.templateId,
|
|
198
|
+
isUserForm
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (existingForm) {
|
|
202
|
+
console.log(
|
|
203
|
+
`[FormInit] Found existing shared form ${existingForm.id} (template: ${template.id}) for appointment ${appointmentId}. Reusing instead of creating duplicate.`
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Reuse existing form - add to linkedForms but don't create new document
|
|
207
|
+
// Note: We still add it to allLinkedFormIds to ensure it's tracked,
|
|
208
|
+
// but we don't add it again if it's already in the appointment's linkedForms
|
|
209
|
+
// (This will be handled by the caller merging the arrays)
|
|
210
|
+
|
|
211
|
+
const linkedForm: LinkedFormInfo = {
|
|
212
|
+
formId: existingForm.id,
|
|
213
|
+
templateId: template.id,
|
|
214
|
+
templateVersion: template.version,
|
|
215
|
+
title: template.title,
|
|
216
|
+
isUserForm: isUserForm,
|
|
217
|
+
isRequired: isRequired,
|
|
218
|
+
sortingOrder: templateRef.sortingOrder,
|
|
219
|
+
status: existingForm.status || FilledDocumentStatus.PENDING,
|
|
220
|
+
path: `${APPOINTMENTS_COLLECTION}/${appointmentId}/${formSubcollectionPath}/${existingForm.id}`,
|
|
221
|
+
};
|
|
222
|
+
initializedFormsInfo.push(linkedForm);
|
|
223
|
+
|
|
224
|
+
// Add to allLinkedFormIds if not already present (to avoid duplicates)
|
|
225
|
+
if (!allLinkedFormIds.includes(existingForm.id)) {
|
|
226
|
+
allLinkedFormIds.push(existingForm.id);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Add to pendingUserFormsIds if it's a required user form and not already completed
|
|
230
|
+
if (isUserForm && isRequired && existingForm.status === FilledDocumentStatus.PENDING) {
|
|
231
|
+
if (!pendingUserFormsIds.includes(existingForm.id)) {
|
|
232
|
+
pendingUserFormsIds.push(existingForm.id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
continue; // Skip creating new form, reuse existing one
|
|
237
|
+
}
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.warn(
|
|
240
|
+
`[FormInit] Error checking for existing form (template: ${templateRef.templateId}):`,
|
|
241
|
+
error
|
|
242
|
+
);
|
|
243
|
+
// Continue to create new form if check fails
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Create new form document (either procedure-specific or general form not found)
|
|
98
248
|
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
99
249
|
const formsCollectionRef = collection(appointmentRef, formSubcollectionPath);
|
|
100
250
|
|
|
@@ -141,8 +291,9 @@ export async function initializeFormsForExtendedProcedure(
|
|
|
141
291
|
};
|
|
142
292
|
initializedFormsInfo.push(linkedForm);
|
|
143
293
|
|
|
294
|
+
const formType = isProcedureSpecific ? 'procedure-specific' : 'general/shared';
|
|
144
295
|
console.log(
|
|
145
|
-
`[FormInit] Created FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
296
|
+
`[FormInit] Created ${formType} FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
146
297
|
);
|
|
147
298
|
} catch (error) {
|
|
148
299
|
console.error(
|
|
@@ -157,6 +308,7 @@ export async function initializeFormsForExtendedProcedure(
|
|
|
157
308
|
|
|
158
309
|
/**
|
|
159
310
|
* Removes all forms associated with a specific procedure from an appointment
|
|
311
|
+
* Only removes procedure-specific forms. Shared forms are kept if still referenced by other procedures.
|
|
160
312
|
* Removes both user forms and doctor forms
|
|
161
313
|
* @param db Firestore instance
|
|
162
314
|
* @param appointmentId Appointment ID
|
|
@@ -170,7 +322,34 @@ export async function removeFormsForExtendedProcedure(
|
|
|
170
322
|
): Promise<string[]> {
|
|
171
323
|
const removedFormIds: string[] = [];
|
|
172
324
|
|
|
325
|
+
// Get appointment to check linkedForms
|
|
173
326
|
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
327
|
+
const appointmentSnap = await getDoc(appointmentRef);
|
|
328
|
+
|
|
329
|
+
if (!appointmentSnap.exists()) {
|
|
330
|
+
console.warn(
|
|
331
|
+
`[FormInit] Appointment ${appointmentId} not found when removing forms for procedure ${procedureId}.`
|
|
332
|
+
);
|
|
333
|
+
return removedFormIds;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const appointment = appointmentSnap.data() as any;
|
|
337
|
+
const linkedForms = appointment.linkedForms || [];
|
|
338
|
+
|
|
339
|
+
// Get all procedure IDs (main + extended) to check if shared forms are still referenced
|
|
340
|
+
const mainProcedureId = appointment.procedureId;
|
|
341
|
+
const extendedProcedureIds = appointment.metadata?.extendedProcedures?.map(
|
|
342
|
+
(ep: any) => ep.procedureId
|
|
343
|
+
) || [];
|
|
344
|
+
const allProcedureIds = [mainProcedureId, ...extendedProcedureIds].filter(Boolean);
|
|
345
|
+
const remainingProcedureIds = allProcedureIds.filter((id: string) => id !== procedureId);
|
|
346
|
+
|
|
347
|
+
// Helper function to check if form appears multiple times in linkedForms (indicating it's shared)
|
|
348
|
+
const isFormSharedAndReferenced = (formId: string): boolean => {
|
|
349
|
+
const formEntries = linkedForms.filter((form: LinkedFormInfo) => form.formId === formId);
|
|
350
|
+
// If form appears multiple times in linkedForms, it's shared across procedures
|
|
351
|
+
return formEntries.length > 1;
|
|
352
|
+
};
|
|
174
353
|
|
|
175
354
|
// Remove from doctor forms subcollection
|
|
176
355
|
const doctorFormsRef = collection(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
|
|
@@ -182,11 +361,49 @@ export async function removeFormsForExtendedProcedure(
|
|
|
182
361
|
|
|
183
362
|
for (const formDoc of doctorFormsSnap.docs) {
|
|
184
363
|
try {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
364
|
+
const formData = formDoc.data() as FilledDocument;
|
|
365
|
+
|
|
366
|
+
// Fetch template to check if form is shared
|
|
367
|
+
let isShared = false;
|
|
368
|
+
if (formData.templateId) {
|
|
369
|
+
try {
|
|
370
|
+
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
371
|
+
const templateSnap = await getDoc(templateDoc);
|
|
372
|
+
if (templateSnap.exists()) {
|
|
373
|
+
const template = templateSnap.data() as DocumentTemplate;
|
|
374
|
+
isShared = !isProcedureSpecificForm(template);
|
|
375
|
+
}
|
|
376
|
+
} catch (error) {
|
|
377
|
+
console.warn(
|
|
378
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
379
|
+
error
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Only delete procedure-specific forms
|
|
385
|
+
// Shared forms are kept even if procedureId matches, as they may be used by other procedures
|
|
386
|
+
if (!isShared) {
|
|
387
|
+
await deleteDoc(formDoc.ref);
|
|
388
|
+
removedFormIds.push(formDoc.id);
|
|
389
|
+
console.log(
|
|
390
|
+
`[FormInit] Removed procedure-specific doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
391
|
+
);
|
|
392
|
+
} else {
|
|
393
|
+
// Check if form is still referenced by other procedures (appears multiple times in linkedForms)
|
|
394
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
395
|
+
console.log(
|
|
396
|
+
`[FormInit] Skipped deletion of shared doctor form ${formDoc.id} - still referenced by other procedures.`
|
|
397
|
+
);
|
|
398
|
+
} else {
|
|
399
|
+
// Shared form but only referenced by this procedure (shouldn't happen, but handle it)
|
|
400
|
+
await deleteDoc(formDoc.ref);
|
|
401
|
+
removedFormIds.push(formDoc.id);
|
|
402
|
+
console.log(
|
|
403
|
+
`[FormInit] Removed shared doctor form ${formDoc.id} - no longer referenced by other procedures.`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
190
407
|
} catch (error) {
|
|
191
408
|
console.error(
|
|
192
409
|
`[FormInit] Error removing doctor form ${formDoc.id}:`,
|
|
@@ -205,11 +422,49 @@ export async function removeFormsForExtendedProcedure(
|
|
|
205
422
|
|
|
206
423
|
for (const formDoc of userFormsSnap.docs) {
|
|
207
424
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
)
|
|
425
|
+
const formData = formDoc.data() as FilledDocument;
|
|
426
|
+
|
|
427
|
+
// Fetch template to check if form is shared
|
|
428
|
+
let isShared = false;
|
|
429
|
+
if (formData.templateId) {
|
|
430
|
+
try {
|
|
431
|
+
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
432
|
+
const templateSnap = await getDoc(templateDoc);
|
|
433
|
+
if (templateSnap.exists()) {
|
|
434
|
+
const template = templateSnap.data() as DocumentTemplate;
|
|
435
|
+
isShared = !isProcedureSpecificForm(template);
|
|
436
|
+
}
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.warn(
|
|
439
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
440
|
+
error
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Only delete procedure-specific forms
|
|
446
|
+
// Shared forms are kept even if procedureId matches, as they may be used by other procedures
|
|
447
|
+
if (!isShared) {
|
|
448
|
+
await deleteDoc(formDoc.ref);
|
|
449
|
+
removedFormIds.push(formDoc.id);
|
|
450
|
+
console.log(
|
|
451
|
+
`[FormInit] Removed procedure-specific user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
452
|
+
);
|
|
453
|
+
} else {
|
|
454
|
+
// Check if form is still referenced by other procedures (appears multiple times in linkedForms)
|
|
455
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
456
|
+
console.log(
|
|
457
|
+
`[FormInit] Skipped deletion of shared user form ${formDoc.id} - still referenced by other procedures.`
|
|
458
|
+
);
|
|
459
|
+
} else {
|
|
460
|
+
// Shared form but only referenced by this procedure (shouldn't happen, but handle it)
|
|
461
|
+
await deleteDoc(formDoc.ref);
|
|
462
|
+
removedFormIds.push(formDoc.id);
|
|
463
|
+
console.log(
|
|
464
|
+
`[FormInit] Removed shared user form ${formDoc.id} - no longer referenced by other procedures.`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
213
468
|
} catch (error) {
|
|
214
469
|
console.error(
|
|
215
470
|
`[FormInit] Error removing user form ${formDoc.id}:`,
|
|
@@ -159,14 +159,25 @@ export async function addItemToZoneUtil(
|
|
|
159
159
|
|
|
160
160
|
// Calculate subtotal for the item
|
|
161
161
|
const now = new Date().toISOString();
|
|
162
|
+
|
|
163
|
+
// Filter out undefined values from item (Firestore doesn't allow undefined)
|
|
164
|
+
const cleanItem = Object.fromEntries(
|
|
165
|
+
Object.entries(item).filter(([_, value]) => value !== undefined)
|
|
166
|
+
) as Omit<ZoneItemData, 'subtotal' | 'parentZone'>;
|
|
167
|
+
|
|
168
|
+
// Determine notesVisibleToPatient value (privacy-first: default to false if notes exist, otherwise omit)
|
|
169
|
+
const notesVisibleToPatientValue = cleanItem.notesVisibleToPatient !== undefined
|
|
170
|
+
? cleanItem.notesVisibleToPatient
|
|
171
|
+
: (cleanItem.notes ? false : undefined);
|
|
172
|
+
|
|
162
173
|
const itemWithSubtotal: ZoneItemData = {
|
|
163
|
-
...
|
|
174
|
+
...cleanItem,
|
|
164
175
|
parentZone: zoneId, // Set parentZone to the zone key
|
|
165
|
-
subtotal: calculateItemSubtotal(
|
|
166
|
-
// Set default visibility to false (privacy-first) if notes exist and visibility not explicitly set
|
|
167
|
-
notesVisibleToPatient: item.notesVisibleToPatient ?? (item.notes ? false : undefined),
|
|
176
|
+
subtotal: calculateItemSubtotal(cleanItem),
|
|
168
177
|
createdAt: now,
|
|
169
178
|
updatedAt: now,
|
|
179
|
+
// Only include notesVisibleToPatient if it has a defined boolean value
|
|
180
|
+
...(typeof notesVisibleToPatientValue === 'boolean' && { notesVisibleToPatient: notesVisibleToPatientValue }),
|
|
170
181
|
};
|
|
171
182
|
|
|
172
183
|
// Add item to zone
|