educode_sales 0.9.82 → 0.9.84

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5c4108bbab976a92d5f43a438e6d39e16331c222a0046a45745e6730e751a87
4
- data.tar.gz: 7e13154cfed9228addd477ffd99ae03ba8d0defcba553f586078ad440035f7d8
3
+ metadata.gz: e498fa041334cb4b387425a7a7f7ff49dffffcee5a72e363b61cb9f32167ff06
4
+ data.tar.gz: 54d79d72be99cab109f9b40fdd4dd789f72f20d115663abd7d99db70f0ee2527
5
5
  SHA512:
6
- metadata.gz: 40591e809b707f2c944e7662373e3d3160eecd854407aa900fc520c2a498a681c1e75e5c012ecab902debaefa41bcb636bdcb9a5717100fcfb5ba25bf3cc4737
7
- data.tar.gz: e7c0c20ceae1b0fbadd8547cd6cf209e762bbe1b451d8659c20ff59b0f1244b94861f066134e4e7cf71fbe213f5e523cb64d5b3a52acdd99bd79d03e7d245de4
6
+ metadata.gz: 1e29ad8d3c8dd8c0753dc6662e26c332444cbf10842d308977883c47149c676b0b1c06c515bcfc33e10fe0bfccbc1cecb5ab1c7f2534001c0c1d7a11042a5def
7
+ data.tar.gz: 5077f36f7bb732c196608fd15c696c06b3d0cf5c74e32c24495b67eadc8b5527eacb1a545966aeea4e3401410ed0e1fbe23c2fb60aea58a8b5513fd1fd570ba6
@@ -233,12 +233,14 @@ module EducodeSales
233
233
  @business = Business.find(params[:id])
234
234
  @last_follow_up = @business.last_follow_up
235
235
  @deployment_type = EducodeSales::FollowUp::BUSINESS_DEPLOYMENT
236
+ @signed_clazz = EducodeSales::FollowUp.signed_clazzs.keys
237
+
236
238
  signed_department = @last_follow_up.signed_department
237
239
  if signed_department
238
- gon.department = {value: signed_department.id, name: "#{signed_department&.school&.name}-#{signed_department&.name}"}
240
+ gon.department = [{value: signed_department.id, name: "#{signed_department&.school&.name}-#{signed_department&.name}"}]
239
241
  gon.value = signed_department.id
240
242
  else
241
- gon.department = {}
243
+ gon.department = []
242
244
  gon.value = ''
243
245
  end
244
246
 
@@ -454,7 +456,7 @@ module EducodeSales
454
456
 
455
457
  private
456
458
  def follow_up_params
457
- params.permit(:o_business_deployment, :plan_signed_date, :bidded_days, :signed_date, :signed_department_id, :service_years, :service_start_time, :service_end_time, :funding_source)
459
+ params.permit(:o_business_deployment, :plan_signed_date, :bidded_days, :signed_date, :signed_department_id, :service_years, :service_start_time, :service_end_time, :funding_source, :signed_clazz)
458
460
  end
459
461
 
460
462
  def product_up_params
@@ -3,7 +3,7 @@ require_dependency "educode_sales/application_controller"
3
3
  module EducodeSales
4
4
  class SaleTrendsController < ApplicationController
5
5
  # authorize_resource class: false
6
-
6
+ include SaleTrendsHelper
7
7
  def trends
8
8
  # authorize! :trends, EducodeSales::SaleTrend
9
9
  @year = params[:year] ? params[:year] : Time.now.year
@@ -382,6 +382,64 @@ module EducodeSales
382
382
  end
383
383
  end
384
384
 
385
+ def goal_forecast
386
+ respond_to do |format|
387
+ format.html do
388
+
389
+ end
390
+ format.js do
391
+ staff_id = params[:staff_id] || nil
392
+ @forecast_count_range = params[:forecast_count_range] || "month"
393
+ forecast_default_dates = ("#{Time.now.year}-01-01".to_date.."#{Time.now.year}-#{Time.now.month}-01".to_date).map { |d| d.strftime("%Y-%m") }.uniq
394
+ sale_names = %w[计划投标额 计划投标额累计 实际中标额 实际中标额累计]
395
+ case @forecast_count_range
396
+ when "week"
397
+ # 年初第一天周数是0会导致周数重复计算
398
+ if params[:forecast_date_day].present?
399
+ date = params[:forecast_date_day].split(" - ")
400
+ dates = (date[0].to_date..date[1].to_date).map { |d| d.strftime("%Y-%W") }.uniq.select { |d| d.split("-")[1] != '00' }
401
+ @forecast_count_data = goal_forecast_week(dates, sale_names, staff_id)
402
+ else
403
+ forecast_default_dates = ("#{Time.now.year}-01-01".to_date.."#{Time.now.year}-#{Time.now.month}-01".to_date).map { |d| d.strftime("%Y-%W") }.uniq.select { |d| d.split("-")[1] != '00' }
404
+ @forecast_count_data = goal_forecast_week(forecast_default_dates, sale_names, staff_id)
405
+ end
406
+ when "quarter"
407
+ # 按年
408
+ if params[:forecast_date_year].present?
409
+ date = params[:forecast_date_year].split(" - ")
410
+ dates = (date[0]..date[1]).to_a
411
+ dates = dates.map { |d| [d.to_s + "-" + "1", d.to_s + "-" + "2", d.to_s + "-" + "3", d.to_s + "-" + "4"] }.flatten
412
+ @forecast_count_data = goal_forecast_quarter(dates, sale_names, staff_id)
413
+ else
414
+ forecast_default_dates = ("#{Time.now.year}-01-01".to_date.."#{Time.now.year}-#{Time.now.month}-01".to_date).map { |d| d.strftime("%Y") }.uniq
415
+ forecast_default_dates = forecast_default_dates.map { |d| [d.to_s + "-" + "1", d.to_s + "-" + "2", d.to_s + "-" + "3", d.to_s + "-" + "4"] }.flatten
416
+ @forecast_count_data = goal_forecast_quarter(forecast_default_dates, sale_names, staff_id)
417
+ end
418
+ when "month" # 按月
419
+ if params[:forecast_date_month].present?
420
+ date = params[:forecast_date_month].split(" - ")
421
+ date[0] = (date[0] + "-01").to_date.to_s
422
+ date[1] = (date[1] + "-01").to_date.end_of_month.to_s
423
+ dates = ((date[0] + "-01").to_date..(date[1] + "-01").to_date).map { |d| d.strftime("%Y-%m") }.uniq
424
+ @forecast_count_data = goal_forecast_month(dates, sale_names, staff_id)
425
+ else
426
+ @forecast_count_data = goal_forecast_month(forecast_default_dates, sale_names, staff_id)
427
+ end
428
+ else
429
+ # 按年
430
+ if params[:forecast_date_year].present?
431
+ date = params[:forecast_date_year].split(" - ")
432
+ dates = (date[0]..date[1]).to_a
433
+ @forecast_count_data = goal_forecast_year(dates, sale_names, staff_id)
434
+ else
435
+ forecast_default_dates = ("#{Time.now.year}-01-01".to_date.."#{Time.now.year}-#{Time.now.month}-01".to_date).map { |d| d.strftime("%Y") }.uniq
436
+ @forecast_count_data = goal_forecast_year(forecast_default_dates, sale_names, staff_id)
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end
442
+
385
443
  private
