/*******************************************************************************
* Copyright (c) 2017 MEDEVIT <office@medevit.at>.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* MEDEVIT <office@medevit.at> - initial API and implementation
******************************************************************************/
package ch.elexis.core.ui.views.rechnung;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.part.ViewPart;
import ch.elexis.core.data.events.ElexisEventDispatcher;
import ch.elexis.core.data.util.BillingUtil;
import ch.elexis.core.ui.UiDesk;
import ch.elexis.data.Fall;
import ch.elexis.data.Konsultation;
import ch.elexis.data.Kontakt;
import ch.rgw.tools.Money;
import ch.rgw.tools.Result;
import ch.rgw.tools.Result.SEVERITY;
import ch.rgw.tools.Result.msg;
import ch.rgw.tools.TimeTool;
public class BillingProposalView extends ViewPart {
public static final String ID = "ch.elexis.core.ui.views.rechnung.BillingProposalView"; //$NON-NLS-1$
private TableViewer viewer;
private BillingProposalViewerComparator comparator;
private Color lightRed = UiDesk.getColorFromRGB("ff8d8d");
private Color lightGreen = UiDesk.getColorFromRGB("a6ffaa");
@Override
public void createPartControl(Composite parent){
viewer = new TableViewer(parent, SWT.FULL_SELECTION | SWT.BORDER | SWT.MULTI | SWT.VIRTUAL);
viewer.getTable().setHeaderVisible(true);
viewer.setContentProvider(new BillingInformationContentProvider(viewer));
comparator = new BillingProposalViewerComparator();
viewer.setComparator(comparator);
TableViewerColumn patNameColumn = new TableViewerColumn(viewer, SWT.NONE);
patNameColumn.getColumn().setWidth(175);
patNameColumn.getColumn().setText("Patient");
patNameColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element){
if (element instanceof BillingInformation) {
return ((BillingInformation) element).getPatientName();
} else {
return super.getText(element);
}
}
});
patNameColumn.getColumn().addSelectionListener(getSelectionAdapter(0));
TableViewerColumn patNrColumn = new TableViewerColumn(viewer, SWT.NONE);
patNrColumn.getColumn().setWidth(50);
patNrColumn.getColumn().setText("PatNr");
patNrColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element){
if (element instanceof BillingInformation) {
return Integer.toString(((BillingInformation) element).getPatientNr());
} else {
return super.getText(element);
}
}
});
patNrColumn.getColumn().addSelectionListener(getSelectionAdapter(1));
TableViewerColumn dateColumn = new TableViewerColumn(viewer, SWT.NONE);
dateColumn.getColumn().setWidth(75);
dateColumn.getColumn().setText("Datum");
dateColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element){
if (element instanceof BillingInformation) {
return ((BillingInformation) element).getDate()
.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
} else {
return super.getText(element);
}
}
});
dateColumn.getColumn().addSelectionListener(getSelectionAdapter(2));
TableViewerColumn accountingSystemColumn = new TableViewerColumn(viewer, SWT.NONE);
accountingSystemColumn.getColumn().setWidth(75);
accountingSystemColumn.getColumn().setText("Fall");
accountingSystemColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element){
if (element instanceof BillingInformation) {
return ((BillingInformation) element).getAccountingSystem();
} else {
return super.getText(element);
}
}
});
TableViewerColumn insurerColumn = new TableViewerColumn(viewer, SWT.NONE);
insurerColumn.getColumn().setWidth(175);
insurerColumn.getColumn().setText("Versicherer");
insurerColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element){
if (element instanceof BillingInformation) {
return ((BillingInformation) element).getInsurer();
} else {
return super.getText(element);
}
}
});
TableViewerColumn totalColumn = new TableViewerColumn(viewer, SWT.NONE);
totalColumn.getColumn().setWidth(75);
totalColumn.getColumn().setText("Total");
totalColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element){
if (element instanceof BillingInformation) {
return ((BillingInformation) element).getTotal();
} else {
return super.getText(element);
}
}
});
TableViewerColumn checkResultColumn = new TableViewerColumn(viewer, SWT.NONE);
checkResultColumn.getColumn().setWidth(200);
checkResultColumn.getColumn().setText("Prüfergebnis");
checkResultColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element){
if (element instanceof BillingInformation) {
return ((BillingInformation) element).getCheckResultMessage();
} else {
return super.getText(element);
}
}
@Override
public Color getBackground(Object element){
if (element instanceof BillingInformation) {
return ((BillingInformation) element).isOk() ? lightGreen : lightRed;
} else {
return super.getForeground(element);
}
}
});
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event){
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
if (selection != null && !selection.isEmpty()) {
if (selection.getFirstElement() instanceof BillingInformation) {
Konsultation kons =
((BillingInformation) selection.getFirstElement()).getKonsultation();
ElexisEventDispatcher.fireSelectionEvent(kons);
}
}
}
});
viewer.getControl().addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e){
if (e.keyCode == SWT.F5) {
refresh();
}
}
});
MenuManager menuManager = new MenuManager();
Menu menu = menuManager.createContextMenu(viewer.getControl());
viewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuManager, viewer);
getSite().setSelectionProvider(viewer);
}
private SelectionAdapter getSelectionAdapter(int index){
SelectionAdapter selectionAdapter = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e){
comparator.setColumn(index);
int dir = comparator.getDirection();
viewer.getTable().setSortDirection(dir);
viewer.refresh();
}
};
return selectionAdapter;
}
@Override
public void setFocus(){
viewer.getControl().setFocus();
}
/**
* Set a {@link List} of {@link Konsultation} as the input for the viewer.
*
* @param proposal
*/
public void setInput(List<Konsultation> proposal){
viewer.setInput(proposal);
}
/**
* Refresh the current content of the viewer.
*/
public void refresh(){
((BillingInformationContentProvider) viewer.getContentProvider()).refresh();
viewer.refresh();
}
/**
* Get a {@link List} of all {@link Konsultation} instances of the viewer, erroneous and valid.
*/
public List<Konsultation> getToBill(){
List<BillingInformation> content =
((BillingInformationContentProvider) viewer.getContentProvider()).getCurrentContent();
if (content != null && !content.isEmpty()) {
return content.parallelStream().map(bi -> bi.getKonsultation())
.collect(Collectors.toList());
}
return Collections.emptyList();
}
public void removeToBill(List<BillingInformation> list){
((BillingInformationContentProvider) viewer.getContentProvider()).removeContent(list);
viewer.refresh();
}
public ProposalLetter getToPrint(){
BillingInformationContentProvider contentProvider =
((BillingInformationContentProvider) viewer.getContentProvider());
contentProvider.resolveAll();
List<BillingInformation> content = contentProvider.getCurrentContent();
BillingProposalViewerComparator comparator =
(BillingProposalViewerComparator) viewer.getComparator();
content.sort(comparator);
ProposalLetter ret = new ProposalLetter();
if (content != null && !content.isEmpty()) {
ret.setProposal(content);
} else {
ret.setProposal(Collections.emptyList());
}
return ret;
}
@XmlRootElement
public static class ProposalLetter {
private List<BillingInformation> proposal;
public ProposalLetter(){
}
public void setProposal(List<BillingInformation> proposal){
this.proposal = proposal;
}
public List<BillingInformation> getProposal(){
return proposal;
}
}
public static class LocalDateAdapter extends XmlAdapter<String, LocalDate> {
private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
@Override
public String marshal(LocalDate date) throws Exception{
return formatter.format(date);
}
@Override
public LocalDate unmarshal(String string) throws Exception{
return LocalDate.parse(string, formatter);
}
}
/**
* View specific model class, including multi threaded property loading.
*
* @author thomas
*
*/
@XmlRootElement(name = "proposal")
public static class BillingInformation {
@XmlTransient
private static ExecutorService executorService = Executors.newFixedThreadPool(8);
@XmlTransient
private volatile boolean resolved;
@XmlTransient
private volatile boolean resolving;
@XmlTransient
private StructuredViewer viewer;
@XmlTransient
private Fall fall;
@XmlTransient
private Konsultation konsultation;
@XmlElement
@XmlJavaTypeAdapter(LocalDateAdapter.class)
private LocalDate date;
@XmlElement
private int patientNr = -1;
@XmlElement
private String patientName;
@XmlElement
private String insurerName;
@XmlElement
private String accountingSystem;
@XmlElement
private String amountTotal;
@XmlElement
private String checkResultMessage;
@XmlElement
private boolean checkResult;
public BillingInformation(){
}
public BillingInformation(StructuredViewer viewer, Fall fall, Konsultation konsultation){
this.viewer = viewer;
this.fall = fall;
this.konsultation = konsultation;
resolved = false;
resolving = false;
date = new TimeTool(konsultation.getDatum()).toLocalDate();
}
public String getPatientName(){
if (patientName == null) {
patientName = fall.getPatient().getLabel(true);
}
return patientName;
}
public Konsultation getKonsultation(){
return konsultation;
}
public Integer getPatientNr(){
if (patientNr == -1) {
try {
patientNr = Integer.parseInt(fall.getPatient().getPatCode());
} catch (NumberFormatException e) {
patientNr = -1;
}
}
return patientNr;
}
public LocalDate getDate(){
return date;
}
public synchronized boolean isResolved(){
if (!resolved && !resolving) {
resolving = true;
executorService.execute(new ResolveLazyFieldsRunnable(viewer, this));
}
return resolved;
}
public void resolve(){
executorService.execute(new ResolveLazyFieldsRunnable(null, this));
while (!isResolved()) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// ignore
}
}
}
public synchronized void refresh(){
resolved = false;
}
public String getCheckResultMessage(){
if (!isResolved()) {
return "...";
} else {
return checkResultMessage;
}
}
public Boolean isOk(){
if (!isResolved()) {
return false;
} else {
return checkResult;
}
}
public String getTotal(){
if (!isResolved()) {
return "...";
} else {
return amountTotal;
}
}
public String getAccountingSystem(){
if (!isResolved()) {
return "...";
} else {
return accountingSystem;
}
}
public String getInsurer(){
if (!isResolved()) {
return "...";
} else {
return insurerName;
}
}
private static class ResolveLazyFieldsRunnable implements Runnable {
private BillingInformation item;
private StructuredViewer viewer;
public ResolveLazyFieldsRunnable(StructuredViewer viewer, BillingInformation item){
this.item = item;
this.viewer = viewer;
}
@Override
public void run(){
resolveInsurer();
resolveAccountingSystem();
resolveTotal();
resolveCheckResult();
item.resolved = true;
item.resolving = false;
if (viewer != null) {
updateViewer();
}
}
private void resolveCheckResult(){
Result<Konsultation> result = BillingUtil.getBillableResult(item.konsultation);
if (result.isOK()) {
item.checkResultMessage = "Ok";
item.checkResult = true;
} else {
StringBuilder sb = new StringBuilder();
for (@SuppressWarnings("rawtypes")
msg message : result.getMessages()) {
if (message.getSeverity() != SEVERITY.OK) {
if (sb.length() > 0) {
sb.append(" / ");
}
sb.append(message.getText());
}
}
item.checkResultMessage = sb.toString();
item.checkResult = false;
}
}
private void resolveTotal(){
Money total = BillingUtil.getTotal(item.konsultation);
item.amountTotal = total.getAmountAsString();
}
private void resolveInsurer(){
String insurerId = (String) item.fall.getInfoElement("Kostenträger");
if (insurerId != null && !insurerId.isEmpty()) {
item.insurerName = Kontakt.load(insurerId).getLabel(true);
} else {
item.insurerName = "";
}
}
private void resolveAccountingSystem(){
item.accountingSystem = item.fall.getAbrechnungsSystem();
}
private void updateViewer(){
Control control = viewer.getControl();
if (control != null && !control.isDisposed()) {
control.getDisplay().asyncExec(new Runnable() {
@Override
public void run(){
if (!control.isDisposed() && control.isVisible()) {
viewer.refresh(item, true);
}
}
});
}
}
}
}
/**
* View specific {@link IStructuredContentProvider} implementation for mapping a list of
* {@link Konsultation} to a list of {@link BillingInformation}.
*
* @author thomas
*
*/
private class BillingInformationContentProvider implements IStructuredContentProvider {
private StructuredViewer viewer;
private List<BillingInformation> currentContent;
public BillingInformationContentProvider(StructuredViewer viewer){
this.viewer = viewer;
}
public void removeContent(List<BillingInformation> list){
currentContent.removeAll(list);
}
public void resolveAll(){
currentContent.parallelStream().forEach(bi -> {
// touch patient number
bi.getPatientNr();
if (!bi.isResolved()) {
bi.resolve();
}
});
}
/**
* Refresh the current list of {@link BillingInformation}
*/
public void refresh(){
if (currentContent != null) {
currentContent = currentContent.parallelStream()
.filter(bi -> bi.getKonsultation().getRechnung() == null)
.collect(Collectors.toList());
currentContent.parallelStream().forEach(bi -> bi.refresh());
}
}
@Override
public Object[] getElements(Object inputElement){
if (inputElement instanceof List<?>) {
return currentContent.toArray();
}
return Collections.emptyList().toArray();
}
@SuppressWarnings("unchecked")
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput){
if (newInput instanceof List<?>) {
currentContent = ((List<Konsultation>) newInput).parallelStream()
.map(k -> new BillingInformation(this.viewer, k.getFall(), k))
.collect(Collectors.toList());
}
}
public List<BillingInformation> getCurrentContent(){
if (currentContent != null) {
return currentContent;
}
return Collections.emptyList();
}
@Override
public void dispose(){
viewer = null;
currentContent = null;
}
}
/**
* View specific {@link ViewerComparator} implementation.
*
* @author thomas
*
*/
private class BillingProposalViewerComparator extends ViewerComparator
implements Comparator<BillingInformation> {
private int propertyIndex;
private static final int DESCENDING = 1;
private int direction = DESCENDING;
public BillingProposalViewerComparator(){
this.propertyIndex = 0;
direction = DESCENDING;
}
public int getDirection(){
return direction == 1 ? SWT.DOWN : SWT.UP;
}
public void setColumn(int column){
if (column == this.propertyIndex) {
// Same column as last sort; toggle the direction
direction = 1 - direction;
} else {
// New column; do an ascending sort
this.propertyIndex = column;
direction = DESCENDING;
}
}
@Override
public int compare(Viewer viewer, Object e1, Object e2){
BillingInformation left = (BillingInformation) e1;
BillingInformation right = (BillingInformation) e2;
return compare(left, right);
}
@Override
public int compare(BillingInformation left, BillingInformation right){
int rc = 0;
switch (propertyIndex) {
case 0:
rc = right.getPatientName().compareTo(left.getPatientName());
break;
case 1:
rc = right.getPatientNr().compareTo(left.getPatientNr());
break;
case 2:
rc = right.getDate().compareTo(left.getDate());
break;
default:
rc = 0;
}
// If descending order, flip the direction
if (direction == DESCENDING) {
rc = -rc;
}
return rc;
}
}
}