ndr_workflow 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +30 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +118 -0
- data/Rakefile +42 -0
- data/SECURITY.md +35 -0
- data/app/constants/esource_constants.rb +51 -0
- data/app/models/e_action.rb +116 -0
- data/app/models/e_batch.rb +207 -0
- data/app/models/e_workflow.rb +23 -0
- data/app/models/ze_actiontype.rb +13 -0
- data/app/models/ze_type.rb +6 -0
- data/app/models/zlookup.rb +9 -0
- data/app/models/zlookup_cached.rb +5 -0
- data/app/models/zprovider.rb +9 -0
- data/app/models/zuser.rb +5 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20160707141753_create_e_batch.rb +25 -0
- data/db/migrate/20160707141754_create_e_action.rb +16 -0
- data/db/migrate/20160707141755_create_e_workflow.rb +17 -0
- data/db/migrate/20160707141756_create_zuser.rb +16 -0
- data/db/migrate/20160707141757_create_ze_actiontype.rb +14 -0
- data/db/migrate/20160707141758_create_ze_type.rb +14 -0
- data/db/migrate/20160707141759_create_z_provider.rb +28 -0
- data/db/migrate/20160707141800_create_constraint.rb +23 -0
- data/lib/ndr_workflow/e_action_progress.rb +121 -0
- data/lib/ndr_workflow/engine.rb +5 -0
- data/lib/ndr_workflow/version.rb +5 -0
- data/lib/ndr_workflow.rb +19 -0
- data/lib/tasks/ndr_workflow_tasks.rake +4 -0
- metadata +157 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3222ceba0ab6c26802391cce5268e1e414b142f0463ae18a7c9ebea06e4e0ae9
|
|
4
|
+
data.tar.gz: 66c4bcb8152c2a0734968bb4f121324b3535ca2888f72e98dc4bb4b669a259ff
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 23d8b24e27d6c9ab8e3f306eed4d9301068d6c1966707cb53abdf440dfcb6baacc3e805deba4f4c32280f6871945e825cd20f840efab5d4533912e519f0f0e10
|
|
7
|
+
data.tar.gz: cf6d925666512f0db92e6045cc84054cd394ba2dc82ba2d387f614001981a39e10fd1fdf1afbc1d8b64c7b77f717367f7638efc9f1dc17d0b762552bf9ca3a96
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
* No unreleased changes
|
|
3
|
+
|
|
4
|
+
## 1.2.5 / 2026-07-13
|
|
5
|
+
### Fixed
|
|
6
|
+
* Support Ruby 3.4. Drop support for Rails 7.0, Ruby 3.1
|
|
7
|
+
* Support Rails 8.1, Ruby 4.0. Drop support for Ruby 3.2
|
|
8
|
+
|
|
9
|
+
## 1.2.4 / 2024-11-13
|
|
10
|
+
### Fixed
|
|
11
|
+
* Support Rails 7.1, 7.2, 8.0, Ruby 3.2. Drop support for Rails 6.1
|
|
12
|
+
|
|
13
|
+
## 1.2.3 / 2022-12-02
|
|
14
|
+
### Fixed
|
|
15
|
+
* Support Ruby 3.1, Rails 7.0
|
|
16
|
+
* Drop support for Ruby 2.6, Rails 5.2
|
|
17
|
+
* Replace Public Health England naming with NHS Digital
|
|
18
|
+
|
|
19
|
+
## 1.2.2 / 2021-01-25
|
|
20
|
+
### Fixed
|
|
21
|
+
* Ensure app/ subdirectories are autoloadable by `zeitwerk`
|
|
22
|
+
|
|
23
|
+
## 1.2.1 / 2021-01-11
|
|
24
|
+
### Fixed
|
|
25
|
+
* Remove deprecations in Rails 6
|
|
26
|
+
|
|
27
|
+
## 1.2.0 / 2020-07-01
|
|
28
|
+
### Added
|
|
29
|
+
* Add `e_batch.e_actions.restart` method
|
|
30
|
+
* Start of CHANGELOG
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2015 YOURNAME
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# NdrWorkflow
|
|
2
|
+
|
|
3
|
+
** THIS ENGINE IS NOT FOR PUBLIC DEPLOYMENT IN ITS CURRENT FORMAT **
|
|
4
|
+
|
|
5
|
+
This is the NHS Digital (NHS-D) National Disease Registers (NDR) Workflow ruby gem, providing:
|
|
6
|
+
|
|
7
|
+
1. core model definitions derived from ERA to allow for workflows and batch processing
|
|
8
|
+
Core tables included:
|
|
9
|
+
EWorkflow, EBatch, EAction
|
|
10
|
+
Supporting lookup tables include:
|
|
11
|
+
ZeActiontype, ZeType, Zprovider, Zuser
|
|
12
|
+
|
|
13
|
+
Currently excluded:
|
|
14
|
+
Conformance data
|
|
15
|
+
Queued_Job
|
|
16
|
+
Back end daemons
|
|
17
|
+
ZPostCode - referenced heavily in Zprovider
|
|
18
|
+
|
|
19
|
+
## Considerations
|
|
20
|
+
1. Postgres only implementation for creating underlying db tables (will work with oracle but no migrations)
|
|
21
|
+
2. FK constraints managed through DB rather than ActiveRecord
|
|
22
|
+
3. Table names set in Engine models - not standard rails
|
|
23
|
+
4.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Add this line to your application's Gemfile:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
gem 'ndr_workflow', path: "PATH_TO_YOUR_GIT_SVN/non-era/gems/ndr_workflow"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
And then execute:
|
|
34
|
+
|
|
35
|
+
$ bundle install
|
|
36
|
+
|
|
37
|
+
If the application needs the physical tables creating in Postgres:
|
|
38
|
+
|
|
39
|
+
$ rails g ndr_workflow:install
|
|
40
|
+
$ rake db:migrate
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
# Rails 3
|
|
45
|
+
|
|
46
|
+
Protected attributes
|
|
47
|
+
Disabe globally in your app in conf/application.rb, with
|
|
48
|
+
config.active_record.whitelist_attributes = false
|
|
49
|
+
OR
|
|
50
|
+
for specific models using attr_accessible : create new model definition in your app dir based on template files in enginer in :
|
|
51
|
+
test/dummy/app/model/NAME.rb
|
|
52
|
+
|
|
53
|
+
# Rails 4
|
|
54
|
+
|
|
55
|
+
Will need to use strong parameters
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# When extending already present models (ERA)
|
|
59
|
+
copy and uncomment the first line from test/dummy/app/model/NAME.rb to app/model/NAME.rb eg:
|
|
60
|
+
```require NdrWorkflow::Engine.root.join('app', 'models', 'e_action')```
|
|
61
|
+
|
|
62
|
+
# When you want to extend the Engine
|
|
63
|
+
create new model definition in your app dir based on file in engines dummy app ie test/dummy/app/model/NAME.rb
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Test Data
|
|
67
|
+
|
|
68
|
+
All test data in this repository is fictitious. Any resemblance to real persons, living or dead, is purely coincidental.
|
|
69
|
+
|
|
70
|
+
Note: Real codes exist in the tests, postcodes for example, but bear no relation to real patient data. Please ensure that you *always* only ever commit dummy data when contributing to this project.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
= Using same class names in app and engine tricky .... (hence use of require)
|
|
74
|
+
http://stackoverflow.com/questions/5045068/extending-controllers-of-a-rails-3-engine-in-the-main-app
|
|
75
|
+
|
|
76
|
+
= TESTING
|
|
77
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-3.0.x bundle install
|
|
78
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-3.0.x bundle exec rake test
|
|
79
|
+
|
|
80
|
+
NDR_WORKFLOW
|
|
81
|
+
================================================================================================================================
|
|
82
|
+
-- Dummy lookup data
|
|
83
|
+
ZeType.create(:shortdesc => "National Prostate Cancer Audit"){|ze_type| ze_type.ze_typeid = "NPCA"}
|
|
84
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "NEW"}
|
|
85
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "AIMP"}
|
|
86
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "PRECHECK"}
|
|
87
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "APOST"}
|
|
88
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "MPOST"}
|
|
89
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "CREA"}
|
|
90
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "RECO"}
|
|
91
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "CHECK"}
|
|
92
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "AMCH"}
|
|
93
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "MMCH"}
|
|
94
|
+
ZeActiontype.create(:shortdesc => "Initialize batch"){|ze_actiontype| ze_actiontype.ze_actiontypeid = "STOP"}
|
|
95
|
+
-- NO POSTCODES
|
|
96
|
+
Zprovider.create(:shortdesc => "ECRIC CAMBRIDGE", :exportid => "0402", :startdate => "007-06-01", :sort => 1, :role => "H", :linac => 0){|ze_provider| ze_provider.zproviderid = "0402"}
|
|
97
|
+
Zuser.create(:zuserid => "ben", :shortdesc => "bens_test_user", :registryid => "0402")
|
|
98
|
+
|
|
99
|
+
-- Workflow model NPCA
|
|
100
|
+
SEQUENCE = %w{NEW AIMP PRECHECK APOST MPOST CREA RECO CHECK AMCH MMCH STOP}
|
|
101
|
+
SEQUENCE.each_with_index do |action, index|
|
|
102
|
+
EWorkflow.create(:e_type => 'NPCA', :last_e_actiontype => action, :next_e_actiontype => SEQUENCE[index+1]) unless SEQUENCE[index+1].nil?
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
-- Create batch for workflow
|
|
106
|
+
EBatch.create(:e_type => "NPCA", :provider => "0402", :registryid => "0402", :media => 'Email', :original_filename => 'textfile_batch.txt',
|
|
107
|
+
:cleaned_filename => '', :numberofrecords => 3, :date_reference1 => '2005.05.05', :date_reference2 => '2007.01.01',
|
|
108
|
+
:e_batchid_traced => '', :comments => 'Addenbrookes, plain text file', :digest => '1b2bd8aecffcaf099b4d529498615ef2c765d1b5',
|
|
109
|
+
:lock_version => 0)
|
|
110
|
+
|
|
111
|
+
-- Create first action for batch
|
|
112
|
+
EAction.create!(:e_batchid => 1, :e_actiontype => "NEW", :started => Time.now, :finished => nil, :startedby => 'ben', :status => '')
|
|
113
|
+
--
|
|
114
|
+
EBatch.new(:e_type => 'NPCA', :original_filename => 'svn/')
|
|
115
|
+
EBatch.first.e_actions.start_and_finish('NEW','ben')
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
require 'ndr_dev_support/tasks'
|
|
3
|
+
begin
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
7
|
+
end
|
|
8
|
+
begin
|
|
9
|
+
require 'rdoc/task'
|
|
10
|
+
rescue LoadError
|
|
11
|
+
require 'rdoc/rdoc'
|
|
12
|
+
require 'rake/rdoctask'
|
|
13
|
+
RDoc::Task = Rake::RDocTask
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
18
|
+
rdoc.title = 'NdrWorkflow'
|
|
19
|
+
rdoc.options << '--line-numbers'
|
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
|
25
|
+
load 'rails/tasks/engine.rake'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Bundler::GemHelper.install_tasks
|
|
30
|
+
|
|
31
|
+
require 'rake/testtask'
|
|
32
|
+
|
|
33
|
+
Rake::TestTask.new(:test) do |t|
|
|
34
|
+
t.libs << 'lib'
|
|
35
|
+
t.libs << 'test'
|
|
36
|
+
t.pattern = 'test/**/*_test.rb'
|
|
37
|
+
t.verbose = false
|
|
38
|
+
t.warning = false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
task :default => :test
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
NHS England takes security and the protection of private data extremely seriously. If you believe you have found a vulnerability or other issue which has compromised or could compromise the security of any of our systems and/or private data managed by our systems, please do not hesitate to contact us using the methods outlined below.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Security](#security)
|
|
8
|
+
- [Table of Contents](#table-of-contents)
|
|
9
|
+
- [Reporting a vulnerability](#reporting-a-vulnerability)
|
|
10
|
+
- [Email](#email)
|
|
11
|
+
- [NCSC](#ncsc)
|
|
12
|
+
- [General Security Enquiries](#general-security-enquiries)
|
|
13
|
+
|
|
14
|
+
## Reporting a vulnerability
|
|
15
|
+
|
|
16
|
+
Please note, email is our preferred method of receiving reports.
|
|
17
|
+
|
|
18
|
+
### Email
|
|
19
|
+
|
|
20
|
+
If you wish to notify us of a vulnerability via email, please include detailed information on the nature of the vulnerability and any steps required to reproduce it.
|
|
21
|
+
|
|
22
|
+
You can reach us at:
|
|
23
|
+
|
|
24
|
+
- [Brian.Shand@nhs.net](mailto:Brian.Shand@nhs.net)
|
|
25
|
+
- [cybersecurity@nhs.net](mailto:cybersecurity@nhs.net)
|
|
26
|
+
|
|
27
|
+
### NCSC
|
|
28
|
+
|
|
29
|
+
You can send your report to the National Cyber Security Centre, who will assess your report and pass it on to NHS England if necessary.
|
|
30
|
+
|
|
31
|
+
You can report vulnerabilities here: [https://www.ncsc.gov.uk/information/vulnerability-reporting](https://www.ncsc.gov.uk/information/vulnerability-reporting)
|
|
32
|
+
|
|
33
|
+
## General Security Enquiries
|
|
34
|
+
|
|
35
|
+
If you have general enquiries regarding our cybersecurity, please reach out to us at [cybersecurity@nhs.net](mailto:cybersecurity@nhs.net)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Make this exist, so autoloaders are happy
|
|
2
|
+
module EsourceConstants; end
|
|
3
|
+
|
|
4
|
+
# Constants that are used in a number of e-source-related programs.
|
|
5
|
+
# The model classes corresponding to 'e_type' strings in e_batch records
|
|
6
|
+
PERMITTED_LISTS_FROM_LOOKUPS = [:laterality] unless defined?(PERMITTED_LISTS_FROM_LOOKUPS)
|
|
7
|
+
|
|
8
|
+
ERROR_INFO = {
|
|
9
|
+
:started => ['Action in progress', 'The action has been started by someone else.'],
|
|
10
|
+
:nobatch => ['Invalid batch', 'Unable to find certain batch in database.'],
|
|
11
|
+
:nocommit => ['Invalid access', "You didn't specify an valid operation."],
|
|
12
|
+
:perform => ['Invalid operation', "You didn't specify an valid operation."],
|
|
13
|
+
:yaml => ['Yaml file', 'Failed to load mapping file.'],
|
|
14
|
+
:wrongdate => ['Invalid date', 'Incorrect file received date.'],
|
|
15
|
+
:nofile => ['Invalid file', 'No file selected.'],
|
|
16
|
+
:nolibrary => ['Missing software', 'Cannot understand this file as no software driver was found.'],
|
|
17
|
+
:wrongformat => ['Invalid file format', 'File format is not accepted by this source.'],
|
|
18
|
+
:fileexisted => ['File existed', 'The file is currently being processed.'],
|
|
19
|
+
:digest => ['File existed', 'File with same digest has been found in repository.'],
|
|
20
|
+
:extinrep => ['File existed', 'File with same name has been found in repository.'],
|
|
21
|
+
:batch => ['Create Batch', 'Failed to create a batch record.'],
|
|
22
|
+
:action_create => ['Create Action', 'Failed to create an action record.'],
|
|
23
|
+
:action_update => ['Update Action', 'Failed to update an action record.'],
|
|
24
|
+
:imported => ['Import', 'This table has already been imported.'],
|
|
25
|
+
:pattern => ['Pattern error', "Data doesn't match the pattern specified in yaml file."],
|
|
26
|
+
:nomapping => ['Unrecognised batch', 'No columns mapping provided for this source.'],
|
|
27
|
+
:nodatereference => ['Invalid date', 'Unspecified date reference.'],
|
|
28
|
+
:baddatereference => ['Invalid date', 'Bad date(s) specified in Date Reference field. '],
|
|
29
|
+
:password => ['Invalid password', "Password doesn't match selected xls file."],
|
|
30
|
+
:blankfile => ['Blank file', 'No data found in specified file.'],
|
|
31
|
+
:header => ['Header line error', 'No header line found or header line conflicts with specified source'],
|
|
32
|
+
:difference => ['Uneven records', 'Some records have at least 3 columns less than others in that batch']
|
|
33
|
+
} unless defined?(ERROR_INFO)
|
|
34
|
+
|
|
35
|
+
DATA_MEDIA = ['Email', 'Floppy Disk', 'CD/DVD', 'Others'] unless defined?(DATA_MEDIA)
|
|
36
|
+
|
|
37
|
+
MATCH_STATUS = {
|
|
38
|
+
:perfect => 'Definite Match', # AMCH_STATUS
|
|
39
|
+
:possible => 'Possible Matches', # AMCH_STATUS
|
|
40
|
+
:none => 'No Match', # AMCH_STATUS
|
|
41
|
+
:alias => 'Alias', # MMCH_STATUS
|
|
42
|
+
:new => 'New', # MMCH_STATUS & DEDU_STATUS
|
|
43
|
+
:manual => 'Manual', # MMCH_STATUS & DEDU_STATUS
|
|
44
|
+
:match => 'Match', # MMCH_STATUS & DEDU_STATUS
|
|
45
|
+
:reset => nil # MMCH_STATUS & DEDU_STATUS, used for "change this decision"
|
|
46
|
+
} unless defined?(MATCH_STATUS)
|
|
47
|
+
|
|
48
|
+
# Batch stats default timeline base
|
|
49
|
+
# If the typical_batch_interval is 1.month,
|
|
50
|
+
# Then the default timeline is 3 * 1.month = 3 months
|
|
51
|
+
BATCH_STATS_TIMELINE_BASE = 3 unless defined?(BATCH_STATS_TIMELINE_BASE)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# EAction table.
|
|
2
|
+
class EAction < NdrWorkflow.abstract_model_class
|
|
3
|
+
include NdrWorkflow::EActionProgress
|
|
4
|
+
|
|
5
|
+
self.table_name = 'e_action'
|
|
6
|
+
self.sequence_name = 'e_action_seq'
|
|
7
|
+
self.primary_key = 'e_actionid'
|
|
8
|
+
|
|
9
|
+
belongs_to :e_batch,
|
|
10
|
+
foreign_key: 'e_batchid'
|
|
11
|
+
|
|
12
|
+
# using 'completed' instead of 'finished' because 'finished'
|
|
13
|
+
# should be named 'finished_at'
|
|
14
|
+
scope :completed, -> { where('finished is not null') }
|
|
15
|
+
|
|
16
|
+
before_save :ensure_timestamp_consistency
|
|
17
|
+
validate :ensure_actiontype_not_nil
|
|
18
|
+
validate :ensure_valid_actiontype
|
|
19
|
+
validate :ensure_startedby_not_nil
|
|
20
|
+
validate :ensure_valid_startedby
|
|
21
|
+
validate :ensure_batchid_not_nil
|
|
22
|
+
validate :ensure_batch_not_nil
|
|
23
|
+
validate :ensure_started_not_nil
|
|
24
|
+
validate :ensure_action_not_in_future
|
|
25
|
+
validate :ensure_started_not_after_finished
|
|
26
|
+
|
|
27
|
+
def ensure_timestamp_consistency
|
|
28
|
+
if started_changed? && (e_batch.e_actions.collect(&:finished).compact.max || started) > started then
|
|
29
|
+
raise RuntimeError, "EBatch already has an action with a timestamp greater than #{started} - Possible clock drift or other inconsitency"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# rdoc validation
|
|
34
|
+
# e_actiontype cannot be nil.
|
|
35
|
+
def ensure_actiontype_not_nil
|
|
36
|
+
errors.add(:e_action, 'e_actiontype can not be nil.') if e_actiontype.blank?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# rdoc validation
|
|
40
|
+
# e_actiontype must exist in our lookup table.
|
|
41
|
+
def ensure_valid_actiontype
|
|
42
|
+
return if ZeActiontype.valid_value?(e_actiontype)
|
|
43
|
+
errors.add(:e_action, 'e_actiontype does not exist.')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# rdoc validation
|
|
47
|
+
# startedby user cannot be nil.
|
|
48
|
+
def ensure_startedby_not_nil
|
|
49
|
+
errors.add(:e_action, 'user can not be nil.') if startedby.blank?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# rdoc validation
|
|
53
|
+
# startedby user must exist in our lookup table.
|
|
54
|
+
def ensure_valid_startedby
|
|
55
|
+
return if Zuser.valid_value?(startedby)
|
|
56
|
+
errors.add(:e_action, 'user does not exist.')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# rdoc validation
|
|
60
|
+
# e_batchid cannot be nil.
|
|
61
|
+
def ensure_batchid_not_nil
|
|
62
|
+
errors.add(:e_action, 'e_batchid cannot be nil.') if e_batchid.blank?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# rdoc validation
|
|
66
|
+
# batch cannot be nil.
|
|
67
|
+
def ensure_batch_not_nil
|
|
68
|
+
errors.add(:e_action, 'does not belong to any batch.') if e_batch.nil?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# rdoc validation
|
|
72
|
+
# started cannot be nil.
|
|
73
|
+
def ensure_started_not_nil
|
|
74
|
+
if started.blank?
|
|
75
|
+
errors.add(:e_action, 'does not have a start time.')
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# rdoc validation
|
|
81
|
+
# started or finished cannot be in the future
|
|
82
|
+
def ensure_action_not_in_future
|
|
83
|
+
unless finished.blank? || started.blank?
|
|
84
|
+
if started > Time.now || finished > Time.now
|
|
85
|
+
errors.add(:e_action, 'action can not be in the future.')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# rdoc validation
|
|
91
|
+
# started must not be after finished
|
|
92
|
+
def ensure_started_not_after_finished
|
|
93
|
+
unless finished.blank? || started.blank?
|
|
94
|
+
if started > finished
|
|
95
|
+
errors.add(:e_action, 'action can not be finished before it started.')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# A quick and simple way to create a single EAction.
|
|
101
|
+
def self.make_and_save!(e_batchid, actiontype, started, finished, userid, status = nil)
|
|
102
|
+
a = create(
|
|
103
|
+
e_batchid: e_batchid,
|
|
104
|
+
e_actiontype: actiontype,
|
|
105
|
+
started: started,
|
|
106
|
+
finished: finished,
|
|
107
|
+
startedby: userid,
|
|
108
|
+
status: status
|
|
109
|
+
)
|
|
110
|
+
if a.errors.to_a.blank?
|
|
111
|
+
a
|
|
112
|
+
else
|
|
113
|
+
raise and return a.errors
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
require 'ndr_support/daterange'
|
|
2
|
+
require 'ndr_support/string/conversions'
|
|
3
|
+
|
|
4
|
+
# The EBatch table
|
|
5
|
+
class EBatch < NdrWorkflow.abstract_model_class
|
|
6
|
+
self.table_name = 'e_batch'
|
|
7
|
+
self.primary_key = 'e_batchid'
|
|
8
|
+
self.sequence_name = 'e_batch_seq'
|
|
9
|
+
|
|
10
|
+
before_save :clean_filename_for_import!
|
|
11
|
+
|
|
12
|
+
# Rails 4 implementation of has_many with scope (order)
|
|
13
|
+
has_many :e_actions, -> { order('e_actionid') }, class_name: 'EAction',
|
|
14
|
+
foreign_key: 'e_batchid',
|
|
15
|
+
dependent: :destroy do
|
|
16
|
+
# starts an action
|
|
17
|
+
def start(actiontype, userid)
|
|
18
|
+
proxy_association.owner.assign_to(userid)
|
|
19
|
+
|
|
20
|
+
attributes = { started: Time.now, startedby: userid, comments: nil }
|
|
21
|
+
|
|
22
|
+
if actiontype == 'CREA'
|
|
23
|
+
create! attributes.merge(e_actiontype: actiontype)
|
|
24
|
+
else
|
|
25
|
+
find_or_create_by!(e_actiontype: actiontype) do |record|
|
|
26
|
+
record.assign_attributes(attributes)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# restart an action
|
|
32
|
+
def restart(actiontype, userid)
|
|
33
|
+
action = where(e_actiontype: actiontype).order('e_actionid desc').first!
|
|
34
|
+
proxy_association.owner.assign_to(userid)
|
|
35
|
+
comments = "#{action.comments} Action was started by #{action.startedby} on #{action.started};"
|
|
36
|
+
action.update!(started: Time.now, startedby: userid, comments: comments)
|
|
37
|
+
action
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# updating the progress of an action could go here?
|
|
41
|
+
# finishes an action
|
|
42
|
+
def finish(actiontype, comments = nil, status = nil)
|
|
43
|
+
action = where(e_actiontype: actiontype).order('e_actionid desc').first!
|
|
44
|
+
action.update!(finished: Time.now, comments: comments, status: status)
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
e_batch = proxy_association.owner
|
|
48
|
+
e_batch.unlock
|
|
49
|
+
rescue ActiveRecord::StaleObjectError
|
|
50
|
+
Rails.logger.warn("NdrWorkflow: failed to unlock stale batch #{e_batch.id}!")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# manual tasks are frequently completed by symultaneously
|
|
55
|
+
# starting and finishing an action
|
|
56
|
+
def start_and_finish(actiontype, userid, comments = nil, status = nil)
|
|
57
|
+
start(actiontype, userid)
|
|
58
|
+
finish(actiontype, comments, status)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
validate :ensure_provider_not_blank
|
|
63
|
+
validate :ensure_registryid_not_blank
|
|
64
|
+
validate :ensure_valid_type
|
|
65
|
+
validate :ensure_valid_provider
|
|
66
|
+
validate :ensure_valid_registryid
|
|
67
|
+
# validate :ensure_datereference_present_once_required
|
|
68
|
+
# --
|
|
69
|
+
# TODO: Replace the code below with x_daterange :date_reference or x_daterange :datereference
|
|
70
|
+
# Either updating calls to datereference with calls to date_reference or rename table columns
|
|
71
|
+
# ++
|
|
72
|
+
composed_of :datereference,
|
|
73
|
+
class_name: 'Daterange',
|
|
74
|
+
mapping:
|
|
75
|
+
[
|
|
76
|
+
[:date_reference1, :date1],
|
|
77
|
+
[:date_reference2, :date2]
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# rdoc validation
|
|
81
|
+
# provider cannot be nil.
|
|
82
|
+
def ensure_provider_not_blank
|
|
83
|
+
errors.add(:provider, 'cannot be blank.') if provider.blank?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# rdoc validation
|
|
87
|
+
# registryid cannot be nil.
|
|
88
|
+
def ensure_registryid_not_blank
|
|
89
|
+
errors.add(:registry, 'cannot be blank.') if registryid.blank?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# rdoc validation
|
|
93
|
+
# e_type must exist in our lookup table.
|
|
94
|
+
def ensure_valid_type
|
|
95
|
+
return if ZeType.valid_value?(e_type)
|
|
96
|
+
errors.add(:e_type, 'does not exist.')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# rdoc validation
|
|
100
|
+
# provider must exist in our lookup table.
|
|
101
|
+
def ensure_valid_provider
|
|
102
|
+
return if Zprovider.valid_value?(provider)
|
|
103
|
+
errors.add(:provider, 'does not exist.')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# rdoc validation
|
|
107
|
+
# registryid must exist in our lookup table.
|
|
108
|
+
def ensure_valid_registryid
|
|
109
|
+
return if Zprovider.valid_value?(registryid)
|
|
110
|
+
errors.add(:registry, 'does not exist.')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def provider_desc
|
|
114
|
+
lookup = Zprovider.find_by_zproviderid(provider)
|
|
115
|
+
if lookup
|
|
116
|
+
description = lookup.shortdesc
|
|
117
|
+
return (description.upcase == description ? description.titlecase : description)
|
|
118
|
+
else
|
|
119
|
+
return provider
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def assign_to(user)
|
|
124
|
+
update_attribute(:inprogress, user)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def unlock
|
|
128
|
+
assign_to(nil)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def last_finished_action
|
|
132
|
+
e_actions.completed.except(:order).order('finished desc, e_actionid desc').first
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# Returns the actiontype that is available for this batch (if known)
|
|
139
|
+
# Method is updated on 2 Mar, 2009: each action will only have one proceeded action
|
|
140
|
+
# in Unified Esources. (Removed old PAS's RECE and MANU actions)
|
|
141
|
+
def next_available_action
|
|
142
|
+
begin
|
|
143
|
+
# TODO: Output of this sometimes disagrees with ROVBATCHACTION, resulting
|
|
144
|
+
# in inconsistent orderings, and batch automator failures. At very
|
|
145
|
+
# least, the batch automator should object more vigorously if the
|
|
146
|
+
# next_available_action disagrees with the ROVBATCHACTION output.
|
|
147
|
+
if la = last_finished_action
|
|
148
|
+
EWorkflow.next_action(e_type, la.e_actiontype)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Is the batch past `target_action' in the workflow?
|
|
154
|
+
def done_action?(target_action)
|
|
155
|
+
return false if last_finished_action.nil? # Haven't start any e_actions
|
|
156
|
+
|
|
157
|
+
at_new = false # Reached the start of the workflow?
|
|
158
|
+
done_it = false # Have we done `target_action'?
|
|
159
|
+
# Where are we in the workflow?
|
|
160
|
+
action = next_available_action || last_finished_action.e_actiontype
|
|
161
|
+
|
|
162
|
+
# Traverse the workflow in reverse from the current action to see
|
|
163
|
+
# if there is a prior `target_action` step for this batch type:
|
|
164
|
+
begin
|
|
165
|
+
step = EWorkflow.find_by_e_type_and_next_e_actiontype(e_type, action)
|
|
166
|
+
|
|
167
|
+
at_new = step.nil?
|
|
168
|
+
action = step.try(:last_e_actiontype)
|
|
169
|
+
done_it = (target_action == action)
|
|
170
|
+
end until (done_it || at_new)
|
|
171
|
+
|
|
172
|
+
done_it
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def in_progress?
|
|
178
|
+
!inprogress.nil?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def belongs_to?(user)
|
|
182
|
+
inprogress == user.to_s
|
|
183
|
+
end
|
|
184
|
+
def free?(user)
|
|
185
|
+
!in_progress? || belongs_to?(user)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# Check if a batch is in progress.
|
|
193
|
+
# Return true if the batch is free or was launched by the current user
|
|
194
|
+
def self.free?(batchid, user)
|
|
195
|
+
find(batchid).free?(user)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def clean_filename_for_import!
|
|
199
|
+
unless original_filename.blank?
|
|
200
|
+
# The file path may be pasted in from the Windows command prompt,
|
|
201
|
+
# so swap back slashes for forward slashes
|
|
202
|
+
original_filename.gsub!('\\', '/')
|
|
203
|
+
original_filename.sub!(/\Asvn[\/]/i, 'svn/') # TODO: required?
|
|
204
|
+
original_filename.strip!
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# EWorkflow describes transitions between E_ACTIONS for the different E_BATCH.E_TYPES.
|
|
2
|
+
class EWorkflow < NdrWorkflow.abstract_model_class
|
|
3
|
+
self.table_name = 'e_workflow'
|
|
4
|
+
self.sequence_name = 'e_workflow_seq'
|
|
5
|
+
self.primary_key = 'e_workflowid'
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# Returns the name of the next step on the waterfall. Caches all results.
|
|
9
|
+
# EWorkflow.next_action('COSD_XML', 'AIMP')
|
|
10
|
+
# #=> "PRECHECK"
|
|
11
|
+
# EWorkflow.next_action('COSD_XML', 'GENA')
|
|
12
|
+
# #=> nil
|
|
13
|
+
|
|
14
|
+
def next_action(e_type, last_e_actiontype)
|
|
15
|
+
@next_actions ||= Hash.new do |hash, (type, last_action)|
|
|
16
|
+
scope = { e_type: type, last_e_actiontype: last_action }
|
|
17
|
+
hash[[type, last_action]] = where(scope).pluck(:next_e_actiontype).first
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@next_actions[[e_type, last_e_actiontype]]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class ZeActiontype < ZlookupCached
|
|
2
|
+
self.table_name = 'ze_actiontype'
|
|
3
|
+
self.primary_key = 'ze_actiontypeid'
|
|
4
|
+
# This method caches the ZeActiontype ids and their descriptions.
|
|
5
|
+
# With the new class loader of Rails 2.2 and later, we cannot prepopulate a
|
|
6
|
+
# constant with this data (as we did in Rails 1.2 and 2.1). Instead, we cache
|
|
7
|
+
# the result in the model.
|
|
8
|
+
def self.type_descriptions
|
|
9
|
+
@@type_descriptions ||= all.inject({}) do |hash, l|
|
|
10
|
+
hash.merge!(l.id.to_sym => l.shortdesc)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Zlookup pattern copied from ERA to allow easy integration of Engine with ERA
|
|
2
|
+
class Zlookup < NdrWorkflow.abstract_model_class
|
|
3
|
+
self.abstract_class = true
|
|
4
|
+
def self.valid_value?(key, strict = false)
|
|
5
|
+
return true if key.blank?
|
|
6
|
+
scope = strict ? current : self
|
|
7
|
+
scope.where(primary_key => key).count == 1
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Zprovider represents providers of health services, e.g. hospitals, GP
|
|
2
|
+
# see modell definition in ERA for standard Zprovider infomration / methods,
|
|
3
|
+
# it was thought this was too far from workflow to be extracted - can always
|
|
4
|
+
# be added back later
|
|
5
|
+
class Zprovider < Zlookup
|
|
6
|
+
self.table_name = 'zprovider'
|
|
7
|
+
self.primary_key = 'zproviderid'
|
|
8
|
+
|
|
9
|
+
end
|
data/app/models/zuser.rb
ADDED
data/config/routes.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# EBatch
|
|
2
|
+
class CreateEBatch < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
create_table :e_batch, id: false do |t|
|
|
5
|
+
t.primary_key :e_batchid, limit: 8
|
|
6
|
+
t.string :e_type, limit: 255
|
|
7
|
+
t.string :provider, limit: 255
|
|
8
|
+
t.string :media, limit: 255
|
|
9
|
+
t.string :original_filename, limit: 255
|
|
10
|
+
t.string :cleaned_filename, limit: 255
|
|
11
|
+
t.integer :numberofrecords, limit: 8 # bigint
|
|
12
|
+
t.datetime :date_reference1
|
|
13
|
+
t.datetime :date_reference2
|
|
14
|
+
t.integer :e_batchid_traced, limit: 8
|
|
15
|
+
t.string :comments, limit: 255
|
|
16
|
+
t.string :digest, limit: 40
|
|
17
|
+
t.integer :lock_version, limit: 8, default: 0, null: false
|
|
18
|
+
t.string :inprogress, limit: 50
|
|
19
|
+
t.string :registryid, limit: 255
|
|
20
|
+
t.integer :on_hold, limit: 2 # smallint
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
add_index :e_batch, [:registryid, :e_type, :provider]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# EAction migration
|
|
2
|
+
class CreateEAction < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
create_table :e_action, id: false do |t|
|
|
5
|
+
t.primary_key :e_actionid, limit: 8
|
|
6
|
+
t.integer :e_batchid, limit: 8, index: true
|
|
7
|
+
t.string :e_actiontype, limit: 255
|
|
8
|
+
t.datetime :started
|
|
9
|
+
t.string :startedby, limit: 255
|
|
10
|
+
t.datetime :finished
|
|
11
|
+
t.string :comments, limit: 4000
|
|
12
|
+
t.string :status, limit: 255, index: true
|
|
13
|
+
t.integer :lock_version, limit: 8, default: 0, null: false
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# EWorflow
|
|
2
|
+
class CreateEWorkflow < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
create_table :e_workflow, id: false do |t|
|
|
5
|
+
t.primary_key :e_workflowid, limit: 8
|
|
6
|
+
t.string :e_type, limit: 255
|
|
7
|
+
t.string :provider, limit: 255
|
|
8
|
+
t.string :last_e_actiontype, limit: 255
|
|
9
|
+
t.string :next_e_actiontype, limit: 255
|
|
10
|
+
t.string :comments, limit: 255
|
|
11
|
+
t.integer :sort, limit: 2 # smallint
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
add_index :e_workflow, [:e_type, :last_e_actiontype, :next_e_actiontype],
|
|
15
|
+
:name => 'e_workflow_etype_leat_neat_ix'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# ZUser
|
|
2
|
+
class CreateZuser < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
create_table :zuser, id: false do |t|
|
|
5
|
+
t.string :zuserid, limit: 255, primary_key: true, default: 1, null: false
|
|
6
|
+
t.string :shortdesc, limit: 64
|
|
7
|
+
t.string :description, limit: 2000
|
|
8
|
+
t.string :exportid, limit: 64
|
|
9
|
+
t.datetime :startdate
|
|
10
|
+
t.datetime :enddate
|
|
11
|
+
t.integer :sort, limit: 8 # bigint
|
|
12
|
+
t.string :registryid, limit: 5
|
|
13
|
+
t.string :qa_supervisorid, limit: 255
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ZEActionType
|
|
2
|
+
class CreateZeActiontype < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
create_table :ze_actiontype, id: false do |t|
|
|
5
|
+
t.string :ze_actiontypeid, limit: 255, primary_key: true, default: 1, null: false
|
|
6
|
+
t.string :shortdesc, limit: 64
|
|
7
|
+
t.string :description, limit: 255
|
|
8
|
+
t.datetime :startdate
|
|
9
|
+
t.datetime :enddate
|
|
10
|
+
t.integer :sort, limit: 8 # bigint
|
|
11
|
+
t.string :comments, limit: 255
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ZEType
|
|
2
|
+
class CreateZeType < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
create_table :ze_type, id: false do |t|
|
|
5
|
+
t.string :ze_typeid, limit: 255, primary_key: true, default: 1, null: false
|
|
6
|
+
t.string :shortdesc, limit: 64
|
|
7
|
+
t.string :description, limit: 255
|
|
8
|
+
t.datetime :startdate
|
|
9
|
+
t.datetime :enddate
|
|
10
|
+
t.integer :sort, limit: 8 # bigint
|
|
11
|
+
t.string :comments, limit: 255
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# ZProvider
|
|
2
|
+
class CreateZProvider < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
create_table :zprovider, id: false do |t|
|
|
5
|
+
t.string :zproviderid, limit: 255, primary_key: true, default: 1, null: false
|
|
6
|
+
t.string :shortdesc, limit: 128
|
|
7
|
+
t.string :description, limit: 2000
|
|
8
|
+
t.string :exportid, limit: 64
|
|
9
|
+
t.datetime :startdate
|
|
10
|
+
t.datetime :enddate
|
|
11
|
+
t.integer :sort, limit: 8 # bigint
|
|
12
|
+
t.string :role, limit: 1
|
|
13
|
+
t.integer :local_hospital, limit: 2, default: 0, null: false # smallint
|
|
14
|
+
t.integer :breast_screening_unit, limit: 2, default: 0, null: false
|
|
15
|
+
t.integer :historical, limit: 2, default: 0, null: false
|
|
16
|
+
t.string :lpi_providercode, limit: 255
|
|
17
|
+
t.string :zpostcodeid, limit: 255
|
|
18
|
+
# Provider has a linear particle accelerator
|
|
19
|
+
t.integer :linac, limit: 2, default: 0, null: false
|
|
20
|
+
t.string :analysisid, limit: 255
|
|
21
|
+
t.integer :nacscode, limit: 2
|
|
22
|
+
t.string :nacs5id, limit: 5
|
|
23
|
+
t.string :successorid, limit: 255
|
|
24
|
+
t.string :local_registryid, limit: 5
|
|
25
|
+
t.string :source, limit: 255
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Add FK constraints to tables
|
|
2
|
+
class CreateConstraint < NdrWorkflow.migration_class
|
|
3
|
+
def change
|
|
4
|
+
unless respond_to?(:add_foreign_key)
|
|
5
|
+
warn "CreateConstraint: `add_foreign_key` is not supported on #{Rails.version}, skipping."
|
|
6
|
+
return
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
add_foreign_key :e_action, :e_batch, column: :e_batchid, primary_key: 'e_batchid'
|
|
10
|
+
add_foreign_key :e_action, :ze_actiontype, column: :e_actiontype, primary_key: 'ze_actiontypeid'
|
|
11
|
+
|
|
12
|
+
add_foreign_key :e_batch, :ze_type, column: :e_type, primary_key: 'ze_typeid'
|
|
13
|
+
add_foreign_key :e_batch, :zprovider, column: :provider, primary_key: 'zproviderid'
|
|
14
|
+
add_foreign_key :e_batch, :zprovider, column: :registryid, primary_key: 'zproviderid'
|
|
15
|
+
|
|
16
|
+
add_foreign_key :e_workflow, :ze_actiontype, column: :last_e_actiontype, primary_key: 'ze_actiontypeid'
|
|
17
|
+
add_foreign_key :e_workflow, :ze_actiontype, column: :next_e_actiontype, primary_key: 'ze_actiontypeid'
|
|
18
|
+
add_foreign_key :e_workflow, :ze_type, column: :e_type, primary_key: 'ze_typeid'
|
|
19
|
+
add_foreign_key :e_workflow, :zprovider, column: :provider, primary_key: 'zproviderid'
|
|
20
|
+
|
|
21
|
+
add_foreign_key :zuser, :zuser, column: :qa_supervisorid, primary_key: 'zuserid'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require 'ndr_support/ourdate'
|
|
2
|
+
require 'ndr_support/ourtime'
|
|
3
|
+
|
|
4
|
+
module NdrWorkflow
|
|
5
|
+
# Logic to be shared by EAction and QueuedJob:
|
|
6
|
+
module EActionProgress
|
|
7
|
+
# Classes allowed inside YAML-serialized comments
|
|
8
|
+
COMMENT_CLASSES = [Ourdate, DateTime, Time, Date, ActiveSupport::TimeWithZone,
|
|
9
|
+
ActiveSupport::TimeZone, ActiveSupport::HashWithIndifferentAccess,
|
|
10
|
+
Symbol].freeze
|
|
11
|
+
|
|
12
|
+
# Set the progress percentage of an activity. Progress will range from
|
|
13
|
+
# min_percent to max_percent as i varies from 0 to total.
|
|
14
|
+
# Sample usage:
|
|
15
|
+
# total = items.length
|
|
16
|
+
# items.each_with_index do |item, i|
|
|
17
|
+
# do_something
|
|
18
|
+
# action.update_percentage(i+1, total)
|
|
19
|
+
# end
|
|
20
|
+
def update_percentage(i, total, min_percent = 0, max_percent = 100)
|
|
21
|
+
@last_updated_at ||= progress['updated_at']
|
|
22
|
+
@last_percentage ||= progress['percentage']
|
|
23
|
+
|
|
24
|
+
current_percentage = total == 0 ? 0 : (min_percent + i * (max_percent - min_percent) / total).to_i
|
|
25
|
+
if @last_updated_at.nil? || @last_percentage.nil? ||
|
|
26
|
+
(current_percentage < @last_percentage) || # Always show negative changes
|
|
27
|
+
(current_percentage > @last_percentage && @last_updated_at < 5.minutes.ago) || # Rate limit positive changes to once every 5 minutes...
|
|
28
|
+
(current_percentage.divmod(5).first > @last_percentage.divmod(5).first && @last_updated_at < 1.minute.ago) # ...but allow >5% changes every 1 minute
|
|
29
|
+
@last_updated_at = Time.now
|
|
30
|
+
@last_percentage = current_percentage
|
|
31
|
+
# yaml = YAML.dump( progress.merge({'percentage' => @last_percentage, 'updated_at' => @last_updated_at}))
|
|
32
|
+
yaml = YAML.dump({'percentage' => @last_percentage, 'updated_at' => @last_updated_at})
|
|
33
|
+
begin
|
|
34
|
+
raise(ActiveRecord::StatementInvalid, <<-DESC) if Rails.env.test?
|
|
35
|
+
libutils is not reliably available in the test schema, so we
|
|
36
|
+
test the fallback behaviour. This ensures it is the fallback
|
|
37
|
+
behaviour that we get, even if libutils is present.
|
|
38
|
+
DESC
|
|
39
|
+
|
|
40
|
+
# Try to update the progress in an anonymous transaction, providing feedback when we
|
|
41
|
+
# wouldn't otherwise get any (i.e. when the update is in a transaction)
|
|
42
|
+
# TODO: Test for the corner case where the e_action is updateed within a transaction and
|
|
43
|
+
# where this is blocked, causing it to fail over to a standard (transaction based) update.
|
|
44
|
+
# The SQL statement is sanitized for security.
|
|
45
|
+
# SECURE: BNS 2012-10-09
|
|
46
|
+
sanitized_sql = self.class.send(:sanitize_sql_array,[
|
|
47
|
+
"BEGIN libutils.set_e_action_comments(?, ?); END;", self.e_actionid, yaml
|
|
48
|
+
])
|
|
49
|
+
ActiveRecord::Base.connection.execute(sanitized_sql)
|
|
50
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
51
|
+
# Update the old way, using standard update
|
|
52
|
+
Rails.logger.warn("EAction#update_percentage failed to use libutils.set_e_action_comments (#{e.message})")
|
|
53
|
+
puts "EAction#update_percentage failed to use libutils.set_e_action_comments (#{e.message})" unless Rails.env.test?
|
|
54
|
+
|
|
55
|
+
# The EAction could be stale as a result of PREP. Eg ChildhoodPreallocator
|
|
56
|
+
reload unless changed?
|
|
57
|
+
update(comments: yaml)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def progress
|
|
63
|
+
if comments
|
|
64
|
+
deserialised_comments = YAML.safe_load(comments, permitted_classes: COMMENT_CLASSES.freeze,
|
|
65
|
+
aliases: true)
|
|
66
|
+
case deserialised_comments
|
|
67
|
+
when Integer
|
|
68
|
+
# Legacy number
|
|
69
|
+
return {'percentage' => comments.to_i}
|
|
70
|
+
when String
|
|
71
|
+
return {'message' => deserialised_comments}
|
|
72
|
+
when Hash
|
|
73
|
+
return deserialised_comments
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
return {}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# This method takes an exception and updates the e_action so that it can be
|
|
80
|
+
# seen easily through the UI.
|
|
81
|
+
def log_error(error, extramessage = nil)
|
|
82
|
+
self.reload unless new_record?
|
|
83
|
+
|
|
84
|
+
error_info = {
|
|
85
|
+
'error_message' => [error.message, extramessage].compact.join(': '),
|
|
86
|
+
'updated_at' => Time.now
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
begin
|
|
90
|
+
if defined?(NdrError)
|
|
91
|
+
fingerprint, log = NdrError.log(error, { :user_id => self.startedby, :user_roles => '(BATCH)' }, nil)
|
|
92
|
+
error_info['error_fingerprint'] = fingerprint.id if fingerprint
|
|
93
|
+
else
|
|
94
|
+
logger.warn("Failed to log EAction exception in the DB! (#{$!.to_s})")
|
|
95
|
+
end
|
|
96
|
+
rescue
|
|
97
|
+
logger.warn("Failed to log EAction exception in the DB! (#{$!.to_s})")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
data = progress.merge(error_info)
|
|
101
|
+
max_len = (EAction.columns_hash['comments'].sql_type.match(/([0-9]*)\)/).nil? ? 0 : EAction.columns_hash['comments'].sql_type.match(/([0-9]*)\)/)[1]).to_i
|
|
102
|
+
# if Era.db_oracle?
|
|
103
|
+
# max_len = EAction.columns_hash['comments'].sql_type.match(/VARCHAR2\(([0-9]*)\)/)[1].to_i
|
|
104
|
+
# elsif Era.db_postgres?
|
|
105
|
+
# max_len = EAction.columns_hash['comments'].sql_type.match(/character varying\(([0-9]*)\)/)[1].to_i
|
|
106
|
+
# end
|
|
107
|
+
if YAML.dump(data).length > max_len
|
|
108
|
+
# Horribly inefficient; preserve as much information as possible.
|
|
109
|
+
len2 = max_len - YAML.dump(data.merge('error_message' => nil)).size
|
|
110
|
+
while YAML.dump(data).length > max_len do
|
|
111
|
+
len2 -= 1
|
|
112
|
+
data['error_message'] = [error.message, extramessage].compact.collect{|s|
|
|
113
|
+
"#{s.to_s[0..len2]}#{'...' if len2 < s.to_s.length}"
|
|
114
|
+
}.join(': ')
|
|
115
|
+
end
|
|
116
|
+
logger.warn("EAction#log_error: Warning: Truncated error message: #{[error.message, extramessage].inspect} for EAction(#{id})")
|
|
117
|
+
end
|
|
118
|
+
update(comments: YAML.dump(data))
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
data/lib/ndr_workflow.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'ndr_workflow/e_action_progress'
|
|
2
|
+
require 'ndr_workflow/engine'
|
|
3
|
+
require 'ndr_workflow/version'
|
|
4
|
+
|
|
5
|
+
# NdrWorkflow
|
|
6
|
+
module NdrWorkflow
|
|
7
|
+
# Allows the host to define a parent for all models.
|
|
8
|
+
# Defaults to ActiveRecord::Base.
|
|
9
|
+
mattr_accessor :abstract_model_class
|
|
10
|
+
self.abstract_model_class = ActiveRecord::Base
|
|
11
|
+
|
|
12
|
+
# Rails 5 uses versioned migrations (required as of 5.1). This helper method returns an
|
|
13
|
+
# appropriate migration superclass for the Rails version of the host application. Note that the
|
|
14
|
+
# 4.2 compatability layer is targetted since all the bundled migrations were written prior to
|
|
15
|
+
# Rails 5.
|
|
16
|
+
def self.migration_class
|
|
17
|
+
Rails::VERSION::MAJOR < 5 ? ActiveRecord::Migration : ActiveRecord::Migration[4.2]
|
|
18
|
+
end
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ndr_workflow
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.2.5
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- NCRS Development team
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '8.2'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '8.2'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: ndr_support
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '5.0'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '5.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rake
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: ndr_dev_support
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '6.0'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '6.0'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: test-unit
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
type: :development
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: pg
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
type: :development
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
description: Core EWorkflow, Ebatch and EAction models mapped from ERA to postgres
|
|
103
|
+
implem.
|
|
104
|
+
email: []
|
|
105
|
+
executables: []
|
|
106
|
+
extensions: []
|
|
107
|
+
extra_rdoc_files: []
|
|
108
|
+
files:
|
|
109
|
+
- CHANGELOG.md
|
|
110
|
+
- MIT-LICENSE
|
|
111
|
+
- README.rdoc
|
|
112
|
+
- Rakefile
|
|
113
|
+
- SECURITY.md
|
|
114
|
+
- app/constants/esource_constants.rb
|
|
115
|
+
- app/models/e_action.rb
|
|
116
|
+
- app/models/e_batch.rb
|
|
117
|
+
- app/models/e_workflow.rb
|
|
118
|
+
- app/models/ze_actiontype.rb
|
|
119
|
+
- app/models/ze_type.rb
|
|
120
|
+
- app/models/zlookup.rb
|
|
121
|
+
- app/models/zlookup_cached.rb
|
|
122
|
+
- app/models/zprovider.rb
|
|
123
|
+
- app/models/zuser.rb
|
|
124
|
+
- config/routes.rb
|
|
125
|
+
- db/migrate/20160707141753_create_e_batch.rb
|
|
126
|
+
- db/migrate/20160707141754_create_e_action.rb
|
|
127
|
+
- db/migrate/20160707141755_create_e_workflow.rb
|
|
128
|
+
- db/migrate/20160707141756_create_zuser.rb
|
|
129
|
+
- db/migrate/20160707141757_create_ze_actiontype.rb
|
|
130
|
+
- db/migrate/20160707141758_create_ze_type.rb
|
|
131
|
+
- db/migrate/20160707141759_create_z_provider.rb
|
|
132
|
+
- db/migrate/20160707141800_create_constraint.rb
|
|
133
|
+
- lib/ndr_workflow.rb
|
|
134
|
+
- lib/ndr_workflow/e_action_progress.rb
|
|
135
|
+
- lib/ndr_workflow/engine.rb
|
|
136
|
+
- lib/ndr_workflow/version.rb
|
|
137
|
+
- lib/tasks/ndr_workflow_tasks.rake
|
|
138
|
+
licenses: []
|
|
139
|
+
metadata: {}
|
|
140
|
+
rdoc_options: []
|
|
141
|
+
require_paths:
|
|
142
|
+
- lib
|
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
|
+
requirements:
|
|
145
|
+
- - ">="
|
|
146
|
+
- !ruby/object:Gem::Version
|
|
147
|
+
version: '3.3'
|
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
requirements: []
|
|
154
|
+
rubygems_version: 3.6.9
|
|
155
|
+
specification_version: 4
|
|
156
|
+
summary: Base workflow schema
|
|
157
|
+
test_files: []
|