386
444
 
387
445
  def sale_trend_params
@@ -390,7 +448,7 @@ module EducodeSales
390
448
 
391
449
  def business_followup_charts(dates, names, x_business_ids, colors, date, clazz)
392
450
  not_business = x_business_ids.present? ? x_business_ids.join(',') : ''
393
- staff_ids = names.present? ? names.map { |d| d[1]}.join(",") : ''
451
+ staff_ids = names.present? ? names.map { |d| d[1] }.join(",") : ''
394
452
 
395
453
  sql = "SELECT DATE_FORMAT(created_at, '#{clazz}') as week, count(*) as counts, staff_id
396
454
  FROM educode_sales_follow_ups
@@ -0,0 +1,6 @@
1
+ require_dependency "educode_sales/application_controller"
2
+
3
+ module EducodeSales
4
+ class SalesDetailsController < ApplicationController
5
+ end
6
+ end
@@ -0,0 +1,310 @@
1
+ module EducodeSales
2
+ module SaleTrendsHelper
3
+ NAMES = %w[计划投标额 计划投标额累计 实际中标额 实际中标额累计]
4
+ def goal_forecast_quarter(labels, selects, staff_id = nil)
5
+ plan_get = plan_get(staff_id, labels, "quarter")
6
+ actual_get = actual_get(staff_id, labels, "quarter")
7
+ datasets = selects.map.with_index do |select, i|
8
+ data = if select == NAMES[0]
9
+ labels.map do |d|
10
+ quarter = d.split("-").last.to_i
11
+ year = d.split("-").first.to_i
12
+ plan_get[[year, quarter]]&.pluck(:budget_amount)&.reject(&:blank?)&.sum.to_f
13
+ end
14
+ elsif select == NAMES[1]
15
+ arr = labels.map do |d|
16
+ quarter = d.split("-").last.to_i
17
+ year = d.split("-").first.to_i
18
+ plan_get[[year, quarter]]&.pluck(:budget_amount)&.reject(&:blank?)&.sum.to_f
19
+ end
20
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
21
+ elsif select == NAMES[2]
22
+ labels.map do |d|
23
+ quarter = d.split("-").last.to_i
24
+ year = d.split("-").first.to_i
25
+ actual_get[[year, quarter]]&.pluck(:actual_amount)&.reject(&:blank?)&.sum.to_f
26
+ end
27
+ elsif select == NAMES[3]
28
+ arr = labels.map do |d|
29
+ quarter = d.split("-").last.to_i
30
+ year = d.split("-").first.to_i
31
+ actual_get[[year, quarter]]&.pluck(:actual_amount)&.reject(&:blank?)&.sum.to_f
32
+ end
33
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
34
+ end
35
+ type = if select == NAMES[3] || select == NAMES[1]
36
+ 'line'
37
+ else
38
+ "bar"
39
+ end
40
+ backgroundColor = if select == NAMES[3] || select == NAMES[1]
41
+ 'rgba(54, 162, 235, 0.2)'
42
+ else
43
+ SaleTrend::COLORS[i]
44
+ end
45
+ {
46
+ type: type,
47
+ label: select,
48
+ data: data,
49
+ backgroundColor:
50
+ backgroundColor,
51
+ borderColor:
52
+ SaleTrend::COLORS[i],
53
+ borderWidth: 1
54
+ }
55
+ end
56
+ {
57
+ labels: labels,
58
+ datasets: datasets
59
+ }
60
+ end
61
+
62
+ def goal_forecast_week(labels, selects, staff_id = nil)
63
+
64
+ plan_get = plan_get(staff_id, labels, "week")
65
+ actual_get = actual_get(staff_id, labels, "week")
66
+ datasets = selects.map.with_index do |select, i|
67
+ data = if select == NAMES[0]
68
+ labels.map do |d|
69
+ week = d.split("-").last
70
+ year = d.split("-").first
71
+ yearweek = "#{year}#{week}".to_i
72
+ plan_get[yearweek].to_f
73
+ end
74
+ elsif select == NAMES[1]
75
+ arr = labels.map do |d|
76
+ week = d.split("-").last
77
+ year = d.split("-").first
78
+ yearweek = "#{year}#{week}".to_i
79
+ plan_get[yearweek].to_f
80
+ end
81
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
82
+ elsif select == NAMES[2]
83
+ labels.map do |d|
84
+ week = d.split("-").last
85
+ year = d.split("-").first
86
+ yearweek = "#{year}#{week}".to_i
87
+ actual_get[yearweek].to_f
88
+ end
89
+ elsif select == NAMES[3]
90
+ arr = labels.map do |d|
91
+ week = d.split("-").last
92
+ year = d.split("-").first
93
+ yearweek = "#{year}#{week}".to_i
94
+ actual_get[yearweek].to_f
95
+ end
96
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
97
+ end
98
+ type = if select == NAMES[0] || select == NAMES[2]
99
+ 'bar'
100
+ else
101
+ "line"
102
+ end
103
+ backgroundColor = if select == NAMES[0] || select == NAMES[2]
104
+ SaleTrend::COLORS[i]
105
+ else
106
+ 'rgba(54, 162, 235, 0.2)'
107
+ end
108
+ {
109
+ type: type,
110
+ label: select,
111
+ data: data,
112
+ backgroundColor:
113
+ backgroundColor,
114
+ borderColor:
115
+ SaleTrend::COLORS[i],
116
+ borderWidth: 1
117
+ }
118
+ end
119
+
120
+ {
121
+ labels: labels,
122
+ datasets: datasets
123
+ }
124
+ end
125
+
126
+ def goal_forecast_month(labels, selects, staff_id = nil)
127
+ plan_get = plan_get(staff_id, labels, "month")
128
+ actual_get = actual_get(staff_id, labels, "month")
129
+ datasets = selects.map.with_index do |select, i|
130
+ data = if select == NAMES[0]
131
+ labels.map do |d|
132
+ month = d.split("-").last.to_i
133
+ year = d.split("-").first.to_i
134
+ plan_get[[year, month]]&.pluck(:budget_amount)&.reject(&:blank?)&.sum.to_f
135
+ end
136
+ elsif select == NAMES[1]
137
+ arr = labels.map do |d|
138
+ month = d.split("-").last.to_i
139
+ year = d.split("-").first.to_i
140
+ plan_get[[year, month]]&.pluck(:budget_amount)&.reject(&:blank?)&.sum.to_f
141
+ end
142
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
143
+ elsif select == NAMES[2]
144
+ labels.map do |d|
145
+ month = d.split("-").last.to_i
146
+ year = d.split("-").first.to_i
147
+ actual_get[[year, month]]&.pluck(:actual_amount)&.reject(&:blank?)&.sum.to_f
148
+ end
149
+ elsif select == NAMES[3]
150
+ arr = labels.map do |d|
151
+ month = d.split("-").last.to_i
152
+ year = d.split("-").first.to_i
153
+ actual_get[[year, month]]&.pluck(:actual_amount)&.reject(&:blank?)&.sum.to_f
154
+ end
155
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
156
+ end
157
+
158
+ type = if select == NAMES[3] || select == NAMES[1]
159
+ 'line'
160
+ else
161
+ "bar"
162
+ end
163
+ backgroundColor = if select == NAMES[3] || select == NAMES[1]
164
+ 'rgba(54, 162, 235, 0.2)'
165
+ else
166
+ SaleTrend::COLORS[i]
167
+ end
168
+ {
169
+ type: type,
170
+ label: select,
171
+ data: data,
172
+ backgroundColor:
173
+ backgroundColor,
174
+ borderColor:
175
+ SaleTrend::COLORS[i],
176
+ borderWidth: 1
177
+ }
178
+ end
179
+
180
+ {
181
+ labels: labels,
182
+ datasets: datasets
183
+ }
184
+ end
185
+
186
+ def goal_forecast_year(labels, selects, staff_id = nil)
187
+ plan_get = plan_get(staff_id, labels, "year")
188
+ actual_get = actual_get(staff_id, labels, "year")
189
+ datasets = selects.map.with_index do |select, i|
190
+ data = if select == NAMES[0]
191
+ labels.map { |d| plan_get[d.to_i] || 0 }
192
+ elsif select == NAMES[1]
193
+ arr = labels.map { |d| plan_get[d.to_i] || 0 }
194
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
195
+ elsif select == NAMES[2]
196
+ labels.map { |d| actual_get[d.to_i] || 0 }
197
+ elsif select == NAMES[3]
198
+ arr = labels.map { |d| actual_get[d.to_i] || 0 }
199
+ arr.map.with_index(1) { |num, i| arr.first(i).inject(:+) }
200
+ end
201
+
202
+ type = if select == NAMES[3] || select == NAMES[1]
203
+ 'line'
204
+ else
205
+ "bar"
206
+ end
207
+ backgroundColor = if select == NAMES[3] || select == NAMES[1]
208
+ 'rgba(54, 162, 235, 0.2)'
209
+ else
210
+ SaleTrend::COLORS[i]
211
+ end
212
+ {
213
+ type: type,
214
+ label: select,
215
+ data: data,
216
+ backgroundColor:
217
+ backgroundColor,
218
+ borderColor:
219
+ SaleTrend::COLORS[i],
220
+ borderWidth: 1
221
+ }
222
+ end
223
+ {
224
+ labels: labels,
225
+ datasets: datasets
226
+ }
227
+ end
228
+
229
+ private
230
+
231
+ def plan_get(staff_id, time_range, type)
232
+ # budget_amount 预算额
233
+ start_time = case type
234
+ when "month"
235
+ time_range.first + "-01"
236
+ when "year"
237
+ time_range.first + "-01-01"
238
+ when "week"
239
+ time_range.first.split("-").first + "-01-01"
240
+ when "quarter"
241
+ time_range.first.split("-").first + "-01-01"
242
+ end
243
+ end_time = case type
244
+ when "month"
245
+ time_range.last + "-31"
246
+ when "year"
247
+ time_range.last + "-12-31"
248
+ when "week"
249
+ time_range.last.split("-").first + "-12-31"
250
+ when "quarter"
251
+ time_range.last.split("-").first + "-12-31"
252
+ end
253
+ staff_id = staff_id.present? ? Array(staff_id) : nil
254
+ data = Business.joins(:last_follow_up)
255
+ .where("educode_sales_follow_ups.invitation_at >= ? and educode_sales_follow_ups.invitation_at <= ? #{staff_id.present? ? "AND educode_sales_follow_ups.staff_id in (#{staff_id.join(",")})" : ""}", start_time, end_time)
256
+ case type
257
+ when "week"
258
+ data.select("educode_sales_follow_ups.*, yearweek(educode_sales_follow_ups.invitation_at) as week").group("yearweek(educode_sales_follow_ups.invitation_at)").sum(:budget_amount)
259
+ when "quarter"
260
+ data = data.select("educode_sales_follow_ups.*, quarter(educode_sales_follow_ups.invitation_at) as quarter, year(educode_sales_follow_ups.invitation_at) as year")
261
+ data.group_by { |d| [d.year, d.quarter] }
262
+ when "month"
263
+ data = data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.invitation_at) as year, month(educode_sales_follow_ups.invitation_at) as month")
264
+ data.group_by { |d| [d.year, d.month] }
265
+ when "year"
266
+ data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.invitation_at) as year").group("year(educode_sales_follow_ups.invitation_at)").sum(:budget_amount)
267
+ end
268
+ end
269
+
270
+ def actual_get(staff_id, time_range, type)
271
+ start_time = case type
272
+ when "month"
273
+ time_range.first + "-01"
274
+ when "year"
275
+ time_range.first + "-01-01"
276
+ when "week"
277
+ time_range.first.split("-").first + "-01-01"
278
+ when "quarter"
279
+ time_range.first.split("-").first + "-01-01"
280
+ end
281
+ end_time = case type
282
+ when "month"
283
+ time_range.last + "-31"
284
+ when "year"
285
+ time_range.last + "-12-31"
286
+ when "week"
287
+ time_range.last.split("-").first + "-12-31"
288
+ when "quarter"
289
+ time_range.last.split("-").first + "-12-31"
290
+ end
291
+ # actual_amount 合同额
292
+ staff_id = staff_id.present? ? Array(staff_id) : nil
293
+ data = Business.joins(:last_follow_up)
294
+ .where("educode_sales_follow_ups.bidded_date >= ? and educode_sales_follow_ups.bidded_date <= ? #{staff_id.present? ? "AND educode_sales_follow_ups.staff_id in (#{staff_id.join(",")})" : ""}", start_time, end_time)
295
+ case type
296
+ when "week"
297
+ data.select("educode_sales_follow_ups.*, yearweek(educode_sales_follow_ups.bidded_date) as week").group("yearweek(educode_sales_follow_ups.bidded_date)").sum(:actual_amount)
298
+ when "quarter"
299
+ data = data.select("educode_sales_follow_ups.*, quarter(educode_sales_follow_ups.bidded_date) as quarter, year(educode_sales_follow_ups.bidded_date) as year")
300
+ data.group_by { |d| [d.year, d.quarter] }
301
+ when "month"
302
+ data = data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.bidded_date) as year, month(educode_sales_follow_ups.bidded_date) as month")
303
+ data.group_by { |d| [d.year, d.month] }
304
+ when "year"
305
+ data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.bidded_date) as year").group("year(educode_sales_follow_ups.bidded_date)").sum(:actual_amount)
306
+ end
307
+ end
308
+
309
+ end
310
+ end
@@ -10,6 +10,8 @@ module EducodeSales
10
10
 
