/*******************************************************************************
* Copyright (c) 2013-2016 MEDEVIT.
* 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 at.medevit.ch.artikelstamm.elexis.common.importer;
import java.io.InputStream;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ui.statushandlers.StatusManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;
import at.medevit.ch.artikelstamm.ARTIKELSTAMM;
import at.medevit.ch.artikelstamm.ARTIKELSTAMM.ITEMS.ITEM;
import at.medevit.ch.artikelstamm.ARTIKELSTAMM.LIMITATIONS.LIMITATION;
import at.medevit.ch.artikelstamm.ARTIKELSTAMM.PRODUCTS.PRODUCT;
import at.medevit.ch.artikelstamm.ArtikelstammConstants;
import at.medevit.ch.artikelstamm.ArtikelstammConstants.TYPE;
import at.medevit.ch.artikelstamm.ArtikelstammHelper;
import at.medevit.ch.artikelstamm.BlackBoxReason;
import at.medevit.ch.artikelstamm.elexis.common.PluginConstants;
import at.medevit.ch.artikelstamm.elexis.common.ui.provider.atccache.ATCCodeCache;
import ch.artikelstamm.elexis.common.ArtikelstammItem;
import ch.elexis.core.constants.StringConstants;
import ch.elexis.core.data.events.ElexisEventDispatcher;
import ch.elexis.core.data.interfaces.IVerrechenbar;
import ch.elexis.core.data.status.ElexisStatus;
import ch.elexis.data.Artikel;
import ch.elexis.data.PersistentObject;
import ch.elexis.data.Prescription;
import ch.elexis.data.Query;
import ch.elexis.data.StockEntry;
import ch.elexis.data.Verrechnet;
import ch.rgw.tools.JdbcLink;
import ch.rgw.tools.JdbcLink.Stm;
public class ArtikelstammImporter {
private static Logger log = LoggerFactory.getLogger(ArtikelstammImporter.class);
private static Map<String, PRODUCT> products = new HashMap<String, PRODUCT>();
private static Map<String, LIMITATION> limitations = new HashMap<String, LIMITATION>();
/**
*
* @param monitor
* @param input
* @param version
* if <code>null</code> use the version from the import file, else the provided
* version value
* @return
*/
public static IStatus performImport(IProgressMonitor monitor, InputStream input,
Integer newVersion){
if (monitor == null) {
monitor = new NullProgressMonitor();
}
String msg = "Aktualisierung des Artikelstamms";
log.info(msg + " ");
monitor.beginTask(msg, 7);
monitor.subTask("Einlesen der Aktualisierungsdaten");
ARTIKELSTAMM importStamm = null;
try {
importStamm = ArtikelstammHelper.unmarshallInputStream(input);
} catch (JAXBException | SAXException je) {
msg = "Fehler beim Einlesen der Import-Datei";
Status status = new ElexisStatus(IStatus.ERROR, PluginConstants.PLUGIN_ID,
ElexisStatus.CODE_NOFEEDBACK, msg, je);
StatusManager.getManager().handle(status, StatusManager.SHOW);
log.info(msg);
return Status.CANCEL_STATUS;
}
monitor.worked(1);
monitor.subTask("Lese Produkte und Limitationen...");
populateProducsAndLimitationsMap(importStamm);
monitor.worked(2);
if (newVersion == null) {
newVersion = importStamm.getVERSIONID();
}
int currentVersion = ArtikelstammItem.getCurrentVersion();
if (newVersion < currentVersion) {
log.warn("[PI] Downgrade initiated v" + currentVersion + " -> v" + newVersion);
}
log.info("[PI] Aktualisiere v" + currentVersion + " auf v" + newVersion);
long startTime = System.currentTimeMillis();
// clean all blackbox marks, as we will determine them newly
monitor.subTask("Black-Box Markierung zurücksetzen");
resetAllBlackboxMarks();
monitor.worked(1);
// mark all items of type importStammType still referenced as blackbox
setBlackboxOnAllReferencedItems(monitor);
// delete all items of type importStammType not blackboxed
monitor.subTask("Lösche nicht Black-Box Artikel");
removeAllNonBlackboxedWithVersionLower(currentVersion, monitor);
monitor.worked(1);
// import the new dataset for type importStammType
monitor.subTask(
"Importiere Artikelstamm " + importStamm.getMONTH() + "/" + importStamm.getYEAR());
importNewItemsIntoDatabase(newVersion, importStamm, monitor);
importProductsForExistingItemsIntoDatabase(newVersion, importStamm, monitor);
// update the version number for type importStammType
monitor.subTask("Setze neue Versionsnummer");
ArtikelstammItem.setCurrentVersion(newVersion);
ArtikelstammItem.setImportSetCreationDate(
importStamm.getCREATIONDATETIME().toGregorianCalendar().getTime());
monitor.worked(1);
long endTime = System.currentTimeMillis();
ElexisEventDispatcher.reload(ArtikelstammItem.class);
log.info("[PI] Artikelstamm import took " + ((endTime - startTime) / 1000) + "sec");
ATCCodeCache.rebuildCache(new SubProgressMonitor(monitor, 1));
monitor.done();
return Status.OK_STATUS;
}
private static void populateProducsAndLimitationsMap(ARTIKELSTAMM importStamm){
products = importStamm.getPRODUCTS().getPRODUCT().stream()
.collect(Collectors.toMap(p -> p.getPRODNO(), p -> p));
limitations = importStamm.getLIMITATIONS().getLIMITATION().stream()
.collect(Collectors.toMap(l -> l.getLIMNAMEBAG(), l -> l));
}
/**
* reset all black-box marks for the item to zero, we have to determine them fresh, otherwise
* once blackboxed - always blackboxed
*
* @param importStammType
*/
private static void resetAllBlackboxMarks(){
log.debug("[BB] Resetting blackbox marks...");
Stm stm = PersistentObject.getConnection().getStatement();
stm.exec("UPDATE " + ArtikelstammItem.TABLENAME + " SET " + ArtikelstammItem.FLD_BLACKBOXED
+ "=" + StringConstants.ZERO);
PersistentObject.getConnection().releaseStatement(stm);
}
/**
* Set {@link ArtikelstammItem#FLD_BLACKBOXED} = 1 to all items of type importStammType still
* being referenced by {@link Prescription}, ...
*
* @param monitor
*
* @param importStammType
*/
private static void setBlackboxOnAllReferencedItems(IProgressMonitor monitor){
// black box all ArtikelStammItem referenced by a prescription
SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1);
Query<Prescription> query = new Query<Prescription>(Prescription.class);
query.add(Prescription.FLD_ARTICLE, Query.LIKE, ArtikelstammItem.class.getName() + "%");
List<Prescription> resultPrescription = query.execute();
monitor.subTask("BlackBox Markierung für Medikationen");
subMonitor.beginTask("", resultPrescription.size());
log.debug("[BB] Setting blackbox marks...");
for (Prescription p : resultPrescription) {
if (p.getArtikel() instanceof ArtikelstammItem) {
ArtikelstammItem ai = (ArtikelstammItem) p.getArtikel();
if (ai != null && ai.exists() && ai instanceof ArtikelstammItem) {
if (ai.get(ArtikelstammItem.FLD_ITEM_TYPE) == null) {
log.error(
"[BB] ItemType is null in ArtikelstammItem [{}] from prescription [{}] -> blackboxing.",
(ai != null) ? ai.getId() : ai, p.getId());
}
ai.set(ArtikelstammItem.FLD_BLACKBOXED,
BlackBoxReason.IS_REFERENCED_IN_FIXMEDICATION.getNumericalReasonString());
} else {
log.error("[BB] Unresolvable ArtikelstammItem in prescription [{}], skipping.",
p.getId());
}
}
subMonitor.worked(1);
}
subMonitor.done();
// black box all import ArtikelStammItem reference by a konsultations leistung
SubProgressMonitor subMonitor2 = new SubProgressMonitor(monitor, 1);
Query<Verrechnet> queryVer = new Query<Verrechnet>(Verrechnet.class);
queryVer.add(Verrechnet.CLASS, Query.LIKE, ArtikelstammItem.class.getName());
List<Verrechnet> resultVerrechnet = queryVer.execute();
monitor.subTask("BlackBox Markierung für Artikel in Konsultationen");
subMonitor2.beginTask("", resultVerrechnet.size());
for (Verrechnet vr : resultVerrechnet) {
// should be ArtikelstammItem already?! NO?
IVerrechenbar verrechenbar = vr.getVerrechenbar();
if (verrechenbar != null
&& verrechenbar.getCodeSystemName().equals(ArtikelstammConstants.CODESYSTEM_NAME)) {
// why do you load again??? is not vr already ai??
ArtikelstammItem ai = ArtikelstammItem.load(verrechenbar.getId());
if (ai != null && ai.exists() && ai instanceof ArtikelstammItem) {
if (ai.get(ArtikelstammItem.FLD_ITEM_TYPE) == null) {
log.warn(
"[BB] ItemType is null in ArtikelstammItem [{}] from Verrechnet [{}] -> blackboxing.",
ai.getId(), vr.getId());
}
ai.set(ArtikelstammItem.FLD_BLACKBOXED,
BlackBoxReason.IS_REFERENCED_IN_CONSULTATION.getNumericalReasonString());
} else {
log.error("[BB] Unresolvable ArtikelstammItem in Verrechnet [{}] -> skipping. ",
vr.getId());
}
}
subMonitor2.worked(1);
}
subMonitor2.done();
// Wenn ein Artikel auf Lager ist, darf er auch nicht gelöscht werden!
SubProgressMonitor subMonitor3 = new SubProgressMonitor(monitor, 1);
Query<StockEntry> qre = new Query<StockEntry>(StockEntry.class);
qre.add(StockEntry.FLD_ARTICLE_TYPE, Query.LIKE, ArtikelstammItem.class.getName());
List<StockEntry> resultLagerartikel = qre.execute();
monitor.subTask("BlackBox Markierung für Lagerartikel");
subMonitor3.beginTask("", resultLagerartikel.size());
for (StockEntry stockEntry : resultLagerartikel) {
Artikel article = stockEntry.getArticle();
if (article instanceof ArtikelstammItem) {
ArtikelstammItem ai = (ArtikelstammItem) article;
ai.set(ArtikelstammItem.FLD_BLACKBOXED,
BlackBoxReason.IS_ON_STOCK.getNumericalReasonString());
subMonitor3.worked(1);
}
}
subMonitor3.done();
}
/**
* remove all articles of importStammType with the cummulatedVersion smaller equal
* currentStammVersion not marked as black-boxed
*
* @param currentStammVersion
* @param monitor
*/
private static void removeAllNonBlackboxedWithVersionLower(int currentStammVersion,
IProgressMonitor monitor){
Query<ArtikelstammItem> qbe = new Query<ArtikelstammItem>(ArtikelstammItem.class);
qbe.add(ArtikelstammItem.FLD_BLACKBOXED, Query.EQUALS, StringConstants.ZERO);
qbe.add(ArtikelstammItem.FLD_CUMMULATED_VERSION, Query.LESS_OR_EQUAL,
currentStammVersion + "");
monitor.subTask("Suche nach zu entfernenden Artikeln ...");
List<ArtikelstammItem> qre = qbe.execute();
log.debug("[RB] Removing {} non-referenced articles...", qre.size());
monitor.subTask("Entferne " + qre.size() + " nicht referenzierte Artikel ...");
boolean success = ArtikelstammItem.purgeEntries(qre);
if (!success)
log.warn("[RB] Error purging items");
}
/**
* Delete all products, collect all defined PRODNO entries and generate the resp. PRODUCT entry
* for it
*
* @param version
* @param importStamm
* @param monitor
*/
private static void importProductsForExistingItemsIntoDatabase(int cummulatedVersion,
ARTIKELSTAMM importStamm, IProgressMonitor monitor){
// delete all product entries not blackboxed
purgeProducts();
// find all defined PRODNO values
Stm stm = PersistentObject.getConnection().getStatement();
ResultSet rs = stm.query("SELECT DISTINCT(" + ArtikelstammItem.FLD_PRODNO + ") FROM "
+ ArtikelstammItem.TABLENAME);
List<String> productList = new ArrayList<String>();
try {
while (rs.next()) {
String prodNo = rs.getString(ArtikelstammItem.FLD_PRODNO);
if (prodNo != null) {
productList.add(prodNo);
}
}
} catch (SQLException e) {
log.error("[IP] Error executing distinct product selection", e);
}
PersistentObject.getConnection().releaseStatement(stm);
// for each defined PRODNO value generate the resp. product entry
log.debug("Importing {} products...", productList.size());
productList.stream().forEachOrdered(prodNo -> {
PRODUCT product = products.get(prodNo);
if (product != null) {
ArtikelstammItem productItem = ArtikelstammItem.load(product.getPRODNO());
String dscr = product.getDSCR();
if (dscr.length() > 100) {
log.warn("[IP] Delimiting dscr [{}] for product [{}] to 100 characters.",
product.getPRODNO(), dscr);
dscr = dscr.substring(0, 100);
}
if (!productItem.isAvailable()) {
productItem = new ArtikelstammItem(cummulatedVersion, TYPE.X,
product.getPRODNO(), null, dscr, StringConstants.EMPTY);
}
productItem.set(new String[] {
ArtikelstammItem.FLD_BLACKBOXED, ArtikelstammItem.FLD_CUMMULATED_VERSION,
ArtikelstammItem.FLD_DSCR, ArtikelstammItem.FLD_ATC
}, StringConstants.ZERO, cummulatedVersion + "", dscr, product.getATC());
} else {
log.error("[IP] Product is null for [{}]", prodNo);
}
});
// TODO fixed length for prodno?
}
private static boolean purgeProducts(){
log.debug("[PP] Purging existing products not blackboxed");
Stm stm = PersistentObject.getConnection().getStatement();
int exec = stm.exec("DELETE FROM " + ArtikelstammItem.TABLENAME + " WHERE TYPE = '"
+ TYPE.X.name() + "' AND " + ArtikelstammItem.FLD_BLACKBOXED + " = 0");
log.debug("[PP] Purged {} products", exec);
PersistentObject.getConnection().releaseStatement(stm);
return true;
}
private static void importNewItemsIntoDatabase(int newVersion, ARTIKELSTAMM importStamm,
IProgressMonitor monitor){
SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, 1);
List<ITEM> importItemList = importStamm.getITEMS().getITEM();
subMonitor.beginTask("Importiere " + importItemList.size() + " items",
importItemList.size());
log.debug("[II] Importing {} items...", importItemList.size());
ArtikelstammItem ai = null;
for (ITEM item : importItemList) {
String itemUuid =
ArtikelstammHelper.createUUID(newVersion, item.getGTIN(), item.getPHAR(), false);
// Is the item to be imported already in the database? This should only happen
// if one re-imports an already imported dataset and the item was marked as black-box
Stm stm = PersistentObject.getConnection().getStatement();
int foundElements = stm.queryInt("SELECT COUNT(*) FROM " + ArtikelstammItem.TABLENAME
+ " WHERE " + ArtikelstammItem.FLD_ID + " " + Query.LIKE + " "
+ JdbcLink.wrap(itemUuid + "%"));
PersistentObject.getConnection().releaseStatement(stm);
if (foundElements == 0) {
String dscr = (item.getDSCR().length() > 99) ? item.getDSCR().substring(0, 100)
: item.getDSCR();
String ptString = Character.toString(item.getPHARMATYPE().charAt(0));
TYPE pharmaType = TYPE.valueOf(ptString.toUpperCase());
ai = new ArtikelstammItem(newVersion, pharmaType, item.getGTIN(), item.getPHAR(),
dscr, StringConstants.EMPTY);
setValuesOnArtikelstammItem(ai, item, false, -1);
} else if (foundElements == 1) {
String itemId = PersistentObject.getConnection()
.queryString("SELECT ID FROM " + ArtikelstammItem.TABLENAME + " WHERE "
+ ArtikelstammItem.FLD_ID + " " + Query.LIKE + " "
+ JdbcLink.wrap(itemUuid + "%"));
ai = ArtikelstammItem.load(itemId);
log.trace("[II] Updating article " + ai.getId() + " (" + item.getDSCR() + ")");
setValuesOnArtikelstammItem(ai, item, true, newVersion);
} else {
log.error("[II] Found " + foundElements + " items for " + itemUuid + ".");
}
subMonitor.worked(1);
}
subMonitor.done();
}
private static void setValuesOnArtikelstammItem(ArtikelstammItem ai, ITEM item,
boolean allValues, final int cummulatedVersion){
List<String> fields = new ArrayList<>();
List<String> values = new ArrayList<>();
// reset blackbox as we updated the article
fields.add(ArtikelstammItem.FLD_BLACKBOXED);
values.add(StringConstants.ZERO);
fields.add(ArtikelstammItem.FLD_GTIN);
values.add(item.getGTIN());
if (allValues) {
// include header values
fields.add(ArtikelstammItem.FLD_DSCR);
String dscr = item.getDSCR();
values.add((dscr.length() > 99) ? item.getDSCR().substring(0, 100).toString()
: item.getDSCR());
fields.add(ArtikelstammItem.FLD_CUMMULATED_VERSION);
values.add(cummulatedVersion + "");
}
String prodno = item.getPRODNO();
if (prodno != null) {
PRODUCT product = products.get(prodno);
if (product != null) {
fields.add(ArtikelstammItem.FLD_ATC);
values.add(product.getATC());
fields.add(ArtikelstammItem.FLD_PRODNO);
values.add(prodno);
String limnamebag = product.getLIMNAMEBAG();
if (limnamebag != null) {
LIMITATION limitation = limitations.get(limnamebag);
fields.add(ArtikelstammItem.FLD_LIMITATION);
values.add(limitation != null ? StringConstants.ONE : StringConstants.ZERO);
if (limitation != null) {
if (limitation.getLIMITATIONPTS() != null) {
fields.add(ArtikelstammItem.FLD_LIMITATION_PTS);
values.add(limitation.getLIMITATIONPTS().toString());
}
fields.add(ArtikelstammItem.FLD_LIMITATION_TEXT);
values.add(limitation.getDSCR());
}
}
}
}
if (item.getCOMP() != null) {
if (item.getCOMP().getNAME() != null) {
fields.add(ArtikelstammItem.FLD_COMP_NAME);
values.add(item.getCOMP().getNAME());
}
if (item.getCOMP().getGLN() != null) {
fields.add(ArtikelstammItem.FLD_COMP_GLN);
values.add(item.getCOMP().getGLN());
}
}
if (item.getPEXF() != null) {
fields.add(ArtikelstammItem.FLD_PEXF);
values.add(item.getPEXF().toString());
}
if (item.getPPUB() != null) {
fields.add(ArtikelstammItem.FLD_PPUB);
values.add(item.getPPUB().toString());
}
if (item.isSLENTRY() != null) {
fields.add(ArtikelstammItem.FLD_SL_ENTRY);
values.add((item.isSLENTRY()) ? StringConstants.ONE : StringConstants.ZERO);
}
if (item.getDEDUCTIBLE() != null) {
fields.add(ArtikelstammItem.FLD_DEDUCTIBLE);
values.add(item.getDEDUCTIBLE().toString());
}
if (item.getGENERICTYPE() != null) {
fields.add(ArtikelstammItem.FLD_GENERIC_TYPE);
values.add(item.getGENERICTYPE());
}
if (item.getIKSCAT() != null) {
fields.add(ArtikelstammItem.FLD_IKSCAT);
values.add(item.getIKSCAT());
}
if (item.isNARCOTIC() != null) {
fields.add(ArtikelstammItem.FLD_NARCOTIC);
values.add((item.isNARCOTIC()) ? StringConstants.ONE : StringConstants.ZERO);
}
if (item.isLPPV() != null) {
fields.add(ArtikelstammItem.FLD_LPPV);
values.add((item.isLPPV()) ? StringConstants.ONE : StringConstants.ZERO);
}
if (item.getPKGSIZE() != null) {
fields.add(ArtikelstammItem.FLD_PKG_SIZE);
String pkgSize = item.getPKGSIZE().toString();
values.add((pkgSize.length() > 6) ? pkgSize.substring(0, 6).toString() : pkgSize);
if (pkgSize.length() > 6) {
log.warn("[II] Delimited pkg size for [{}] being [{}] to 6 characters.", ai.getId(),
item.getPKGSIZE().toString());
}
}
ai.set(fields.toArray(new String[0]), values.toArray(new String[0]));
}
}