diff --git a/Gemfile b/Gemfile index 4cabd09b9..44f4937ad 100644 --- a/Gemfile +++ b/Gemfile @@ -91,3 +91,5 @@ gem 'bulk_insert' # elasticsearch gem 'searchkick' + +gem 'aasm' diff --git a/Gemfile.lock b/Gemfile.lock index 94893687b..60e1ae811 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,6 +8,8 @@ PATH GEM remote: https://gems.ruby-china.com/ specs: + aasm (5.0.5) + concurrent-ruby (~> 1.0) actioncable (5.2.1) actionpack (= 5.2.1) nio4r (~> 2.0) @@ -324,6 +326,7 @@ PLATFORMS ruby DEPENDENCIES + aasm active_decorator acts-as-taggable-on (~> 6.0) awesome_print diff --git a/app/controllers/bidding_users_controller.rb b/app/controllers/bidding_users_controller.rb new file mode 100644 index 000000000..ad0de6587 --- /dev/null +++ b/app/controllers/bidding_users_controller.rb @@ -0,0 +1,24 @@ +class BiddingUsersController < ApplicationController + before_action :require_login, :check_auth + + def create + ProjectPackages::BiddingService.call(current_package, current_user) + render_ok + rescue ProjectPackages::BiddingService::Error => ex + render_error(ex.message) + end + + def win + package = current_user.project_packages.find(params[:project_package_id]) + ProjectPackages::WinBiddingService.call(package, params) + render_ok + rescue ProjectPackages::WinBiddingService::Error => ex + render_error(ex.message) + end + + private + + def current_package + @_current_package ||= ProjectPackage.find(params[:project_package_id]) + end +end \ No newline at end of file diff --git a/app/controllers/project_package_categories_controller.rb b/app/controllers/project_package_categories_controller.rb new file mode 100644 index 000000000..80d364a05 --- /dev/null +++ b/app/controllers/project_package_categories_controller.rb @@ -0,0 +1,6 @@ +class ProjectPackageCategoriesController < ApplicationController + def index + categories = ProjectPackageCategory.cached_data + render_ok(count: categories.size, categories: categories) + end +end \ No newline at end of file diff --git a/app/controllers/project_packages_controller.rb b/app/controllers/project_packages_controller.rb new file mode 100644 index 000000000..de896c920 --- /dev/null +++ b/app/controllers/project_packages_controller.rb @@ -0,0 +1,78 @@ +class ProjectPackagesController < ApplicationController + include PaginateHelper + + before_action :require_login, :check_auth, only: %i[create update destroy] + + helper_method :current_package, :package_manageable? + + def index + packages = ProjectPackage.where(status: %w(published bidding_ended bidding_finished)) + + packages = packages.where(project_package_category_id: params[:category_id]) if params[:category_id].present? + + keyword = params[:keyword].to_s.strip + packages = packages.where('title LIKE ?', "%#{keyword}%") if keyword.present? + + @count = packages.count + + direction = params[:sort_direction] == 'asc' ? 'asc' : 'desc' + sort = params[:sort_by] == 'price' ? 'min_price' : 'published_at' + packages = packages.order("#{sort} #{direction}") + + @packages = paginate packages.includes(:creator, :attachments, :project_package_category, bidding_users: :user) + end + + def show + return render_forbidden unless current_package.visitable? || package_manageable? + + current_package.increment!(:visit_count) + end + + def create + package = current_user.project_packages.new + ProjectPackages::SaveService.call(package, save_params) + + package.increment!(:visit_count) + render_ok(id: package.id) + rescue ProjectPackages::SaveService::Error => ex + render_error(ex.message) + end + + def update + package = current_user.project_packages.find(params[:id]) + return render_error('该状态下不能编辑') unless package.editable? + + ProjectPackages::SaveService.call(package, save_params) + package.increment!(:visit_count) + render_ok(id: package.id) + rescue ProjectPackages::SaveService::Error => ex + render_error(ex.message) + end + + def destroy + package = ProjectPackage.find(params[:id]) + return render_forbidden unless package.deletable? && package_manageable? + + package.destroy! + + Tiding.create!(user_id: package.creator_id, trigger_user_id: 1, container_id: package.id, + container_type: 'ProjectPackage', tiding_type: 'Destroyed', extra: package.title) + + render_ok + end + + private + + def current_package + @_current_package ||= ProjectPackage.find(params[:id]) + end + + def package_manageable? + current_user&.id == current_package.creator_id || admin_or_business? + end + + def save_params + params.permit(*%i[category_id title content attachment_ids deadline_at min_price max_price + contact_name contact_phone code publish]) + end +end \ No newline at end of file diff --git a/app/decorators/project_package_decorator.rb b/app/decorators/project_package_decorator.rb new file mode 100644 index 000000000..7dec24565 --- /dev/null +++ b/app/decorators/project_package_decorator.rb @@ -0,0 +1,5 @@ +module ProjectPackageDecorator + extend ApplicationDecorator + + display_time_method :updated_at, :deadline_at, :published_at +end \ No newline at end of file diff --git a/app/forms/project_packages/project_packages/agree_apply_service.rb b/app/forms/project_packages/project_packages/agree_apply_service.rb new file mode 100644 index 000000000..7cef7f70c --- /dev/null +++ b/app/forms/project_packages/project_packages/agree_apply_service.rb @@ -0,0 +1,36 @@ +class ProjectPackages::AgreeApplyService < ApplicationService + 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/forms/project_packages/project_packages/apply_publish_service.rb b/app/forms/project_packages/project_packages/apply_publish_service.rb new file mode 100644 index 000000000..157ed2cb6 --- /dev/null +++ b/app/forms/project_packages/project_packages/apply_publish_service.rb @@ -0,0 +1,31 @@ +class ProjectPackages::ApplyPublishService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :package + + def initialize(package) + @package = package + end + + def call + return if package.applying? + + raise Error, '该状态下不能申请发布' unless package.may_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/forms/project_packages/project_packages/bidding_service.rb b/app/forms/project_packages/project_packages/bidding_service.rb new file mode 100644 index 000000000..125f707e7 --- /dev/null +++ b/app/forms/project_packages/project_packages/bidding_service.rb @@ -0,0 +1,29 @@ +class ProjectPackages::BiddingService < ApplicationService + 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/forms/project_packages/project_packages/end_bidding_service.rb b/app/forms/project_packages/project_packages/end_bidding_service.rb new file mode 100644 index 000000000..6d43db949 --- /dev/null +++ b/app/forms/project_packages/project_packages/end_bidding_service.rb @@ -0,0 +1,26 @@ +class ProjectPackages::EndBiddingService < ApplicationService + 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.may_end_bidding? && package.deadline_at < Time.now + end +end \ No newline at end of file diff --git a/app/forms/project_packages/project_packages/refuse_apply_service.rb b/app/forms/project_packages/project_packages/refuse_apply_service.rb new file mode 100644 index 000000000..142efe1e0 --- /dev/null +++ b/app/forms/project_packages/project_packages/refuse_apply_service.rb @@ -0,0 +1,38 @@ +class ProjectPackages::RefuseApplyService < ApplicationService + 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/forms/project_packages/project_packages/save_service.rb b/app/forms/project_packages/project_packages/save_service.rb new file mode 100644 index 000000000..bcfc19a10 --- /dev/null +++ b/app/forms/project_packages/project_packages/save_service.rb @@ -0,0 +1,79 @@ +class ProjectPackages::SaveService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :package, :params + + def initialize(package, params) + @package = package + @params = params + end + + def call + ProjectPackages::SaveForm.new(params).validate! + + check_code_valid! if need_check_code? + + is_create = package.new_record? + raise Error, '类型不存在' unless ProjectPackageCategory.where(id: params[:category_id]).exists? + params[:project_package_category_id] = params[:category_id].to_i + + raise Error, '竞标截止时间不能小于当前时间' if params[:deadline_at].present? && params[:deadline_at].to_time < Time.now + + if params[:min_price].blank? && params[:max_price].present? + params[:min_price] = params[:max_price] + params[:max_price] = nil + end + + 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.new_record? && 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 + if new_ids.present? + Attachment.where(id: new_ids, container_id: nil).update_all(container_id: package.id, container_type: 'ProjectPackage') + end + 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/forms/project_packages/project_packages/win_bidding_service.rb b/app/forms/project_packages/project_packages/win_bidding_service.rb new file mode 100644 index 000000000..831c29449 --- /dev/null +++ b/app/forms/project_packages/project_packages/win_bidding_service.rb @@ -0,0 +1,50 @@ +class ProjectPackages::WinBiddingService < ApplicationService + 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.may_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) + columns = %i[user_id trigger_user_id container_id container_type tiding_type created_at updated_at] + + Tiding.bulk_insert(*columns) do |worker| + base_attr = { trigger_user_id: package.creator_id, container_id: package.id, + container_type: 'ProjectPackage', tiding_type: type } + user_ids.each do |user_id| + worker.add(base_attr.merge(user_id: user_id)) + end + end + end +end \ No newline at end of file diff --git a/app/forms/project_packages/save_form.rb b/app/forms/project_packages/save_form.rb new file mode 100644 index 000000000..9755e1ddc --- /dev/null +++ b/app/forms/project_packages/save_form.rb @@ -0,0 +1,15 @@ +class ProjectPackages::SaveForm + include ActiveModel::Model + + attr_accessor :category_id, :title, :content, :attachment_ids, :deadline_at, + :min_price, :max_price, :contact_name, :contact_phone, :code, :publish + + validates :category_id, presence: true + validates :title, presence: true + validates :content, presence: true + validates :deadline_at, presence: true + validates :min_price, numericality: { greater_than: 0 }, allow_blank: true + validates :max_price, numericality: { greater_than: ->(obj){ obj.min_price.to_i } }, allow_blank: true + validates :contact_name, presence: true + validates :contact_phone, presence: true +end diff --git a/app/models/bidding_user.rb b/app/models/bidding_user.rb new file mode 100644 index 000000000..b518fb45e --- /dev/null +++ b/app/models/bidding_user.rb @@ -0,0 +1,24 @@ +class BiddingUser < ApplicationRecord + 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 + + def status_text + I18n.t("bidding_user.status.#{status}") + 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 000000000..4b160f99d --- /dev/null +++ b/app/models/project_package.rb @@ -0,0 +1,78 @@ +class ProjectPackage < ApplicationRecord + include AASM + + belongs_to :creator, class_name: 'User' + belongs_to :project_package_category + + has_many :project_package_applies, dependent: :destroy + has_one :process_project_package_apply, -> { where(status: :pending) }, class_name: 'ProjectPackageApply' + + has_many :bidding_users, dependent: :delete_all + has_many :win_bidding_users, -> { where(status: :bidding_won) }, class_name: 'BiddingUser' + has_many :lose_bidding_users, -> { where(status: :bidding_lost) }, class_name: 'BiddingUser' + + has_many :attachments, as: :container, dependent: :destroy + + 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 category_name + project_package_category.name + end + + def visitable? + !editable? + end + + def editable? + pending? || applying? || refused? + end + + def deletable? + pending? || refused? + end + + def deadline? + deadline_at < Time.now + end + + def bidding_end? + flag = deadline? + end_bidding! if flag && may_end_bidding? + flag + end + + def can_bidding?(user) + published? && !bidding_end? && user.id != creator_id && !bidding_users.exists?(user_id: user.id) + end + + def status_text + I18n.t("project_package.status.#{status}") + end +end diff --git a/app/models/project_package_apply.rb b/app/models/project_package_apply.rb new file mode 100644 index 000000000..5116f075f --- /dev/null +++ b/app/models/project_package_apply.rb @@ -0,0 +1,19 @@ +class ProjectPackageApply < ApplicationRecord + include AASM + + 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/project_package_category.rb b/app/models/project_package_category.rb new file mode 100644 index 000000000..403fdf4cb --- /dev/null +++ b/app/models/project_package_category.rb @@ -0,0 +1,23 @@ +class ProjectPackageCategory < ApplicationRecord + default_scope { order(position: :asc) } + + has_many :project_packages, dependent: :destroy + + after_commit :reset_cache_data + + def self.cached_data + Rails.cache.fetch(data_cache_key, expires_in: 1.days) do + ProjectPackageCategory.select(:id, :name).as_json + end + end + + def self.data_cache_key + 'project_package_category/cached_data' + end + + private + + def reset_cache_data + Rails.cache.delete(self.class.data_cache_key) + end +end \ No newline at end of file diff --git a/app/services/project_packages/save_service.rb b/app/services/project_packages/save_service.rb new file mode 100644 index 000000000..b921d7967 --- /dev/null +++ b/app/services/project_packages/save_service.rb @@ -0,0 +1,79 @@ +class ProjectPackages::SaveService + Error = Class.new(StandardError) + + attr_reader :package, :params + + def initialize(package, params) + @package = package + @params = params + end + + def call + ProjectPackages::SaveForm.new(params).validate! + + check_code_valid! if need_check_code? + + is_create = package.new_record? + raise Error, '类型不存在' unless ProjectPackageCategory.where(id: params[:category_id]).exists? + params[:project_package_category_id] = params[:category_id].to_i + + raise Error, '竞标截止时间不能小于当前时间' if params[:deadline_at].present? && params[:deadline_at].to_time < Time.now + + if params[:min_price].blank? && params[:max_price].present? + params[:min_price] = params[:max_price] + params[:max_price] = nil + end + + 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.new_record? && 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 + if new_ids.present? + Attachment.where(id: new_ids, container_id: nil).update_all(container_id: package.id, container_type: 'ProjectPackage') + end + 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/views/project_packages/index.json.jbuilder b/app/views/project_packages/index.json.jbuilder new file mode 100644 index 000000000..e26efe36e --- /dev/null +++ b/app/views/project_packages/index.json.jbuilder @@ -0,0 +1,13 @@ +json.count @count +json.project_packages do + json.array! @packages.each do |package| + json.extract! package, :id, :title, :content, :category_name, :status, + :visit_count, :bidding_users_count, :min_price, :max_price + + json.category_id package.project_package_category_id + + json.updated_at package.display_updated_at + json.deadline_at package.display_deadline_at + json.published_at package.display_published_at + end +end \ No newline at end of file diff --git a/app/views/project_packages/show.json.jbuilder b/app/views/project_packages/show.json.jbuilder new file mode 100644 index 000000000..7fee5af29 --- /dev/null +++ b/app/views/project_packages/show.json.jbuilder @@ -0,0 +1,43 @@ +package = current_package + +json.extract! package, :id, :title, :content, :category_name, :status, + :visit_count, :bidding_users_count, :min_price, :max_price + +json.category_id package.project_package_category_id + +# 只有自己和管理员才返回私人信息 +if package_manageable? + json.contact_name package.contact_name + json.contact_phone package.contact_phone +end + +json.updated_at package.display_updated_at +json.deadline_at package.display_deadline_at +json.published_at package.display_published_at + +json.creator do + json.partial! 'users/user_simple', user: package.creator +end + +json.attachments do + json.array! package.attachments, partial: 'attachments/attachment_simple', as: :attachment +end + +json.bidding_users do + json.array! package.bidding_users.includes(:user).each do |bidding_user| + json.partial! 'users/user_simple', user: bidding_user.user + + json.status bidding_user.status + end +end + +json.operation do + if current_user + manageable = package_manageable? + + json.can_bidding package.can_bidding?(current_user) + json.can_select_bidding_user package.bidding_end? && package.bidding_ended? && manageable + json.can_edit package.editable? && manageable + json.can_delete package.deletable? && manageable + end +end \ No newline at end of file diff --git a/config/locales/bidding_users/zh-CN.yml b/config/locales/bidding_users/zh-CN.yml new file mode 100644 index 000000000..6420725fd --- /dev/null +++ b/config/locales/bidding_users/zh-CN.yml @@ -0,0 +1,6 @@ +'zh-CN': + bidding_user: + status: + pending: 竞标中 + bidding_won: 已中标 + bidding_lost: 未中标 \ No newline at end of file diff --git a/config/locales/forms/save_project_package_form.zh-CN.yml b/config/locales/forms/save_project_package_form.zh-CN.yml new file mode 100644 index 000000000..662e59941 --- /dev/null +++ b/config/locales/forms/save_project_package_form.zh-CN.yml @@ -0,0 +1,13 @@ +'zh-CN': + activemodel: + attributes: + project_packages/save_form: + category_id: 类型 + title: 标题 + content: 描述 + deadline_at: 截止日期 + min_price: 最小价格 + max_price: 最大价格 + contact_name: 联系人姓名 + contact_phone: 联系人电话 + code: 验证码 diff --git a/config/locales/project_packages/zh-CN.yml b/config/locales/project_packages/zh-CN.yml new file mode 100644 index 000000000..82075ffab --- /dev/null +++ b/config/locales/project_packages/zh-CN.yml @@ -0,0 +1,9 @@ +zh-CN: + project_package: + status: + pending: 已创建 + applying: 审核中 + refused: 已拒绝 + published: 竞标中 + bidding_ended: 待选标 + bidding_finished: 已完成 \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index cabdcb18e..210071b97 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -682,6 +682,13 @@ Rails.application.routes.draw do resources :students, only: [:index] end end + + resources :project_package_categories, only: [:index] + resources :project_packages, only: [:index, :show, :create, :update, :destroy] do + resources :bidding_users, only: [:create] do + post :win, on: :collection + end + end end #git 认证回调