require 'json-schema' module Ecom module Core class TaskTemplate < ApplicationRecord TASK_STEP_SCHEMA = { 'type' => 'object', 'required' => %w[name total_contribution_percentage_to_task], 'additionalProperties' => false, 'properties' => { 'name' => { 'type' => 'string' }, 'total_contribution_percentage_to_task' => { 'type' => 'integer', 'minimum' => 1, 'maximum' => 100 } } }.freeze # here the json schema has a bug ( including all properties as required) # and because task_step_name is required on a condition ( only if the # milestone is after a task step ), we can solve this with a workaround # by specifying task_step_name in patternProperties. MILESTONE_SCHEMA = { 'type' => 'object', 'required' => %w[name is_after_task_step], 'additionalProperties' => false, 'properties' => { 'name' => { 'type' => 'string' }, 'is_after_task_step' => { 'type' => 'boolean' } }, "patternProperties": { "task_step_name": { 'type' => 'string' } } }.freeze has_ancestry belongs_to :task_template_type has_and_belongs_to_many :work_product_templates, join_table: 'ecom_core_task_templates_work_product_templates' has_and_belongs_to_many :resource_types, join_table: 'ecom_core_task_templates_resource_types' belongs_to :takeoff_calculator, optional: true validates :name, :code, presence: true validates :code, uniqueness: true delegate(:name, to: :task_template_type, prefix: true) validate :task_steps_schema_validator validate :milestones_schema_validator validate :validate_sum_of_task_step_contribution validate :validate_milestone_task_step_association def full_name parent_name = parent&.name return name unless parent_name "#{name} - #{parent_name}" end def crew_types resource_types.where(type: 'Ecom::Core::CrewType') end def material_types resource_types.where(type: 'Ecom::Core::MaterialType') end def equipment_types resource_types.where(type: 'Ecom::Core::EquipmentType') end def task_steps_schema_validator return if task_steps.nil? validation_errors = JSON::Validator.fully_validate(TASK_STEP_SCHEMA, task_steps, strict: true, list: true, clear_cache: true) errors.add(:task_steps, validation_errors) unless validation_errors.empty? end def milestones_schema_validator return if milestones.nil? validation_errors = JSON::Validator.fully_validate(MILESTONE_SCHEMA, milestones, strict: true, list: true, clear_cache: true) errors.add(:milestones, validation_errors) unless validation_errors.empty? end def validate_sum_of_task_step_contribution return if task_steps.nil? filtered_task_steps = task_steps.select { |task_step| task_step['total_contribution_percentage_to_task'] } sum_of_task_step_contribution = filtered_task_steps.sum { |task_step| task_step['total_contribution_percentage_to_task'] } errors.add(:task_steps, 'Sum of task step contributions has to be 100') if sum_of_task_step_contribution != 100 end def validate_milestone_task_step_association return if milestones.nil? task_step_names = task_steps&.map { |task_step| task_step['name'] } milestones_associated_with_task_step = milestones.select { |milestone| milestone['is_after_task_step'] } milestones_associated_with_task_step.each do |milestone| if milestone['task_step_name'].nil? errors.add(:milestones, 'if the milestone is after a task step, please specify the task step name') next end unless task_step_names.include? milestone['task_step_name'] errors.add(:milestone, 'task step for the given milestone does not exist in list of task steps') end end end end end end