educode_sales 0.9.68 → 0.9.69

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/educode_sales/indexlogo.png +0 -0
  3. data/app/assets/javascripts/educode_sales/extent/treeTable.js +235 -0
  4. data/app/controllers/educode_sales/customers_controller.rb +107 -0
  5. data/app/controllers/educode_sales/follow_ups_controller.rb +6 -0
  6. data/app/controllers/educode_sales/projects_controller.rb +420 -0
  7. data/app/controllers/educode_sales/roles_controller.rb +4 -1
  8. data/app/controllers/educode_sales/teachers_controller.rb +38 -13
  9. data/app/helpers/educode_sales/application_helper.rb +1 -0
  10. data/app/models/educode_sales/business.rb +21 -5
  11. data/app/models/educode_sales/business_history.rb +8 -0
  12. data/app/models/educode_sales/permission.rb +2 -1
  13. data/app/models/educode_sales/project.rb +53 -0
  14. data/app/models/educode_sales/role_area.rb +2 -1
  15. data/app/views/educode_sales/activities/show_teachers.html.erb +1 -1
  16. data/app/views/educode_sales/businesses/index.html.erb +5 -0
  17. data/app/views/educode_sales/businesses/index.json.jbuilder +3 -0
  18. data/app/views/educode_sales/businesses/time_line.html.erb +9 -5
  19. data/app/views/educode_sales/customers/edit_major.html.erb +872 -0
  20. data/app/views/educode_sales/customers/list.html.erb +602 -0
  21. data/app/views/educode_sales/customers/list.json.jbuilder +33 -0
  22. data/app/views/educode_sales/follow_ups/index.json.jbuilder +2 -2
  23. data/app/views/educode_sales/projects/detail.html.erb +269 -0
  24. data/app/views/educode_sales/projects/edit.html.erb +246 -0
  25. data/app/views/educode_sales/projects/history.html.erb +41 -0
  26. data/app/views/educode_sales/projects/index.html.erb +688 -0
  27. data/app/views/educode_sales/projects/index.json.jbuilder +69 -0
  28. data/app/views/educode_sales/teachers/_index.html.erb +17 -1
  29. data/app/views/educode_sales/teachers/index.json.jbuilder +2 -0
  30. data/app/views/layouts/educode_sales/application.html.erb +25 -0
  31. data/config/routes.rb +12 -0
  32. data/db/migrate/20230411134203_add_realname_to_teacher.rb +5 -0
  33. data/db/migrate/20230412083510_add_p_staff_id_to_businesses.rb +20 -0
  34. data/db/migrate/20230412092000_create_business_histories.rb +10 -0
  35. data/db/migrate/20230413015619_add_permissions_to_projects.rb +11 -0
  36. data/db/migrate/20230413031029_add_pre_time_to_businesses.rb +5 -0
  37. data/lib/educode_sales/version.rb +1 -1
  38. metadata +24 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68edf1ae34edbb71f7c415a1bf1f298683c507681463630bbe54cd9e008a1d18
4
- data.tar.gz: f7dc04cdeab6ff2fe4e696265e5175abd99095dda76fd8dbba2263afa103190e
3
+ metadata.gz: 5de22a63054e2a25d82a48c86687b07143302a4216d3690cb5431fcf17ca7672
4
+ data.tar.gz: b375afaa7854500c9bc3a7dbc6a8f83ef3c86e89bc9d5cf826280fde12102c98
5
5
  SHA512:
6
- metadata.gz: 2fed0873dcabcf99b57d6713f2df0e59efc030ffbc27b23e7cacd02e05e81d42db0e3e571663a3230ee43a3885bcc5f3833f0cf3ed601f1a7136f62ace3a3833
7
- data.tar.gz: 653629bfc15b4d1ad604462f17b61ddf9512e26d9cc0f806177b8048d2f665ec1ff28d2c33191ed9ef1c7167db9ee895c70239493d45bf03dddae4cfbf426eaf
6
+ metadata.gz: 015b587adf4090852c386117efff6d8fd7ea39a3a0bb6191d806cc96a68cb2c83e8cd487de8bcc7a221052fb4ebbe1eb0a67660865ddaadaf8746f4e309273f7
7
+ data.tar.gz: aae61faba2cf647fa61460ba958c89d761d8321a9eaa0bdc66855a8336bdc6552387f7d48f86fb9cd400a6e478ae58c7153e7311c5c85adc3de51f39a95fbc01
@@ -0,0 +1,235 @@
1
+ layui.define(['jquery'], function(exports) {
2
+ var MOD_NAME = 'treeTable',
3
+ o = layui.jquery,
4
+ tree = function() {};
5
+ tree.prototype.config = function() {
6
+ return {
7
+ top_value: 0,
8
+ primary_key: 'id',
9
+ parent_key: 'pid',
10
+ hide_class: 'layui-hide',
11
+ icon: {
12
+ open: 'layui-icon layui-icon-triangle-d',
13
+ close: 'layui-icon layui-icon-triangle-r',
14
+ left: 16,
15
+ },
16
+ cols: [],
17
+ checked: {},
18
+ is_click_icon: false,
19
+ is_checkbox: false,
20
+ is_cache: true,
21
+ };
22
+ }
23
+ tree.prototype.template = function(e) {
24
+ var t = this,
25
+ level = [],
26
+ tbody = '',
27
+ is_table = o('table' + e.elem).length || !(e.is_click_icon = true),
28
+ checkbox = e.is_checkbox ? '<div class="layui-unselect layui-form-checkbox cbx" lay-skin="primary"><i class="layui-icon layui-icon-ok"></i></div>' : '',
29
+ checked = checkbox ? checkbox.replace('cbx', 'cbx layui-form-checked') : '',
30
+ thead = checkbox && '<th style="width:28px;">' + (o.inArray(e.top_value, e.checked.data) > -1 ? checked : checkbox) + '</th>';
31
+ o.each(t.data(e, e.data), function(idx, item) {
32
+ var tr = '',
33
+ is_checked = false,
34
+ hide_class = (item[e.parent_key] == e.top_value) || (item[e.parent_key] == t.cache(e, item[e.parent_key])) ? '' : e.hide_class;
35
+ // 设置每行数据层级
36
+ item.level = level[item[e.primary_key]] = item[e.parent_key] != e.top_value ? (level[item[e.parent_key]] + 1) : 0;
37
+ // 设置是否为最后一级
38
+ item.is_end = !e.childs[item[e.primary_key]];
39
+ o.each(e.cols, function(index, obj) {
40
+ var style = '';
41
+ obj.width && (style += 'width:' + obj.width + ';'), obj.align && (style += 'text-align:' + obj.align + ';'), style && (style = 'style="' + style + '"');
42
+ // 标记设置行checkbox选中
43
+ if(e.is_checkbox && e.checked && o.inArray(item[e.checked.key], e.checked.data) > -1) {
44
+ is_checked = true;
45
+ }
46
+ // 第一次遍历头部的时候拼接表格头部
47
+ idx || (thead += '<th ' + style + '>' + obj.title + '</th>');
48
+ // 指定列加入开启、关闭小图标
49
+ var icon = (obj.key == e.icon_key && !item.is_end) ? '<i class="' + (t.cache(e, item[e.primary_key]) ? e.icon.open : e.icon.close) + '"></i>' : '<span></span>';
50
+ // 指定列小图标按照层级向后位移
51
+ var left = (obj.key == e.icon_key ? level[item[e.primary_key]] * e.icon.left + 'px' : '');
52
+ icon = icon.replace('>', ' style="margin-left:' + left + ';">');
53
+ // 拼接行
54
+ tr += '<td ' + style + (left ? 'data-down' : '') + '>' + icon + (is_table ? '' : (is_checked ? checked : checkbox)) + (obj.template ? obj.template(item) : item[obj.key]) + '</td>';
55
+ });
56
+ var box = is_table ? o(is_checked ? checked : checkbox).wrap('<td style="width:28px;">').parent().prop('outerHTML') : '';
57
+ tbody += '<tr class="' + hide_class + '" data-id="' + item[e.primary_key] + '" data-pid="' + item[e.parent_key] + '">' + box + tr + '</tr>';
58
+ });
59
+ // 处理表树和树的赋值模板
60
+ var table = is_table ? '<thead><tr data-id="' + e.top_value + '">' + thead + '</tr></thead><tbody>' + tbody + '</tbody>' : tbody.replace(/<tr/g, '<ul').replace(/tr>/g, 'ul>').replace(/<td/g, '<li').replace(/td>/g, 'li>');
61
+ // 确认点击图标或点击列触发展开关闭
62
+ var click_btn = e.is_click_icon ? '[data-down] i:not(.layui-icon-ok)' : '[data-down]';
63
+ // 模板渲染并处理点击展开收起等功能
64
+ o(e.elem).html(table).off('click', click_btn).on('click', click_btn, function() {
65
+ var tr = o(this).parents('[data-id]'),
66
+ td = tr.find('[data-down]'),
67
+ id = tr.data('id'),
68
+ pid = tr.data('pid'),
69
+ is_open = (td.find('i:not(.layui-icon-ok)').attr('class') == e.icon.close);
70
+ if(is_open) {
71
+ // 展开子级(子级出现、更改图标)
72
+ td.find('i:not(.layui-icon-ok)').attr('class', e.icon.open);
73
+ td.parents(e.elem).find('[data-pid=' + id + ']').removeClass(e.hide_class);
74
+ t.cache(e, id, true);
75
+ } else {
76
+ // 关闭子级(更改图标、隐藏所有子孙级)
77
+ td.find('i:not(.layui-icon-ok)').attr('class', e.icon.close);
78
+ t.childs_hide(e, id);
79
+ }
80
+ // 设置监听展开关闭
81
+ layui.event.call(this, MOD_NAME, 'tree(flex)', {
82
+ elem: this,
83
+ item: e.childs[pid][id],
84
+ table: e.elem,
85
+ is_open: is_open,
86
+ })
87
+ }).off('click', '.cbx').on('click', '.cbx', function() {
88
+ var is_checked = o(this).toggleClass('layui-form-checked').hasClass('layui-form-checked'),
89
+ tr = o(this).parents('[data-id]'),
90
+ id = tr.data('id'),
91
+ pid = tr.data('pid');
92
+ t.childs_checkbox(e, id, is_checked);
93
+ t.parents_checkbox(e, pid);
94
+ // 设置监听checkbox选择
95
+ layui.event.call(this, MOD_NAME, 'tree(box)', {
96
+ elem: this,
97
+ item: pid === undefined ? {} : e.childs[pid][id],
98
+ table: e.elem,
99
+ is_checked: is_checked,
100
+ })
101
+ }).off('click', '[lay-filter]').on('click', '[lay-filter]', function() {
102
+ var tr = o(this).parents('[data-id]'),
103
+ id = tr.data('id'),
104
+ pid = tr.data('pid'),
105
+ filter = o(this).attr("lay-filter");
106
+ return layui.event.call(this, MOD_NAME, 'tree(' + filter + ')', {
107
+ elem: this,
108
+ item: e.childs[pid][id],
109
+ })
110
+ });
111
+ e.end && e.end(e);
112
+ };
113
+ // 同级全部选中父级选中/同级全部取消取消父级
114
+ tree.prototype.parents_checkbox = function(e, pid) {
115
+ var po = o(e.elem).find('[data-pid=' + pid + ']'),
116
+ co = o(e.elem).find('[data-id=' + pid + ']'),
117
+ len = o(e.elem).find('[data-pid=' + pid + '] .cbx.layui-form-checked').length;
118
+ if(po.length == len || len == 0) {
119
+ var pid = co.data('pid');
120
+ len ? co.find('.cbx').addClass('layui-form-checked') : co.find('.cbx').removeClass('layui-form-checked');
121
+ pid === undefined || this.parents_checkbox(e, pid);
122
+ }
123
+ };
124
+ // 子级反选
125
+ tree.prototype.childs_checkbox = function(e, id, is_checked) {
126
+ var t = this;
127
+ o(e.elem).find('[data-pid=' + id + ']').each(function() {
128
+ var checkbox = o(this).find('.cbx');
129
+ is_checked ? checkbox.addClass('layui-form-checked') : checkbox.removeClass('layui-form-checked');
130
+ t.childs_checkbox(e, o(this).data('id'), is_checked);
131
+ })
132
+ };
133
+ // 点击收起循环隐藏子级元素
134
+ tree.prototype.childs_hide = function(e, id) {
135
+ var t = this;
136
+ t.cache(e, id, false);
137
+ o(e.elem).find('[data-pid=' + id + ']:not(.' + e.hide_class + ')').each(function() {
138
+ var td = o(this).find('[data-down]'),
139
+ i = td.find('i:not(.layui-icon-ok)');
140
+ // 关闭更换小图标
141
+ i.length && i.attr('class', e.icon.close);
142
+ // 隐藏子级
143
+ td.parents(e.elem).find('[data-pid=' + id + ']').addClass(e.hide_class);
144
+ t.childs_hide(e, o(this).data('id'))
145
+ });
146
+ };
147
+ // 重新组合数据,父子级关系跟随
148
+ tree.prototype.data = function(e) {
149
+ var lists = [],
150
+ childs = [];
151
+ o.each(e.data, function(idx, item) {
152
+ lists[item[e.primary_key]] = item;
153
+ if(!childs[item[e.parent_key]]) {
154
+ childs[item[e.parent_key]] = [];
155
+ }
156
+ childs[item[e.parent_key]][item[e.primary_key]] = item;
157
+ });
158
+ e.childs = childs;
159
+ return this.tree_data(e, lists, e.top_value, []);
160
+ };
161
+ tree.prototype.tree_data = function(e, lists, pid, data) {
162
+ var t = this;
163
+ if(lists[pid]) {
164
+ data.push(lists[pid]);
165
+ delete lists[pid]
166
+ }
167
+ o.each(e.data, function(index, item) {
168
+ if(item[e.parent_key] == pid) {
169
+ data.concat(t.tree_data(e, lists, item[e.primary_key], data))
170
+ }
171
+ });
172
+ return data;
173
+ };
174
+ tree.prototype.render = function(e) {
175
+ var t = this;
176
+ e = o.extend(t.config(), e);
177
+ if(e.url) {
178
+ o.get(e.url, function(res) {
179
+ e.data = res;
180
+ t.template(e);
181
+ })
182
+ } else {
183
+ t.template(e);
184
+ }
185
+ return e;
186
+ };
187
+ // 获取已选值集合
188
+ tree.prototype.checked = function(e) {
189
+ var ids = [];
190
+ o(e.elem).find('.cbx.layui-form-checked').each(function() {
191
+ var id = o(this).parents('[data-id]').data('id');
192
+ ids.push(id);
193
+ })
194
+ return ids;
195
+ };
196
+ // 全部展开
197
+ tree.prototype.openAll = function(e) {
198
+ var t = this;
199
+ o.each(e.data, function(idx, item) {
200
+ item[e.primary_key] && t.cache(e, item[e.primary_key], true);
201
+ })
202
+ t.render(e);
203
+ }
204
+ // 全部关闭
205
+ tree.prototype.closeAll = function(e) {
206
+ localStorage.setItem(e.elem.substr(1), '');
207
+ this.render(e);
208
+ }
209
+ tree.prototype.on = function(events, callback) {
210
+ return layui.onevent.call(this, MOD_NAME, events, callback)
211
+ };
212
+ // 存储折叠状态
213
+ tree.prototype.cache = function(e, val, option) {
214
+ if(!e.is_cache) {
215
+ return false;
216
+ }
217
+ var t = this,
218
+ name = e.elem.substr(1),
219
+ val = val.toString(),
220
+ cache = localStorage.getItem(name) ? localStorage.getItem(name).split(',') : [],
221
+ index = o.inArray(val, cache);
222
+ if(option === undefined) {
223
+ return index == -1 ? false : val
224
+ }
225
+ if(option && index == -1) {
226
+ cache.push(val)
227
+ }
228
+ if(!option && index > -1) {
229
+ cache.splice(index, 1)
230
+ }
231
+ localStorage.setItem(name, cache.join(','));
232
+ };
233
+ var tree = new tree();
234
+ exports(MOD_NAME, tree)
235
+ });
@@ -156,6 +156,97 @@ module EducodeSales
156
156
  end
