fix: wrong fee validity calculation and payment fields visibility in appointment
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
index a65f46f..8432169 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
@@ -13,13 +13,14 @@
   "patient",
   "column_break_3",
   "status",
+  "section_break_5",
   "section_break_3",
   "max_visits",
   "visited",
-  "valid_till",
+  "ref_appointments",
   "column_break_6",
-  "ref_invoice",
-  "start_date"
+  "start_date",
+  "valid_till"
  ],
  "fields": [
   {
@@ -51,12 +52,6 @@
    "label": "Valid till"
   },
   {
-   "fieldname": "ref_invoice",
-   "fieldtype": "Link",
-   "label": "Reference Invoice",
-   "options": "Sales Invoice"
-  },
-  {
    "fieldname": "section_break_3",
    "fieldtype": "Section Break",
    "label": "Validity"
@@ -84,15 +79,26 @@
    "read_only": 1
   },
   {
-   "fetch_from": "ref_invoice.posting_date",
+   "fetch_from": "ref_appointment.appointment_date",
    "fieldname": "start_date",
    "fieldtype": "Date",
    "label": "Start Date",
    "read_only": 1
+  },
+  {
+   "fieldname": "ref_appointments",
+   "fieldtype": "Table MultiSelect",
+   "label": "Reference Appointments",
+   "options": "Fee Validity Reference"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break"
   }
  ],
  "links": [],
- "modified": "2020-03-09 23:14:08.581821",
+ "modified": "2020-03-14 21:42:40.766973",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Fee Validity",
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
index b7e76b0..fc15410 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
@@ -23,32 +23,20 @@
 			elif valid_till < today:
 				self.status = 'Expired'
 
-
-def update_fee_validity(fee_validity, date, ref_invoice=None):
-	max_visits = frappe.db.get_single_value("Healthcare Settings", "max_visits")
-	valid_days = frappe.db.get_single_value("Healthcare Settings", "valid_days")
-	if not valid_days:
-		valid_days = 1
-	if not max_visits:
-		max_visits = 1
-	date = getdate(date)
-	valid_till = date + datetime.timedelta(days=int(valid_days))
-	fee_validity.max_visits = max_visits
+def create_fee_validity(appointment):
+	fee_validity = frappe.new_doc('Fee Validity')
+	fee_validity.practitioner = appointment.practitioner
+	fee_validity.patient = appointment.patient
+	fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1
+	valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1
 	fee_validity.visited = 1
-	fee_validity.valid_till = valid_till
-	fee_validity.ref_invoice = ref_invoice
+	fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days))
+	fee_validity.append('ref_appointments', {
+		'appointment': appointment.name
+	})
 	fee_validity.save(ignore_permissions=True)
 	return fee_validity
 
-
-def create_fee_validity(practitioner, patient, date, ref_invoice=None):
-	fee_validity = frappe.new_doc("Fee Validity")
-	fee_validity.practitioner = practitioner
-	fee_validity.patient = patient
-	fee_validity = update_fee_validity(fee_validity, date, ref_invoice)
-	return fee_validity
-
-
 def update_validity_status():
 	docs = frappe.get_all('Fee Validity', filters={'status': ['not in', ['Completed', 'Expired']]})
 	for doc in docs:
