parent
4b4a0d52e6
commit
4d9ccfc528
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,120 @@
|
|||||||
|
<div class="edu-class-container mb15">
|
||||||
|
<div class="edu-con-top clearfix">
|
||||||
|
<p class="ml15 fl color-grey">众包需求发布</p>
|
||||||
|
</div>
|
||||||
|
<div class="edu-con-bg01 mt15">
|
||||||
|
<div class="edu-tab clearfix mb20">
|
||||||
|
<ul id="edu-tab-nav" class="border-bottom-orange">
|
||||||
|
<li id="edu-tab-nav-1" class="new-tab-nav background-orange" onclick="HoverLi(1);">
|
||||||
|
<%= link_to "待审批", project_package_applies_path(status: :pending), class: 'tab_type', remote: true %>
|
||||||
|
</li>
|
||||||
|
<li id="edu-tab-nav-2" class="new-tab-nav" onclick="HoverLi(2);">
|
||||||
|
<%= link_to "已审批", project_package_applies_path(status: [:refused, :agreed]), class: 'tab_type', remote: true %>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="cl"></div>
|
||||||
|
<div id="edu-tab-con-1">
|
||||||
|
<div class="mt10">
|
||||||
|
<div class="edu-position fr task-form-30 mb10 mr15">
|
||||||
|
<input class="task-form-100 panel-box-sizing" placeholder="输入众包需求标题进行检索" type="text" id="search_name">
|
||||||
|
<a href="javascript:void(0);" class="edu-btn-search font-16 color-grey mt10" id="search"><i class="fa fa-search"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="cl"></div>
|
||||||
|
<div id="authentication_list" class="auth_table">
|
||||||
|
<%= render :partial => "managements/project_package_applies/project_package_apply_list"%>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="edu-tab-con-2" class="undis">
|
||||||
|
<div class="mt10">
|
||||||
|
<p class="fl task-form-60 mt8 ml15 clearfix">
|
||||||
|
<%= 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 %>
|
||||||
|
</p>
|
||||||
|
<div class="edu-position fr task-form-30 mb10 fr mr15">
|
||||||
|
<input class="task-form-100 panel-box-sizing" placeholder="输入教学案例标题、编号进行检索" type="text" id="project_package_search_name">
|
||||||
|
<a href="javascript:void(0);" class="edu-btn-search font-16 color-grey mt10" id="project_package_search"><i class="fa fa-search"></i></a>
|
||||||
|
</div>
|
||||||
|
<div class="cl"></div>
|
||||||
|
<div id="project_package_authentication_list" class="auth_table">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cl"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
/* -------------------------- 拒绝 ------------------------------------ */
|
||||||
|
function reject_project_package_authentication_reason(nThis){
|
||||||
|
var reason = $(nThis).parent().parent().find('div');
|
||||||
|
reason.find("input").val("");
|
||||||
|
reason.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------- 取消 ------------------------------------ */
|
||||||
|
function project_package_hide_reject_reason(nThis){
|
||||||
|
var reason = $(nThis).parent().parent();
|
||||||
|
reason.find("input").val("");
|
||||||
|
reason.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------- 提交拒绝原因 --------------------------------- */
|
||||||
|
function project_package_submit_reject_reason(id, nThis){
|
||||||
|
var nReason = $(nThis).parent().parent();
|
||||||
|
var reason = nReason.find("input").val();
|
||||||
|
if (reason == '') {
|
||||||
|
alert('请输入原因');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: '/managements/project_package_applies/' + id + '/refuse',
|
||||||
|
type: 'post',
|
||||||
|
data: {reason: reason},
|
||||||
|
success: function(data){
|
||||||
|
if (data && data.status != -1) {
|
||||||
|
$('#authentication_list .admin-con-box.apply-' + id).remove();
|
||||||
|
|
||||||
|
if($('#authentication_list .admin-con-box').length == 0){
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------- 按名字进行搜索(未审批) ----------------------------- */
|
||||||
|
$("#search").live("click", function(){
|
||||||
|
var iName = $("#search_name").val();
|
||||||
|
$.ajax({
|
||||||
|
url: "/managements/project_package_applies",
|
||||||
|
dataType: 'script',
|
||||||
|
data: { search: iName, status: 'pending' }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ------------------- 按名字进行搜索(已审批)-------------------- */
|
||||||
|
$("#project_package_search").live("click", function(){
|
||||||
|
var iName = $("#project_package_search_name").val();
|
||||||
|
var id = $("#project_package_all_authentication").parent().find(".active").attr("id");
|
||||||
|
var status = '';
|
||||||
|
|
||||||
|
if(id == "project_package_all_authentication"){
|
||||||
|
status = ['refused', 'agreed'];
|
||||||
|
}else if(id=="project_package_agree_authentication"){
|
||||||
|
status = 'agreed';
|
||||||
|
}else{
|
||||||
|
status = 'refused';
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: "/managements/project_package_applies",
|
||||||
|
dataType: 'script',
|
||||||
|
data: { search: iName, status: status}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
@ -0,0 +1,30 @@
|
|||||||
|
var nTabIcon_1 = $("#edu-tab-con-1");
|
||||||
|
var nTabIcon_2 = $("#edu-tab-con-2");
|
||||||
|
var nTabNav_1 = $("#edu-tab-nav-1");
|
||||||
|
var nTabNav_2 = $("#edu-tab-nav-2");
|
||||||
|
var nAudit = $("#project_package_all_authentication").parent();
|
||||||
|
|
||||||
|
<% if params[:status].to_s == 'pending' %>
|
||||||
|
$("#authentication_list").html("<%= j( render :partial => "managements/project_package_applies/project_package_apply_list" ) %>");
|
||||||
|
nTabNav_1.addClass("background-orange");
|
||||||
|
nTabNav_2.removeClass("background-orange");
|
||||||
|
nTabIcon_1.show();
|
||||||
|
nTabIcon_2.hide();
|
||||||
|
<% else %>
|
||||||
|
$("#project_package_authentication_list").html("<%= j( render :partial => "managements/project_package_applies/project_package_apply_list" ) %>");
|
||||||
|
nTabNav_1.removeClass("background-orange");
|
||||||
|
nTabNav_2.addClass("background-orange");
|
||||||
|
nTabIcon_1.hide();
|
||||||
|
nTabIcon_2.show();
|
||||||
|
/* -------------------------- 未审批(全部、同意、拒绝点击时动态样式) ------------------------------ */
|
||||||
|
if(<%= params[:status].to_s == 'agreed' %>){
|
||||||
|
nAudit.find(".active").removeClass("active");
|
||||||
|
$("#project_package_agree_authentication").addClass("active");
|
||||||
|
}else if(<%= params[:status].to_s == 'refused' %>){
|
||||||
|
nAudit.find(".active").removeClass("active");
|
||||||
|
$("#project_package_reject_authentication").addClass("active");
|
||||||
|
}else{
|
||||||
|
nAudit.find(".active").removeClass("active");
|
||||||
|
$("#project_package_all_authentication").addClass("active");
|
||||||
|
}
|
||||||
|
<% end %>
|
@ -0,0 +1,28 @@
|
|||||||
|
class CreateProjectPackages < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :project_packages do |t|
|
||||||
|
t.references :creator
|
||||||
|
t.string :category
|
||||||
|
t.string :status
|
||||||
|
|
||||||
|
t.string :title
|
||||||
|
t.text :content
|
||||||
|
t.string :contact_name
|
||||||
|
t.string :contact_phone
|
||||||
|
|
||||||
|
t.decimal :min_price
|
||||||
|
t.decimal :max_price
|
||||||
|
|
||||||
|
t.integer :visit_count, default: 0
|
||||||
|
t.integer :bidding_users_count, default: 0
|
||||||
|
|
||||||
|
t.datetime :deadline_at
|
||||||
|
t.datetime :published_at
|
||||||
|
t.datetime :bidding_finished_at
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :project_packages, :published_at
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,12 @@
|
|||||||
|
class CreateBiddingUsers < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :bidding_users do |t|
|
||||||
|
t.references :project_package
|
||||||
|
t.references :user
|
||||||
|
|
||||||
|
t.string :status
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,14 @@
|
|||||||
|
class CreateProjectPackageApplies < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :project_package_applies do |t|
|
||||||
|
t.references :project_package
|
||||||
|
|
||||||
|
t.string :status
|
||||||
|
t.string :reason
|
||||||
|
|
||||||
|
t.datetime :refused_at
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in new issue