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 '实训发布', shixun_authorization_managements_path %>
  • <%= link_to '实践课程发布', subject_authorization_managements_path %>
  • <%= link_to '教学案例发布', library_applies_path(status: :pending) %>
  • +
  • <%= link_to '众包需求发布', project_package_applies_path(status: :pending) %>
  • 认证+ diff --git a/app/views/managements/project_package_applies/_project_package_apply_list.html.erb b/app/views/managements/project_package_applies/_project_package_apply_list.html.erb new file mode 100644 index 00000000..baa5944d --- /dev/null +++ b/app/views/managements/project_package_applies/_project_package_apply_list.html.erb @@ -0,0 +1,76 @@ +<% if @applies.present? %> + <% @applies.each do |apply| %> + <% user = apply.project_package.user %> + <% project_package = apply.project_package %> +
    + + <%= image_tag(url_to_avatar(user), :class => "fl with10 edu-ad-user", :alt => "头像", :width => "50", :height => "50" ) %> + +
    + +
    +
    + <% end %> +
    +
    +
      + <%= pagination_links_full @obj_pages, @obj_count, :per_page_links => false, :remote => true, :flag => true, :is_new => true %> +
    +
    +
    +
    +<% else %> + <%= render :partial => "welcome/no_data" %> +<% end %> + + \ No newline at end of file diff --git a/app/views/managements/project_package_applies/index.html.erb b/app/views/managements/project_package_applies/index.html.erb new file mode 100644 index 00000000..4fbf7f60 --- /dev/null +++ b/app/views/managements/project_package_applies/index.html.erb @@ -0,0 +1,120 @@ +
    +
    +

    众包需求发布

    +
    +
    +
    +
      +
    • + <%= link_to "待审批", project_package_applies_path(status: :pending), class: 'tab_type', remote: true %> +
    • +
    • + <%= link_to "已审批", project_package_applies_path(status: [:refused, :agreed]), class: 'tab_type', remote: true %> +
    • +
    +
    +
    +
    +
    + + +
    +
    +
    + <%= render :partial => "managements/project_package_applies/project_package_apply_list"%> +
    +
    +
    + +
    +
    +

    + <%= 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 %> +

    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/app/views/managements/project_package_applies/index.js.erb b/app/views/managements/project_package_applies/index.js.erb new file mode 100644 index 00000000..4e6d737d --- /dev/null +++ b/app/views/managements/project_package_applies/index.js.erb @@ -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 %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index c9d01c3b..bfdabd5c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -744,6 +744,13 @@ RedmineApp::Application.routes.draw do ## oauth相关 post :refuse end end + + resources :project_package_applies, only: [:index] do + member do + post :agree + post :refuse + end + end end end end diff --git a/db/migrate/20190709004039_create_project_packages.rb b/db/migrate/20190709004039_create_project_packages.rb new file mode 100644 index 00000000..0ded2fdb --- /dev/null +++ b/db/migrate/20190709004039_create_project_packages.rb @@ -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 diff --git a/db/migrate/20190709010031_create_bidding_users.rb b/db/migrate/20190709010031_create_bidding_users.rb new file mode 100644 index 00000000..597d658c --- /dev/null +++ b/db/migrate/20190709010031_create_bidding_users.rb @@ -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 diff --git a/db/migrate/20190709023928_create_project_package_applies.rb b/db/migrate/20190709023928_create_project_package_applies.rb new file mode 100644 index 00000000..6bdff178 --- /dev/null +++ b/db/migrate/20190709023928_create_project_package_applies.rb @@ -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