diff --git a/app/api/mobile/api.rb b/app/api/mobile/api.rb
index 894ba099..8d01bb5b 100644
--- a/app/api/mobile/api.rb
+++ b/app/api/mobile/api.rb
@@ -166,6 +166,21 @@ module Mobile
return user if user
nil
end
+
+ def paginate(objs)
+ page = params[:page].to_i <= 0 ? 1 : params[:page].to_i
+ per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : 20
+
+ Kaminari.paginate_array(objs).page(page).per(per_page)
+ end
+
+ def render_ok(data = {})
+ { status: 0, message: 'success' }.merge(data)
+ end
+
+ def render_error(message)
+ { status: -1, message: message }
+ end
end
mount Apis::Auth
@@ -197,7 +212,7 @@ module Mobile
mount Apis::Ecloud
mount Apis::Cnmooc
-
+ mount Apis::ProjectPackages
# add_swagger_documentation ({host: 'educoder.0bug.info', api_version: 'v1', base_path: '/api'}) if Rails.env.development?
add_swagger_documentation ({api_version: 'v1', base_path: '/api'}) if Rails.env.development?
diff --git a/app/api/mobile/apis/project_packages.rb b/app/api/mobile/apis/project_packages.rb
new file mode 100644
index 00000000..d5c4143f
--- /dev/null
+++ b/app/api/mobile/apis/project_packages.rb
@@ -0,0 +1,144 @@
+module Mobile
+ module Apis
+ class ProjectPackages < Grape::API
+ helpers do
+ def current_package
+ @_current_package ||= ProjectPackage.find(params[:id])
+ end
+
+ def symbolize_params
+ params.to_hash.symbolize_keys
+ end
+ end
+
+ resources :project_packages do
+ desc 'project packages index'
+ params do
+ optional :category, type: String, desc: '类型'
+ optional :keyword, type: String, desc: '搜索关键字'
+ optional :sort_by, type: String, desc: '排序'
+ optional :page, type: Integer, desc: '页数'
+ optional :per_page, type: Integer, desc: '分页大小'
+ end
+ get do
+ packages = ProjectPackage.where(status: %w(published bidding_ended bidding_finished))
+
+ packages = packages.where(category: params[:category]) if params[:category].present?
+
+ keyword = params[:keyword].to_s.strip
+ packages = packages.where('title LIKE ?', "%#{keyword}%") if keyword.present?
+
+ count = packages.count
+
+ if params[:sort_by] == 'price'
+ packages = packages.order('min_price desc')
+ else
+ packages = packages.order('published_at desc')
+ end
+ packages = paginate packages.includes(:creator, :attachments, bidding_users: :user)
+
+ present :count, count
+ present :project_packages, packages, with: Mobile::Entities::ProjectPackage, type: :index, user: current_user
+ end
+
+
+ desc 'project package show'
+ params do
+ requires :id, type: Integer, desc: 'ID'
+ end
+ route_param :id do
+ get do
+ authenticate!
+
+ present current_package, with: Mobile::Entities::ProjectPackage, type: :show, user: current_user
+ end
+
+ resource :bidding_users do
+ desc 'user bidding'
+ params {}
+ post do
+ authenticate!
+ begin
+ ::ProjectPackages::BiddingService.new(current_package, current_user).call
+ render_ok
+ rescue ::ProjectPackages::BiddingService::Error => ex
+ render_error(ex.message)
+ end
+ end
+
+ desc 'select bidding user win'
+ params do
+ requires :user_ids, type: Array[Integer], desc: '中标者用户ID数组'
+ end
+ post 'win' do
+ authenticate!
+ begin
+ package = current_user.project_packages.find(params[:id])
+ ProjectPackages::WinBiddingService.new(package, symbolize_params).call
+ rescue ProjectPackages::WinBiddingService::Error =>ex
+ render_error(ex.message)
+ end
+ end
+ end
+ end
+
+
+ desc 'create project packages'
+ params do
+ requires :category, type: String, desc: '类型'
+ requires :title, type: String, desc: '标题'
+ requires :content, type: String, desc: '描述'
+ optional :attachment_ids, type: Array[Integer], desc: '附件ID数组'
+ requires :deadline_at, type: DateTime, desc: '截止日期'
+ requires :min_price, type: Float, desc: '最小费用'
+ optional :max_price, type: Float, desc: '最大费用'
+ requires :contact_name, type: String, desc: '联系人姓名'
+ requires :contact_phone, type: String, desc: '联系人手机号'
+ optional :code, type: String, desc: '验证码'
+ optional :publish, type: Boolean, desc: '是否申请发布'
+ end
+ post do
+ authenticate!
+ begin
+ package = current_user.project_packages.new
+ ::ProjectPackages::SaveService.new(package, symbolize_params).call
+ package.increment_visit_count!
+ render_ok
+ rescue ::ProjectPackages::SaveService::Error => ex
+ render_error(ex.message)
+ end
+ end
+
+
+ desc 'update project package'
+ params do
+ requires :id, type: Integer, desc: 'ID'
+ requires :category, type: String, desc: '类型'
+ requires :title, type: String, desc: '标题'
+ requires :content, type: String, desc: '描述'
+ optional :attachment_ids, type: Array[Integer], desc: '附件ID数组'
+ requires :deadline_at, type: DateTime, desc: '截止日期'
+ requires :min_price, type: Float, desc: '最小费用'
+ optional :max_price, type: Float, desc: '最大费用'
+ requires :contact_name, type: String, desc: '联系人姓名'
+ requires :contact_phone, type: String, desc: '联系人手机号'
+ optional :code, type: String, desc: '验证码'
+ optional :publish, type: Boolean, desc: '是否申请发布'
+ end
+ put ':id' do
+ authenticate!
+ begin
+ package = current_user.project_packages.find(params[:id])
+ return render_error('该状态下不能编辑') unless package.editable?
+
+ ::ProjectPackages::SaveService.new(package, symbolize_params).call
+ package.increment_visit_count!
+ render_ok
+ rescue ::ProjectPackages::SaveService::Error => ex
+ render_error(ex.message)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/api/mobile/entities/project_package.rb b/app/api/mobile/entities/project_package.rb
new file mode 100644
index 00000000..fdf94a32
--- /dev/null
+++ b/app/api/mobile/entities/project_package.rb
@@ -0,0 +1,65 @@
+module Mobile
+ module Entities
+ class ProjectPackage < Grape::Entity
+ include ApplicationHelper
+ include ActionView::Helpers::NumberHelper
+
+ expose :id
+ expose :title
+ expose :category
+ expose :status
+ expose :visit_count, if: { type: :index }
+ expose :bidding_users_count, if: { type: :index }
+ expose :min_price
+ expose :max_price
+
+ expose :deadline_at do |package, _|
+ package.deadline_at.try(:utc).try(:iso8601)
+ end
+ expose :published_at do |package, _|
+ package.published_at.try(:utc).try(:iso8601)
+ end
+
+ expose :can_bidding, if: { type: :show } do |package, opts|
+ package.can_bidding?(opts[:user])
+ end
+
+ expose :can_select_bidding_user, if: { type: :show } do |package, opts|
+ opts[:user].id == package.creator_id && !package.bidding_end?
+ end
+
+ expose :creator, if: { type: :show } do |package, _|
+ {
+ id: package.creator.id,
+ login: package.creator.login,
+ name: package.creator.show_real_name,
+ avatar_url: "/images/#{url_to_avatar(package.creator)}"
+ }
+ end
+
+ expose :attachments, if: { type: :show } do |package, _|
+ package.attachments.map do |attachment|
+ {
+ id: attachment.id,
+ title: attachment.title,
+ filesize: number_to_human_size(attachment.filesize),
+ url: "attachments/download/#{attachment.id}",
+ }
+ end
+ end
+
+ expose :bidding_users, if: { type: :show } do |package, _|
+ package.bidding_users.map do |bidding_user|
+ user = bidding_user.user
+ {
+ id: user.id,
+ login: user.login,
+ name: user.name,
+ avatar_url: "/images/#{url_to_avatar(user)}",
+ status: bidding_user.status
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb
index 3368da12..7e474611 100644
--- a/app/controllers/account_controller.rb
+++ b/app/controllers/account_controller.rb
@@ -681,7 +681,7 @@ class AccountController < ApplicationController
render :json => req
end
- # 发送验证码:type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码
+ # 发送验证码:type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码 9:验证手机号有效
# 验证码是否有效
def valid_verification_code
req = Hash.new(false)
@@ -700,7 +700,7 @@ class AccountController < ApplicationController
render :json => req
end
- # 发送验证码:type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码
+ # 发送验证码:type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码 9:验证手机号有效
def get_verification_code
code = %W(0 1 2 3 4 5 6 7 8 9)
type = params[:type].to_i
@@ -787,6 +787,22 @@ class AccountController < ApplicationController
else
req[:status] = 2
end
+ elsif params[:type] == 9
+ if params[:value] =~ /^1\d{10}$/
+ begin
+ verification_code = code.sample(6).join
+ status = Trustie::Sms.send(mobile: params[:value], code: verification_code)
+ if status == 0
+ VerificationCode.create(:phone => params[:value], :status => 1, :code_type => type, :code => verification_code)
+ end
+ req[:msg] = code_msg status
+ rescue => e
+ Rails.logger.error "发送验证码出错: #{e}"
+ end
+ req[:status] = 1
+ else
+ req[:status] = 2
+ end
else
if params[:value] =~ /^[a-zA-Z0-9]+([._\\]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/
if User.where(:mail => params[:value]).count > 0
diff --git a/app/controllers/managements/project_package_applies_controller.rb b/app/controllers/managements/project_package_applies_controller.rb
new file mode 100644
index 00000000..6394b745
--- /dev/null
+++ b/app/controllers/managements/project_package_applies_controller.rb
@@ -0,0 +1,46 @@
+class Managements::ProjectPackageAppliesController < Managements::BaseController
+ before_filter :set_menu_type
+
+ def index
+ applies = ProjectPackageApply.order('project_package_applies.updated_at desc')
+
+ search = params[:search].to_s.strip
+ if search.present?
+ applies = applies.joins(:project_package).where('project_packages.title like :search', search: "%#{search}%")
+ end
+
+ applies = applies.where(status: params[:status].presence || :pending)
+
+ @applies = paginateHelper applies.includes(project_package: { creator: :user_extensions })
+
+ respond_to do |format|
+ format.js
+ format.html
+ end
+ end
+
+ def agree
+ ProjectPackages::AgreeApplyService.new(current_apply).call
+ render json: { status: 0 }
+ rescue ProjectPackages::AgreeApplyService::Error => e
+ render json: { status: -1, message: e.message }
+ end
+
+ def refuse
+ ProjectPackages::RefuseApplyService.new(current_apply, reason: params[:reason]).call
+ render json: { status: 0 }
+ rescue ProjectPackages::RefuseApplyService::Error => e
+ render json: { status: -1, message: e.message }
+ end
+
+ private
+
+ def current_apply
+ @_current_apply ||= ProjectPackageApply.find(params[:id])
+ end
+
+ def set_menu_type
+ @menu_type = 10
+ @sub_type = 9
+ end
+end
\ No newline at end of file
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index b65a2fa8..51825709 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -754,6 +754,7 @@ module ApplicationHelper
when 6 then '实训课程发布'
when 7 then '职业认证'
when 8 then '教学案例发布'
+ when 9 then '众包需求发布'
else '职业认证'
end
when 11
@@ -7453,6 +7454,8 @@ def tiding_url tiding
my_account_path
when 'Library'
tiding.tiding_type == 'Apply' ? library_applies_path : library_path(tiding.container_id)
+ when 'ProjectPackage'
+ tiding.container.present? ? "/project_packages/#{tiding.container_id}" : 'javascript:void(0)'
end
end
diff --git a/app/models/bidding_user.rb b/app/models/bidding_user.rb
new file mode 100644
index 00000000..5869b6d4
--- /dev/null
+++ b/app/models/bidding_user.rb
@@ -0,0 +1,20 @@
+class BiddingUser < ActiveRecord::Base
+ include AASM
+
+ belongs_to :user
+ belongs_to :project_package, counter_cache: true
+
+ aasm(:status) do
+ state :pending, initiali: true
+ state :bidding_won
+ state :bidding_lost
+
+ event :win do
+ transitions from: [:pending], to: :bid_won
+ end
+
+ event :lose do
+ transitions from: [:pending], to: :bid_lost
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/models/project_package.rb b/app/models/project_package.rb
new file mode 100644
index 00000000..b9b6ac46
--- /dev/null
+++ b/app/models/project_package.rb
@@ -0,0 +1,76 @@
+class ProjectPackage < ActiveRecord::Base
+ include AASM
+
+ acts_as_attachable
+
+ CATEGORY_VALUES = %w(front backend mobile database cloud_compute_and_big_data devops_and_test ai other)
+
+ attr_accessible :title, :content, :category, :deadline_at, :contact_name, :contact_phone, :min_price, :max_price
+
+ belongs_to :creator, class_name: 'User'
+
+ has_many :project_package_applies, dependent: :destroy
+ has_one :process_project_package_apply, conditions: { status: :pending }, class_name: 'ProjectPackageApply'
+
+ has_many :bidding_users, dependent: :delete_all
+ has_many :win_bidding_users, conditions: { status: :bidding_won }, class_name: 'BiddingUser'
+ has_many :lose_bidding_users, conditions: { status: :bidding_lost }, class_name: 'BiddingUser'
+
+ has_many :attachments, as: :container, dependent: :destroy
+
+ validates :category, presence: true, inclusion: { in: CATEGORY_VALUES }
+ validates :title, presence: true, length: { maximum: 60 }
+ validates :content, presence: true
+ validates :deadline_at, presence: true
+ validates :contact_name, presence: true, length: { maximum: 20 }
+ validates :contact_phone, presence: true, format: { with: /1\d{10}/ }
+ validates :min_price, numericality: { greater_than: 0 }
+ validates :max_price, numericality: { greater_than: ->(obj){ obj.min_price } }, allow_blank: true
+
+ aasm(:status) do
+ state :pending, initiali: true
+ state :applying
+ state :refused
+ state :published
+ state :bidding_ended
+ state :bidding_finished
+
+ event :apply do
+ transitions from: [:pending, :refused], to: :applying
+ end
+
+ event :refuse do
+ transitions from: :applying, to: :refused
+ end
+
+ event :publish do
+ transitions from: :applying, to: :published
+ end
+
+ event :end_bidding do
+ transitions from: :published, to: :bidding_ended
+ end
+
+ event :finish_bidding do
+ transitions from: [:bidding_ended], to: :bidding_finished
+ end
+ end
+
+ def editable?
+ pending? || applying? || refused?
+ end
+
+ def bidding_end?
+ flag = deadline_at < Time.now
+ end_bidding! if flag && can_end_bidding?
+ flag
+ end
+
+ def can_bidding?(user)
+ published? && !bidding_end? && !bidding_users.exists?(user_id: user.id)
+ end
+
+ def increment_visit_count!
+ ProjectPackage.connection.execute("update project_packages set visit_count = COALESCE(visit_count, 0) + 1 where id = #{id}")
+ end
+end
\ No newline at end of file
diff --git a/app/models/project_package_apply.rb b/app/models/project_package_apply.rb
new file mode 100644
index 00000000..06c7493f
--- /dev/null
+++ b/app/models/project_package_apply.rb
@@ -0,0 +1,21 @@
+class ProjectPackageApply < ActiveRecord::Base
+ include AASM
+
+ attr_accessible :project_package_id, :reason
+
+ belongs_to :project_package
+
+ aasm(:status) do
+ state :pending, initiali: true
+ state :refused
+ state :agreed
+
+ event :refuse do
+ transitions from: :pending, to: :refused
+ end
+
+ event :agree do
+ transitions from: :pending, to: :agreed
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/models/tiding.rb b/app/models/tiding.rb
index 5107374e..986d9cef 100644
--- a/app/models/tiding.rb
+++ b/app/models/tiding.rb
@@ -365,6 +365,27 @@ class Tiding < ActiveRecord::Base
elsif tiding_type == 'System'
text = status == 1 ? "审核已通过" : "审核未通过,
原因:#{extra}"
"你提交的发布教学案例申请:#{library.try(:title)},#{text}"
+ end
+ when 'ProjectPackage'
+ package = ProjectPackage.find_by_id(container_id)
+ case tiding_type
+ when 'Apply' then
+ "申请发布众包需求:#{package.try(:title)}"
+ when 'System' then
+ text = status == 1 ? "审核已通过" : "审核未通过,
原因:#{extra}"
+ "你提交的众包需求申请:#{package.try(:title)},#{text}"
+ when 'Created' then
+ "你创建了众包需求:#{package.try(:title)}"
+ when 'Destroyed' then
+ "你删除了众包需求:#{extra}"
+ when 'Bidding' then
+ "应征了你发布的众包任务:#{package.try(:title)}"
+ when 'BiddingEnd' then
+ "你发布的众包任务:#{package.try(:title)},已进入选标阶段,请尽快进行选择确认!"
+ when 'BiddingWon' then
+ "恭喜,你应征的众包任务:#{package.try(:title)},在评选环节中标了"
+ when 'BiddingLost' then
+ "很遗憾,你应征投稿的众包任务:#{package.try(:title)},未中标"
end
else
logger.error "error type: 1"
diff --git a/app/models/user.rb b/app/models/user.rb
index c206d51e..b5545206 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -274,6 +274,9 @@ class User < Principal
has_one :user_source
+ # 众包
+ has_many :project_packages, foreign_key: :creator_id, dependent: :destroy
+
## end
# default_scope -> { includes(:user_extensions, :user_score) }
diff --git a/app/models/verification_code.rb b/app/models/verification_code.rb
index e3fefc83..4bc473b3 100644
--- a/app/models/verification_code.rb
+++ b/app/models/verification_code.rb
@@ -2,4 +2,8 @@ class VerificationCode < ActiveRecord::Base
#status:发送状态
#code_type:发送类型:type 1:注册手机验证码 2:找回密码手机验证码 3:找回密码邮箱验证码 4:绑定手机 5:绑定邮箱 6:手机验证码登录 7:邮箱验证码登录 8:邮箱注册验证码
attr_accessible :code, :code_type, :email, :phone, :status
+
+ def valid_code?
+ (Time.now.to_i - created_at.to_i) <= 10*60
+ end
end
diff --git a/app/services/project_packages/agree_apply_service.rb b/app/services/project_packages/agree_apply_service.rb
new file mode 100644
index 00000000..c8d8ebde
--- /dev/null
+++ b/app/services/project_packages/agree_apply_service.rb
@@ -0,0 +1,36 @@
+class ProjectPackages::AgreeApplyService
+ Error = Class.new(StandardError)
+
+ attr_reader :apply, :package
+
+ def initialize(apply)
+ @apply = apply
+ @package = apply.project_package
+ end
+
+ def call
+ raise Error, '该状态下不能进行此操作' unless apply.may_agree? && package.may_publish?
+
+ ActiveRecord::Base.transaction do
+ apply.agree!
+
+ # 发布
+ package.publish
+ package.published_at = Time.now
+ package.save!
+
+ # 消息
+ send_agree_notify!
+ end
+ end
+
+ private
+ def send_agree_notify!
+ Tiding.where(container_id: package.id, container_type: 'ProjectPackage',
+ tiding_type: 'Apply', status: 0).update_all(status: 1)
+
+ Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
+ container_id: package.id, container_type: 'ProjectPackage',
+ tiding_type: 'System', status: 1)
+ end
+end
\ No newline at end of file
diff --git a/app/services/project_packages/apply_publish_service.rb b/app/services/project_packages/apply_publish_service.rb
new file mode 100644
index 00000000..45aee77b
--- /dev/null
+++ b/app/services/project_packages/apply_publish_service.rb
@@ -0,0 +1,31 @@
+class ProjectPackages::ApplyPublishService
+ Error = Class.new(StandardError)
+
+ attr_reader :package
+
+ def initialize(package)
+ @package = package
+ end
+
+ def call
+ return if package.applying?
+
+ raise Error, '该状态下不能申请发布' if package.can_apply?
+
+ ActiveRecord::Base.transaction do
+ package.apply!
+
+ package.project_package_applies.create!
+
+ send_project_package_apply_notify!
+ end
+ end
+
+ private
+
+ def send_project_package_apply_notify!
+ Tiding.create!(user_id: 1, trigger_user_id: package.creator_id,
+ container_id: package.id, container_type: 'ProjectPackage',
+ tiding_type: 'Apply', status: 0)
+ end
+end
diff --git a/app/services/project_packages/bidding_service.rb b/app/services/project_packages/bidding_service.rb
new file mode 100644
index 00000000..9ad9ce4f
--- /dev/null
+++ b/app/services/project_packages/bidding_service.rb
@@ -0,0 +1,29 @@
+class ProjectPackages::BiddingService
+ Error = Class.new(StandardError)
+
+ attr_reader :package, :user
+
+ def initialize(package, user)
+ @package = package
+ @user = user
+ end
+
+ def call
+ raise Error, '竞标已截止' if package.bidding_end?
+ raise Error, '不能参与自己发布的竞标' if package.creator_id == user.id
+ raise Error, '您已参与竞标' if package.bidding_users.exists?(user_id: user.id)
+
+ ActiveRecord::Base.transaction do
+ package.bidding_users.create!(user_id: user.id)
+
+ send_bidding_notify!
+ end
+ end
+
+ private
+
+ def send_bidding_notify!
+ Tiding.create!(user_id: package.creator_id, trigger_user_id: user.id,
+ container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'Bidding')
+ end
+end
\ No newline at end of file
diff --git a/app/services/project_packages/end_bidding_service.rb b/app/services/project_packages/end_bidding_service.rb
new file mode 100644
index 00000000..86be02c7
--- /dev/null
+++ b/app/services/project_packages/end_bidding_service.rb
@@ -0,0 +1,26 @@
+class ProjectPackages::EndBiddingService
+ attr_reader :package
+
+ def initialize(package)
+ @package = package
+ end
+
+ def call
+ return unless package_deadline?
+
+ package.end_bidding!
+
+ send_bidding_end_notify!
+ end
+
+ private
+
+ def send_bidding_end_notify!
+ Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
+ container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'BiddingEnd')
+ end
+
+ def package_deadline?
+ package.deadline_at < Time.now
+ end
+end
\ No newline at end of file
diff --git a/app/services/project_packages/refuse_apply_service.rb b/app/services/project_packages/refuse_apply_service.rb
new file mode 100644
index 00000000..ee4c5bb7
--- /dev/null
+++ b/app/services/project_packages/refuse_apply_service.rb
@@ -0,0 +1,38 @@
+class ProjectPackages::RefuseApplyService
+ Error = Class.new(StandardError)
+
+ attr_reader :apply, :package, :params
+
+ def initialize(apply, params)
+ @apply = apply
+ @package = apply.project_package
+ @params = params
+ end
+
+ def call
+ raise Error, '该状态下不能进行此操作' unless apply.may_refuse? && package.may_refuse?
+
+ ActiveRecord::Base.transaction do
+ apply.refuse
+ apply.reason = params[:reason].to_s.strip
+ apply.save!
+
+ # 发布
+ package.refuse!
+
+ # 消息
+ send_refuse_notify!
+ end
+ end
+
+ private
+
+ def send_refuse_notify!
+ Tiding.where(container_id: package.id, container_type: 'ProjectPackage',
+ tiding_type: 'Apply', status: 0).update_all(status: 1)
+
+ Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
+ container_id: package.id, container_type: 'ProjectPackage',
+ tiding_type: 'System', status: 2, extra: apply.reason)
+ end
+end
diff --git a/app/services/project_packages/save_service.rb b/app/services/project_packages/save_service.rb
new file mode 100644
index 00000000..550ef346
--- /dev/null
+++ b/app/services/project_packages/save_service.rb
@@ -0,0 +1,66 @@
+class ProjectPackages::SaveService
+ Error = Class.new(StandardError)
+
+ attr_reader :package, :params
+
+ def initialize(package, params)
+ @package = package
+ @params = params
+ end
+
+ def call
+ check_code_valid! if need_check_code?
+
+ is_create = package.new_record?
+
+ ActiveRecord::Base.transaction do
+ package.assign_attributes(params)
+ package.save!
+
+ # 处理附件
+ deal_attachments
+
+ send_create_notify! if is_create
+
+ ProjectPackages::ApplyPublishService.call(package) if with_publish?
+ end
+
+
+ package
+ rescue ProjectPackages::ApplyPublishService::Error => ex
+ raise Error, ex.message
+ end
+
+ private
+
+ def need_check_code?
+ (package.new_record? && params[:contact_phone] != package.creator.phone) || package.contact_phone != params[:contact_phone]
+ end
+
+ def check_code_valid!
+ raise Error, '验证码不能为空' if params[:code].blank?
+
+ code = VerificationCode.where(phone: params[:contact_phone], code_type: 9, code: params[:code]).last
+ raise Error, '无效的验证码' if code.blank? || !code.valid_code?
+ end
+
+ def deal_attachments
+ attachment_ids = Array.wrap(params[:attachment_ids]).compact.map(&:to_i) || []
+ old_attachment_ids = package.attachments.pluck(:id)
+
+ destroy_ids = old_attachment_ids - attachment_ids
+ package.attachments.where(id: destroy_ids).delete_all
+
+ new_ids = attachment_ids - old_attachment_ids
+ Attachment.where(id: new_ids).update_all(container_type: package.id, container_type: 'ProjectPackage') if new_ids.present?
+ end
+
+ def send_create_notify!
+ Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
+ container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'Created')
+ end
+
+ def with_publish?
+ params[:publish].to_s == 'true'
+ end
+end
\ No newline at end of file
diff --git a/app/services/project_packages/win_bidding_service.rb b/app/services/project_packages/win_bidding_service.rb
new file mode 100644
index 00000000..f3efdcb7
--- /dev/null
+++ b/app/services/project_packages/win_bidding_service.rb
@@ -0,0 +1,47 @@
+class ProjectPackages::WinBiddingService
+ Error = Class.new(StandardError)
+
+ attr_reader :package, :params
+
+ def initialize(package, params)
+ @package = package
+ @params = params
+ end
+
+ def call
+ raise Error, '竞标报名还未结束' unless package.bidding_end?
+ raise Error, '该状态下不能选择中标者' unless package.can_finish_bidding?
+
+ win_user_ids = Array.wrap(params[:user_ids]).compact.map(&:to_i)
+ bidding_user_ids = package.bidding_users.pluck(:user_id)
+
+ win_user_ids = bidding_user_ids & win_user_ids
+ raise Error, '请选择中标者' if win_user_ids.blank?
+
+ ActiveRecord::Base.transaction do
+ package.finish_bidding!
+
+ # win bidding users
+ package.bidding_users.where(user_id: win_user_ids).update_all(status: :bidding_won)
+ # lose bidding users
+ lost_user_ids = bidding_user_ids - win_user_ids
+ package.bidding_users.where(user_id: lost_user_ids).update_all(status: :bidding_lost)
+
+ send_bidding_result_notify!('BiddingWon', win_user_ids)
+ send_bidding_result_notify!('BiddingLost', lost_user_ids)
+ end
+
+ package
+ end
+
+ private
+
+ def send_bidding_result_notify!(type, user_ids)
+ sql = 'insert into tidings(user_id, trigger_user_id, container_id, container_type, tiding_type) values'
+ base_opts = [package.creator_id, package.id, 'ProjectPackage', type]
+
+ values = '(' + user_ids.map { |id| base_opts.clone.unshift(id).join(',') }.join('),(') + ')'
+
+ sql + values
+ end
+end
\ No newline at end of file
diff --git a/app/views/layouts/base_management.html.erb b/app/views/layouts/base_management.html.erb
index d17a05de..c59f80a8 100644
--- a/app/views/layouts/base_management.html.erb
+++ b/app/views/layouts/base_management.html.erb
@@ -117,6 +117,7 @@
众包需求发布
++ <%= link_to "全部", project_package_applies_path(status: [:refused, :agreed]), :class => "edu-filter-cir-grey mr5 fl font-12 active", :id => "project_package_all_authentication", :remote => true %> + <%= link_to "同意", project_package_applies_path(status: :agreed), :class => "edu-filter-cir-grey mr5 fl font-12", :id => "project_package_agree_authentication", :remote => true %> + <%= link_to "拒绝", project_package_applies_path(status: :refused), :class => "edu-filter-cir-grey mr5 fl font-12", :id => "project_package_reject_authentication", :remote => true %> +
+ + +