Reference

Complete

This section contains reference information which will be useful to administrators, integrators, and others who need to know "what's under the hood".

Audit

Complete

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> 

Caching

Complete

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:

  • won't be seen immediately; or
  • won't be seen until the object is edited, assuming it is not in the second level cache
  • won't be seen till restart

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:

  • editing/loading archetypes via Administration - Archetypes
  • Tomcat restart

3. Lookups

Elements in this cache can be updated:

  • by editing via Administration - Lookups
  • when cache capacity is exceeded - least recently used elements are discarded
  • on low memory - elements will be shed from the cache to reclaim memory
  • by Tomcat restart

4. Appointments/tasks

Elements in this cache can be updated:

  • by editing via Workflow - Scheduling, Workflow - Tasks
  • when cache capacity is exceeded - least recently used elements are discarded
  • on low memory - elements will be shed from the cache to reclaim memory
  • by Tomcat restart

5. Current user context

This includes the current:

  • practice
  • practice location
  • stock location
  • schedule view
  • schedule
  • work list view
  • work list
  • customer
  • patient
  • clinician
  • user
  • till
  • deposit account

Refreshed by:

  • switching objects (e.g changing practice locations).
  • logging out/in

Functions - JXPath Extensions

Complete

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 GeneralDate, 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.

General Functions

Function Description

openvpms:get(object, node)
openvpms:get(object, node, default)

  • object: any archetype
  • node: the node name
  • default: the default value

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:

  • displayName - returns the display name of object's archetype
  • shortName - returns the archetype short name

Examples

  • openvpms:get(.,"displayName")
  • openvpms:get(.,"shortName")
  • openvpms:get(.,"name")
  • openvpms:get(., "customer.entity.id")

Object Reference Examples (the first two are equivalent)

  • openvpms:get(openvpms:get(.,"patient.objectReference"),"id")
  • openvpms:get(.,"patient.objectReference")/id
  • openvpms:get(openvpms:get(.,"patient.objectReference"),"id","No patient")

openvpms:set(object, node, value)

  • object: any archetype
  • node: the node name
  • value: the value to be set
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

  • openvpms:set(.,"deceasedDate",java.util.Date.new())
  • openvpms:set(.,"deceased",true())

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)
openvpms:lookup(object, node, default)

  • object: any archetype
  • node: the node name
  • default: the default
     

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

  • openvpms:lookup(., "title")
  • openvpms:lookup(., "patient.entity.species")
  • openvpms:lookup(openvpms:get(.,"patient.objectReference"),"breed")
  • openvpms:lookup(openvpms:get(.,"patient.objectReference"),"breed","No patient")

Date Functions

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)

  • dateTime: the date/time to add to
  • period: the period to add

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:

  • h = hour
  • d = day
  • w = week
  • m = month
  • q = quarter (i.e. 3 months)
  • y = year
  • s = start.  For weeks, s refers to Monday
  • e = end. For weeks, e refers to Friday

Examples

Given: dateTime = 4/10/2015 and date:now() = 30/6/2016 05:39

Expression Result
  • date:add(dateTime, "1m")
04/11/2015
  • date:add(dateTime, "-2m")
04/08/2015
  • date:add(dateTime, "0me")
31/10/2015
  • date:add(dateTime, "1ms")
01/11/2015
  • date:add(dateTime, "1ys")
01/01/2016
  • date:add(dateTime, "1q")
04/01/2016
  • date:add(dateTime, "-2w")
20/09/2015
  • date:add(dateTime, "-2w3d")
17/09/2015
  • date:add(dateTime, "-2w+3d")
23/09/2015
  • date:add(date:now(), "-12h")
29/6/2016 17:39
  • date:add(date:today(), "4h")
30/6/2016 04:00

date:format(dateTime, pattern)

  • dateTime: the date/time to format
  • pattern: the format pattern

Formats a date/time according to a SimpleDateFormat pattern.

Examples

Given: dateTime = 3:30:05pm 20/8/2012

Expression Result
  • date:format(dateTime, "MMM yyyy")
Aug 2012
  • date:format(dateTime, "MMMM yyyy")
August 2012
  • date:format(dateTime, "dd/MM/yy 'at' hh:mm:ss")
20/08/12 at 17:30:05
  • date:format(dateTime, "EEE, MMM d")
Mon, Aug 20

date:formatDate(date)

  • date: the date to format

Formats a date using the current locale's MEDIUM format.

Examples

Given: dateTime = 10:38 am 15/7/2013

Expression Result
  • date:formatDate(dateTime)
Jul 15, 2013

date:formatDate(date, style)

  • date: the date to format
  • style: the style to use. One of "short", "medium", or "long"

Formats a date using the specified format for the current locale.

Examples

Given: dateTime = 11:00am 15/7/2013

Expression Result
  • date:formatDate(dateTime, "short")
15/07/13
  • date:formatDate(dateTime, "medium")
15/07/2013
  • date:formatDate(dateTime, "long")
15 July 2013

date:formatTime(time)

  • time: the time to format

Formats a time using the current locale's MEDIUM format.

Examples

Given: dateTime = 3:30:05pm 20/8/2012

Expression Result
  • date:formatTime(dateTime)
3:30:05 PM

date:formatTime(time, style)

  • time: the time to format
  • style: the style to use. One of "short", "medium", or "long"

Formats a time using the specified format for the current locale.

Examples

Given: dateTime = 3:30:05pm 20/8/2012

