org.openvpms.web.workspace.workflow.checkin.CheckInWorkflowTestCase.java Source code

Java tutorial

Introduction

Here is the source code for org.openvpms.web.workspace.workflow.checkin.CheckInWorkflowTestCase.java

Source

/*
 * Version: 1.0
 *
 * The contents of this file are subject to the OpenVPMS License Version
 * 1.0 (the 'License'); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.openvpms.org/license/
 *
 * Software distributed under the License is distributed on an 'AS IS' basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Copyright 2014 (C) OpenVPMS Ltd. All Rights Reserved.
 */

package org.openvpms.web.workspace.workflow.checkin;

import nextapp.echo2.app.event.WindowPaneListener;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Before;
import org.junit.Test;
import org.openvpms.archetype.rules.act.ActStatus;
import org.openvpms.archetype.rules.finance.account.CustomerAccountArchetypes;
import org.openvpms.archetype.rules.patient.PatientArchetypes;
import org.openvpms.archetype.rules.product.ProductArchetypes;
import org.openvpms.archetype.rules.workflow.ScheduleArchetypes;
import org.openvpms.archetype.rules.workflow.ScheduleTestHelper;
import org.openvpms.archetype.test.TestHelper;
import org.openvpms.component.business.domain.im.act.Act;
import org.openvpms.component.business.domain.im.act.FinancialAct;
import org.openvpms.component.business.domain.im.common.Entity;
import org.openvpms.component.business.domain.im.common.IMObject;
import org.openvpms.component.business.domain.im.party.Party;
import org.openvpms.component.business.domain.im.product.Product;
import org.openvpms.component.business.domain.im.security.User;
import org.openvpms.component.business.service.archetype.helper.ActBean;
import org.openvpms.component.business.service.archetype.helper.EntityBean;
import org.openvpms.web.component.app.Context;
import org.openvpms.web.component.app.LocalContext;
import org.openvpms.web.component.im.doc.DocumentTestHelper;
import org.openvpms.web.component.im.edit.EditDialog;
import org.openvpms.web.component.im.edit.SaveHelper;
import org.openvpms.web.component.im.layout.DefaultLayoutContext;
import org.openvpms.web.component.im.layout.LayoutContext;
import org.openvpms.web.component.im.query.BrowserDialog;
import org.openvpms.web.echo.dialog.PopupDialog;
import org.openvpms.web.echo.error.ErrorHandler;
import org.openvpms.web.echo.help.HelpContext;
import org.openvpms.web.workspace.customer.charge.AbstractCustomerChargeActEditorTest;
import org.openvpms.web.workspace.customer.charge.CustomerChargeActItemEditor;
import org.openvpms.web.workspace.customer.charge.CustomerChargeTestHelper;
import org.openvpms.web.workspace.customer.charge.TestChargeEditor;
import org.openvpms.web.workspace.patient.charge.VisitChargeItemEditor;
import org.openvpms.web.workspace.patient.visit.VisitEditorDialog;
import org.openvpms.web.workspace.workflow.WorkflowTestHelper;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.openvpms.archetype.test.TestHelper.getDatetime;
import static org.openvpms.web.test.EchoTestHelper.fireDialogButton;
import static org.openvpms.web.workspace.workflow.WorkflowTestHelper.createAppointment;
import static org.openvpms.web.workspace.workflow.WorkflowTestHelper.createWorkList;

/**
 * Tests the {@link CheckInWorkflow}.
 *
 * @author Tim Anderson
 */
public class CheckInWorkflowTestCase extends AbstractCustomerChargeActEditorTest {

    /**
     * The patient.
     */
    private Party patient;

    /**
     * The customer.
     */
    private Party customer;

    /**
     * The clinician.
     */
    private User clinician;

    /**
     * The context to pass to the workflow.
     */
    private Context context;

    /**
     * The work list.
     */
    private Party workList;

    /**
     * Tracks errors logged.
     */
    private List<String> errors = new ArrayList<String>();

