This section contains reference information which will be useful to administrators, integrators, and others who need to know "what's under the hood".
OpenVPMS has a simple but incomplete audit facility that needs to be explicitly enabled - see below.
Some records (eg invoices) record the 'author' - ie the name of the user who created them. However, this is not universal - for example no author is recorded on the customer record.
Audit Service
The audit service is very limited. It has never been developed beyond prototype phase. As such, it doesn't audit all of the operations performed on the database nor does it log the user that performed the operation. It works by intercepting calls to the archetype service but it does not intercept the method where multiple objects are saved at once.
This method is used in many places, including:
* editing
* lookup merging
* customer merging
* invoice/credit reversals
* document generation
* customer balance updates
* till clearing
If you want to try it out, you need to edit applicationContext.xml located in <TOMCAT_HOME>/webapps/openvpms/WEB-INF/ and uncomment auditDao, auditService, autoProxyCreator, and auditServiceAdvisor so that things look as follows:
<bean id="auditDao" class="org.openvpms.component.business.dao.hibernate.im.audit.AuditDAOHibernate"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="auditService" class="org.openvpms.component.business.service.audit.AuditService"> <constructor-arg ref="auditDao"/> </bean> <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>archetypeService</value> </list> </property> <property name="interceptorNames"> <list> <value>auditServiceAdvisor</value> </list> </property> </bean> <bean id="auditServiceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" ref="auditService"/> <property name="patterns"> <list> <value>.*ArchetypeService\.save</value> <value>.*ArchetypeService\.remove</value> </list> </property> </bean>
There are multiple caches within OpenVPMS. These are used to improve performance, but in some cases and circumstances changes you make may not be immediately effective.
In general, if you update the database outside OpenVPMS, the changes:
The major caches are listed below:
1. Hibernate's second level cache, configured by ehcache.xml
Elements in this cache are set with the flag eternal="true", which means that they don't expire unless the cache capacity is reached, in which case the least recently used will be tossed out.
2. Archetypes
Elements in this cache can only be updated by:
3. Lookups
Elements in this cache can be updated:
4. Appointments/tasks
Elements in this cache can be updated:
5. Current user context
This includes the current:
Refreshed by:
OpenVPMS uses JXPath to perform expression evaluation within archetypes, macros, and reports.
The following extension functions can be used within expressions. They are grouped as General, Date, Expression, List, Party, Math, Macro, History, Product, Reminder, Supplier and User functions.
In the examples below you will see arguments like $patient, $customer, $visit, etc. This is a convenient shorthand for openvpms:get(.,'xxxx.entity') where xxxx is patient, customer, etc. You can also use Application Fields and here also there needs to be a $ prefix, eg $OpenVPMS.location to access the current Practice Location.
Note that the $ shorthand is only available in the GUI environment. Specifically, it can be used in macros invoked the by user, but not in macros invoked by macro:eval() when generating forms and letters.
To enclose string arguments, you can use either " or ', ie "ABC" and 'ABC' are equivalent.
Function | Description |
---|---|
openvpms:get(object, node)
Note that in the case where the node is an object reference (such as the node patient in the act.customerAppointment) then the node 'patient.objectReference' will return the patient object and one can then use this to access the patient details. In this case (see examples) using the / syntax to access the child node provides a shorthand. |
Returns the named node. If the object is null, then the default value is returned, or a null string if no default value is provided. The node name may be a single or composite name. The former returns the immediate node. A composite name is multiple node names separated by ".". Two special node names are defined:
Examples
Object Reference Examples (the first two are equivalent)
|
openvpms:set(object, node, value)
|
Sets the named node to the supplied value.
The node name may be a single or composite name. The former sets the immediate node. A composite name is multiple node names separated by ".". Examples
Note that this is a powerful low level function which should be avoided if there is a dedicated function for the task (as there is indeed for the above two examples). Indeed after build 7516, this function cannot be used in reports and macros, and can only be used in demographic updates. |
openvpms:lookup(object, node)
|
Returns the name of a lookup node. If object is null, then the default value is returned, or a null string if no default value is provided. The node name may be a single or composite name. The former returns the immediate node. A composite name is multiple node names separated by ".". Examples
|
Note that the date: functions return a timestamp. If you want a printable result then you will need to use date:format to transform the timestamp into printable text - something like:
date:format(date:add(openvpms:get(.,"startTime"),"2yr"),"d MMMM yyyy")
Function | Description | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
date:now() date:today() date:tomorrow() date:yesterday() |
Returns the current date and time Returns the current date, with the time set to 00:00 Returns tomorrow's date, with the time set to 00:00 Returns yesterday's date, with the time set to 00:00 Note that java.util.Date.new() - which may be see in earlier code - is equivalent to date:now(). |
||||||||||||||||||||||||
date:add(dateTime, period)
|
Adds (or subtracts) a period to a date/time. The period is of the form:
({+|-}(0..9)+(h|d|m|w|q|y)+{s|e}) where:
Examples Given: dateTime = 4/10/2015 and date:now() = 30/6/2016 05:39
|
||||||||||||||||||||||||
date:format(dateTime, pattern)
|
Formats a date/time according to a SimpleDateFormat pattern. Examples Given: dateTime = 3:30:05pm 20/8/2012
|
||||||||||||||||||||||||
date:formatDate(date)
|
Formats a date using the current locale's MEDIUM format. Examples Given: dateTime = 10:38 am 15/7/2013
|
||||||||||||||||||||||||
date:formatDate(date, style)
|
Formats a date using the specified format for the current locale. Examples Given: dateTime = 11:00am 15/7/2013
|
||||||||||||||||||||||||
date:formatTime(time)
|
Formats a time using the current locale's MEDIUM format. Examples Given: dateTime = 3:30:05pm 20/8/2012
|
||||||||||||||||||||||||
date:formatTime(time, style)
|
Formats a time using the specified format for the current locale. Examples Given: dateTime = 3:30:05pm 20/8/2012
|
||||||||||||||||||||||||
date:formatDateTime(datetime)
|
Formats a date-time using the MEDIUM format for the current locale. Examples Given: dateTime = 3:30:05pm 20/8/2012
|
||||||||||||||||||||||||
date:formatDateTime(datetime, style)
|
Formats a date-time using the specified format for the current locale. Examples Given: dateTime = 3:30:05pm 20/8/2012
|
||||||||||||||||||||||||
date:formatDateTime(datetime, dateStyle, timeStyle)
|
Formats a date-time using the specified date and time styles for the current locale Examples Given: dateTime = 9:58:12am 28/7/2013
|
Function | Description |
---|---|
expr:concatIf(value1, value2)
|
Concatentates two strings, if both are not null/empty. If either is null/empty, returns an empty string. Examples
|
expr:concatIf(value1, value2, value3)
|
Concatentates three strings, if all are not null/empty. If any is null/empty, returns an empty string. Examples
|
expr:if(condition, value)
|
Evaluates the result of condition. If true, returns value, otherwise returns null. Examples
|
expr:if(condition, trueValue, falseValue)
|
Evaluates the result of condition. If true, returns trueValue, otherwise returns falseValue. Examples
|
expr:ifempty(value, elseValue)
|
Returns the first argument if it is not null or empty, or the second argument if it is. Examples
|
expr:trim(value, maxLength)
|
Truncates value if its length exceeds maxLength, otherwise returns value unchanged.
|
expr:var(name)
|
Returns the value of the named variable if it exists, otherwise returns null. Examples
|
expr:var(name, value)
|
Returns the value of the named variable if it exists, otherwise returns value. Examples
|
The following functions operate on lists of objects.
Function | Description |
---|---|
list:join(objects, node)
|
Iterates through a list of objects, joining the string value of the specified node, separated by commas.
|
list:join(objects, node, separator)
|
Iterates through a list of objects, joining the string value of the specified node, separated by separator.
Example
returns a comma separated list of the patient's aliases |
list:names(objects)
|
Concatenates object names, comma separated.
|
list:names(objects, separator)
|
Concatenates object names, separated by separator
|
list:sort(objects, node)
|
Sorts objects on a node.
|
list:sortNames(objects)
|
Sorts and concatenates the names of the objects, separated by commas.
|
list:sortNames(objects, separator)
|
Concatenates object names, separated by separator
|
list:sortNamesOf(node)
|
Sorts and concatenate the object names of the specified node, separated by commas.
|
list:values(object, node)
|
Returns the values of a collection for the specified object. The node argument may refer to a single node, or a composite node. Null values are ignored.
|
list:values(node)
|
Returns the values of a collection for the current object. The node argument may refer to a single node, or a composite node. Null values are ignored.
|
list:distinct(object, node)
|
Returns the distinct values of a collection for the specified object. The node argument may refer to a single node, or a composite node. Null values are ignored.
|
list:distinct(node)
|
Returns the distinct values of a collection for the current object. The node argument may refer to a single node, or a composite node. Null values are ignored.
|
The following functions operate on party archetypes, such as customers, patients, suppliers, and organisations.
Function | Description |
---|---|
party:getPartyFullName(party)
|
Returns the full name of the specified party Examples
|
party:getPartyFullName(act)
|
If the act has a customer node, then the full name of the customer will be returned. If there is no customer node, but there is a patient node, the full name of the owner of the patient will be returned. Examples
|
party:getPatientOwner(patient)
|
Returns the owner of a patient. Examples
|
party:getPatientOwner(act)
|
Returns the owner of a patient associated with an act. Examples
|
party:getPatientCurrentOwner(act)
|
Returns the current owner of a patient associated with an act. Examples
|
party:setPatientInactive(patient)
|
Flags a patient as being inactive. Examples
|
party:setPatientDeceased(patient)
|
Flags a patient as being deceased. This also:
Examples
|
party:setPatientDesexed(patient)
|
Flags a patient as being desexed. Examples
|
party:getPreferredContacts(party)
|
Returns a formatted string of preferred contacts for a party. Examples
|
party:getBillingAddress(party)
|
Returns a formatted billing address for a party. This looks for:
Returns an empty string if there is no location contact. Examples
|
party:getBillingAddress(act)
|
Returns a formatted billing address for a customer associated with act. If the act has a customer node, then this customer will be passed to party:getBillingAddress(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getBillingAddress(party) Returns an empty string if there is no location contact. Examples
|
party:getCorrespondenceAddress(party)
|
Returns a formatted correspondence address for a party. This looks for:
Returns an empty string if there is no location contact. Examples
|
party:getCorrespondenceAddress(act)
|
Returns a formatted correspondence address for a customer associated with act. If the act has a customer node, then this customer will be passed to party:getCorrespondenceAddress(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getCorrespondenceAddress(party) Returns an empty string if there is no location contact. Examples
|
party:getTelephone(party)
|
Returns a formatted telephone number for a party. This looks for:
Returns an empty string if there is no phone contact. Examples
|
party:getTelephone(act)
|
Returns a formatted telephone number for a customer associated with act. If the act has a customer node, then this customer will be passed to party:getTelephone(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getTelephone(party) Returns an empty string if there is no phone contact. Examples
|
party:getHomeTelephone(party)
|
Returns a formatted home telephone number for a party. This looks for:
Returns an empty string if there is no phone contact. Examples
|
party:getHomeTelephone(act)
|
Returns a formatted home telephone number for a customer associated with act. If the act has a customer node, then this customer will be passed to party:getHomeTelephone(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getHomeTelephone(party) Returns an empty string if there is no phone contact. Examples
|
party:getWorkTelephone(party)
|
Returns a formatted work telephone number for a party. This looks for the phone contact with WORK purpose. Returns an empty string if there is no such contact. Examples
|
party:getWorkTelephone(act)
|
Returns a formatted work telephone number for a customer associated with act.
If the act has a customer node, then this customer will be passed to party:getWorkTelephone(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getWorkTelephone(party) Returns an empty string if there is no phone contact. Examples
|
party:getMobileTelephone(party)
|
Returns a formatted mobile telephone number for a party. This looks for the phone contact with MOBILE purpose. Returns an empty string if there is no such contact. Examples
|
party:getMobileTelephone(act)
|
Returns a formatted mobile telephone number for a customer associated with act.
If the act has a customer node, then this customer will be passed to party:getMobileTelephone(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getMobileTelephone(party) Returns an empty string if there is no phone contact. Examples
|
party:getFaxNumber(party)
|
Returns a formatted fax number for a party. Returns an empty string if there is no fax contact. Examples
|
party:getFaxNumber(act)
|
Returns a formatted fax number for a customer associated with act.
If the act has a customer node, then this customer will be passed to party:getFaxNumber(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getFaxNumber(party) Returns an empty string if there is no fax contact. Examples
|
party:getEmailAddress(party)
|
Returns a formatted email address for a party. Returns an empty string if there is no email contact. Examples
|
party:getEmailAddress(act)
|
Returns a formatted email address for a customer associated with act.
If the act has a customer node, then this customer will be passed to party:getEmailAddress(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getEmailAddress(party) Returns an empty string if there is no email contact. Examples
|
party:getWebsite(party)
|
Returns the website for a party. Returns an empty string if there is no website contact. Examples
|
party:getContactPurposes(contact)
|
Returns a formatted string of contact purposes for a contact. |
party:identities(party)
|
Returns a formatted string of a party's identities. |
party:getAccountBalance(customer)
|
Returns the account balance for a customer. Examples
|
party:getAccountBalance(act)
|
Returns the account balance for a customer associated with act. If the act has a customer node, then this customer will be passed to party:getAccountBalance(customer) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getAccountBalance(customer) Examples
|
party:getPatientReferralVet(patient)
|
Returns the referral vet for a patient. This is the patient's associated veterinarian from the first matching Referral (either a Referred From or Referred To relationship) active at the current time. Examples
|
party:getPatientReferralVet(act)
|
Returns the referral vet for a patient linked to act. This is the patient's associated veterinarian from the first matching Referral (either a Referred From or Referred To relationship) active at the act's start time. Examples
|
party:getPatientReferralVetPractice(patient)
|
Returns the referral vet practice for a vet associated with the supplied patient, active at the current time. Examples
|
party:getPatientReferralVetPractice(act)
|
Returns the referral vet practice for a vet associated with the supplied act's patient, active at the act's start time. Examples
|
party:getReferralVetPractice(vet, date)
|
Returns the referral vet practice for a vet, for the specified date. |
party:getPatientAge(patient)
|
Returns the age of the patient. Examples
|
party:getPatientMicrochip(patient)
|
Returns a patient's microchip, or an empty string if none exists. Examples
|
party:getPatientMicrochip(act)
|
Returns the microchip for a patient linked to act. Examples
|
party:getPatientMicrochips(patient)
|
Returns a patient's microchips, or an empty string if none exists. If there are more than one, then the values are separated by commas, eg Examples
|
party:getPatientMicrochips(act)
|
Returns the microchips for a patient linked to act. Examples
|
party:getMicrochip(party)
|
Returns the most recent active entityIdentity.microchip for party. Examples
|
party:getMicrochip(act)
|
Returns the most recent active entityIdentity.microchip for a patient linked to act. Examples
|
party:getPatientPetTag(patient)
|
Returns the pet tag for a patient,or an empty string if none exists. Examples
|
party:getPatientPetTag(act)
|
Returns the pet tag for a patient linked to act. Examples
|
party:getPatientRabiesTag(patient)
|
Returns the rabies tag for a patient,or an empty string if none exists. Examples
|
party:getPatientRabiesTag(act)
|
Returns the rabies tag for a patient linked to act. Examples
|
party:getPatientWeight(patient)
|
Returns the formatted weight for a patient, or an empty string if the patient has not been weighed. This uses the most recent recorded weight for the patient.
|
party:getPatientWeight(act)
|
Returns the formatted weight for a patient linked to act, or an empty string if the patient has not been weighed. This uses the most recent recorded weight for the patient. Examples
|
party:getWeight(patient)
|
Returns the patient weight in kg. If the patient has no recorded weight, returns 0. This uses the most recent recorded weight for the patient. Examples
|
party:getWeight(patient, units)
|
Returns the patient weight in the specified units. If the patient has no recorded weight, returns 0. This uses the most recent recorded weight for the patient. Examples
|
party:getWeight(act)
|
Returns the weight (in kilograms) for a patient linked to act, or 0 if there is no patient, or the patient has no weight recorded. This uses the most recent recorded weight for the patient. Examples
|
party:getWeight(act, units)
|
Returns the weight in units for a patient linked to act, or 0 if there is no patient, or the patient has no weight recorded. This uses the most recent recorded weight for the patient. Examples
|
party:getPatientDesexStatus(patient)
|
Returns the desex status of a patient. Examples
|
party:getPatientDesexStatus(act)
|
Returns the desex status of a patient linked to act. Examples
|
party:getPatientVisit(patient)
|
Returns the most recent Visit (i.e. act.patientClinicalEvent) for a patient. Examples
|
party:getPractice() |
Returns the practice object which can then be used to access other practice nodes. Note however, that the OpenVPMS.practice Application Fields form is more convenient. Example
|
party:getPracticeAddress() | Returns the practice address as a single line, or an empty string if the practice has no location contact. If you need it in multi-line format use say party:getBillingAddress(party:getPractice()) |
party:getPracticeTelephone() | Returns the practice WORK telephone, or an empty string if the practice has no phone contact with the purpose Work. |
party:getPracticeFaxNumber() | Returns the practice fax number, or an empty string if the practice has no fax contact |
party:getAppointments(party, interval, units)
|
Returns the Pending appointments for a customer or patient starting from now to the specified interval.
Examples
|
|
Returns the BPay Customer Reference Number for a party. This is the id of the party plus a check digit generated using the Luhn 10 algorithm. Examples
|
party:getBpayID(act)
|
Returns a the BPay CRN (see above) for a customer associated with act.
If the act has a customer node, then this customer will be passed to party:getBpayID(party) If there is no customer node, but there is a patient node, the owner of the patient will be passed to party:getBpayID(party) Examples
|
party:getLetterheadContacts(location)
|
Returns:
This function is normally invoked as: |
Function | Description |
---|---|
math:round(value, n) |
Rounds value to n decimal places. Examples
|
math:pow(value, n) |
Returns valuen Examples
|
The following function can be used in document template content files. Its primary use is to provide standardised text strings for multiple documents.
Function | Description | ||||
---|---|---|---|---|---|
macro:eval(macro)
|
Expands the specified macro.
Examples Given: the macro fn_bloggs contains the expression:
|
||||
macro:eval(macro, expr)
|
Expands the specified macro, using expr to provide the context for the macro. Examples Given: the standard @qid macro which uses the dispensingVerb macro
|
The patient history functions are restricted for use within Jasper Reports. They are designed to be used in sub-report expressions. E.g.:
$P{dataSource}.getExpressionDataSource("history:medication(openvpms:get(.,'patient.entity'))")
Function | Description |
---|---|
history:charges(patient)
|
Returns all invoice item acts for a patient, ordered on descending start time.
|
history:charges(patient, productTypeName)
|
Returns all invoice item acts for a patient, that have the specified product type name, ordered on descending start time.
|
history:charges(patient, date)
|
Returns all invoice item acts for a patient on the specified date, ordered on descending start time.
|
history:charges(patient, from, to)
|
Returns all invoice item acts for a patient between the specified dates, ordered on descending start time. If a date is null, indicates it is unbounded. The second example gets the charges for the last 60 days.
|
history:charges(patient, productTypeName, date)
|
Returns all invoice item acts for a patient that have the specified product type name, on the specified date, ordered on descending start time.
|
history:charges(patient, productTypeName, from, to)
|
Returns all charge acts for a patient that have the specified product type name, between the specified dates, ordered on descending start time. If a date is null, indicates it is unbounded. The second example gets the Vaccinations for the last 6 months.
|
history:medication(patient)
|
Returns all medication acts for a patient, ordered on descending start time.
|
history:medication(patient, productTypeName)
|
Returns all medication acts for a patient, that have the specified product type name, ordered on descending start time.
|
history:medication(patient, date)
|
Returns all medication acts for a patient on the specified date, ordered on descending start time.
|
history:medication(patient, from, to)
|
Returns all medication acts for a patient between the specified date/times, ordered on descending start time. If a date is null, indicates it is unbounded. The first example gets medications from 1/1/2010 through to 00:00:00 today, the second those in the last 12 hours.
|
history:medication(patient, productTypeName, date)
|
Returns all medication acts for a patient that have the specified product type name, on the specified date, ordered on descending start time.
|
history:medication(patient, productTypeName, from, to)
|
Returns all medication acts for a patient that have the specified product type name, between the specified date/times, ordered on descending start time. If a date is null, indicates it is unbounded.
|
Function | Description |
---|---|
price(product,taxExPrice)
|
Returns the price for a product, given a tax-exclusive price.
|
price(product,taxExPrice, taxInclusive)
|
Returns the price for a product, given a tax-exclusive price.
|
round(price)
|
Rounds a price according to the practice currency rounding convention.
|
taxRate(product)
|
Returns the tax rate for a product, expressed as a percentage.
|
Reminders functions returning lists of act.patientReminder acts are restricted for use within Jasper Reports. They are designed to be used in sub-report expressions. E.g.:
$P{dataSource}.getExpressionDataSource("reminder:getReminders(., 1, 'YEARS')")
Function | Description |
---|---|
getReminders(act, dueInterval, dueUnits)
|
Returns a list of reminders for a customer's patients for the customer associated with the supplied act.
|
getReminders(act, dueInterval, dueUnits, includeOverdue)
|
Returns a list of reminders for a customer's patients for the customer associated with the supplied act, optionally including overdue reminders.
|
getReminders(customer, dueInterval, dueUnits)
|
Returns a list of reminders for a customer's patients.
|
getReminders(customer, dueInterval, dueUnits, includeOverdue)
|
Returns a list of reminders for a customer's patients.
|
getDocumentFormReminder(act)
|
Returns a reminder associated with a Form act, if any. For forms linked to an invoice item, this If there are multiple reminders with the same due date, the reminder with the lesser Id will be used. For forms not linked to an invoice item that have a product with reminders, a reminder with the nearest due date to that of the form's start time will be returned. For forms that don't meet the above, null is returned.
|
getReminders(patient, date)
|
Returns all reminders for a patient starting on the specified date.
|
getReminders(patient, from, to)
|
Returns all reminders for a patient starting on or after from and before to.
|
getReminders(patient, productTypeName, date)
|
Returns all reminders for a patient starting on the specified date, having a product with the specified product types.
|
getReminders(patient, productTypeName, from, to)
|
Returns all reminders for a patient starting on or after from and before to, having a product with the specified product types.
|
Function | Description |
---|---|
accountId(supplier, location)
|
Returns the account identifier that a practice location uses when corresponding with a supplier. Examples
|
accountId(supplierId, location)
|
Returns the account identifier that a practice location uses when corresponding with a supplier. This form accepts the supplier identifier, and can be used when there is no other way to access the supplier details. Examples
|
Function | Description |
---|---|
user:format(user, style)
|
Formats a user name according to the specified style. Styles are configured for the practice via the Short User Name Format, Medium User Name Format and Long User Name Format options. Examples
|
OpenVPMS supports the XPath 1.0 functions and operators to perform expression evaluation within archetypes, macros, and reports.
By far the most commonly function is concat, used to concatenate strings. Hence concat('ab','34','ef') yields 'ab34ef'.
However, others are used. One terrifyingly complex example is that used to generate the customer's name. It is as follows:
concat(/details/companyName,substring(concat(/details/lastName,',',/details/firstName),0,number(not(/details/companyName))*string-length(concat(/details/lastName,',',/details/firstName))+1))
This returns the companyName if there is one, else lastName,firstName. It relies on the concatenation of two mutually exclusive strings, the first one being empty if the condition is false, the second one being empty if the condition is true. This is called "Becker's method", attributed to Oliver Becker.
In the above you can see the functions concat, substring, number, not, string-length, and the operators * and + being used.
Note also that using the jxpath function expr:ifempty() function provides a far simpler version of the above, for example:
expr:ifempty(openvpms:get(.,'product.entity.printedName'), openvpms:get(.,'product.entity.name'))
The table below shows the available functions. (Others are defined but are not relevant to our use.)
Note that except for sum() which returns a BigDecimal, all the functions that return a number (eg ceiling(), count() etc) return a Double. This can be ignored except when you use the functions in JasperReports. So the field [count(list:distinct(., "items.target.patient.entity.id"))] must be typed as java.lang.Double .
Also, the arguments cannot be JasperReports fields, but must be jxpath functions. Hence
[concat(product.entity.printedName,... ] will not work, but
[concat(openvpms:get(., 'product.entity.printedName'), .... ] will
boolean(e) | Converts object e to Boolean type. False values include numeric zero, empty strings, and empty node sets; other values are considered true. |
ceiling(e) | Returns the integer closest to infinity that is less than or equal to e. Examples: ceiling(5.9) returns 6; ceiling(-5.9) returns -5. |
concat(e1, e2, ...) | Concatenates the string values of its arguments and returns the result as a single string. |
contains(s1, s2) | True if string s1 contains s2. |
count(c) | Returns the number of nodes in the collection c as returned by some of the list: functions. Example: count(list:distinct(., "items.target.patient.entity.id")) |
false() | Returns the Boolean “false” value. |
floor(e) | Returns the integer closest to minus infinity that is greater than or equal to e. Examples: floor(5.9) returns 5; floor(-5.9) returns -6. |
normalize-space(s) | Returns the string s, except that all leading and trailing whitespace are removed, and all internal clumps of whitespace are replaced by single spaces. Note that a newline character counts as a whitespace character. |
not(e) | Returns the Boolean complement of the truth value of expression e: true if e is false, false if it is true. |
number(e) | Converts an expression e to a number. If e is not a valid number, you get the special numeric value NaN (not a number). If e is a Boolean value, you get 1 for true and 0 for false. |
round(e) | Returns the integer closest to the value of expression e. Values with a fractional part of 0.5 are rounded towards infinity. Examples: round(5.1) returns 5; round(5.5) returns 6; and round(-5.5) returns -5. |
starts-with(s1, s2) | True if string s1 starts with string s2. |
string(e) | Converts e to a string. |
string-length(s) | Returns the length of string s. |
substring(s, n1, n2) | Returns a substring of s starting at position n1 (counting from 1), and ending n2 characters later, or at the end of the string, whichever comes first. You can omit the third argument, and it will return the substring starting at position n1 and going through the end of s. For example, "substring('abcdefgh', 3, 4)" returns "cdef". |
substring-after(s1, s2) | If s2 is a substring of s1, returns the part of s1 after the first occurrence of s2; otherwise it returns the empty string. |
substring-before(s1, s2) | If s2 is a substring of s1, returns the part of s1 before the first occurrence of s2; otherwise it returns the empty string. |
sum(c) | Returns the sum, for each node collectionc, of the result of converting the string-values of the node to a number. |
translate(s1, s2, s3) | The result is a copy of string s1 with each occurrence of a character from string s2 replaced with the corresponding character from string s3. If s3 is shorter than s2, this function will delete from its result any characters from s2 that don't correspond to characters in s3 eg translate(s1, '
', '') will strip any newline charcters from s1 and translate(s1, '
', ' ') will translate any newline charcters to spaces |
true() | Returns the Boolean “true” value. |
Below are the operators used in XPath expressions. In the table below, e stands for any XPath expression.
e1+e2 | If e1and e2are numbers, their sum. |
e1-e2 | e1minus e2. |
e1*e2 | The product of e1and e2. |
e1 div e2 | If e1and e2are numbers, their quotient as a floating-point value. |
e1 mod e2 | The floating-point remainder of e1divided by e2. |
e1 = e2 | Tests to see if e1equals e2. |
e1 < e2 | Tests to see if e1is less than e2. You can't say e1< e2inside an attribute: the less-than sign must be escaped as "<". |
e1 <= e2 | Tests to see if e1is less than or equal to e2. |
e1 > e2 | Tests for greater-than. |
e1 >= e2 | Tests for greater or equal. |
e1 != e2 | Tests for inequality. |
e1 and e2 | True if both e1and e2are true. If e1is false, e2is not evaluated. |
e1 or e2 | True if either e1or e2is true. If e1is true, e2is not evaluated. |
/e | Evaluate estarting at the document node. For example, "/barge"selects the <barge>element that is the child of the document node. |
e1/e2 | The /operator separates levels in a tree. For example, "/barge/load"selects all <load>children of the <barge>element child of the document node. |
//e | Abbreviation for descendant-or-self::e. |
./e | Abbreviation for self::e. |
../e | Abbreviation for parent::e. |
@e | Abbreviation for attribute::e. |
e1|e2 | Selects the union of nodes that match e1and those that match e2. |
* | A wild-card operator; matches all nodes of the proper type for the context. For example, "*"selects all child elements of the context node, and "feet/@*"selects all attributes of the context node's <feet>children. |
e1[e2] | Square brackets enclose a predicate, which specifies an expression e2that selects nodes from a larger set e1.For example, in the XPath expression "para[@class='note']", the paraselects all <para>children of the context node, and then the predicate selects only the children that have an attribute class="note". Another example: "item[1]"would select the first <item>child of the context node. |
$e | The dollar sign indicates that the following name is a variable name. For example, in an XSLT script, if variable n is set to 357, <xsl:value-of select="$n"/>is expanded to the string "357". |
OpenVPMS supports a subset of the HL7 2.5 messaging standard, to enable transfer of clinical and administrative data to other applications.
The following Patient Administration messages are supported:
Message Type | Message Event | Description | Triggered | Direction |
---|---|---|---|---|
ADT | A01 | Admit/visit notification |
|
Outbound |
ADT | A03 | Discharge/end visit |
|
Outbound |
ADT | A08 | Update patient information |
|
Outbound |
ADT | A011 | Cancel admit/visit notification |
|
Outbound |
The following pharmacy messages are supported:
Message Type | Message Event | Description | Triggered | Direction |
---|---|---|---|---|
RDE | O11 | Pharmacy order message |
|
Outbound |
RDS | O13 | Pharmacy dispense message |
|
Inbound |
The following laboratory messages are supported:
Message Type | Message Event | Description | Triggered | Direction |
---|---|---|---|---|
ORM | O11 | General order message |
|
Outbound |
ORM | O11 | General order message |
|
Inbound |
When you click Help in the top menu line or press Alt-H, a help topics window is displayed.
In the standard system this provides four links, Help Topics (which links to this), An Introduction to OpenVPMS (which links to the Introduction section), OpenVPMS Concepts and Glossary (which links to the Concepts section) and User's Forum (which links to http://www.openvpms.org/category/forums/users/general).
You can modify these and add more topics by editing the file help.properties in <TOMCAT_HOME>\webapps\openvpms\WEB-INF\classes\localisation.
If you are going to provide Local Procedures, then this is how to provide links to them.
Note that you can provide a simple local procedures document by writing what you need with your favourite word processor and then creating a pdf (called say localProcs.pdf). You can place this in TOMCAT_HOME>\webapps\ROOT and the help.properties entry will be like:
help.topic.5.title = Local procedures
help.topic.5.url = http://localhost:8080/localProcs.pdf
Reminders may be configured to be exported during reminder processing instead of mailed.
This file can be either comma or tab-delimited, depending on the Practice configuration.
The file contains the following fields:
Field | Description |
---|---|
Customer Identifier | Corresponds to the id node of party.customerperson |
Customer Title | Corresponds to the title node of party.customerperson |
Customer First Name | Corresponds to the firstName node of party.customerperson |
Customer Initials | Corresponds to the initials node of party.customerperson |
Customer Surname | Corresponds to the lastName node of party.customerperson |
Company Name | Corresponds to the companyName node of party.customerperson |
Customer Street Address | Corresponds to the address node of contact.location |
Customer Suburb | Corresponds to the suburb node of contact.location |
Customer State | Corresponds to the state node of contact.location |
Customer Postcode | Corresponds to the postCode node of contact.location |
Customer Phone | Corresponds to the areaCode and telephoneNumber nodes of contact.phoneNumber |
Customer SMS | Corresponds to the areaCode and telephoneNumber nodes of the contact.phoneNumber that has Allow SMS checked. |
Customer Email | Corresponds to the emailAddress node of contact.email. |
Patient Identifier | Corresponds to the id node party.patientpet |
Patient Name | Corresponds to the name node of party.patientpet |
Patient Species | Corresponds to the species node of party.patientpet |
Patient Breed | Corresponds to the breed node of party.patientpet |
Patient Sex | Corresponds to the sex node of party.patientpet |
Patient Colour | Corresponds to the colour node of party.patientpet |
Patient Date Of Birth | Corresponds to the dateOfBirth node of party.patientpet |
Reminder Type Identifier | Corresponds to the id node of entity.reminderType |
Reminder Type Name | Corresponds to the name node of entity.reminderType |
Reminder Due Date | Corresponds to the endTime node of act.patientReminder |
Reminder Count | Corresponds to the reminderCount node of act.patientReminder |
Reminder Last Sent Date | Corresponds to the lastSent node of act.patientReminder |
Patient Weight | The last recorded weight of the patient |
Patient Weight Units | The units of the last recorded weight |
Patient Weight Date | The date when the weight was recorded |
Practice Location | Corresponds to the name of the Practice Location linked via practice node of party.customerperson |
OpenVPMS supports reporting using:
In order to create a report using any of these, you will need to know the names of the available fields.
The first step is to determine the template Type, from this you can get from the table below the name of the archetype. Note that three (those in CAPS) are not archetypes - see Special Reports below.
Template Type | Archetype |
---|---|
Appointment | act.customerAppointment |
Bank Deposit | act.bankDeposit |
Customer Account Balance | CUSTOMER_BALANCE |
Customer Counter Sale | act.customerAccountChargesCounter |
Customer Credit | act.customerAccountChargesCredit |
Customer Estimation | act.customerEstimation |
Customer Form | act.customerDocumentForm |
Customer Invoice | act.customerAccountChargesInvoice |
Customer Letter | act.customerDocumentLetter |
Customer Payment | act.customerAccountPayment |
Customer Refund | act.customerAccountRefund |
Customer Statement | act.customerAccountOpeningBalance |
Grouped Reminders | GROUPED_REMINDERS |
Message | act.userMessage |
Patient Form | act.patientDocumentForm |
Patient Image | act.patientDocumentImage |
Patient Letter | act.patientDocumentLetter |
Patient Medication Label | act.patientMedication |
Patient Visit | act.patientClinicalEvent |
Prescription | act.patientPrescription |
Problem | act.patientClinicalProblem |
Reminder Report | act.patientReminder |
Stock Adjustment | act.stockAdjust |
Stock Transfer | act.stockTransfer |
Supplier Credit | act.supplierAccountChargesCredit |
Supplier Delivery | act.supplierDelivery |
Supplier Form | act.supplierDocumentForm |
Supplier Invoice | act.supplierAccountChargesInvoice |
Supplier Letter | act.supplierDocumentLetter |
Supplier Order | act.supplierOrder |
Supplier Remittance | act.supplierAccountPayment |
Task | act.customerTask |
Till Balance | act.tillBalance |
Work in Progress Charges | WORK_IN_PROGRESS_CHARGES |
To determine the available fields to use in the report, looking at the corresponding archetypes (see Administration|Archetypes).
For example, the act.customerAppointment has the following nodes:
<node name="id" path="/id" type="java.lang.Long" hidden="true" readOnly="true" />
<node name="name" type="java.lang.String" path="/name" hidden="true" minCardinality="1" derived="true"
derivedValue="'Appointment'" />
<node name="customer" path="/participations" type="java.util.HashSet" minCardinality="1" maxCardinality="1"
filter="participation.customer" />
<node name="patient" path="/participations" type="java.util.HashSet" minCardinality="0" maxCardinality="1"
filter="participation.patient" />
<node name="appointmentType" path="/participations" type="java.util.HashSet" minCardinality="1" maxCardinality="1"
filter="participation.appointmentType" />
<node name="startTime" path="/activityStartTime" type="java.util.Date" minCardinality="1" />
<node name="endTime" path="/activityEndTime" type="java.util.Date" minCardinality="1" />
Each of these nodes may be used in reports as follows:
Node | JasperReports | Word MERGEFIELD/OpenOffice User Field | |
---|---|---|---|
Field | Expression Class | ||
id | $F{id} | java.lang.Long | id |
name | $F{name} | java.lang.String | name |
customer | $F{customer.entity.name} | java.lang.String | customer.entity.name |
patient | $F{patient.entity.name} | java.lang.String | patient.entity.name |
appointmentType | $F{appointmentType.entity.name} | java.lang.String | appointmentType.entity.name |
startTime | $F{startTime} | java.util.Date | startTime |
endTime | $F{endTime} | java.util.Date | endTime |
Where a node refers to an archetype (e.g. the customer node is a collection of participation.customer), you can use "." to drill down on the nodes of that archetype.
In the above, "customer.entity.name" means:
To flesh this out a little, let us consider the act.customerAccountChargesInvoice and the party.customerperson archetypes which display (in part) as follows:
If you look at the Path column you can see 8 types of entries as follows:
Type | Name | Path | Access via |
---|---|---|---|
Simple | amount | /total | total |
Simple/Detail | reference | /details/reference | reference |
Participation | location | /participations | location.entity.xxx |
SourceAct | items | /sourceActRelationships | items.target.xxx |
TargetAct | reverses | /targetActRelationships | reverses.source.xxx |
EntityLink | practice | /entityLinks | practice.target.xxx |
Contact | contacts | /contacts | [function] |
Classification | tax | /classifications | [function] |
Lookup | title | /details/title | title title.xxx |
Lookup Local | status | /status | status status.xxx |
In the above xxx indicates one nodes of the entity, target or source - eg location.target.name - the way to figure out whether it should be entity or target or source is to look at the archetype for the coupling archetype, eg participation.location, actRelationship.customerAccountInvoiceItem, entityLink.customerLocation.
The [function] entry indicates that for these nodes (which can have multiple occurances - eg a customer can have multiple contacts), one needs to use one of the party:get() functions to access the information.
For items which have their values constrained by either one of the Lookups (eg title) or a set of local settings in the archetype (eg status) then using the simple reference (eg title or status) will access the stored value (eg MRS or IN_PROGRESS). However, you can also used the .xxx form as follows:
The following fields are available to all reports1, and represent the current selections in the application, if any. Note that if on the Workflow|Scheduling or |Work Lists screen you select an appointment or task, these become the current aqppointment or task. However, the fact that the associated customer and patient details are displayed in the left panel does NOT mean that these become the current customer and patient. The current customer and patient are those displayed on the Customers|Information and Patients|Information screens.
These fields may also be used in XPath expressions, by prefixing the field name with a $ (i.e. they are available in expressions as variables).
Field | Archetype | Description |
---|---|---|
OpenVPMS.patient | party.patientpet | The current patient |
OpenVPMS.customer | party.customerperson | The current customer |
OpenVPMS.practice | party.organisationPractice | The current practice |
OpenVPMS.location | party.organisationLocation | The current practice location |
OpenVPMS.stockLocation | party.organisationStockLocation | The current stock location |
OpenVPMS.supplier | party.supplier* | The current supplier |
OpenVPMS.product | product.* | The current product |
OpenVPMS.deposit | party.organisationDeposit | The current deposit account |
OpenVPMS.till | party.organisationTill | The current till |
OpenVPMS.clinician | security.user | The current clinician |
OpenVPMS.user | security.user | The current user |
OpenVPMS.invoice | act.customerAccountChargesInvoice |
The current invoice. Only valid:
|
OpenVPMS.visit | act.patientClinicalEvent |
The visit for the current patient. Only valid:
|
OpenVPMS.appointment | act.customerAppointment |
The current appointment. Only valid:
|
OpenVPMS.task | act.customerTask | The current task. Only valid:
|
1 With the exception of JasperReports SQL sub-reports
Field | JasperReports | Word MERGEFIELD/OpenOffice User Field | |
---|---|---|---|
Field | Expression Class | ||
Practice name | $F{OpenVPMS.practice.name} | java.lang.String | OpenVPMS.practice.name |
Customer first name | $F{OpenVPMS.customer.firstName} | java.lang.String | OpenVPMS.customer.firstName |
Customer last name | $F{OpenVPMS.customer.lastName} | java.lang.String | OpenVPMS.customer.lastName |
Patient identifier | $F{OpenVPMS.patient.id} | java.lang.Long | OpenVPMS.patient.id |
Appointment date/time | $F{OpenVPMS.appointment.startTime} | java.util.Date | OpenVPMS.appointment.startTime |
User name | $F{OpenVPMS.user.name} | java.lang.String | OpenVPMS.user.name |
Description | Expression |
---|---|
Practice telephone | [party:getTelephone($OpenVPMS.practice)] |
Practice Location address | [party:getCorrespondenceAddress($OpenVPMS.location)] |
The current appointment start time as date with 'No current ...' check | [expr:var( 'OpenVPMS.appointment.startTime', 'No current appointment')] |
The current appointment start time as long date/time | [date:formatDateTime( $OpenVPMS.appointment.startTime, "long")] |
The current appointment start time like 'Monday 16 March at 9:45 AM' with 'No current …' check | [expr:if(boolean(expr:var( 'OpenVPMS.appointment.startTime')), date:format(expr:var( 'OpenVPMS.appointment.startTime'), "EEEE d MMMM 'at' K:mm a"),'No current appointment')] |
Note that the expr:var() function needs the name of the variable to test (eg 'OpenVPMS.appointment.startTime') whereas the date:format functions needs the actual variable, eg $OpenVPMS.appointment.startTime or
expr:var( 'OpenVPMS.appointment.startTime').
JasperReports queries support parameters using $P{parameter name} syntax.
To use Application Fields as parameters, reference the field within a parameter's Default Value Expression.
E.g. to pass the current customer identifier, create a parameter with the following settings:
Note that Class must be java.lang.String to avoid compilation errors, and that the parameter must be prompted for otherwise the $OpenVPMS.... will not be replaced with its current value.
If there is no current item (ie customer in the above case) then the parameter will be initialised to null. Hence if the parameter was set as follows:
then the SQL code should be like the following - note the comparison to the string "0" to handle the 'all' case:
and for the display of the parameter in the report, you might want to use an expression like the following so that the 'no current' case is clearly identified:
($P{customerID}==null)||($P{customerID}.compareTo("")==0)?"--NONE--":$P{customerID}
These are special reports that are generated by running queries that:
For CUSTOMER_BALANCE, the fields are:
Field |
Type |
Description |
---|---|---|
customer.objectReference | org.openvpms.component .business.domain.im.common .IMObjectReference | The customer reference |
customer.name | java.lang.String | The customer name |
balance | java.math.BigDecimal | The customer balance |
overdueBalance | java.math.BigDecimal | The customer overdue balance |
creditBalance | java.math.BigDecimal | The customer credit balance |
lastPaymentDate | java.util.Date | The customer's last payment date |
lastPaymentAmount | java.math.BigDecimal | The customer's last payment amount |
lastInvoiceDate | java.util.Date | The customer's last invoice date |
lastInvoiceAmount | java.math.BigDecimal | The customer's last invoice amount |
unbilledAmount | java.math.BigDecimal | The customer's unbilled amount |
For GROUPED_REMINDERS, the fields are:
Field | Type | Description |
---|---|---|
customer | party.customerperson | The reminder customer |
patient | party.patientpet | The reminder patient |
reminderType | entity.reminderType | The reminder type |
product | product.* | The reminder product |
clinician | security.user | The reminder clinician |
startTime | java.util.Date | The reminder start date |
endTime | java.util.Date | The reminder due date |
reminderCount | java.lang.Integer | The reminder count |
act | act.patientReminder | The reminder act |
For WORK_IN_PROGRESS_CHARGES, the report is supplied with Invoices, Credits and Counter charges (act.customerAccountChargesInvoice, act.customerAccountChargesCredit, and act.customerAccountChargesCounter archetypes respectively), that have an In Progress, Complete or On Hold status.
The available fields are therefore the nodes present in each of these archetypes.
JasperReports can evaluate xpath expressions by declaring a variable that uses the EVALUATE() function. This takes a single argument, the expression to evaluate e.g.:
EVALUATE("party:getAccountBalance(.)")
Report parameters can be accessed by the function by prefixing their names with $P. SQL-based JasperReports can also access the report fields, by prefixing their names with $F. e.g.:
EVALUATE("product:price($F.productId, $F.unitPrice, $P.includeTax)")
The above calls the product:price() function, supplying the values of the productId and unitPrice fields, and the includeTax parameter.
If you do need to create of modify documents and reports, the following should be useful.
OpenVPMS uses two 'vehicles' to generate printed (or pdf) output: OpenOffice (specifically its 'soffice' component) and JasperReports. Note that since OpenOffice can handle Microsoft Word documents, you can happily use Word rather than Open Office Writer to prepare letters and forms.
Which of these two is used depends on the document content set for the document template. (See Administration|Templates and Create/Edit Template.) If the document content is a .jrxml file, then JasperReports is used, if it is an .odt or .doc or .docx file then OpenOffice is used.
To create and edit the .jrxml files you use JasperSoft's Studio (which has superseded iReports but which can still be used if desired).
Note that JasperReports is used for any document that has more complex datasets such as invoices, statements, reports etc as these typically have multiple rows of data and require groupings and other data processing functions not available in OpenOffice. These are also rarely changed except during implementation.
OpenOffice is used for customer, supplier and patient documents. These are more likely to be modified and added to by the practice so a standard word processor editor is more appropriate.
The reports and forms/letters/documents get data in two ways:
The available field names are documented here; the available expressions here. In addition http://www.openvpms.org/document-merging-open-office-writer provides a useful 'cheat-sheet'.
Input parameters (commonly user input to letters) are entered via a parameter input screen.
Open Office Notes
The simplest way to generate a new .odt template is to use the sample templates included in the distribution. These call out almost all the available fields and you can copy the required fields from the sample document.
Microsoft Word Notes
The simplest way to generate a new .doc (or .docx) template is to use the sample templates included in the distribution. These call out almost all the available fields and you can copy the required fields from the sample document.
Note that although Word supports conditional fields, these are not implemented by the Open Office component used by OpenVPMS. Hence if you need conditional fields (eg to switch between 'him' and 'her' based on the patient's sex) then you will either need to use .odt templates. Alternatively you use the macro:eval function to run a macro that will return his or her depending on the sex.
Studio Notes
The following is not intended to be 'how to use Studio' but rather a set of notes that may help you when you are examining an existing report and trying to understand how it works.
Field names: For reports, when the datasource is the MySQL database, the field names are the names of the fields returned by the SQL query. Otherwise the datasource is a data collection and the field names are used the access the data collection - either via simple archetype dot notation (eg customer.entity.name), or via xpath expressions enclosed in square brackets (eg [party:getPartyFullName(.)] ). While the former feels quite normal - ie using $F{customer.entity.name} is a reasonable syntax, the latter appears peculiar - $F{[party:getPartyFullName(.)]} does not look like a normal way to access something. However, it works - what is happening is that the OpenVPMS code examines the name of the field, sees that it starts with '[' and ends with ']' and knows to interpret the contents as an xpath expression.
Sorting and Grouping: The Grouping facility assumes that the records are appropriately sorted. That is, if you use grouping to organise the output by patient/date/product type then the data must be sorted in this order - defining the groups does not do the sorting for you. For SQL based reports, you do the sorting in the SQL query. Otherwise you can do it either in the datasource specification (see below) or using the inbuilt facility - this is accessed via the Sort Fields entry in the Outline window
.
or alternatively via the Sorting tab in the Dataset and Query Dialog window (accessed via the icon)
Note that using the inbuilt sort, you can sort using fields (including ones that are xpath expressions) and variables, whereas in the datasource specification (see below) you can only sort by node names. Hence it is normal to do all the sorting here and omit all sort fields from the datasource specification.
Sub-reports: [Note that in contrast with 1.8 and earlier releases, sub-reports are now supported in all bands.] When using sub-reports you must specify the datasource for the sub-report in the main report. If the main report is using the MySQL database as the datasource, then the datasource part of the sub-report field in the main report will be set like the following:
However, if the main report is not using MySQL as the datasource (ie this is the sub-report for Customer Invoices or Customer Statements) then the datasource part of the sub-report field in the main report will be set like the following:
The datasource expression can also have a second argument as follows:
$P{dataSource}.getDataSource("items", new String[]{"target.patient.entity.name", "target.startTime"})
Here, the first argument "items" specifies the node in the report's data source (eg act.customerAccountChargesInvoice for an invoice), and hence this accesses the invoice's items. The strings in the second argument specify the names of the nodes to sort the data by. Note that these must be node names, they cannot be xpath expressions. Hence, as indicated earlier sorting is normally done in the report itself rather than in the getDataSource call.
Now the 'items' node of the act.customerAccountChargesInvoice archetype is a collection of actRelationship.customerAccountInvoiceItem archetypes, and in these the node named 'target' is the collection of act.customerAccountInvoiceItem archetypes which are the invoice line items.
Hence in the sub-report, the field names are target.xxx where xxx is the node within the act.customerAccountInvoiceItem archetype.
NOTE that the Subreport Expression contains the name of the Content of subreport - not the template name. Hence if you upate say the Invoice Items template with a modified content file (say InvoiceItems-BE-1.jxrml) then the Invoice template will not be able to find the items content because you have changed its name. That is, if you are editing the content of a subreport, you must not change the name of the content file.
Furthermore you should not have multiple templates that have content of the same name - IF one of the templates is a subreport. This is because when the subreport content is used when the report is run, it is highly likely that the wrong one will used since there are multiple content files with the same name.
Studio Preview: If you building a report that uses the MySQL datasource, then you can use Studio's Preview facility. To set this up you need to add the datasource.
Click the New Data Adaptor button:
First select a location to save the data adaptor XML file that is being built:
Press Next and select the Database JDBC Connection:
Then press Next to create a new 'Database JDBC connection' and then set the details as follows:
and then use the Driver Classpath tab to set the location of the mySQL connector jar:
You will need to adjust the C:\OpenVPMS\Current-release to match your <OPENVPMS-HOME> location.
Then press Finish.
You will find that when using the Preview facility that a common error is to leave the Data Adaptor set to its default of 'One Empty Record' - the symptom is that the report finds no data. If this happens, check that you have the data adaptor set correctly.
Email Letterhead Support
If reports and documents are set up to be printed on letterhead paper, then if they are emailed rather than printed, one needs a facility to insert the letterhead when the item is being emailed but not if it is being printed.
This is achieved through the IsEmail property which is supported for OpenOffice and JasperReports templates.
To use it in OpenOffice templates:
The "cartrophen first reminder.odt" and "desex first reminder.odt" templates in the release distribution demonstrate this.
To use it in JasperReports templates:
Any of the JasperReports in the release distribution illustrate this.
OpenVPMS supports wildcard searches as outlined in Wildcards
In many cases, wildcards are automatically added to Search criteria, so that any record with a name that contains the criteria is returned. E.g. searching for a diagnosis with oedema might return:
Blepharoedema |
Hypersensitivity (allergic) skin disorder - angioedema |
Myxoedema |
Pulmonary oedema |
Pulmonary oedema - cardiogenic |
Peripheral oedema |
Pulmonary oedema - non-cardiogenic |
Lymphoedema |
This is not true of customer, patient or product searches; here the default behaviour is to return records with names that start with the criteria. E.g. searching for a customer named Smith won't return records with the name Goldsmith. This can be changed by prefixing the criteria with a *. E.g. *smith
To make contains searches the default for customers, patients or products, the QueryFactory.properties file in <TOMCAT_HOME>/webapps/openvpms/WEB-INF/classes can be changed.
# # Uncomment the following two lines to make substring searches for customers the default. # Note that for large no.s of customers, this could affect performance # #party.customer* org.openvpms.web.component.im.query.CustomerQuery,contains=true #party.organisationOTC org.openvpms.web.component.im.query.CustomerQuery,contains=true # # Uncomment the following line to make substring searches for patients the default. # Note that for large no.s of patients, this could affect performance # #party.patient* org.openvpms.web.component.im.query.PatientQuery,contains=true # # Uncomment the following line to make substring searches for products the default. # #product.* org.openvpms.web.component.im.product.ProductQuery,contains=true
E.g., the following enables contains searches for all of them:
# # Uncomment the following two lines to make substring searches for customers the default. # Note that for large no.s of customers, this could affect performance # party.customer* org.openvpms.web.component.im.query.CustomerQuery,contains=true party.organisationOTC org.openvpms.web.component.im.query.CustomerQuery,contains=true # # Uncomment the following line to make substring searches for patients the default. # Note that for large no.s of patients, this could affect performance # party.patient* org.openvpms.web.component.im.query.PatientQuery,contains=true # # Uncomment the following line to make substring searches for products the default. # product.* org.openvpms.web.component.im.product.ProductQuery,contains=true
Note that:
This section contains topic relevant to setting up the system.
Note that if you do edit Administration|Organisation items (say to change an email address), then the new values will not become available until the next time you log on (because these organisation settings are fetched once at logon time to improve performance).
To get email to work you need to do the following:
Use Administration|Organisation|Mail Server to define at least one Mail Server by setting the various parameters (ie Mail Host, Port, Username, Password, and Connection Security).
For the Practice, set the default mail server. This is used if one is not set for a Practice Location, and is also used by the email-to-SMS gateway.
If a Practice Location needs its own Mail Server record, define one for it, and set this for the Practice Location. You may want to do this for situations where you are sending mail from a gmail address specific to the location, and you want to use the gmail smtp server for this gmail address (so that gmail does not raise warning flags).
Then define the Email contact(s), and if appropriate, add different ones for each applicable purpose (eg Billing, Correspondence, Reminder). Note that you can set the Email contacts at either the Practice or Location level. If you set them at the Location level, these will be used in preference to the those set at the Practice level.
If you are having problems, you can enable debugging by editing the mailSender bean in :
$TOMCAT_HOME/webapps/openvpms/WEB-INF/openvpms-web-app.xml
Change
<bean id="mailSender" class="org.openvpms.web.component.service.MailService" scope="prototype"/>
to
<bean id="mailSender" class="org.openvpms.web.component.service.MailService" scope="prototype">
<property name="javaMailProperties">
<props>
<prop key="mail.smtps.debug">true</prop>
</props>
</property>
</bean>
You can then look in the log at $TOMCAT_HOME/openvpms.log for the debug output.
You may want to run multiple OpenVPMS systems hosted in the one server, either for test and production systems, or in cases where multiple practices share the same server.
Note that if you are running multiple systems, it is important that you do not get confused about which system you are using. You can use a different screen colour for each system (see here for how to do this). You should also use different user login names and passwords (so as to prevent an unthinking person signing in to the wrong system). You should also set different database user names and passwords for each system.
One way to run multiple systems is of course to run each in its own virtual machine with these hosted on the one piece of hardware.
However, you can also host multiple systems on the one server. (Note however that if you use the ESCI facility, then you cannot host multiple systems on the one server.) The way to set this up is as follows:
1. install the first system as per the instructions in the readme.txt file.
2. for the next OpenVPMS system, install the OpenVPMS software in its own folder/directory, now referred to as <OPENVPMS_HOME_X>
3. the database access is defined in the file <OPENVPMS_HOME_X>/conf/hibernate.properties, specifically the three lines
hibernate.connection.url=jdbc:mysql://localhost:3306/openvpms
hibernate.connection.username=openvpms
hibernate.connection.password=openvpms
Hence for this OpenVPMS system we need to change the database name in the first line from openvpms to say openvpmsX. The user name and password should be changed to say openvpmsU and openvpmsP to decrease the likelihood of you getting confused and doing database operations on the wrong database.
To create the database, we need to edit the file <OPENVPMS_HOME_X>/db/create.sql so it reads as follows:
# # Script to create the openvpmsX database, and add a single user 'openvpms', # with all privileges # CREATE DATABASE `openvpmsX` /*!40100 DEFAULT CHARACTER SET utf8 */; GRANT ALL PRIVILEGES ON openvpmsX.* TO 'openvpmsU'@'localhost' IDENTIFIED BY 'openvpmsP' WITH GRANT OPTION; GRANT ALL PRIVILEGES ON openvpmsX.* TO 'openvpmsU'@'%' IDENTIFIED BY 'openvpmsP' WITH GRANT OPTION; COMMIT;
where "openvmsX" is the name of the new database, "openvpmsU" the user name, and "openvpmsP" the password. You can now create the database as per the readme.txt, ie do
> cd <OPENVPMS_HOME_X>/db
> mysql -u admin -p < createdb.sql
You can now populate the database as per the readme.txt steps, or you can simply dump the contents of the base openvpms database, and then restore this into the new openvpmsX database.
4. Finally we need to set up the Tomcat application. The easiest way to do this is to first copy the <TOMCAT_HOME>/webapps/openvpms directory to <TOMCAT_HOME>/webapps/openvpmsX. You then need to edit two files as follows:
a) in <TOMCAT_HOME>/webapps/openvpmsX/WEB-INF/web.xml, the webAppRootKey value needs to be set to "openvpmsX" as per the following
<!-- Needed by spring if you want to deploy app more than once as different name. Change value - to something unique --> <context-param> <param-name>webAppRootKey</param-name> <param-value>openvpmsX</param-value> </context-param>
b) edit <TOMCAT_HOME>/webapps/openvpmsX/WEB-INF/classes/hibernate.properties so it matches <OPENVPMS_HOME_X>/conf/hibernate.properties - see step 3 above.
Now restart Tomcat and you can then access the new system at http://localhost:8080/openvpmsX/app
5. Repeat steps 2-4 for each addition system that you require.
While most of the country dependent settings (such as tax and currency) can be set within the application itself (for example via Administration|Lookups|Country and Administration|Organisation|Practice), some setup needs to be done outside the application. This section documents the available adjustments.
If you use Rabies Tags in your country/jurisdiction, you will need to enable them. You will need to modify the party.patientPet archetype. The best way to do this is to modify the file party.patientpet.adl and then use Administration|Archetypes to import the modified version. This file is located in <OpenVPMS-Home>update\archetypes\org\openvpms\archetype\patient. You should of course save the modified file in a different place. Change the lines
<!-- For jurisdictions that use rabies tags, uncomment the following -->
<!--
<propertyMap name="archetype">
<property name="shortName" value="entityIdentity.rabiesTag"/>
</propertyMap>
-->
to
<!-- For we use rabies tags -->
<propertyMap name="archetype">
<property name="shortName" value="entityIdentity.rabiesTag"/>
</propertyMap>
Pet Tags are enabled in the standard release. If you do not use these in your country/jurisdiction, you can do the opposite of the above - ie comment out the pet tags, ie change
<propertyMap name="archetype">
<property name="shortName"
value="entityIdentity.petTag"/>
</propertyMap>
to
<!-- suppress pet tags
<propertyMap name="archetype">
<property name="shortName"
value="entityIdentity.petTag"/>
</propertyMap>
-->
Warning - this requires appropriate technical skills.
There are two ways of adjusting the propercasing facility: modifying the appropriate archetype to turn it off for a specific field; and editing the properties file. For the first, modify the appropriate node in the archetype to remove the propercase Assertion Descriptor. For the second, proceed as follows.
If you want to adjust how propercasing is performed on various words, you need to edit (after saving a copy) the file propercase.properties in the folder <TOMCAT HOME>\webapps\openvpms\WEB-INF\classes\localisation
Each line of the file has the format <keyword>.N = <string> where <keyword> is one of the keywords shown in the table below, N is a digit or digits, and <string> is the propercase version the string.
Keyword | Meaning | Examples |
---|---|---|
space | Strings that must appear surround by spaces. | space.1 = & |
spaceBefore | Strings that must appear with a space before them. | spaceBefore.1 = ( |
spaceAfter | Strings that must appear with a space after them. | spaceAfter.1 = ) spaceAfter.2 = . |
exceptions | Strings that are to be cased as given ignoring other case rules. |
exceptions.1 = von |
startsWith | Strings that must appear with the specified case at the start of a word. Where they appear, they force capitalisation of the next character in the word. | startsWith.1 = Mac startsWith.2 = Mc startsWith.3 = d' startsWith.4 = ( startsWith.5 = ` startsWith.6 = " |
contains | Strings that must appear with the specified case anywhere in a word. Where they appear, they force capitalisation of the next character in the word. | contains.1 = - contains.2 = ' contains.3 = 0 contains.4 = 1 contains.5 = 2 contains.6 = 3 etc for each digit |
endsWith | Strings that must appear with the specified case at the end of a word. | endsWith.1 = 's |
Hence the "startsWith.1 = Mac" would force Macaw to MacAw but for the "exceptions.153 = Macaw".
Note that you can functionally 'comment out' an entry by adding a leading # (or any other character) because this changes the key from say 'startsWith' to '#startsWith' which will not be recognised and will be ignored.
To get SMS to work, you first need to have email working. Having done that you can proceed with the SMS setup discussed below.
Two email-to-SMS gateways are supported out of the box:
To sign up, go to: http://www.smsglobal.com/en-au/solutions/signup_page.php
SMSGlobal allows you to send a small number of test messages for free after you register.
Now use Administration|Organisation|SMS Configuration: SMSGlobal Email2SMS
Clickatell allows you to send a small number of test messages for free after you register but sends a preformatted SMS until you pay.
To sign up:
After signing up, logon and click the 'Manage My Products' tab. Then click 'SMTP [Email to SMS]' link the click "Submit and Get API ID". The API ID is needed when configuring OpenVPMS.
Now use Administration|Organisation|SMS Configuration: Clickatell SMTP Connection
If you want to use another provider, then you should be able to do so using the Generic Gateway. You will need some knowledge of the gateway message format requirements and xpath expressions.
See Administration|Organisation|SMS Configuration: Generic Email Gateway
The SMSGlobal and Clickatell configuration screens hide the underlying detail, and indeed it is possible to configure an SMSGlobal or Clickatell gateway using the Generic configuration screens. Similarly, it is possible to create a tailored screen for another vendor. If you want to do this see here.
Having configured the sms gateway, you now need to set the practice to use the required gateway by using Administration|Organisation|Practice. Remember that you also need to set up email for the practice.
This page discusses the problems of localisation.
Time
The time in OpenVPMS is set from the time in the 'server' machine - ie the one running the MySQL database and Tomcat application. If the user and the server are all in the same timezone, then there is no problem.
However, if you are using a hosting service somewhere else, then it may be necessary to set the server time to match the local time where your users are. [For example the OpenVPMS demo system runs on a server with its time set to GMT. If you log onto this from say Sydney, then early in the day you will find that 'today' on the server (and thus the default date on invoices you create) will be yesterday your time because GMT is 10 hours behind AEST.]
If you have users spread across multiple timezones, then you will need to educate those in the 'non-home' time zones that theirs will be different. Thus if you have a practice spread across the NSW/Queensland border but with headquarters in NSW, in summer your Surfers Paradise branch will be running an hour behind. In this case you probably want to run the appointment schedule for the Surfers branch on local time, but the timestamps on any activity will be NSW, not Queensland time.
Date Format
The date format used is set in the <TOMCAT_HOME>/webapps/openvpms/WEB-INF/classes/org/openvpms/web/resource/localisation/messages.properties file (or the local version - see Language below).
Specifically, if you need to use say a US date format, then you need to adjust the settings in this file. Note that as well as the date formats, you can also adjust the numeric formats if needed.
Note that messages.properties settings affect the display on the screen. See below for Reports and Documents.
Language
OpenVPMS uses standard java i18n support so if you name your message properties file according to your locale settings, eg messages_fr.properties for France, then it will be used instead of the standard messages.properties.
Note that you can do minor language tweaks by editing the message.properties file. For example if you think that Organisation should really be Organization then you can change it here. [Though it would be better to create a message.properties-us file which includes both the spelling adjustments and the mm/dd/yy date formats.]
Reports and Documents
The standard reports and documents that are generated from Document Templates using JasperReports based content have been written to be locale sensitive for both dates and currencies. The locale used is that set for the user running the Tomcat application.
The letters and forms which use Open Office based content are not locale sensitive. The format of dates is as follows:
For most things you can change the display colour via the appropriate Administration screen - for example Users to set the clinician colour on the scheduling and work list screens. However, for three things you have to change files in the styles directory.
The files default.stylesheet and default.properties, are located in the <TOMCAT_HOME>/webapps/openvpms/WEB-INF/classes/style folder. It is sensible to make a backup copy of these files before editing them. Having saved the file(s) you will need to restart Tomcat to get the changes to have effect.
Note that it may be better to use the site over-ride facility - see Style - Site.
The colours are set as Hex codes - see the colour block at the bottom of this page.
Appointment/Task Status
You can change the colours used to highlight the status of appointments on the scheduling screen and tasks on the work list screen.
This can be done by overriding the following from default.properties:
# # Schedule status colours # schedule.pending.colour = '#ffffff' schedule.checkedin.colour = '#dbf714' schedule.inprogress.colour = '#ffcccc' schedule.completed.colour = '#ccffff' schedule.cancelled.colour = '#cc99ff' schedule.billed.colour = '#ccffcc' schedule.admitted.colour = '#ffcc99'
Screen Colours
If you want to change the standard OpenVPMS green background on the screens to another colour, in the file default.properties, look for the line containing theme.colour = '#339933'. Change the 339933 to the get the required colour. In the same part of the file, you will also find the title, button and selection colours as well as the colours used for the various medical history items.
Reminder Status
You can change the standard green/orange/red settings for the reminder not due/due/overdue status.
In the file default.properties, look for the line containing reminder.notdue.colour. This and the due and overdue lines set the colours. Change as required - but note that this will not change the colour of the red bell icon used to warn of an overdue reminder.
History Colours
If you want to change the colours of items on the Medical Records screen, in the file default.properties, look for the line containing "# Patient history colours" - the following lines set the colours for the different items.
Sample Colours
The sample block below shows the hex codes of the set of 'web-safe' colours. You will find 'OpenVPMS Green' in the second column, 10 down. See also this forum post for a tool to compare colours.
You can adjust the size of some column widths to suit local usage by overriding properties in the default.properties file. This is done by adding the property to a file named site.properties in the <TOMCAT_HOME>/webapps/openvpms/WEB-INF/classes/style folder. Having saved the file you will need to restart Tomcat to get the changes to have effect.
Medical Records: Clinician Width
You can change the size of the Clinician field (shown if you set the 'Show Clinician in History Items' option for the Practice). You may want to either increase this to show long names, or decrease it if you use just initials.
The width is controlled by the history.clinician.width setting.
Selector Field Width
You can change the size of the 'selector' fields - ie those with the binocular icon following them. You may want to do this if you find that the standard size is often too narrow to show all the data.
The width is controlled by the selector.width setting.
The default for label alignment is left so that things look as follows:
However, you can change this to right so that the above becomes:
To change the label alignment you need to edit default.properties, located in <TOMCAT_HOME>/webapps/openvpms/WEB-INF/classes/style/default.properties- and having edited it (after first taking a backup copy) you need to restart Tomcat to get the changes to have effect.
Go to the section on Label alignment. i.e.:
# # Label alignment. # This specifies the horizontal label alignment when: # . editing and viewing objects # . inputting report parameters # Possible values are 'left' and 'right' label.align = 'left'
and change 'left' to 'right'.
Note that it may be better to use the site over-ride facility - see Style - Site.
Instead of customising default.stylesheet and default.properties located in the <TOMCAT_HOME>/webapps/openvpms/WEB-INF/classes/style directory, you can place these changes into files named site.stylesheet and site.properties in the same directory, and they will override the default values. For site.stylesheet, enclose all styles in a root <stylesheet> element. For example:
<stylesheet> <style name="SomeLayout" type="nextapp.echo2.app.SplitPane"> <properties> <property name="resizable" value="false"/> <property name="separatorPosition" value="${SomeLayout.separatorPosition}"/> <property name="separatorHeight" value="0px"/> </properties> </style> <style name="SomeOtherLayout" type="nextapp.echo2.app.SplitPane"> <properties> </style> </stylesheet>
However, it is important to understand how the facility works. There are two problems:
1. styles cannot refer to styles defined in different stylesheets
So you can't have a base-name attribute in site.stylesheet that refers to a style defined in default.stylsheet. You need to either:
2. styles that have the same name must all be copied
If a style name appears for different classes (e.g. "default" for nextapp.echo2.app.Label, and nextapp.echo2.app.WindowPane), and you want to customise the Label version, you have to copy all other occurences of "default" as well. This is due to a bug/feature in the echo2 framework, that replaces all instances of a style when merging stylesheets.
This section covers areas that affect the performance of the system. It is anticipated that more topics will be added here in the future.
Medical Records Retrieval
The standard system can perform slowly when retrieving medical records for a patient with a long history. The apparent performance can be improved by limiting the number of visits retrieved for each query of the medical history. The standard system retrieves the complete medical history and then pages it out 20 visits at a time.
One can change this behaviour by limiting the number of visits on each query. If this is set to a number less than 20, then each 'page' of the medical history will display just that number of visits, and as you page through the history, a new query will be issued to get the next block of visits.
To limit the number of visits retrieved on each query to say 5, create a file called QueryFactory.properties containing one record as follows:
act.patientClinicalEvent org.openvpms.web.workspace.patient.history.PatientHistoryQuery,maxResults=5
This file needs to be placed in the folder <TOMCAT_HOME>/webapps/openvpms/WEB_INF/classes
Hardware Selection
As always, the general advice is to buy the fastest you can afford. Adding more memory will improve performance provided that MySQL is configured with large buffers. In fact, given a 64-bit operating system, there is no reason that the database cannot be held fully in memory.
MySQL
MySQL configuration is a black art. Parameters important for performance are:
innodb_buffer_pool_size - set as large as possible. Even if you have a small machine (ie one with only 4GB of memory) you should be able to set to this to 800MB. If you have a large machine (ie one with 16GB) then you should be able to set to to 8000MB or more.