11
11
  InfoClazz = ['跟进信息', '建议信息', '评论信息', '审核信息']
12
12
 
13
+ enum signed_clazz: ['直签', '代签', '总集', '渠道']
14
+
13
15
  enum funding_source: ['贷款资金', '财政资金', '自由资金']
14
16
 
15
17
  include ::Deletable
@@ -2,12 +2,12 @@ module EducodeSales
2
2
  class SaleTrend < ApplicationRecord
3
3
 
4
4
  COLORS = [
5
- '#44D7B6', '#4CACFF', '#F7B500', '#FF0000', '#FF7F00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF',
6
- '#8B00FF', 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)',
5
+ '#44D7B6', '#7CFC00', '#4CACFF', "#110FD7", '#F7B500', '#48D1CC', '#FF0000', '#FF7F00', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF',
6
+ '#8B00FF', 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)',
7
7
  'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)', 'rgba(255, 0, 250, 1)', '#A2D9CE', '#D6EAF8', '#E8DAEF', '#FADBD8',
8
8
  '#F5B7B1', '#E6B0AA', '#FCF3CF', '#FDEBD0', '#F6DDCC', '#D98880', '#EC7063', '#A569BD', '#85C1E9', '#45B39D', '#F7DC6F',
9
9
  '#DC7633', '#839192', '#6C3483', '#B03A2E', '#7B7D7D', '#4D5656', '#28B463', '#0E6655', '#F39C12', '#0E6251', '#1B4F72',