    /**
     * Tests the check-in workflow when launched from an appointment with no patient.
     */
    @Test
    public void testCheckInFromAppointmentNoPatient() {
        Date startTime = TestHelper.getDatetime("2013-01-01 09:00:00");
        Date arrivalTime = TestHelper.getDatetime("2013-01-01 08:50:00"); // arrived early
        Act appointment = createAppointment(startTime, customer, null, clinician);

        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setPatient(patient); // need to pre-set patient and work list
        workflow.setWorkList(workList); // so they can be selected in popups
        workflow.setArrivalTime(arrivalTime);
        workflow.start();

        // as the appointment has no patient, a pop should be displayed to select one
        workflow.selectPatient(patient);

        // select the work list and verify a task has been created.
        workflow.selectWorkList(workList, customer, patient);

        // add the patient weight
        workflow.addWeight(patient, BigDecimal.valueOf(10), clinician);

        workflow.printPatientDocuments(PopupDialog.SKIP_ID);

        // edit the clinical event
        PopupDialog eventDialog = workflow.editVisit();
        fireDialogButton(eventDialog, PopupDialog.OK_ID);
        workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);

        // verify the workflow is complete
        workflow.checkComplete(true, customer, patient, context);
    }

    /**
     * Tests the check-in workflow when launched from an appointment with a patient.
     * <p/>
     * No patient selection dialog should be displayed.
     */
    @Test
    public void testCheckInFromAppointmentWithPatient() {
        Date startTime = TestHelper.getDatetime("2013-01-01 09:00:00");
        Date arrivalTime = TestHelper.getDatetime("2013-01-01 09:33:00"); // arrived late
        Act appointment = createAppointment(startTime, customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setArrivalTime(arrivalTime);
        runCheckInToVisit(workflow);

        // edit the clinical event
        PopupDialog eventDialog = workflow.editVisit();
        fireDialogButton(eventDialog, PopupDialog.OK_ID);
        workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);
        workflow.checkComplete(true, customer, patient, context);
    }

    /**
     * Verifies that a new patient can be created if the appointment doesn't have one.
     */
    @Test
    public void testCreatePatient() {
        Act appointment = createAppointment(customer, null, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setWorkList(workList); // need to pre-set work list so it can be selected in popup
        workflow.start();

        // create the new patient
        BrowserDialog<Party> dialog = workflow.getSelectionDialog();
        fireDialogButton(dialog, BrowserDialog.NEW_ID);
        EditDialog editDialog = workflow.editPatient("Fluffy");
        Party newPatient = (Party) editDialog.getEditor().getObject();
        fireDialogButton(editDialog, PopupDialog.OK_ID);

        // verify the patient has been created and is owned by the customer
        workflow.checkPatient(newPatient, customer);

        workflow.selectWorkList(workList, customer, newPatient);

        workflow.addWeight(newPatient, BigDecimal.ONE, clinician);

        workflow.printPatientDocuments(PopupDialog.SKIP_ID);

        PopupDialog eventDialog = workflow.editVisit();
        fireDialogButton(eventDialog, PopupDialog.OK_ID);
        workflow.checkEvent(newPatient, clinician, ActStatus.IN_PROGRESS);

        workflow.checkComplete(true, customer, newPatient, context);
    }

    /**
     * Verify that the workflow cancels if a new patient is created but editing cancelled via the cancel button.
     */
    @Test
    public void testCancelCreatePatient() {
        Act appointment = createAppointment(customer, null, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.start();

        BrowserDialog<Party> dialog = workflow.getSelectionDialog();
        fireDialogButton(dialog, BrowserDialog.NEW_ID);
        EditDialog editDialog = workflow.editPatient("Fluffy");
        Party patient = (Party) editDialog.getEditor().getObject();
        fireDialogButton(editDialog, PopupDialog.CANCEL_ID);
        assertNull(get(patient));
        workflow.checkComplete(false, null, null, context);
    }

    /**
     * Verify that the workflow cancels if a new patient is created but editing cancelled via the 'user close' button.
     */
    @Test
    public void testCancelCreatePatientByUserClose() {
        Act appointment = createAppointment(customer, null, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.start();

        BrowserDialog<Party> dialog = workflow.getSelectionDialog();
        fireDialogButton(dialog, BrowserDialog.NEW_ID);
        EditDialog editDialog = workflow.editPatient("Fluffy");
        Party patient = (Party) editDialog.getEditor().getObject();
        editDialog.userClose();
        assertNull(get(patient));
        workflow.checkComplete(false, null, null, context);
    }

    /**
     * Verify that the workflow cancels if a patient selection is cancelled via the cancel button.
     */
    @Test
    public void testCancelSelectPatient() {
        checkCancelSelectPatient(false);
    }

    /**
     * Verify that the workflow cancels if a patient selection is cancelled via the 'user close' button.
     */
    @Test
    public void testCancelSelectPatientByUserClose() {
        checkCancelSelectPatient(true);
    }

    /**
     * Verifies that selecting a work list can be skipped, and that no task is created.
     */
    @Test
    public void testSkipSelectWorkList() {
        Date startTime = TestHelper.getDatetime("2013-01-01 09:00:00");
        Act appointment = createAppointment(startTime, customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setWorkList(workList); // need to pre-set work list so it can be selected in popup
        workflow.setArrivalTime(startTime);
        workflow.start();

        // skip work-list selection and verify no task is created
        BrowserDialog<Act> browser = workflow.getSelectionDialog();
        fireDialogButton(browser, PopupDialog.SKIP_ID);
        assertNull(workflow.getContext().getObject(ScheduleArchetypes.TASK));

        // add the patient weight
        workflow.addWeight(patient, BigDecimal.valueOf(10), clinician);

        // skip form printing
        workflow.printPatientDocuments(PopupDialog.SKIP_ID);

        // edit the clinical event
        PopupDialog eventDialog = workflow.editVisit();
        fireDialogButton(eventDialog, PopupDialog.OK_ID);
        workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);

        // verify the workflow is complete
        workflow.checkComplete(true, customer, patient, context);
    }

    /**
     * Verifies that if there is no clinician in the appointment, it defaults to that of the context.
     */
    @Test
    public void testDefaultClinicianFromContext() {
        Act appointment = createAppointment(customer, patient, null); // no clinician on appointment
        context.setClinician(clinician);
        checkClinician(appointment, clinician, context);
    }

    /**
     * Verifies that if there is no clinician on the appointment or context, then no clinician is populated.
     */
    @Test
    public void testNoClinician() {
        Act appointment = createAppointment(customer, patient, null); // no clinician on appointment
        checkClinician(appointment, null, context);
    }

    /**
     * Verify that the workflow cancels if a work-list selection is cancelled via the cancel button.
     */
    @Test
    public void testCancelSelectWorklist() {
        checkCancelSelectWorkList(false);
    }

    /**
     * Verify that the workflow cancels if a work-list selection is cancelled via the 'user close' button.
     */
    @Test
    public void testCancelSelectWorklistByUserClose() {
        checkCancelSelectWorkList(true);
    }

    /**
     * Verify that the workflow cancels if document selection is cancelled via the cancel button.
     */
    @Test
    public void testCancelSelectDocument() {
        checkCancelSelectDialog(false);
    }

    /**
     * Verify that the workflow cancels if document selection is cancelled via the 'user close' button.
     */
    @Test
    public void testCancelSelectDocumentByUserClose() {
        checkCancelSelectDialog(true);
    }

    /**
     * Tests the behaviour of cancelling the clinical event edit using the cancel button.
     */
    @Test
    public void testCancelEditEvent() {
        checkCancelEditEvent(false);
    }

    /**
     * Tests the behaviour of cancelling the clinical event edit using the 'user close' button.
     */
    @Test
    public void testCancelEditEventByUserClose() {
        checkCancelEditEvent(true);
    }

    /**
     * Verifies that no patient weight act is created if it is skipped.
     */
    @Test
    public void testSkipPatientWeight() {
        Act appointment = createAppointment(customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        runCheckInToWeight(workflow);

        // skip the weight entry and verify that the context has a weight act that is unsaved
        fireDialogButton(workflow.getEditDialog(), PopupDialog.SKIP_ID);
        IMObject weight = workflow.getContext().getObject(PatientArchetypes.PATIENT_WEIGHT);
        assertNotNull(weight);
        assertTrue(weight.isNew());

        workflow.printPatientDocuments(PopupDialog.SKIP_ID);

        // edit the clinical event
        PopupDialog eventDialog = workflow.editVisit();
        fireDialogButton(eventDialog, PopupDialog.OK_ID);
        workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);

        workflow.checkComplete(true, customer, patient, context);
    }

    /**
     * Verify that the workflow cancels if weight input is cancelled via the cancel button.
     */
    @Test
    public void testCancelEditPatientWeight() {
        checkCancelPatientWeight(false);
    }

    /**
     * Verify that the workflow cancels if weight input is cancelled via the 'user close' button.
     */
    @Test
    public void testCancelEditPatientWeightByUserClose() {
        checkCancelPatientWeight(true);
    }

    /**
     * Performs a check in, one day after a patient was invoiced but using the same invoice.
     * <p/>
     * This verifies the fix for OVPMS-1302.
     */
    @Test
    public void testCheckInWithInProgressInvoice() {
        // Two visits should be created, one on date1, the other on date2
        Date date1 = getDatetime("2012-01-01 10:00:00");
        Date date2 = getDatetime("2012-01-02 12:00:00");

        // Invoice the customer for a medication1 for the patient on 1/1/2012.
        // Leave the invoice IN_PROGRESS
        Product medication1 = TestHelper.createProduct();
        FinancialAct charge = (FinancialAct) create(CustomerAccountArchetypes.INVOICE);
        charge.setActivityStartTime(date1);

        context.setCustomer(customer);
        context.setPatient(patient);
        context.setPractice(getPractice());
        context.setClinician(clinician);
        LayoutContext layoutContext = new DefaultLayoutContext(context, new HelpContext("foo", null));

        TestChargeEditor editor = new TestChargeEditor(charge, layoutContext, false);
        editor.getComponent();
        CustomerChargeActItemEditor itemEditor1 = editor.addItem();
        itemEditor1.setStartTime(date1); // otherwise will default to now
        setItem(editor, itemEditor1, patient, medication1, BigDecimal.TEN, editor.getQueue());

        assertTrue(SaveHelper.save(editor));

        // Verify that an event has been created, linked to the charge
        Act item1 = (Act) itemEditor1.getObject();
        ActBean bean = new ActBean(item1);
        Act event1 = bean.getSourceAct(PatientArchetypes.CLINICAL_EVENT_CHARGE_ITEM);
        assertNotNull(event1);
        assertEquals(date1, event1.getActivityStartTime());
        assertEquals(ActStatus.IN_PROGRESS, event1.getStatus());

        // complete the event. This will force a new event to be created on subsequent check-in
        event1.setActivityEndTime(getDatetime("2012-01-01 11:30:00"));
        event1.setStatus(ActStatus.COMPLETED);
        save(event1);

        Act appointment = createAppointment(date2, customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setArrivalTime(date2);

        runCheckInToVisit(workflow);

        // Add another invoice item.
        Product medication2 = TestHelper.createProduct();
        VisitChargeItemEditor itemEditor2 = workflow.addVisitInvoiceItem(patient, clinician, medication2);

        // close the dialog
        PopupDialog eventDialog = workflow.editVisit();
        fireDialogButton(eventDialog, PopupDialog.OK_ID);
        Act event2 = workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);
        workflow.checkComplete(true, customer, patient, context);

        // verify the second item is linked to event2
        Act item2 = (Act) itemEditor2.getObject();
        ActBean bean2 = new ActBean(item2);
        assertEquals(event2, bean2.getSourceAct(PatientArchetypes.CLINICAL_EVENT_CHARGE_ITEM));

        // verify the second event is not the same as the first, and that none of the acts in the second event
        // are the same as those in the first
        assertNotEquals(event1, event2);
        ActBean event1Bean = new ActBean(event1);
        ActBean event2Bean = new ActBean(event2);
        List<Act> event1Items = event1Bean.getActs();
        List<Act> event2Items = event2Bean.getActs();
        Collection inBoth = CollectionUtils.intersection(event1Items, event2Items);
        assertTrue(inBoth.isEmpty());
    }

    /**
     * Verifies that changing the clinician on an invoice item propagates through to the documents associated
     * with the invoice.
     */
    @Test
    public void testChangeClinicianOnInvoiceItem() {
        Date startTime = TestHelper.getDatetime("2013-01-01 09:00:00");
        Act appointment = createAppointment(startTime, customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setArrivalTime(startTime);
        workflow.start();

        fireDialogButton(workflow.getSelectionDialog(), PopupDialog.SKIP_ID);
        fireDialogButton(workflow.getWeightEditor(), PopupDialog.SKIP_ID);
        workflow.printPatientDocuments(PopupDialog.SKIP_ID);

        Product product = CustomerChargeTestHelper.createProduct(ProductArchetypes.MEDICATION, BigDecimal.TEN,
                getPractice());
        addDocumentTemplate(product);
        addDocumentTemplate(product);

        // edit the charge
        VisitEditorDialog dialog = workflow.getVisitEditorDialog();
        dialog.getEditor().selectCharges(); // make sure the charges tab is selected, to enable the Apply button
        VisitChargeItemEditor itemEditor = workflow.addVisitInvoiceItem(patient, clinician, product);
        itemEditor.setClinician(clinician);
        fireDialogButton(dialog, PopupDialog.APPLY_ID);

        List<Act> documents1 = getDocuments(itemEditor);
        assertEquals(2, documents1.size());

        User clinician2 = TestHelper.createClinician();
        itemEditor.setClinician(clinician2);
        fireDialogButton(dialog, PopupDialog.OK_ID);

        List<Act> documents2 = getDocuments(itemEditor);
        assertEquals(2, documents2.size());
        assertEquals(clinician2, getClinician(documents2.get(0)));
        assertEquals(clinician2, getClinician(documents2.get(1)));

        workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);
        workflow.checkComplete(true, customer, patient, context);

        assertTrue(errors.isEmpty());
    }

    /**
     * Sets up the test case.
     */
    @Before
    public void setUp() {
        super.setUp();
        customer = TestHelper.createCustomer();
        patient = TestHelper.createPatient(customer);
        clinician = TestHelper.createClinician();
        User user = TestHelper.createUser();
        Entity taskType = ScheduleTestHelper.createTaskType();
        workList = createWorkList(taskType, 1);
        context = new LocalContext();
        context.setLocation(TestHelper.createLocation());
        context.setUser(user);

        // register an ErrorHandler to collect errors
        ErrorHandler.setInstance(new ErrorHandler() {
            @Override
            public void error(Throwable cause) {
                errors.add(cause.getMessage());
            }

            public void error(String title, String message, Throwable cause, WindowPaneListener listener) {
                errors.add(message);
            }
        });
    }

    /**
     * Verify that the workflow cancels if a patient selection is cancelled.
     *
     * @param userClose if <tt>true</tt> cancel via the 'user close' button, otherwise use the 'cancel' button
     */
    private void checkCancelSelectPatient(boolean userClose) {
        Act appointment = createAppointment(customer, null, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setPatient(patient); // need to pre-set patient so it can be selected in popup
        workflow.start();

        BrowserDialog<Party> dialog = workflow.getSelectionDialog();
        WorkflowTestHelper.cancelDialog(dialog, userClose);
        workflow.checkComplete(false, null, null, context);
    }

    /**
     * Tests that the workflow cancels if the work-list selection is cancelled.
     *
     * @param userClose if <tt>true</tt> cancel via the 'user close' button, otherwise use the 'cancel' button
     */
    private void checkCancelSelectWorkList(boolean userClose) {
        Act appointment = createAppointment(customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setWorkList(workList); // need to pre-set work-list so it can be selected in popup
        workflow.start();

        // cancel work-list selection and verify no task is created
        PopupDialog dialog = workflow.getSelectionDialog();
        WorkflowTestHelper.cancelDialog(dialog, userClose);
        assertNull(workflow.getContext().getObject(ScheduleArchetypes.TASK));

        // verify the workflow is complete
        workflow.checkComplete(false, null, null, context);
    }

    /**
     * Verify that the workflow cancels if document selection is cancelled via the cancel button.
     *
     * @param userClose if <tt>true</tt> cancel the selection by the 'user close' button otherwise via the cancel button
     */
    private void checkCancelSelectDialog(boolean userClose) {
        Act appointment = createAppointment(customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        runCheckInToWeight(workflow);

        workflow.addWeight(patient, BigDecimal.ONE, clinician);

        BrowserDialog<Entity> dialog = workflow.getPrintDocumentsDialog();
        WorkflowTestHelper.cancelDialog(dialog, userClose);

        workflow.checkComplete(false, null, null, context);
    }

    /**
     * Tests the behaviour of cancelling the clinical event edit. The event should save, and the workflow cancel.
     *
     * @param userClose if <tt>true</tt> cancel the edit by the 'user close' button otherwise via the cancel button
     */
    private void checkCancelEditEvent(boolean userClose) {
        Act appointment = createAppointment(customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        runCheckInToVisit(workflow);

        // edit the clinical event
        PopupDialog eventDialog = workflow.editVisit();
        WorkflowTestHelper.cancelDialog(eventDialog, userClose);

        // event is saved regardless of cancel
        workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);
        workflow.checkInvoice(clinician, BigDecimal.ZERO, ActStatus.IN_PROGRESS, false);
        workflow.checkComplete(false, null, null, context);
    }

    /**
     * Verify that the workflow cancels if weight input is cancelled via the 'user close' button.
     *
     * @param userClose if <tt>true</tt> cancel via the 'user close' button, otherwise use the 'cancel' button
     */
    private void checkCancelPatientWeight(boolean userClose) {
        Act appointment = createAppointment(customer, patient, clinician);
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        runCheckInToWeight(workflow);

        EditDialog editor = workflow.getWeightEditor();
        WorkflowTestHelper.cancelDialog(editor, userClose);
        workflow.checkComplete(false, null, null, context);
    }

    /**
     * Runs the check-in workflow up to the weight editing step.
     *
     * @param workflow the workflow
     */
    private void runCheckInToWeight(CheckInWorkflowRunner workflow) {
        workflow.setWorkList(workList); // need to pre-set work list so it can be selected in popup
        workflow.start();
        workflow.selectWorkList(workList, customer, patient);
    }

    /**
     * Runs the check-in workflow up to the visit editing step.
     *
     * @param workflow the workflow
     */
    private void runCheckInToVisit(CheckInWorkflowRunner workflow) {
        runCheckInToWeight(workflow);

        workflow.addWeight(patient, BigDecimal.valueOf(20), clinician);
        workflow.printPatientDocuments(PopupDialog.SKIP_ID);
    }

    /**
     * Verifies that the clinician is populated correctly.
     *
     * @param appointment the appointment
     * @param clinician   the expected clinician. May be {@code null}
     * @param context     the context
     */
    private void checkClinician(Act appointment, User clinician, Context context) {
        CheckInWorkflowRunner workflow = new CheckInWorkflowRunner(appointment, getPractice(), context);
        workflow.setWorkList(workList); // need to pre-set work list so it can be selected in popup
        workflow.start();

        workflow.selectWorkList(workList, customer, patient);

        workflow.addWeight(patient, BigDecimal.valueOf(20), clinician); // clinician defaults from context

        workflow.printPatientDocuments(PopupDialog.SKIP_ID);

        // edit the clinical event
        PopupDialog eventDialog = workflow.editVisit();
        fireDialogButton(eventDialog, PopupDialog.OK_ID);
        workflow.checkEvent(patient, clinician, ActStatus.IN_PROGRESS);
        workflow.checkInvoice(clinician, BigDecimal.ZERO, ActStatus.IN_PROGRESS, true);
        workflow.checkComplete(true, customer, patient, context);
    }

    /**
     * Returns the documents associated with a charge item.
     *
     * @param itemEditor the item editor
     * @return the documents
     */
    private List<Act> getDocuments(VisitChargeItemEditor itemEditor) {
        ActBean bean = new ActBean((Act) itemEditor.getObject());
        return bean.getNodeActs("documents");
    }

    /**
     * Returns the clinician from an act.
     *
     * @param act the act
     * @return the clinician. May be {@code null}
     */
    private User getClinician(Act act) {
        ActBean bean = new ActBean(act);
        return (User) bean.getNodeParticipant("clinician");
    }

    /**
     * Helper to add a document template to a product.
     *
     * @param product the product
     * @return the document template
     */
    private Entity addDocumentTemplate(Product product) {
        Entity template = DocumentTestHelper.createDocumentTemplate(PatientArchetypes.DOCUMENT_FORM);
        EntityBean bean = new EntityBean(product);
        bean.addNodeRelationship("documents", template);
        bean.save();
        return template;
    }
}