diff --git a/build.gradle.kts b/build.gradle.kts index 0c7288e3..744b8469 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ version = "1.0.0" val coroutinesVersion = "1.8.0" val syfoXmlCodegenVersion = "2.0.1" -val ibmMqVersion = "9.3.1.0" +val ibmMqVersion = "9.3.4.1" val javaxActivationVersion = "1.1.1" val jacksonVersion = "2.16.1" val jaxbApiVersion = "2.4.0-b180830.0359" @@ -12,7 +12,6 @@ val ktorVersion = "2.3.8" val logbackVersion = "1.5.0" val logstashEncoderVersion = "7.4" val prometheusVersion = "0.16.0" -val smCommonVersion = "2.0.8" val jaxwsApiVersion = "2.3.1" val commonsTextVersion = "1.11.0" val javaxAnnotationApiVersion = "1.3.2" @@ -91,11 +90,7 @@ dependencies { implementation("no.nav.helse.xml:xmlfellesformat:$syfoXmlCodegenVersion") implementation("no.nav.helse.xml:kith-hodemelding:$syfoXmlCodegenVersion") implementation("no.nav.helse.xml:kith-apprec:$syfoXmlCodegenVersion") - - implementation("no.nav.helse:syfosm-common-models:$smCommonVersion") - implementation("no.nav.helse:syfosm-common-networking:$smCommonVersion") - implementation("no.nav.helse:syfosm-common-kafka:$smCommonVersion") - implementation("no.nav.helse:syfosm-common-mq:$smCommonVersion") + implementation("com.ibm.mq:com.ibm.mq.allclient:$ibmMqVersion") constraints { implementation("org.json:json:$jsonVersion") { because("override transient from com.ibm.mq:com.ibm.mq.allclient") diff --git a/gradle.properties b/gradle.properties index 353088a2..f40276fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,3 @@ kotlin.code.style=official org.gradle.caching=true +kotlin.daemon.jvmargs=-Xmx1g diff --git a/src/main/kotlin/no/nav/syfo/bootstrap/KafkaClients.kt b/src/main/kotlin/no/nav/syfo/bootstrap/KafkaClients.kt index 0e6ed56c..91a95fd0 100644 --- a/src/main/kotlin/no/nav/syfo/bootstrap/KafkaClients.kt +++ b/src/main/kotlin/no/nav/syfo/bootstrap/KafkaClients.kt @@ -1,15 +1,32 @@ package no.nav.syfo.bootstrap +import java.util.Properties +import kotlin.reflect.KClass import no.nav.syfo.EnvironmentVariables import no.nav.syfo.apprec.Apprec import no.nav.syfo.kafka.aiven.KafkaUtils -import no.nav.syfo.kafka.toProducerConfig import no.nav.syfo.model.ManuellOppgave import no.nav.syfo.model.OpprettOppgaveKafkaMessage import no.nav.syfo.model.ReceivedSykmelding import no.nav.syfo.model.ValidationResult import no.nav.syfo.util.JacksonKafkaSerializer +import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.clients.producer.KafkaProducer +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.serialization.Serializer +import org.apache.kafka.common.serialization.StringSerializer + +fun Properties.toProducerConfig( + groupId: String, + valueSerializer: KClass>, + keySerializer: KClass> = StringSerializer::class +): Properties = + Properties().also { + it.putAll(this) + it[ConsumerConfig.GROUP_ID_CONFIG] = groupId + it[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = valueSerializer.java + it[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = keySerializer.java + } class KafkaClients(environmentVariables: EnvironmentVariables) { diff --git a/src/main/kotlin/no/nav/syfo/kafka/aiven/KafkaEnvironment.kt b/src/main/kotlin/no/nav/syfo/kafka/aiven/KafkaEnvironment.kt new file mode 100644 index 00000000..d710440a --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/kafka/aiven/KafkaEnvironment.kt @@ -0,0 +1,15 @@ +package no.nav.syfo.kafka.aiven + +data class KafkaEnvironment( + val KAFKA_BROKERS: String = getEnvVar("KAFKA_BROKERS"), + val KAFKA_CLIENT_ID: String = getEnvVar("HOSTNAME"), + val KAFKA_TRUSTSTORE_PATH: String = getEnvVar("KAFKA_TRUSTSTORE_PATH"), + val KAFKA_KEYSTORE_PATH: String = getEnvVar("KAFKA_KEYSTORE_PATH"), + val KAFKA_CREDSTORE_PASSWORD: String = getEnvVar("KAFKA_CREDSTORE_PASSWORD") +) { + companion object { + fun getEnvVar(varName: String, defaultValue: String? = null) = + System.getenv(varName) + ?: defaultValue ?: throw RuntimeException("Missing required variable \"$varName\"") + } +} diff --git a/src/main/kotlin/no/nav/syfo/kafka/aiven/KafkaUtils.kt b/src/main/kotlin/no/nav/syfo/kafka/aiven/KafkaUtils.kt new file mode 100644 index 00000000..114c8306 --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/kafka/aiven/KafkaUtils.kt @@ -0,0 +1,29 @@ +package no.nav.syfo.kafka.aiven + +import java.util.Properties +import org.apache.kafka.clients.CommonClientConfigs +import org.apache.kafka.clients.producer.ProducerConfig +import org.apache.kafka.common.config.SslConfigs + +class KafkaUtils { + companion object { + fun getAivenKafkaConfig(clientId: String): Properties { + return Properties().also { + val kafkaEnv = KafkaEnvironment() + it[CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG] = kafkaEnv.KAFKA_BROKERS + it[CommonClientConfigs.SECURITY_PROTOCOL_CONFIG] = "SSL" + it[CommonClientConfigs.CLIENT_ID_CONFIG] = "${kafkaEnv.KAFKA_CLIENT_ID}-$clientId" + it[SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG] = "jks" + it[SslConfigs.SSL_KEYSTORE_TYPE_CONFIG] = "PKCS12" + it[SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG] = kafkaEnv.KAFKA_TRUSTSTORE_PATH + it[SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG] = kafkaEnv.KAFKA_CREDSTORE_PASSWORD + it[SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG] = kafkaEnv.KAFKA_KEYSTORE_PATH + it[SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG] = kafkaEnv.KAFKA_CREDSTORE_PASSWORD + it[SslConfigs.SSL_KEY_PASSWORD_CONFIG] = kafkaEnv.KAFKA_CREDSTORE_PASSWORD + it[SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG] = "" + it[ProducerConfig.ACKS_CONFIG] = "all" + it[ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG] = "true" + } + } + } +} diff --git a/src/main/kotlin/no/nav/syfo/model/Merknad.kt b/src/main/kotlin/no/nav/syfo/model/Merknad.kt new file mode 100644 index 00000000..78015a39 --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/model/Merknad.kt @@ -0,0 +1,3 @@ +package no.nav.syfo.model + +data class Merknad(val type: String, val beskrivelse: String?) diff --git a/src/main/kotlin/no/nav/syfo/model/ReceivedSykmelding.kt b/src/main/kotlin/no/nav/syfo/model/ReceivedSykmelding.kt new file mode 100644 index 00000000..fd47e9d7 --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/model/ReceivedSykmelding.kt @@ -0,0 +1,31 @@ +package no.nav.syfo.model + +import java.time.LocalDateTime + +data class ReceivedSykmelding( + val sykmelding: Sykmelding, + val personNrPasient: String, + val tlfPasient: String?, + val personNrLege: String, + val legeHelsepersonellkategori: String?, + val legeHprNr: String?, + val navLogId: String, + val msgId: String, + val legekontorOrgNr: String?, + val legekontorHerId: String?, + val legekontorReshId: String?, + val legekontorOrgName: String, + val mottattDato: LocalDateTime, + val rulesetVersion: String?, + val merknader: List?, + val partnerreferanse: String?, + val vedlegg: List?, + val utenlandskSykmelding: UtenlandskSykmelding?, + /** + * Full fellesformat as a XML payload, this is only used for infotrygd compat and should be + * removed in thefuture + */ + val fellesformat: String, + /** TSS-ident, this is only used for infotrygd compat and should be removed in thefuture */ + val tssid: String? +) diff --git a/src/main/kotlin/no/nav/syfo/model/Sykmelding.kt b/src/main/kotlin/no/nav/syfo/model/Sykmelding.kt new file mode 100644 index 00000000..4ee6cf1d --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/model/Sykmelding.kt @@ -0,0 +1,199 @@ +package no.nav.syfo.model + +import java.time.LocalDate +import java.time.LocalDateTime + +data class Sykmelding( + val id: String, + val msgId: String, + val pasientAktoerId: String, + val medisinskVurdering: MedisinskVurdering, + val skjermesForPasient: Boolean, + val arbeidsgiver: Arbeidsgiver, + val perioder: List, + val prognose: Prognose?, + val utdypendeOpplysninger: Map>, + val tiltakArbeidsplassen: String?, + val tiltakNAV: String?, + val andreTiltak: String?, + val meldingTilNAV: MeldingTilNAV?, + val meldingTilArbeidsgiver: String?, + val kontaktMedPasient: KontaktMedPasient, + val behandletTidspunkt: LocalDateTime, + val behandler: Behandler, + val avsenderSystem: AvsenderSystem, + val syketilfelleStartDato: LocalDate?, + val signaturDato: LocalDateTime, + val navnFastlege: String? +) + +data class MedisinskVurdering( + val hovedDiagnose: Diagnose?, + val biDiagnoser: List, + val svangerskap: Boolean, + val yrkesskade: Boolean, + val yrkesskadeDato: LocalDate?, + val annenFraversArsak: AnnenFraversArsak? +) + +data class Diagnose(val system: String, val kode: String, val tekst: String?) + +data class AnnenFraversArsak(val beskrivelse: String?, val grunn: List) + +data class Arbeidsgiver( + val harArbeidsgiver: HarArbeidsgiver, + val navn: String?, + val yrkesbetegnelse: String?, + val stillingsprosent: Int? +) + +enum class HarArbeidsgiver( + val codeValue: String, + val text: String, + val oid: String = "2.16.578.1.12.4.1.1.8130" +) { + EN_ARBEIDSGIVER("1", "Én arbeidsgiver"), + FLERE_ARBEIDSGIVERE("2", "Flere arbeidsgivere"), + INGEN_ARBEIDSGIVER("3", "Ingen arbeidsgiver") +} + +data class Periode( + val fom: LocalDate, + val tom: LocalDate, + val aktivitetIkkeMulig: AktivitetIkkeMulig?, + val avventendeInnspillTilArbeidsgiver: String?, + val behandlingsdager: Int?, + val gradert: Gradert?, + val reisetilskudd: Boolean +) + +data class AktivitetIkkeMulig( + val medisinskArsak: MedisinskArsak?, + val arbeidsrelatertArsak: ArbeidsrelatertArsak? +) + +data class ArbeidsrelatertArsak( + val beskrivelse: String?, + val arsak: List +) + +data class MedisinskArsak(val beskrivelse: String?, val arsak: List) + +enum class ArbeidsrelatertArsakType( + val codeValue: String, + val text: String, + val oid: String = "2.16.578.1.12.4.1.1.8132" +) { + MANGLENDE_TILRETTELEGGING("1", "Manglende tilrettelegging på arbeidsplassen"), + ANNET("9", "Annet") +} + +enum class MedisinskArsakType( + val codeValue: String, + val text: String, + val oid: String = "2.16.578.1.12.4.1.1.8133" +) { + TILSTAND_HINDRER_AKTIVITET("1", "Helsetilstanden hindrer pasienten i å være i aktivitet"), + AKTIVITET_FORVERRER_TILSTAND("2", "Aktivitet vil forverre helsetilstanden"), + AKTIVITET_FORHINDRER_BEDRING("3", "Aktivitet vil hindre/forsinke bedring av helsetilstanden"), + ANNET("9", "Annet") +} + +data class Gradert(val reisetilskudd: Boolean, val grad: Int) + +data class Prognose( + val arbeidsforEtterPeriode: Boolean, + val hensynArbeidsplassen: String?, + val erIArbeid: ErIArbeid?, + val erIkkeIArbeid: ErIkkeIArbeid? +) + +data class ErIArbeid( + val egetArbeidPaSikt: Boolean, + val annetArbeidPaSikt: Boolean, + val arbeidFOM: LocalDate?, + val vurderingsdato: LocalDate? +) + +data class ErIkkeIArbeid( + val arbeidsforPaSikt: Boolean, + val arbeidsforFOM: LocalDate?, + val vurderingsdato: LocalDate? +) + +data class MeldingTilNAV(val bistandUmiddelbart: Boolean, val beskrivBistand: String?) + +data class KontaktMedPasient(val kontaktDato: LocalDate?, val begrunnelseIkkeKontakt: String?) + +data class Behandler( + val fornavn: String, + val mellomnavn: String?, + val etternavn: String, + val aktoerId: String, + val fnr: String, + val hpr: String?, + val her: String?, + val adresse: Adresse, + val tlf: String? +) + +data class Adresse( + val gate: String?, + val postnummer: Int?, + val kommune: String?, + val postboks: String?, + val land: String? +) + +data class AvsenderSystem(val navn: String, val versjon: String) + +data class SporsmalSvar( + val sporsmal: String, + val svar: String, + val restriksjoner: List +) + +enum class SvarRestriksjon( + val codeValue: String, + val text: String, + val oid: String = "2.16.578.1.12.4.1.1.8134" +) { + SKJERMET_FOR_ARBEIDSGIVER("A", "Informasjonen skal ikke vises arbeidsgiver"), + SKJERMET_FOR_PASIENT("P", "Informasjonen skal ikke vises pasient"), + SKJERMET_FOR_NAV("N", "Informasjonen skal ikke vises NAV") +} + +enum class AnnenFraverGrunn( + val codeValue: String, + val text: String, + val oid: String = "2.16.578.1.12.4.1.1.8131" +) { + GODKJENT_HELSEINSTITUSJON("1", "Når vedkommende er innlagt i en godkjent helseinstitusjon"), + BEHANDLING_FORHINDRER_ARBEID( + "2", + "Når vedkommende er under behandling og legen erklærer at behandlingen gjør det nødvendig at vedkommende ikke arbeider" + ), + ARBEIDSRETTET_TILTAK("3", "Når vedkommende deltar på et arbeidsrettet tiltak"), + MOTTAR_TILSKUDD_GRUNNET_HELSETILSTAND( + "4", + "Når vedkommende på grunn av sykdom, skade eller lyte får tilskott når vedkommende på grunn av sykdom, skade eller lyte får tilskott" + ), + NODVENDIG_KONTROLLUNDENRSOKELSE( + "5", + "Når vedkommende er til nødvendig kontrollundersøkelse som krever minst 24 timers fravær, reisetid medregnet" + ), + SMITTEFARE( + "6", + "Når vedkommende myndighet har nedlagt forbud mot at han eller hun arbeider på grunn av smittefare" + ), + ABORT("7", "Når vedkommende er arbeidsufør som følge av svangerskapsavbrudd"), + UFOR_GRUNNET_BARNLOSHET( + "8", + "Når vedkommende er arbeidsufør som følge av behandling for barnløshet" + ), + DONOR("9", "Når vedkommende er donor eller er under vurdering som donor"), + BEHANDLING_STERILISERING( + "10", + "Når vedkommende er arbeidsufør som følge av behandling i forbindelse med sterilisering" + ) +} diff --git a/src/main/kotlin/no/nav/syfo/model/UtenlandskSykmelding.kt b/src/main/kotlin/no/nav/syfo/model/UtenlandskSykmelding.kt new file mode 100644 index 00000000..2b49770e --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/model/UtenlandskSykmelding.kt @@ -0,0 +1,6 @@ +package no.nav.syfo.model + +data class UtenlandskSykmelding( + val land: String, + val folkeRegistertAdresseErBrakkeEllerTilsvarende: Boolean, +) diff --git a/src/main/kotlin/no/nav/syfo/model/ValidationResult.kt b/src/main/kotlin/no/nav/syfo/model/ValidationResult.kt new file mode 100644 index 00000000..2f2ea93d --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/model/ValidationResult.kt @@ -0,0 +1,16 @@ +package no.nav.syfo.model + +data class ValidationResult(val status: Status, val ruleHits: List) + +data class RuleInfo( + val ruleName: String, + val messageForSender: String, + val messageForUser: String, + val ruleStatus: Status +) + +enum class Status { + OK, + MANUAL_PROCESSING, + INVALID +} diff --git a/src/main/kotlin/no/nav/syfo/mq/MQEnvironment.kt b/src/main/kotlin/no/nav/syfo/mq/MQEnvironment.kt new file mode 100644 index 00000000..b87984b3 --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/mq/MQEnvironment.kt @@ -0,0 +1,12 @@ +package no.nav.syfo.mq + +data class MQEnvironment( + val MQ_KEYSTORE_PASSWORD: String = getEnvVar("MQ_KEYSTORE_PASSWORD"), + val MQ_KEYSTORE_PATH: String = getEnvVar("MQ_KEYSTORE_PATH") +) { + companion object { + fun getEnvVar(varName: String, defaultValue: String? = null) = + System.getenv(varName) + ?: defaultValue ?: throw RuntimeException("Missing required variable \"$varName\"") + } +} diff --git a/src/main/kotlin/no/nav/syfo/mq/MqTlsUtils.kt b/src/main/kotlin/no/nav/syfo/mq/MqTlsUtils.kt new file mode 100644 index 00000000..52834379 --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/mq/MqTlsUtils.kt @@ -0,0 +1,16 @@ +package no.nav.syfo.mq + +import java.util.Properties + +class MqTlsUtils { + companion object { + fun getMqTlsConfig(): Properties { + return Properties().also { + val mqEnv = MQEnvironment() + it["javax.net.ssl.keyStore"] = mqEnv.MQ_KEYSTORE_PATH + it["javax.net.ssl.keyStorePassword"] = mqEnv.MQ_KEYSTORE_PASSWORD + it["javax.net.ssl.keyStoreType"] = "jks" + } + } + } +} diff --git a/src/main/kotlin/no/nav/syfo/mq/MqUtils.kt b/src/main/kotlin/no/nav/syfo/mq/MqUtils.kt new file mode 100644 index 00000000..5f3480f2 --- /dev/null +++ b/src/main/kotlin/no/nav/syfo/mq/MqUtils.kt @@ -0,0 +1,36 @@ +package no.nav.syfo.mq + +import com.ibm.mq.jms.MQConnectionFactory +import com.ibm.msg.client.wmq.WMQConstants +import com.ibm.msg.client.wmq.compat.base.internal.MQC +import javax.jms.MessageConsumer +import javax.jms.MessageProducer +import javax.jms.Session +import javax.net.ssl.SSLSocketFactory + +interface MqConfig { + val mqHostname: String + val mqPort: Int + val mqGatewayName: String + val mqChannelName: String +} + +fun connectionFactory(config: MqConfig) = + MQConnectionFactory().apply { + hostName = config.mqHostname + port = config.mqPort + queueManager = config.mqGatewayName + transportType = WMQConstants.WMQ_CM_CLIENT + channel = config.mqChannelName + ccsid = 1208 + sslSocketFactory = SSLSocketFactory.getDefault() + sslCipherSuite = "*TLS13ORHIGHER" + setIntProperty(WMQConstants.JMS_IBM_ENCODING, MQC.MQENC_NATIVE) + setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET, 1208) + } + +fun Session.consumerForQueue(queueName: String): MessageConsumer = + createConsumer(createQueue(queueName)) + +fun Session.producerForQueue(queueName: String): MessageProducer = + createProducer(createQueue(queueName)) diff --git a/src/main/kotlin/no/nav/syfo/util/MetrikkUtil.kt b/src/main/kotlin/no/nav/syfo/util/MetrikkUtil.kt index cca29bb6..5bc7b722 100644 --- a/src/main/kotlin/no/nav/syfo/util/MetrikkUtil.kt +++ b/src/main/kotlin/no/nav/syfo/util/MetrikkUtil.kt @@ -10,11 +10,11 @@ fun countNewDiagnoseCode(medisinskVurdering: MedisinskVurdering) { val newDiagnoseCode = listOf("R991", "U071") if ( medisinskVurdering.hovedDiagnose != null && - newDiagnoseCode.contains(medisinskVurdering.hovedDiagnose!!.kode) + newDiagnoseCode.contains(medisinskVurdering.hovedDiagnose.kode) ) { NEW_DIAGNOSE_COUNTER.inc() } else if ( - !medisinskVurdering.biDiagnoser.isNullOrEmpty() && + medisinskVurdering.biDiagnoser.isNotEmpty() && medisinskVurdering.biDiagnoser.find { newDiagnoseCode.contains(it.kode) } != null ) { NEW_DIAGNOSE_COUNTER.inc()