10
- '#FAE5D3', '#82E0AA', '#FF00FF', '#663399', '#4B0082', '#7CFC00', '#48D1CC'
10
+ '#FAE5D3', '#82E0AA', '#FF00FF', '#663399', '#4B0082', '#7CFC00'
11
11
  ]
12
12
  end
13
13
  end
@@ -0,0 +1,7 @@
1
+ module EducodeSales
2
+ class SalesDetail < ApplicationRecord
3
+ belongs_to :business
4
+ belongs_to :product_catalog
5
+ belongs_to :staff
6
+ end
7
+ end
@@ -0,0 +1,230 @@
1
+ module EducodeSales
2
+ class ReturnForecastService
3
+ attr_reader :user, :params, :request
4
+ # ReturnForecastService.new(current_user, request, params).call
5
+ def initialize(user, request, params)
6
+ @user = user
7
+ @request = request
8
+ @params = params
9
+ end
10
+
11
+ def call
12
+ end
13
+
14
+ def return_forecast_quarter(labels, selects, staff_id = nil)
15
+ plan_get = plan_get(staff_id, labels, "quarter")
16
+ actual_get = actual_get(staff_id, labels, "quarter")
17
+ datasets = selects.map do |select|
18
+ if select == "计划投标额"
19
+ {
20
+ label: select,
21
+ data: labels.map do |d|
22
+ quarter = d.split("-").last.to_i
23
+ year = d.split("-").first.to_i
24
+ plan_get[[year, quarter]]&.pluck(:budget_amount)&.reject(&:blank?)&.sum.to_f
25
+ end,
26
+ backgroundColor: [
27
+ 'rgba(255, 99, 132, 0.2)',
28
+ ],
29
+ borderColor: [
30
+ 'rgba(255,99,132,1)',
31
+ ],
32
+ borderWidth: 1
33
+ }
34
+ else
35
+ {
36
+ label: select,
37
+ data: labels.map do |d|
38
+ quarter = d.split("-").last.to_i
39
+ year = d.split("-").first.to_i
40
+ actual_get[[year, quarter]]&.pluck(:actual_amount)&.reject(&:blank?)&.sum.to_f
41
+ end,
42
+ backgroundColor: [
43
+ 'rgba(54, 162, 235, 0.2)',
44
+ ],
45
+ borderColor: [
46
+ 'rgba(54, 162, 235, 1)',
47
+ ],
48
+ borderWidth: 1
49
+ }
50
+ end
51
+ end
52
+
53
+ {
54
+ labels: labels,
55
+ datasets: datasets
56
+ }
57
+ end
58
+
59
+ def return_forecast_week(labels, selects, staff_id = nil)
60
+
61
+ plan_get = plan_get(staff_id, labels, "week")
62
+ actual_get = actual_get(staff_id, labels, "week")
63
+ datasets = selects.map do |select|
64
+ if select == "计划投标额"
65
+ {
66
+ label: select,
67
+ data: labels.map do |d|
68
+ week = d.split("-").last
69
+ year = d.split("-").first
70
+ yearweek = "#{year}#{week}".to_i
71
+ plan_get[yearweek].to_f
72
+ end,
73
+ backgroundColor: [
74
+ 'rgba(255, 99, 132, 0.2)',
75
+ ],
76
+ borderColor: [
77
+ 'rgba(255,99,132,1)',
78
+ ],
79
+ borderWidth: 1
80
+ }
81
+ else
82
+ {
83
+ label: select,
84
+ data: labels.map do |d|
85
+ week = d.split("-").last
86
+ year = d.split("-").first
87
+ yearweek = "#{year}#{week}".to_i
88
+ actual_get[yearweek].to_f
89
+ end,
90
+ backgroundColor: [
91
+ 'rgba(54, 162, 235, 0.2)',
92
+ ],
93
+ borderColor: [
94
+ 'rgba(54, 162, 235, 1)',
95
+ ],
96
+ borderWidth: 1
97
+ }
98
+ end
99
+ end
100
+
101
+ {
102
+ labels: labels,
103
+ datasets: datasets
104
+ }
105
+ end
106
+
107
+ def return_forecast_month(labels, selects, staff_id = nil)
108
+ plan_get = plan_get(staff_id, labels, "month")
109
+ actual_get = actual_get(staff_id, labels, "month")
110
+ datasets = selects.map do |select|
111
+ if select == "计划投标额"
112
+ {
113
+ label: select,
114
+ data: labels.map do |d|
115
+ month = d.split("-").last.to_i
116
+ year = d.split("-").first.to_i
117
+ plan_get[[year, month]]&.pluck(:budget_amount)&.reject(&:blank?)&.sum.to_f
118
+ end,
119
+ backgroundColor: [
120
+ 'rgba(255, 99, 132, 0.2)',
121
+ ],
122
+ borderColor: [
123
+ 'rgba(255,99,132,1)',
124
+ ],
125
+ borderWidth: 1
126
+ }
127
+ else
128
+ {
129
+ label: select,
130
+ data: labels.map do |d|
131
+ month = d.split("-").last.to_i
132
+ year = d.split("-").first.to_i
133
+ actual_get[[year, month]]&.pluck(:actual_amount)&.reject(&:blank?)&.sum.to_f
134
+ end,
135
+ backgroundColor: [
136
+ 'rgba(54, 162, 235, 0.2)',
137
+ ],
138
+ borderColor: [
139
+ 'rgba(54, 162, 235, 1)',
140
+ ],
141
+ borderWidth: 1
142
+ }
143
+ end
144
+ end
145
+
146
+ {
147
+ labels: labels,
148
+ datasets: datasets
149
+ }
150
+ end
151
+
152
+ def return_forecast_year(labels, selects, staff_id = nil)
153
+ plan_get = plan_get(staff_id, labels, "year")
154
+ actual_get = actual_get(staff_id, labels, "year")
155
+ datasets = selects.map do |select|
156
+ if select == "计划投标额"
157
+ {
158
+ label: select,
159
+ data: labels.map { |d| plan_get[d.to_i] || 0 },
160
+ backgroundColor: [
161
+ 'rgba(255, 99, 132, 0.2)',
162
+ ],
163
+ borderColor: [
164
+ 'rgba(255,99,132,1)',
165
+ ],
166
+ borderWidth: 1
167
+ }
168
+ else
169
+ {
170
+ label: select,
171
+ data: labels.map { |d| actual_get[d.to_i] || 0 },
172
+ backgroundColor: [
173
+ 'rgba(54, 162, 235, 0.2)',
174
+ ],
175
+ borderColor: [
176
+ 'rgba(54, 162, 235, 1)',
177
+ ],
178
+ borderWidth: 1
179
+ }
180
+ end
181
+ end
182
+
183
+ {
184
+ labels: labels,
185
+ datasets: datasets
186
+ }
187
+ end
188
+
189
+ private
190
+
191
+ def plan_get(staff_id, time_range, type)
192
+ # budget_amount 预算额
193
+ staff_id = staff_id.present? ? Array(staff_id) : nil
194
+ data = Business.joins(:last_follow_up)
195
+ .where("educode_sales_follow_ups.invitation_at >= ? and educode_sales_follow_ups.invitation_at <= ? #{staff_id.present? ? "AND educode_sales_follow_ups.staff_id in (#{staff_id.join(",")})" : ""}", time_range.first, time_range.last)
196
+ case type
197
+ when "week"
198
+ data.select("educode_sales_follow_ups.*, yearweek(educode_sales_follow_ups.invitation_at) as week").group("yearweek(educode_sales_follow_ups.invitation_at)").sum(:budget_amount)
199
+ when "quarter"
200
+ data = data.select("educode_sales_follow_ups.*, quarter(educode_sales_follow_ups.invitation_at) as quarter, year(educode_sales_follow_ups.invitation_at) as year")
201
+ data.group_by { |d| [d.year, d.quarter] }
202
+ when "month"
203
+ data = data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.invitation_at) as year, month(educode_sales_follow_ups.invitation_at) as month")
204
+ data.group_by { |d| [d.year, d.month] }
205
+ when "year"
206
+ data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.invitation_at) as year").group("year(educode_sales_follow_ups.invitation_at)").sum(:budget_amount)
207
+ end
208
+ end
209
+
210
+ def actual_get(staff_id, time_range, type)
211
+ # actual_amount 合同额
212
+ staff_id = staff_id.present? ? Array(staff_id) : nil
213
+ data = Business.joins(:last_follow_up)
214
+ .where("educode_sales_follow_ups.bidded_date >= ? and educode_sales_follow_ups.bidded_date <= ? #{staff_id.present? ? "AND educode_sales_follow_ups.staff_id in (#{staff_id.join(",")})" : ""}", time_range.first, time_range.last)
215
+ case type
216
+ when "week"
217
+ data.select("educode_sales_follow_ups.*, yearweek(educode_sales_follow_ups.bidded_date) as week").group("yearweek(educode_sales_follow_ups.bidded_date)").sum(:actual_amount)
218
+ when "quarter"
219
+ data = data.select("educode_sales_follow_ups.*, quarter(educode_sales_follow_ups.bidded_date) as quarter, year(educode_sales_follow_ups.bidded_date) as year")
220
+ data.group_by { |d| [d.year, d.quarter] }
221
+ when "month"
222
+ data = data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.bidded_date) as year, month(educode_sales_follow_ups.bidded_date) as month")
223
+ data.group_by { |d| [d.year, d.month] }
224
+ when "year"
225
+ data.select("educode_sales_follow_ups.*, year(educode_sales_follow_ups.bidded_date) as year").group("year(educode_sales_follow_ups.bidded_date)").sum(:actual_amount)
226
+ end
227
+ end
228
+
229
+ end
230
+ end
@@ -516,8 +516,8 @@
516
516
  },
