educode_sales 0.9.83 → 0.9.86

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.
@@ -0,0 +1,8 @@
1
+ module EducodeSales
2
+ class SalesDetail < ApplicationRecord
3
+ belongs_to :business
4
+ belongs_to :product_catalog
5
+ belongs_to :staff
6
+ enum custom_clazz: ['非定制', '低定制', '高定制', '全定制']
7
+ end
8
+ 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
@@ -110,6 +110,7 @@
110
110
  <% if can? :assign_contract, EducodeSales::Business %>
111
111
  <a class="layui-btn layui-btn-default layui-btn-xs data-count-edit" lay-event="assign">指派售后</a>
112
112
  <% end %>
113
+ <a class="layui-btn layui-btn-default layui-btn-xs data-count-edit" lay-event="add_sales">添加销售明细</a>
113
114
 
114
115
  </script>
115
116
 
@@ -431,7 +432,7 @@
431
432
  },
432
433
  {
433
434
  title: '操作',
434
- minWidth: 220,
435
+ minWidth: 300,
435
436
  toolbar: '#currentTableBar',
436
437
  align: "center",
437
438
  fixed: 'right'
@@ -546,6 +547,26 @@
546
547
  layer.full(sindex);
547
548
  });
548
549
  return false;
550
+ } else if (obj.event === 'add_sales') {
551
+ business_id = data.id
552
+ var content = miniPage.getHrefContent('/missions/contracts/new_sales_detail?id=' + data.id);
553
+ var openWH = miniPage.getOpenWidthHeight();
554
+ new_sale_index = layer.open({
555
+ title: '添加销售明细',
556
+ type: 1,
557
+ shade: 0.2,
558
+ maxmin: true,
559
+ shadeClose: true,
560
+ area: [openWH[0] + 'px', openWH[1] + 'px'],
561
+ offset: [openWH[2] + 'px', openWH[3] + 'px'],
562
+ content: content,
563
+ success: function (layero, index) {
564
+ form.render('select');
565
+ }
566
+ });
567
+ $(window).on("resize", function () {
568
+ layer.full(new_follow_index);
569
+ });
549
570
  }
550
571
  });
551
572
 