Expression Result
  • date:formatTime(dateTime, "short")
3:30 PM
  • date:formatTime(dateTime, "medium")
3:30:05 PM
  • date:formatTime(dateTime, "long")
3:30:05 PM

date:formatDateTime(datetime)

  • datetime: the date-time to format
     

Formats a date-time using the MEDIUM format for the current locale.

Examples

Given: dateTime = 3:30:05pm 20/8/2012

Expression Result
  • date:formatDateTime(dateTime)
20/08/2012 3:30:05 PM

date:formatDateTime(datetime, style)

  • datetime: the date-time to format
  • style: the style to use. One of "short", "medium", or "long"

 

Formats a date-time using the specified format for the current locale.

Examples

Given: dateTime = 3:30:05pm 20/8/2012

Expression Result
  • date:formatDateTime(dateTime, "short")
20/08/12 3:30 PM
  • date:formatDateTime(dateTime, "medium")
20/08/2012 3:30:05 PM
  • date:formatDateTime(dateTime, "long")
20 August 2012 3:30:05 PM

date:formatDateTime(datetime, dateStyle, timeStyle)

  • datetime: the date-time to format
  • dateStyle: the style to use to format the date. One of "short", "medium", or "long"
  • timeStyle: the style to use to format the date. One of "short", "medium", or "long"

Formats a date-time using the specified date and time styles for the current locale

Examples

Given: dateTime = 9:58:12am 28/7/2013

Expression Result
  • date:formatDateTime(dateTime, "long", "short")
28 July 2013 9:58 AM

Expression Functions

Function Description

expr:concatIf(value1, value2)

  • value1: the first string
  • value2: the second string

Concatentates two strings, if both are not null/empty. If either is null/empty, returns an empty string.

Examples

  • expr:concatIf(' - ', /details/reason))

expr:concatIf(value1, value2, value3)

  • value1: the first string
  • value2: the second string
  • value3: the third string

Concatentates three strings, if all are not null/empty. If any is null/empty, returns an empty string.

Examples

  • expr:concatIf(' (', party:identities(), ')')

expr:if(condition, value)

  • condition: the condition to evaluate
  • value: the value to return if the condition is true

Evaluates the result of condition. If true, returns value, otherwise returns null.

Examples

  • expr:if(openvpms:get(.,'sex')='MALE','M')

expr:if(condition, trueValue, falseValue)

  • condition: the condition to evaluate
  • trueValue: the value to return if the condition is true
  • falseValue: the value to return if the condition is false

Evaluates the result of condition. If true, returns trueValue, otherwise returns falseValue.

Examples

  • expr:if(openvpms:get(.,'sex')='MALE','M', 'F')

expr:ifempty(value, elseValue)

  • value: the string value to return if it is non-null nor empty
  • elseValue: the string value to return if value is null or empty.

Returns the first argument if it is not null or empty, or the second argument if it is.

Examples

  • expr:ifempty($description, $name)
  • expr:ifempty(expr:concatIf($firstName, ' ', $lastName), $name)

expr:trim(value, maxLength)

  • value: the string
  • maxLength: the maximum length

Truncates value if its length exceeds maxLength, otherwise returns value unchanged.

  • expr:trim(concat(openvpms:lookup(.,'title'), ' ', /details/firstName, ' ', /details/lastName, expr:concatIf(' (', list:sortNamesOf('classifications'), ')')),255)

expr:var(name)

  • name: the variable name
     

Returns the value of the named variable if it exists, otherwise returns null.

Examples

  • expr:var("patient.name")

expr:var(name, value)

  • name: the variable name
  • value: the value to return if the variable doesn't exist

Returns the value of the named variable if it exists, otherwise returns value.

Examples

  • expr:var("patient", "There is no current patient")

List Functions

The following functions operate on lists of objects.

Function Description

list:join(objects, node)

  • objects: the objects
  • node: the node name

Iterates through a list of objects, joining the string value of the specified node, separated by commas.

  • list:join(openvpms:get(.,'classifications'), 'name')

 

list:join(objects, node, separator)

  • objects: the objects
  • separator: the value separator

Iterates through a list of objects, joining the string value of the specified node, separated by separator.

  • list:join(openvpms:get(.,'classifications'), 'name', '; ')

Example

  • list:join(openvpms:get(.,'patient.entity.identities')[archetypeId/shortName='entityIdentity.alias'], 'alias')

returns a comma separated list of the patient's aliases

list:names(objects)

  • objects: the objects

Concatenates object names, comma separated.

  • list:names(openvpms:get(., 'classifications'))

list:names(objects, separator)

  • objects: the objects
  • separator: the name separator