diff --git a/erpnext/healthcare/doctype/fee_validity_reference/__init__.py b/erpnext/healthcare/doctype/fee_validity_reference/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/doctype/fee_validity_reference/__init__.py
diff --git a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json
new file mode 100644
index 0000000..40f128e
--- /dev/null
+++ b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json
@@ -0,0 +1,32 @@
+{
+ "actions": [],
+ "creation": "2020-03-13 16:08:42.859996",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "appointment"
+ ],
+ "fields": [
+  {
+   "fieldname": "appointment",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Patient Appointment",
+   "options": "Patient Appointment",
+   "reqd": 1
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-03-15 00:27:02.076470",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Fee Validity Reference",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py
new file mode 100644
index 0000000..c819280
--- /dev/null
+++ b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class FeeValidityReference(Document):
+	pass
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
index 2d03c88..bbd9ae3 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
@@ -14,6 +14,7 @@
   "collect_registration_fee",
   "registration_fee",
   "automate_appointment_invoicing",
+  "enable_free_follow_ups",
   "max_visits",
   "valid_days",
   "healthcare_service_items",
@@ -78,10 +79,12 @@
    "options": "Currency"
   },
   {
+   "depends_on": "eval:doc.enable_free_follow_ups == 1",
    "description": "Time period (Valid number of days) for free consultations",
    "fieldname": "valid_days",
    "fieldtype": "Int",
-   "label": "Valid number of days"
+   "label": "Valid number of days",
+   "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1"
   },
   {
    "collapsible": 1,
@@ -287,15 +290,23 @@
    "label": "Remind Before"
   },
   {
+   "depends_on": "eval:doc.enable_free_follow_ups == 1",
    "description": "The number of free follow ups (Patient Encounters in valid days) allowed",
    "fieldname": "max_visits",
    "fieldtype": "Int",
-   "label": "Number of Patient Encounters in valid days"
+   "label": "Number of Patient Encounters in valid days",
+   "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_free_follow_ups",
+   "fieldtype": "Check",
+   "label": "Enable Free Follow-ups"
   }
  ],
  "issingle": 1,
  "links": [],
- "modified": "2020-03-09 17:43:23.251559",
+ "modified": "2020-03-13 14:34:40.962503",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Settings",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index 6b264d4..82d70f8 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -40,24 +40,6 @@
 
 		if (frm.is_new()) {
 			frm.page.set_primary_action(__('Check Availability'), function() {
-				frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, 'automate_appointment_invoicing', (settings) => {
-					if (settings.automate_appointment_invoicing) {
-						if (!frm.doc.mode_of_payment) {
-							frappe.msgprint({
-								title: __('Not Allowed'),
-								message: __('Please select a Mode of Payment first'),
-								indicator: 'red'
-							});
-						}
-						if (!frm.doc.paid_amount) {
-							frappe.msgprint({
-								title: __('Not Allowed'),
-								message: __('Please set the Paid Amount first'),
-								indicator: 'red'
-							});
-						}
-					}
-				});
 				if (!frm.doc.patient) {
 					frappe.msgprint({
 						title: __('Not Allowed'),
@@ -65,7 +47,33 @@
 						indicator: 'red'
 					});
 				} else {
-					check_and_set_availability(frm);
+					frappe.call({
+						method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd',
+						args: {'patient': frm.doc.patient},
+						callback: function(data) {
+							if (data.message == true) {
+								if (frm.doc.mode_of_payment && frm.doc.paid_amount) {
+									check_and_set_availability(frm);
+								}
+								if (!frm.doc.mode_of_payment) {
+									frappe.msgprint({
+										title: __('Not Allowed'),
+										message: __('Please select a Mode of Payment first'),
+										indicator: 'red'
+									});
+								}
+								if (!frm.doc.paid_amount) {
+									frappe.msgprint({
+										title: __('Not Allowed'),
+										message: __('Please set the Paid Amount first'),
+										indicator: 'red'
+									});
+								}
+							} else {
+								check_and_set_availability(frm);
+							}
+						}
+					});
 				}
 			});
 		} else {
@@ -107,8 +115,12 @@
 				create_vital_signs(frm);
 			}, __('Create'));
 		}
+	},
 