@@ -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,116 @@
1
+ <%= Gon::Base.render_data %>
2
+ <div style="padding:30px">
3
+ <div class="layui-form layuimini-form" lay-filter="searchform">
4
+ <div class="layui-form-item">
5
+ <div class="layui-inline">
6
+ <label class="layui-form-label">产品名称:</label>
7
+ <div class="layui-input-inline" style="line-height: 38px;width: 400px;" id="product_name">
8
+ </div>
9
+ <div class="layui-form-mid layui-text-em"><button class="layui-btn layui-btn-sm" id="open_product">选择产品</button></div>
10
+ <input type="hidden" name="product_catalog_id" id="product_catalog_id">
11
+ </div>
12
+ </div>
13
+ <div class="layui-form-item">
14
+ <div class="layui-inline">
15
+ <label class="layui-form-label required">单价:</label>
16
+ <div class="layui-input-inline">
17
+ <input type="number" name="price" value="" class="layui-input" lay-verify="required">
18
+ </div>
19
+ </div>
20
+ <div class="layui-inline">
21
+ <label class="layui-form-label required">数量:</label>
22
+ <div class="layui-input-inline">
23
+ <input type="number" name="amount" value="" class="layui-input" lay-verify="required">
24
+ </div>
25
+ </div>
26
+ <div class="layui-inline">
27
+ <label class="layui-form-label required">金额:</label>
28
+ <div class="layui-input-inline">
29
+ <input type="number" name="total_price" value="" class="layui-input" lay-verify="required">
30
+ </div>
31
+ </div>
32
+
33
+ </div>
34
+ <div class="layui-form-item">
35
+ <div class="layui-inline">
36
+ <label class="layui-form-label required">定制类型:</label>
37
+ <div class="layui-input-inline">
38
+ <%= select_tag "custom_clazz", options_for_select(EducodeSales::SalesDetail.custom_clazzs.keys), { 'lay-filter': 'source_method', 'lay-verify': "required", include_blank: true, "lay-search": "" } %>
39
+ </div>
40
+ </div>
41
+ <div class="layui-inline">
42
+ <label class="layui-form-label required">交货时间:</label>
43
+ <div class="layui-input-inline">
44
+ <input type="text" name="delivery_date" value="" class="layui-input" lay-verify="required" id="delivery_date">
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div class="layui-form-item">
49
+ <div class="layui-input-block">
50
+ <button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">保存</button>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <script type="text/html" id="sale_toolbar">
57
+ <div class="layui-btn-container">
58
+ <button class="layui-btn layui-btn-sm" lay-event="select_product">选择产品</button>
59
+ </div>
60
+ </script>
61
+ <script>
62
+ function selectProduct(data) {
63
+ $("#product_catalog_id").val(data.id)
64
+ product_name.innerText = [data.name, data.item_clazz, data.brand, data.specification, data.unit, data.source_method, data.param, data.supplier].join(" ")
65
+ }
66
+ layui.use(['form', 'table', 'upload', 'laytpl', 'request', 'laydate'], function() {
67
+ var form = layui.form,
68
+ layer = layui.layer,
69
+ table = layui.table,
70
+ laydate = layui.laydate,
71
+ laytpl = layui.laytpl,
72
+ request = layui.request,
73
+ $ = layui.$;
74
+ form.render();
75
+
76
+ laydate.render({
77
+ elem: '#delivery_date',
78
+ });
79
+
80
+
81
+ $("#open_product").on("click", function() {
82
+ var content = miniPage.getHrefContent('/missions/contracts/select_product');
83
+ var openWH = miniPage.getOpenWidthHeight();
84
+ product_index = layer.open({
85
+ title: '选择产品',
86
+ type: 1,
87
+ shade: 0.2,
88
+ maxmin: true,
89
+ shadeClose: true,
90
+ area: [openWH[0] + 'px', openWH[1] + 'px'],
91
+ offset: [openWH[2] + 'px', openWH[3] + 'px'],
92
+ content: content,
93
+ success: function (layero, index) {
94
+ form.render('select');
95
+ }
96
+ });
97
+ $(window).on("resize", function () {
98
+ layer.full(new_follow_index);
99
+ });
100
+ })
101
+
102
+ form.on('submit(saveBtn)', function(data) {
103
+ console.log( data.field)
104
+ data.field.business_id = parent.business_id;
105
+ if (data.field.product_catalog_id == "") {
106
+ layer.alert("请选择产品")
107
+ return false;
108
+ }
109
+ request.post("missions/contracts/sales_detail", data.field, function(res) {
110
+ layer.close(parent.new_sale_index)
111
+ // parent.table.reload('add_sale_detail_table')
112
+ })
113
+ return false;
114
+ });
115
+ });
116
+ </script>
@@ -0,0 +1,126 @@
1
+ <div style="padding:30px">
2
+ <div class="layui-form layuimini-form" style="padding:30px" lay-filter="search_product_form">
3
+ <div class="layui-form-item">
4
+ <div class="layui-inline">
5
+ <label class="layui-form-label">产品名称</label>
6
+ <div class="layui-input-inline">
7
+ <input type="text" name="name" class="layui-input" value="">
8
+ </div>
9
+ </div>
10
+ <div class="layui-inline">
11
+ <label class="layui-form-label">销售品目</label>
12
+ <div class="layui-input-inline">
13
+ <%= select_tag "item_clazz", options_for_select(EducodeSales::ProductCatalog.item_clazzs.keys), { 'lay-filter': 'item_clazz', include_blank: true , "lay-search": "" } %>
14
+ </div>
15
+ </div>
16
+ <div class="layui-inline">
17
+ <button type="submit" id="search_bt" class="layui-btn layui-btn-primary" lay-submit lay-filter="search_product_catalog_form">搜索</button>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ <div id="sale_detail_table_wraper">
22
+ <table class="layui-hide" id="sale_detail_table" lay-filter="sale_detail_table"></table>
23
+ </div>
24
+ </div>
25
+
26
+ <script type="text/html" id="sale_toolbar">
27
+ <div class="layui-btn-container">
28
+ <button class="layui-btn layui-btn-sm" lay-event="select_product">选择产品</button>
29
+ </div>
30
+ </script>
31
+ <script>
32
+ layui.use(['form', 'table', 'upload', 'laytpl', 'request', 'selectInput'], function() {
33
+ var form = layui.form,
34
+ layer = layui.layer,
35
+ table = layui.table,
36
+ laytpl = layui.laytpl,
37
+ request = layui.request,
38
+ $ = layui.$;
39
+ form.render();
40
+
41
+ var cols_table = [
42
+ [{
43
+ type: 'radio',
44
+ fixed: true
45
+ }, {
46
+ field: 'id',
47
+ width: 60,
48
+ title: '序号',
49
+ }, {
50
+ field: 'name',
51
+ width: 120,
52
+ title: '产品名称',
53
+ }, {
54
+ field: 'item_clazz',
55
+ width: 150,
56
+ title: '销售品目',
57
+ }, {
58
+ field: 'brand',
59
+ width: 150,
60
+ title: '品牌',
61
+ }, {
62
+ field: 'specification',
63
+ width: 150,
64
+ title: '规格型号',
65
+ }, {
66
+ field: 'unit',
67
+ width: 150,
68
+ title: '单位',
69
+ }, {
70
+ field: 'param',
71
+ width: 150,
72
+ title: '参数',
73
+ }, {
74
+ field: 'source_method',
75
+ width: 150,
76
+ title: '来源方式',
77
+ }, {
78
+ field: 'supplier',
79
+ width: 150,
80
+ title: '供货商',
81
+ }, ]
82
+ ]
83
+
84
+ var sale_detail_table = table.render({
85
+ elem: '#sale_detail_table',
86
+ url: '/missions/contracts/product_list',
87
+ autoSort: false,
88
+ title: '销售产品',
89
+ toolbar: '#sale_toolbar',
90
+ cols: cols_table,
91
+ limit: 20,
92
+ limits: [10, 15, 20, 30, 40, 50, 60, 70, 80, 90],
93
+ page: true,
94
+ skin: 'line',
95
+ });
96
+
97
+ form.on('submit(search_product_catalog_form)', function(data) {
98
+ var data = form.val("search_product_form");
99
+ console.log(data)
100
+ table.reload('sale_detail_table', {
101
+ url: '/missions/contracts/product_list',
102
+ page: {
103
+ curr: 1
104
+ },
105
+ where: {
106
+ q: data,
107
+ }
108
+ }, 'data');
109
+ return false;
110
+ });
111
+
112
+ table.on('toolbar(sale_detail_table)', function(obj) {
113
+ var id = obj.config.id;
114
+ if (obj.event == 'select_product') {
115
+ var checkStatus = table.checkStatus(id);
116
+ var row = checkStatus.data;
117
+ if (row[0]) {
118
+ var data = row[0];
119
+ parent.selectProduct(data)
120
+ layer.close(product_index);
121
+ }
122
+ }
123
+ })
124
+
125
+ });
126
+ </script>
@@ -6,7 +6,10 @@
6
6
  <div class="layui-inline m-t-10">
7
7
  <label class="layui-form-label">销售人员</label>
8
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 } %>
9
+ <% common = EducodeSales::Common.find_by(clazz: 'staff_type', name: '销售')
10
+ staffs = EducodeSales::Staff.where(job_type: common.id)
11
+ %>
12
+ <%= select_tag "staff_id", options_for_select(staffs.includes(:user).map { |d| [d.user.real_name, d.id] }, params[:staff_id]), { 'lay-filter': 'staff_id', include_blank: true } %>
10
13
  </div>
11
14
  </div>
12
15
  <div class="layui-inline m-t-10">
@@ -139,7 +142,7 @@
139
142
 
140
143
  var ctx = document.getElementById('forecast_myChart2');
141
144
  var forecast_myChart2 = new Chart(ctx, {
142
- type: 'line',
145
+ type: 'bar',
143
146
  data: <%=raw @forecast_count_data.to_json %>,
144
147
  options: {
145
148
  elements: {