diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 0000000000..e9ce865bf6
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,24 @@
+---
+engines:
+ duplication:
+ enabled: true
+ config:
+ languages:
+ - ruby
+ - javascript
+ - python
+ - php
+ fixme:
+ enabled: true
+ rubocop:
+ enabled: true
+ratings:
+ paths:
+ - "**.inc"
+ - "**.js"
+ - "**.jsx"
+ - "**.module"
+ - "**.php"
+ - "**.py"
+ - "**.rb"
+exclude_paths: []
diff --git a/.github/workflows/build_publish.yml b/.github/workflows/build_publish.yml
new file mode 100644
index 0000000000..0d65180b02
--- /dev/null
+++ b/.github/workflows/build_publish.yml
@@ -0,0 +1,39 @@
+name: Build and Publish package
+on:
+ push:
+ branches: [ master ]
+ workflow_dispatch:
+
+jobs:
+ build-publish-package:
+ name: Build and Publish package
+ runs-on: macos-11
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ server-id: nexus-sonatype
+ server-username: NEXUS_USERNAME
+ server-password: NEXUS_PASSWORD
+ - name: Cache Maven packages
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2
+ - name: Install Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '2.7'
+ bundler-cache: true
+ - name: Install compass
+ run: |
+ sudo gem install compass -v 1.0.3
+ - name: Build and deploy with Maven
+ run: ./mvnw --no-transfer-progress clean -U deploy
+ env:
+ NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
+ NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
+
diff --git a/.github/workflows/validate_pr.yml b/.github/workflows/validate_pr.yml
new file mode 100644
index 0000000000..9049223614
--- /dev/null
+++ b/.github/workflows/validate_pr.yml
@@ -0,0 +1,33 @@
+name: Validate PR
+on:
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ build:
+ name: Build
+ runs-on: macos-11
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Cache Maven packages
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2
+ - name: Install Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '2.7'
+ bundler-cache: true
+ - name: Install compass
+ run: |
+ sudo gem install compass -v 1.0.3
+ - name: Build with Maven
+ run: |
+ ./mvnw install -U -Dmaven.javadoc.skip=true -V -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
+ ./mvnw verify -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
diff --git a/.gitignore b/.gitignore
index e08c79410a..ac7e5e5d4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,20 @@
target/
.DS_Store
+.idea
+*.iml
+*/logs/*
+logs/*
+classes/
+.sass-cache
+.rubygems-provided
+.rubygems
+sass-external
+bahmnicore-omod/src/main/webapp/resources/styles/bahmnicore.css
+.mvn/wrapper/*.jar
+.vscode
+
+
+# Eclipse project files
+.settings
+.classpath
+.project
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000000..8c79a83ae4
--- /dev/null
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000000..3f1d2224ad
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,1156 @@
+AllCops:
+ DisabledByDefault: true
+
+#################### Lint ################################
+
+Lint/AmbiguousOperator:
+ Description: >-
+ Checks for ambiguous operators in the first argument of a
+ method invocation without parentheses.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args'
+ Enabled: true
+
+Lint/AmbiguousRegexpLiteral:
+ Description: >-
+ Checks for ambiguous regexp literals in the first argument of
+ a method invocation without parenthesis.
+ Enabled: true
+
+Lint/AssignmentInCondition:
+ Description: "Don't use assignment in conditions."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition'
+ Enabled: true
+
+Lint/BlockAlignment:
+ Description: 'Align block ends correctly.'
+ Enabled: true
+
+Lint/CircularArgumentReference:
+ Description: "Don't refer to the keyword argument in the default value."
+ Enabled: true
+
+Lint/ConditionPosition:
+ Description: >-
+ Checks for condition placed in a confusing position relative to
+ the keyword.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition'
+ Enabled: true
+
+Lint/Debugger:
+ Description: 'Check for debugger calls.'
+ Enabled: true
+
+Lint/DefEndAlignment:
+ Description: 'Align ends corresponding to defs correctly.'
+ Enabled: true
+
+Lint/DeprecatedClassMethods:
+ Description: 'Check for deprecated class method calls.'
+ Enabled: true
+
+Lint/DuplicateMethods:
+ Description: 'Check for duplicate methods calls.'
+ Enabled: true
+
+Lint/EachWithObjectArgument:
+ Description: 'Check for immutable argument given to each_with_object.'
+ Enabled: true
+
+Lint/ElseLayout:
+ Description: 'Check for odd code arrangement in an else block.'
+ Enabled: true
+
+Lint/EmptyEnsure:
+ Description: 'Checks for empty ensure block.'
+ Enabled: true
+
+Lint/EmptyInterpolation:
+ Description: 'Checks for empty string interpolation.'
+ Enabled: true
+
+Lint/EndAlignment:
+ Description: 'Align ends correctly.'
+ Enabled: true
+
+Lint/EndInMethod:
+ Description: 'END blocks should not be placed inside method definitions.'
+ Enabled: true
+
+Lint/EnsureReturn:
+ Description: 'Do not use return in an ensure block.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure'
+ Enabled: true
+
+Lint/Eval:
+ Description: 'The use of eval represents a serious security risk.'
+ Enabled: true
+
+Lint/FormatParameterMismatch:
+ Description: 'The number of parameters to format/sprint must match the fields.'
+ Enabled: true
+
+Lint/HandleExceptions:
+ Description: "Don't suppress exception."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'
+ Enabled: true
+
+Lint/InvalidCharacterLiteral:
+ Description: >-
+ Checks for invalid character literals with a non-escaped
+ whitespace character.
+ Enabled: true
+
+Lint/LiteralInCondition:
+ Description: 'Checks of literals used in conditions.'
+ Enabled: true
+
+Lint/LiteralInInterpolation:
+ Description: 'Checks for literals used in interpolation.'
+ Enabled: true
+
+Lint/Loop:
+ Description: >-
+ Use Kernel#loop with break rather than begin/end/until or
+ begin/end/while for post-loop tests.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break'
+ Enabled: true
+
+Lint/NestedMethodDefinition:
+ Description: 'Do not use nested method definitions.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods'
+ Enabled: true
+
+Lint/NonLocalExitFromIterator:
+ Description: 'Do not use return in iterator to cause non-local exit.'
+ Enabled: true
+
+Lint/ParenthesesAsGroupedExpression:
+ Description: >-
+ Checks for method calls with a space before the opening
+ parenthesis.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
+ Enabled: true
+
+Lint/RequireParentheses:
+ Description: >-
+ Use parentheses in the method call to avoid confusion
+ about precedence.
+ Enabled: true
+
+Lint/RescueException:
+ Description: 'Avoid rescuing the Exception class.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues'
+ Enabled: true
+
+Lint/ShadowingOuterLocalVariable:
+ Description: >-
+ Do not use the same name as outer local variable
+ for block arguments or block local variables.
+ Enabled: true
+
+Lint/StringConversionInInterpolation:
+ Description: 'Checks for Object#to_s usage in string interpolation.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s'
+ Enabled: true
+
+Lint/UnderscorePrefixedVariableName:
+ Description: 'Do not use prefix `_` for a variable that is used.'
+ Enabled: true
+
+Lint/UnneededDisable:
+ Description: >-
+ Checks for rubocop:disable comments that can be removed.
+ Note: this cop is not disabled when disabling all cops.
+ It must be explicitly disabled.
+ Enabled: true
+
+Lint/UnusedBlockArgument:
+ Description: 'Checks for unused block arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
+ Enabled: true
+
+Lint/UnusedMethodArgument:
+ Description: 'Checks for unused method arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
+ Enabled: true
+
+Lint/UnreachableCode:
+ Description: 'Unreachable code.'
+ Enabled: true
+
+Lint/UselessAccessModifier:
+ Description: 'Checks for useless access modifiers.'
+ Enabled: true
+
+Lint/UselessAssignment:
+ Description: 'Checks for useless assignment to a local variable.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
+ Enabled: true
+
+Lint/UselessComparison:
+ Description: 'Checks for comparison of something with itself.'
+ Enabled: true
+
+Lint/UselessElseWithoutRescue:
+ Description: 'Checks for useless `else` in `begin..end` without `rescue`.'
+ Enabled: true
+
+Lint/UselessSetterCall:
+ Description: 'Checks for useless setter call to a local variable.'
+ Enabled: true
+
+Lint/Void:
+ Description: 'Possible use of operator/literal/variable in void context.'
+ Enabled: true
+
+###################### Metrics ####################################
+
+Metrics/AbcSize:
+ Description: >-
+ A calculated magnitude based on number of assignments,
+ branches, and conditions.
+ Reference: 'http://c2.com/cgi/wiki?AbcMetric'
+ Enabled: false
+ Max: 20
+
+Metrics/BlockNesting:
+ Description: 'Avoid excessive block nesting'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count'
+ Enabled: true
+ Max: 4
+
+Metrics/ClassLength:
+ Description: 'Avoid classes longer than 250 lines of code.'
+ Enabled: true
+ Max: 250
+
+Metrics/CyclomaticComplexity:
+ Description: >-
+ A complexity metric that is strongly correlated to the number
+ of test cases needed to validate a method.
+ Enabled: true
+
+Metrics/LineLength:
+ Description: 'Limit lines to 80 characters.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
+ Enabled: false
+
+Metrics/MethodLength:
+ Description: 'Avoid methods longer than 30 lines of code.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
+ Enabled: true
+ Max: 30
+
+Metrics/ModuleLength:
+ Description: 'Avoid modules longer than 250 lines of code.'
+ Enabled: true
+ Max: 250
+
+Metrics/ParameterLists:
+ Description: 'Avoid parameter lists longer than three or four parameters.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params'
+ Enabled: true
+
+Metrics/PerceivedComplexity:
+ Description: >-
+ A complexity metric geared towards measuring complexity for a
+ human reader.
+ Enabled: false
+
+##################### Performance #############################
+
+Performance/Count:
+ Description: >-
+ Use `count` instead of `select...size`, `reject...size`,
+ `select...count`, `reject...count`, `select...length`,
+ and `reject...length`.
+ Enabled: true
+
+Performance/Detect:
+ Description: >-
+ Use `detect` instead of `select.first`, `find_all.first`,
+ `select.last`, and `find_all.last`.
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code'
+ Enabled: true
+
+Performance/FlatMap:
+ Description: >-
+ Use `Enumerable#flat_map`
+ instead of `Enumerable#map...Array#flatten(1)`
+ or `Enumberable#collect..Array#flatten(1)`
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code'
+ Enabled: true
+ EnabledForFlattenWithoutParams: false
+ # If enabled, this cop will warn about usages of
+ # `flatten` being called without any parameters.
+ # This can be dangerous since `flat_map` will only flatten 1 level, and
+ # `flatten` without any parameters can flatten multiple levels.
+
+Performance/ReverseEach:
+ Description: 'Use `reverse_each` instead of `reverse.each`.'
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code'
+ Enabled: true
+
+Performance/Sample:
+ Description: >-
+ Use `sample` instead of `shuffle.first`,
+ `shuffle.last`, and `shuffle[Fixnum]`.
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code'
+ Enabled: true
+
+Performance/Size:
+ Description: >-
+ Use `size` instead of `count` for counting
+ the number of elements in `Array` and `Hash`.
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code'
+ Enabled: true
+
+Performance/StringReplacement:
+ Description: >-
+ Use `tr` instead of `gsub` when you are replacing the same
+ number of characters. Use `delete` instead of `gsub` when
+ you are deleting characters.
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code'
+ Enabled: true
+
+##################### Rails ##################################
+
+Rails/ActionFilter:
+ Description: 'Enforces consistent use of action filter methods.'
+ Enabled: false
+
+Rails/Date:
+ Description: >-
+ Checks the correct usage of date aware methods,
+ such as Date.today, Date.current etc.
+ Enabled: false
+
+Rails/Delegate:
+ Description: 'Prefer delegate method for delegations.'
+ Enabled: false
+
+Rails/FindBy:
+ Description: 'Prefer find_by over where.first.'
+ Enabled: false
+
+Rails/FindEach:
+ Description: 'Prefer all.find_each over all.find.'
+ Enabled: false
+
+Rails/HasAndBelongsToMany:
+ Description: 'Prefer has_many :through to has_and_belongs_to_many.'
+ Enabled: false
+
+Rails/Output:
+ Description: 'Checks for calls to puts, print, etc.'
+ Enabled: false
+
+Rails/ReadWriteAttribute:
+ Description: >-
+ Checks for read_attribute(:attr) and
+ write_attribute(:attr, val).
+ Enabled: false
+
+Rails/ScopeArgs:
+ Description: 'Checks the arguments of ActiveRecord scopes.'
+ Enabled: false
+
+Rails/TimeZone:
+ Description: 'Checks the correct usage of time zone aware methods.'
+ StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time'
+ Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
+ Enabled: false
+
+Rails/Validation:
+ Description: 'Use validates :attribute, hash of validations.'
+ Enabled: false
+
+################## Style #################################
+
+Style/AccessModifierIndentation:
+ Description: Check indentation of private/protected visibility modifiers.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected'
+ Enabled: false
+
+Style/AccessorMethodName:
+ Description: Check the naming of accessor methods for get_/set_.
+ Enabled: false
+
+Style/Alias:
+ Description: 'Use alias_method instead of alias.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method'
+ Enabled: false
+
+Style/AlignArray:
+ Description: >-
+ Align the elements of an array literal if they span more than
+ one line.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays'
+ Enabled: false
+
+Style/AlignHash:
+ Description: >-
+ Align the elements of a hash literal if they span more than
+ one line.
+ Enabled: false
+
+Style/AlignParameters:
+ Description: >-
+ Align the parameters of a method call if they span more
+ than one line.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent'
+ Enabled: false
+
+Style/AndOr:
+ Description: 'Use &&/|| instead of and/or.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or'
+ Enabled: false
+
+Style/ArrayJoin:
+ Description: 'Use Array#join instead of Array#*.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join'
+ Enabled: false
+
+Style/AsciiComments:
+ Description: 'Use only ascii symbols in comments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments'
+ Enabled: false
+
+Style/AsciiIdentifiers:
+ Description: 'Use only ascii symbols in identifiers.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers'
+ Enabled: false
+
+Style/Attr:
+ Description: 'Checks for uses of Module#attr.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr'
+ Enabled: false
+
+Style/BeginBlock:
+ Description: 'Avoid the use of BEGIN blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks'
+ Enabled: false
+
+Style/BarePercentLiterals:
+ Description: 'Checks if usage of %() or %Q() matches configuration.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand'
+ Enabled: false
+
+Style/BlockComments:
+ Description: 'Do not use block comments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments'
+ Enabled: false
+
+Style/BlockEndNewline:
+ Description: 'Put end statement of multiline block on its own line.'
+ Enabled: false
+
+Style/BlockDelimiters:
+ Description: >-
+ Avoid using {...} for multi-line blocks (multiline chaining is
+ always ugly).
+ Prefer {...} over do...end for single-line blocks.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
+ Enabled: false
+
+Style/BracesAroundHashParameters:
+ Description: 'Enforce braces style around hash parameters.'
+ Enabled: false
+
+Style/CaseEquality:
+ Description: 'Avoid explicit use of the case equality operator(===).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality'
+ Enabled: false
+
+Style/CaseIndentation:
+ Description: 'Indentation of when in a case/when/[else/]end.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case'
+ Enabled: false
+
+Style/CharacterLiteral:
+ Description: 'Checks for uses of character literals.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals'
+ Enabled: false
+
+Style/ClassAndModuleCamelCase:
+ Description: 'Use CamelCase for classes and modules.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes'
+ Enabled: false
+
+Style/ClassAndModuleChildren:
+ Description: 'Checks style of children classes and modules.'
+ Enabled: false
+
+Style/ClassCheck:
+ Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.'
+ Enabled: false
+
+Style/ClassMethods:
+ Description: 'Use self when defining module/class methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods'
+ Enabled: false
+
+Style/ClassVars:
+ Description: 'Avoid the use of class variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars'
+ Enabled: false
+
+Style/ClosingParenthesisIndentation:
+ Description: 'Checks the indentation of hanging closing parentheses.'
+ Enabled: false
+
+Style/ColonMethodCall:
+ Description: 'Do not use :: for method call.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons'
+ Enabled: false
+
+Style/CommandLiteral:
+ Description: 'Use `` or %x around command literals.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x'
+ Enabled: false
+
+Style/CommentAnnotation:
+ Description: 'Checks formatting of annotation comments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords'
+ Enabled: false
+
+Style/CommentIndentation:
+ Description: 'Indentation of comments.'
+ Enabled: false
+
+Style/ConstantName:
+ Description: 'Constants should use SCREAMING_SNAKE_CASE.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case'
+ Enabled: false
+
+Style/DefWithParentheses:
+ Description: 'Use def with parentheses when there are arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
+ Enabled: false
+
+Style/DeprecatedHashMethods:
+ Description: 'Checks for use of deprecated Hash methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key'
+ Enabled: false
+
+Style/Documentation:
+ Description: 'Document classes and non-namespace modules.'
+ Enabled: false
+
+Style/DotPosition:
+ Description: 'Checks the position of the dot in multi-line method calls.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains'
+ Enabled: false
+
+Style/DoubleNegation:
+ Description: 'Checks for uses of double negation (!!).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang'
+ Enabled: false
+
+Style/EachWithObject:
+ Description: 'Prefer `each_with_object` over `inject` or `reduce`.'
+ Enabled: false
+
+Style/ElseAlignment:
+ Description: 'Align elses and elsifs correctly.'
+ Enabled: false
+
+Style/EmptyElse:
+ Description: 'Avoid empty else-clauses.'
+ Enabled: false
+
+Style/EmptyLineBetweenDefs:
+ Description: 'Use empty lines between defs.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods'
+ Enabled: false
+
+Style/EmptyLines:
+ Description: "Don't use several empty lines in a row."
+ Enabled: false
+
+Style/EmptyLinesAroundAccessModifier:
+ Description: "Keep blank lines around access modifiers."
+ Enabled: false
+
+Style/EmptyLinesAroundBlockBody:
+ Description: "Keeps track of empty lines around block bodies."
+ Enabled: false
+
+Style/EmptyLinesAroundClassBody:
+ Description: "Keeps track of empty lines around class bodies."
+ Enabled: false
+
+Style/EmptyLinesAroundModuleBody:
+ Description: "Keeps track of empty lines around module bodies."
+ Enabled: false
+
+Style/EmptyLinesAroundMethodBody:
+ Description: "Keeps track of empty lines around method bodies."
+ Enabled: false
+
+Style/EmptyLiteral:
+ Description: 'Prefer literals to Array.new/Hash.new/String.new.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash'
+ Enabled: false
+
+Style/EndBlock:
+ Description: 'Avoid the use of END blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks'
+ Enabled: false
+
+Style/EndOfLine:
+ Description: 'Use Unix-style line endings.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf'
+ Enabled: false
+
+Style/EvenOdd:
+ Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
+ Enabled: false
+
+Style/ExtraSpacing:
+ Description: 'Do not use unnecessary spacing.'
+ Enabled: false
+
+Style/FileName:
+ Description: 'Use snake_case for source file names.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
+ Enabled: false
+
+Style/InitialIndentation:
+ Description: >-
+ Checks the indentation of the first non-blank non-comment line in a file.
+ Enabled: false
+
+Style/FirstParameterIndentation:
+ Description: 'Checks the indentation of the first parameter in a method call.'
+ Enabled: false
+
+Style/FlipFlop:
+ Description: 'Checks for flip flops'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops'
+ Enabled: false
+
+Style/For:
+ Description: 'Checks use of for or each in multiline loops.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops'
+ Enabled: false
+
+Style/FormatString:
+ Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf'
+ Enabled: false
+
+Style/GlobalVars:
+ Description: 'Do not introduce global variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars'
+ Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html'
+ Enabled: false
+
+Style/GuardClause:
+ Description: 'Check for conditionals that can be replaced with guard clauses'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
+ Enabled: false
+
+Style/HashSyntax:
+ Description: >-
+ Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax
+ { :a => 1, :b => 2 }.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals'
+ Enabled: false
+
+Style/IfUnlessModifier:
+ Description: >-
+ Favor modifier if/unless usage when you have a
+ single-line body.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier'
+ Enabled: false
+
+Style/IfWithSemicolon:
+ Description: 'Do not use if x; .... Use the ternary operator instead.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs'
+ Enabled: false
+
+Style/IndentationConsistency:
+ Description: 'Keep indentation straight.'
+ Enabled: false
+
+Style/IndentationWidth:
+ Description: 'Use 2 spaces for indentation.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
+ Enabled: false
+
+Style/IndentArray:
+ Description: >-
+ Checks the indentation of the first element in an array
+ literal.
+ Enabled: false
+
+Style/IndentHash:
+ Description: 'Checks the indentation of the first key in a hash literal.'
+ Enabled: false
+
+Style/InfiniteLoop:
+ Description: 'Use Kernel#loop for infinite loops.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop'
+ Enabled: false
+
+Style/Lambda:
+ Description: 'Use the new lambda literal syntax for single-line blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line'
+ Enabled: false
+
+Style/LambdaCall:
+ Description: 'Use lambda.call(...) instead of lambda.(...).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call'
+ Enabled: false
+
+Style/LeadingCommentSpace:
+ Description: 'Comments should start with a space.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space'
+ Enabled: false
+
+Style/LineEndConcatenation:
+ Description: >-
+ Use \ instead of + or << to concatenate two string literals at
+ line end.
+ Enabled: false
+
+Style/MethodCallParentheses:
+ Description: 'Do not use parentheses for method calls with no arguments.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens'
+ Enabled: false
+
+Style/MethodDefParentheses:
+ Description: >-
+ Checks if the method definitions have or don't have
+ parentheses.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens'
+ Enabled: false
+
+Style/MethodName:
+ Description: 'Use the configured style when naming methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
+ Enabled: false
+
+Style/ModuleFunction:
+ Description: 'Checks for usage of `extend self` in modules.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function'
+ Enabled: false
+
+Style/MultilineBlockChain:
+ Description: 'Avoid multi-line chains of blocks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
+ Enabled: false
+
+Style/MultilineBlockLayout:
+ Description: 'Ensures newlines after multiline block do statements.'
+ Enabled: false
+
+Style/MultilineIfThen:
+ Description: 'Do not use then for multi-line if/unless.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then'
+ Enabled: false
+
+Style/MultilineOperationIndentation:
+ Description: >-
+ Checks indentation of binary operations that span more than
+ one line.
+ Enabled: false
+
+Style/MultilineTernaryOperator:
+ Description: >-
+ Avoid multi-line ?: (the ternary operator);
+ use if/unless instead.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary'
+ Enabled: false
+
+Style/NegatedIf:
+ Description: >-
+ Favor unless over if for negative conditions
+ (or control flow or).
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives'
+ Enabled: false
+
+Style/NegatedWhile:
+ Description: 'Favor until over while for negative conditions.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives'
+ Enabled: false
+
+Style/NestedTernaryOperator:
+ Description: 'Use one expression per branch in a ternary operator.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary'
+ Enabled: false
+
+Style/Next:
+ Description: 'Use `next` to skip iteration instead of a condition at the end.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals'
+ Enabled: false
+
+Style/NilComparison:
+ Description: 'Prefer x.nil? to x == nil.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
+ Enabled: false
+
+Style/NonNilCheck:
+ Description: 'Checks for redundant nil checks.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks'
+ Enabled: false
+
+Style/Not:
+ Description: 'Use ! instead of not.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not'
+ Enabled: false
+
+Style/NumericLiterals:
+ Description: >-
+ Add underscores to large numeric literals to improve their
+ readability.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics'
+ Enabled: false
+
+Style/OneLineConditional:
+ Description: >-
+ Favor the ternary operator(?:) over
+ if/then/else/end constructs.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator'
+ Enabled: false
+
+Style/OpMethod:
+ Description: 'When defining binary operators, name the argument other.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
+ Enabled: false
+
+Style/OptionalArguments:
+ Description: >-
+ Checks for optional arguments that do not appear at the end
+ of the argument list
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments'
+ Enabled: false
+
+Style/ParallelAssignment:
+ Description: >-
+ Check for simple usages of parallel assignment.
+ It will only warn when the number of variables
+ matches on both sides of the assignment.
+ This also provides performance benefits
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment'
+ Enabled: false
+
+Style/ParenthesesAroundCondition:
+ Description: >-
+ Don't use parentheses around the condition of an
+ if/unless/while.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if'
+ Enabled: false
+
+Style/PercentLiteralDelimiters:
+ Description: 'Use `%`-literal delimiters consistently'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces'
+ Enabled: false
+
+Style/PercentQLiterals:
+ Description: 'Checks if uses of %Q/%q match the configured preference.'
+ Enabled: false
+
+Style/PerlBackrefs:
+ Description: 'Avoid Perl-style regex back references.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'
+ Enabled: false
+
+Style/PredicateName:
+ Description: 'Check the names of predicate methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark'
+ Enabled: false
+
+Style/Proc:
+ Description: 'Use proc instead of Proc.new.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc'
+ Enabled: false
+
+Style/RaiseArgs:
+ Description: 'Checks the arguments passed to raise/fail.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages'
+ Enabled: false
+
+Style/RedundantBegin:
+ Description: "Don't use begin blocks when they are not needed."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit'
+ Enabled: false
+
+Style/RedundantException:
+ Description: "Checks for an obsolete RuntimeException argument in raise/fail."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror'
+ Enabled: false
+
+Style/RedundantReturn:
+ Description: "Don't use return where it's not required."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return'
+ Enabled: false
+
+Style/RedundantSelf:
+ Description: "Don't use self where it's not needed."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required'
+ Enabled: false
+
+Style/RegexpLiteral:
+ Description: 'Use / or %r around regular expressions.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r'
+ Enabled: false
+
+Style/RescueEnsureAlignment:
+ Description: 'Align rescues and ensures correctly.'
+ Enabled: false
+
+Style/RescueModifier:
+ Description: 'Avoid using rescue in its modifier form.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers'
+ Enabled: false
+
+Style/SelfAssignment:
+ Description: >-
+ Checks for places where self-assignment shorthand should have
+ been used.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment'
+ Enabled: false
+
+Style/Semicolon:
+ Description: "Don't use semicolons to terminate expressions."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon'
+ Enabled: false
+
+Style/SignalException:
+ Description: 'Checks for proper usage of fail and raise.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method'
+ Enabled: false
+
+Style/SingleLineBlockParams:
+ Description: 'Enforces the names of some block params.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks'
+ Enabled: false
+
+Style/SingleLineMethods:
+ Description: 'Avoid single-line methods.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods'
+ Enabled: false
+
+Style/SpaceBeforeFirstArg:
+ Description: >-
+ Checks that exactly one space is used between a method name
+ and the first argument for method calls without parentheses.
+ Enabled: true
+
+Style/SpaceAfterColon:
+ Description: 'Use spaces after colons.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceAfterComma:
+ Description: 'Use spaces after commas.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceAroundKeyword:
+ Description: 'Use spaces around keywords.'
+ Enabled: false
+
+Style/SpaceAfterMethodName:
+ Description: >-
+ Do not put a space between a method name and the opening
+ parenthesis in a method definition.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces'
+ Enabled: false
+
+Style/SpaceAfterNot:
+ Description: Tracks redundant space after the ! operator.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang'
+ Enabled: false
+
+Style/SpaceAfterSemicolon:
+ Description: 'Use spaces after semicolons.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceBeforeBlockBraces:
+ Description: >-
+ Checks that the left block brace has or doesn't have space
+ before it.
+ Enabled: false
+
+Style/SpaceBeforeComma:
+ Description: 'No spaces before commas.'
+ Enabled: false
+
+Style/SpaceBeforeComment:
+ Description: >-
+ Checks for missing space between code and a comment on the
+ same line.
+ Enabled: false
+
+Style/SpaceBeforeSemicolon:
+ Description: 'No spaces before semicolons.'
+ Enabled: false
+
+Style/SpaceInsideBlockBraces:
+ Description: >-
+ Checks that block braces have or don't have surrounding space.
+ For blocks taking parameters, checks that the left brace has
+ or doesn't have trailing space.
+ Enabled: false
+
+Style/SpaceAroundBlockParameters:
+ Description: 'Checks the spacing inside and after block parameters pipes.'
+ Enabled: false
+
+Style/SpaceAroundEqualsInParameterDefault:
+ Description: >-
+ Checks that the equals signs in parameter default assignments
+ have or don't have surrounding space depending on
+ configuration.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals'
+ Enabled: false
+
+Style/SpaceAroundOperators:
+ Description: 'Use a single space around operators.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceInsideBrackets:
+ Description: 'No spaces after [ or before ].'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
+ Enabled: false
+
+Style/SpaceInsideHashLiteralBraces:
+ Description: "Use spaces inside hash literal braces - or don't."
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators'
+ Enabled: false
+
+Style/SpaceInsideParens:
+ Description: 'No spaces after ( or before ).'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces'
+ Enabled: false
+
+Style/SpaceInsideRangeLiteral:
+ Description: 'No spaces inside range literals.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals'
+ Enabled: false
+
+Style/SpaceInsideStringInterpolation:
+ Description: 'Checks for padding/surrounding spaces inside string interpolation.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation'
+ Enabled: false
+
+Style/SpecialGlobalVars:
+ Description: 'Avoid Perl-style global variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms'
+ Enabled: false
+
+Style/StringLiterals:
+ Description: 'Checks if uses of quotes match the configured preference.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals'
+ Enabled: false
+
+Style/StringLiteralsInInterpolation:
+ Description: >-
+ Checks if uses of quotes inside expressions in interpolated
+ strings match the configured preference.
+ Enabled: false
+
+Style/StructInheritance:
+ Description: 'Checks for inheritance from Struct.new.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new'
+ Enabled: false
+
+Style/SymbolLiteral:
+ Description: 'Use plain symbols instead of string symbols when possible.'
+ Enabled: false
+
+Style/SymbolProc:
+ Description: 'Use symbols as procs instead of blocks when possible.'
+ Enabled: false
+
+Style/Tab:
+ Description: 'No hard tabs.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
+ Enabled: false
+
+Style/TrailingBlankLines:
+ Description: 'Checks trailing blank lines and final newline.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof'
+ Enabled: false
+
+Style/TrailingCommaInArguments:
+ Description: 'Checks for trailing comma in parameter lists.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma'
+ Enabled: false
+
+Style/TrailingCommaInLiteral:
+ Description: 'Checks for trailing comma in literals.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas'
+ Enabled: false
+
+Style/TrailingWhitespace:
+ Description: 'Avoid trailing whitespace.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace'
+ Enabled: false
+
+Style/TrivialAccessors:
+ Description: 'Prefer attr_* methods to trivial readers/writers.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'
+ Enabled: false
+
+Style/UnlessElse:
+ Description: >-
+ Do not use unless with else. Rewrite these with the positive
+ case first.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless'
+ Enabled: false
+
+Style/UnneededCapitalW:
+ Description: 'Checks for %W when interpolation is not needed.'
+ Enabled: false
+
+Style/UnneededPercentQ:
+ Description: 'Checks for %q/%Q when single quotes or double quotes would do.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q'
+ Enabled: false
+
+Style/TrailingUnderscoreVariable:
+ Description: >-
+ Checks for the usage of unneeded trailing underscores at the
+ end of parallel variable assignment.
+ Enabled: false
+
+Style/VariableInterpolation:
+ Description: >-
+ Don't interpolate global, instance and class variables
+ directly in strings.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate'
+ Enabled: false
+
+Style/VariableName:
+ Description: 'Use the configured style when naming variables.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars'
+ Enabled: false
+
+Style/WhenThen:
+ Description: 'Use when x then ... for one-line cases.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases'
+ Enabled: false
+
+Style/WhileUntilDo:
+ Description: 'Checks for redundant do after while or until.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do'
+ Enabled: false
+
+Style/WhileUntilModifier:
+ Description: >-
+ Favor modifier while/until usage when you have a
+ single-line body.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier'
+ Enabled: false
+
+Style/WordArray:
+ Description: 'Use %w or %W for arrays of words.'
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w'
+ Enabled: false
diff --git a/DrugInventory.java b/DrugInventory.java
deleted file mode 100644
index 92c2c6de44..0000000000
--- a/DrugInventory.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package org.raxa.module.raxacore;
-
-/**
- * Copyright 2012, Raxa
- *
- * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-import java.io.Serializable;
-import java.util.Date;
-
-import org.openmrs.BaseOpenmrsMetadata;
-
-public class DrugInventory extends BaseOpenmrsMetadata implements Serializable {
-
- private Integer drugInventoryId;
-
- private String drugInventoryName;
-
- private String drugInventoryDescription;
-
- private Integer drugId;
-
- private Integer quantity;
-
- private Date expiryDate;
-
- private String batch;
-
- private Integer value;
-
- private String status;
-
- private Integer providerId;
-
- private Integer locationId;
-
- private Integer drugPurchaseOrderId;
-
- public DrugInventory() {
-
- }
-
- public Integer getId() {
- // TODO Auto-generated method stub
- return getDrugInventoryId();
- }
-
- public void setId(Integer arg0) {
- // TODO Auto-generated method stub
- setDrugInventoryId(arg0);
- }
-
- public Integer getDrugInventoryId() {
- return drugInventoryId;
- }
-
- public void setDrugInventoryId(Integer drugInventoryId) {
- this.drugInventoryId = drugInventoryId;
- }
-
- public String getDrugInventoryName() {
- return drugInventoryName;
- }
-
- public void setDrugInventoryName(String drugInventoryName) {
- this.drugInventoryName = drugInventoryName;
- }
-
- public String getDrugInventoryDescription() {
- return drugInventoryDescription;
- }
-
- public void setDrugInventoryDescription(String drugInventoryDescription) {
- this.drugInventoryDescription = drugInventoryDescription;
- }
-
- public Integer getDrugId() {
- return drugId;
- }
-
- public void setDrugId(Integer drugd) {
- this.drugId = drugId;
- }
-
- public Integer getQuantity() {
- return quantity;
- }
-
- public void setQuantity(Integer quantity) {
- this.quantity = quantity;
- }
-
- public Date getExpiryDate() {
- return expiryDate;
- }
-
- public void setExpiryDate(Date expiryDate) {
- this.expiryDate = expiryDate;
- }
-
- public String getBatch() {
- return batch;
- }
-
- public void setBatch(String batch) {
- this.batch = batch;
- }
-
- public Integer getValue() {
- return value;
- }
-
- public void setValue(Integer value) {
- this.value = value;
- }
-
- public String getStatus() {
- return status;
- }
-
- public void setStatus(String status) {
- this.status = status;
- }
-
- public Integer getProviderId() {
- return providerId;
- }
-
- public void setProviderId(Integer providerId) {
- this.providerId = providerId;
- }
-
- public Integer getLocationId() {
- return locationId;
- }
-
- public void setLocationId(Integer locationId) {
- this.locationId = locationId;
- }
-
- public Integer getDrugPurchaseOrderId() {
- return drugPurchaseOrderId;
- }
-
- public void setDrugPurchaseOrderId(Integer drugPurchaseOrderId) {
- this.drugPurchaseOrderId = drugPurchaseOrderId;
- }
-
-}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000..0ad25db4bd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000000..0d40080504
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,7 @@
+Copyright (C) 2018 OpenMRS, Inc
+
+This product includes software developed under the stewardship of the Bahmni Coalition, under fiscal sponsorship of OpenMRS, Inc. (http://www.openmrs.org/)
+
+This product includes software developed at ThoughtWorks, Inc (http://www.thoughtworks.com/)
+
+This software contains code derived from the RAXA-Core (https://github.com/Raxa/raxacore)
\ No newline at end of file
diff --git a/OpenMRSFormatter.xml b/OpenMRSFormatter.xml
deleted file mode 100644
index 35c5cdf8e3..0000000000
--- a/OpenMRSFormatter.xml
+++ /dev/null
@@ -1,267 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/README.md b/README.md
index 4b057d34d3..69d2ff937e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,25 @@
-raxacore
-========
+Tracing from raxacore to bahmni-core
-The raxacore is an OpenMRS module that hosts all the services that are required by the RaxaEMR on-top of the OpenMRS installation.
-The module provides core REST services required for core RaxaEMR management
-RaxaEMR modules may have their own backend services provided by other OpenMRS modules
-This module should only depend on the OpenMRS core and webservices.rest module
+# OpenMRS module bahmnicore
-The raxacore module provides the following services
-- PatientListService
\ No newline at end of file
+This module provides necessary services for running Bahmni
+
+## Build
+
+[](https://github.com/Bahmni/bahmni-core/actions)
+
+### Prerequisite
+ JDK 1.8
+ ruby 2.2+
+ RubyGems
+ Compass 1.0.3 (gem install compass)
+
+### Clone the repository and build the omod
+
+ git clone https://github.com/bahmni/bahmni-core
+ cd bahmni-core
+ ./mvnw clean install
+
+## Deploy
+
+Copy ```bahmni-core/bahmnicore-omod/target/bahmnicore-omod-VERSION-SNAPSHOT.omod``` into OpenMRS modules directory and restart OpenMRS
diff --git a/admin/pom.xml b/admin/pom.xml
new file mode 100644
index 0000000000..66185a5bf6
--- /dev/null
+++ b/admin/pom.xml
@@ -0,0 +1,290 @@
+
+
+ 4.0.0
+
+ bahmni
+ org.bahmni.module
+ 1.2.0-SNAPSHOT
+
+
+ admin
+ Admin
+
+
+
+
+ org.bahmni.module
+ bahmni-migrator
+ ${bahmniJavaUtilsVersion}
+
+
+ org.bahmni.module
+ common
+ ${bahmniJavaUtilsVersion}
+
+
+ org.bahmni.module
+ auditlog-api
+ provided
+
+
+ org.bahmni.module
+ bahmni-commons-api
+
+
+ net.sf.opencsv
+ opencsv
+ 2.0
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.openmrs.test
+ openmrs-test
+ pom
+ test
+
+
+ junit
+ junit
+ test
+
+
+ org.openmrs.module
+ emrapi-api
+ ${emrapi-omod.version}
+
+
+ org.openmrs.module
+ emrapi-api-2.2
+ ${emrapi-omod.version}
+
+
+ org.openmrs.module
+ emrapi-api-1.12
+ ${emrapi-omod.version}
+
+
+ org.bahmni.module
+ bahmni-emr-api
+ ${project.parent.version}
+
+
+ org.openmrs.module
+ reporting-api
+
+
+ org.openmrs.module
+ calculation-api
+
+
+ org.openmrs.module
+ serialization.xstream-api-2.0
+
+
+ org.openmrs.module
+ providermanagement-api
+
+
+ org.openmrs.module
+ appframework-api
+
+
+ org.openmrs.api
+ openmrs-api
+ ${openMRSVersion}
+ jar
+
+
+ org.bahmni.module
+ bahmnicore-api
+ ${project.parent.version}
+ jar
+
+
+ org.bahmni.module
+ bahmnicore-api
+ ${project.parent.version}
+ test-jar
+ test
+
+
+ org.codehaus.jackson
+ jackson-core-asl
+ 1.9.13
+ provided
+
+
+ org.codehaus.jackson
+ jackson-mapper-asl
+ 1.9.13
+ provided
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.2.5
+ provided
+
+
+ org.bahmni.module
+ reference-data-api
+ ${project.parent.version}
+ provided
+
+
+ org.bahmni.module
+ episodes-api
+ ${episodes.version}
+ provided
+
+
+ org.bahmni.module
+ form2-utils
+ ${bahmniJavaUtilsVersion}
+
+
+ org.openmrs.web
+ openmrs-web
+ test
+
+
+ org.openmrs.web
+ openmrs-web
+ test-jar
+ test
+
+
+ javax.servlet
+ javax.servlet-api
+ test
+
+
+ org.bahmni.test
+ bahmni-test-commons
+ ${project.parent.version}
+ test-jar
+ test
+
+
+ org.openmrs.api
+ openmrs-api
+ ${openMRSVersion}
+ test-jar
+ test
+
+
+ javax.servlet
+ servlet-api
+
+
+
+
+ org.openmrs.module
+ addresshierarchy-api
+ ${addressHierarchyVersion}
+ provided
+
+
+ org.openmrs.module
+ addresshierarchy-omod
+ ${addressHierarchyVersion}
+ provided
+
+
+ org.openmrs.module
+ legacyui-omod
+
+
+ org.openmrs.module
+ webservices.rest-omod
+ ${openMRSWebServicesVersion}
+ jar
+ test
+
+
+ org.openmrs.module
+ webservices.rest-omod-common
+ ${openMRSWebServicesVersion}
+ tests
+ test
+
+
+ com.google.code.gson
+ gson
+ 2.3.1
+
+
+ org.ict4h.openmrs
+ openmrs-atomfeed-api
+ ${openmrsAtomfeedVersion}
+ test
+
+
+ org.openmrs.module
+ metadatamapping-api
+ ${metadatamapping.version}
+ test
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+
+ src/test/resources
+ true
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.7.5.201505241946
+
+
+ check
+
+ report
+ check
+
+
+
+
+ BUNDLE
+
+
+ LINE
+ COVEREDRATIO
+ 0.35
+
+
+ BRANCH
+ COVEREDRATIO
+ 0.36
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/ConceptMapper.java b/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/ConceptMapper.java
new file mode 100644
index 0000000000..3c6a8b4570
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/ConceptMapper.java
@@ -0,0 +1,118 @@
+package org.bahmni.module.admin.concepts.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.models.ConceptReferenceTermRow;
+import org.bahmni.module.admin.csv.models.ConceptRow;
+import org.bahmni.module.referencedata.labconcepts.contract.Concept;
+import org.bahmni.module.referencedata.labconcepts.contract.ConceptReferenceTerm;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getKeyValueList;
+
+public class ConceptMapper {
+
+ public Concept map(ConceptRow conceptRow) {
+ Concept concept = new Concept();
+ concept.setUuid(conceptRow.getUuid());
+ concept.setClassName(conceptRow.conceptClass);
+ concept.setDataType(conceptRow.getDataType());
+ concept.setDescription(conceptRow.getDescription());
+ concept.setUniqueName(conceptRow.getName());
+ concept.setDisplayName(conceptRow.getShortName());
+ concept.setUnits(conceptRow.getUnits());
+ concept.setHiNormal(conceptRow.getHiNormal());
+ concept.setLowNormal(conceptRow.getLowNormal());
+ if (Objects.equals(conceptRow.getAllowDecimal(), "") || conceptRow.getAllowDecimal() == null) {
+ concept.setAllowDecimal("true");
+ } else {
+ concept.setAllowDecimal(conceptRow.getAllowDecimal());
+ }
+ concept.setLocale(conceptRow.getLocale());
+ addSynonyms(conceptRow, concept);
+ addAnswers(conceptRow, concept);
+ addConceptReferenceTerms(conceptRow, concept);
+
+ return concept;
+ }
+
+ private void addSynonyms(ConceptRow conceptRow, Concept concept) {
+ List synonyms = new ArrayList<>();
+ for (KeyValue synonym : conceptRow.getSynonyms()) {
+ if (!StringUtils.isEmpty(synonym.getValue())) {
+ synonyms.add(synonym.getValue());
+ }
+ }
+ concept.setSynonyms(synonyms);
+ }
+
+ private void addAnswers(ConceptRow conceptRow, Concept concept) {
+ List answers = new ArrayList<>();
+ List> sortedAnswers = sortAnswersAccordingToNumericValueOfKey(conceptRow.getAnswers());
+ for (Map.Entry answer : sortedAnswers) {
+ if (!StringUtils.isEmpty(answer.getValue())) {
+ answers.add(answer.getValue());
+ }
+ }
+ concept.setAnswers(answers);
+ }
+
+ private List> sortAnswersAccordingToNumericValueOfKey(List answers) {
+ HashMap answersMap = new HashMap();
+ for (KeyValue answer : answers) {
+ answersMap.put(Integer.parseInt(answer.getKey()), answer.getValue());
+ }
+ List> sortedAnswers = new ArrayList>(
+ answersMap.entrySet()
+ );
+ Collections.sort(
+ sortedAnswers
+ , new Comparator>() {
+ public int compare(Map.Entry a, Map.Entry b) {
+ return Integer.compare(a.getKey(), b.getKey());
+ }
+ }
+ );
+ return sortedAnswers;
+ }
+
+
+ private void addConceptReferenceTerms(ConceptRow conceptRow, Concept concept) {
+ ConceptReferenceTerm conceptReferenceTerm;
+ for (ConceptReferenceTermRow referenceTerm : conceptRow.getReferenceTerms()) {
+ conceptReferenceTerm = new ConceptReferenceTerm(referenceTerm.getReferenceTermCode(), referenceTerm.getReferenceTermRelationship(), referenceTerm.getReferenceTermSource());
+ concept.getConceptReferenceTermsList().add(conceptReferenceTerm);
+ }
+ }
+
+ //TODO need to change
+ public ConceptRow map(Concept concept) {
+ String name = concept.getUniqueName();
+ String description = concept.getDescription();
+ String shortName = concept.getDisplayName();
+ String conceptClass = concept.getClassName();
+ String conceptDatatype = concept.getDataType();
+ String locale = concept.getLocale();
+ List conceptSynonyms = getKeyValueList("synonym", concept.getSynonyms());
+ List conceptAnswers = getKeyValueList("answer", concept.getAnswers());
+
+ List referenceTermRows = new ArrayList<>();
+ for (ConceptReferenceTerm term : concept.getConceptReferenceTermsList()) {
+ referenceTermRows.add(new ConceptReferenceTermRow(term.getReferenceTermSource(), term.getReferenceTermCode(), term.getReferenceTermRelationship()));
+ }
+ String uuid = concept.getUuid();
+ String units = concept.getUnits();
+ String hiNormal = concept.getHiNormal();
+ String lowNormal = concept.getLowNormal();
+ String allowDecimal = concept.getAllowDecimal();
+ return new ConceptRow(uuid, name, description, conceptClass, shortName, conceptDatatype, units, hiNormal,
+ lowNormal, allowDecimal, referenceTermRows, conceptSynonyms, conceptAnswers, locale);
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/ConceptSetMapper.java b/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/ConceptSetMapper.java
new file mode 100644
index 0000000000..03c39cc31a
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/ConceptSetMapper.java
@@ -0,0 +1,117 @@
+package org.bahmni.module.admin.concepts.mapper;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.models.ConceptReferenceTermRow;
+import org.bahmni.module.admin.csv.models.ConceptRow;
+import org.bahmni.module.admin.csv.models.ConceptRows;
+import org.bahmni.module.admin.csv.models.ConceptSetRow;
+import org.bahmni.module.referencedata.labconcepts.contract.ConceptReferenceTerm;
+import org.bahmni.module.referencedata.labconcepts.contract.ConceptSet;
+import org.bahmni.module.referencedata.labconcepts.contract.Concepts;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getKeyValueList;
+
+public class ConceptSetMapper {
+
+ private ConceptMapper conceptMapper;
+
+ public ConceptSetMapper() {
+ conceptMapper = new ConceptMapper();
+ }
+
+ public ConceptSet map(ConceptSetRow conceptSetRow) {
+ ConceptSet conceptSet = new ConceptSet();
+ conceptSet.setUuid(conceptSetRow.getUuid());
+ conceptSet.setUniqueName(conceptSetRow.getName());
+ conceptSet.setDisplayName(conceptSetRow.getShortName());
+ conceptSet.setClassName(conceptSetRow.conceptClass);
+ conceptSet.setDescription(conceptSetRow.description);
+ conceptSet.setChildren(getChildren(conceptSetRow));
+
+ List conceptReferenceTerms = new ArrayList<>();
+ for (ConceptReferenceTermRow term : conceptSetRow.referenceTerms) {
+ conceptReferenceTerms.add(new ConceptReferenceTerm(term.getReferenceTermCode(), term.getReferenceTermRelationship(), term.getReferenceTermSource()));
+ }
+
+ conceptSet.setConceptReferenceTermsList(conceptReferenceTerms);
+ return conceptSet;
+ }
+
+ private List getChildren(ConceptSetRow conceptSetRow) {
+ List children = new ArrayList<>();
+ List> sortedChildren = sortChildrenAccordingToNumericValueOfKey(conceptSetRow.getChildren());
+ for (Map.Entry child : sortedChildren) {
+ if(!StringUtils.isEmpty(child.getValue())) {
+ children.add(child.getValue());
+ }
+ }
+ return children;
+ }
+
+ private List> sortChildrenAccordingToNumericValueOfKey(List children) {
+ HashMap childrenMap = new HashMap();
+ for (KeyValue child : children) {
+ childrenMap.put(Integer.parseInt(child.getKey()), child.getValue());
+ }
+ List> sortedChildren = new ArrayList>(
+ childrenMap.entrySet()
+ );
+ Collections.sort(
+ sortedChildren
+ , new Comparator>() {
+ public int compare(Map.Entry a, Map.Entry b) {
+ return Integer.compare(a.getKey(), b.getKey());
+ }
+ }
+ );
+ return sortedChildren;
+ }
+
+
+ // private ConceptReferenceTerm getConceptReferenceTerm(ConceptSetRow conceptSetRow) {
+// ConceptReferenceTerm conceptReferenceTerm = new ConceptReferenceTerm();
+// conceptReferenceTerm.setReferenceTermCode(conceptSetRow.referenceTermCode);
+// conceptReferenceTerm.setReferenceTermRelationship(conceptSetRow.referenceTermRelationship);
+// conceptReferenceTerm.setReferenceTermSource(conceptSetRow.referenceTermSource);
+// return conceptReferenceTerm;
+// }
+//
+ public ConceptSetRow map(ConceptSet conceptSet) {
+ String name = conceptSet.getUniqueName();
+ String description = conceptSet.getDescription();
+ String shortName = conceptSet.getDisplayName();
+ String conceptClass = conceptSet.getClassName();
+ List children = getKeyValueList("child", conceptSet.getChildren());
+
+ List referenceTermRows = new ArrayList<>();
+ for (ConceptReferenceTerm term : conceptSet.getConceptReferenceTermsList()) {
+ referenceTermRows.add(new ConceptReferenceTermRow(term.getReferenceTermSource(), term.getReferenceTermCode(), term.getReferenceTermRelationship()));
+ }
+ String uuid = conceptSet.getUuid();
+ return new ConceptSetRow(uuid, name, description, conceptClass, shortName, referenceTermRows, children);
+ }
+
+
+ public ConceptRows mapAll(Concepts concepts) {
+ ConceptRows conceptRows = new ConceptRows();
+ List conceptRowList = new ArrayList<>();
+ List conceptSetRowList = new ArrayList<>();
+ for (org.bahmni.module.referencedata.labconcepts.contract.Concept concept : concepts.getConceptList()) {
+ conceptRowList.add(conceptMapper.map(concept));
+ }
+ for (ConceptSet conceptSet : concepts.getConceptSetList()) {
+ conceptSetRowList.add(map(conceptSet));
+ }
+ conceptRows.setConceptRows(conceptRowList);
+ conceptRows.setConceptSetRows(conceptSetRowList);
+ return conceptRows;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/DrugMapper.java b/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/DrugMapper.java
new file mode 100644
index 0000000000..8391e6013d
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/concepts/mapper/DrugMapper.java
@@ -0,0 +1,19 @@
+package org.bahmni.module.admin.concepts.mapper;
+
+import org.bahmni.module.admin.csv.models.DrugRow;
+import org.bahmni.module.referencedata.labconcepts.contract.Drug;
+
+public class DrugMapper {
+ public Drug map(DrugRow drugRow) {
+ Drug drug = new Drug();
+ drug.setUuid(drugRow.getUuid());
+ drug.setName(drugRow.getName());
+ drug.setGenericName(drugRow.getGenericName());
+ drug.setDosageForm(drugRow.getDosageForm());
+ drug.setStrength(drugRow.getStrength());
+ drug.setMinimumDose(drugRow.getMinimumDose());
+ drug.setMaximumDose(drugRow.getMaximumDose());
+ drug.setCombination(drugRow.getCombination());
+ return drug;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/config/dao/BahmniConfigDao.java b/admin/src/main/java/org/bahmni/module/admin/config/dao/BahmniConfigDao.java
new file mode 100644
index 0000000000..df2e2eaf3d
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/config/dao/BahmniConfigDao.java
@@ -0,0 +1,19 @@
+package org.bahmni.module.admin.config.dao;
+
+import org.bahmni.module.admin.config.model.BahmniConfig;
+
+import java.util.List;
+
+public interface BahmniConfigDao {
+ BahmniConfig get(String appName, String configName);
+
+ BahmniConfig get(String uuid);
+
+ List getAllFor(String appName);
+
+ BahmniConfig save(BahmniConfig bahmniConfig);
+
+ BahmniConfig update(BahmniConfig existingConfig);
+
+ List getAll();
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/config/dao/impl/BahmniConfigDaoImpl.java b/admin/src/main/java/org/bahmni/module/admin/config/dao/impl/BahmniConfigDaoImpl.java
new file mode 100644
index 0000000000..293af614f9
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/config/dao/impl/BahmniConfigDaoImpl.java
@@ -0,0 +1,87 @@
+package org.bahmni.module.admin.config.dao.impl;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.bahmni.module.admin.config.dao.BahmniConfigDao;
+import org.bahmni.module.admin.config.model.BahmniConfig;
+import org.hibernate.Query;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class BahmniConfigDaoImpl implements BahmniConfigDao {
+
+ @Autowired
+ private SessionFactory sessionFactory;
+
+ @Override
+ public BahmniConfig get(String appName, String configName) {
+ List appConfig = new ArrayList<>();
+ Session currentSession = sessionFactory.getCurrentSession();
+ Query query = currentSession.createQuery(
+ "select config from BahmniConfig config " +
+ " where config.appName = :appName and config.configName = :configName");
+ query.setParameter("appName", appName);
+ query.setParameter("configName", configName);
+ appConfig.addAll(query.list());
+ return CollectionUtils.isEmpty(appConfig) ? null : appConfig.get(0);
+ }
+
+ @Override
+ public BahmniConfig get(String uuid) {
+ List appConfig = new ArrayList<>();
+ Session currentSession = sessionFactory.getCurrentSession();
+ Query query = currentSession.createQuery(
+ "select config from BahmniConfig config " +
+ " where config.uuid = :uuid ");
+ query.setParameter("uuid", uuid);
+ appConfig.addAll(query.list());
+ return CollectionUtils.isEmpty(appConfig) ? null : appConfig.get(0);
+ }
+
+ //Mihir: Don't try to the merge the top one and this method together, since we are using a CLOB in MYSQL
+ //its a streaming Datatype, so best not to load things we don't require in the memory.
+ @Override
+ public List getAllFor(String appName) {
+ List appConfigs = new ArrayList<>();
+ Session currentSession = sessionFactory.getCurrentSession();
+ Query query = currentSession.createQuery(
+ "select config from BahmniConfig config " +
+ " where config.appName = :appName ");
+ query.setParameter("appName", appName);
+ appConfigs.addAll(query.list());
+ for (BahmniConfig bahmniConfig : appConfigs) {
+ bahmniConfig.setConfig(null);
+ }
+ return appConfigs;
+ }
+
+ @Override
+ public List getAll() {
+ List appConfigs = new ArrayList<>();
+ Session currentSession = sessionFactory.getCurrentSession();
+ Query query = currentSession.createQuery(
+ "select distinct config.appName from BahmniConfig config ");
+ appConfigs.addAll(query.list());
+ return appConfigs;
+ }
+
+ @Override
+ @Transactional
+ public BahmniConfig save(BahmniConfig bahmniConfig) {
+ sessionFactory.getCurrentSession().save(bahmniConfig);
+ return get(bahmniConfig.getAppName(), bahmniConfig.getConfigName());
+ }
+
+ @Override
+ @Transactional
+ public BahmniConfig update(BahmniConfig bahmniConfig) {
+ sessionFactory.getCurrentSession().update(bahmniConfig);
+ return bahmniConfig;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/config/model/BahmniConfig.java b/admin/src/main/java/org/bahmni/module/admin/config/model/BahmniConfig.java
new file mode 100644
index 0000000000..4f00a11062
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/config/model/BahmniConfig.java
@@ -0,0 +1,111 @@
+package org.bahmni.module.admin.config.model;
+
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.openmrs.Auditable;
+import org.openmrs.BaseOpenmrsObject;
+import org.openmrs.User;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class BahmniConfig extends BaseOpenmrsObject implements Auditable, Serializable {
+ private Integer configId;
+
+ private String appName;
+
+ private String configName;
+
+ private User creator;
+
+ private Date dateCreated;
+
+ private User changedBy;
+
+ private Date dateChanged;
+
+ private String config;
+
+ public Integer getConfigId() {
+ return configId;
+ }
+
+ public void setConfigId(Integer configId) {
+ this.configId = configId;
+ }
+
+ public String getConfig() {
+ return config;
+ }
+
+ public void setConfig(String config) {
+ this.config = config;
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ public String getConfigName() {
+ return configName;
+ }
+
+ public void setConfigName(String configName) {
+ this.configName = configName;
+ }
+
+ @Override
+ public Integer getId() {
+ return getConfigId();
+ }
+
+ @Override
+ public void setId(Integer id) {
+ setConfigId(id);
+ }
+
+ @Override
+ @JsonIgnore
+ public User getCreator() {
+ return creator;
+ }
+
+ @Override
+ public void setCreator(User creator) {
+ this.creator = creator;
+ }
+
+ @Override
+ public Date getDateCreated() {
+ return dateCreated;
+ }
+
+ @Override
+ public void setDateCreated(Date dateCreated) {
+ this.dateCreated = dateCreated;
+ }
+
+ @Override
+ @JsonIgnore
+ public User getChangedBy() {
+ return changedBy;
+ }
+
+ @Override
+ public void setChangedBy(User changedBy) {
+ this.changedBy = changedBy;
+ }
+
+ @Override
+ public Date getDateChanged() {
+ return dateChanged;
+ }
+
+ @Override
+ public void setDateChanged(Date dateChanged) {
+ this.dateChanged = dateChanged;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/config/service/BahmniConfigService.java b/admin/src/main/java/org/bahmni/module/admin/config/service/BahmniConfigService.java
new file mode 100644
index 0000000000..44d6534cb1
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/config/service/BahmniConfigService.java
@@ -0,0 +1,17 @@
+package org.bahmni.module.admin.config.service;
+
+import org.bahmni.module.admin.config.model.BahmniConfig;
+
+import java.util.List;
+
+public interface BahmniConfigService {
+ BahmniConfig get(String appName, String configName);
+
+ List getAllFor(String appName);
+
+ BahmniConfig save(BahmniConfig bahmniConfig);
+
+ BahmniConfig update(BahmniConfig bahmniConfig);
+
+ List getAll();
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/config/service/impl/BahmniConfigServiceImpl.java b/admin/src/main/java/org/bahmni/module/admin/config/service/impl/BahmniConfigServiceImpl.java
new file mode 100644
index 0000000000..74e808ac5d
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/config/service/impl/BahmniConfigServiceImpl.java
@@ -0,0 +1,64 @@
+package org.bahmni.module.admin.config.service.impl;
+
+import org.bahmni.module.admin.config.dao.BahmniConfigDao;
+import org.bahmni.module.admin.config.model.BahmniConfig;
+import org.bahmni.module.admin.config.service.BahmniConfigService;
+import org.openmrs.api.context.Context;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+@Service
+public class BahmniConfigServiceImpl implements BahmniConfigService {
+ private BahmniConfigDao bahmniConfigDao;
+
+ @Autowired
+ public BahmniConfigServiceImpl(BahmniConfigDao bahmniConfigDao) {
+ this.bahmniConfigDao = bahmniConfigDao;
+ }
+
+ @Override
+ public BahmniConfig get(String appName, String configName) {
+ return bahmniConfigDao.get(appName, configName);
+ }
+
+ @Override
+ public List getAllFor(String appName) {
+ return bahmniConfigDao.getAllFor(appName);
+ }
+
+ @Override
+ public BahmniConfig save(BahmniConfig bahmniConfig) {
+ createNewConfig(bahmniConfig);
+ return bahmniConfigDao.save(bahmniConfig);
+ }
+
+ @Override
+ public BahmniConfig update(BahmniConfig configUpdate) {
+ BahmniConfig existingConfig = bahmniConfigDao.get(configUpdate.getUuid());
+ updateExistingConfig(configUpdate, existingConfig);
+ BahmniConfig updatedConfig = bahmniConfigDao.update(existingConfig);
+ return bahmniConfigDao.get(updatedConfig.getUuid());
+ }
+
+ @Override
+ public List getAll() {
+ return bahmniConfigDao.getAll();
+ }
+
+ private void createNewConfig(BahmniConfig bahmniConfig) {
+ bahmniConfig.setDateCreated(new Date());
+ bahmniConfig.setCreator(Context.getAuthenticatedUser());
+ bahmniConfig.setUuid(UUID.randomUUID().toString());
+ bahmniConfig.setConfigId(null);
+ }
+
+ private void updateExistingConfig(BahmniConfig updatedConfig, BahmniConfig existingConfig) {
+ existingConfig.setConfig(updatedConfig.getConfig());
+ existingConfig.setChangedBy(Context.getAuthenticatedUser());
+ existingConfig.setDateChanged(new Date());
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/exporter/ConceptSetExporter.java b/admin/src/main/java/org/bahmni/module/admin/csv/exporter/ConceptSetExporter.java
new file mode 100644
index 0000000000..1c121ca0b8
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/exporter/ConceptSetExporter.java
@@ -0,0 +1,31 @@
+package org.bahmni.module.admin.csv.exporter;
+
+import org.bahmni.module.admin.concepts.mapper.ConceptSetMapper;
+import org.bahmni.module.admin.csv.models.ConceptRows;
+import org.bahmni.module.referencedata.labconcepts.contract.Concepts;
+import org.bahmni.module.referencedata.labconcepts.service.ReferenceDataConceptService;
+import org.openmrs.api.APIException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ConceptSetExporter {
+
+ @Autowired
+ private ReferenceDataConceptService conceptService;
+ private final ConceptSetMapper conceptSetMapper;
+
+ public ConceptSetExporter() {
+ conceptSetMapper = new ConceptSetMapper();
+ }
+
+ public ConceptRows exportConcepts(String conceptName) {
+ Concepts conceptSet = conceptService.getConcept(conceptName);
+ if (conceptSet == null) {
+ throw new APIException("Concept " + conceptName + " not found");
+ }
+ ConceptRows conceptRows = conceptSetMapper.mapAll(conceptSet);
+ conceptRows.makeCSVReady();
+ return conceptRows;
+ }
+}
\ No newline at end of file
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptReferenceTermRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptReferenceTermRow.java
new file mode 100644
index 0000000000..98b47fca6c
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptReferenceTermRow.java
@@ -0,0 +1,61 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.apache.commons.lang3.StringUtils;
+import org.bahmni.csv.annotation.CSVHeader;
+
+public class ConceptReferenceTermRow {
+ @CSVHeader(name = "reference-term-source")
+ private String referenceTermSource;
+
+ @CSVHeader(name = "reference-term-code")
+ private String referenceTermCode;
+
+ @CSVHeader(name = "reference-term-relationship")
+ private String referenceTermRelationship;
+
+ public String getReferenceTermSource() {
+ return referenceTermSource;
+ }
+
+ public void setReferenceTermSource(String referenceTermSource) {
+ this.referenceTermSource = referenceTermSource;
+ }
+
+ public String getReferenceTermCode() {
+ return referenceTermCode;
+ }
+
+ public void setReferenceTermCode(String referenceTermCode) {
+ this.referenceTermCode = referenceTermCode;
+ }
+
+ public String getReferenceTermRelationship() {
+ return referenceTermRelationship;
+ }
+
+ public void setReferenceTermRelationship(String referenceTermRelationship) {
+ this.referenceTermRelationship = referenceTermRelationship;
+ }
+
+ public ConceptReferenceTermRow() {
+
+ }
+
+ public ConceptReferenceTermRow(String referenceTermSource, String referenceTermCode, String referenceTermRelationship) {
+ this.referenceTermSource = referenceTermSource;
+ this.referenceTermCode = referenceTermCode;
+ this.referenceTermRelationship = referenceTermRelationship;
+ }
+
+ public boolean isEmpty() {
+ return StringUtils.isBlank(referenceTermSource) && StringUtils.isBlank(referenceTermCode) && StringUtils.isBlank(referenceTermRelationship);
+ }
+
+ public String[] getRowValues() {
+ return new String[]{referenceTermSource, referenceTermCode, referenceTermRelationship};
+ }
+
+ public ConceptReferenceTermRow getHeaders() {
+ return new ConceptReferenceTermRow("reference-term-source", "reference-term-code", "reference-term-relationship");
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptRow.java
new file mode 100644
index 0000000000..d7426d9d00
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptRow.java
@@ -0,0 +1,232 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRegexHeader;
+import org.bahmni.csv.annotation.CSVRepeatingHeaders;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getStringArray;
+
+public class ConceptRow extends CSVEntity {
+ @CSVHeader(name = "uuid", optional = true)
+ public String uuid;
+
+ @CSVHeader(name = "name")
+ public String name;
+
+ @CSVHeader(name = "description", optional = true)
+ public String description;
+
+ @CSVHeader(name = "class")
+ public String conceptClass;
+
+ @CSVHeader(name = "shortname")
+ public String shortName;
+
+ @CSVRepeatingHeaders(names = {"reference-term-source", "reference-term-code", "reference-term-relationship"}, type = ConceptReferenceTermRow.class)
+ public List referenceTerms = new ArrayList<>();
+
+ @CSVHeader(name = "datatype")
+ public String dataType;
+
+ @CSVRegexHeader(pattern = "synonym.*")
+ public List synonyms;
+
+ @CSVRegexHeader(pattern = "answer.*")
+ public List answers;
+
+ @CSVHeader(name = "units", optional = true)
+ public String units;
+
+ @CSVHeader(name = "High Normal", optional = true)
+ public String hiNormal;
+
+ @CSVHeader(name = "Low Normal", optional = true)
+ public String lowNormal;
+
+ @CSVHeader(name = "Allow Decimal", optional = true)
+ public String allowDecimal;
+
+ @CSVHeader(name = "locale", optional = true)
+ public String locale;
+
+ public ConceptRow(String uuid, String name, String description, String conceptClass, String shortName, String dataType,
+ String units, String hiNormal, String lowNormal, String allowDecimal, List referenceTermRows,
+ List synonyms, List answers, String locale) {
+ this.uuid = uuid;
+ this.name = name;
+ this.description = description;
+ this.conceptClass = conceptClass;
+ this.shortName = shortName;
+ this.dataType = dataType;
+ this.synonyms = synonyms;
+ this.answers = answers;
+ this.units = units;
+ this.hiNormal = hiNormal;
+ this.lowNormal = lowNormal;
+ this.allowDecimal = allowDecimal;
+ this.referenceTerms = referenceTermRows;
+ this.locale = locale;
+ String[] aRow = {uuid, name, description, conceptClass, shortName, dataType, units, hiNormal, lowNormal, allowDecimal,locale};
+ String[] synonymsRow = getStringArray(synonyms);
+ String[] answersRow = getStringArray(answers);
+ aRow = ArrayUtils.addAll(aRow, ArrayUtils.addAll(synonymsRow, answersRow));
+ aRow = ArrayUtils.addAll(aRow, getReferenceTermRowValues());
+ originalRow(aRow);
+ }
+
+ public ConceptRow getHeaders() {
+ List synonymHeaders = new ArrayList<>();
+ List answerHeaders = new ArrayList<>();
+ List referenceTermHeaders = new ArrayList<>();
+ for (int count = 1; count <= synonyms.size(); count++) {
+ synonymHeaders.add(new KeyValue("synonymHeader", "synonym." + count));
+ }
+ for (int count = 1; count <= answers.size(); count++) {
+ answerHeaders.add(new KeyValue("answerHeader", "answer." + count));
+ }
+ for (ConceptReferenceTermRow referenceTerm : referenceTerms) {
+ referenceTermHeaders.add(referenceTerm.getHeaders());
+ }
+
+ //TODO FIX reference terms
+ return new ConceptRow("uuid", "name", "description", "class", "shortname", "datatype", "units", "High Normal", "Low Normal","Allow Decimal", referenceTermHeaders, synonymHeaders, answerHeaders,"locale");
+ }
+
+ public ConceptRow() {
+ }
+
+ public List getSynonyms() {
+ return synonyms == null ? new ArrayList() : synonyms;
+ }
+
+ public List getAnswers() {
+ return answers == null ? new ArrayList() : answers;
+ }
+
+ public String getName() {
+ return name == null ? null : name.trim();
+ }
+
+ public String getConceptClass() {
+ return conceptClass == null ? null : conceptClass.trim();
+ }
+
+ public String getUuid() {
+ try {
+ UUID uuid = UUID.fromString(this.uuid.trim());
+ return uuid.toString();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public String getDataType() {
+ return StringUtils.isEmpty(dataType) ? "N/A" : dataType;
+ }
+
+ public String getDescription() {
+ return StringUtils.isEmpty(description) ? null : description;
+ }
+
+ public String getShortName() {
+ return StringUtils.isEmpty(shortName) ? null : shortName;
+ }
+
+ public List getReferenceTerms() {
+ return referenceTerms;
+ }
+
+ public void setReferenceTerms(List referenceTerms) {
+ this.referenceTerms = referenceTerms;
+ }
+
+ public String getUnits() {
+ return units;
+ }
+
+ public void setUnits(String units) {
+ this.units = units;
+ }
+
+ public String getHiNormal() {
+ return hiNormal;
+ }
+
+ public void setHiNormal(String hiNormal) {
+ this.hiNormal = hiNormal;
+ }
+
+ public String getLowNormal() {
+ return lowNormal;
+ }
+
+ public void setLowNormal(String lowNormal) {
+ this.lowNormal = lowNormal;
+ }
+
+ public String getAllowDecimal() { return allowDecimal; }
+
+ public void setAllowDecimal(String allowDecimal) { this.allowDecimal = allowDecimal; }
+
+ public String getLocale() {
+ return locale;
+ }
+
+ public void setLocale(String locale) {
+ this.locale = locale;
+ }
+
+ public void adjust(int maxSynonyms, int maxAnswers, int maxReferenceTerms) {
+ addBlankSynonyms(maxSynonyms);
+ addBlankAnswers(maxAnswers);
+ addBlankReferenceTerms(maxReferenceTerms);
+ String[] aRow = {uuid, name, description, conceptClass, shortName, dataType, units, hiNormal, lowNormal, allowDecimal, locale};
+ String[] synonymsRow = getStringArray(synonyms);
+ String[] answersRow = getStringArray(answers);
+ aRow = ArrayUtils.addAll(aRow, ArrayUtils.addAll(synonymsRow, answersRow));
+ aRow = ArrayUtils.addAll(aRow, getReferenceTermRowValues());
+ originalRow(aRow);
+ }
+
+ private String[] getReferenceTermRowValues() {
+ String[] aRow = new String[0];
+ for (ConceptReferenceTermRow referenceTerm : referenceTerms) {
+ aRow = ArrayUtils.addAll(aRow, referenceTerm.getRowValues());
+ }
+ return aRow;
+ }
+
+ private void addBlankReferenceTerms(int maxReferenceTerms) {
+ int counter = this.getReferenceTerms().size();
+ while (counter <= maxReferenceTerms) {
+ this.referenceTerms.add(new ConceptReferenceTermRow(null, null, null));
+ counter++;
+ }
+ }
+
+ private void addBlankAnswers(int maxAnswers) {
+ int counter = this.getAnswers().size();
+ this.answers = this.getAnswers();
+ while (counter <= maxAnswers) {
+ this.answers.add(new KeyValue("answer", ""));
+ counter++;
+ }
+ }
+
+ private void addBlankSynonyms(int maxSynonyms) {
+ int counter = this.getSynonyms().size();
+ this.synonyms = this.getSynonyms();
+ while (counter <= maxSynonyms) {
+ this.synonyms.add(new KeyValue("synonym", ""));
+ counter++;
+ }
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptRows.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptRows.java
new file mode 100644
index 0000000000..07075c18f3
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptRows.java
@@ -0,0 +1,94 @@
+package org.bahmni.module.admin.csv.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConceptRows {
+ private List conceptRows;
+ private List conceptSetRows;
+
+ public List getConceptRows() {
+ return conceptRows == null ? new ArrayList() : conceptRows;
+ }
+
+ public void setConceptRows(List conceptRows) {
+ this.conceptRows = conceptRows;
+ }
+
+ public List getConceptSetRows() {
+ return conceptSetRows == null ? new ArrayList() : conceptSetRows;
+ }
+
+ public void setConceptSetRows(List conceptSetRows) {
+ this.conceptSetRows = conceptSetRows;
+ }
+
+ public ConceptRows makeCSVReady() {
+ int maxSynonyms = getMaxSynonyms();
+ int maxAnswers = getMaxAnswers();
+ int maxReferenceTerms = getMaxReferenceTerms();
+ int maxConceptSetReferenceTerms = getMaxConceptSetReferenceTerms();
+ int maxSetMembers = getMaxSetMembers();
+ conceptRows.add(0, new ConceptRow());
+ conceptSetRows.add(0, new ConceptSetRow());
+ for (ConceptRow conceptRow : getConceptRows()) {
+ conceptRow.adjust(maxSynonyms, maxAnswers, maxReferenceTerms);
+ }
+ for (ConceptSetRow conceptSetRow : getConceptSetRows()) {
+ conceptSetRow.adjust(maxSetMembers, maxConceptSetReferenceTerms);
+ }
+ conceptRows.set(0, conceptRows.get(0).getHeaders());
+ conceptSetRows.set(0, conceptSetRows.get(0).getHeaders());
+ return this;
+ }
+
+ private int getMaxSetMembers() {
+ int maxSetMembers = 0;
+ for (ConceptSetRow conceptSetRow : getConceptSetRows()) {
+ if (conceptSetRow.getChildren().size() > maxSetMembers) {
+ maxSetMembers = conceptSetRow.getChildren().size();
+ }
+ }
+ return maxSetMembers;
+ }
+
+ private int getMaxSynonyms() {
+ int maxSynonyms = 0;
+ for (ConceptRow conceptRow : getConceptRows()) {
+ if (conceptRow.getSynonyms().size() > maxSynonyms) {
+ maxSynonyms = conceptRow.getSynonyms().size();
+ }
+ }
+ return maxSynonyms;
+ }
+
+ private int getMaxAnswers() {
+ int maxAnswers = 0;
+ for (ConceptRow conceptRow : getConceptRows()) {
+ if (conceptRow.getAnswers().size() > maxAnswers) {
+ maxAnswers = conceptRow.getAnswers().size();
+ }
+ }
+ return maxAnswers;
+ }
+
+ private int getMaxReferenceTerms() {
+ int maxReferenceTerms = 0;
+ for (ConceptRow conceptRow : getConceptRows()) {
+ if (conceptRow.getReferenceTerms().size() > maxReferenceTerms) {
+ maxReferenceTerms = conceptRow.getReferenceTerms().size();
+ }
+ }
+ return maxReferenceTerms;
+ }
+
+ private int getMaxConceptSetReferenceTerms() {
+ int maxReferenceTerms = 0;
+ for (ConceptSetRow conceptSetRow : getConceptSetRows()) {
+ if (conceptSetRow.referenceTerms.size() > maxReferenceTerms) {
+ maxReferenceTerms = conceptSetRow.referenceTerms.size();
+ }
+ }
+ return maxReferenceTerms;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptSetRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptSetRow.java
new file mode 100644
index 0000000000..83657b4fb7
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/ConceptSetRow.java
@@ -0,0 +1,129 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRegexHeader;
+import org.bahmni.csv.annotation.CSVRepeatingHeaders;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getStringArray;
+
+public class ConceptSetRow extends CSVEntity {
+ @CSVHeader(name = "uuid", optional = true)
+ public String uuid;
+
+ @CSVHeader(name = "name")
+ public String name;
+
+ @CSVHeader(name = "description", optional = true)
+ public String description;
+
+ @CSVHeader(name = "class")
+ public String conceptClass;
+
+ @CSVHeader(name = "shortname")
+ public String shortName;
+
+ @CSVRepeatingHeaders(names = {"reference-term-source", "reference-term-code", "reference-term-relationship"}, type = ConceptReferenceTermRow.class)
+ public List referenceTerms = new ArrayList<>();
+
+ @CSVRegexHeader(pattern = "child.*")
+ public List children;
+
+ public List getChildren() {
+ return children == null ? new ArrayList() : children;
+ }
+
+ public String getShortName() {
+ return (shortName != null && StringUtils.isEmpty(shortName.trim())) ? null : shortName;
+ }
+
+ public String getName() {
+ return name == null ? null : name.trim();
+ }
+
+ public String getConceptClass() {
+ return conceptClass == null ? null : conceptClass.trim();
+ }
+
+ public String getUuid() {
+ try {
+ UUID uuid = UUID.fromString(this.uuid.trim());
+ return uuid.toString();
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ public ConceptSetRow getHeaders() {
+ List childHeaders = new ArrayList<>();
+ for (int childCount = 1; childCount <= children.size(); childCount++) {
+ childHeaders.add(new KeyValue("childHeader", "child." + childCount));
+ }
+
+ List referenceTermHeaders = new ArrayList<>();
+ for (ConceptReferenceTermRow referenceTerm : referenceTerms) {
+ referenceTermHeaders.add(referenceTerm.getHeaders());
+ }
+ return new ConceptSetRow("uuid", "name", "description", "class", "shortname", referenceTermHeaders, childHeaders);
+ }
+
+ public ConceptSetRow(String uuid, String name, String description, String conceptClass, String shortName, List referenceTerms, List children) {
+ this.uuid = uuid;
+ this.name = name;
+ this.description = description;
+ this.conceptClass = conceptClass;
+ this.shortName = shortName;
+ this.children = children;
+ this.referenceTerms = referenceTerms;
+ String[] aRow = {uuid, name, description, conceptClass, shortName};
+ String[] childrenRow = getStringArray(children);
+ aRow = ArrayUtils.addAll(aRow, childrenRow);
+ aRow = ArrayUtils.addAll(aRow, getReferenceTermRowValues());
+ originalRow(aRow);
+ }
+
+ public ConceptSetRow() {
+ }
+
+ public void adjust(int maxSetMembers, int maxConceptSetReferenceTerms) {
+ addBlankChildren(maxSetMembers);
+ addBlankReferenceTerms(maxConceptSetReferenceTerms);
+ String[] aRow = {uuid, name, description, conceptClass, shortName};
+ String[] childrenRow = getStringArray(children);
+ aRow = ArrayUtils.addAll(aRow, childrenRow);
+ aRow = ArrayUtils.addAll(aRow, getReferenceTermRowValues());
+ originalRow(aRow);
+ }
+
+ private void addBlankChildren(int maxSetMembers) {
+ int counter = this.getChildren().size();
+ this.children = this.getChildren();
+ while (counter <= maxSetMembers) {
+ this.children.add(new KeyValue("child", ""));
+ counter++;
+ }
+ }
+
+ private void addBlankReferenceTerms(int maxReferenceTerms) {
+ int counter = this.referenceTerms.size();
+ while (counter <= maxReferenceTerms) {
+ this.referenceTerms.add(new ConceptReferenceTermRow(null, null, null));
+ counter++;
+ }
+ }
+
+ private String[] getReferenceTermRowValues() {
+ String[] aRow = new String[0];
+ for (ConceptReferenceTermRow referenceTerm : referenceTerms) {
+ aRow = ArrayUtils.addAll(aRow, referenceTerm.getRowValues());
+ }
+ return aRow;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/DrugRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/DrugRow.java
new file mode 100644
index 0000000000..b0fa2b597b
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/DrugRow.java
@@ -0,0 +1,95 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.apache.commons.lang3.BooleanUtils;
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.annotation.CSVHeader;
+
+public class DrugRow extends CSVEntity {
+ @CSVHeader(name = "uuid", optional = true)
+ private String uuid;
+
+ @CSVHeader(name = "Name")
+ private String name;
+
+ @CSVHeader(name = "Generic Name")
+ private String genericName;
+
+ @CSVHeader(name = "Combination", optional = true)
+ private String combination;
+
+ @CSVHeader(name = "Strength", optional = true)
+ private String strength;
+
+ @CSVHeader(name = "Dosage Form")
+ private String dosageForm;
+
+ @CSVHeader(name = "Minimum Dose", optional = true)
+ private String minimumDose;
+
+ @CSVHeader(name = "Maximum Dose", optional = true)
+ private String maximumDose;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getGenericName() {
+ return genericName;
+ }
+
+ public void setGenericName(String genericName) {
+ this.genericName = genericName;
+ }
+
+ public void setStrength(String strength) {
+ this.strength = strength;
+ }
+
+ public String getStrength() {
+ return strength;
+ }
+
+ public void setDosageForm(String dosageForm) {
+ this.dosageForm = dosageForm;
+ }
+
+ public String getDosageForm() {
+ return dosageForm;
+ }
+
+ public void setMinimumDose(String minimumDose) {
+ this.minimumDose = minimumDose;
+ }
+
+ public String getMinimumDose() {
+ return minimumDose;
+ }
+
+ public void setMaximumDose(String maximumDose) {
+ this.maximumDose = maximumDose;
+ }
+
+ public String getMaximumDose() {
+ return maximumDose;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public void setUuid(String uuid) {
+ this.uuid = uuid;
+ }
+
+ public Boolean getCombination() {
+ return BooleanUtils.toBoolean(combination);
+ }
+
+ public void setCombination(String combination) {
+ this.combination = combination;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/EncounterRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/EncounterRow.java
new file mode 100644
index 0000000000..a9d6cdc46d
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/EncounterRow.java
@@ -0,0 +1,71 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.apache.commons.lang.StringUtils;
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRegexHeader;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateFromString;
+
+public class EncounterRow extends CSVEntity {
+
+ @CSVHeader(name = "EncounterDate")
+ public String encounterDateTime;
+
+ @CSVRegexHeader(pattern = "Obs.*")
+ public List obsRows;
+
+ @CSVRegexHeader(pattern = "Diagnosis.*")
+ public List diagnosesRows;
+
+ public Date getEncounterDate() throws ParseException {
+ return getDateFromString(encounterDateTime);
+ }
+
+ public boolean hasObservations() {
+ return obsRows != null && !obsRows.isEmpty();
+ }
+
+ public boolean hasDiagnoses() {
+ return diagnosesRows != null && !diagnosesRows.isEmpty();
+ }
+
+ public boolean isEmpty() {
+ return areObservationsEmpty() && areDiagnosesEmpty() && noEncounterDate();
+ }
+
+ private boolean areDiagnosesEmpty() {
+ if (diagnosesRows == null || diagnosesRows.isEmpty())
+ return true;
+
+ for (KeyValue diagnosisRow : diagnosesRows) {
+ if (StringUtils.isNotBlank(diagnosisRow.getValue())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean areObservationsEmpty() {
+ if (obsRows == null || obsRows.isEmpty())
+ return true;
+
+ for (KeyValue obsRow : obsRows) {
+ if (StringUtils.isNotBlank(obsRow.getValue())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean noEncounterDate() {
+ return StringUtils.isBlank(encounterDateTime);
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/FormerConceptReferenceRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/FormerConceptReferenceRow.java
new file mode 100644
index 0000000000..b3fa16b895
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/FormerConceptReferenceRow.java
@@ -0,0 +1,33 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRepeatingHeaders;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FormerConceptReferenceRow extends CSVEntity {
+ @CSVHeader(name = "concept-name")
+ private String conceptName;
+
+ @CSVRepeatingHeaders(names = {"reference-term-source", "reference-term-code", "reference-term-relationship"}, type = ConceptReferenceTermRow.class)
+ private List referenceTerms = new ArrayList<>();
+
+
+ public String getConceptName() {
+ return conceptName;
+ }
+
+ public void setConceptName(String conceptName) {
+ this.conceptName = conceptName;
+ }
+
+ public List getReferenceTerms() {
+ return referenceTerms;
+ }
+
+ public void setReferenceTerms(List referenceTerms) {
+ this.referenceTerms = referenceTerms;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/LabResultRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/LabResultRow.java
new file mode 100644
index 0000000000..8bae6e0fad
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/LabResultRow.java
@@ -0,0 +1,38 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.apache.commons.lang3.StringUtils;
+
+public class LabResultRow {
+ private String test;
+ private String result;
+
+ public LabResultRow() {
+ }
+
+ public LabResultRow(String test, String result) {
+ this.test = test;
+ this.result = result;
+ }
+
+ public String getTest() {
+ return test;
+ }
+
+ public LabResultRow setTest(String test) {
+ this.test = test;
+ return this;
+ }
+
+ public String getResult() {
+ return result;
+ }
+
+ public LabResultRow setResult(String result) {
+ this.result = result;
+ return this;
+ }
+
+ public boolean isEmpty() {
+ return StringUtils.isBlank(test) && StringUtils.isBlank(result);
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/LabResultsRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/LabResultsRow.java
new file mode 100644
index 0000000000..b9757bcc17
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/LabResultsRow.java
@@ -0,0 +1,83 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRegexHeader;
+import org.bahmni.csv.annotation.CSVRepeatingHeaders;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateFromString;
+
+public class LabResultsRow extends CSVEntity {
+ @CSVHeader(name = "Registration Number")
+ private String patientIdentifier;
+
+ @CSVRegexHeader(pattern = "Patient.*")
+ private List patientAttributes;
+
+ @CSVHeader(name = "Date")
+ private String testDateString;
+
+ @CSVHeader(name = "Visit Type")
+ private String visitType;
+
+ @CSVRepeatingHeaders(names = {"Test", "Result"}, type = LabResultRow.class)
+ private List testResults;
+
+ public List getTestResults() {
+ List labResultRows = new ArrayList<>();
+ for (LabResultRow testResult : testResults) {
+ if (!testResult.isEmpty()) {
+ labResultRows.add(testResult);
+ }
+ }
+ return labResultRows;
+ }
+
+ public LabResultsRow setTestResults(List testResults) {
+ this.testResults = testResults;
+ return this;
+ }
+
+ public Date getTestDate() throws ParseException {
+ return getDateFromString(this.testDateString);
+ }
+
+ public List getPatientAttributes() {
+ return patientAttributes;
+ }
+
+ public LabResultsRow setPatientAttributes(List patientAttributes) {
+ this.patientAttributes = patientAttributes;
+ return this;
+ }
+
+ public String getPatientIdentifier() {
+ return patientIdentifier;
+ }
+
+ public LabResultsRow setPatientIdentifier(String patientIdentifier) {
+ this.patientIdentifier = patientIdentifier;
+ return this;
+ }
+
+ public LabResultsRow setTestDateString(String testDateString) {
+ this.testDateString = testDateString;
+ return this;
+ }
+
+ public String getVisitType() {
+ return visitType;
+ }
+
+ public LabResultsRow setVisitType(String visitType) {
+ this.visitType = visitType;
+ return this;
+ }
+}
+
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/MultipleEncounterRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/MultipleEncounterRow.java
new file mode 100644
index 0000000000..329b51e253
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/MultipleEncounterRow.java
@@ -0,0 +1,67 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRegexHeader;
+import org.bahmni.csv.annotation.CSVRepeatingRegexHeaders;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateFromString;
+
+public class MultipleEncounterRow extends CSVEntity {
+
+ @CSVHeader(name = "Registration Number")
+ public String patientIdentifier;
+
+ @CSVHeader(name = "encounterType")
+ public String encounterType;
+
+ @CSVHeader(name = "visitType")
+ public String visitType;
+
+ @CSVHeader(name = "providerName", optional = true)
+ public String providerName;
+
+ @CSVHeader(name = "Visit Start Date", optional = true)
+ public String visitStartDate;
+
+ @CSVHeader(name = "Visit End Date", optional = true)
+ public String visitEndDate;
+
+ @CSVRegexHeader(pattern = "Patient.*")
+ public List patientAttributes;
+
+ @CSVRepeatingRegexHeaders(type = EncounterRow.class)
+ public List encounterRows;
+
+ public List getNonEmptyEncounterRows() {
+ List nonEmptyEncounters = new ArrayList<>();
+ if (encounterRows == null)
+ return nonEmptyEncounters;
+
+ for (EncounterRow encounterRow : encounterRows) {
+ if (!encounterRow.isEmpty()) {
+ nonEmptyEncounters.add(encounterRow);
+ }
+ }
+ return nonEmptyEncounters;
+ }
+
+ public Date getVisitStartDate() throws ParseException {
+ if (visitStartDate == null)
+ return null;
+ return getDateFromString(visitStartDate);
+ }
+
+ public Date getVisitEndDate() throws ParseException {
+ if (visitEndDate == null)
+ return null;
+ return getDateFromString(visitEndDate);
+ }
+}
+
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/PatientProgramRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/PatientProgramRow.java
new file mode 100644
index 0000000000..81b26fcc48
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/PatientProgramRow.java
@@ -0,0 +1,30 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRegexHeader;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateFromString;
+
+public class PatientProgramRow extends CSVEntity {
+ @CSVHeader(name = "Registration Number")
+ public String patientIdentifier;
+
+ @CSVRegexHeader(pattern = "Patient.*")
+ public List patientAttributes;
+
+ @CSVHeader(name = "Program")
+ public String programName;
+
+ @CSVHeader(name = "EnrollmentDate")
+ public String enrollmentDateTime;
+
+ public Date getEnrollmentDate() throws ParseException {
+ return getDateFromString(enrollmentDateTime);
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/PatientRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/PatientRow.java
new file mode 100644
index 0000000000..8afd830965
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/PatientRow.java
@@ -0,0 +1,44 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.annotation.CSVHeader;
+import org.bahmni.csv.annotation.CSVRegexHeader;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateFromString;
+
+public class PatientRow extends CSVEntity {
+ @CSVHeader(name = "First Name")
+ public String firstName;
+ @CSVHeader(name = "Middle Name")
+ public String middleName;
+ @CSVHeader(name = "Last Name")
+ public String lastName;
+ @CSVHeader(name = "Registration Number")
+ public String registrationNumber;
+ @CSVHeader(name = "Gender")
+ public String gender;
+ @CSVHeader(name = "Age", optional = true)
+ public String age;
+ @CSVHeader(name = "Birth Date", optional = true)
+ public String birthdate;
+ @CSVHeader(name = "Registration Date", optional = true)
+ public String registrationDate;
+
+ @CSVRegexHeader(pattern = "Address.*")
+ public List addressParts;
+
+ @CSVRegexHeader(pattern = "Attribute.*")
+ public List attributes = new ArrayList<>();
+
+ public Date getRegistrationDate() throws ParseException {
+ if (registrationDate == null)
+ return null;
+ return getDateFromString(registrationDate);
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/ReferenceTermRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/ReferenceTermRow.java
new file mode 100644
index 0000000000..2ed05a2b57
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/ReferenceTermRow.java
@@ -0,0 +1,72 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.annotation.CSVHeader;
+
+public class ReferenceTermRow extends CSVEntity {
+ @CSVHeader(name = "Code")
+ private String code;
+
+ @CSVHeader(name = "Source")
+ private String source;
+
+ @CSVHeader(name = "Name")
+ private String name;
+
+ @CSVHeader(name = "Description", optional = true)
+ private String description;
+
+ @CSVHeader(name = "Version", optional = true)
+ private String version;
+
+ public ReferenceTermRow() {
+ }
+
+ public ReferenceTermRow(String code, String source, String name, String description, String version) {
+ this.code = code;
+ this.source = source;
+ this.name = name;
+ this.description = description;
+ this.version = version;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/RelationshipRow.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/RelationshipRow.java
new file mode 100644
index 0000000000..b9ffce0144
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/RelationshipRow.java
@@ -0,0 +1,85 @@
+package org.bahmni.module.admin.csv.models;
+
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.annotation.CSVHeader;
+
+public class RelationshipRow extends CSVEntity {
+
+ @CSVHeader(name = "Registration_Number")
+ private String patientIdentifier;
+
+ @CSVHeader(name = "Relationship_Type")
+ private String relationshipType;
+
+ @CSVHeader(name = "Related_To_Registration_Number")
+ private String patientRelationIdentifier;
+
+ @CSVHeader(name = "Provider_Name")
+ private String providerName;
+
+ @CSVHeader(name = "Relationship_StartDate")
+ private String startDate;
+
+ @CSVHeader(name = "Relationship_EndDate")
+ private String endDate;
+
+ public RelationshipRow(String patientIdentifier, String patientRelationIdentifier, String providerName, String relationshipType, String startDate, String endDate) {
+ this.patientIdentifier = patientIdentifier;
+ this.patientRelationIdentifier = patientRelationIdentifier;
+ this.providerName = providerName;
+ this.relationshipType = relationshipType;
+ this.startDate = startDate;
+ this.endDate = endDate;
+ }
+
+ public RelationshipRow() {
+ }
+
+ public String getPatientIdentifier() {
+ return patientIdentifier;
+ }
+
+ public void setPatientIdentifier(String patientIdentifier) {
+ this.patientIdentifier = patientIdentifier;
+ }
+
+ public String getPatientRelationIdentifier() {
+ return patientRelationIdentifier;
+ }
+
+ public void setPatientRelationIdentifier(String patientRelationIdentifier) {
+ this.patientRelationIdentifier = patientRelationIdentifier;
+ }
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
+ public String getRelationshipType() {
+ return relationshipType;
+ }
+
+ public void setRelationshipType(String relationshipType) {
+ this.relationshipType = relationshipType;
+ }
+
+ public String getStartDate() {
+ return startDate;
+ }
+
+ public void setStartDate(String startDate) {
+ this.startDate = startDate;
+ }
+
+ public String getEndDate() {
+ return endDate;
+ }
+
+ public void setEndDate(String endDate) {
+ this.endDate = endDate;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/models/SectionPositionValue.java b/admin/src/main/java/org/bahmni/module/admin/csv/models/SectionPositionValue.java
new file mode 100644
index 0000000000..58823c66b9
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/models/SectionPositionValue.java
@@ -0,0 +1,60 @@
+package org.bahmni.module.admin.csv.models;
+
+public class SectionPositionValue {
+
+ private String value;
+ private String sectionIndex;
+ private int valueIndex;
+
+ private int multiSelectIndex;
+ private int addmoreIndex;
+
+ public SectionPositionValue(String value, String sectionIndex, int valueIndex, int multiSelectIndex, int addmoreIndex) {
+ this.value = value;
+ this.sectionIndex = sectionIndex;
+ this.valueIndex = valueIndex;
+ this.multiSelectIndex = multiSelectIndex;
+ this.addmoreIndex = addmoreIndex;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getSectionIndex() {
+ return sectionIndex;
+ }
+
+ public void setSectionIndex(String sectionIndex) {
+ this.sectionIndex = sectionIndex;
+ }
+
+ public int getValueIndex() {
+ return valueIndex;
+ }
+
+ public void setValueIndex(int valueIndex) {
+ this.valueIndex = valueIndex;
+ }
+
+ public int getMultiSelectIndex() {
+ return multiSelectIndex;
+ }
+
+ public void setMultiSelectIndex(int multiSelectIndex) {
+ this.multiSelectIndex = multiSelectIndex;
+ }
+
+ public int getAddmoreIndex() {
+ return addmoreIndex;
+ }
+
+ public void setAddmoreIndex(int addmoreIndex) {
+ this.addmoreIndex = addmoreIndex;
+ }
+
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/BahmniPatientMatchingAlgorithm.java b/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/BahmniPatientMatchingAlgorithm.java
new file mode 100644
index 0000000000..5febe0a13b
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/BahmniPatientMatchingAlgorithm.java
@@ -0,0 +1,13 @@
+package org.bahmni.module.admin.csv.patientmatchingalgorithm;
+
+import org.bahmni.csv.KeyValue;
+import org.openmrs.Patient;
+
+import java.util.List;
+
+public class BahmniPatientMatchingAlgorithm extends PatientMatchingAlgorithm {
+ @Override
+ public Patient run(List patientList, List patientAttributes) {
+ return patientList.size() > 0 ? patientList.get(0) : null;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/PatientMatchingAlgorithm.java b/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/PatientMatchingAlgorithm.java
new file mode 100644
index 0000000000..e7a9ada377
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/PatientMatchingAlgorithm.java
@@ -0,0 +1,20 @@
+package org.bahmni.module.admin.csv.patientmatchingalgorithm;
+
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.patientmatchingalgorithm.exception.CannotMatchPatientException;
+import org.openmrs.Patient;
+
+import java.util.List;
+
+public abstract class PatientMatchingAlgorithm {
+ public String valueFor(String keyToSearch, List patientAttributes) {
+ for (KeyValue patientAttributeKeyValue : patientAttributes) {
+ if (patientAttributeKeyValue.getKey().equalsIgnoreCase(keyToSearch)) {
+ return patientAttributeKeyValue.getValue();
+ }
+ }
+ return null;
+ }
+
+ public abstract Patient run(List patientList, List patientAttributes) throws CannotMatchPatientException;
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/exception/CannotMatchPatientException.java b/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/exception/CannotMatchPatientException.java
new file mode 100644
index 0000000000..0f13c6a958
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/patientmatchingalgorithm/exception/CannotMatchPatientException.java
@@ -0,0 +1,37 @@
+package org.bahmni.module.admin.csv.patientmatchingalgorithm.exception;
+
+import org.openmrs.Patient;
+
+import java.util.List;
+
+public class CannotMatchPatientException extends Exception {
+ private String patientIds = "";
+
+ public CannotMatchPatientException() {
+ }
+
+ public CannotMatchPatientException(List patients) {
+ this.patientIds = getPatientIds(patients);
+ }
+
+ @Override
+ public String getMessage() {
+ return "No matching patients found. Potential matches:'" + patientIds + "'";
+ }
+
+ @Override
+ public String toString() {
+ return "CannotMatchPatientException{Potential matches patientIds='" + patientIds + "'}";
+ }
+
+ private String getPatientIds(List patients) {
+ if (patients == null)
+ return "";
+
+ StringBuffer patientIdsBuffer = new StringBuffer();
+ for (Patient patient : patients) {
+ patientIdsBuffer.append(patient.getPatientIdentifier().getIdentifier()).append(", ");
+ }
+ return patientIdsBuffer.toString();
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptPersister.java
new file mode 100644
index 0000000000..392aa9cf69
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptPersister.java
@@ -0,0 +1,37 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.commons.lang.StringUtils;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.concepts.mapper.ConceptMapper;
+import org.bahmni.module.admin.csv.models.ConceptRow;
+import org.bahmni.module.referencedata.labconcepts.contract.Concept;
+import org.bahmni.module.referencedata.labconcepts.service.ReferenceDataConceptService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ConceptPersister implements EntityPersister {
+
+ @Autowired
+ private ReferenceDataConceptService referenceDataConceptService;
+
+ @Override
+ public Messages validate(ConceptRow conceptRow) {
+ Messages messages = new Messages();
+ if (StringUtils.isEmpty(conceptRow.name)) {
+ messages.add("Concept Name not specified\n");
+ }
+ if (StringUtils.isEmpty(conceptRow.conceptClass)) {
+ messages.add("Concept Class not specified\n");
+ }
+ return messages;
+ }
+
+ @Override
+ public Messages persist(ConceptRow conceptRow) {
+ Concept concept = new ConceptMapper().map(conceptRow);
+ referenceDataConceptService.saveConcept(concept);
+ return new Messages();
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptReferenceTermPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptReferenceTermPersister.java
new file mode 100644
index 0000000000..3527e0f975
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptReferenceTermPersister.java
@@ -0,0 +1,96 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.csv.models.ConceptReferenceTermRow;
+import org.bahmni.module.admin.csv.models.FormerConceptReferenceRow;
+import org.bahmni.module.referencedata.labconcepts.service.ReferenceDataConceptReferenceTermService;
+import org.openmrs.Concept;
+import org.openmrs.ConceptMap;
+import org.openmrs.ConceptReferenceTerm;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.isNull;
+
+@Service
+public class ConceptReferenceTermPersister implements EntityPersister {
+ private static final Logger log = LogManager.getLogger(ConceptReferenceTermPersister.class);
+
+ @Autowired
+ private ReferenceDataConceptReferenceTermService referenceTermService;
+
+ @Autowired
+ private ConceptService conceptService;
+
+
+ private UserContext userContext;
+
+ public void init(UserContext userContext) {
+ this.userContext = userContext;
+ }
+
+ @Override
+ public Messages persist(FormerConceptReferenceRow formerConceptReferenceRow) {
+ Concept concept = conceptService.getConceptByName(formerConceptReferenceRow.getConceptName());
+ concept.getConceptMappings().addAll(getNewConceptMappings(formerConceptReferenceRow.getReferenceTerms(), concept));
+ conceptService.saveConcept(concept);
+
+ return new Messages();
+ }
+
+ private List getNewConceptMappings(List referenceTerms, Concept concept) {
+ return referenceTerms.stream().map(termRow -> getConceptMap(concept, termRow)).collect(Collectors.toList());
+ }
+
+ private ConceptMap getConceptMap(Concept concept, ConceptReferenceTermRow termRow) {
+ String code = termRow.getReferenceTermCode();
+ String source = termRow.getReferenceTermSource();
+
+ ConceptReferenceTerm conceptReferenceTerm = referenceTermService.getConceptReferenceTerm(code, source);
+ ConceptMap conceptMap = new ConceptMap();
+ conceptMap.setConceptReferenceTerm(conceptReferenceTerm);
+ conceptMap.setConcept(concept);
+ conceptMap.setConceptMapType(conceptService.getConceptMapTypeByName(termRow.getReferenceTermRelationship()));
+ return conceptMap;
+ }
+
+ @Override
+ public Messages validate(FormerConceptReferenceRow formerConceptReferenceRow) {
+ Messages messages = new Messages();
+ Context.openSession();
+ Context.setUserContext(userContext);
+
+ String conceptName = formerConceptReferenceRow.getConceptName();
+ if (isNull(conceptService.getConceptByName(conceptName)))
+ messages.add(String.format("%s concept is not present", conceptName));
+
+ formerConceptReferenceRow.getReferenceTerms().forEach(termRow -> {
+ String code = termRow.getReferenceTermCode();
+ String source = termRow.getReferenceTermSource();
+
+ try {
+ referenceTermService.getConceptReferenceTerm(code, source);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ messages.add(String.format("%s reference term code is not present in %s source", code, source));
+ }
+ if (isNull(conceptService.getConceptMapTypeByName(termRow.getReferenceTermRelationship())))
+ messages.add(String.format("%s concept map type is not present", termRow.getReferenceTermRelationship()));
+
+ });
+
+ Context.flushSession();
+ Context.closeSession();
+
+ return messages;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptSetPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptSetPersister.java
new file mode 100644
index 0000000000..c142cd8b9e
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ConceptSetPersister.java
@@ -0,0 +1,88 @@
+package org.bahmni.module.admin.csv.persister;
+
+
+import org.apache.commons.lang.StringUtils;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.concepts.mapper.ConceptSetMapper;
+import org.bahmni.module.admin.csv.models.ConceptRows;
+import org.bahmni.module.admin.csv.models.ConceptSetRow;
+import org.bahmni.module.referencedata.labconcepts.contract.ConceptSet;
+import org.bahmni.module.referencedata.labconcepts.service.ReferenceDataConceptService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class ConceptSetPersister implements EntityPersister {
+
+ @Autowired
+ private ReferenceDataConceptService referenceDataConceptService;
+
+
+
+ @Override
+ public Messages validate(ConceptSetRow conceptSetRow) {
+ Messages messages = new Messages();
+ if (StringUtils.isEmpty(conceptSetRow.name)) {
+ messages.add("Concept Name not specified\n");
+ }
+ if (StringUtils.isEmpty(conceptSetRow.conceptClass)) {
+ messages.add("Concept Class not specified\n");
+ }
+ for (KeyValue conceptKeyValue: conceptSetRow.getChildren()) {
+ if(conceptKeyValue.getValue().equals(conceptSetRow.getName())){
+ messages.add("Concept introduces cycle\n");
+ }
+ }
+
+ return messages;
+
+ }
+
+ private boolean createsCycle(ConceptSetRow newConceptSetRow){
+
+ List descendantNames = getAllDescendantsNames(newConceptSetRow);
+ for (String descendantName: descendantNames) {
+ if(descendantName.equals(newConceptSetRow.getName())){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private List getAllDescendantsNames(ConceptSetRow newConceptSetRow) {
+ List descendants = new ArrayList<>();
+
+ List conceptRowsCollection = new ArrayList<>();
+ for (KeyValue concept: newConceptSetRow.getChildren()) {
+ if(StringUtils.isNotEmpty(concept.getValue())) {
+ conceptRowsCollection.add(new ConceptSetMapper().mapAll(referenceDataConceptService.getConcept(concept.getValue())));
+ }
+ }
+ for (ConceptRows conceptRows: conceptRowsCollection) {
+ for (ConceptSetRow conceptSetRow: conceptRows.getConceptSetRows()) {
+ descendants.add(conceptSetRow.getName());
+ }
+ }
+ return descendants;
+ }
+
+
+ @Override
+ public Messages persist(ConceptSetRow conceptSetRow) {
+ Messages messages = new Messages();
+ if(createsCycle(conceptSetRow)){
+ messages.add("Concept Set introduces cycle\n");
+ return messages;
+ }
+
+ ConceptSet concept = new ConceptSetMapper().map(conceptSetRow);
+ referenceDataConceptService.saveConcept(concept);
+ return new Messages();
+ }
+
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/DatabasePersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/DatabasePersister.java
new file mode 100644
index 0000000000..09fc0e553c
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/DatabasePersister.java
@@ -0,0 +1,41 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.CSVEntity;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+
+public class DatabasePersister implements EntityPersister {
+ private final EntityPersister persister;
+ private final UserContext userContext;
+ private static final Logger log = LogManager.getLogger(DatabasePersister.class);
+
+ public DatabasePersister(EntityPersister persister) {
+ this.persister = persister;
+ userContext = Context.getUserContext();
+ }
+
+ @Override
+ public Messages persist(T csvEntity) {
+ try {
+ Context.openSession();
+ Context.setUserContext(userContext);
+ return persister.persist(csvEntity);
+ } catch (Throwable e) {
+ log.error(e.getMessage(), e);
+ Context.clearSession();
+ return new Messages(e);
+ } finally {
+ Context.flushSession();
+ Context.closeSession();
+ }
+ }
+
+ @Override
+ public Messages validate(T csvEntity) {
+ return persister.validate(csvEntity);
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/DrugPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/DrugPersister.java
new file mode 100644
index 0000000000..bbb4bc440d
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/DrugPersister.java
@@ -0,0 +1,36 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.commons.lang.StringUtils;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.concepts.mapper.DrugMapper;
+import org.bahmni.module.admin.csv.models.DrugRow;
+import org.bahmni.module.referencedata.labconcepts.contract.Drug;
+import org.bahmni.module.referencedata.labconcepts.service.ReferenceDataDrugService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DrugPersister implements EntityPersister {
+ @Autowired
+ private ReferenceDataDrugService referenceDataDrugService;
+
+ @Override
+ public Messages validate(DrugRow drugRow) {
+ Messages messages = new Messages();
+ if (StringUtils.isEmpty(drugRow.getName())) {
+ messages.add("Drug name not specified\n");
+ }
+ if (StringUtils.isEmpty(drugRow.getGenericName())) {
+ messages.add("Drug generic name not specified\n");
+ }
+ return messages;
+ }
+
+ @Override
+ public Messages persist(DrugRow drugRow) {
+ Drug drug = new DrugMapper().map(drugRow);
+ referenceDataDrugService.saveDrug(drug);
+ return new Messages();
+ }
+}
\ No newline at end of file
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/EncounterPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/EncounterPersister.java
new file mode 100644
index 0000000000..22a3da5124
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/EncounterPersister.java
@@ -0,0 +1,154 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.csv.models.MultipleEncounterRow;
+import org.bahmni.module.admin.csv.service.PatientMatchService;
+import org.bahmni.module.admin.encounter.BahmniEncounterTransactionImportService;
+import org.bahmni.module.admin.retrospectiveEncounter.service.DuplicateObservationService;
+import org.openmrs.Patient;
+import org.openmrs.Provider;
+import org.openmrs.User;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+import org.openmrs.module.auditlog.service.AuditLogService;
+import org.openmrs.module.bahmniemrapi.drugorder.mapper.BahmniProviderMapper;
+import org.openmrs.module.bahmniemrapi.encountertransaction.contract.BahmniEncounterTransaction;
+import org.openmrs.module.bahmniemrapi.encountertransaction.service.BahmniEncounterTransactionService;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Component
+public class EncounterPersister implements EntityPersister {
+ public static final String IMPORT_ID = "IMPORT_ID_";
+ @Autowired
+ private PatientMatchService patientMatchService;
+ @Autowired
+ private BahmniEncounterTransactionService bahmniEncounterTransactionService;
+ @Autowired
+ private DuplicateObservationService duplicateObservationService;
+ @Autowired
+ private BahmniEncounterTransactionImportService bahmniEncounterTransactionImportService;
+ @Autowired
+ private AuditLogService auditLogService;
+
+ private UserContext userContext;
+ private String patientMatchingAlgorithmClassName;
+ private boolean shouldMatchExactPatientId;
+ private String loginUuid;
+ private boolean shouldPerformForm2Validations;
+
+ private static final Logger log = LogManager.getLogger(EncounterPersister.class);
+
+ public void init(UserContext userContext, String patientMatchingAlgorithmClassName, boolean shouldMatchExactPatientId, String loginUuid, boolean shouldPerformForm2Validations) {
+ this.userContext = userContext;
+ this.patientMatchingAlgorithmClassName = patientMatchingAlgorithmClassName;
+ this.shouldMatchExactPatientId = shouldMatchExactPatientId;
+ this.loginUuid = loginUuid;
+ this.shouldPerformForm2Validations = shouldPerformForm2Validations;
+ }
+
+ @Override
+ public Messages validate(MultipleEncounterRow multipleEncounterRow) {
+ return new Messages();
+ }
+
+ @Override
+ public Messages persist(MultipleEncounterRow multipleEncounterRow) {
+ // This validation is needed as patientservice toString returns all patients for empty patient identifier
+ if (StringUtils.isEmpty(multipleEncounterRow.patientIdentifier)) {
+ return noMatchingPatients(multipleEncounterRow);
+ }
+ synchronized ((IMPORT_ID + multipleEncounterRow.patientIdentifier).intern()) {
+ try {
+ Context.openSession();
+ Context.setUserContext(userContext);
+
+ Patient patient = patientMatchService.getPatient(patientMatchingAlgorithmClassName, multipleEncounterRow.patientAttributes,
+ multipleEncounterRow.patientIdentifier, shouldMatchExactPatientId);
+ if (patient == null) {
+ return noMatchingPatients(multipleEncounterRow);
+ }
+
+ Set providers = getProviders(multipleEncounterRow.providerName);
+
+ if(providers.isEmpty()) {
+ return noMatchingProviders(multipleEncounterRow);
+ }
+
+ List bahmniEncounterTransactions = bahmniEncounterTransactionImportService.getBahmniEncounterTransaction(multipleEncounterRow, patient, shouldPerformForm2Validations);
+
+ for (BahmniEncounterTransaction bahmniEncounterTransaction : bahmniEncounterTransactions) {
+ bahmniEncounterTransaction.setLocationUuid(loginUuid);
+ bahmniEncounterTransaction.setProviders(providers);
+ duplicateObservationService.filter(bahmniEncounterTransaction, patient, multipleEncounterRow.getVisitStartDate(), multipleEncounterRow.getVisitEndDate());
+ }
+ Boolean isAuditLogEnabled = Boolean.valueOf(Context.getAdministrationService().getGlobalProperty("bahmni.enableAuditLog"));
+ for (BahmniEncounterTransaction bahmniEncounterTransaction : bahmniEncounterTransactions) {
+ BahmniEncounterTransaction updatedBahmniEncounterTransaction = bahmniEncounterTransactionService.save(bahmniEncounterTransaction, patient, multipleEncounterRow.getVisitStartDate(), multipleEncounterRow.getVisitEndDate());
+ if (isAuditLogEnabled) {
+ Map params = new HashMap<>();
+ params.put("encounterUuid", updatedBahmniEncounterTransaction.getEncounterUuid());
+ params.put("encounterType", updatedBahmniEncounterTransaction.getEncounterType());
+ auditLogService.createAuditLog(patient.getUuid(), "EDIT_ENCOUNTER", "EDIT_ENCOUNTER_MESSAGE", params, "MODULE_LABEL_ADMIN_KEY");
+ }
+ }
+
+ return new Messages();
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ Context.clearSession();
+ return new Messages(e);
+ } finally {
+ Context.flushSession();
+ Context.closeSession();
+ }
+ }
+ }
+
+ private Set getProviders(String providerName) {
+ Set encounterTransactionProviders = new HashSet<>();
+
+ if (StringUtils.isEmpty(providerName)) {
+ providerName = userContext.getAuthenticatedUser().getUsername();
+ }
+
+ User user = Context.getUserService().getUserByUsername(providerName);
+
+ if (user == null){
+ return encounterTransactionProviders;
+ }
+
+ Collection providers = Context.getProviderService().getProvidersByPerson(user.getPerson());
+
+ Set providerSet = new HashSet<>(providers);
+
+ BahmniProviderMapper bahmniProviderMapper = new BahmniProviderMapper();
+
+ Iterator iterator = providerSet.iterator();
+ while (iterator.hasNext()) {
+ encounterTransactionProviders.add(bahmniProviderMapper.map((Provider) iterator.next()));
+ }
+
+ return encounterTransactionProviders;
+ }
+
+ private Messages noMatchingPatients(MultipleEncounterRow multipleEncounterRow) {
+ return new Messages("No matching patients found with ID:'" + multipleEncounterRow.patientIdentifier + "'");
+ }
+
+ private Messages noMatchingProviders(MultipleEncounterRow multipleEncounterRow) {
+ return new Messages("No matching providers found with username:'" + multipleEncounterRow.providerName + "'");
+ }
+}
\ No newline at end of file
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/LabResultPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/LabResultPersister.java
new file mode 100644
index 0000000000..648bda52df
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/LabResultPersister.java
@@ -0,0 +1,163 @@
+package org.bahmni.module.admin.csv.persister;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.csv.models.LabResultRow;
+import org.bahmni.module.admin.csv.models.LabResultsRow;
+import org.bahmni.module.admin.csv.service.PatientMatchService;
+import org.openmrs.CareSetting;
+import org.openmrs.Concept;
+import org.openmrs.ConceptNumeric;
+import org.openmrs.Encounter;
+import org.openmrs.EncounterRole;
+import org.openmrs.Obs;
+import org.openmrs.Order;
+import org.openmrs.Patient;
+import org.openmrs.Provider;
+import org.openmrs.Visit;
+import org.openmrs.api.APIException;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.EncounterService;
+import org.openmrs.api.OrderService;
+import org.openmrs.api.ProviderService;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+import org.openmrs.module.auditlog.service.AuditLogService;
+import org.openmrs.module.bahmniemrapi.encountertransaction.command.impl.BahmniVisitAttributeService;
+import org.openmrs.module.bahmniemrapi.encountertransaction.service.VisitIdentificationHelper;
+import org.openmrs.module.bahmniemrapi.laborder.contract.LabOrderResult;
+import org.openmrs.module.bahmniemrapi.laborder.mapper.LabOrderResultMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+
+@Component
+public class LabResultPersister implements EntityPersister {
+ private static final Log log = LogFactory.getLog(LabResultPersister.class);
+ public static final String LAB_RESULT_ENCOUNTER_TYPE = "LAB_RESULT";
+ public static final String LAB_ORDER_TYPE = "Lab Order";
+ private String patientMatchingAlgorithmClassName;
+ private boolean shouldMatchExactPatientId;
+
+ @Autowired
+ private PatientMatchService patientMatchService;
+ @Autowired
+ private ConceptService conceptService;
+ @Autowired
+ private OrderService orderService;
+ @Autowired
+ private ProviderService providerService;
+ @Autowired
+ private EncounterService encounterService;
+ @Autowired
+ private VisitIdentificationHelper visitIdentificationHelper;
+ @Autowired
+ private LabOrderResultMapper labOrderResultMapper;
+ @Autowired
+ private BahmniVisitAttributeService bahmniVisitAttributeSaveCommand;
+ @Autowired
+ private AuditLogService auditLogService;
+ private UserContext userContext;
+ private String loginLocationUuid;
+
+ public void init(UserContext userContext, String patientMatchingAlgorithmClassName, boolean shouldMatchExactPatientId, String loginLocationUuid) {
+ this.userContext = userContext;
+ this.patientMatchingAlgorithmClassName = patientMatchingAlgorithmClassName;
+ this.shouldMatchExactPatientId = shouldMatchExactPatientId;
+ this.loginLocationUuid = loginLocationUuid;
+ }
+
+ @Override
+ public Messages persist(LabResultsRow labResultsRow) {
+ try {
+ Patient patient = patientMatchService.getPatient(patientMatchingAlgorithmClassName, labResultsRow.getPatientAttributes(), labResultsRow.getPatientIdentifier(), shouldMatchExactPatientId);
+ Visit visit = visitIdentificationHelper.getVisitFor(patient, labResultsRow.getVisitType(), labResultsRow.getTestDate(), labResultsRow.getTestDate(), labResultsRow.getTestDate(), loginLocationUuid);
+ Encounter encounter = new Encounter();
+ encounter.setPatient(patient);
+ encounter.setEncounterDatetime(labResultsRow.getTestDate());
+ encounter.setEncounterType(encounterService.getEncounterType(LAB_RESULT_ENCOUNTER_TYPE));
+ encounter.addProvider(encounterService.getEncounterRoleByUuid(EncounterRole.UNKNOWN_ENCOUNTER_ROLE_UUID), getProvider());
+ HashSet resultObservations = new HashSet<>();
+ for (LabResultRow labResultRow : labResultsRow.getTestResults()) {
+ org.openmrs.Concept concept = conceptService.getConceptByName(labResultRow.getTest());
+ if (concept.isNumeric()) {
+ ConceptNumeric cn = (ConceptNumeric) concept;
+ if (!cn.isAllowDecimal() && labResultRow.getResult().contains(".")) {
+ throw new APIException("Decimal is not allowed for " + cn.getName() + " concept");
+ }
+ }
+ Order testOrder = getTestOrder(patient, labResultRow, labResultsRow.getTestDate());
+ encounter.addOrder(testOrder);
+ resultObservations.add(getResultObs(labResultRow.getResult(), testOrder, concept));
+ }
+ visit.addEncounter(encounter);
+ Encounter savedEncounter = encounterService.saveEncounter(encounter);
+ bahmniVisitAttributeSaveCommand.save(savedEncounter);
+ Boolean isAuditLogEnabled = Boolean.valueOf(Context.getAdministrationService().getGlobalProperty("bahmni.enableAuditLog"));
+ if (isAuditLogEnabled) {
+ Map params = new HashMap<>();
+ params.put("encounterUuid", savedEncounter.getUuid());
+ params.put("encounterType", savedEncounter.getEncounterType().getName());
+ auditLogService.createAuditLog(patient.getUuid(), "EDIT_ENCOUNTER", "EDIT_ENCOUNTER_MESSAGE", params, "MODULE_LABEL_ADMIN_KEY");
+ }
+ saveResults(encounter, resultObservations);
+ return new Messages();
+ } catch (Exception e) {
+ throw new APIException(e.getMessage(), e);
+ }
+ }
+
+ // Hack: OpenMRS doesn't allow saving order and its associated observations in single call
+ // throws error object references an unsaved transient instance - save the transient instance before flushing
+ private void saveResults(Encounter encounter, HashSet resultObservations) {
+ for (Obs obs : resultObservations) {
+ encounter.addObs(obs);
+ }
+ encounterService.saveEncounter(encounter);
+ }
+
+ private Order getTestOrder(Patient patient, LabResultRow labResultRow, Date testDate) throws ParseException {
+ Order order = new Order();
+ order.setConcept(conceptService.getConceptByName(labResultRow.getTest()));
+ order.setDateActivated(testDate);
+ order.setAutoExpireDate(testDate);
+ order.setPatient(patient);
+ order.setOrderType(orderService.getOrderTypeByName(LAB_ORDER_TYPE));
+ order.setCareSetting(orderService.getCareSettingByName(CareSetting.CareSettingType.OUTPATIENT.toString()));
+ order.setOrderer(getProvider());
+ return order;
+ }
+
+ private Obs getResultObs(String labResult, Order testOrder, Concept concept) {
+ LabOrderResult labOrderResult = new LabOrderResult();
+ labOrderResult.setResult(labResult);
+ labOrderResult.setResultDateTime(testOrder.getDateActivated());
+ if (concept.getDatatype().getHl7Abbreviation().equals(org.openmrs.ConceptDatatype.CODED)) {
+ Concept resultConcept = conceptService.getConceptByName(labResult);
+ if (resultConcept != null)
+ labOrderResult.setResultUuid(resultConcept.getUuid());
+ else
+ log.warn(String.format("Concept is not available in OpenMRS for concept name: %s", labResult));
+ }
+ return labOrderResultMapper.map(labOrderResult, testOrder, testOrder.getConcept());
+ }
+
+ private Provider getProvider() {
+ Collection providers = providerService.getProvidersByPerson(userContext.getAuthenticatedUser().getPerson());
+ return providers.size() > 0 ? providers.iterator().next() : null;
+ }
+
+ @Override
+ public Messages validate(LabResultsRow labResultsRow) {
+ return new Messages();
+ }
+}
\ No newline at end of file
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/PatientPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/PatientPersister.java
new file mode 100644
index 0000000000..d8865873eb
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/PatientPersister.java
@@ -0,0 +1,86 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.common.config.registration.service.RegistrationPageReaderService;
+import org.bahmni.common.config.registration.service.RegistrationPageService;
+import org.bahmni.common.config.registration.service.impl.RegistrationPageReaderServiceImpl;
+import org.bahmni.common.config.registration.service.impl.RegistrationPageServiceImpl;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.csv.models.PatientRow;
+import org.bahmni.module.admin.csv.service.CSVAddressService;
+import org.bahmni.module.admin.csv.service.CSVPatientService;
+import org.openmrs.api.AdministrationService;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.PatientService;
+import org.openmrs.api.PersonService;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+import org.openmrs.module.addresshierarchy.service.AddressHierarchyService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PatientPersister implements EntityPersister {
+ private UserContext userContext;
+
+ @Autowired
+ private PatientService patientService;
+
+ @Autowired
+ private PersonService personService;
+
+ @Autowired
+ private ConceptService conceptService;
+
+ @Autowired
+ @Qualifier("adminService")
+ private AdministrationService administrationService;
+
+ private CSVAddressService csvAddressService;
+
+ private static final Logger log = LogManager.getLogger(PatientPersister.class);
+
+ private RegistrationPageReaderService registrationPageReaderService = new RegistrationPageReaderServiceImpl();
+
+ private RegistrationPageService registrationPageService = new RegistrationPageServiceImpl(registrationPageReaderService);
+
+
+ public void init(UserContext userContext) {
+ this.userContext = userContext;
+ }
+
+ @Override
+ public Messages persist(PatientRow patientRow) {
+ try {
+ Context.openSession();
+ Context.setUserContext(userContext);
+
+ new CSVPatientService(patientService, personService, conceptService, administrationService, getAddressHierarchyService(), registrationPageService).save(patientRow);
+
+ return new Messages();
+ } catch (Throwable e) {
+ log.error(e.getMessage(), e);
+ Context.clearSession();
+ return new Messages(e);
+ } finally {
+ Context.flushSession();
+ Context.closeSession();
+ }
+ }
+
+ private CSVAddressService getAddressHierarchyService() {
+ if (csvAddressService == null) {
+ AddressHierarchyService addressHierarchyService = Context.getService(AddressHierarchyService.class);
+ this.csvAddressService = new CSVAddressService(addressHierarchyService);
+ }
+ return csvAddressService;
+ }
+
+ @Override
+ public Messages validate(PatientRow csvEntity) {
+ return new Messages();
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/PatientProgramPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/PatientProgramPersister.java
new file mode 100644
index 0000000000..3aff82d3db
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/PatientProgramPersister.java
@@ -0,0 +1,114 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.csv.models.PatientProgramRow;
+import org.bahmni.module.admin.csv.service.PatientMatchService;
+import org.openmrs.ConceptName;
+import org.openmrs.Patient;
+import org.openmrs.PatientProgram;
+import org.openmrs.Program;
+import org.openmrs.api.ProgramWorkflowService;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class PatientProgramPersister implements EntityPersister {
+ @Autowired
+ private PatientMatchService patientMatchService;
+ @Autowired
+ private ProgramWorkflowService programWorkflowService;
+
+ private UserContext userContext;
+
+ private static final Logger log = LogManager.getLogger(PatientProgramPersister.class);
+ private String patientMatchingAlgorithmClassName;
+
+ public void init(UserContext userContext, String patientMatchingAlgorithmClassName) {
+ this.userContext = userContext;
+ this.patientMatchingAlgorithmClassName = patientMatchingAlgorithmClassName;
+ }
+
+ @Override
+ public Messages validate(PatientProgramRow patientProgramRow) {
+ return new Messages();
+ }
+
+ @Override
+ public Messages persist(PatientProgramRow patientProgramRow) {
+ // This validation is needed as patientservice toString returns all patients for empty patient identifier
+ if (StringUtils.isEmpty(patientProgramRow.patientIdentifier)) {
+ return noMatchingPatients(patientProgramRow);
+ }
+
+ try {
+ Context.openSession();
+ Context.setUserContext(userContext);
+
+ boolean shouldMatchExactPatientId = false; //Mujir - defaulting to false for now. Not sure if we have program data for migration that does not have exact patient identifiers.
+
+ Patient patient = patientMatchService.getPatient(patientMatchingAlgorithmClassName, patientProgramRow.patientAttributes, patientProgramRow.patientIdentifier, shouldMatchExactPatientId);
+ if (patient == null) {
+ return noMatchingPatients(patientProgramRow);
+ }
+
+ Program program = getProgramByName(patientProgramRow.programName);
+ List existingEnrolledPrograms = programWorkflowService.getPatientPrograms(patient, program, null, null, null, null, false);
+ if (existingEnrolledPrograms != null && !existingEnrolledPrograms.isEmpty()) {
+ return new Messages(getErrorMessage(existingEnrolledPrograms));
+ }
+
+ PatientProgram patientProgram = new PatientProgram();
+ patientProgram.setPatient(patient);
+ patientProgram.setProgram(program);
+ patientProgram.setDateEnrolled(patientProgramRow.getEnrollmentDate());
+
+ programWorkflowService.savePatientProgram(patientProgram);
+
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ Context.clearSession();
+ return new Messages(e);
+ } finally {
+ Context.flushSession();
+ Context.closeSession();
+ }
+ return new Messages();
+ }
+
+ private String getErrorMessage(List patientPrograms) {
+ PatientProgram existingProgram = patientPrograms.get(0);
+ String errorMessage = "Patient already enrolled in " + existingProgram.getProgram().getName() + " from " + existingProgram.getDateEnrolled();
+ errorMessage += existingProgram.getDateCompleted() == null ? "" : " to " + existingProgram.getDateCompleted();
+ return errorMessage;
+ }
+
+ private Messages noMatchingPatients(PatientProgramRow patientProgramRow) {
+ return new Messages("No matching patients found with ID:'" + patientProgramRow.patientIdentifier + "'");
+ }
+
+ private Program getProgramByName(String programName) {
+ for (Program program : programWorkflowService.getAllPrograms()) {
+ if (isNamed(program, programName)) {
+ return program;
+ }
+ }
+ throw new RuntimeException("No matching Program found with name: " + programName);
+ }
+
+ private boolean isNamed(Program program, String programName) {
+ for (ConceptName conceptName : program.getConcept().getNames()) {
+ if (programName.equalsIgnoreCase(conceptName.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/ReferenceTermPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ReferenceTermPersister.java
new file mode 100644
index 0000000000..76c63454dc
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/ReferenceTermPersister.java
@@ -0,0 +1,60 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.csv.models.ReferenceTermRow;
+import org.bahmni.module.referencedata.labconcepts.service.ReferenceDataConceptReferenceTermService;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ReferenceTermPersister implements EntityPersister {
+
+ private UserContext userContext;
+ private static final Logger log = LogManager.getLogger(PatientPersister.class);
+
+ @Autowired
+ private ReferenceDataConceptReferenceTermService referenceDataConceptReferenceTermService;
+
+ public void init(UserContext userContext) {
+ this.userContext = userContext;
+ }
+
+ @Override
+ public Messages persist(ReferenceTermRow referenceTermRow) {
+ try {
+ Context.openSession();
+ Context.setUserContext(userContext);
+
+ referenceDataConceptReferenceTermService.saveOrUpdate(getConceptReferenceTerm(referenceTermRow));
+ } catch (Throwable e) {
+ log.error(e.getMessage(), e);
+ Context.clearSession();
+ return new Messages(e);
+ } finally {
+ Context.flushSession();
+ Context.closeSession();
+ }
+ return new Messages();
+ }
+
+ private org.bahmni.module.referencedata.labconcepts.contract.ConceptReferenceTerm getConceptReferenceTerm(ReferenceTermRow referenceTermRow) {
+ return new org.bahmni.module.referencedata.labconcepts.contract.ConceptReferenceTerm(
+ referenceTermRow.getCode(),
+ referenceTermRow.getName(),
+ null,
+ referenceTermRow.getSource(),
+ referenceTermRow.getDescription(),
+ referenceTermRow.getVersion()
+ );
+ }
+
+ @Override
+ public Messages validate(ReferenceTermRow referenceTermRow) {
+ return new Messages();
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/persister/RelationshipPersister.java b/admin/src/main/java/org/bahmni/module/admin/csv/persister/RelationshipPersister.java
new file mode 100644
index 0000000000..14dac8869d
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/persister/RelationshipPersister.java
@@ -0,0 +1,96 @@
+package org.bahmni.module.admin.csv.persister;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.EntityPersister;
+import org.bahmni.csv.Messages;
+import org.bahmni.module.admin.csv.models.RelationshipRow;
+import org.bahmni.module.admin.csv.service.CSVRelationshipService;
+import org.bahmni.module.admin.csv.utils.CSVUtils;
+import org.bahmni.module.bahmnicommons.api.service.BahmniPatientService;
+import org.openmrs.api.AdministrationService;
+import org.openmrs.api.PersonService;
+import org.openmrs.api.ProviderService;
+import org.openmrs.api.context.Context;
+import org.openmrs.api.context.UserContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+
+@Component
+public class RelationshipPersister implements EntityPersister {
+
+ @Autowired
+ private BahmniPatientService patientService;
+
+ @Autowired
+ private ProviderService providerService;
+
+ @Autowired
+ private PersonService personService;
+
+ @Autowired
+ @Qualifier("adminService")
+ private AdministrationService administrationService;
+
+ private static final Logger log = LogManager.getLogger(RelationshipPersister.class);
+ private UserContext userContext;
+
+ public void init(UserContext userContext) {
+ this.userContext = userContext;
+ }
+
+ @Override
+ public Messages validate(RelationshipRow relationshipRow) {
+ return new Messages();
+ }
+
+ void validateRow(RelationshipRow relationshipRow){
+ if (StringUtils.isEmpty(relationshipRow.getPatientIdentifier())) {
+ throw new RuntimeException("Patient unique identifier not specified.");
+ }
+
+ if (StringUtils.isEmpty(relationshipRow.getPatientRelationIdentifier()) && StringUtils.isEmpty(relationshipRow.getProviderName())) {
+ throw new RuntimeException("Both Provider Name and Relation Identifier cannot be null.");
+ }
+
+ if (StringUtils.isEmpty(relationshipRow.getRelationshipType())) {
+ throw new RuntimeException("Relationship type is not specified.");
+ }
+
+ if ((!StringUtils.isEmpty(relationshipRow.getStartDate()) && !StringUtils.isEmpty(relationshipRow.getEndDate()))) {
+ try {
+ if (CSVUtils.getDateFromString(relationshipRow.getStartDate()).after(CSVUtils.getDateFromString(relationshipRow.getEndDate()))){
+ throw new RuntimeException("Start date should be before end date.");
+ }
+ } catch (ParseException e) {
+ throw new RuntimeException("Could not parse provided dates. Please provide date in format yyyy-mm-dd");
+ }
+ }
+ }
+
+ @Override
+ public Messages persist(RelationshipRow relationshipRow) {
+ try {
+ Context.openSession();
+ Context.setUserContext(userContext);
+ validateRow(relationshipRow);
+ new CSVRelationshipService(patientService, personService, providerService, administrationService).save(relationshipRow);
+
+ return new Messages();
+ } catch (Throwable e) {
+ log.error(e.getMessage(), e);
+ Context.clearSession();
+ return new Messages(e);
+ } finally {
+ Context.flushSession();
+ Context.closeSession();
+ }
+
+ }
+
+}
+
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVAddressService.java b/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVAddressService.java
new file mode 100644
index 0000000000..c87eb6f44f
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVAddressService.java
@@ -0,0 +1,55 @@
+package org.bahmni.module.admin.csv.service;
+
+import org.bahmni.csv.KeyValue;
+import org.openmrs.PersonAddress;
+import org.openmrs.module.addresshierarchy.AddressHierarchyLevel;
+import org.openmrs.module.addresshierarchy.service.AddressHierarchyService;
+import org.openmrs.module.addresshierarchy.util.AddressHierarchyUtil;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class CSVAddressService {
+
+ private AddressHierarchyService addressHierarchyService;
+ private List addressHierarchyLevels;
+
+ public CSVAddressService() {
+ }
+
+ public CSVAddressService(AddressHierarchyService addressHierarchyService) {
+ this.addressHierarchyService = addressHierarchyService;
+ }
+
+ public PersonAddress getPersonAddress(List addressParts) {
+ if (addressHierarchyLevels == null) {
+ addressHierarchyLevels = addressHierarchyService.getAddressHierarchyLevels();
+ }
+
+ return mapPersonAddressFields(addressParts, addressHierarchyLevels);
+ }
+
+ private PersonAddress mapPersonAddressFields(List addressParts, List addressHierarchyLevels) {
+ Map addressFieldToValueMap = new HashMap<>();
+ for (KeyValue addressPart : addressParts) {
+ AddressHierarchyLevel addressHierarchyLevel = findAddressHierarchyLevel(addressPart.getKey(), addressHierarchyLevels);
+ addressFieldToValueMap.put(addressHierarchyLevel.getAddressField().getName(), addressPart.getValue());
+ }
+ return AddressHierarchyUtil.convertAddressMapToPersonAddress(addressFieldToValueMap);
+ }
+
+ private AddressHierarchyLevel findAddressHierarchyLevel(String key, List addressHierarchyLevels) {
+ for (AddressHierarchyLevel addressHierarchyLevel : addressHierarchyLevels) {
+ if (addressHierarchyLevel.getName().equals(key)) {
+ return addressHierarchyLevel;
+ }
+ }
+ throw new RuntimeException(String.format("Address Hierarchy level {0} does not exist.", key));
+ }
+
+
+}
+
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVPatientService.java b/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVPatientService.java
new file mode 100644
index 0000000000..455eff5b29
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVPatientService.java
@@ -0,0 +1,158 @@
+package org.bahmni.module.admin.csv.service;
+
+import org.apache.commons.lang.StringUtils;
+import org.bahmni.common.config.registration.service.RegistrationPageService;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.models.PatientRow;
+import org.openmrs.Concept;
+import org.openmrs.ConceptName;
+import org.openmrs.Patient;
+import org.openmrs.PatientIdentifier;
+import org.openmrs.PatientIdentifierType;
+import org.openmrs.PersonAddress;
+import org.openmrs.PersonAttribute;
+import org.openmrs.PersonAttributeType;
+import org.openmrs.PersonName;
+import org.openmrs.api.AdministrationService;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.PatientService;
+import org.openmrs.api.PersonService;
+
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateStringInSupportedFormat;
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateFromString;
+
+public class CSVPatientService {
+
+ private static final String BAHMNI_PRIMARY_IDENTIFIER_TYPE = "bahmni.primaryIdentifierType";
+ private static final String REGISTRATION_PAGE_VALIDATION_MANDATORY_FIELD = "bahmni.registration.validation.mandatoryField";
+ private boolean DEFAULT_MANDATORY_FIELD_VALIDATION = true;
+
+ private PatientService patientService;
+ private PersonService personService;
+ private ConceptService conceptService;
+ private AdministrationService administrationService;
+ private CSVAddressService csvAddressService;
+ private RegistrationPageService registrationPageService;
+
+ public CSVPatientService(PatientService patientService, PersonService personService, ConceptService conceptService, AdministrationService administrationService, CSVAddressService csvAddressService, RegistrationPageService registrationPageService) {
+ this.patientService = patientService;
+ this.personService = personService;
+ this.conceptService = conceptService;
+ this.administrationService = administrationService;
+ this.csvAddressService = csvAddressService;
+ this.registrationPageService = registrationPageService;
+ }
+
+ public Patient save(PatientRow patientRow) throws ParseException {
+ Patient patient = new Patient();
+ PersonName personName = new PersonName(patientRow.firstName, patientRow.middleName, patientRow.lastName);
+ personName.setPreferred(true);
+ patient.addName(personName);
+
+ addPersonAttributes(patient, patientRow);
+
+ if (!StringUtils.isBlank(patientRow.birthdate)) {
+ patient.setBirthdate(getDateFromString(patientRow.birthdate));
+ } else if (!StringUtils.isBlank(patientRow.age)) {
+ patient.setBirthdateFromAge(Integer.parseInt(patientRow.age), new Date());
+ }
+ patient.setGender(patientRow.gender);
+ patient.addIdentifier(new PatientIdentifier(patientRow.registrationNumber, getPatientIdentifierType(), null));
+
+ List addressParts = patientRow.addressParts;
+ PersonAddress personAddress = csvAddressService.getPersonAddress(addressParts);
+ if (personAddress != null) {
+ patient.addAddress(personAddress);
+ }
+
+ patient.setPersonDateCreated(patientRow.getRegistrationDate());
+
+ patientService.savePatient(patient);
+
+ return patient;
+ }
+
+ private void addPersonAttributes(Patient patient, PatientRow patientRow) throws ParseException {
+ final List mandatoryAttributes = registrationPageService.getMandatoryAttributes();
+ for (KeyValue attribute : patientRow.attributes) {
+ if (validateForMandatoryAttribute(mandatoryAttributes, attribute))
+ continue;
+ PersonAttributeType personAttributeType = findAttributeType(attribute.getKey());
+ if (personAttributeType.getFormat().equalsIgnoreCase("org.openmrs.Concept")) {
+ Concept concept = getConceptByName(attribute.getValue());
+ if (concept != null) {
+ patient.addAttribute(new PersonAttribute(personAttributeType, concept.getId().toString()));
+ } else {
+ throw new RuntimeException("Invalid value for Attribute." + attribute.getKey());
+ }
+ } else if (personAttributeType.getFormat().startsWith("java.lang.")) {
+ patient.addAttribute(new PersonAttribute(findAttributeType(attribute.getKey()), attribute.getValue()));
+ } else if (personAttributeType.getFormat().startsWith("org.openmrs.util.AttributableDate")) {
+
+ String dateString = attribute.getValue();
+ //Validating the Date format
+ getDateFromString(dateString);
+ String supportedDateString = getDateStringInSupportedFormat(dateString);
+ patient.addAttribute(new PersonAttribute(findAttributeType(attribute.getKey()),supportedDateString));
+ }
+ }
+ }
+
+ private boolean validateForMandatoryAttribute(List mandatoryAttributes, KeyValue attribute) {
+ boolean skipCurrentAttribute = false;
+ final boolean mandatoryFieldValidationRequired = isMandatoryFieldValidationRequired();
+ if(StringUtils.isBlank(attribute.getValue())) {
+ if(mandatoryFieldValidationRequired) {
+ if(mandatoryAttributes == null)
+ throw new RuntimeException("Error in reading patient registration config");
+ else if(mandatoryAttributes.contains(attribute.getKey()))
+ throw new RuntimeException(String.format("Missing value for mandatory attribute \"%s\"", attribute.getKey()));
+ else
+ skipCurrentAttribute = true;
+ } else
+ skipCurrentAttribute = true;
+ }
+ return skipCurrentAttribute;
+ }
+
+ private Concept getConceptByName(String name) {
+ List concepts = conceptService.getConceptsByName(name);
+ if (concepts != null) {
+ for (Concept concept : concepts) {
+ Collection nameCollection = concept.getNames();
+ for (ConceptName conceptName : nameCollection) {
+ if (conceptName.getName().equalsIgnoreCase(name) && (conceptName.isPreferred() || conceptName.isFullySpecifiedName() || conceptName.isShort())) {
+ return concept;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private PersonAttributeType findAttributeType(String key) {
+ for (PersonAttributeType personAttributeType : personService.getAllPersonAttributeTypes(false)) {
+ if (key.equalsIgnoreCase(personAttributeType.getName())) {
+ return personAttributeType;
+ }
+ }
+
+ throw new RuntimeException(String.format("Person Attribute %s not found", key));
+ }
+
+ private PatientIdentifierType getPatientIdentifierType() {
+ String globalProperty = administrationService.getGlobalProperty(BAHMNI_PRIMARY_IDENTIFIER_TYPE);
+ return patientService.getPatientIdentifierTypeByUuid(globalProperty);
+ }
+
+ private boolean isMandatoryFieldValidationRequired() {
+ String mandatoryFieldValidationGlobalProperty = administrationService.getGlobalProperty(REGISTRATION_PAGE_VALIDATION_MANDATORY_FIELD);
+ return StringUtils.isNotBlank(mandatoryFieldValidationGlobalProperty) ? Boolean.valueOf(mandatoryFieldValidationGlobalProperty) : DEFAULT_MANDATORY_FIELD_VALIDATION;
+ }
+
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVRelationshipService.java b/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVRelationshipService.java
new file mode 100644
index 0000000000..cf46580a72
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/service/CSVRelationshipService.java
@@ -0,0 +1,165 @@
+package org.bahmni.module.admin.csv.service;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.bahmni.module.admin.csv.models.RelationshipRow;
+import org.bahmni.module.bahmnicommons.api.service.BahmniPatientService;
+import org.openmrs.Patient;
+import org.openmrs.Person;
+import org.openmrs.Provider;
+import org.openmrs.Relationship;
+import org.openmrs.RelationshipType;
+import org.openmrs.api.AdministrationService;
+import org.openmrs.api.PersonService;
+import org.openmrs.api.ProviderService;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateFromString;
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getTodayDate;
+
+public class CSVRelationshipService {
+
+ private static final String BAHMNI_RELATIONSHIP_TYPE_MAP_PROPERTY = "bahmni.relationshipTypeMap";
+ private static final String PATIENT_RELATIONSHIP = "patient";
+ private static final String PROVIDER_RELATIONSHIP = "provider";
+
+ private BahmniPatientService patientService;
+ private PersonService personService;
+ private ProviderService providerService;
+ private AdministrationService administrationService;
+
+
+ public CSVRelationshipService(BahmniPatientService patientService, PersonService personService, ProviderService providerService, AdministrationService administrationService) {
+ this.patientService = patientService;
+ this.personService = personService;
+ this.providerService = providerService;
+ this.administrationService = administrationService;
+ }
+
+ public Relationship save(RelationshipRow relationshipRow) throws ParseException {
+ List patientList = patientService.get(relationshipRow.getPatientIdentifier(), true);
+ if (null == patientList || patientList.size() == 0) {
+ throw new RuntimeException("No matching patients found with ID:'" + relationshipRow.getPatientIdentifier() + "'");
+ } else {
+ Patient patient = patientList.get(0);
+ Relationship relationship = createRelationship(relationshipRow, patient);
+ return personService.saveRelationship(relationship);
+ }
+ }
+
+ private Relationship createRelationship(RelationshipRow relationshipRow, Patient patient) throws ParseException {
+
+ RelationshipType relationshipType = getMatchingRelationship(relationshipRow.getRelationshipType());
+ Person personB = getRelatedPerson(relationshipRow);
+ Relationship relationship = checkForExistingRelationship(patient, relationshipType, personB);
+
+ if (relationship == null){
+ relationship = new Relationship();
+ relationship.setPersonA(patient);
+ relationship.setPersonB(personB);
+ relationship.setRelationshipType(relationshipType);
+ }
+
+ relationship.setStartDate(getStartDate(relationshipRow));
+ relationship.setEndDate(getEndDate(relationshipRow));
+ return relationship;
+ }
+
+ private Person getRelatedPerson(RelationshipRow relationshipRow) {
+ String relationshipMapProperty = administrationService.getGlobalProperty(BAHMNI_RELATIONSHIP_TYPE_MAP_PROPERTY);
+ Map relationshipMap = new Gson().fromJson(relationshipMapProperty, new TypeToken>() {
+ }.getType());
+
+ if (isProviderRelationship(relationshipMap, relationshipRow.getRelationshipType())) {
+ return getProvider(relationshipRow);
+
+ } else if (isPatientRelationship(relationshipMap, relationshipRow.getRelationshipType())) {
+ return getPatient(relationshipRow);
+
+ } else {
+ throw new RuntimeException("Relationship not found " + relationshipRow.getProviderName());
+ }
+ }
+
+ private Person getProvider(RelationshipRow relationshipRow) {
+ if (StringUtils.isEmpty(relationshipRow.getProviderName())) {
+ throw new RuntimeException("Provider name not found");
+ }
+ List matchedProvider = providerService.getProviders(relationshipRow.getProviderName(), null, null, null);
+ if (CollectionUtils.isEmpty(matchedProvider)) {
+ throw new RuntimeException("No matching provider found with ID:'" + relationshipRow.getProviderName() + "'");
+ }
+ return matchedProvider.get(0).getPerson();
+ }
+
+ private Person getPatient(RelationshipRow relationshipRow) {
+ List matchedPatient = patientService.get(relationshipRow.getPatientRelationIdentifier(), true);
+
+ if (CollectionUtils.isEmpty(matchedPatient)) {
+ throw new RuntimeException("No matching patients found with ID:'" + relationshipRow.getPatientRelationIdentifier() + "'");
+ }
+ return matchedPatient.get(0);
+ }
+
+ private Relationship checkForExistingRelationship(Patient patient, RelationshipType relationshipType, Person matchedPerson) {
+ List existingRelationship = personService.getRelationships(patient, matchedPerson, relationshipType);
+ if (CollectionUtils.isNotEmpty(existingRelationship)) {
+ return existingRelationship.get(0);
+ }
+ return null;
+ }
+
+ private RelationshipType getMatchingRelationship(String relationshipType) {
+ List relationshipTypes = patientService.getByAIsToB(relationshipType);
+
+ if (CollectionUtils.isEmpty(relationshipTypes)) {
+ throw new RuntimeException("No matching relationship type found with relationship type name:'" + relationshipType + "'");
+ }
+ return relationshipTypes.get(0);
+ }
+
+ private List getRelationshipTypes(Map relationshipMap, String relationship) {
+ return relationshipMap != null ? (List) relationshipMap.get(relationship) : null;
+ }
+
+ private boolean isProviderRelationship(Map relationshipMap, String relationshipType) {
+ List relationshipTypes = getRelationshipTypes(relationshipMap, PROVIDER_RELATIONSHIP);
+ return relationshipTypes != null && containsIgnoreCase(relationshipTypes, relationshipType);
+ }
+
+ private boolean isPatientRelationship(Map relationshipMap, String relationshipType) {
+ List relationshipTypes = getRelationshipTypes(relationshipMap, PATIENT_RELATIONSHIP);
+ return relationshipTypes != null && containsIgnoreCase(relationshipTypes, relationshipType);
+ }
+
+ private Date getStartDate(RelationshipRow relationshipRow) throws ParseException {
+ if (!StringUtils.isEmpty(relationshipRow.getStartDate())) {
+ return getDateFromString(relationshipRow.getStartDate());
+ }
+ return getTodayDate();
+ }
+
+ private Date getEndDate(RelationshipRow relationshipRow) throws ParseException {
+ if (!StringUtils.isEmpty(relationshipRow.getEndDate())) {
+ return getDateFromString(relationshipRow.getEndDate());
+ }
+ return null;
+ }
+
+ private boolean containsIgnoreCase(List relationshipTypes, String relationshipType) {
+ for (String relType : relationshipTypes) {
+ if(relationshipType.equalsIgnoreCase(relType)){
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/service/FormFieldPathGeneratorService.java b/admin/src/main/java/org/bahmni/module/admin/csv/service/FormFieldPathGeneratorService.java
new file mode 100644
index 0000000000..46e68d8d7d
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/service/FormFieldPathGeneratorService.java
@@ -0,0 +1,146 @@
+package org.bahmni.module.admin.csv.service;
+
+import org.bahmni.csv.KeyValue;
+import org.bahmni.form2.service.FormFieldPathService;
+import org.bahmni.module.admin.csv.models.SectionPositionValue;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.bahmni.module.admin.observation.CSVObservationHelper.getLastItem;
+import static org.springframework.util.CollectionUtils.isEmpty;
+
+@Component
+public class FormFieldPathGeneratorService {
+
+ private static final String FORM_NAMESPACE = "Bahmni";
+ private static final String FORM_FIELD_PATH_SEPARATOR = "/";
+ private Map formFieldPathAddmoreAttribute = new HashMap<>();
+ private Map> addmoreSectionIndices = new HashMap<>();
+
+ private FormFieldPathService formFieldPathService;
+
+ @Autowired
+ public FormFieldPathGeneratorService(FormFieldPathService formFieldPathService) {
+ this.formFieldPathService = formFieldPathService;
+ }
+
+ public void setFormNamespaceAndFieldPath(List form2Observations, List form2CSVHeaderParts) {
+ if (isEmpty(form2Observations)) {
+ return;
+ }
+ final EncounterTransaction.Observation observation = getLastItem(form2Observations);
+ final String formFieldPath = formFieldPathService.getFormFieldPath(form2CSVHeaderParts);
+ observation.setFormFieldPath(formFieldPath);
+ observation.setFormNamespace(FORM_NAMESPACE);
+ }
+
+ public void setFormNamespaceAndFieldPathForMultiSelectObs(List form2Observations, List form2CSVHeaderParts, List multiSelectForm2CSVObservations) {
+ if (isEmpty(form2Observations)) {
+ return;
+ }
+ int prevObsCount = form2Observations.size() - multiSelectForm2CSVObservations.size();
+ for(int i = 0; i < multiSelectForm2CSVObservations.size(); i++) {
+ final EncounterTransaction.Observation observation = form2Observations.get(prevObsCount + i);
+ final String formFieldPath = formFieldPathService.getFormFieldPath(form2CSVHeaderParts);
+ observation.setFormFieldPath(formFieldPath);
+ observation.setFormNamespace(FORM_NAMESPACE);
+ }
+ }
+
+ public void setFormNamespaceAndFieldPathForAddmoreObs(List form2Observations, List form2CSVHeaderParts, List addmoreForm2CSVObservations) {
+ if (isEmpty(form2Observations)) {
+ return;
+ }
+ int prevObsCount = form2Observations.size() - addmoreForm2CSVObservations.size();
+ final String formFieldPath = formFieldPathService.getFormFieldPath(form2CSVHeaderParts);
+ String[] tokens = formFieldPath.split(FORM_FIELD_PATH_SEPARATOR);
+ int formFieldPathPosition = tokens.length - 1;
+ String path = tokens[formFieldPathPosition];
+ String controlIdPrefix = path.split("-")[0];
+
+ for(int i = 0; i < addmoreForm2CSVObservations.size(); i++) {
+ final EncounterTransaction.Observation observation = form2Observations.get(prevObsCount + i);
+ tokens[formFieldPathPosition] = controlIdPrefix + "-" + i;
+ observation.setFormFieldPath(String.join(FORM_FIELD_PATH_SEPARATOR, tokens));
+ observation.setFormNamespace(FORM_NAMESPACE);
+ }
+ }
+
+ public void setFormNamespaceAndFieldPathForJsonValue(List form2Observations, List form2CSVHeaderParts, List addmoreSectionCSVObservations, List sectionPositionValues) {
+ if (isEmpty(form2Observations)) {
+ return;
+ }
+
+ updateFormFieldPathWithAddmoreAttribute(form2CSVHeaderParts);
+
+ int prevObsCount = form2Observations.size() - addmoreSectionCSVObservations.size();
+ final String formFieldPath = formFieldPathService.getFormFieldPath(form2CSVHeaderParts);
+
+ for(int i = 0; i < addmoreSectionCSVObservations.size(); i++) {
+ final EncounterTransaction.Observation observation = form2Observations.get(prevObsCount + i);
+ updateObsWithFormFieldPath(observation, form2CSVHeaderParts, sectionPositionValues, prevObsCount, formFieldPath, i);
+ }
+ }
+
+ private void updateObsWithFormFieldPath(EncounterTransaction.Observation observation, List form2CSVHeaderParts, List sectionPositionValues, int prevObsCount, String formFieldPath, int csvObservationIndex) {
+ String[] tokens = formFieldPath.split(FORM_FIELD_PATH_SEPARATOR);
+ List indicesInJson = addmoreSectionIndices.get(form2CSVHeaderParts.toString());
+ String sectionPositionIndex = sectionPositionValues.get(csvObservationIndex).getSectionIndex();
+
+ // update form field path for sections based on JSON value
+ for(int j = 0; j < indicesInJson.size() - 1; j++) {
+ int addmoreSectionIndex = 0;
+ int sectionIndexPosition = indicesInJson.get(j);
+ String partialFormFieldPath = tokens[sectionIndexPosition];
+ String controlIdPrefix = partialFormFieldPath.split("-")[0];
+ if(sectionPositionIndex.contains(FORM_FIELD_PATH_SEPARATOR)) {
+ String[] indices = sectionPositionIndex.split(FORM_FIELD_PATH_SEPARATOR);
+ addmoreSectionIndex = Integer.parseInt(indices[j+1]);
+ } else {
+ addmoreSectionIndex = Integer.parseInt(sectionPositionIndex);
+ }
+ tokens[sectionIndexPosition] = controlIdPrefix + "-" + addmoreSectionIndex;
+ }
+
+ // update form field path for section having observation.
+ int sectionWithObsIndexPosition = indicesInJson.get(indicesInJson.size() - 1);
+ String partialFormFieldPath = tokens[sectionWithObsIndexPosition];
+ String controlIdPrefix = partialFormFieldPath.split("-")[0];
+ tokens[sectionWithObsIndexPosition] = controlIdPrefix + "-" + sectionPositionValues.get(csvObservationIndex).getValueIndex();
+
+ if(sectionPositionValues.get(csvObservationIndex).getAddmoreIndex() != -1) {
+ int obsAddmoreIndex = sectionPositionValues.get(csvObservationIndex).getAddmoreIndex();
+ String addmoreFormControlId = tokens[tokens.length - 1];
+ String addmoreControlIdPrefix = addmoreFormControlId.split("-")[0];
+ tokens[tokens.length - 1] = addmoreControlIdPrefix + "-" + obsAddmoreIndex;
+ }
+ observation.setFormFieldPath(String.join(FORM_FIELD_PATH_SEPARATOR, tokens));
+ observation.setFormNamespace(FORM_NAMESPACE);
+ }
+
+ private void updateFormFieldPathWithAddmoreAttribute(List form2CSVHeaderParts) {
+ if(formFieldPathAddmoreAttribute.get(form2CSVHeaderParts.toString()) == null) {
+ List indices = new ArrayList<>();
+ boolean isFirstAddmoreIdentified = false;
+ int intialSectionsWithoutAddmore = 0;
+ for (int i = 1; i < form2CSVHeaderParts.size(); i++) {
+ List headerPartsList = form2CSVHeaderParts.subList(0, i + 1);
+ boolean addmore = formFieldPathService.isAddmore(headerPartsList);
+ if(!addmore && !isFirstAddmoreIdentified) {
+ isFirstAddmoreIdentified = true;
+ intialSectionsWithoutAddmore++;
+ }
+ formFieldPathAddmoreAttribute.put(headerPartsList.toString(), addmore);
+ if(addmore)
+ indices.add(i - intialSectionsWithoutAddmore);
+ }
+ addmoreSectionIndices.put(form2CSVHeaderParts.toString(), indices);
+ }
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/service/PatientMatchService.java b/admin/src/main/java/org/bahmni/module/admin/csv/service/PatientMatchService.java
new file mode 100644
index 0000000000..c10a94e696
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/service/PatientMatchService.java
@@ -0,0 +1,62 @@
+package org.bahmni.module.admin.csv.service;
+
+import groovy.lang.GroovyClassLoader;
+import org.apache.commons.lang.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.patientmatchingalgorithm.BahmniPatientMatchingAlgorithm;
+import org.bahmni.module.admin.csv.patientmatchingalgorithm.PatientMatchingAlgorithm;
+import org.bahmni.module.admin.csv.patientmatchingalgorithm.exception.CannotMatchPatientException;
+import org.bahmni.module.bahmnicommons.api.service.BahmniPatientService;
+import org.openmrs.Patient;
+import org.openmrs.util.OpenmrsUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class PatientMatchService {
+ @Autowired
+ private BahmniPatientService patientService;
+
+ private static final String PATIENT_MATCHING_ALGORITHM_DIRECTORY = "/patientMatchingAlgorithm/";
+ private static final Logger log = LogManager.getLogger(PatientMatchService.class);
+
+ // Mujir - an implementation could use multiple patient matching algorithms
+ protected Map patientMatchingAlgorithms = new HashMap<>();
+
+ public Patient getPatient(String matchingAlgorithmClassName, List patientAttributes, String patientIdentifier, boolean shouldMatchExactPatientId) throws IOException, IllegalAccessException, InstantiationException, CannotMatchPatientException {
+ List matchingPatients = patientService.get(patientIdentifier, shouldMatchExactPatientId);
+ return matchPatients(matchingPatients, patientAttributes, matchingAlgorithmClassName);
+ }
+
+ private Patient matchPatients(List matchingPatients, List patientAttributes, String matchingAlgorithmClassName) throws IOException, IllegalAccessException, InstantiationException, CannotMatchPatientException {
+ if (StringUtils.isEmpty(matchingAlgorithmClassName)) {
+ return new BahmniPatientMatchingAlgorithm().run(matchingPatients, patientAttributes);
+ }
+ PatientMatchingAlgorithm patientMatchingAlgorithm = getPatientMatchingAlgorithm(matchingAlgorithmClassName);
+ log.debug("PatientMatching : Using Algorithm in " + patientMatchingAlgorithm.getClass().getName());
+ return patientMatchingAlgorithm.run(matchingPatients, patientAttributes);
+ }
+
+ private PatientMatchingAlgorithm getPatientMatchingAlgorithm(String matchingAlgorithmClassName) throws IOException, InstantiationException, IllegalAccessException {
+ PatientMatchingAlgorithm patientMatchingAlgorithm = patientMatchingAlgorithms.get(matchingAlgorithmClassName);
+ if (patientMatchingAlgorithm == null) {
+ Class clazz = new GroovyClassLoader().parseClass(new File(getAlgorithmClassPath(matchingAlgorithmClassName)));
+ patientMatchingAlgorithms.put(matchingAlgorithmClassName, (PatientMatchingAlgorithm) clazz.newInstance());
+ }
+
+ return patientMatchingAlgorithms.get(matchingAlgorithmClassName);
+ }
+
+ private String getAlgorithmClassPath(String matchingAlgorithmClassName) {
+ return OpenmrsUtil.getApplicationDataDirectory() + PATIENT_MATCHING_ALGORITHM_DIRECTORY + matchingAlgorithmClassName;
+ }
+
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/csv/utils/CSVUtils.java b/admin/src/main/java/org/bahmni/module/admin/csv/utils/CSVUtils.java
new file mode 100644
index 0000000000..e1896b181f
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/csv/utils/CSVUtils.java
@@ -0,0 +1,79 @@
+package org.bahmni.module.admin.csv.utils;
+
+import org.bahmni.csv.KeyValue;
+import org.bahmni.csv.exception.MigrationException;
+import org.openmrs.api.context.Context;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class CSVUtils {
+
+ public static final String ENCOUNTER_DATE_PATTERN = "yyyy-M-d";
+ private final static String GLOBAL_DATE_FORMAT = "bahmni.admin.csv.upload.dateFormat";
+
+ public static String getCsvGlobalDateFormat(){
+ return Context.getAdministrationService().getGlobalProperty(GLOBAL_DATE_FORMAT);
+ };
+
+ public static String[] getStringArray(List keyValueList) {
+ List stringList = new ArrayList<>();
+ for (KeyValue keyValue : keyValueList) {
+ stringList.add(keyValue.getValue());
+ }
+ return stringList.toArray(new String[]{});
+ }
+
+ public static List getKeyValueList(String key, List stringList) {
+ List keyValueList = new ArrayList<>();
+ for (String string : stringList) {
+ keyValueList.add(new KeyValue(key, string));
+ }
+ return keyValueList;
+ }
+
+ public static String getDateStringInSupportedFormat(String dateString) throws ParseException {
+ String dateGlobalProperty = getCsvGlobalDateFormat();
+ SimpleDateFormat simpleDateFormat;
+ if( dateGlobalProperty != null) {
+ simpleDateFormat = new SimpleDateFormat(dateGlobalProperty);
+ simpleDateFormat.setLenient(false);
+ Date date = new Date(simpleDateFormat.parse(dateString).getTime());
+
+ SimpleDateFormat defaultDateFormat = new SimpleDateFormat(ENCOUNTER_DATE_PATTERN);
+ return defaultDateFormat.format(date);
+ }else{
+ return dateString;
+ }
+ };
+
+ public static Date getDateFromString(String dateString) throws ParseException {
+ // All csv imports use the date format from global properties
+ SimpleDateFormat simpleDateFormat;
+ String dateGlobalProperty = getCsvGlobalDateFormat();
+ String expectedDateFormat = dateGlobalProperty != null ? dateGlobalProperty : ENCOUNTER_DATE_PATTERN;
+ try {
+ if (dateGlobalProperty != null) {
+ dateString = getDateStringInSupportedFormat(dateString);
+ }
+ simpleDateFormat = new SimpleDateFormat(ENCOUNTER_DATE_PATTERN);
+ simpleDateFormat.setLenient(false);
+ return simpleDateFormat.parse(dateString);
+ }
+ catch (ParseException e){
+ throw new MigrationException("Date format " + dateString + " doesn't match `bahmni.admin.csv.upload.dateFormat` global property, expected format " + expectedDateFormat );
+ }
+ };
+
+ public static Date getTodayDate() throws ParseException {
+ DateFormat dateFormat = new SimpleDateFormat(ENCOUNTER_DATE_PATTERN);
+ Date date = new Date();
+ String dateString = dateFormat.format(date);
+ return getDateFromString(dateString);
+ }
+
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/encounter/BahmniEncounterTransactionImportService.java b/admin/src/main/java/org/bahmni/module/admin/encounter/BahmniEncounterTransactionImportService.java
new file mode 100644
index 0000000000..4a8a1168c9
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/encounter/BahmniEncounterTransactionImportService.java
@@ -0,0 +1,73 @@
+package org.bahmni.module.admin.encounter;
+
+import org.bahmni.module.admin.csv.models.EncounterRow;
+import org.bahmni.module.admin.csv.models.MultipleEncounterRow;
+import org.bahmni.module.admin.observation.DiagnosisMapper;
+import org.bahmni.module.admin.observation.ObservationMapper;
+import org.openmrs.EncounterType;
+import org.openmrs.Patient;
+import org.openmrs.api.EncounterService;
+import org.openmrs.module.bahmniemrapi.diagnosis.contract.BahmniDiagnosisRequest;
+import org.openmrs.module.bahmniemrapi.encountertransaction.contract.BahmniEncounterTransaction;
+import org.openmrs.module.bahmniemrapi.encountertransaction.mapper.ETObsToBahmniObsMapper;
+import org.openmrs.module.bahmniemrapi.encountertransaction.mapper.parameters.AdditionalBahmniObservationFields;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class BahmniEncounterTransactionImportService {
+
+ private EncounterService encounterService;
+ private ObservationMapper observationMapper;
+ private DiagnosisMapper diagnosisMapper;
+ private ETObsToBahmniObsMapper fromETObsToBahmniObs;
+
+ @Autowired
+ public BahmniEncounterTransactionImportService(EncounterService encounterService,
+ ObservationMapper observationMapper, DiagnosisMapper diagnosisMapper,
+ ETObsToBahmniObsMapper fromETObsToBahmniObs) {
+ this.encounterService = encounterService;
+ this.observationMapper = observationMapper;
+ this.diagnosisMapper = diagnosisMapper;
+ this.fromETObsToBahmniObs = fromETObsToBahmniObs;
+ }
+
+ public List getBahmniEncounterTransaction(MultipleEncounterRow multipleEncounterRow, Patient patient, boolean shouldPerformForm2Validations) throws ParseException {
+ if (multipleEncounterRow.encounterRows == null || multipleEncounterRow.encounterRows.isEmpty())
+ return new ArrayList<>();
+
+ List bahmniEncounterTransactions = new ArrayList<>();
+
+ EncounterType requestedEncounterType = encounterService.getEncounterType(multipleEncounterRow.encounterType);
+ if (requestedEncounterType == null) {
+ throw new RuntimeException("Encounter type:'" + multipleEncounterRow.encounterType + "' not found.");
+ }
+ String encounterType = multipleEncounterRow.encounterType;
+ String visitType = multipleEncounterRow.visitType;
+
+ for (EncounterRow encounterRow : multipleEncounterRow.getNonEmptyEncounterRows()) {
+ List allObservations = observationMapper.getObservations(encounterRow, shouldPerformForm2Validations);
+ List allDiagnosis = diagnosisMapper.getBahmniDiagnosis(encounterRow);
+
+ BahmniEncounterTransaction bahmniEncounterTransaction = new BahmniEncounterTransaction();
+ bahmniEncounterTransaction.setPatientUuid(patient.getUuid());
+ bahmniEncounterTransaction.setBahmniDiagnoses(allDiagnosis);
+ bahmniEncounterTransaction.setObservations(fromETObsToBahmniObs.create(allObservations, new AdditionalBahmniObservationFields(null, encounterRow.getEncounterDate(), null, null)));
+
+ bahmniEncounterTransaction.setEncounterDateTime(encounterRow.getEncounterDate());
+ bahmniEncounterTransaction.setEncounterType(encounterType);
+ bahmniEncounterTransaction.setVisitType(visitType);
+
+ bahmniEncounterTransactions.add(bahmniEncounterTransaction);
+ }
+
+
+ return bahmniEncounterTransactions;
+ }
+
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/observation/CSVObservationHelper.java b/admin/src/main/java/org/bahmni/module/admin/observation/CSVObservationHelper.java
new file mode 100644
index 0000000000..0236e868d9
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/observation/CSVObservationHelper.java
@@ -0,0 +1,249 @@
+package org.bahmni.module.admin.observation;
+
+import org.apache.commons.lang.StringUtils;
+import org.bahmni.csv.KeyValue;
+import org.openmrs.Concept;
+import org.openmrs.ConceptName;
+import org.openmrs.ConceptNumeric;
+import org.openmrs.Obs;
+import org.openmrs.api.APIException;
+import org.openmrs.api.AdministrationService;
+import org.openmrs.api.ConceptService;
+import org.openmrs.api.context.Context;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction.Observation;
+import org.openmrs.module.emrapi.encounter.exception.ConceptNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+import java.security.InvalidParameterException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static java.util.Objects.nonNull;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.springframework.util.CollectionUtils.isEmpty;
+import static org.bahmni.module.admin.csv.utils.CSVUtils.getDateStringInSupportedFormat;
+
+@Component
+public class CSVObservationHelper {
+ private static final String FORM2_TYPE = "form2";
+ private static final String OBS_PATH_SPLITTER_PROPERTY = "bahmni.admin.csv.upload.obsPath.splitter";
+ private static final String DEFAULT_OBSPATH_SPLITTER = ".";
+ private static final String MULTI_SELECT_OBS_SPLITTER_PROPERTY = "bahmni.admin.csv.upload.obs.multiSelect.splitter";
+ private static final String DEFAULT_MULTI_SELECT_OBS_SPLITTER = "|";
+ private static final String ADDMORE_OBS_SPLITTER_PROPERTY = "bahmni.admin.csv.upload.obs.addmore.splitter";
+ private static final String DEFAULT_ADDMORE_OBS_SPLITTER = "|";
+ private static final String DATE = "Date";
+ private final ConceptCache conceptCache;
+ private final ConceptService conceptService;
+ private AdministrationService administrationService;
+
+ @Autowired
+ CSVObservationHelper(ConceptService conceptService,
+ @Qualifier("adminService") AdministrationService administrationService) {
+ this.conceptCache = new ConceptCache(conceptService);
+ this.conceptService = conceptService;
+ this.administrationService = administrationService;
+ }
+
+ public static T getLastItem(List items) {
+ if (isEmpty(items)) {
+ throw new InvalidParameterException("Empty items");
+ }
+ return items.get(items.size() - 1);
+ }
+
+ protected Concept getConcept(String conceptName) {
+ return conceptCache.getConcept(conceptName);
+ }
+
+ public void createObservations(List observations, Date encounterDate,
+ List obsRows, List conceptNames) throws ParseException {
+ Observation existingObservation = getRootObservationIfExists(observations, conceptNames, null);
+ if (existingObservation == null) {
+ for(KeyValue obsRow : obsRows)
+ observations.add(createObservation(conceptNames, encounterDate, obsRow));
+ } else {
+ for(KeyValue obsRow : obsRows)
+ updateObservation(conceptNames, existingObservation, encounterDate, obsRow);
+ }
+ }
+
+ public void createObservations(List observations, Date encounterDate,
+ KeyValue obsRow, List conceptNames) throws ParseException {
+ Observation existingObservation = getRootObservationIfExists(observations, conceptNames, null);
+ if (existingObservation == null) {
+ observations.add(createObservation(conceptNames, encounterDate, obsRow));
+ } else {
+ updateObservation(conceptNames, existingObservation, encounterDate, obsRow);
+
+ }
+ }
+
+ public void verifyNumericConceptValue(KeyValue obsRow, List conceptNames) {
+ String lastConceptName = getLastItem(conceptNames);
+ Concept lastConcept = conceptService.getConceptByName(lastConceptName);
+ if (lastConcept != null && lastConcept.isNumeric()) {
+ ConceptNumeric cn = (ConceptNumeric) lastConcept;
+ if (!cn.getAllowDecimal() && obsRow.getValue().contains(".")) {
+ throw new APIException("Decimal is not allowed for " + cn.getName() + " concept");
+ }
+ }
+ }
+
+ private void updateObservation(List conceptNames, Observation existingObservation,
+ Date encounterDate, KeyValue obsRow) throws ParseException {
+ existingObservation.addGroupMember(createObservation(conceptNames, encounterDate, obsRow));
+ }
+
+ private Observation getRootObservationIfExists(List observations, List conceptNames,
+ Observation existingObservation) {
+ for (Observation observation : observations) {
+ if (observation.getConcept().getName().equals(conceptNames.get(0))) {
+ conceptNames.remove(0);
+ if (conceptNames.size() == 0) {
+ conceptNames.add(observation.getConcept().getName());
+ return existingObservation;
+ }
+ existingObservation = observation;
+ return getRootObservationIfExists(observation.getGroupMembers(), conceptNames, existingObservation);
+ }
+ }
+ return existingObservation;
+ }
+
+ private Observation createObservation(List conceptNames, Date encounterDate, KeyValue obsRow) throws ParseException {
+ Concept obsConcept = conceptCache.getConcept(conceptNames.get(0));
+ EncounterTransaction.Concept concept = new EncounterTransaction.Concept(obsConcept.getUuid(),
+ obsConcept.getName().getName());
+
+ Observation observation = new Observation();
+ observation.setConcept(concept);
+ observation.setObservationDateTime(encounterDate);
+ if (conceptNames.size() == 1) {
+ observation.setValue(getValue(obsRow, obsConcept));
+ if (obsConcept.getDatatype().isNumeric()) {
+ validateAndUpdateObservationInterpretation(obsRow, obsConcept, observation);
+ } else if(obsConcept.getDatatype().isDate()) {
+ observation.getConcept().setDataType(DATE);
+ }
+ } else {
+ conceptNames.remove(0);
+ observation.addGroupMember(createObservation(conceptNames, encounterDate, obsRow));
+ }
+ return observation;
+ }
+
+ private void validateAndUpdateObservationInterpretation(KeyValue obsRow, Concept obsConcept, Observation observation) {
+ verifyNumericConceptValue(obsRow, Collections.singletonList(obsConcept.getName().getName()));
+ Double recordedObsValue = Double.parseDouble(obsRow.getValue());
+ Double hiNormal = ((ConceptNumeric) obsConcept).getHiNormal();
+ Double lowNormal = ((ConceptNumeric) obsConcept).getLowNormal();
+ if (nonNull(recordedObsValue) && ((nonNull(hiNormal) && recordedObsValue.compareTo(hiNormal) > 0) || (nonNull(lowNormal) && recordedObsValue.compareTo(lowNormal) < 0))) {
+ observation.setInterpretation(String.valueOf(Obs.Interpretation.ABNORMAL));
+ }
+ };
+
+ private Object getValue(KeyValue obsRow, Concept obsConcept) throws ParseException {
+ Map valueConcept = null;
+ if(obsConcept.getDatatype().isDate()) {
+ String dateString = obsRow.getValue();
+ return getDateStringInSupportedFormat(dateString);
+ }
+ if (obsConcept.getDatatype().isCoded()) {
+ List valueConcepts = conceptService.getConceptsByName(obsRow.getValue());
+ for (Concept concept : valueConcepts) {
+ ConceptName name = concept.getFullySpecifiedName(Context.getLocale()) != null ?
+ concept.getFullySpecifiedName(Context.getLocale()) : concept.getName();
+ if (name.getName().equalsIgnoreCase(obsRow.getValue())) {
+ valueConcept = new LinkedHashMap<>();
+ Map conceptNameDetails = new HashMap<>();
+ conceptNameDetails.put("name", concept.getName().getName());
+ conceptNameDetails.put("uuid", concept.getName().getUuid());
+ conceptNameDetails.put("display", concept.getDisplayString());
+ conceptNameDetails.put("locale", concept.getName().getLocale());
+ conceptNameDetails.put("localePreferred", concept.getName().getLocalePreferred());
+ conceptNameDetails.put("conceptNameType", concept.getName().getConceptNameType());
+ valueConcept.put("uuid", concept.getUuid());
+ valueConcept.put("name", conceptNameDetails);
+ valueConcept.put("names", concept.getNames());
+ valueConcept.put("displayString", concept.getDisplayString());
+ valueConcept.put("translationKey", concept.getName());
+ break;
+ }
+ }
+ if (valueConcept == null)
+ throw new ConceptNotFoundException(obsRow.getValue() + " not found");
+ return valueConcept;
+ }
+ return obsRow.getValue();
+ }
+
+ public List getCSVHeaderParts(KeyValue csvObservation) {
+ String key = csvObservation.getKey();
+ return isNotBlank(key) ? new ArrayList<>(asList(key.split(String.format("\\%s", getObsPathSplitter()))))
+ : new ArrayList<>();
+ }
+
+ private String getObsPathSplitter() {
+ final String obsPathSplitter = administrationService.getGlobalProperty(OBS_PATH_SPLITTER_PROPERTY);
+ return isNotBlank(obsPathSplitter) ? obsPathSplitter : DEFAULT_OBSPATH_SPLITTER;
+ }
+
+ public List getMultiSelectObs(KeyValue csvObservation) {
+ String multiSelectRawValue = csvObservation.getValue();
+ return isNotBlank(multiSelectRawValue) ? new ArrayList<>(asList(multiSelectRawValue.split(String.format("\\%s", getMultiSelectObsSplitter()))))
+ : new ArrayList<>();
+ }
+
+ public List getAddmoreObs(KeyValue csvObservation) {
+ String addmoreRawValue = csvObservation.getValue();
+ return isNotBlank(addmoreRawValue) ? new ArrayList<>(asList(addmoreRawValue.split(String.format("\\%s", getAddmoreObsSplitter()))))
+ : new ArrayList<>();
+ }
+
+ private String getMultiSelectObsSplitter() {
+ final String multiSelectObsSplitter = administrationService.getGlobalProperty(MULTI_SELECT_OBS_SPLITTER_PROPERTY);
+ return isNotBlank(multiSelectObsSplitter) ? multiSelectObsSplitter : DEFAULT_MULTI_SELECT_OBS_SPLITTER;
+ }
+
+ private String getAddmoreObsSplitter() {
+ final String addmoreObsSplitter = administrationService.getGlobalProperty(ADDMORE_OBS_SPLITTER_PROPERTY);
+ return isNotBlank(addmoreObsSplitter) ? addmoreObsSplitter : DEFAULT_ADDMORE_OBS_SPLITTER;
+ }
+
+ public List getMultiSelectObsForJsonValue(String jsonValue) {
+ List multiSelectValues = new ArrayList<>();
+ if(isNotBlank(jsonValue))
+ multiSelectValues.addAll(asList(jsonValue.split(String.format("\\%s", getMultiSelectObsSplitter()))));
+ return multiSelectValues;
+ }
+
+ public List getAddmoreObsForJsonValue(String jsonValue) {
+ List addmoreValues = new ArrayList<>();
+ if(isNotBlank(jsonValue))
+ addmoreValues.addAll(asList(jsonValue.split(String.format("\\%s", getAddmoreObsSplitter()))));
+ return addmoreValues;
+ }
+ public boolean isForm2Type(KeyValue obsRow) {
+ String key = obsRow.getKey();
+ if (StringUtils.isNotBlank(key)) {
+ String[] csvHeaderParts = key.split((String.format("\\%s", getObsPathSplitter())));
+ return csvHeaderParts[0].equalsIgnoreCase(FORM2_TYPE);
+ }
+ return false;
+ }
+
+ public boolean isForm1Type(KeyValue keyValue) {
+ return !isForm2Type(keyValue);
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/observation/ConceptCache.java b/admin/src/main/java/org/bahmni/module/admin/observation/ConceptCache.java
new file mode 100644
index 0000000000..434430655c
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/observation/ConceptCache.java
@@ -0,0 +1,32 @@
+package org.bahmni.module.admin.observation;
+
+import org.openmrs.Concept;
+import org.openmrs.api.ConceptService;
+import org.openmrs.module.emrapi.encounter.exception.ConceptNotFoundException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConceptCache {
+ private Map cachedConcepts = new HashMap<>();
+ private ConceptService conceptService;
+
+ public ConceptCache(ConceptService conceptService) {
+ this.conceptService = conceptService;
+ }
+
+ public Concept getConcept(String conceptName) {
+ if (!cachedConcepts.containsKey(conceptName)) {
+ cachedConcepts.put(conceptName, fetchConcept(conceptName));
+ }
+ return cachedConcepts.get(conceptName);
+ }
+
+ private Concept fetchConcept(String conceptName) {
+ Concept obsConcept = conceptService.getConceptByName(conceptName);
+ if (obsConcept == null)
+ throw new ConceptNotFoundException("Concept '" + conceptName + "' not found");
+
+ return obsConcept;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/observation/DiagnosisMapper.java b/admin/src/main/java/org/bahmni/module/admin/observation/DiagnosisMapper.java
new file mode 100644
index 0000000000..0617b5ffe4
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/observation/DiagnosisMapper.java
@@ -0,0 +1,75 @@
+package org.bahmni.module.admin.observation;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.models.EncounterRow;
+import org.openmrs.Concept;
+import org.openmrs.api.ConceptService;
+import org.openmrs.module.bahmniemrapi.diagnosis.contract.BahmniDiagnosisRequest;
+import org.openmrs.module.emrapi.diagnosis.Diagnosis;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.openmrs.module.emrapi.encounter.exception.ConceptNotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Component(value = "adminDiagnosisMapper")
+public class DiagnosisMapper {
+
+ private static final Logger log = LogManager.getLogger(DiagnosisMapper.class);
+
+ private final ConceptCache conceptCache;
+
+ @Autowired
+ public DiagnosisMapper(ConceptService conceptService) {
+ this.conceptCache = new ConceptCache(conceptService);
+ }
+
+ public List getBahmniDiagnosis(EncounterRow encounterRow) throws ParseException {
+ List bahmniDiagnoses = new ArrayList<>();
+ if (encounterRow.hasDiagnoses()) {
+ Date encounterDate = encounterRow.getEncounterDate();
+ for (KeyValue uniqueDiagnosisKeyValue : encounterRow.diagnosesRows) {
+ String diagnosis = uniqueDiagnosisKeyValue.getValue();
+ if (StringUtils.isNotBlank(diagnosis)) {
+ BahmniDiagnosisRequest bahmniDiagnosisRequest = createDiagnosis(encounterDate, diagnosis);
+ bahmniDiagnoses.add(bahmniDiagnosisRequest);
+ }
+ }
+ }
+ return bahmniDiagnoses;
+ }
+
+ private BahmniDiagnosisRequest createDiagnosis(Date encounterDate, String diagnosis) throws ParseException {
+ Concept obsConcept = getConcept(diagnosis);
+
+ BahmniDiagnosisRequest bahmniDiagnosisRequest = new BahmniDiagnosisRequest();
+ bahmniDiagnosisRequest.setOrder(String.valueOf(Diagnosis.Order.PRIMARY));
+ bahmniDiagnosisRequest.setCertainty(String.valueOf(Diagnosis.Certainty.CONFIRMED));
+ bahmniDiagnosisRequest.setDiagnosisDateTime(encounterDate);
+
+ if (obsConcept == null) {
+ bahmniDiagnosisRequest.setFreeTextAnswer(diagnosis);
+ } else {
+ EncounterTransaction.Concept diagnosisConcept = new EncounterTransaction.Concept(obsConcept.getUuid(), obsConcept.getName().getName());
+ bahmniDiagnosisRequest.setCodedAnswer(diagnosisConcept);
+ }
+
+ return bahmniDiagnosisRequest;
+ }
+
+ protected Concept getConcept(String diagnosis) {
+ try {
+ return conceptCache.getConcept(diagnosis);
+ } catch (ConceptNotFoundException cnfe) {
+ log.error(cnfe.getMessage() + " Setting it as free text answer", cnfe);
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/admin/src/main/java/org/bahmni/module/admin/observation/ObservationMapper.java b/admin/src/main/java/org/bahmni/module/admin/observation/ObservationMapper.java
new file mode 100644
index 0000000000..9b98944513
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/observation/ObservationMapper.java
@@ -0,0 +1,42 @@
+package org.bahmni.module.admin.observation;
+
+import org.bahmni.module.admin.csv.models.EncounterRow;
+import org.bahmni.module.admin.observation.handler.CSVObsHandler;
+import org.openmrs.api.ConceptService;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component(value = "adminObservationMapper")
+public class ObservationMapper {
+
+ @Autowired
+ private List csvObsHandlers;
+
+ @Autowired
+ @Deprecated
+ public ObservationMapper(ConceptService conceptService) {
+ }
+
+ public List getObservations(EncounterRow encounterRow) throws ParseException {
+ final List observations = new ArrayList<>();
+ for (CSVObsHandler csvObsHandler : csvObsHandlers) {
+ observations.addAll(csvObsHandler.handle(encounterRow));
+ }
+ return observations;
+ }
+
+ public List getObservations(EncounterRow encounterRow, boolean shouldPerformForm2Validations) throws ParseException {
+ final List observations = new ArrayList<>();
+ for (CSVObsHandler csvObsHandler : csvObsHandlers) {
+ final List allObs = csvObsHandler.handle(encounterRow, shouldPerformForm2Validations);
+ if(allObs != null)
+ observations.addAll(allObs);
+ }
+ return observations;
+ }
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/observation/handler/CSVObsHandler.java b/admin/src/main/java/org/bahmni/module/admin/observation/handler/CSVObsHandler.java
new file mode 100644
index 0000000000..cf4dee5504
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/observation/handler/CSVObsHandler.java
@@ -0,0 +1,18 @@
+package org.bahmni.module.admin.observation.handler;
+
+
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.models.EncounterRow;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+
+import java.text.ParseException;
+import java.util.List;
+
+public interface CSVObsHandler {
+
+ List getRelatedCSVObs(EncounterRow encounterRow);
+
+ List handle(EncounterRow encounterRow) throws ParseException;
+
+ List handle(EncounterRow encounterRow, boolean shouldPerformForm2Validations) throws ParseException;
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/observation/handler/Form1CSVObsHandler.java b/admin/src/main/java/org/bahmni/module/admin/observation/handler/Form1CSVObsHandler.java
new file mode 100644
index 0000000000..45008e29a0
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/observation/handler/Form1CSVObsHandler.java
@@ -0,0 +1,54 @@
+package org.bahmni.module.admin.observation.handler;
+
+
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.models.EncounterRow;
+import org.bahmni.module.admin.observation.CSVObservationHelper;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+
+@Component
+public class Form1CSVObsHandler implements CSVObsHandler {
+
+ private CSVObservationHelper csvObservationHelper;
+
+ @Autowired
+ public Form1CSVObsHandler(CSVObservationHelper csvObservationHelper) {
+ this.csvObservationHelper = csvObservationHelper;
+ }
+
+ @Override
+ public List getRelatedCSVObs(EncounterRow encounterRow) {
+ return encounterRow.obsRows.stream().filter(csvObservation -> csvObservationHelper.isForm1Type(csvObservation))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List handle(EncounterRow encounterRow) throws ParseException {
+ List observations = new ArrayList<>();
+ List csvObservations = getRelatedCSVObs(encounterRow);
+ for (KeyValue csvObservation : csvObservations) {
+ if (isNotBlank(csvObservation.getValue())) {
+ List conceptNames = csvObservationHelper.getCSVHeaderParts(csvObservation);
+ csvObservationHelper.verifyNumericConceptValue(csvObservation, conceptNames);
+ csvObservationHelper.createObservations(observations, encounterRow.getEncounterDate(),
+ csvObservation, conceptNames);
+ }
+ }
+ return observations;
+ }
+
+ @Override
+ public List handle(EncounterRow encounterRow, boolean shouldPerformForm2Validations) throws ParseException {
+ return null;
+ }
+
+}
diff --git a/admin/src/main/java/org/bahmni/module/admin/observation/handler/Form2CSVObsHandler.java b/admin/src/main/java/org/bahmni/module/admin/observation/handler/Form2CSVObsHandler.java
new file mode 100644
index 0000000000..ca7ba9a529
--- /dev/null
+++ b/admin/src/main/java/org/bahmni/module/admin/observation/handler/Form2CSVObsHandler.java
@@ -0,0 +1,288 @@
+package org.bahmni.module.admin.observation.handler;
+
+import org.bahmni.csv.KeyValue;
+import org.bahmni.module.admin.csv.models.EncounterRow;
+import org.bahmni.module.admin.csv.models.SectionPositionValue;
+import org.bahmni.module.admin.csv.service.FormFieldPathGeneratorService;
+import org.bahmni.module.admin.csv.utils.CSVUtils;
+import org.bahmni.module.admin.observation.CSVObservationHelper;
+import org.bahmni.form2.service.FormFieldPathService;
+import org.openmrs.api.APIException;
+import org.openmrs.module.emrapi.encounter.domain.EncounterTransaction;
+import org.openmrs.module.webservices.rest.SimpleObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.ParseException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.bahmni.module.admin.observation.CSVObservationHelper.getLastItem;
+
+@Component
+public class Form2CSVObsHandler implements CSVObsHandler {
+
+ private static final String DATE = "Date";
+ private static final String ATTRIBUTE_QUERY_SEPARATOR = "?";
+ private static final String ATTRIBUTE_SEPARATOR = "&";
+ private static final String ATTRIBUTE_ISJSON = "isJson";
+ private static final String SECTION_SPLITTER = "/";
+ private static final String KEY_SECTION_VALUES = "values";
+ private static final int NOT_MULTISELECT_OBS_INDEX = -1;
+ private static final int NOT_ADDMORE_OBS_INDEX = -1;
+
+ private CSVObservationHelper csvObservationHelper;
+ private FormFieldPathService formFieldPathService;
+ private FormFieldPathGeneratorService formFieldPathGeneratorService;
+
+ @Autowired
+ public Form2CSVObsHandler(CSVObservationHelper csvObservationHelper, FormFieldPathService formFieldPathService, FormFieldPathGeneratorService formFieldPathGeneratorService) {
+ this.csvObservationHelper = csvObservationHelper;
+ this.formFieldPathService = formFieldPathService;
+ this.formFieldPathGeneratorService = formFieldPathGeneratorService;
+ }
+
+ @Override
+ public List getRelatedCSVObs(EncounterRow encounterRow) {
+ return encounterRow.obsRows.stream().filter(csvObservation -> csvObservationHelper.isForm2Type(csvObservation))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public List handle(EncounterRow encounterRow) throws ParseException {
+ List form2Observations = new ArrayList<>();
+ List form2CSVObservations = getRelatedCSVObs(encounterRow);
+ for (KeyValue form2CSVObservation : form2CSVObservations) {
+ if (isNotBlank(form2CSVObservation.getValue())) {
+ final List form2CSVHeaderParts = getCSVHeaderPartsByIgnoringForm2KeyWord(form2CSVObservation);
+ verifyCSVHeaderHasConcepts(form2CSVObservation, form2CSVHeaderParts);
+ csvObservationHelper.verifyNumericConceptValue(form2CSVObservation, form2CSVHeaderParts);
+ csvObservationHelper.createObservations(form2Observations, encounterRow.getEncounterDate(),
+ form2CSVObservation, getConceptNames(form2CSVHeaderParts));
+ formFieldPathGeneratorService.setFormNamespaceAndFieldPath(form2Observations, form2CSVHeaderParts);
+ }
+ }
+ return form2Observations;
+ }
+
+ @Override
+ public List handle(EncounterRow encounterRow, boolean shouldPerformForm2Validations) throws ParseException {
+ if(!shouldPerformForm2Validations)
+ return handle(encounterRow);
+ List form2Observations = new ArrayList<>();
+ List form2CSVObservations = getRelatedCSVObs(encounterRow);
+ for (KeyValue form2CSVObservation : form2CSVObservations) {
+ Map headerAttributes = parseCSVHeader(form2CSVObservation);
+ boolean isJsonAttribute = headerAttributes.getOrDefault(ATTRIBUTE_ISJSON, false);
+ final List form2CSVHeaderParts = getCSVHeaderPartsByIgnoringForm2KeyWord(form2CSVObservation);
+ final boolean validCSVHeader = formFieldPathService.isValidCSVHeader(form2CSVHeaderParts);
+ if(!validCSVHeader)
+ throw new APIException(format("No concepts found in %s", form2CSVObservation.getKey()));
+ if (isNotBlank(form2CSVObservation.getValue())) {
+ if(isJsonAttribute) {
+ processJsonConceptValue(form2CSVObservation, form2CSVHeaderParts, form2Observations, encounterRow);
+ } else {
+ verifyCSVHeaderHasConcepts(form2CSVObservation, form2CSVHeaderParts);
+ csvObservationHelper.verifyNumericConceptValue(form2CSVObservation, form2CSVHeaderParts);
+ verifyForMultiSelect(encounterRow, form2Observations, form2CSVObservation, form2CSVHeaderParts);
+ verifyForAddMore(encounterRow, form2Observations, form2CSVObservation, form2CSVHeaderParts);
+ verifyAndValidateObs(encounterRow, form2Observations, form2CSVObservation, form2CSVHeaderParts);
+ }
+ } else {
+ verifyForMandatoryObs(form2CSVHeaderParts);
+ }
+ }
+ return form2Observations;
+ }
+
+ private void verifyCSVHeaderHasConcepts(KeyValue form2CSVObservation, List form2CSVHeaderParts) {
+ if (form2CSVHeaderParts.size() <= 1) {
+ throw new APIException(format("No concepts found in %s", form2CSVObservation.getKey()));
+ }
+ }
+
+ private List getCSVHeaderPartsByIgnoringForm2KeyWord(KeyValue csvObservation) {
+ final List csvHeaderParts = csvObservationHelper.getCSVHeaderParts(csvObservation);
+ // removes form2 keyword
+ csvHeaderParts.remove(0);
+ return csvHeaderParts;
+ }
+
+ private List getConceptNames(List form2CSVHeaderParts) {
+ return asList(getLastItem(form2CSVHeaderParts));
+ }
+
+ private void verifyForMultiSelect(EncounterRow encounterRow, List