-		frm.events.toggle_payment_fields(frm);
+	patient: function(frm) {
+		if (frm.doc.patient) {
+			frm.trigger('toggle_payment_fields');
+		}
 	},
 
 	get_procedure_from_encounter: function(frm) {
@@ -116,17 +128,24 @@
 	},
 
 	toggle_payment_fields: function(frm) {
-		frappe.db.get_value('Healthcare Settings', {name: 'Healthcare Settings'}, ['automate_appointment_invoicing'], (settings) => {
-			if (settings.automate_appointment_invoicing == 1) {
-				frm.set_df_property('mode_of_payment', 'hidden', 0);
-				frm.set_df_property('paid_amount', 'hidden', 0);
-				frm.set_df_property('mode_of_payment', 'reqd', 1);
-				frm.set_df_property('paid_amount', 'reqd', 1);
-			} else {
-				frm.set_df_property('mode_of_payment', 'hidden', 1);
-				frm.set_df_property('paid_amount', 'hidden', 1);
-				frm.set_df_property('mode_of_payment', 'reqd', 0);
-				frm.set_df_property('paid_amount', 'reqd', 0);
+		frappe.call({
+			method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd',
+			args: {'patient': frm.doc.patient},
+			callback: function(data) {
+				if (data.message.fee_validity) {
+					// if fee validity exists and automated appointment invoicing is enabled,
+					// show payment fields as non-mandatory
+					frm.set_df_property('mode_of_payment', 'hidden', 0);
+					frm.set_df_property('paid_amount', 'hidden', 0);
+					frm.set_df_property('mode_of_payment', 'reqd', 0);
+					frm.set_df_property('paid_amount', 'reqd', 0);
+				} else {
+					// if automated appointment invoicing is disabled, hide fields
+					frm.toggle_display('mode_of_payment', data.message ? 1 : 0);
+					frm.toggle_display('paid_amount', data.message ? 1 : 0);
+					frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0);
+					frm.toggle_reqd('paid_amount', data.message ? 1 :0);
+				}
 			}
 		});
 	}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index e8e65ed..1cf90c6 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -13,7 +13,7 @@
 from frappe.core.doctype.sms_settings.sms_settings import send_sms
 from erpnext.hr.doctype.employee.employee import is_holiday
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
-from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge
+from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity
 
 class PatientAppointment(Document):
 	def validate(self):
@@ -84,19 +84,28 @@
 					frappe.db.set_value('Patient Appointment', self.name, 'notes', comments)
 
 	def update_fee_validity(self):
-		fee_validity = check_fee_validity(self)
+		fee_validity = manage_fee_validity(self)
 		if fee_validity:
-			visited = fee_validity.visited + 1
-			frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited)
-			if fee_validity.ref_invoice:
-				frappe.db.set_value('Patient Appointment', self.name, 'invoiced', True)
-				frappe.db.set_value('Patient Appointment', self.name, 'ref_sales_invoice', fee_validity.ref_invoice)
 			frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
 
 
+@frappe.whitelist()
+def check_payment_fields_reqd(patient):
+	automate_invoicing = frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing')
+	free_follow_ups = frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups')
+	if automate_invoicing:
+		if free_follow_ups:
+			fee_validity = frappe.db.exists('Fee Validity', {'patient': patient, 'status': 'Ongoing'})
+			if fee_validity:
+				return {'fee_validity': fee_validity}
+		return True
+	return False
+
 def invoice_appointment(appointment_doc):
-	if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing') and \
-			not frappe.db.get_value('Patient Appointment', appointment_doc.name, 'invoiced'):
+	automate_invoicing = frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing')
+	appointment_invoiced = frappe.db.get_value('Patient Appointment', appointment_doc.name, 'invoiced')
+	fee_validity = check_fee_validity(appointment_doc)
+	if automate_invoicing and not appointment_invoiced and not fee_validity:
 		sales_invoice = frappe.new_doc('Sales Invoice')
 		sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer')
 		sales_invoice.appointment = appointment_doc.name
@@ -136,51 +145,19 @@
 
 def cancel_appointment(appointment_id):
 	appointment = frappe.get_doc('Patient Appointment', appointment_id)
-	# If invoiced --> fee_validity update visit as -1
 	if appointment.invoiced:
 		sales_invoice = check_sales_invoice_exists(appointment)
 		if sales_invoice and cancel_sales_invoice(sales_invoice):
-			frappe.msgprint(
-				_('Appointment {0} and Sales Invoice {1} cancelled'.format(appointment.name, sales_invoice.name))
-			)
+			msg = _('Appointment {0} and Sales Invoice {1} cancelled').format(appointment.name, sales_invoice.name)
 		else:
-			validity = check_fee_validity(appointment.practitioner, appointment.patient)
-			if validity:
-				fee_validity = frappe.get_doc('Fee Validity', validity)
-				if validate_appointment_in_fee_validity(appointment, fee_validity.valid_till, fee_validity.ref_invoice):
-					visited = fee_validity.visited - 1
-					frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited)
-					frappe.msgprint(
-						_('Appointment cancelled. Please review and cancel the invoice {0}'.format(fee_validity.ref_invoice))
-					)
-				else:
-					frappe.msgprint(_('Appointment Cancelled'))
-			else:
-				frappe.msgprint(_('Appointment Cancelled'))
+			msg = _('Appointment Cancelled. Please review and cancel the invoice {0}').format(fee_validity.ref_invoice)
 	else:
-		frappe.msgprint(_('Appointment Cancelled'))
+		fee_validity = manage_fee_validity(appointment)
+		msg = _('Appointment Cancelled.')
+		if fee_validity:
+			msg += _('Fee Validity {0} updated.').format(fee_validity.name)
 
-
-def validate_appointment_in_fee_validity(appointment, valid_end_date, ref_invoice):
-	valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days')
-	max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits')
-	valid_start_date = add_days(getdate(valid_end_date), -int(valid_days))
-
-	# Appointments which have same fee validity range with the appointment
-	appointments = frappe.get_list('Patient Appointment', {
-		'patient': appointment.patient,
-		'invoiced': True,
-		'appointment_date': ('<=', getdate(valid_end_date)),
-		'appointment_date':('>=', getdate(valid_start_date)),
-		'practitioner': appointment.practitioner
-		}, order_by='appointment_date desc', limit=int(max_visits))
-
-	if appointments and len(appointments) > 0:
-		appointment_obj = appointments[len(appointments)-1]
-		sales_invoice = check_sales_invoice_exists(appointment_obj)
-		if sales_invoice.name == ref_invoice:
-			return True
-	return False
+	frappe.msgprint(msg)
 
 
 def cancel_sales_invoice(sales_invoice):
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index 3c4e783..d1b5b7c 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -9,7 +9,7 @@
 import math
 from frappe.utils import time_diff_in_hours, rounded, getdate, add_days
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
-from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity, update_fee_validity
+from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
 from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
 
 @frappe.whitelist()
@@ -317,7 +317,6 @@
 		if frappe.db.get_value('Patient Appointment', item.reference_dn, 'procedure_template'):
 			dt_from_appointment = 'Clinical Procedure'
 		else:
-			manage_fee_validity(item.reference_dn, method, ref_invoice)
 			dt_from_appointment = 'Patient Encounter'
 		manage_doc_for_appointment(dt_from_appointment, item.reference_dn, invoiced)
 
@@ -349,7 +348,8 @@
 def check_fee_validity(appointment):
 	validity = frappe.db.exists('Fee Validity', {
 		'practitioner': appointment.practitioner,
-		'patient': appointment.patient
+		'patient': appointment.patient,
+		'status': 'Ongoing'
 	})
 	if not validity:
 		return
@@ -359,83 +359,21 @@
 	if fee_validity.valid_till >= appointment_date and fee_validity.visited < fee_validity.max_visits:
 		return fee_validity
 
-
-def manage_fee_validity(appointment_name, method, ref_invoice=None):
-	appointment_doc = frappe.get_doc('Patient Appointment', appointment_name)
-	fee_validity = check_fee_validity(appointment_doc)
-	do_not_update = False
-	visited = 0
+def manage_fee_validity(appointment):
+	fee_validity = check_fee_validity(appointment)
 	if fee_validity:
-		if method == 'on_cancel' and appointment_doc.status != 'Closed':
-			if ref_invoice == fee_validity.ref_invoice:
-				visited = fee_validity.visited - 1
-				if visited < 0:
-					visited = 0
-				frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited)
-			do_not_update = True
-		elif method == 'on_submit' and fee_validity.visited < fee_validity.max_visits:
-			visited = fee_validity.visited + 1
-			frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited)
-			do_not_update = True
+		if appointment.status == 'Cancelled' and fee_validity.visited > 0:
+			fee_validity.visited -= 1
+			frappe.db.delete('Fee Validity Reference', {'appointment': appointment.name})
 		else:
-			do_not_update = False
-
-		if not do_not_update:
-			fee_validity = update_fee_validity(fee_validity, appointment_doc.appointment_date, ref_invoice)
+			fee_validity.visited += 1
+			fee_validity.append('ref_appointments', {
+				'appointment': appointment.name
+			})
+		fee_validity.save(ignore_permissions=True)
 	else:
-		fee_validity = create_fee_validity(appointment_doc.practitioner, appointment_doc.patient, appointment_doc.appointment_date, ref_invoice)
-
-	visited = fee_validity.visited
-	mark_appointments_as_invoiced(fee_validity, ref_invoice, method, appointment_doc, visited)
-
-	if method == 'on_cancel':
-		ref_invoice_in_fee_validity = frappe.db.get_value('Fee Validity', fee_validity.name, 'ref_invoice')
-		if ref_invoice_in_fee_validity == ref_invoice:
-			frappe.delete_doc('Fee Validity', fee_validity.name)
-
-
-def mark_appointments_as_invoiced(fee_validity, ref_invoice, method, appointment_doc, visited):
-	if method == 'on_cancel':
-		invoiced = True
-	else:
-		invoiced = False
-
-	patient_appointments = appointments_valid_in_fee_validity(appointment_doc, invoiced)
-	if patient_appointments and fee_validity:
-		visit = visited
-		for appointment in patient_appointments:
-			if method == 'on_cancel' and appointment.status != 'Closed':
-				if ref_invoice == fee_validity.ref_invoice:
-					visited -= 1
-					if visited < 0:
-						visited = 0
-					frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited)
-				frappe.db.set_value('Patient Appointment', appointment.name, 'invoiced', False)
-				manage_doc_for_appointment('Patient Encounter', appointment.name, False)
-			elif method == 'on_submit' and int(fee_validity.max_visits) > visit:
-				if ref_invoice == fee_validity.ref_invoice:
-					visited += 1
-					frappe.db.set_value('Fee Validity', fee_validity.name, 'visited', visited)
-				frappe.db.set_value('Patient Appointment', appointment.name, 'invoiced', True)
-				manage_doc_for_appointment('Patient Encounter', appointment.name, True)
-			if ref_invoice == fee_validity.ref_invoice:
-				visit = visit + 1
-
-
-def appointments_valid_in_fee_validity(appointment, invoiced):
-	valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days')
-	max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits')
-	if int(max_visits) < 1:
-		max_visits = 1
-	valid_days_date = add_days(getdate(appointment.appointment_date), int(valid_days))
-
-	return frappe.get_list('Patient Appointment',{
-		'patient': appointment.patient,
-		'invoiced': invoiced,
-		'appointment_date':('<=', valid_days_date),
-		'appointment_date':('>=', getdate(appointment.appointment_date)),
-		'practitioner': appointment.practitioner
-	}, order_by='appointment_date', limit=int(max_visits)-1)
+		fee_validity = create_fee_validity(appointment)
+	return fee_validity
 
 
 def manage_doc_for_appointment(dt_from_appointment, appointment, invoiced):