Concatenates object names, separated by separator

  • list:names(openvpms:get(., ''classifications'), '/')

list:sort(objects, node)

  • objects: the objects to sort
  • node: the node to sort on

Sorts objects on a node.

  • list:sort(openvpms:get(., 'classifications'), 'name)

list:sortNames(objects)

  • objects: the objects

Sorts and concatenates the names of the objects, separated by commas.

  • list:sortNames(openvpms:get(., 'classifications'), 'name)
     

list:sortNames(objects, separator)

  • objects: the objects
  • separator: the name separator

Concatenates object names, separated by separator

  • list:sortNames(openvpms:get(., ''classifications'), '; ')

list:sortNamesOf(node)

  • node: the collection node of the current object

Sorts and concatenate the object names of the specified node, separated by commas.

  • list:sortNamesOf('classifications')

list:values(object, node)

  • object: the object
  • node: the collection node of the current object. May be a composite node name

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(., 'items')
  • list:values($customer, 'patients.entity')
  • list:values($invoice, 'items.target.documents.target')

list:values(node)

  • node: the collection node of the current object. May be a composite node name

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:values('items')
  • list:values('patients.entity')
  • list:values($invoice, 'items.target.documents.target')

list:distinct(object, node)

  • object: the object
  • node: the collection node of the current object. May be a composite node name

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($invoice, 'items.target.patient.entity')
  • list:distinct($invoice, 'items.target.product.entity.id')

list:distinct(node)

  • node: the collection node of the current object. May be a composite node name

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.

  • list:distinct('items.target.clinician.entity')

 

Party Functions

The following functions operate on party archetypes, such as customers, patients, suppliers, and organisations.

Function Description

party:getPartyFullName(party)

  • party: any party archetype

Returns the full name of the specified party

Examples

  • party:getPartyFullName(openvpms:get(., "customer.entity"))

party:getPartyFullName(act)

  • act: any act archetype with a customer or patient node

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:getPartyFullName(.)

party:getPatientOwner(patient)

  • patient: any party.patient* archetype

Returns the owner of a patient.

Examples

  • party:getPatientOwner(openvpms:get(.,"patient.entity"))

party:getPatientOwner(act)

  • act: any act archetype with a patient node

 

Returns the owner of a patient associated with an act.
If a patient has had multiple owners, then the returned owner will be that whose ownership period encompasses the act start time. If there is no such owner, the returned owner will be that whose ownership began closest to the act start time.

Examples

  • party:getPatientOwner($visit)

party:getPatientCurrentOwner(act)

  • act: any act archetype with a patient node

Returns the current owner of a patient associated with an act.

Examples

  • party:getPatientCurrentOwner($visit)

party:setPatientInactive(patient)

  • patient: any party.patient* archetype

Flags a patient as being inactive.

Examples

  • party:setPatientInactive($patient)

party:setPatientDeceased(patient)

  • patient: any party.patient* archetype

Flags a patient as being deceased.

This also:

  • flags the patient as being inactive
  • sets the patient's deceasedDate node to the current date/time.

Examples

  • party:setPatientDeceased(openvpms:get(.,"patient.entity"))

party:setPatientDesexed(patient)

  • patient: any party.patient* archetype

Flags a patient as being desexed.

Examples

  • party:setPatientDeceased(openvpms:get(.,"patient.entity"))

party:getPreferredContacts(party)

  • party: any party.* archetype

Returns a formatted string of preferred contacts for a party.

Examples

  • party:getPreferredContacts($customer)
  • party:getPreferredContacts(openvpms:get(.,"supplier.entity"))

party:getBillingAddress(party)

  • party: any party.* archetype

Returns a formatted billing address for a party. This looks for:

  • a location contact with BILLING purpose; or if none found
  • the preferred location; or if none found
  • any location contact the party may have

Returns an empty string if there is no location contact.

Examples

  • party:getBillingAddress($customer)
  • party:getBillingAddress(openvpms:get(., "customer.entity"))

party:getBillingAddress(act)

  • act: any act archetype with a customer or patient node

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:getBillingAddress($visit)
  • party:getBillingAddress(.)

party:getCorrespondenceAddress(party)

  • party: any party.* archetype

Returns a formatted correspondence address for a party. This looks for:

  • a location contact with CORRESPONDENCE purpose; or if none found
  • the preferred location; or if none found
  • any location contact the party may have

Returns an empty string if there is no location contact.

Examples

  • party:getCorrespondenceAddress($customer)
  • party:getCorrespondenceAddress(openvpms:get(., "customer.entity"))

party:getCorrespondenceAddress(act)

  • act: any act archetype with a customer or patient node

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:getCorrespondenceAddress($visit)
  • party:getCorrespondenceAddress(.)

party:getTelephone(party)

  • party: any party.* archetype

Returns a formatted telephone number for a party. This looks for:

  • the preferred phone contact; or if none found
  • any available phone contact

Returns an empty string if there is no phone contact.

Examples

  • party:getTelephone($customer)
  • party:getTelephone(openvpms:get(., "customer.entity"))

party:getTelephone(act)

  • act: any act archetype with a customer or patient node

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:getTelephone($visit)
  • party:getTelephone(.)

party:getHomeTelephone(party)

  • party: any party.* archetype

Returns a formatted home telephone number for a party. This looks for:

  • the phone contact with HOME purpose; or if none found
  • the preferred phone contact; or if none found
  • any available phone contact

Returns an empty string if there is no phone contact.

Examples

  • party:getHomeTelephone($customer)
  • party:getHomeTelephone(openvpms:get(., "customer.entity"))

party:getHomeTelephone(act)

  • act: any act archetype with a customer or patient node

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:getHomeTelephone($visit)
  • party:getHomeTelephone(.)

party:getWorkTelephone(party)

  • party: any party.* archetype

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($customer)
  • party:getWorkTelephone(openvpms:get(., "customer.entity"))

party:getWorkTelephone(act)

  • act: any act archetype with a customer or patient node
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:getWorkTelephone($visit)
  • party:getWorkTelephone(.)

party:getMobileTelephone(party)

  • party: any party.* archetype

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($customer)
  • party:getMobileTelephone(openvpms:get(., "customer.entity"))

party:getMobileTelephone(act)

  • act: any act archetype with a customer or patient node
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:getMobileTelephone($visit)
  • party:getMobileTelephone(.)

party:getFaxNumber(party)

  • party: any party.* archetype

Returns a formatted fax number for a party.

Returns an empty string if there is no fax contact.

Examples

  • party:getFaxNumber($supplier)
  • party:getFaxNumber(openvpms:get(., "supplier.entity"))

party:getFaxNumber(act)

  • act: any act archetype with a customer or patient node
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:getFaxNumber($visit)
  • party:getFaxNumber(.)

party:getEmailAddress(party)

  • party: any party.* archetype

Returns a formatted email address for a party.

Returns an empty string if there is no email contact.

Examples

  • party:getEmailAddress($customer)
  • party:getEmailAddress(openvpms:get(., "customer.entity"))

party:getEmailAddress(act)

  • act: any act archetype with a customer or patient node
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:getEmailAddress($visit)
  • party:getEmailAddress(.)

party:getWebsite(party)

  • party: any party.* archetype

Returns the website for a party.

Returns an empty string if there is no website contact.

Examples

  • party:getWebsite($supplier)
  • party:getWebsite(party:getLetterheadContacts($OpenVPMS.location))

party:getContactPurposes(contact)

  • contact: any contact.* archetype
Returns a formatted string of contact purposes for a contact.

party:identities(party)

  • party: any party.* archetype
Returns a formatted string of a party's identities.

party:getAccountBalance(customer)

  • customer: a party.customerperson

Returns the account balance for a customer.

Examples

  • party:getAccountBalance($customer)
  • party:getAccountBalance(openvpms:get(.,"customer.entity"))

party:getAccountBalance(act)

  • act: any act archetype with a customer or patient node

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:getAccountBalance($invoice)
  • party:getAccountBalance(.)

party:getPatientReferralVet(patient)

  • patient: a party.patientpet

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($patient)
  • party:getPatientReferralVet(openvpms:get(., "patient.entity"))

party:getPatientReferralVet(act)

  • act: any act archetype with a patient node

 

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:getPatientReferralVet($visit)
  • party:getPatientReferralVet(.)

party:getPatientReferralVetPractice(patient)

  • patient: a party.patientpet

Returns the referral vet practice for a vet associated with the supplied patient, active at the current time.

Examples

  • party:getPatientReferralVetPractice($patient)
  • party:getPatientReferralVetPractice(openvpms:get(., "patient.entity"))

party:getPatientReferralVetPractice(act)

  • act: any act archetype with a patient node

Returns the referral vet practice for a vet associated with the supplied act's patient, active at the act's start time.

Examples

  • party:getPatientReferralVetPractice($visit)
  • party:getPatientReferralVetPractice(.)

party:getReferralVetPractice(vet, date)

  • vet:  a party.supplierVeterinarian
  • date: the date

Returns the referral vet practice for a vet, for the specified date.

party:getPatientAge(patient)

  • patient: a party.patientpet

Returns the age of the patient.
If the patient is deceased, the age of the patient when they died will be returned.

Examples

  • party:getPatientAge($patient)
  • party:getPatientAge(.,openvpms:get(., "patient.entity"))

party:getPatientMicrochip(patient)

  • patient: a party.patientpet

Returns a patient's microchip, or an empty string if none exists.
Note that if the patient has multiple microchips then the one with the highest identifier (ie the most recently added one) will be returned.

Examples

  • party:getPatientMicrochip($patient)
  • party:getPatientMicrochip(., openvpms:get(., "patient.entity"))

party:getPatientMicrochip(act)

  • act: any act archetype with a patient node

Returns the microchip for a patient linked to act.

Examples

  • party:getPatientMicrochip(.)
  • party:getPatientMicrochip($visit)

party:getPatientMicrochips(patient)

  • patient: a party.patientpet

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
045*610*874, 982 009 101 673 028

Examples

  • party:getPatientMicrochips($patient)
  • party:getPatientMicrochips(., openvpms:get(., "patient.entity"))

party:getPatientMicrochips(act)

  • act: any act archetype with a patient node

Returns the microchips for a patient linked to act.

Examples

  • party:getPatientMicrochips(.)
  • party:getPatientMicrochips($visit)

party:getMicrochip(party)

  • party: any party (typically party.patientpet)

Returns the most recent active entityIdentity.microchip for party.

Examples

  • openvpms:get(party:getMicrochip($patient), "identity")
  • openvpms:lookup(party:getMicrochip(.), "implantSite")
  • openvpms:get(party:getMicrochip(.), "implantDate")

party:getMicrochip(act)

  • act: any act archetype with a patient node

Returns the most recent active entityIdentity.microchip for a patient linked to act.

Examples

  • openvpms:lookup(party:getMicrochip($visit), "implantSite")
  • openvpms:get(party:getMicrochip($visit), "implantDate")

party:getPatientPetTag(patient)

  • patient: a party.patientpet

Returns the pet tag for a patient,or an empty string if none exists.

Examples

  • party:getPatientPetTag($patient)
  • party:getPatientPetTag(., openvpms:get(., "patient.entity"))

party:getPatientPetTag(act)

  • act: any act archetype with a patient node

Returns the pet tag for a patient linked to act.

Examples

  • party:getPatientPetTag(.)
  • party:getPatientPetTag($visit)

party:getPatientRabiesTag(patient)

  • patient: a party.patientpet

Returns the rabies tag for a patient,or an empty string if none exists.

Examples

  • party:getPatientRabiesTag($patient)
  • party:getPatientRabiesTag(., openvpms:get(., "patient.entity"))

party:getPatientRabiesTag(act)

  • act: any act archetype with a patient node

Returns the rabies tag for a patient linked to act.

Examples

  • party:getPatientRabiesTag(.)
  • party:getPatientRabiesTag($visit)

party:getPatientWeight(patient)

  • patient: a party.patientpet

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($patient)
  • party:getPatientWeight(., openvpms:get(., "patient.entity"))

party:getPatientWeight(act)

  • act: any act archetype with a patient node

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:getPatientWeight(.)
  • party:getPatientWeight($visit)

party:getWeight(patient)

  • patient: a party.patientpet

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)
  • party:getWeight(openvpms:get(. 'patient.entity'))

party:getWeight(patient, units)

  • patient: a party.patientpet
  • units: the weight units. One of 'KILOGRAMS', 'GRAMS', or 'POUNDS'

 

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($patient, 'KILOGRAMS')
  • party:getWeight(openvpms:get(. 'patient.entity', 'GRAMS'))
  • party:getWeight($patient, 'POUNDS')

party:getWeight(act)

  • act: any act archetype with a patient node

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(.)
  • party:getWeight($visit)

party:getWeight(act, units)

  • act: any act archetype with a patient node
  • units: the weight units. One of 'KILOGRAMS', 'GRAMS', or 'POUNDS'

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:getWeight(., 'KILOGRAMS')
  • party:getWeight($visit, 'POUNDS)

party:getPatientDesexStatus(patient)

  • patient: a party.patientpet

Returns the desex status of a patient.

Examples

  • party:getDesexStatus($patient)
  • party:getDesexStatus(., openvpms:get(., "patient.entity"))

party:getPatientDesexStatus(act)

  • act: any act archetype with a patient node

Returns the desex status of a patient linked to act.

Examples

  • party:getDesexStatus(.)
  • party:getDesexStatus($visit)

party:getPatientVisit(patient)

  • patient: a party.patientpet

Returns the most recent Visit (i.e. act.patientClinicalEvent) for a patient.

Examples

  • party:getPatientVisit($patient)
  • party:getPatientVisit(., openvpms:get(., "patient.entity"))
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

  • openvpms:get(party:getPractice(), "name"))
  • OpenVPMS.practice.name
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)

  • party: a customer or patient
  • interval:  the interval, relative to the current date/time
  • units: the interval units
Returns the Pending appointments for a customer or patient starting from now to the specified interval.

Examples

  • party:getAppointments($OpenVPMS.customer, 1, 'YEARS')
  • party:getAppointments(openvpms:get(.,'patient.entity'), 6, 'MONTHS')

party:getBpayID(party)

  • party: any party.*

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($customer)
  • party:getBpayID(., openvpms:get(., "customer.entity"))

party:getBpayID(act)

  • act: any act archetype with a customer or patient node
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:getBpayID($visit)
  • party:getBpayID(.)

party:getLetterheadContacts(location)

  • location: the practice location

Returns:

  • $OpenVPMS.location.letterhead.target.contacts.target if the Contact Source is set in the Letterhead record; or
  • $OpenVPMS.location if the Contact Source is not set

This function is normally invoked as:
     party:getLetterheadContacts($OpenVPMS.location)

Math Functions

Function Description
math:round(value, n)

Rounds value to n decimal places.

Examples

  • math:round(22 div 7, 3)
math:pow(value, n)

Returns valuen

Examples

  • math:pow(party:getWeight($patient), 2)
     

 

Macro Functions

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)

  • macro: the macro (i.e. its code and any prefix number) to expand enclosed in quotes

Expands the specified macro.
Use this form when:

  • expanding macros that don't need any context
  • expanding report macros that need access to the document that generated them. Note that in this case, this form is NOT equivalent to:
      macro:eval(macro, .)
    It is equivalent to:
      macro:eval(macro, /)

Examples

Given: the macro fn_bloggs contains the expression:
                  'Dr Joe Bloggs,  BSc(Vetbiol), BVMS(Hons)'

Expression Result
  • macro:eval("fn_bloggs")
Dr Joe Bloggs,  BSc(Vetbiol), BVMS(Hons)

macro:eval(macro, expr)

  • macro: the macro (i.e. its code and any prefix number) to expand enclosed in quotes
  • expr: an expression

Expands the specified macro, using expr to provide the context for the macro.

Examples

Given: the standard @qid macro which uses the dispensingVerb macro

Expression Result
  • macro:eval("3@qid",  openvpms:get('product.entity'))
Give 3 Tablet(s) Four Times a Day

History Functions

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)

  • patient: the patient

Returns all invoice item acts for a patient, ordered on descending start time.

  • history:charges(openvpms:get(.,'patient.entity'))

history:charges(patient, productTypeName)

  • patient: the patient
  • productTypeName: the name of the product type. May contain wildcards

Returns all invoice item acts for a patient, that have the specified product type name, ordered on descending start time.

  • history:charges(openvpms:get(.,'patient.entity'), 'Vaccinations')

history:charges(patient, date)

  • patient: the patient
  • date: the date
Returns all invoice item acts for a patient on the specified date, ordered on descending start time.
  • history:charges($patient, java.sql.Date.valueOf('2016-05-03'))

history:charges(patient, from, to)

  • patient: the patient
  • from: the start date/time, inclusive
  • to: the end date/time, exclusive

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(openvpms:get(.,'patient.entity'), java.sql.Date.valueOf('2016-01-01'), null)
  • history:charges(openvpms:get(.,'patient.entity'), date:add(java.util.Date.new(),'-60d'), null)

history:charges(patient, productTypeName, date)

  • patient: the patient
  • productTypeName: the name of the product type. May contain wildcards
  • date: the 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(openvpms:get(.,'patient.entity'), 'Vacc*', java.sql.Date.valueOf('2016-05-03'))

history:charges(patient, productTypeName, from, to)

  • patient: the patient
  • productTypeName: the name of the product type. May contain wildcards
  • from: the start date/time, inclusive
  • to: the end date/time, exclusive

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:charges(openvpms:get(.,'patient.entity'), 'Vaccinations', java.sql.Date.valueOf('2011-01-01'), java.sql.Date.valueOf('2016-01-01'))
  • history:charges(openvpms:get(.,'patient.entity'), 'Vaccinations', date:add(java.util.Date.new(),'-6m'), null)

history:medication(patient)

  • patient: the patient

Returns all medication acts for a patient, ordered on descending start time.

  • history:medication(openvpms:get(.,'patient.entity'))

history:medication(patient, productTypeName)

  • patient: the patient
  • productTypeName: the name of the product type. May contain wildcards

Returns all medication acts for a patient, that have the specified product type name, ordered on descending start time.

  • history:medication(openvpms:get(.,'patient.entity'), 'Vaccinations')

history:medication(patient, date)

  • patient: the patient
  • date: the date
Returns all medication acts for a patient on the specified date, ordered on descending start time.
  • history:medication($patient, java.sql.Date.valueOf('2010-01-01'))

history:medication(patient, from, to)

  • patient: the patient
  • from: the start date/time, inclusive
  • to: the end date/time, exclusive

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(openvpms:get(.,'patient.entity'), java.sql.Date.valueOf('2010-01-01'), date:today())
  • history:medication(openvpms:get(.,'patient.entity'), date:add(date:now(),'-12h', null)

history:medication(patient, productTypeName, date)

  • patient: the patient
  • productTypeName: the name of the product type. May contain wildcards
  • date: the 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(openvpms:get(.,'patient.entity'), 'Vacc*', date:today())

history:medication(patient, productTypeName, from, to)

  • patient: the patient
  • productTypeName: the name of the product type. May contain wildcards
  • from: the start date/time, inclusive
  • to: the end date/time, exclusive

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.

  • history:medication(openvpms:get(.,'patient.entity'), 'Vaccinations', date:add(date:now(),'-12h', null)

Product Functions

Function Description

price(product,taxExPrice)

  • product: a product/product identifier
  • taxExPrice: the tax-exclusive price

Returns the price for a product, given a tax-exclusive price.
The returned price will either include tax, or be rounded according to currency conventions, based on the practice Show Prices Tax Inclusive option
.

  • EVALUATE("product:price($F.productId, $F.unitPrice)")

price(product,taxExPrice, taxInclusive)

  • product: a product/product identifier
  • taxExPrice: the tax-exclusive price
  • taxInclusive: if true(), return the tax-inclusive price otherwise round it according to the currency rounding convention

Returns the price for a product, given a tax-exclusive price.

  • EVALUATE("product:price($F.productId, $F.unitPrice, $P.includeTax)")

round(price)

  • price: the price to round

Rounds a price according to the practice currency rounding convention.

  • EVALUATE(product:round("product:price($F.cost)"))

taxRate(product)

  • product: a product/product identifier

Returns the tax rate for a product, expressed as a percentage.

  • EVALUATE(product:taxRate("product:taxRate($F.productId)"))

Reminder Functions

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)

  • act: any act archetype with a customer node
  • dueInterval: the due interval, relative to the current date
  • dueUnits: the due interval units

Returns a list of reminders for a customer's patients for the customer associated with the supplied act.

  • reminder:getReminders(., 3, "MONTHS")

getReminders(act, dueInterval, dueUnits, includeOverdue)

  • act: any act archetype with a customer node
  • dueInterval: the due interval, relative to the current date
  • dueUnits: the due interval units
  • includeOverdue: if true, include overdue reminders

Returns a list of reminders for a customer's patients for the customer associated with the supplied act, optionally including overdue reminders.

  • reminder:getReminders(., 1, "YEARS", true())

getReminders(customer, dueInterval, dueUnits)

  • customer: the customer
  • dueInterval: the due interval, relative to the current date
  • dueUnits: the due interval units

Returns a list of reminders for a customer's patients.

  • reminder:getReminders(., 3, "MONTHS")

getReminders(customer, dueInterval, dueUnits, includeOverdue)

  • customer: the customer
  • dueInterval: the due interval, relative to the current date
  • dueUnits: the due interval units
  • includeOverdue: if true, include overdue reminders

Returns a list of reminders for a customer's patients.

  • reminder:getReminders(., 3, "MONTHS")

getDocumentFormReminder(act)

  • act: an act.patientDocumentForm act

 

Returns a reminder associated with a Form act, if any.

For forms linked to an invoice item, this
uses the invoice item to get the reminder. If there are multiple reminders for the invoice item, the one with the nearest due date will be returned.

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.

  • reminder:getDocumentFormReminder(.)

getReminders(patient, date)

  • patient: the patient
  • date: the date

Returns all reminders for a patient starting on the specified date.
Reminders may be IN_PROGRESS, COMPLETED, or CANCELLED.

  • reminder:getReminders(openvpms:get(., 'patient.entity'), openvpms:get(. 'startTime'))

getReminders(patient, from, to)

  • patient: the patient
  • from: the start date/time, inclusive
  • to: the end date/time, exclusive

Returns all reminders for a patient starting on or after from and before to.
Reminders may be IN_PROGRESS, COMPLETED, or CANCELLED.

  • reminder:getReminders(openvpms:get(., 'patient.entity'), date:add(date:today,'-1m'),date:today())

getReminders(patient, productTypeName, date)

  • patient: the patient
  • productTypeName: the product type name. May contain wildcards
  • date: the date

Returns all reminders for a patient starting on the specified date, having a product with the specified product types.
Reminders may be IN_PROGRESS, COMPLETED, or CANCELLED.

  • reminder:getReminders(openvpms:get(., 'patient.entity'), 'Checkup', openvpms:get(. 'startTime'))
  • reminder:getReminders(openvpms:get(., 'patient.entity'), 'Vacc*', openvpms:get(. 'startTime'))

getReminders(patient, productTypeName, from, to)

  • patient: the patient
  • productTypeName: the product type name. May contain wildcards
  • from: the start date/time, inclusive
  • to: the end date/time, exclusive

Returns all reminders for a patient starting on or after from and before to, having a product with the specified product types.
Reminders may be IN_PROGRESS, COMPLETED, or CANCELLED.

  • reminder:getReminders(openvpms:get(., 'patient.entity'), 'Checkup', date:add(date:today,'-30d'), date:add(date:today(),'30d'))
  • reminder:getReminders(openvpms:get(., 'patient.entity'), 'Vacc*', date:add(date:today,'-30d'), date:add(date:today(),'30d'))

 

Supplier Functions

Function Description
accountId(supplier, location)
  • supplier: a party.supplierorganisation
  • location: the practice location

Returns the account identifier that a practice location uses when corresponding with a supplier.

Examples

  • supplier:accountId(openvpms:get(., 'supplier.entity), $OpenVPMS.location)
accountId(supplierId, location)
  • supplierId: the supplier identifier
  • location: the practice 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

  • supplier:accountId(1234567, $OpenVPMS.location)

 

User Functions

Function Description
user:format(user, style)
  • user: a user/user identifier
  • style: the style to use. One of "short", "medium", or "long"

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

  • user:format(openvpms:get(., 'clinician.entity'), 'long')
  • EVALUATE(user:format($F.clinician_id, 'medium'))
  • concat($nl,"Regards, ",$user.firstName,$nl,user:format($user, 'long'))

 

 

Functions - XPath

Complete

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, '&#xA;', '') will strip any newline charcters from s1
and
translate(s1, '&#xA;', ' ') 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 &lt; 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 "&lt;".
e1 &lt;= e2 Tests to see if e1is less than or equal to e2.
e1 &gt; e2 Tests for greater-than.
e1 &gt;= 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".

HL7

Complete

OpenVPMS supports a subset of the HL7 2.5 messaging standard, to enable transfer of clinical and administrative data to other applications.

Patient Administration Messages

The following Patient Administration messages are supported:

Message Type Message Event Description Triggered Direction
ADT A01 Admit/visit notification
  • When an appointment changes status from Pending to In Progress, Admitted, or Checked In
Outbound
ADT A03 Discharge/end visit
  • When an appointment changes status to Completed.
  • When an invoice is finalised without the patient being checked-in
Outbound
ADT A08 Update patient information
  • When a patient is saved
  • When a patient weight record is saved or deleted
  • When a patient Allergy alert is saved or deleted
  • When product ordered via a Pharmacy is invoiced, and the patient isn't currently admitted
Outbound
ADT A011 Cancel admit/visit notification
  • When an appointment status changes to Cancel after being In Progress, Admitted, or Checked In
  • When Check-In is cancelled
Outbound

 

Pharmacy Messages

The following pharmacy messages are supported:

Message Type Message Event Description Triggered Direction
RDE O11 Pharmacy order message
  • When a product linked to a Pharmacy is invoiced
  • When an ordered product quantity changes. This generates an amendment
  • When the item associated with an ordered product is deleted. This generates a cancellation.
Outbound
RDS O13 Pharmacy dispense message
  • When a pharmacy product is dispensed
Inbound

 

Laboratory Messages

The following laboratory messages are supported:

Message Type Message Event Description Triggered Direction
ORM O11 General order message
  • When a patient Investigation is created during invoicing, and the Investigation Type is linked to a Laboratory
  • When the item associated with an ordered Investigation is deleted. This generates a cancellation.
Outbound
ORM O11 General order message
  • When a Laboratory cancels an order.
Inbound

 

 

Help Topics

Complete

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

Reminder Export format

Complete

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

Report fields

Complete

OpenVPMS supports reporting using:

  • JasperReports - these are created using Jaspersoft Studio
  • Microsoft Word documents with MERGEFIELDs
  • OpenOfffice documents with User Fields

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:

  1. retrieve the object associated with "customer.entity"; and
  2. return its name node

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:

  • title.code -> MRS
  • title.name -> Mrs
  • title.id -> 123  (the id of the lookup)
  • title.shortName -> lookup.personTitle (the archetype)
  • title.displayName -> the display name of the lookup
  • status.code -> IN_PROGRESS
  • status.name -> In Progress

Application Fields

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:

  • if in checkin, consult, checkout workflows
  • if an invoice is selected in the charges workspace
OpenVPMS.visit act.patientClinicalEvent

The visit for the current patient. Only valid:

  • if a patient visit is selected
  • if in the checkin, consult, or checkout workflow
OpenVPMS.appointment act.customerAppointment

The current appointment. Only valid:

  • when checking, consulting or checking out using an appointment
  • if an appointment is selected
OpenVPMS.task act.customerTask The current task. Only valid:
  • when consulting or checking out using a task
  • if a task is selected

1 With the exception of JasperReports SQL sub-reports

Field Samples
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

Expression Samples

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').

Application Fields as Report Parameters

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:

  • Name: customerId
  • Class: java.lang.String
  • Is For Prompting: Yes
  • Default Value Expression: "$OpenVPMS.customer.id"

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}

 

Special Reports

 

These are special reports that are generated by running queries that:

  • derive values
  • return partial objects
  • return multiple objects

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 Expressions

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.

Reports and Documents

Complete

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:

  • Reports (ie things run from the Reporting|Reports menu) are all handled by JasperReports. These MUST have their Report Type set to 'Report' in their document template. The report parameters (if any) are passed as 'Parameters' and the 'datasource' is set to access the MySQL database.  The report then uses SQL to extract the required information from the MySQL database. Selection criteria and other parameters are entered via a report criteria screen.
     
  • Forms, letters, and system documents & reports (ie invoices & statements etc) may be handled by either JasperReports (for the complicated ones) or OpenOffice. For these the Report Type in their document templates will always be other than 'Report', eg 'Customer Invoice', 'Patient Letter', 'Customer Statement' etc. In both cases an appropriate dataset is made available - and this is determined by the Report Type. For OpenOffice (or M/S Word) this is accessed via merge fields, ie the facility that is commonly used in word processors to do mail merge. For JasperReports, the 'datasource' is set to access this dataset. In both cases the field names are the same. Note that the term 'field name' is a little misleading - as well as things that look like names such as customer.entity.name and patient.entity.species, you can use more complicated expressions like [openvpms:get(party:getPatientOwner(.),'lastName')] and [macro:eval('standardConditions')]. Expressions are always enclosed by square brackets.

    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:

  1. Create a new field by going Insert -> Fields -> Other -> User Field
  2. In Name, enter "IsEmail" (without quotes)
  3. In Value, enter "unset" (without quotes)
  4. Click on the green tick
  5. Create a section to display when printing by going Insert ->Section
  6. Name it "Print Letterhead"
  7. Tick the Hide box
  8. Enter: IsEmail == "true" in the With Condition box
  9. Click OK
  10. Repeat 5-9 for a section named "Email Letterhead" but enter IsEmail == "false" in the With Condition Box.

The "cartrophen first reminder.odt" and "desex first reminder.odt" templates in the release distribution demonstrate this. 

To use it in JasperReports templates:

  1. Add a Boolean parameter, IsEmail
  2. For elements that should only be displayed when emailing, set their "Print When Expression" expression to: $P{IsEmail}.equals(Boolean.TRUE)
  3. For elements that should only be displayed when printing/previwing, set their "Print When Expression" expression to: $P{IsEmail}.equals(Boolean.FALSE)

Any of the JasperReports in the release distribution illustrate this.

Searching

Complete

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:

  • Enabling customers or patient searches could adversely affect performance. In the worst case scenario, every customer or patient will be scanned for matches.
  • Any change won't be picked up until either the web application or Tomcat is restarted.
  • The change will be need to be re-applied each time the web application is deployed.

 

Setup

Complete

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).

Email

Complete

 

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.

Multiple systems

Complete

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.

Other countries/jurisdictions

Complete

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.

Rabies Tags

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

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>
    -->

 

 

Propercasing

Complete

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
exceptions.2 =  van
exceptions.3 =  de
exceptions.4 =  la
exceptions.5 =  da
exceptions.6 =  di
exceptions.7 =  PO Box
exceptions.8 =  La Trobe
exceptions.9 =  Macquarie
exceptions.10 =  GPO
exceptions.14 =  1st
exceptions.15 =  2nd
exceptions.16 =  3rd
...
exceptions.22 =  9th
exceptions.23 =  0th
exceptions.24 =  ADEC
exceptions.26 =  ALB
exceptions.27 =  ALP
exceptions.28 =  ALT
exceptions.29 =  APTT
exceptions.30 =  AST
exceptions.31 =  AVID
...
exceptions.153 = Macaw
exceptions.154 = Mackeral

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.

SMS

Complete

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.

Times, Dates and Language

Complete

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:

  • With User Fields like 'patient.entity.dateOfBirth' and 'startTime', these return the date as a string from OpenVPMS and the format of this date depends on the messages.properties date format setting.
  • With Date Fields, these need to be explicitly formatted as you require.

Style - Colours

Complete

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.

Style - Column Widths

Complete

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.

Style - Labels

Complete

 

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.

Style - Site

Complete

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:

  • incorporate the original style in the custom style
  • copy the original style to site.stylesheet

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.

Raised as https://openvpms.atlassian.net/browse/OVPMS-1387

Tuning

Complete

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.