517
517
  {
518
518
  field: 'invitation_at',
519
- width: 105,
520
- title: '招标时间',
519
+ width: 150,
520
+ title: '计划投标时间',
521
521
  sort: true,
522
522
  hide: gon.filter.invitation_at
523
523
  },
@@ -102,7 +102,7 @@
102
102
  <hr>
103
103
  <div class="layui-inline">
104
104
  <label class="layui-form-label">计划签单时间:</label>
105
- <div class="layui-input-inline" style="line-height: 38px;">
105
+ <div class="layui-input-inline" style="line-height: 38px;width: 100px;">
106
106
  <input type="text" class="layui-input date" name="plan_signed_date" autocomplete="off" value="<%= @last_follow_up&.plan_signed_date %>">
107
107
  </div>
108
108
  </div>
@@ -115,10 +115,16 @@
115
115
  </div>
116
116
  <div class="layui-inline">
117
117
  <label class="layui-form-label" style="width:100px;">实际签单时间:</label>
118
- <div class="layui-input-inline" style="line-height: 38px;">
118
+ <div class="layui-input-inline" style="line-height: 38px;width: 100px;">
119
119
  <input type="text" class="layui-input date" name="signed_date" autocomplete="off" value="<%= @last_follow_up&.signed_date %>">
120
120
  </div>
121
121
  </div>
122
+ <div class="layui-inline">
123
+ <label class="layui-form-label">签约类型:</label>
124
+ <div class="layui-input-inline" style="line-height: 38px;width:110px;">
125
+ <%= select_tag "signed_clazz", options_for_select(@signed_clazz, @last_follow_up&.signed_clazz), class: 'required' %>
126
+ </div>
127
+ </div>
122
128
  <div class="layui-inline">
