test_redmine_vz 0.0.24
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/README.md +71 -0
- data/Rakefile +20 -0
- data/config/currency_iso.json +2532 -0
- data/doc/CHANGELOG +55 -0
- data/doc/LICENSE.txt +339 -0
- data/lib/redmine_crm.rb +67 -0
- data/lib/redmine_crm/currency.rb +439 -0
- data/lib/redmine_crm/currency/formatting.rb +227 -0
- data/lib/redmine_crm/currency/heuristics.rb +151 -0
- data/lib/redmine_crm/currency/loader.rb +24 -0
- data/lib/redmine_crm/helpers/tags_helper.rb +15 -0
- data/lib/redmine_crm/helpers/vote_helper.rb +38 -0
- data/lib/redmine_crm/liquid/drops/issues_drop.rb +61 -0
- data/lib/redmine_crm/liquid/drops/news_drop.rb +45 -0
- data/lib/redmine_crm/liquid/drops/projects_drop.rb +78 -0
- data/lib/redmine_crm/liquid/drops/users_drop.rb +59 -0
- data/lib/redmine_crm/liquid/filters.rb +85 -0
- data/lib/redmine_crm/money_helper.rb +67 -0
- data/lib/redmine_crm/rcrm_acts_as_taggable.rb +342 -0
- data/lib/redmine_crm/rcrm_acts_as_viewed.rb +287 -0
- data/lib/redmine_crm/rcrm_acts_as_votable.rb +79 -0
- data/lib/redmine_crm/rcrm_acts_as_voter.rb +27 -0
- data/lib/redmine_crm/tag.rb +81 -0
- data/lib/redmine_crm/tag_list.rb +112 -0
- data/lib/redmine_crm/tagging.rb +20 -0
- data/lib/redmine_crm/version.rb +3 -0
- data/lib/redmine_crm/votable.rb +334 -0
- data/lib/redmine_crm/vote.rb +30 -0
- data/lib/redmine_crm/voter.rb +136 -0
- data/redmine_crm.gemspec +22 -0
- data/test/acts_as_taggable_test.rb +384 -0
- data/test/currency_test.rb +292 -0
- data/test/database.yml +17 -0
- data/test/fixtures/issue.rb +14 -0
- data/test/fixtures/issues.yml +12 -0
- data/test/fixtures/taggings.yml +32 -0
- data/test/fixtures/tags.yml +11 -0
- data/test/fixtures/user.rb +7 -0
- data/test/fixtures/users.yml +5 -0
- data/test/fixtures/votable_caches.yml +2 -0
- data/test/fixtures/votables.yml +4 -0
- data/test/fixtures/vote_classes.rb +54 -0
- data/test/fixtures/voters.yml +6 -0
- data/test/liquid_test.rb +80 -0
- data/test/money_helper_test.rb +12 -0
- data/test/schema.rb +100 -0
- data/test/tag_test.rb +63 -0
- data/test/tagging_test.rb +14 -0
- data/test/tags_helper_test.rb +29 -0
- data/test/test_helper.rb +118 -0
- data/test/viewed_test.rb +45 -0
- data/test/votable_model_test.rb +478 -0
- data/test/votable_test.rb +17 -0
- data/test/vote_helper_test.rb +28 -0
- data/test/voter_model_test.rb +296 -0
- metadata +141 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
class Currency
|
3
|
+
module Heuristics
|
4
|
+
|
5
|
+
# An robust and efficient algorithm for finding currencies in
|
6
|
+
# text. Using several algorithms it can find symbols, iso codes and
|
7
|
+
# even names of currencies.
|
8
|
+
# Although not recommendable, it can also attempt to find the given
|
9
|
+
# currency in an entire sentence
|
10
|
+
#
|
11
|
+
# Returns: Array (matched results)
|
12
|
+
def analyze(str)
|
13
|
+
return Analyzer.new(str, search_tree).process
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Build a search tree from the currency database
|
19
|
+
def search_tree
|
20
|
+
@_search_tree ||= {
|
21
|
+
:by_symbol => currencies_by_symbol,
|
22
|
+
:by_iso_code => currencies_by_iso_code,
|
23
|
+
:by_name => currencies_by_name
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def currencies_by_symbol
|
28
|
+
{}.tap do |r|
|
29
|
+
table.each do |dummy, c|
|
30
|
+
symbol = (c[:symbol]||"").downcase
|
31
|
+
symbol.chomp!('.')
|
32
|
+
(r[symbol] ||= []) << c
|
33
|
+
|
34
|
+
(c[:alternate_symbols]||[]).each do |ac|
|
35
|
+
ac = ac.downcase
|
36
|
+
ac.chomp!('.')
|
37
|
+
(r[ac] ||= []) << c
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def currencies_by_iso_code
|
44
|
+
{}.tap do |r|
|
45
|
+
table.each do |dummy,c|
|
46
|
+
(r[c[:iso_code].downcase] ||= []) << c
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def currencies_by_name
|
52
|
+
{}.tap do |r|
|
53
|
+
table.each do |dummy,c|
|
54
|
+
name_parts = c[:name].downcase.split
|
55
|
+
name_parts.each {|part| part.chomp!('.')}
|
56
|
+
|
57
|
+
# construct one branch per word
|
58
|
+
root = r
|
59
|
+
while name_part = name_parts.shift
|
60
|
+
root = (root[name_part] ||= {})
|
61
|
+
end
|
62
|
+
|
63
|
+
# the leaf is a currency
|
64
|
+
(root[:value] ||= []) << c
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Analyzer
|
70
|
+
attr_reader :search_tree, :words
|
71
|
+
attr_accessor :str, :currencies
|
72
|
+
|
73
|
+
def initialize str, search_tree
|
74
|
+
@str = (str||'').dup
|
75
|
+
@search_tree = search_tree
|
76
|
+
@currencies = []
|
77
|
+
end
|
78
|
+
|
79
|
+
def process
|
80
|
+
format
|
81
|
+
return [] if str.empty?
|
82
|
+
|
83
|
+
search_by_symbol
|
84
|
+
search_by_iso_code
|
85
|
+
search_by_name
|
86
|
+
|
87
|
+
prepare_reply
|
88
|
+
end
|
89
|
+
|
90
|
+
def format
|
91
|
+
str.gsub!(/[\r\n\t]/,'')
|
92
|
+
str.gsub!(/[0-9][\.,:0-9]*[0-9]/,'')
|
93
|
+
str.gsub!(/[0-9]/, '')
|
94
|
+
str.downcase!
|
95
|
+
@words = str.split
|
96
|
+
@words.each {|word| word.chomp!('.'); word.chomp!(',') }
|
97
|
+
end
|
98
|
+
|
99
|
+
def search_by_symbol
|
100
|
+
words.each do |word|
|
101
|
+
if found = search_tree[:by_symbol][word]
|
102
|
+
currencies.concat(found)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def search_by_iso_code
|
108
|
+
words.each do |word|
|
109
|
+
if found = search_tree[:by_iso_code][word]
|
110
|
+
currencies.concat(found)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def search_by_name
|
116
|
+
# remember, the search tree by name is a construct of branches and leaf!
|
117
|
+
# We need to try every combination of words within the sentence, so we
|
118
|
+
# end up with a x^2 equation, which should be fine as most names are either
|
119
|
+
# one or two words, and this is multiplied with the words of given sentence
|
120
|
+
|
121
|
+
search_words = words.dup
|
122
|
+
|
123
|
+
while search_words.length > 0
|
124
|
+
root = search_tree[:by_name]
|
125
|
+
|
126
|
+
search_words.each do |word|
|
127
|
+
if root = root[word]
|
128
|
+
if root[:value]
|
129
|
+
currencies.concat(root[:value])
|
130
|
+
end
|
131
|
+
else
|
132
|
+
break
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
search_words.delete_at(0)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def prepare_reply
|
141
|
+
codes = currencies.map do |currency|
|
142
|
+
currency[:iso_code]
|
143
|
+
end
|
144
|
+
codes.uniq!
|
145
|
+
codes.sort!
|
146
|
+
codes
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
class Currency
|
3
|
+
module Loader
|
4
|
+
DATA_PATH = File.expand_path("../../../../config", __FILE__)
|
5
|
+
|
6
|
+
# Loads and returns the currencies stored in JSON files in the config directory.
|
7
|
+
#
|
8
|
+
# @return [Hash]
|
9
|
+
def load_currencies
|
10
|
+
currencies = parse_currency_file("currency_iso.json")
|
11
|
+
# currencies.merge! parse_currency_file("currency_non_iso.json")
|
12
|
+
# currencies.merge! parse_currency_file("currency_backwards_compatible.json")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_currency_file(filename)
|
18
|
+
json = File.read("#{DATA_PATH}/#{filename}")
|
19
|
+
json.force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
|
20
|
+
JSON.parse(json, :symbolize_names => true)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
module TagsHelper
|
3
|
+
# See the README for an example using tag_cloud.
|
4
|
+
def tag_cloud(tags, classes)
|
5
|
+
return if tags.empty?
|
6
|
+
|
7
|
+
max_count = tags.sort_by(&:count).last.count.to_f
|
8
|
+
|
9
|
+
tags.each do |tag|
|
10
|
+
index = ((tag.count / max_count) * (classes.size - 1)).round
|
11
|
+
yield tag, classes[index]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
module ActsAsVotable::Helpers
|
3
|
+
|
4
|
+
# this helper provides methods that help find what words are
|
5
|
+
# up votes and what words are down votes
|
6
|
+
#
|
7
|
+
# It can be called
|
8
|
+
#
|
9
|
+
# votable_object.votable_words.that_mean_true
|
10
|
+
#
|
11
|
+
module Words
|
12
|
+
|
13
|
+
def votable_words
|
14
|
+
VotableWords
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class VotableWords
|
20
|
+
|
21
|
+
def self.that_mean_true
|
22
|
+
['up', 'upvote', 'like', 'liked', 'positive', 'yes', 'good', 'true', 1, true]
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.that_mean_false
|
26
|
+
['down', 'downvote', 'dislike', 'disliked', 'negative', 'no', 'bad', 'false', 0, false]
|
27
|
+
end
|
28
|
+
|
29
|
+
# check is word is a true or bad vote
|
30
|
+
# if the word is unknown, then it counts it as a true/good
|
31
|
+
# vote. this exists to allow all voting to be good by default
|
32
|
+
def self.meaning_of word
|
33
|
+
!that_mean_false.include?(word)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
module Liquid
|
3
|
+
class IssuesDrop < ::Liquid::Drop
|
4
|
+
def initialize(issues)
|
5
|
+
@issues = issues
|
6
|
+
end
|
7
|
+
|
8
|
+
def before_method(id)
|
9
|
+
issue = @issues.where(:id => id).first || Issue.new
|
10
|
+
IssueDrop.new issue
|
11
|
+
end
|
12
|
+
|
13
|
+
def all
|
14
|
+
@all ||= @issues.map do |issue|
|
15
|
+
IssueDrop.new issue
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(&block)
|
20
|
+
all.each(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def size
|
24
|
+
@issues.size
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class IssueDrop < ::Liquid::Drop
|
29
|
+
include ActionView::Helpers::UrlHelper
|
30
|
+
|
31
|
+
delegate :id,
|
32
|
+
:subject,
|
33
|
+
:description,
|
34
|
+
:visible?,
|
35
|
+
:open?,
|
36
|
+
:start_date,
|
37
|
+
:due_date,
|
38
|
+
:overdue?,
|
39
|
+
:completed_percent,
|
40
|
+
:updated_on,
|
41
|
+
:created_on,
|
42
|
+
:to => :@issue
|
43
|
+
|
44
|
+
def initialize(issue)
|
45
|
+
@issue = issue
|
46
|
+
end
|
47
|
+
|
48
|
+
def link
|
49
|
+
link_to @issue.name, self.url
|
50
|
+
end
|
51
|
+
|
52
|
+
def url
|
53
|
+
Rails.application.routes.url_helpers.issue_path(@issue)
|
54
|
+
end
|
55
|
+
|
56
|
+
def author
|
57
|
+
@users ||= UsersDrop.new @issue.author
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
module Liquid
|
3
|
+
class NewssDrop < ::Liquid::Drop
|
4
|
+
def initialize(newss)
|
5
|
+
@newss = newss
|
6
|
+
end
|
7
|
+
|
8
|
+
def before_method(id)
|
9
|
+
news = @newss.where(:id => id).first || News.new
|
10
|
+
NewsDrop.new news
|
11
|
+
end
|
12
|
+
|
13
|
+
def last
|
14
|
+
NewsDrop.new News.last
|
15
|
+
end
|
16
|
+
|
17
|
+
def all
|
18
|
+
@all ||= @newss.map do |news|
|
19
|
+
NewsDrop.new news
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
all.each(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
@newss.size
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class NewsDrop < ::Liquid::Drop
|
33
|
+
|
34
|
+
delegate :id, :title, :summary, :description, :visible?, :commentable?, :to => :@news
|
35
|
+
|
36
|
+
def initialize(news)
|
37
|
+
@news = news
|
38
|
+
end
|
39
|
+
|
40
|
+
def author
|
41
|
+
UserDrop.new @news.author
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
module Liquid
|
3
|
+
class ProjectsDrop < ::Liquid::Drop
|
4
|
+
|
5
|
+
def initialize(projects)
|
6
|
+
@projects = projects
|
7
|
+
end
|
8
|
+
|
9
|
+
def before_method(identifier)
|
10
|
+
project = @projects.where(:identifier => identifier).first || Project.new
|
11
|
+
ProjectDrop.new project
|
12
|
+
end
|
13
|
+
|
14
|
+
def all
|
15
|
+
@all ||= @projects.map do |project|
|
16
|
+
ProjectDrop.new project
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def active
|
21
|
+
@active ||= @projects.select(&:active?).map do |project|
|
22
|
+
ProjectDrop.new project
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
all.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def size
|
31
|
+
@projects.size
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ProjectDrop < ::Liquid::Drop
|
36
|
+
include ActionView::Helpers::UrlHelper
|
37
|
+
|
38
|
+
delegate :id,
|
39
|
+
:identifier,
|
40
|
+
:name,
|
41
|
+
:is_public,
|
42
|
+
:description,
|
43
|
+
:visible?,
|
44
|
+
:active?,
|
45
|
+
:archived?,
|
46
|
+
:short_description,
|
47
|
+
:start_date,
|
48
|
+
:due_date,
|
49
|
+
:overdue?,
|
50
|
+
:completed_percent,
|
51
|
+
:to => :@project
|
52
|
+
|
53
|
+
def initialize(project)
|
54
|
+
@project = project
|
55
|
+
end
|
56
|
+
|
57
|
+
def link
|
58
|
+
link_to @project.name, self.url
|
59
|
+
end
|
60
|
+
|
61
|
+
def url
|
62
|
+
Rails.application.routes.url_helpers.project_path(@project)
|
63
|
+
end
|
64
|
+
|
65
|
+
def issues
|
66
|
+
@issues ||= IssuesDrop.new @project.issues.visible
|
67
|
+
end
|
68
|
+
|
69
|
+
def users
|
70
|
+
@users ||= UsersDrop.new @project.users
|
71
|
+
end
|
72
|
+
|
73
|
+
def subprojects
|
74
|
+
@subprojects ||= ProjectsDrop.new @project.children
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module RedmineCrm
|
2
|
+
module Liquid
|
3
|
+
class UsersDrop < ::Liquid::Drop
|
4
|
+
def initialize(users)
|
5
|
+
@users = users
|
6
|
+
end
|
7
|
+
|
8
|
+
def before_method(login)
|
9
|
+
user = @users.where(:login => login).first || User.new
|
10
|
+
UserDrop.new user
|
11
|
+
end
|
12
|
+
|
13
|
+
def current
|
14
|
+
UserDrop.new User.current
|
15
|
+
end
|
16
|
+
|
17
|
+
def all
|
18
|
+
@all ||= @users.map do |user|
|
19
|
+
UserDrop.new user
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
all.each(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
@users.size
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class UserDrop < ::Liquid::Drop
|
33
|
+
|
34
|
+
delegate :id, :name, :firstname, :lastname, :mail, :active?, :admin?, :logged?, :language, :to => :@user
|
35
|
+
|
36
|
+
def initialize(user)
|
37
|
+
@user = user
|
38
|
+
end
|
39
|
+
|
40
|
+
def avatar
|
41
|
+
ApplicationController.helpers.avatar(@user)
|
42
|
+
end
|
43
|
+
|
44
|
+
def permissions
|
45
|
+
roles = @user.memberships.collect { |m| m.roles }.flatten.uniq
|
46
|
+
roles << (@user.logged? ? Role.non_member : Role.anonymous)
|
47
|
+
roles.map(&:permissions).flatten.uniq.map(&:to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
def groups
|
51
|
+
@user.groups.map(&:name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def projects
|
55
|
+
ProjectsDrop.new @user.memberships.map(&:project).flatten.select(&:visible?).uniq
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|