@blackcode_sa/metaestetics-api 1.14.51 → 1.14.52

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
@@ -4957,6 +4957,45 @@ var import_firestore10 = require("firebase/firestore");
4957
4957
 
4958
4958
  // src/services/appointment/utils/form-initialization.utils.ts
4959
4959
  var import_firestore9 = require("firebase/firestore");
4960
+ function isProcedureSpecificForm(template) {
4961
+ const tags = template.tags || [];
4962
+ const titleLower = template.title.toLowerCase();
4963
+ if (tags.includes("procedure-specific")) {
4964
+ return true;
4965
+ }
4966
+ if (tags.includes("shared")) {
4967
+ return false;
4968
+ }
4969
+ if (tags.some((tag) => {
4970
+ const tagLower = tag.toLowerCase();
4971
+ return tagLower.includes("consent") || tagLower === "consent-form";
4972
+ })) {
4973
+ return true;
4974
+ }
4975
+ if (titleLower.includes("consent")) {
4976
+ return true;
4977
+ }
4978
+ return false;
4979
+ }
4980
+ async function findExistingFormByTemplate(db, appointmentId, templateId, isUserForm) {
4981
+ const formSubcollection = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
4982
+ const appointmentRef = (0, import_firestore9.doc)(db, APPOINTMENTS_COLLECTION, appointmentId);
4983
+ const formsCollectionRef = (0, import_firestore9.collection)(appointmentRef, formSubcollection);
4984
+ const q = (0, import_firestore9.query)(
4985
+ formsCollectionRef,
4986
+ (0, import_firestore9.where)("templateId", "==", templateId)
4987
+ );
4988
+ const querySnapshot = await (0, import_firestore9.getDocs)(q);
4989
+ if (querySnapshot.empty) {
4990
+ return null;
4991
+ }
4992
+ const docSnap = querySnapshot.docs[0];
4993
+ const data = docSnap.data();
4994
+ if (!data.id) {
4995
+ data.id = docSnap.id;
4996
+ }
4997
+ return data;
4998
+ }
4960
4999
  async function initializeFormsForExtendedProcedure(db, appointmentId, procedureId, technologyTemplates, patientId, practitionerId, clinicId) {
4961
5000
  const initializedFormsInfo = [];
4962
5001
  const pendingUserFormsIds = [];
@@ -4999,6 +5038,49 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
4999
5038
  const isRequired = templateRef.isRequired;
5000
5039
  const isUserForm = templateRef.isUserForm || false;
5001
5040
  const formSubcollectionPath = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
5041
+ const isProcedureSpecific = isProcedureSpecificForm(template);
5042
+ let existingForm = null;
5043
+ if (!isProcedureSpecific) {
5044
+ try {
5045
+ existingForm = await findExistingFormByTemplate(
5046
+ db,
5047
+ appointmentId,
5048
+ templateRef.templateId,
5049
+ isUserForm
5050
+ );
5051
+ if (existingForm) {
5052
+ console.log(
5053
+ `[FormInit] Found existing shared form ${existingForm.id} (template: ${template.id}) for appointment ${appointmentId}. Reusing instead of creating duplicate.`
5054
+ );
5055
+ const linkedForm = {
5056
+ formId: existingForm.id,
5057
+ templateId: template.id,
5058
+ templateVersion: template.version,
5059
+ title: template.title,
5060
+ isUserForm,
5061
+ isRequired,
5062
+ sortingOrder: templateRef.sortingOrder,
5063
+ status: existingForm.status || "pending" /* PENDING */,
5064
+ path: `${APPOINTMENTS_COLLECTION}/${appointmentId}/${formSubcollectionPath}/${existingForm.id}`
5065
+ };
5066
+ initializedFormsInfo.push(linkedForm);
5067
+ if (!allLinkedFormIds.includes(existingForm.id)) {
5068
+ allLinkedFormIds.push(existingForm.id);
5069
+ }
5070
+ if (isUserForm && isRequired && existingForm.status === "pending" /* PENDING */) {
5071
+ if (!pendingUserFormsIds.includes(existingForm.id)) {
5072
+ pendingUserFormsIds.push(existingForm.id);
5073
+ }
5074
+ }
5075
+ continue;
5076
+ }
5077
+ } catch (error) {
5078
+ console.warn(
5079
+ `[FormInit] Error checking for existing form (template: ${templateRef.templateId}):`,
5080
+ error
5081
+ );
5082
+ }
5083
+ }
5002
5084
  const appointmentRef = (0, import_firestore9.doc)(db, APPOINTMENTS_COLLECTION, appointmentId);
5003
5085
  const formsCollectionRef = (0, import_firestore9.collection)(appointmentRef, formSubcollectionPath);
5004
5086
  const filledDocumentData = {
@@ -5036,8 +5118,9 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
5036
5118
  path: docRef.path
5037
5119
  };
5038
5120
  initializedFormsInfo.push(linkedForm);
5121
+ const formType = isProcedureSpecific ? "procedure-specific" : "general/shared";
5039
5122
  console.log(
5040
- `[FormInit] Created FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
5123
+ `[FormInit] Created ${formType} FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
5041
5124
  );
5042
5125
  } catch (error) {
5043
5126
  console.error(
@@ -5049,8 +5132,28 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
5049
5132
  return { initializedFormsInfo, pendingUserFormsIds, allLinkedFormIds };
5050
5133
  }
5051
5134
  async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
5135
+ var _a, _b;
5052
5136
  const removedFormIds = [];
5053
5137
  const appointmentRef = (0, import_firestore9.doc)(db, APPOINTMENTS_COLLECTION, appointmentId);
5138
+ const appointmentSnap = await (0, import_firestore9.getDoc)(appointmentRef);
5139
+ if (!appointmentSnap.exists()) {
5140
+ console.warn(
5141
+ `[FormInit] Appointment ${appointmentId} not found when removing forms for procedure ${procedureId}.`
5142
+ );
5143
+ return removedFormIds;
5144
+ }
5145
+ const appointment = appointmentSnap.data();
5146
+ const linkedForms = appointment.linkedForms || [];
5147
+ const mainProcedureId = appointment.procedureId;
5148
+ const extendedProcedureIds = ((_b = (_a = appointment.metadata) == null ? void 0 : _a.extendedProcedures) == null ? void 0 : _b.map(
5149
+ (ep) => ep.procedureId
5150
+ )) || [];
5151
+ const allProcedureIds = [mainProcedureId, ...extendedProcedureIds].filter(Boolean);
5152
+ const remainingProcedureIds = allProcedureIds.filter((id) => id !== procedureId);
5153
+ const isFormSharedAndReferenced = (formId) => {
5154
+ const formEntries = linkedForms.filter((form) => form.formId === formId);
5155
+ return formEntries.length > 1;
5156
+ };
5054
5157
  const doctorFormsRef = (0, import_firestore9.collection)(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
5055
5158
  const doctorFormsQuery = (0, import_firestore9.query)(
5056
5159
  doctorFormsRef,
@@ -5059,11 +5162,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
5059
5162
  const doctorFormsSnap = await (0, import_firestore9.getDocs)(doctorFormsQuery);
5060
5163
  for (const formDoc of doctorFormsSnap.docs) {
5061
5164
  try {
5062
- await (0, import_firestore9.deleteDoc)(formDoc.ref);
5063
- removedFormIds.push(formDoc.id);
5064
- console.log(
5065
- `[FormInit] Removed doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
5066
- );
5165
+ const formData = formDoc.data();
5166
+ let isShared = false;
5167
+ if (formData.templateId) {
5168
+ try {
5169
+ const templateDoc = (0, import_firestore9.doc)(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
5170
+ const templateSnap = await (0, import_firestore9.getDoc)(templateDoc);
5171
+ if (templateSnap.exists()) {
5172
+ const template = templateSnap.data();
5173
+ isShared = !isProcedureSpecificForm(template);
5174
+ }
5175
+ } catch (error) {
5176
+ console.warn(
5177
+ `[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
5178
+ error
5179
+ );
5180
+ }
5181
+ }
5182
+ if (!isShared) {
5183
+ await (0, import_firestore9.deleteDoc)(formDoc.ref);
5184
+ removedFormIds.push(formDoc.id);
5185
+ console.log(
5186
+ `[FormInit] Removed procedure-specific doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
5187
+ );
5188
+ } else {
5189
+ if (isFormSharedAndReferenced(formDoc.id)) {
5190
+ console.log(
5191
+ `[FormInit] Skipped deletion of shared doctor form ${formDoc.id} - still referenced by other procedures.`
5192
+ );
5193
+ } else {
5194
+ await (0, import_firestore9.deleteDoc)(formDoc.ref);
5195
+ removedFormIds.push(formDoc.id);
5196
+ console.log(
5197
+ `[FormInit] Removed shared doctor form ${formDoc.id} - no longer referenced by other procedures.`
5198
+ );
5199
+ }
5200
+ }
5067
5201
  } catch (error) {
5068
5202
  console.error(
5069
5203
  `[FormInit] Error removing doctor form ${formDoc.id}:`,
@@ -5079,11 +5213,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
5079
5213
  const userFormsSnap = await (0, import_firestore9.getDocs)(userFormsQuery);
5080
5214
  for (const formDoc of userFormsSnap.docs) {
5081
5215
  try {
5082
- await (0, import_firestore9.deleteDoc)(formDoc.ref);
5083
- removedFormIds.push(formDoc.id);
5084
- console.log(
5085
- `[FormInit] Removed user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
5086
- );
5216
+ const formData = formDoc.data();
5217
+ let isShared = false;
5218
+ if (formData.templateId) {
5219
+ try {
5220
+ const templateDoc = (0, import_firestore9.doc)(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
5221
+ const templateSnap = await (0, import_firestore9.getDoc)(templateDoc);
5222
+ if (templateSnap.exists()) {
5223
+ const template = templateSnap.data();
5224
+ isShared = !isProcedureSpecificForm(template);
5225
+ }
5226
+ } catch (error) {
5227
+ console.warn(
5228
+ `[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
5229
+ error
5230
+ );
5231
+ }
5232
+ }
5233
+ if (!isShared) {
5234
+ await (0, import_firestore9.deleteDoc)(formDoc.ref);
5235
+ removedFormIds.push(formDoc.id);
5236
+ console.log(
5237
+ `[FormInit] Removed procedure-specific user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
5238
+ );
5239
+ } else {
5240
+ if (isFormSharedAndReferenced(formDoc.id)) {
5241
+ console.log(
5242
+ `[FormInit] Skipped deletion of shared user form ${formDoc.id} - still referenced by other procedures.`
5243
+ );
5244
+ } else {
5245
+ await (0, import_firestore9.deleteDoc)(formDoc.ref);
5246
+ removedFormIds.push(formDoc.id);
5247
+ console.log(
5248
+ `[FormInit] Removed shared user form ${formDoc.id} - no longer referenced by other procedures.`
5249
+ );
5250
+ }
5251
+ }
5087
5252
  } catch (error) {
5088
5253
  console.error(
5089
5254
  `[FormInit] Error removing user form ${formDoc.id}:`,
package/dist/index.mjs CHANGED
@@ -4843,6 +4843,45 @@ import { updateDoc as updateDoc5, serverTimestamp as serverTimestamp4, doc as do
4843
4843
 
4844
4844
  // src/services/appointment/utils/form-initialization.utils.ts
4845
4845
  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";
4846
+ function isProcedureSpecificForm(template) {
4847
+ const tags = template.tags || [];
4848
+ const titleLower = template.title.toLowerCase();
4849
+ if (tags.includes("procedure-specific")) {
4850
+ return true;
4851
+ }
4852
+ if (tags.includes("shared")) {
4853
+ return false;
4854
+ }
4855
+ if (tags.some((tag) => {
4856
+ const tagLower = tag.toLowerCase();
4857
+ return tagLower.includes("consent") || tagLower === "consent-form";
4858
+ })) {
4859
+ return true;
4860
+ }
4861
+ if (titleLower.includes("consent")) {
4862
+ return true;
4863
+ }
4864
+ return false;
4865
+ }
4866
+ async function findExistingFormByTemplate(db, appointmentId, templateId, isUserForm) {
4867
+ const formSubcollection = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
4868
+ const appointmentRef = doc6(db, APPOINTMENTS_COLLECTION, appointmentId);
4869
+ const formsCollectionRef = collection5(appointmentRef, formSubcollection);
4870
+ const q = query5(
4871
+ formsCollectionRef,
4872
+ where5("templateId", "==", templateId)
4873
+ );
4874
+ const querySnapshot = await getDocs5(q);
4875
+ if (querySnapshot.empty) {
4876
+ return null;
4877
+ }
4878
+ const docSnap = querySnapshot.docs[0];
4879
+ const data = docSnap.data();
4880
+ if (!data.id) {
4881
+ data.id = docSnap.id;
4882
+ }
4883
+ return data;
4884
+ }
4846
4885
  async function initializeFormsForExtendedProcedure(db, appointmentId, procedureId, technologyTemplates, patientId, practitionerId, clinicId) {
4847
4886
  const initializedFormsInfo = [];
4848
4887
  const pendingUserFormsIds = [];
@@ -4885,6 +4924,49 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
4885
4924
  const isRequired = templateRef.isRequired;
4886
4925
  const isUserForm = templateRef.isUserForm || false;
4887
4926
  const formSubcollectionPath = isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
4927
+ const isProcedureSpecific = isProcedureSpecificForm(template);
4928
+ let existingForm = null;
4929
+ if (!isProcedureSpecific) {
4930
+ try {
4931
+ existingForm = await findExistingFormByTemplate(
4932
+ db,
4933
+ appointmentId,
4934
+ templateRef.templateId,
4935
+ isUserForm
4936
+ );
4937
+ if (existingForm) {
4938
+ console.log(
4939
+ `[FormInit] Found existing shared form ${existingForm.id} (template: ${template.id}) for appointment ${appointmentId}. Reusing instead of creating duplicate.`
4940
+ );
4941
+ const linkedForm = {
4942
+ formId: existingForm.id,
4943
+ templateId: template.id,
4944
+ templateVersion: template.version,
4945
+ title: template.title,
4946
+ isUserForm,
4947
+ isRequired,
4948
+ sortingOrder: templateRef.sortingOrder,
4949
+ status: existingForm.status || "pending" /* PENDING */,
4950
+ path: `${APPOINTMENTS_COLLECTION}/${appointmentId}/${formSubcollectionPath}/${existingForm.id}`
4951
+ };
4952
+ initializedFormsInfo.push(linkedForm);
4953
+ if (!allLinkedFormIds.includes(existingForm.id)) {
4954
+ allLinkedFormIds.push(existingForm.id);
4955
+ }
4956
+ if (isUserForm && isRequired && existingForm.status === "pending" /* PENDING */) {
4957
+ if (!pendingUserFormsIds.includes(existingForm.id)) {
4958
+ pendingUserFormsIds.push(existingForm.id);
4959
+ }
4960
+ }
4961
+ continue;
4962
+ }
4963
+ } catch (error) {
4964
+ console.warn(
4965
+ `[FormInit] Error checking for existing form (template: ${templateRef.templateId}):`,
4966
+ error
4967
+ );
4968
+ }
4969
+ }
4888
4970
  const appointmentRef = doc6(db, APPOINTMENTS_COLLECTION, appointmentId);
4889
4971
  const formsCollectionRef = collection5(appointmentRef, formSubcollectionPath);
4890
4972
  const filledDocumentData = {
@@ -4922,8 +5004,9 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
4922
5004
  path: docRef.path
4923
5005
  };
4924
5006
  initializedFormsInfo.push(linkedForm);
5007
+ const formType = isProcedureSpecific ? "procedure-specific" : "general/shared";
4925
5008
  console.log(
4926
- `[FormInit] Created FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
5009
+ `[FormInit] Created ${formType} FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
4927
5010
  );
4928
5011
  } catch (error) {
4929
5012
  console.error(
@@ -4935,8 +5018,28 @@ async function initializeFormsForExtendedProcedure(db, appointmentId, procedureI
4935
5018
  return { initializedFormsInfo, pendingUserFormsIds, allLinkedFormIds };
4936
5019
  }
4937
5020
  async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
5021
+ var _a, _b;
4938
5022
  const removedFormIds = [];
4939
5023
  const appointmentRef = doc6(db, APPOINTMENTS_COLLECTION, appointmentId);
5024
+ const appointmentSnap = await getDoc6(appointmentRef);
5025
+ if (!appointmentSnap.exists()) {
5026
+ console.warn(
5027
+ `[FormInit] Appointment ${appointmentId} not found when removing forms for procedure ${procedureId}.`
5028
+ );
5029
+ return removedFormIds;
5030
+ }
5031
+ const appointment = appointmentSnap.data();
5032
+ const linkedForms = appointment.linkedForms || [];
5033
+ const mainProcedureId = appointment.procedureId;
5034
+ const extendedProcedureIds = ((_b = (_a = appointment.metadata) == null ? void 0 : _a.extendedProcedures) == null ? void 0 : _b.map(
5035
+ (ep) => ep.procedureId
5036
+ )) || [];
5037
+ const allProcedureIds = [mainProcedureId, ...extendedProcedureIds].filter(Boolean);
5038
+ const remainingProcedureIds = allProcedureIds.filter((id) => id !== procedureId);
5039
+ const isFormSharedAndReferenced = (formId) => {
5040
+ const formEntries = linkedForms.filter((form) => form.formId === formId);
5041
+ return formEntries.length > 1;
5042
+ };
4940
5043
  const doctorFormsRef = collection5(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
4941
5044
  const doctorFormsQuery = query5(
4942
5045
  doctorFormsRef,
@@ -4945,11 +5048,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
4945
5048
  const doctorFormsSnap = await getDocs5(doctorFormsQuery);
4946
5049
  for (const formDoc of doctorFormsSnap.docs) {
4947
5050
  try {
4948
- await deleteDoc2(formDoc.ref);
4949
- removedFormIds.push(formDoc.id);
4950
- console.log(
4951
- `[FormInit] Removed doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
4952
- );
5051
+ const formData = formDoc.data();
5052
+ let isShared = false;
5053
+ if (formData.templateId) {
5054
+ try {
5055
+ const templateDoc = doc6(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
5056
+ const templateSnap = await getDoc6(templateDoc);
5057
+ if (templateSnap.exists()) {
5058
+ const template = templateSnap.data();
5059
+ isShared = !isProcedureSpecificForm(template);
5060
+ }
5061
+ } catch (error) {
5062
+ console.warn(
5063
+ `[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
5064
+ error
5065
+ );
5066
+ }
5067
+ }
5068
+ if (!isShared) {
5069
+ await deleteDoc2(formDoc.ref);
5070
+ removedFormIds.push(formDoc.id);
5071
+ console.log(
5072
+ `[FormInit] Removed procedure-specific doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
5073
+ );
5074
+ } else {
5075
+ if (isFormSharedAndReferenced(formDoc.id)) {
5076
+ console.log(
5077
+ `[FormInit] Skipped deletion of shared doctor form ${formDoc.id} - still referenced by other procedures.`
5078
+ );
5079
+ } else {
5080
+ await deleteDoc2(formDoc.ref);
5081
+ removedFormIds.push(formDoc.id);
5082
+ console.log(
5083
+ `[FormInit] Removed shared doctor form ${formDoc.id} - no longer referenced by other procedures.`
5084
+ );
5085
+ }
5086
+ }
4953
5087
  } catch (error) {
4954
5088
  console.error(
4955
5089
  `[FormInit] Error removing doctor form ${formDoc.id}:`,
@@ -4965,11 +5099,42 @@ async function removeFormsForExtendedProcedure(db, appointmentId, procedureId) {
4965
5099
  const userFormsSnap = await getDocs5(userFormsQuery);
4966
5100
  for (const formDoc of userFormsSnap.docs) {
4967
5101
  try {
4968
- await deleteDoc2(formDoc.ref);
4969
- removedFormIds.push(formDoc.id);
4970
- console.log(
4971
- `[FormInit] Removed user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
4972
- );
5102
+ const formData = formDoc.data();
5103
+ let isShared = false;
5104
+ if (formData.templateId) {
5105
+ try {
5106
+ const templateDoc = doc6(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
5107
+ const templateSnap = await getDoc6(templateDoc);
5108
+ if (templateSnap.exists()) {
5109
+ const template = templateSnap.data();
5110
+ isShared = !isProcedureSpecificForm(template);
5111
+ }
5112
+ } catch (error) {
5113
+ console.warn(
5114
+ `[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
5115
+ error
5116
+ );
5117
+ }
5118
+ }
5119
+ if (!isShared) {
5120
+ await deleteDoc2(formDoc.ref);
5121
+ removedFormIds.push(formDoc.id);
5122
+ console.log(
5123
+ `[FormInit] Removed procedure-specific user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
5124
+ );
5125
+ } else {
5126
+ if (isFormSharedAndReferenced(formDoc.id)) {
5127
+ console.log(
5128
+ `[FormInit] Skipped deletion of shared user form ${formDoc.id} - still referenced by other procedures.`
5129
+ );
5130
+ } else {
5131
+ await deleteDoc2(formDoc.ref);
5132
+ removedFormIds.push(formDoc.id);
5133
+ console.log(
5134
+ `[FormInit] Removed shared user form ${formDoc.id} - no longer referenced by other procedures.`
5135
+ );
5136
+ }
5137
+ }
4973
5138
  } catch (error) {
4974
5139
  console.error(
4975
5140
  `[FormInit] Error removing user form ${formDoc.id}:`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.14.51",
4
+ "version": "1.14.52",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",
@@ -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
- // Create form document in subcollection
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
- await deleteDoc(formDoc.ref);
186
- removedFormIds.push(formDoc.id);
187
- console.log(
188
- `[FormInit] Removed doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
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
- await deleteDoc(formDoc.ref);
209
- removedFormIds.push(formDoc.id);
210
- console.log(
211
- `[FormInit] Removed user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
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}:`,