123
129
  <label class="layui-form-label" style="width:100px;">签约单位:</label>
124
130
  <div class="layui-input-inline" style="line-height: 38px;">
@@ -611,7 +617,7 @@
611
617
  initValue: gon.value, // 渲染初始化默认值
612
618
  hasSelectIcon: false,
613
619
  placeholder: '请输入头歌平台上单位部门', // 渲染的inputplaceholder值
614
- data: [gon.department],
620
+ data: gon.department,
615
621
  remoteSearch: true, // 是否启用远程搜索 默认是false,和远程搜索回调保存同步
616
622
  remoteMethod: function (value, cb) { // 远程搜索的回调函数
617
623
  if (!value) {
@@ -0,0 +1,166 @@
1
+ <div id="tab_6" lay-filter="test1">
2
+ <div class="" style="padding-right: 50px">
3
+ <div style="margin: 10px 10px 10px 10px">
4
+ <form class="layui-form layui-form-pane" lay-filter="search_form">
5
+ <div class="layui-form-item">
6
+ <div class="layui-inline m-t-10">
7
+ <label class="layui-form-label">销售人员</label>
8
+ <div class="layui-input-inline">
9
+ <%= select_tag "staff_id", options_for_select(EducodeSales::Staff.all.includes(:user).map { |d| [d.user.real_name, d.id] }, params[:staff_id]), { 'lay-filter': 'staff_id', include_blank: true } %>
10
+ </div>
11
+ </div>
12
+ <div class="layui-inline m-t-10">
13
+ <label class="layui-form-label">视图</label>
14
+ <div class="layui-input-inline">
15
+ <%= select_tag "forecast_count_range", options_for_select([['按周', 'week'], ['按月', 'month'], ['按季', 'quarter'], ['按年', 'year']], params[:forecast_count_range] || "month"), { 'lay-filter': 'forecast_count' } %>
16
+ </div>
17
+ </div>
18
+ <div class="layui-inline">
19
+ <label class="layui-form-label">时间范围</label>
20
+ <div class="layui-input-inline">
21
+ <input type="text" class="layui-input forecast_month " id="forecast_date_month" name="forecast_date_month" placeholder=" - " value="<%= params[:forecast_date_month].present? ? params[:forecast_date_month] : Time.now.year.to_s + "-01" + " - " + Time.now.strftime("%Y-%m") %>" autocomplete="off">
22
+ <input type="text" class="layui-input forecast_year layui-hide" id="forecast_date_year" name="forecast_date_year" placeholder=" - " value="<%= params[:forecast_date_year] %>" autocomplete="off">
23
+ <input type="text" class="layui-input forecast_day layui-hide" id="forecast_date_day" name="forecast_date_day" placeholder=" - " value="<%= params[:forecast_date_day] %>" autocomplete="off">
24
+ </div>
25
+ </div>
26
+ <div class="layui-inline">
27
+ <button type="button" id="search_bt" class="forecast_count_bt layui-btn layui-btn-primary" lay-submit lay-filter="search_sales">确定
28
+ </button>
29
+ </div>
30
+ </div>
31
+ </form>
32
+ </div>
33
+ <canvas id="forecast_myChart2" width="960" height="350"></canvas>
34
+ <!-- <canvas id="forecast_myChart2_1" width="960" height="350" style="margin-top: 30px;display: none"></canvas>-->
35
+ </div>
36
+ </div>
37
+ <script>
38
+ var forecast_type = "<%=@forecast_count_range %>";
39
+ layui.use(['form', 'jquery', 'request', 'element'], function () {
40
+ form = layui.form;
41
+
42
+ var opt = {
43
+ events: false,
44
+ tooltips: {
45
+ enabled: false
46
+ },
47
+ layout: {
48
+ padding: {
49
+ left: 50,
50
+ right: 50,
51
+ top: 0,
52
+ bottom: 0
53
+ }
54
+ },
55
+ hover: {
56
+ animationDuration: 0
57
+ },
58
+ animation: {
59
+ duration: 1,
60
+ onComplete: function () {
61
+ var chartInstance = this.chart, ctx = chartInstance.ctx;
62
+ ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
63
+ ctx.textAlign = 'center';
64
+ ctx.textBaseline = 'bottom';
65
+
66
+ this.data.datasets.forEach(function (dataset, i) {
67
+ ctx.fillStyle = "#444";
68
+ var meta = chartInstance.controller.getDatasetMeta(i);
69
+ meta.data.forEach(function (bar, index) {
70
+ var data = dataset.data[index];
71
+ ctx.fillText(data, bar._model.x, bar._model.y - 5);
72
+ });
73
+ });
74
+ }
75
+ }
76
+ };
77
+
78
+ var res_data = <%=raw @forecast_count_data.to_json %>;
79
+ var datasets = [];
80
+ var color = ['#44D7B6', '#4CACFF', '#F7B500', '#FF0000', '#FF7F00'];
81
+ res_data.datasets.forEach(function (d, i) {
82
+ datasets.push({
83
+ label: d.label,
84
+ type: 'line',
85
+ data: d.data1,
86
+ borderColor: color[i],
87
+ pointRadius: 1,
88
+ fill: false,
89
+ lineTension: 0
90
+ })
91
+ })
92
+
93
+ laydate.render({
94
+ type: 'year',
95
+ elem: '#forecast_date_year',
96
+ range: true
97
+ });
98
+ laydate.render({
99
+ elem: '#forecast_date_day',
100
+ range: true
101
+ });
102
+ laydate.render({
103
+ type: 'month',
104
+ elem: '#forecast_date_month',
105
+ range: true
106
+ });
107
+
108
+ if (forecast_type == "month") {
109
+ $(".forecast_year").addClass('layui-hide')
110
+ $(".forecast_day").addClass('layui-hide')
111
+ $(".forecast_month").removeClass('layui-hide')
112
+ } else if (forecast_type == "year" || forecast_type == "quarter") {
113
+ $(".forecast_month").addClass('layui-hide')
114
+ $(".forecast_year").removeClass('layui-hide')
115
+ $(".forecast_day").addClass('layui-hide')
116
+ } else {
117
+ $(".forecast_day").removeClass('layui-hide')
118
+ $(".forecast_month").addClass('layui-hide')
119
+ $(".forecast_year").addClass('layui-hide')
120
+ }
121
+
122
+ form.on('select(forecast_count)', function (data) {
123
+ const value = data.value;
124
+ if (value == "month") {
125
+ $(".forecast_year").addClass('layui-hide')
126
+ $(".forecast_day").addClass('layui-hide')
127
+ $(".forecast_month").removeClass('layui-hide')
128
+ } else if (value == "year" || value == "quarter") {
129
+ $(".forecast_month").addClass('layui-hide')
130
+ $(".forecast_year").removeClass('layui-hide')
131
+ $(".forecast_day").addClass('layui-hide')
132
+ } else {
133
+ $(".forecast_day").removeClass('layui-hide')
134
+ $(".forecast_month").addClass('layui-hide')
135
+ $(".forecast_year").addClass('layui-hide')
136
+ }
137
+ })
138
+
139
+
140
+ var ctx = document.getElementById('forecast_myChart2');
141
+ var forecast_myChart2 = new Chart(ctx, {
142
+ type: 'bar',
143
+ data: <%=raw @forecast_count_data.to_json %>,
144
+ options: {
145
+ elements: {
146
+ line: {
147
+ tension: 0
148
+ }
149
+ }
150
+ }
151
+ });
152
+ form.render();
153
+ form.on("submit(search_sales)", function (data) {
154
+ var layer_index = layer.load(0, {shade: [0.1, '#fff']});
155
+ Rails.ajax({
156
+ url: '/missions/sale_trends/goal_forecast',
157
+ type: 'GET',
158
+ data: $.param(data.field, true),
159
+ dataType: "script",
160
+ success: function (res) {
161
+ layer.close(layer_index);
162
+ }
163
+ });
164
+ })
165
+ })
166
+ </script>
@@ -0,0 +1 @@
1
+ $("#page_6").html("<%= j render 'goal_forecast' %>");
@@ -5,18 +5,19 @@
5
5
  <li class="">销售额分析</li>
6
6
  <li class="">商机区域分布</li>
7
7
  <li class="">销售人员跟进分析</li>
8
+ <li class="">中标预测图</li>
8
9
  </ul>
9
- <div class="layui-tab-content">
10
+ <div class="layui-tab-content">
10
11
  <div class="layui-tab-item layui-show">
11
12
  <form class="layui-form" action="">
12
13
  <div class="welcome">
13
14
  <div class="content-body" style="padding: 15px;">
14
- <div class="m-b-10">
15
- <i class="fa fa-warning icon"></i>年度
16
- <div class="layui-input-inline">
17
- <%= select_tag "years", options_for_select(@years, @year), {'lay-filter': 'year'} %>
18
- </div>
15
+ <div class="m-b-10">
16
+ <i class="fa fa-warning icon"></i>年度
17
+ <div class="layui-input-inline">
18
+ <%= select_tag "years", options_for_select(@years, @year), { 'lay-filter': 'year' } %>
19
19
  </div>
20
+ </div>
20
21
  <div class="layui-card-bodys">
21
22
  <div class="">
22
23
  <div class="layui-row layui-col-space30">
@@ -25,7 +26,7 @@
25
26
  <div class="panel-body">
26
27
  <div class="panel-title">
27
28
  <% if can? :sale_trends, EducodeSales::SaleTrend %>
28
- <span class="layui-icon layui-icon-edit pull-right" id="chance_money_edit"></span>
29
+ <span class="layui-icon layui-icon-edit pull-right" id="chance_money_edit"></span>
29
30
  <% end %>
30
31
  <h5>商机目标</h5>
31
32
  </div>
@@ -220,58 +221,63 @@
220
221
  <div id="page_2"></div>
221
222
  </div>
222
223
 
223
- <div class="layui-tab-item">
224
+ <div class="layui-tab-item">
224
225
  <div id="page_3"></div>
225
- </div>
226
+ </div>
226
227
 
227
- <div class="layui-tab-item">
228
- <div id="page_4"></div>
229
- </div>
230
- <div class="layui-tab-item">
231
- <div id="page_5"></div>
232
- </div>
228
+ <div class="layui-tab-item">
229
+ <div id="page_4"></div>
230
+ </div>
231
+ <div class="layui-tab-item">
232
+ <div id="page_5"></div>
233
+ </div>
234
+ <div class="layui-tab-item">
235
+ <div id="page_6"></div>
236
+ </div>
233
237
  </div>
234
238
  </div>
235
239
 
236
240
 
237
- <script>
241
+ <script>
238
242
  layui.use(['form', 'jquery', 'request', 'element'], function () {
239
- var $ = layui.jquery,
240
- form = layui.form,
241
- request = layui.request;
243
+ var $ = layui.jquery,
244
+ form = layui.form,
245
+ request = layui.request;
242
246
  var year = "<%= @year %>";
243
247
  var element = layui.element;
244
248
  laydate = layui.laydate;
245
249
  element.render();
246
- element.on('tab(sale_trend_tab)', function(data) {
247
- if (data.index == 1 && $("#tab_2").length == 0) {
248
- loadPage('/missions/sale_trends/business_followup_analysis')
249
- } else if (data.index == 2 && $("#tab_3").length == 0 ) {
250
- loadPage('/missions/sale_trends/sales_analysis')
251
- } else if (data.index == 3 && $("#tab_4").length == 0 ) {
252
- loadPage('/missions/sale_trends/business_area')
253
- } else if (data.index == 4 && $("#tab_5").length == 0 ) {
254
- loadPage('/missions/sale_trends/sales_followup_analysis')
255
- }
250
+ element.on('tab(sale_trend_tab)', function (data) {
251
+ if (data.index == 1 && $("#tab_2").length == 0) {
252
+ loadPage('/missions/sale_trends/business_followup_analysis')
253
+ } else if (data.index == 2 && $("#tab_3").length == 0) {
254
+ loadPage('/missions/sale_trends/sales_analysis')
255
+ } else if (data.index == 3 && $("#tab_4").length == 0) {
256
+ loadPage('/missions/sale_trends/business_area')
257
+ } else if (data.index == 4 && $("#tab_5").length == 0) {
258
+ loadPage('/missions/sale_trends/sales_followup_analysis')
259
+ } else if (data.index == 5 && $("#tab_6").length == 0) {
260
+ loadPage('/missions/sale_trends/goal_forecast')
261
+ }
256
262
  });
257
263
 
258
264
 
259
265
  function loadPage(url) {
260
- var layer_index = layer.load(0, {shade: [0.1, '#fff']});
261
- Rails.ajax({
262
- url: url,
263
- type: 'GET',
264
- dataType: "script",
265
- success: function (res) {
266
- layer.close(layer_index);
267
- }
268
- });
266
+ var layer_index = layer.load(0, {shade: [0.1, '#fff']});
267
+ Rails.ajax({
268
+ url: url,
269
+ type: 'GET',
270
+ dataType: "script",
271
+ success: function (res) {
272
+ layer.close(layer_index);
273
+ }
274
+ });
269
275
  }
270
276
 
271
277
  function editForm(el) {
272
- $("#" + el + "_edit").click(function() {
273
- if ($("#" + el + "_field")[0].classList.contains("layui-hide")) {
274
- $("#" + el + "_field").removeClass("layui-hide")
278
+ $("#" + el + "_edit").click(function () {
279
+ if ($("#" + el + "_field")[0].classList.contains("layui-hide")) {
280
+ $("#" + el + "_field").removeClass("layui-hide")
275
281
  $("#" + el + "_value").addClass("layui-hide")
276
282
  } else {
277
283
  $("#" + el + "_field").addClass("layui-hide")
@@ -71,6 +71,8 @@
71
71
  <dd><a href="/missions/contracts/product_list" class="<%= current?('layui-this', product_list_contracts_path) %>"><i style="padding-right: 35px"></i><%= image_tag "educode_sales/4.商机管理.png",size:"15"%><i style="font-size: 25px; padding-right: 15px"></i>产品目录</a>
72
72
  </dd>
73
73
  <% end %>
74
+ <dd><a href="/missions/sales_details" class="<%= current?('layui-this', sales_details_path) %>"><i style="padding-right: 35px"></i><%= image_tag "educode_sales/4.商机管理.png",size:"15"%><i style="font-size: 25px; padding-right: 15px"></i>销售明细</a>
75
+ </dd>
74
76
  <% if can? :read, EducodeSales::MoneyPlan %>
75
77
  <dd><a href="/missions/money_plans" class="<%= current?('layui-this', money_plans_path) %>"><i style="padding-right: 35px"></i><%= image_tag "educode_sales/money_plans.png",size:"15"%><i style="font-size: 25px; padding-right: 15px"></i>回款计划</a>
76
78
  </dd>
data/config/routes.rb CHANGED
@@ -22,6 +22,9 @@ EducodeSales::Engine.routes.draw do
22
22
  resources :sessions do
23
23
  end
24
24
 
25
+ resources :sales_details do
26
+ end
27
+
25
28
  resources :contracts do
26
29
  collection do
27
30
  get :follow_ups
@@ -122,6 +125,7 @@ EducodeSales::Engine.routes.draw do
122
125
  get :sales_analysis
123
126
  get :business_area
124
127
  get :sales_followup_analysis
128
+ get :goal_forecast
125
129
  end
126
130
  end
127
131
 
@@ -0,0 +1,16 @@
1
+ class CreateEducodeSalesSalesDetails < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :educode_sales_sales_details do |t|
4
+ t.references :business
5
+ t.references :product_catalog
6
+ t.references :staff
7
+ t.float :price
8
+ t.float :amount
9
+ t.float :total_price
10
+ t.integer :custom_clazz
11
+ t.date :delivery_date
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module EducodeSales
2
- VERSION = '0.9.82'
2
+ VERSION = '0.9.84'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: educode_sales
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.82
4
+ version: 0.9.84
5
5
  platform: ruby
6
6
  authors:
7
7
  - anke1460
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-09 00:00:00.000000000 Z
11
+ date: 2023-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -173,6 +173,7 @@ files:
173
173
  - app/controllers/educode_sales/sale_reports_controller.rb
174
174
  - app/controllers/educode_sales/sale_trends_controller.rb
175
175
  - app/controllers/educode_sales/sales_controller.rb
176
+ - app/controllers/educode_sales/sales_details_controller.rb
176
177
  - app/controllers/educode_sales/sessions_controller.rb
177
178
  - app/controllers/educode_sales/staffs_controller.rb
178
179
  - app/controllers/educode_sales/teacher_follows_controller.rb
@@ -185,6 +186,7 @@ files:
185
186
  - app/helpers/educode_sales/business_helper.rb
186
187
  - app/helpers/educode_sales/follow_up_helper.rb
187
188
  - app/helpers/educode_sales/home_helper.rb
189
+ - app/helpers/educode_sales/sale_trends_helper.rb
188
190
  - app/helpers/educode_sales/sessions_helper.rb
189
191
  - app/helpers/educode_sales/teachers_helper.rb
190
192
  - app/jobs/educode_sales/application_job.rb
@@ -242,6 +244,7 @@ files:
242
244
  - app/models/educode_sales/sale_plan.rb
243
245
  - app/models/educode_sales/sale_report.rb
244
246
  - app/models/educode_sales/sale_trend.rb
247
+ - app/models/educode_sales/sales_detail.rb
245
248
  - app/models/educode_sales/staff.rb
246
249
  - app/models/educode_sales/staff_permission.rb
247
250
  - app/models/educode_sales/staff_school.rb
@@ -249,6 +252,7 @@ files:
249
252
  - app/models/educode_sales/teacher.rb
250
253
  - app/models/educode_sales/teacher_assign_follow.rb
251
254
  - app/models/educode_sales/teacher_follow.rb
255
+ - app/services/return_forecast_service.rb
252
256
  - app/views/educode_sales/activities/_follows.html.erb
253
257
  - app/views/educode_sales/activities/_index.html.erb
254
258
  - app/views/educode_sales/activities/edit.html.erb
@@ -516,10 +520,12 @@ files:
516
520
  - app/views/educode_sales/sale_reports/show.html.erb
517
521
  - app/views/educode_sales/sale_trends/_business_area.html.erb
518
522
  - app/views/educode_sales/sale_trends/_business_followup_analysis.html.erb
523
+ - app/views/educode_sales/sale_trends/_goal_forecast.html.erb
519
524
  - app/views/educode_sales/sale_trends/_sales_analysis.html.erb
520
525
  - app/views/educode_sales/sale_trends/_sales_followup_analysis.html.erb
521
526
  - app/views/educode_sales/sale_trends/business_area.js.erb
522
527
  - app/views/educode_sales/sale_trends/business_followup_analysis.js.erb
528
+ - app/views/educode_sales/sale_trends/goal_forecast.js.erb
523
529
  - app/views/educode_sales/sale_trends/operations.html.erb
524
530
  - app/views/educode_sales/sale_trends/sales_analysis.js.erb
525
531
  - app/views/educode_sales/sale_trends/sales_followup_analysis.js.erb
@@ -532,6 +538,7 @@ files:
532
538
  - app/views/educode_sales/sales/school.html.erb
533
539
  - app/views/educode_sales/sales/trends.html.erb
534
540
  - app/views/educode_sales/sales/xschool.html.erb
541
+ - app/views/educode_sales/sales_details/index.html.erb
535
542
  - app/views/educode_sales/sessions/login.html.erb
536
543
  - app/views/educode_sales/staffs/edit.html.erb
537
544
  - app/views/educode_sales/staffs/follow_up_departments.json.jbuilder
@@ -663,6 +670,7 @@ files:
663
670
  - db/migrate/20230430134710_create_educode_sales_assign_staffs.rb
664
671
  - db/migrate/20230501034307_create_educode_sales_follow_up_trends.rb
665
672
  - db/migrate/20230502142912_create_educode_sales_product_catalogs.rb
673
+ - db/migrate/20230510144317_create_educode_sales_sales_details.rb
666
674
  - lib/educode_sales.rb
667
675
  - lib/educode_sales/engine.rb
668
676
  - lib/educode_sales/version.rb