157
157
  end
158
158
 
159
+ def list
160
+ authorize! :read, Customer
161
+ respond_to do |format|
162
+ format.html do
163
+ common = Common.find_by(clazz: 'staff_type', name: '销售')
164
+ @staffs = Staff.joins(:user).where(job_type: common.id).map { |d| [d.user.real_name, d.id] }
165
+ gon.school_tags = SchoolTag.where(for_missions: true).map { |d| {value: d.id, name: d.name } }
166
+ end
167
+ format.json do
168
+ if @current_admin.is_admin?
169
+ @customers = School.all
170
+ else
171
+ level = @current_admin.role.role_areas.find_by(clazz: '客户管理').level
172
+ case level
173
+ when '自己'
174
+ school_ids = CustomerExtension.where(customer_staff_id: @current_admin.id).pluck(:school_id)
175
+ @customers = School.where(id: school_ids)
176
+ when '区域'
177
+ a_school_ids = School.where(province: @current_admin.areas.pluck(:name)).ids
178
+ b_school_ids = CustomerExtension.where(customer_staff_id: @current_admin.id).pluck(:school_id)
179
+ school_ids = a_school_ids + b_school_ids + StaffSchool.where(staff_id: @current_admin.id).pluck(:school_id)
180
+ @customers = School.where(id: school_ids)
181
+ else
182
+ @customers = School.all
183
+ end
184
+ end
185
+
186
+ @customers = @customers.select("
187
+ schools.*,
188
+ departments.id AS department_id,
189
+ departments.name AS department_name,
190
+ department_majors.name AS major,
191
+ department_majors.id AS major_id
192
+ ")
193
+
194
+ @customers = @customers.joins("
195
+ LEFT JOIN departments ON schools.id = departments.school_id
196
+ LEFT JOIN department_majors ON department_majors.department_id = departments.id
197
+ ")
198
+ part_a_ids = CustomerFollow.all.pluck(:school_id)
199
+ part_b_ids = Business.where(id: EducodeSales::FollowUp.pluck(:business_id)).pluck(:school_id)
200
+ ids = part_a_ids + part_b_ids + CustomerAdd.all.pluck(:school_id)
201
+ @customers = @customers.where(id: ids)
202
+ if params[:q].present? && params[:q][:name].present?
203
+ @customers = @customers.where("schools.name like ?", "%#{params[:q][:name]}%")
204
+ end
205
+ if params[:q].present? && params[:q][:area].present?
206
+ @customers = @customers.where("schools.province = ?", "#{params[:q][:area]}")
207
+ end
208
+ if params[:q].present? && params[:q][:staff_id].present?
209
+ school_ids = EducodeSales::CustomerExtension.where(customer_staff_id: params[:q][:staff_id]).pluck(:school_id)
210
+ @customers = @customers.where(id: school_ids)
211
+ end
212
+ if params[:q].present? && params[:q][:property].present?
213
+ property = params[:q][:property].split(",").map(&:to_i)
214
+ @customers = @customers.joins(:school_tags).where("school_tags.id in (?)", property).distinct
215
+ end
216
+
217
+ if params[:q].present? && params[:q][:date].present?
218
+ ids = EducodeSales::CustomerFollow.all.pluck(:school_id)
219
+ @customers = @customers.where(id: ids)
220
+ school_ids = []
221
+ date = params[:q][:date].split(" - ")
222
+ @customers.each do |d|
223
+ business_ids = EducodeSales::Business.where(school_id: d.id).ids
224
+ follow_ups = EducodeSales::FollowUp.where(business_id: business_ids)
225
+ customer_follows = EducodeSales::CustomerFollow.where(school_id: d.id)
226
+ next if follow_ups.blank? && customer_follows.blank?
227
+ a_last_follow_time = follow_ups.last&.created_at&.to_s
228
+ b_last_follow_time = customer_follows.last&.created_at&.to_s
229
+ if a_last_follow_time.present? && b_last_follow_time.present?
230
+ last_follow_time = ((a_last_follow_time < b_last_follow_time) ? b_last_follow_time : a_last_follow_time)
231
+ else
232
+ last_follow_time = (a_last_follow_time || b_last_follow_time)
233
+ end
234
+ if last_follow_time.present? && last_follow_time > date[0] && last_follow_time < date[1]
235
+ school_ids << d.id
236
+ end
237
+ end
238
+ @customers = @customers.where(id: school_ids)
239
+ end
240
+
241
+ if params[:page].present?
242
+ @customers = @customers.order(id: :desc).page(params[:page]).per(params[:limit])
243
+ else
244
+ @customers = @customers.order(id: :desc)
245
+ end
246
+ end
247
+ end
248
+ end
249
+
159
250
  def update_major
160
251
  department = Department.find(params[:id])
161
252
  major = department.department_majors.find(params[:major_id])
@@ -256,6 +347,18 @@ module EducodeSales
256
347
  render layout: false
257
348
  end
258
349
 
350
+ def edit_major
351
+ @school = School.find(params[:id])
352
+ @school_property = @school.school_property
353
+ if params[:major].present?
354
+ @major = DepartmentMajor.find_by(id: params[:major])
355
+ end
356
+ if params[:department_id].present?
357
+ @department = Department.find_by(id: params[:department_id])
358
+ end
359
+ render layout: false
360
+ end
361
+
259
362
  def batch_update_school_tags
260
363
  xlsx = Roo::Spreadsheet.open(params[:file])
261
364
  ods = xlsx.sheet(0)
@@ -288,6 +391,10 @@ module EducodeSales
288
391
 
289
392
  def update
290
393
  @school = School.find(params[:id])
394
+ if params[:department_id].present?
395
+ department = @school.departments.find(params[:department_id]).update(name: params[:department])
396
+ end
397
+
291
398
  ActiveRecord::Base.transaction do
292
399
  property = SchoolProperty.find_or_create_by!(project_985: params[:project_985].present? ? 1 : 0,
293
400
  project_211: params[:project_211].present? ? 1 : 0,
@@ -257,6 +257,12 @@ module EducodeSales
257
257
  render_success
258
258
  end
259
259
 
260
+ def comment
261
+ followup = FollowUp.find(params[:id])
262
+ followup.update(comment: params[:comment])
263
+ render_success
264
+ end
265
+
260
266
  private
261
267
 
262
268
  def load_business