diff --git a/Gemfile b/Gemfile index dfc8d1bfb..7695fa176 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,7 @@ group :development, :test do gem 'rubocop', '~> 1.45', require: false gem 'rubocop-performance', '~> 1.16', require: false gem 'rubocop-rails', '~> 2.17', require: false + gem 'rubocop-rspec', '~> 1.42', require: false end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 74a1d4551..a9808eaa4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,6 +38,7 @@ PATH activerecord_json_validator (~> 2.1, >= 2.1.3) aws-sdk-s3 byebug + counter_culture (~> 3.2) dry-validation (~> 1.10) elasticsearch (~> 8.5) exception_notification @@ -227,6 +228,9 @@ GEM console (1.23.2) fiber-annotation fiber-local + counter_culture (3.5.2) + activerecord (>= 4.2) + activesupport (>= 4.2) crack (0.4.5) rexml crass (1.0.6) @@ -601,6 +605,8 @@ GEM activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) + rubocop-rspec (1.42.0) + rubocop (>= 0.87.0) ruby-progressbar (1.13.0) ruby-vips (2.1.4) ffi (~> 1.12) @@ -808,6 +814,7 @@ DEPENDENCIES rubocop (~> 1.45) rubocop-performance (~> 1.16) rubocop-rails (~> 2.17) + rubocop-rspec (~> 1.42) shoulda-matchers (~> 5.0) spree_cm_commissioner! spree_dev_tools! diff --git a/app/assets/images/cm-driver-seat-icons.svg b/app/assets/images/cm-driver-seat-icons.svg new file mode 100644 index 000000000..f64ae376b --- /dev/null +++ b/app/assets/images/cm-driver-seat-icons.svg @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/cm-normal-seat-icons.svg b/app/assets/images/cm-normal-seat-icons.svg new file mode 100755 index 000000000..052219489 --- /dev/null +++ b/app/assets/images/cm-normal-seat-icons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/cm-vip-seat-icons.svg b/app/assets/images/cm-vip-seat-icons.svg new file mode 100755 index 000000000..810c431de --- /dev/null +++ b/app/assets/images/cm-vip-seat-icons.svg @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/assets/javascripts/spree_cm_commissioner/backend.js b/app/assets/javascripts/spree_cm_commissioner/backend.js index aa9dea788..60adae16a 100644 --- a/app/assets/javascripts/spree_cm_commissioner/backend.js +++ b/app/assets/javascripts/spree_cm_commissioner/backend.js @@ -3,3 +3,7 @@ //= require spree_cm_commissioner/calendar //= require spree_cm_commissioner/role_form //= require spree_cm_commissioner/address_hide_form +//= require spree_cm_commissioner/vehicle_seat +//= require spree_cm_commissioner/selected_field_filter_origin_destination +//= require spree_cm_commissioner/location_checkbox_manager +//= require spree_cm_commissioner/trip_stop_selection diff --git a/app/assets/javascripts/spree_cm_commissioner/location_checkbox_manager.js b/app/assets/javascripts/spree_cm_commissioner/location_checkbox_manager.js new file mode 100644 index 000000000..3765feeb0 --- /dev/null +++ b/app/assets/javascripts/spree_cm_commissioner/location_checkbox_manager.js @@ -0,0 +1,45 @@ +const LocationCheckboxManager = { + initialize: function () { + const locationCheckboxes = document.querySelectorAll(".location"); + + locationCheckboxes.forEach(function (locationCheckbox) { + locationCheckbox.addEventListener("change", function () { + LocationCheckboxManager.handleCheckboxChange(this); + }); + + LocationCheckboxManager.handleCheckboxChange(locationCheckbox); + }); + }, + + handleCheckboxChange: function (changedCheckbox) { + const pointCheckbox = document.querySelector(".stop"); + const stopCheckbox = document.querySelector(".station"); + const branchCheckbox = document.querySelector(".branch"); + + if (!stopCheckbox || !branchCheckbox || !pointCheckbox) { + alert("Please select at the last one."); + event.preventDefault(); + } + + const isLocationChecked = changedCheckbox.checked; + + if (isLocationChecked) { + stopCheckbox.checked = false; + stopCheckbox.disabled = true; + + branchCheckbox.checked = false; + branchCheckbox.disabled = true; + + pointCheckbox.checked = false; + pointCheckbox.disabled = true; + } else { + stopCheckbox.disabled = false; + branchCheckbox.disabled = false; + pointCheckbox.disabled = false; + } + }, +}; + +document.addEventListener("spree:load", function () { + LocationCheckboxManager.initialize(); +}); diff --git a/app/assets/javascripts/spree_cm_commissioner/selected_field_filter_origin_destination.js b/app/assets/javascripts/spree_cm_commissioner/selected_field_filter_origin_destination.js new file mode 100644 index 000000000..4783d3359 --- /dev/null +++ b/app/assets/javascripts/spree_cm_commissioner/selected_field_filter_origin_destination.js @@ -0,0 +1,37 @@ +var SelectFieldFilterOriginDestination = { + initialize: function (originSelector, destinationSelector, selectedOriginId, selectedDestinationId) { + function filterOptions(originId, destinationSelector) { + $(destinationSelector + ' option').each(function () { + if ($(this).val() == originId) { + $(this).prop('disabled', true).hide(); + } + else { + $(this).prop('disabled', false).show(); + } + }); + } + + $(originSelector).val(selectedOriginId); + $(destinationSelector).val(selectedDestinationId); + + filterOptions(selectedOriginId, destinationSelector); + filterOptions(selectedDestinationId, originSelector); + + + $(document).on('change', originSelector, function () { + var originId = $(this).val(); + filterOptions(originId, destinationSelector); + }); + + $(document).on('change', destinationSelector, function () { + var destinationId = $(this).val(); + filterOptions(destinationId, originSelector); + }); + } +} + +document.addEventListener("spree:load", function () { + var selectedOriginId = $('#product_origin_id').val(); + var selectedDestinationId = $('#product_destination_id').val(); + SelectFieldFilterOriginDestination.initialize('#product_origin_id', '#product_destination_id', selectedOriginId, selectedDestinationId); +}); \ No newline at end of file diff --git a/app/assets/javascripts/spree_cm_commissioner/trip_stop_selection.js b/app/assets/javascripts/spree_cm_commissioner/trip_stop_selection.js new file mode 100644 index 000000000..b9e9f206d --- /dev/null +++ b/app/assets/javascripts/spree_cm_commissioner/trip_stop_selection.js @@ -0,0 +1,41 @@ +var boardingAndDropOffSelectionFilter = { + initialize: function (boardingSelector, dropOffSelector) { + var selectedOriginIds = $(boardingSelector).val(); + var selectedDestinationIds = $(dropOffSelector).val(); + + function filterOptions(selectedIds, selector) { + $(selector + " option").each(function () { + var optionValue = $(this).val(); + // Check if the optionValue exists in the selectedIds array + if (selectedIds.includes(optionValue)) { + $(this).prop("disabled", true).hide(); + } else { + $(this).prop("disabled", false).show(); + } + }); + } + + $(boardingSelector).val(selectedOriginIds); + $(dropOffSelector).val(selectedDestinationIds); + + filterOptions(selectedOriginIds, dropOffSelector); + filterOptions(selectedDestinationIds, boardingSelector); + + $(document).on("change", boardingSelector, function () { + var originIds = $(this).val(); + filterOptions(originIds, dropOffSelector); + }); + + $(document).on("change", dropOffSelector, function () { + var destinationIds = $(this).val(); + filterOptions(destinationIds, boardingSelector); + }); + }, +}; + +document.addEventListener("spree:load", function () { + boardingAndDropOffSelectionFilter.initialize( + "#boarding_stops", + "#drop-off_stops" + ); +}); diff --git a/app/assets/javascripts/spree_cm_commissioner/vehicle_seat.js b/app/assets/javascripts/spree_cm_commissioner/vehicle_seat.js new file mode 100644 index 000000000..cf59de7b3 --- /dev/null +++ b/app/assets/javascripts/spree_cm_commissioner/vehicle_seat.js @@ -0,0 +1,291 @@ +const VehcileSeatLayoutHandler = { + Seat: class { + constructor(row, column, label) { + this.row = row; + this.column = column; + this.label = label; + this.layer = VehcileSeatLayoutHandler.layer.value; + this.seat_type = 0; + this.vehicle_type_id = VehcileSeatLayoutHandler.vehicleTypeId; + } + }, + + initialize: function () { + this.keyInit(); + this.listenToRow(); + this.listenToColumn(); + this.listenToSuffix(); + this.listenToLayer(); + this.listenToLabel(); + this.listenToSubmitButton(); + this.typeSelect(); + }, + + keyInit: function () { + this.row = document.getElementById("row"); + this.column = document.getElementById("column"); + this.seatsContainer = document.querySelector(".seatsContainer"); + this.label = document.getElementById("label"); + this.layer = document.getElementById("layer"); + this.submitButton = document.querySelector(".saveButton"); + this.segments = window.location.pathname.split("/"); + this.vehicleTypeId = this.segments[this.segments.length - 2]; + this.suffix = document.getElementById("suffix"); + this.selectedRow = null; + this.selectedColumn = null; + this.seats = []; + }, + + listenToSuffix: function () { + this.suffix.addEventListener("input", () => { + let newLabel = this.labelGenerator(this.suffix.checked); + if ( + this.row.value > 0 && + this.column.value > 0 && + this.seats && + this.label.value + ) { + for (let i = 0; i < this.row.value; i++) { + for (let j = 0; j < this.column.value; j++) { + if ( + this.seats[i][j].seat_type != 1 && + this.seats[i][j].seat_type != 3 + ) { + let tmpLabel = this.seats[i][j].label.replace(label.value, ""); + this.seats[i][j].label = newLabel(tmpLabel, label.value); + } + } + } + this.getSeats(); + } + }); + }, + + listenToLayer: function () { + this.layer.addEventListener("input", () => { + if (this.row.value > 0 && this.column.value > 0 && this.seats) { + this.addLayer(); + console.log("updated layer"); + console.log(this.seats); + } + }); + }, + + listenToRow: function () { + this.row.addEventListener("input", () => { + if (this.row.value > 0 && this.column.value > 0) { + this.constructSeats(); + this.getSeats(); + } + }); + }, + + listenToColumn: function () { + this.column.addEventListener("input", () => { + if (this.row.value > 0 && this.column.value > 0) { + this.constructSeats(); + this.getSeats(); + } + }); + }, + + listenToLabel: function () { + this.label.addEventListener("input", () => { + let newLabel = this.labelGenerator(this.suffix.checked); + if (this.row.value > 0 && this.column.value > 0 && this.seats) { + let tmpLabel = 0; + this.seats.forEach((seatRow) => { + seatRow.forEach((seat) => { + if (seat.seat_type != 1 && seat.seat_type != 3) { + seat.label = newLabel(++tmpLabel, label.value); + } else { + seat.label = "NA"; + } + }); + }); + this.getSeats(); + } + }); + }, + + listenToSubmitButton: function () { + this.submitButton.addEventListener("click", () => { + if (seats.length > 0) { + this.submit(); + } + }); + }, + + labelGenerator: function (isSuffix) { + let newLabel; + isSuffix + ? (newLabel = (tmpLabel, labelValue) => `${tmpLabel}${labelValue}`) + : (newLabel = (tmpLabel, labelValue) => `${labelValue}${tmpLabel}`); + return newLabel; + }, + + constructSeats: function () { + seats = []; + let tmpLabel = 0; + let newLabel = this.labelGenerator(suffix.checked); + for (let i = 1; i <= this.row.value; i++) { + let columns = []; + for (let j = 1; j <= this.column.value; j++) { + columns.push(new this.Seat(i, j, newLabel(++tmpLabel, label.value))); + } + seats.push(columns); + } + this.seats = seats; + }, + + getSeats: function () { + $.ajax({ + url: "/transit/vehicle_types/vehicle_seats/load_seat", + type: "POST", + data: { + row: this.row.value, + column: this.column.value, + label: this.label.value, + seats: JSON.stringify(this.seats), + }, + success: (response) => { + this.seatsContainer.innerHTML = response; + this.addSeatClickListener(); + this.editLabel(); + }, + error: function (xhr, status, error) { + show_flash("error", error); + }, + }); + }, + + submit: function () { + $.ajax({ + url: `/transit/vehicle_types/${this.vehicleTypeId}/vehicle_seats`, + type: "POST", + data: { + seats: JSON.stringify(this.seats), + }, + success: (response) => { + location.reload(); + }, + error: (xhr, status, error) => { + console.error("Error updating seat layer:", error); + }, + }); + }, + + addLayer: function () { + this.seats.forEach((seatRow) => { + seatRow.forEach((seat) => { + seat.layer = layer.value; + }); + }); + }, + + addSeatClickListener: function () { + let self = this; + let selectedSeat = null; + document.querySelectorAll(".seat-container").forEach((seat) => { + let seatIcon = (seat) => { + return seat.querySelector(".seat-icon"); + }; + let seatLabel = (seat) => { + return seat.querySelector(".seat-label"); + }; + + seat.addEventListener("click", function () { + if (selectedSeat && selectedSeat != this) { + seatIcon(selectedSeat).classList.remove("selected-seat"); + seatLabel(selectedSeat).disabled = true; + } + + seatIcon(this).classList.add("selected-seat"); + seatLabel(this).disabled = false; + selectedSeat = this; + + self.selectedRow = + parseInt(this.querySelector(".seat-icon").dataset.seatRow) - 1; + self.selectedColumn = + parseInt(this.querySelector(".seat-icon").dataset.seatColumn) - 1; + let seat_type = + self.seats[self.selectedRow][self.selectedColumn].seat_type; + document.getElementById("type").value = seat_type; + }); + }); + }, + + editLabel: function () { + let self = this; + document.querySelectorAll(".seat-label").forEach((label) => { + label.addEventListener("change", () => { + self.seats[self.selectedRow][self.selectedColumn].label = + document.getElementById( + `${self.selectedRow + 1}${self.selectedColumn + 1}` + ).value; + self.getSeats(); + }); + }); + }, + + typeSelect: function () { + document.getElementById("type").addEventListener("input", () => { + this.seats[this.selectedRow][this.selectedColumn].seat_type = parseInt( + document.getElementById("type").value + ); + let newLabel = this.labelGenerator(this.suffix.checked); + let tmpLabel = 0; + + for (let i = 0; i < this.row.value; i++) { + for (let j = 0; j < this.column.value; j++) { + if ( + this.seats[i][j].seat_type != 1 && + this.seats[i][j].seat_type != 3 + ) { + this.seats[i][j].label = newLabel(++tmpLabel, label.value); + } else { + this.seats[i][j].label = "NA"; + } + } + } + this.getSeats(); + }); + }, +}; + +const VehicleSeatViewHandler = { + initialize: function () { + $(document).ready(function () { + if ($(".view-layer-btn").length > 0) { + $(".view-layer-btn").click(function () { + let layer = $(this).data("layer"); + let layer_name = $(this).data("layer-name"); + let column = $(this).data("layer-column"); + let row = $(this).data("layer-row"); + let seatsViewContainer = document.querySelector(".modal-body"); + $.ajax({ + url: "/transit/vehicle_types/layer", + type: "POST", + data: { + row: row, + layer_name: layer_name, + column: column, + seats: JSON.stringify(layer), + }, + success: function (response) { + seatsViewContainer.innerHTML = response; + }, + error: function (xhr, status, error) { + show_flash("error", error); + }, + }); + }); + } + }); + }, +}; + +document.addEventListener("spree:load", function () { + VehcileSeatLayoutHandler.initialize(); + VehicleSeatViewHandler.initialize(); +}); diff --git a/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss b/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss index 9cd1b4c51..c2ff9d5de 100644 --- a/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss +++ b/app/assets/stylesheets/spree_cm_commissioner/backend/commissioner_admin.css.scss @@ -1,3 +1,4 @@ @import "spree/backend/spree_admin"; @import "spree_cm_commissioner/backend/calendar"; @import "spree_cm_commissioner/backend/service_calendar"; +@import "spree_cm_commissioner/backend/vehicle_seat" diff --git a/app/assets/stylesheets/spree_cm_commissioner/backend/vehicle_seat.scss b/app/assets/stylesheets/spree_cm_commissioner/backend/vehicle_seat.scss new file mode 100644 index 000000000..74f5df9de --- /dev/null +++ b/app/assets/stylesheets/spree_cm_commissioner/backend/vehicle_seat.scss @@ -0,0 +1,28 @@ +/* seats.css */ + +.seat { + display: inline-block; + width: 20px; + height: 20px; + background-color: #ccc; + margin: 2px; + cursor: pointer; +} + +.selected-seat { + // background-color: #f00; + border: 1px dotted grey; +} + +.hidden-seat{ + width: 35px; + display: inline-block; +} + +.empty-seat{ + border: 1px solid black; +} + +.hidden-input{ + visibility: hidden; +} diff --git a/app/assets/stylesheets/spree_cm_commissioner/vehicle_seat.css b/app/assets/stylesheets/spree_cm_commissioner/vehicle_seat.css new file mode 100644 index 000000000..644549c44 --- /dev/null +++ b/app/assets/stylesheets/spree_cm_commissioner/vehicle_seat.css @@ -0,0 +1,7 @@ +/* + * This is a manifest file that'll automatically include all the stylesheets available in this directory + * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at + * the top of the compiled file, but it's generally better to create a new file per style scope. + * + *= require spree_cm_commissioner/vehicle/vehicle_seat +*/ diff --git a/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb b/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb new file mode 100644 index 000000000..ad8a5afbe --- /dev/null +++ b/app/controllers/concerns/spree_cm_commissioner/transit/taxon_bitwise.rb @@ -0,0 +1,44 @@ +module SpreeCmCommissioner + module Transit + module TaxonBitwise + extend ActiveSupport::Concern + + BIT_STOP = 0b001 + BIT_STATION = 0b010 + BIT_BRANCH = 0b100 + BIT_LOCATION = 0b1000 + + included do + attr_accessor :stop, :station, :branch, :location + + before_validation :at_least_one_checkbox_selected + end + + def stop? + data_type & BIT_STOP != 0 + end + + def station? + data_type & BIT_STATION != 0 + end + + def branch? + data_type & BIT_BRANCH != 0 + end + + def location? + data_type & BIT_LOCATION != 0 + end + + private + + def at_least_one_checkbox_selected + if stop.nil? && station.nil? && branch.nil? && location.nil? + nil + elsif stop.to_i.zero? && station.to_i.zero? && branch.to_i.zero? && location.to_i.zero? + errors.add(:base, 'At least one checkbox (stop, branch, or location) must be selected') + end + end + end + end +end diff --git a/app/controllers/spree/transit/amenities_controller.rb b/app/controllers/spree/transit/amenities_controller.rb new file mode 100644 index 000000000..3cc76c68d --- /dev/null +++ b/app/controllers/spree/transit/amenities_controller.rb @@ -0,0 +1,79 @@ +module Spree + module Transit + class AmenitiesController < Spree::Transit::BaseController + before_action :load_data + before_action :load_vendor + before_action :load_amenity + before_action :setup_new_option_value, only: :edit + + def load_data + @amenities = Spree::OptionType.all + end + + def update_values_positions + ApplicationRecord.transaction do + params[:positions].each do |id, index| + Spree::OptionValue.where(id: id).update_all(position: index) + end + end + + respond_to do |format| + format.html { redirect_to spree.transit_routes_url } + format.js { render plain: 'Ok' } + end + end + + def update + load_amenity + if @amenity.update(amenity_params) + flash[:success] = 'Option Type updated successfully.' + else + flash[:error] = "Unable to update Option Type. Errors: #{@amenity.errors.full_messages.join(', ')}" + end + redirect_back(fallback_location: edit_transit_amenity_path(@amenity)) + end + + def location_after_save + edit_transit_amenity_path(@amenity) + end + + def collection + load_data + + @objects = model_class.where(kind: :vehicle_type) + @search = @objects.ransack(params[:q]) + @collection = @search.result + + @collection + end + + def load_vendor + @vendor ||= vendors.find { |v| v[:slug] == session[:transit_current_vendor_slug] } || vendors.first + end + + def model_class + Spree::OptionType + end + + def object_name + 'option_type' + end + + def setup_new_option_value + @amenity.option_values.build if @amenity.option_values.empty? + end + + private + + def load_amenity + ActiveRecord::Base.connected_to(role: :writing) do + @amenity = Spree::OptionType.amenities + end + end + + def amenity_params + params.require(:option_type).permit(:name, :presentation, :kind, :attr_type, option_values_attributes: {}) + end + end + end +end diff --git a/app/controllers/spree/transit/amenity_values_controller.rb b/app/controllers/spree/transit/amenity_values_controller.rb new file mode 100644 index 000000000..2a10738a4 --- /dev/null +++ b/app/controllers/spree/transit/amenity_values_controller.rb @@ -0,0 +1,16 @@ +module Spree + module Transit + class AmenityValuesController < Spree::Transit::BaseController + # This method is added here to allow `edit_polymorphic_path` to work + def edit_admin_option_value_path(option_value) + spree.edit_admin_option_type_url(option_value.option_type) + end + + def destroy + option_value = Spree::OptionValue.find(params[:id]) + option_value.destroy + render plain: nil + end + end + end +end diff --git a/app/controllers/spree/transit/base_controller.rb b/app/controllers/spree/transit/base_controller.rb new file mode 100644 index 000000000..3f270398c --- /dev/null +++ b/app/controllers/spree/transit/base_controller.rb @@ -0,0 +1,53 @@ +module Spree + module Transit + class BaseController < Spree::Admin::ResourceController + helper_method :current_vendor, :vendors + before_action :required_vendor_user! + before_action :current_vendor + layout 'spree/layouts/transit' + + def vendors + @vendors ||= spree_current_user.vendors.to_a + end + + def page + params[:page] || 1 + end + + def per_page + params[:per_page] || 12 + end + + # @overrided + def required_vendor_user! + return unless vendors.empty? + + raise SpreeCmCommissioner::UnauthorizedVendorError + end + + def current_vendor + @current_vendor ||= vendors.find { |v| v[:slug] == session[:transit_current_vendor_slug] } || vendors.first + session[:transit_current_vendor_slug] ||= @current_vendor&.slug + + @current_vendor + end + + def collection_url(options = {}) + if parent_data.present? + spree.polymorphic_url([:transit, parent, model_class], options) + else + spree.polymorphic_url([:transit, model_class], options) + end + end + + def edit_object_url(object, options = {}) + if parent_data.present? + spree.send "edit_transit_#{resource.model_name}_#{resource.object_name}_url", + parent, object, options + else + spree.send "edit_transit_#{resource.object_name}_url", object, options + end + end + end + end +end diff --git a/app/controllers/spree/transit/branches_controller.rb b/app/controllers/spree/transit/branches_controller.rb new file mode 100644 index 000000000..aeeaa50d5 --- /dev/null +++ b/app/controllers/spree/transit/branches_controller.rb @@ -0,0 +1,35 @@ +module Spree + module Transit + class BranchesController < Spree::Transit::BaseController + def new + @branch = SpreeCmCommissioner::Branch.new + super + end + + def collection + return @collection if defined?(@collection) + + current_vendor.branches + + @search = current_vendor.branches.includes(:state).ransack(params[:q]) + @collection = @search.result + end + + def location_after_save + transit_branches_url + end + + def model_class + SpreeCmCommissioner::Branch + end + + def object_name + 'spree_cm_commissioner_branch' + end + + def branch_params + params.require(:branch).permit(:name, :lat, :lon) + end + end + end +end diff --git a/app/controllers/spree/transit/locations_controller.rb b/app/controllers/spree/transit/locations_controller.rb new file mode 100644 index 000000000..07badbed6 --- /dev/null +++ b/app/controllers/spree/transit/locations_controller.rb @@ -0,0 +1,38 @@ +module Spree + module Transit + class LocationsController < Spree::Transit::BaseController + def load_location + @locations = Spree::State.all + end + + def load_country + @countries = Spree::Country.where(id: current_vendor.default_country) + end + + def new + @object.country_id = Spree::Country.find_by(id: current_vendor.default_country)&.id + end + + def collection + return @collection if defined?(@collection) + + load_location + + @search = @locations.ransack(params[:q]) + @collection = @search.result + end + + def location_after_save + transit_locations_url + end + + def model_class + Spree::State + end + + def object_name + 'state' + end + end + end +end diff --git a/app/controllers/spree/transit/places_controller.rb b/app/controllers/spree/transit/places_controller.rb new file mode 100644 index 000000000..3d4ef8591 --- /dev/null +++ b/app/controllers/spree/transit/places_controller.rb @@ -0,0 +1,151 @@ +module Spree + module Transit + class PlacesController < Spree::Transit::BaseController + before_action :load_place_taxonomy + helper 'spree/transit/sortable_tree' + + def load_place_taxonomy + @place_taxonomy = Spree::Taxonomy.place + end + + before_action :set_permalink_part, only: [:edit] + respond_to :html, :js + helper 'spree/transit/sortable_tree' + before_action :set_data_type, only: %i[update create] + + def index; end + + def new + @taxon = Spree::Taxon.new + @taxon.taxonomy = @place_taxonomy + end + + def update # rubocop:disable Metrics/AbcSize + successful = @taxon.transaction do + parent_id = params[:taxon][:parent_id] + set_position + assign_parent(parent_id) + + @taxon.save! + + # regenerate permalink + regenerate_permalink if parent_id + + set_permalink_params + + # check if we need to rename child taxons if parent name or permalink changes + @update_children = true if params[:taxon][:name] != @taxon.name || params[:taxon][:permalink] != @taxon.permalink + + @taxon.create_icon(attachment: taxon_params[:icon]) if taxon_params[:icon] + @taxon.update(taxon_params.except(:icon)) + end + if successful + flash[:success] = flash_message_for(@taxon, :successfully_updated) + + # rename child taxons + rename_child_taxons if @update_children + + respond_with(@taxon) do |format| + format.html { redirect_to spree.edit_transit_place_url(params[:id]) } + format.json { render json: @taxon.to_json } + end + else + respond_with(@taxon) do |format| + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @taxon.errors.full_messages.to_sentence, status: :unprocessable_entity } + end + end + end + + def location_after_save + spree.transit_places_url + end + + def set_permalink_part + @permalink_part = @object.permalink.split('/').last + @parent_permalink = @object.permalink.split('/')[0...-1].join('/') + @parent_permalink += '/' if @parent_permalink.present? + end + + def set_permalink_params + set_permalink_part + + return unless params.key? 'permalink_part' + + params[:taxon][:permalink] = @parent_permalink + params[:permalink_part] + end + + def taxon_params + params.require(:taxon).permit(:stop, :station, :branch, :location, permitted_taxon_attributes) + end + + def set_position + new_position = params[:taxon][:position] + @taxon.child_index = new_position.to_i if new_position + end + + # If this method is applied to the model, it will set stop, location, and + # taxon attributes to 0 when a drag-and-drop action changes the position. + def set_data_type + place_types = params[:taxon] + if place_types[:stop].to_i.zero? && place_types[:station].to_i.zero? && place_types[:branch].to_i.zero? + # If not checked, it will set the location instead. + place_types[:location] = 1 + @taxon[:data_type] = (place_types[:location].to_i * (2**3)) + end + + result = (place_types[:stop].to_i * (2**0)) + (place_types[:station].to_i * (2**1)) + (place_types[:branch].to_i * (2**2)) + (place_types[:location].to_i * (2**3)) # rubocop:disable Layout/LineLength + @taxon[:data_type] = result + end + + def assign_parent(parent_id) + @taxon.parent = current_store.taxons.find(parent_id) if parent_id + end + + def parent_data + if action_name == 'index' + nil + else + super + end + end + + def rename_child_taxons + @taxon.descendants.each do |taxon| + reload_taxon_and_set_permalink(taxon) + end + end + + def regenerate_permalink + reload_taxon_and_set_permalink(@taxon) + @update_children = true + end + + def reload_taxon_and_set_permalink(taxon) + taxon.reload + taxon.set_permalink + taxon.save! + end + + def collection_url + spree.polymorphic_url([:transit, place.to_sym], options) + end + + def edit_transit_taxon_path(taxon) + spree.edit_transit_taxonomy_taxon_path(taxon.taxonomy, taxon.id) + end + + def scope + current_store.taxonomies + end + + def model_class + Spree::Taxon + end + + def object_name + 'taxon' + end + end + end +end diff --git a/app/controllers/spree/transit/reservations_controller.rb b/app/controllers/spree/transit/reservations_controller.rb new file mode 100644 index 000000000..7cd03c0c0 --- /dev/null +++ b/app/controllers/spree/transit/reservations_controller.rb @@ -0,0 +1,15 @@ +module Spree + module Transit + class ReservationsController < Spree::Transit::BaseController + def index; end + + def model_class + Spree::Order + end + + def object_name + 'order' + end + end + end +end diff --git a/app/controllers/spree/transit/routes_controller.rb b/app/controllers/spree/transit/routes_controller.rb new file mode 100644 index 000000000..89e08741e --- /dev/null +++ b/app/controllers/spree/transit/routes_controller.rb @@ -0,0 +1,66 @@ +module Spree + module Transit + class RoutesController < Spree::Transit::BaseController + before_action :load_data + + def scope + current_vendor.products.where(product_type: :transit) + end + + def show + redirect_to spree.edit_transit_route_path(@object) + end + + def load_data + @product ||= model_class.find_by(id: params[:id]) + @option_types = OptionType.order(:name) + @shipping_categories = ShippingCategory.order(:name) + @selected_option_type_ids = Spree::OptionType.where(name: %w[origin destination]).ids + @taxons = Spree::Taxonomy.place.taxons + end + + def new + @object.product_type = :transit + end + + def collection + return @collection if defined?(@collection) + + params[:q] ||= {} + params[:q][:deleted_at_null] ||= '1' + + params[:q][:s] ||= 'updated_at desc' + + current_vendor.products.where(product_type: :transit) + + @search = current_vendor.products.where(product_type: :transit).ransack(params[:q].reject { |k, _v| k.to_s == 'deleted_at_null' }) + + @collection = @search.result + .page(params[:page]) + .per(params[:per_page] || Spree::Backend::Config[:variants_per_page]) + @collection + end + + # overrided + def find_resource + product_scope.with_deleted.friendly.find(params[:id]) + end + + def product_scope + current_store.products.accessible_by(current_ability, :index) + end + + def location_after_save + transit_routes_url + end + + def model_class + Spree::Product + end + + def object_name + 'product' + end + end + end +end diff --git a/app/controllers/spree/transit/service_calendars_controller.rb b/app/controllers/spree/transit/service_calendars_controller.rb new file mode 100644 index 000000000..e9b505d22 --- /dev/null +++ b/app/controllers/spree/transit/service_calendars_controller.rb @@ -0,0 +1,109 @@ +module Spree + module Transit + class ServiceCalendarsController < Spree::Transit::BaseController + before_action :load_vendor + create.before :set_vendor + update.before :set_vendor + + new_action.before :set_exception_rules + + helper 'spree_cm_commissioner/transit/service_calendars' + + def update_status + if @object.update(active: !@object.active) + flash[:success] = flash_message_for(@object, :successfully_updated) + else + flash[:error] = @object.errors.full_messages.to_sentence + end + + redirect_to transit_vendor_service_calendars_url + rescue ActiveRecord::RecordInvalid => e + flash[:error] = e.message + redirect_to transit_vendor_service_calendars_url + end + + def location_after_save + transit_vendor_service_calendars_url + end + + protected + + def collection_url(options = {}) + transit_vendor_service_calendars_url(options) + end + + def permitted_resource_params + add_exception_rules_type(params[:spree_cm_commissioner_service_calendar][:exception_rules]) + service_calendar_params = params.require(:spree_cm_commissioner_service_calendar) + .permit(:calendarable_id, :calendarable_type, :start_date, :end_date, + :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday, :name, :not_available_reason, + :service_type + ) + service_calendar_params[:exception_rules] = build_exception_rules(params[:spree_cm_commissioner_service_calendar][:exception_rules]) + service_calendar_params + end + + def model_class + SpreeCmCommissioner::ServiceCalendar + end + + def object_name + 'spree_cm_commissioner_service_calendars' + end + + private + + def build_exception_rules(exception_rules) + exception_rules.values.reject! { |rule| rule['from'].blank? || rule['to'].blank? } || exception_rules.values + end + + def add_exception_rules_type(exception_rules) + exception_rules.values.each do |rule| + rule['type'] = 'exclusion' + end + end + + def set_vendor + @object.calendarable_type = calendarable_type + @object.calendarable_id = calendarable_id + end + + def load_vendor + @vendor ||= vendors.find { |v| v[:slug] == session[:transit_current_vendor_slug] } || vendors.first + end + + def collection + load_vendor + + @objects = model_class.where( + calendarable_type: calendarable_type, + calendarable_id: calendarable_id + ).order(id: :desc) + + @search = @objects.ransack(params[:q]) + @collection = @search.result + + @collection + end + + def set_exception_rules + @exception_rules = [{ from: DateTime.now, to: DateTime.now, type: 'exclusion', reason: nil }] + end + + def build_resource + today = Time.zone.today + model_class.new(start_date: today, + exception_rules: [{ from: nil, to: nil }] + ) + end + + def calendarable_id + @vendor.id + end + + def calendarable_type + @vendor.class.name + end + end + end +end diff --git a/app/controllers/spree/transit/stops_controller.rb b/app/controllers/spree/transit/stops_controller.rb new file mode 100644 index 000000000..4c932ebe9 --- /dev/null +++ b/app/controllers/spree/transit/stops_controller.rb @@ -0,0 +1,32 @@ +module Spree + module Transit + class StopsController < Spree::Transit::BaseController + before_action :load_location + + def collection + return @collection if defined?(@collection) + + current_vendor.stops + + @search = current_vendor.stops.includes(:state).ransack(params[:q]) + @collection = @search.result + end + + def load_location + @locations = Spree::State.all + end + + def location_after_save + transit_stops_url + end + + def model_class + SpreeCmCommissioner::Stop + end + + def object_name + 'spree_cm_commissioner_stop' + end + end + end +end diff --git a/app/controllers/spree/transit/trip_stops_controller.rb b/app/controllers/spree/transit/trip_stops_controller.rb new file mode 100644 index 000000000..52712f2a9 --- /dev/null +++ b/app/controllers/spree/transit/trip_stops_controller.rb @@ -0,0 +1,35 @@ +module Spree + module Transit + class TripStopsController < Spree::Transit::BaseController + before_action :load_data + def index; end + + def load_data + @product = Spree::Product.find_by(slug: params[:route_id]) + @trip = SpreeCmCommissioner::Trip.find_by(id: params[:trip_id]) if params[:trip_id] + @trip_stops = @trip.trip_stops.sort_by(&:sequence) + end + + def update_sequences + ApplicationRecord.transaction do + params[:positions].each do |id, index| + SpreeCmCommissioner::TripStop.where(id: id).update(sequence: index) + end + end + + respond_to do |format| + format.html { redirect_to transit_route_trip_trip_stops_path(@product, @product.trip) } + format.js { render plain: 'Ok' } + end + end + + def object_name + 'spree_cm_commissioner_trip_stop' + end + + def model_class + SpreeCmCommissioner::TripStop + end + end + end +end diff --git a/app/controllers/spree/transit/trips_controller.rb b/app/controllers/spree/transit/trips_controller.rb new file mode 100644 index 000000000..9b99359f7 --- /dev/null +++ b/app/controllers/spree/transit/trips_controller.rb @@ -0,0 +1,90 @@ +module Spree + module Transit + class TripsController < Spree::Transit::BaseController + before_action :load_data + + def load_data + @product = Spree::Product.find_by(slug: params[:route_id]) + @vehicles = SpreeCmCommissioner::Vehicle.includes(:vehicle_type).order('cm_vehicle_types.name') + @stops = Spree::Taxonomy.place.taxons.select(&:stop?).to_a + @places = Spree::Taxonomy.place.taxons.select(&:location?).to_a + @trip = SpreeCmCommissioner::Trip.find_by(id: params[:id]) if params[:id] + end + + def model_class + SpreeCmCommissioner::Trip + end + + def edit + @trip_stops = @trip.trip_stops.sort_by(&:sequence) + @boarding_stops = @trip.trip_stops.boarding.to_a.pluck(:stop_id) + @drop_off_stops = @trip.trip_stops.drop_off.to_a.pluck(:stop_id) + end + + def create + @trip = model_class.new + if @trip.update(trip_params(nested_stop_attributes)) + flash[:success] = flash_message_for(@trip, :successfully_created) + redirect_to edit_transit_route_trip_path(@product, @product.trip.id) + else + flash[:error] = "create failed. Errors: #{@trip.errors.full_messages.join(', ')}" + redirect_back(fallback_location: new_transit_route_trip_path(@product)) + end + end + + def update + if @trip.update(trip_params(nested_stop_attributes)) + flash[:success] = flash_message_for(@trip, :successfully_updated) + else + flash[:error] = "update failed. Errors: #{@trip.errors.full_messages.join(', ')}" + end + redirect_back(fallback_location: edit_transit_route_trip_path(@product, @trip)) + end + + def nested_stop_attributes + cm_params = params.require('spree_cm_commissioner_trip') + trip_stops = @trip.trip_stops.index_by(&:stop_id) + + boarding_points_attributes = (cm_params.delete('boarding_points') || []).compact_blank + .map do |point_id| + trip_stop = trip_stops.delete(point_id.to_i) + { stop_id: point_id, stop_type: 'boarding', id: trip_stop.try(:id) } + end + + drop_off_points_attributes = (cm_params.delete('drop_off_points') || []).compact_blank + .map do |point_id| + trip_stop = trip_stops.delete(point_id.to_i) + { stop_id: point_id, stop_type: 'drop_off', id: trip_stop.try(:id) } + end + + trip_stops.each_value do |trip_stop| + next unless trip_stop.stop_id != @trip.origin_id && trip_stop.stop_id != @trip.destination_id + + boarding_points_attributes << { id: trip_stop.id, + _destroy: '1' + } + end + + cm_params[:trip_stops_attributes] = boarding_points_attributes + drop_off_points_attributes + cm_params + end + + def trip_params(params) + params.permit( + :origin_id, :destination_id, :vehicle_id, :hours, :minutes, :seconds, + 'departure_time(1i)', 'departure_time(2i)', 'departure_time(3i)', + 'departure_time(4i)', 'departure_time(5i)', :product_id, + trip_stops_attributes: %i[stop_id stop_type _destroy id] + ) + end + + def object_name + 'spree_cm_commissioner_trip' + end + + def location_after_save + transit_routes_path + end + end + end +end diff --git a/app/controllers/spree/transit/vectors/amenity_values_controller.rb b/app/controllers/spree/transit/vectors/amenity_values_controller.rb new file mode 100644 index 000000000..4afe33c76 --- /dev/null +++ b/app/controllers/spree/transit/vectors/amenity_values_controller.rb @@ -0,0 +1,40 @@ +module Spree + module Transit + module Vectors + class AmenityValuesController < Spree::Transit::BaseController + skip_before_action :load_resource, only: :update + before_action :load_object, only: :update + + def load_object + option_value_id = params['option-value-id'] + @object = Spree::OptionValue.find(option_value_id) + end + + # @overrided + def collection + @vector_icons = SpreeCmCommissioner::VectorIcon.all + @collection ||= Spree::OptionType.where(kind: kind) + end + + # @overrided + def model_class + Spree::OptionValue + end + + # @overrided + def object_name + 'option_value' + end + + # @overrided + def collection_url(options = { kind: kind }) + transit_vectors_amenity_values_url(options) + end + + def kind + @kind = 'vehicle_type' + end + end + end + end +end diff --git a/app/controllers/spree/transit/vectors/icons_controller.rb b/app/controllers/spree/transit/vectors/icons_controller.rb new file mode 100644 index 000000000..d9d1f6027 --- /dev/null +++ b/app/controllers/spree/transit/vectors/icons_controller.rb @@ -0,0 +1,26 @@ +module Spree + module Transit + module Vectors + class IconsController < Spree::Transit::BaseController + # @overrided + def collection + @collection ||= searcher.page(params[:page]).per(params[:per_page]) + end + + def model_class + SpreeCmCommissioner::VectorIcon + end + + private + + def searcher + model_class.search( + path_prefix: params[:path_prefix], + extension: params[:extension], + query: params[:query] + ) + end + end + end + end +end diff --git a/app/controllers/spree/transit/vehicle_photos_controller.rb b/app/controllers/spree/transit/vehicle_photos_controller.rb new file mode 100644 index 000000000..39f2cf12e --- /dev/null +++ b/app/controllers/spree/transit/vehicle_photos_controller.rb @@ -0,0 +1,53 @@ +module Spree + module Transit + class VehiclePhotosController < Spree::Transit::BaseController + prepend_before_action :load_vehicle + + create.before :set_viewable + update.before :set_viewable + + def index + end + + def load_vehicle + @vehicle ||= current_vendor.vehicles.find(params[:vehicle_id]) + end + + def set_viewable + @object.viewable_type = viewable_type + @object.viewable_id = viewable_id + end + + def viewable_type + @vehicle.class.name + end + + def viewable_id + @vehicle.id + end + + def collection + @objects = model_class.where( + viewable_type: viewable_type, + viewable_id: viewable_id + ) + end + + def location_after_save + transit_vehicle_vehicle_photos_url + end + + def model_class + SpreeCmCommissioner::VehiclePhoto + end + + def object_name + 'spree_cm_commissioner_vehicle_photo' + end + + def collection_url(options = {}) + transit_vehicle_vehicle_photos_url(options) + end + end + end +end \ No newline at end of file diff --git a/app/controllers/spree/transit/vehicle_seats_controller.rb b/app/controllers/spree/transit/vehicle_seats_controller.rb new file mode 100644 index 000000000..da1c7a201 --- /dev/null +++ b/app/controllers/spree/transit/vehicle_seats_controller.rb @@ -0,0 +1,29 @@ +module Spree + module Transit + class VehicleSeatsController < Spree::Transit::BaseController + def load_seat + @seats = JSON.parse(params[:seats]).to_a + render :partial => 'spree/transit/vehicle_seats/seats' + end + + def create + @seats = params[:seats] + + @flatten_seats = JSON.parse(@seats).flatten + @flatten_seats.each do |seat| + SpreeCmCommissioner::VehicleSeat.create(seat) + end + end + + private + + def model_class + SpreeCmCommissioner::VehicleSeat + end + + def object_name + 'spree_cm_commissioner_vehicle_seat' + end + end + end +end diff --git a/app/controllers/spree/transit/vehicle_types_controller.rb b/app/controllers/spree/transit/vehicle_types_controller.rb new file mode 100644 index 000000000..aadc6b9bc --- /dev/null +++ b/app/controllers/spree/transit/vehicle_types_controller.rb @@ -0,0 +1,81 @@ +module Spree + module Transit + class VehicleTypesController < Spree::Transit::BaseController + before_action :load_amenities, except: %i[index layer] + before_action :load_status, except: %i[index layer] + + def index + respond_with(@collection) + end + + def location_after_save + edit_transit_vehicle_type_path(@object) + end + + def layer + @seats = JSON.parse(params[:seats]).to_a + @row = params[:row] + @column = params[:column] + @layer_name = params[:layer_name] + @created_at = params[:created_at] + render :partial => 'spree/transit/vehicle_types/seat_view' + end + + def load_amenities + @amenities = Spree::OptionType.amenities.option_values.pluck(:name, :id) + @selected_option_value_ids = @object.option_values.pluck(:id) + end + + def load_status + @statuses = SpreeCmCommissioner::VehicleType.state_machine.states.map(&:name) + end + + def new + @vehicle_type = SpreeCmCommissioner::VehicleType.new + end + + def scope + @vehicle_types = current_vendor.vehicle_types + end + + def collection + return @collection if defined?(@collection) + + scope + + @search = scope.ransack(params[:q]) + @collection = @search.result + end + + def edit + @seats = @object.seat_layers + end + + # overrided + def permitted_resource_params + vehicle_type_params = params[:spree_cm_commissioner_vehicle_type] + selected_option_value_ids = vehicle_type_params[:option_value_ids] + + option_values = Spree::OptionValue.where(id: selected_option_value_ids) + { option_values: option_values, + name: vehicle_type_params[:name], + code: vehicle_type_params[:code], + vendor_id: vehicle_type_params[:vendor_id], + route_type: vehicle_type_params[:route_type], + status: vehicle_type_params[:status], + allow_seat_selection: vehicle_type_params[:allow_seat_selection], + vehicle_seats_count: vehicle_type_params[:vehicle_seats_count] + } + end + + # @overrided + def model_class + SpreeCmCommissioner::VehicleType + end + + def object_name + 'spree_cm_commissioner_vehicle_type' + end + end + end +end diff --git a/app/controllers/spree/transit/vehicles_controller.rb b/app/controllers/spree/transit/vehicles_controller.rb new file mode 100644 index 000000000..4b68da166 --- /dev/null +++ b/app/controllers/spree/transit/vehicles_controller.rb @@ -0,0 +1,54 @@ +module Spree + module Transit + class VehiclesController < Spree::Transit::BaseController + before_action :set_vendor, if: -> { member_action? } + before_action :load_vehicle_types + + def new + @vehicle = SpreeCmCommissioner::Vehicle.new + super + end + + def edit + @vehicle = current_vendor.vehicles.find(params[:id]) + end + + def load_vehicle_types + @vehicle_types = current_vendor.vehicle_types + end + + def scope + @vehicles = current_vendor.vehicles + end + + def collection + return @collection if defined?(@collection) + + scope + + @search = scope.includes(:vehicle_type).ransack(params[:q]) + @collection = @search.result + end + + def location_after_save + transit_vehicles_url + end + + def model_class + SpreeCmCommissioner::Vehicle + end + + def object_name + 'spree_cm_commissioner_vehicle' + end + + def vehicle_params + params.require(:vehicle).permit(:code, :license_plate) + end + + def set_vendor + permitted_resource_params[:vendor] = current_vendor + end + end + end +end diff --git a/app/controllers/spree/transit/vendors_controller.rb b/app/controllers/spree/transit/vendors_controller.rb new file mode 100644 index 000000000..792ea1cb3 --- /dev/null +++ b/app/controllers/spree/transit/vendors_controller.rb @@ -0,0 +1,83 @@ +module Spree + module Transit + class VendorsController < Spree::Transit::BaseController + before_action :build_logo, only: %i[create update] + before_action :build_payment_qrcode, only: %i[create update] + + def create + @vendor.build_image(attachment: permitted_resource_params.delete(:image)) if permitted_resource_params[:image] + super + end + + def update + @vendor.create_image(attachment: permitted_resource_params.delete(:image)) if permitted_resource_params[:image] + format_translations if defined? SpreeGlobalize + super + end + + def update_positions + params[:positions].each do |id, position| + vendor = Spree::Vendor.find(id) + vendor.set_list_position(position) + end + + respond_to do |format| + format.js { render plain: 'Ok' } + end + end + + private + + def find_resource + Vendor.with_deleted.friendly.find(params[:id]) + end + + def collection + params[:q] = {} if params[:q].blank? + vendors = super.order(priority: :asc) + @search = vendors.ransack(params[:q]) + + @collection = @search.result + .includes(vendor_includes) + .page(params[:page]) + .per(params[:per_page]) + end + + def vendor_includes + { + image: [], + products: [] + } + end + + def format_translations + return if params[:vendor][:translations_attributes].blank? + + params[:vendor][:translations_attributes].each do |_, data| + translation = @vendor.translations.find_or_create_by(locale: data[:locale]) + translation.name = data[:name] + translation.about_us = data[:about_us] + translation.contact_us = data[:contact_us] + translation.slug = data[:slug] + translation.save! + end + end + + def build_logo + return unless permitted_resource_params[:logo] + + @vendor.build_logo(attachment: permitted_resource_params.delete(:logo)) + end + + def build_payment_qrcode + return unless permitted_resource_params[:payment_qrcode] + + @vendor.build_payment_qrcode(attachment: permitted_resource_params.delete(:payment_qrcode)) + end + + def location_after_save + edit_transit_vendor_url(@current_vendor.id) + end + end + end +end diff --git a/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb b/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb new file mode 100644 index 000000000..e98029f7f --- /dev/null +++ b/app/finders/spree_cm_commissioner/line_items/find_by_variant_decorator.rb @@ -0,0 +1,20 @@ +module SpreeCmCommissioner + module LineItems + module FindByVariantDecorator + # ovveride + def execute(order:, variant:, options: {}) + return super unless variant.product.product_type == 'transit' + + order.line_items.detect do |line_item| + next if options[:date].present? && !(line_item.variant_id == variant.id && line_item.date == options[:date]) + + Spree::Dependencies.cart_compare_line_items_service.constantize.call(order: order, line_item: line_item, options: options).value + end + end + end + end +end + +unless Spree::LineItems::FindByVariant.included_modules.include?(SpreeCmCommissioner::LineItems::FindByVariantDecorator) + Spree::LineItems::FindByVariant.prepend(SpreeCmCommissioner::LineItems::FindByVariantDecorator) +end diff --git a/app/helpers/spree/transit/navigation_helper_decorator.rb b/app/helpers/spree/transit/navigation_helper_decorator.rb new file mode 100644 index 000000000..bf725867f --- /dev/null +++ b/app/helpers/spree/transit/navigation_helper_decorator.rb @@ -0,0 +1,14 @@ +module Spree + module Transit + module NavigationHelperDecorator + def transit_main_menu_tree(text, icon: nil, sub_menu: nil, url: '#') + content_tag :li, class: 'sidebar-menu-item d-block w-100 text-muted' do + main_menu_item(text, url: url, icon: icon) + + render(partial: "spree/transit/shared/sub_menu/#{sub_menu}") + end + end + end + end +end + +Spree::Admin::NavigationHelper.prepend(Spree::Transit::NavigationHelperDecorator) diff --git a/app/helpers/spree/transit/sortable_tree_helper.rb b/app/helpers/spree/transit/sortable_tree_helper.rb new file mode 100644 index 000000000..06919231e --- /dev/null +++ b/app/helpers/spree/transit/sortable_tree_helper.rb @@ -0,0 +1,32 @@ +module Spree + module Transit + module SortableTreeHelper + def sortable_tree_bar(parent_resource, child_resource) + partial_name = parent_resource.class.name.demodulize.underscore + render "spree/transit/shared/sortable_tree/#{partial_name}", parent_resource: parent_resource, child_resource: child_resource + end + + def build_sortable_tree(parent_resource, child_resource) + child_resource_name = child_resource.class.name.demodulize.underscore + descendants = [] + + unless child_resource.leaf? + child_resource.children.each do |child_item| + descendants << build_sortable_tree(parent_resource, child_item) unless child_resource.leaf? + end + end + + row = sortable_tree_bar(parent_resource, child_resource) + container = content_tag(:div, raw(descendants.join), data: { sortable_tree_parent_id_value: child_resource.id }) + + content_tag(:div, row + container, + class: 'sortable-tree-item draggable removable-dom-element', + data: { + sortable_tree_resource_name_value: child_resource_name.singularize, + sortable_tree_update_url_value: "/api/v2/platform/#{child_resource_name.pluralize}/#{child_resource.id}/reposition" + } + ) + end + end + end +end diff --git a/app/helpers/spree_cm_commissioner/transit/service_calendars_helper.rb b/app/helpers/spree_cm_commissioner/transit/service_calendars_helper.rb new file mode 100644 index 000000000..c8695e48e --- /dev/null +++ b/app/helpers/spree_cm_commissioner/transit/service_calendars_helper.rb @@ -0,0 +1,17 @@ +module SpreeCmCommissioner + module Transit + module ServiceCalendarsHelper + def toggle_status_btn(resource) + label = resource.active ? 'Active' : 'Disabled' + btn_active_class = resource.active ? 'btn-primary' : 'btn-warning' + + button_to( + label, + update_status_transit_vendor_service_calendar_url(resource.id, resource), + form: { data: { confirm: 'Are you sure?' }, class: "btn btn-sm btn-active #{btn_active_class}" }, + method: :patch + ) + end + end + end +end diff --git a/app/models/concerns/spree_cm_commissioner/product_type.rb b/app/models/concerns/spree_cm_commissioner/product_type.rb index 5cfe30c96..fcfa560d4 100644 --- a/app/models/concerns/spree_cm_commissioner/product_type.rb +++ b/app/models/concerns/spree_cm_commissioner/product_type.rb @@ -5,7 +5,7 @@ module SpreeCmCommissioner module ProductType extend ActiveSupport::Concern - PRODUCT_TYPES = %i[accommodation service ecommerce].freeze + PRODUCT_TYPES = %i[accommodation service ecommerce transit].freeze included do enum product_type: PRODUCT_TYPES if table_exists? && column_names.include?('product_type') diff --git a/app/models/concerns/spree_cm_commissioner/route_type.rb b/app/models/concerns/spree_cm_commissioner/route_type.rb new file mode 100644 index 000000000..caff4a77b --- /dev/null +++ b/app/models/concerns/spree_cm_commissioner/route_type.rb @@ -0,0 +1,11 @@ +module SpreeCmCommissioner + module RouteType + extend ActiveSupport::Concern + + ROUTE_TYPES = %i[automobile subway rails ferry].freeze + + included do + enum route_type: ROUTE_TYPES if table_exists? && column_names.include?('route_type') + end + end +end diff --git a/app/models/concerns/spree_cm_commissioner/service_calendar_type.rb b/app/models/concerns/spree_cm_commissioner/service_calendar_type.rb new file mode 100644 index 000000000..51e08f96b --- /dev/null +++ b/app/models/concerns/spree_cm_commissioner/service_calendar_type.rb @@ -0,0 +1,11 @@ +module SpreeCmCommissioner + module ServiceCalendarType + extend ActiveSupport::Concern + + SERVICE_TYPES = %i[unavailable available].freeze + + included do + enum service_type: SERVICE_TYPES + end + end +end diff --git a/app/models/concerns/spree_cm_commissioner/taxonomy_kind.rb b/app/models/concerns/spree_cm_commissioner/taxonomy_kind.rb new file mode 100644 index 000000000..aeaeb27ae --- /dev/null +++ b/app/models/concerns/spree_cm_commissioner/taxonomy_kind.rb @@ -0,0 +1,11 @@ +module SpreeCmCommissioner + module TaxonomyKind + extend ActiveSupport::Concern + + KINDS = %i[cms category transit].freeze + + included do + enum kind: KINDS if table_exists? && column_names.include?('kind') + end + end +end diff --git a/app/models/spree_cm_commissioner/branch.rb b/app/models/spree_cm_commissioner/branch.rb new file mode 100644 index 000000000..dfc70e012 --- /dev/null +++ b/app/models/spree_cm_commissioner/branch.rb @@ -0,0 +1,12 @@ +require_dependency 'spree_cm_commissioner' + +module SpreeCmCommissioner + class Branch < SpreeCmCommissioner::Place + belongs_to :state, class_name: 'Spree::State', optional: true + belongs_to :vendor, class_name: 'Spree::Vendor' + + def validate_reference? + false + end + end +end diff --git a/app/models/spree_cm_commissioner/line_item_decorator.rb b/app/models/spree_cm_commissioner/line_item_decorator.rb index 22d1f9943..b1e19bbdb 100644 --- a/app/models/spree_cm_commissioner/line_item_decorator.rb +++ b/app/models/spree_cm_commissioner/line_item_decorator.rb @@ -3,11 +3,16 @@ module LineItemDecorator def self.prepended(base) base.include SpreeCmCommissioner::LineItemDurationable base.delegate :need_confirmation, to: :product - base.belongs_to :accepted_by, class_name: 'Spree::User', optional: true base.belongs_to :rejected_by, class_name: 'Spree::User', optional: true + base.has_many :line_item_seats, class_name: 'SpreeCmCommissioner::LineItemSeat', dependent: :destroy + + base.accepts_nested_attributes_for :line_item_seats, allow_destroy: true base.before_save :update_vendor_id + base.before_save :update_quantity, if: :transit? + + base.validate :validate_seats_reservation, if: :transit? base.delegate :product_type, :accommodation?, :service?, :ecommerce?, to: :product base.before_create :add_due_date, if: :subscription? @@ -15,6 +20,8 @@ def self.prepended(base) base.whitelisted_ransackable_attributes |= %w[to_date from_date] end + delegate :transit?, to: :variant + def reservation? date_present? && !subscription? end @@ -35,8 +42,27 @@ def amount end end + # override + def sufficient_stock? + return super unless variant.product.product_type == 'transit' + + transit_sufficient_stock? + end + private + def transit_sufficient_stock? + return selected_seats_available? if reservation_trip.allow_seat_selection + + seat_quantity_available?(reservation_trip) + end + + def update_quantity + return if line_item_seats.blank? + + self.quantity = line_item_seats.size + end + def update_vendor_id self.vendor_id = variant.vendor_id end @@ -69,6 +95,45 @@ def due_days end from_date + day.days end + + def validate_seats_reservation + if reservation_trip.allow_seat_selection && !selected_seats_available? + errors.add(:base, :some_seats_are_booked, message: 'Some seats are already booked') + elsif !reservation_trip.allow_seat_selection && !seat_quantity_available?(reservation_trip) + errors.add(:quantity, :exceeded_available_quantity, message: 'exceeded available quantity') + end + end + + def selected_seats_available? + selected_seat_ids = line_item_seats.map(&:seat_id) + !selected_seat_ids_occupied?(selected_seat_ids) + end + + def seat_quantity_available?(trip) + booked_quantity = Spree::LineItem.joins(:order) + .where(variant_id: variant_id, date: date, spree_orders: { state: 'complete' }) + .where.not(spree_line_items: { id: id }) + .sum(:quantity) + remaining_quantity = trip.vehicle.number_of_seats - booked_quantity + remaining_quantity >= quantity + end + + def reservation_trip + return @trip if defined? @trip + + route = Spree::Variant.find_by(id: variant_id).product + @trip = route.trip + end + + def selected_seat_ids_occupied?(selected_seat_ids) + # check to see if there are any selected_ids exist in the line_item_seats and belongs to completed order + SpreeCmCommissioner::LineItemSeat.joins(line_item: :order) + .where(seat_id: selected_seat_ids, date: date, variant_id: variant_id, spree_orders: { state: 'complete', + canceled_at: nil +} + ) + .present? + end end end diff --git a/app/models/spree_cm_commissioner/line_item_seat.rb b/app/models/spree_cm_commissioner/line_item_seat.rb new file mode 100644 index 000000000..3e5953f9b --- /dev/null +++ b/app/models/spree_cm_commissioner/line_item_seat.rb @@ -0,0 +1,10 @@ +require_dependency 'spree_cm_commissioner' +module SpreeCmCommissioner + class LineItemSeat < SpreeCmCommissioner::Base + belongs_to :line_item, class_name: 'Spree::LineItem' + belongs_to :seat, class_name: 'SpreeCmCommissioner::VehicleSeat' + belongs_to :variant, class_name: 'Spree::Variant' + + validates :date, presence: true + end +end diff --git a/app/models/spree_cm_commissioner/option_type_decorator.rb b/app/models/spree_cm_commissioner/option_type_decorator.rb index 30db3c45b..a4eb4efd5 100644 --- a/app/models/spree_cm_commissioner/option_type_decorator.rb +++ b/app/models/spree_cm_commissioner/option_type_decorator.rb @@ -1,10 +1,10 @@ module SpreeCmCommissioner module OptionTypeDecorator - ATTRIBUTE_TYPES = %w[float integer string boolean date coordinate state_selection].freeze + ATTRIBUTE_TYPES = %w[float integer string boolean date coordinate state_selection amenity departure_time duration vehicle_id].freeze def self.prepended(base) base.include SpreeCmCommissioner::ParameterizeName - base.enum kind: %i[variant product vendor] + base.enum kind: %i[variant product vendor vehicle_type transit] base.validates :name, presence: true base.validates :attr_type, inclusion: { in: ATTRIBUTE_TYPES } @@ -13,6 +13,16 @@ def self.prepended(base) base.scope :promoted, -> { where(promoted: true) } base.whitelisted_ransackable_attributes = %w[kind] + + def base.amenities + Spree::OptionType.where(kind: 'vehicle_type', name: 'amenities', presentation: 'Amenities', attr_type: 'amenity').first_or_create + end + + def base.vehicle + Spree::OptionType.where(presentation: 'vehicle', attr_type: 'vehicle_id', kind: 'variant', + name: 'vehicle' + ).first_or_create + end end private diff --git a/app/models/spree_cm_commissioner/option_value_vehicle_type.rb b/app/models/spree_cm_commissioner/option_value_vehicle_type.rb new file mode 100644 index 000000000..1e4c568a6 --- /dev/null +++ b/app/models/spree_cm_commissioner/option_value_vehicle_type.rb @@ -0,0 +1,8 @@ +require_dependency 'spree_cm_commissioner' + +module SpreeCmCommissioner + class OptionValueVehicleType < ApplicationRecord + belongs_to :vehicle_type, class_name: 'SpreeCmCommissioner::VehicleType', dependent: :destroy + belongs_to :option_value, class_name: 'Spree::OptionValue', dependent: :destroy + end +end diff --git a/app/models/spree_cm_commissioner/place.rb b/app/models/spree_cm_commissioner/place.rb index af7531b08..12c42f320 100644 --- a/app/models/spree_cm_commissioner/place.rb +++ b/app/models/spree_cm_commissioner/place.rb @@ -2,11 +2,23 @@ module SpreeCmCommissioner class Place < ApplicationRecord - validates :reference, presence: true - validates :lat, presence: true - validates :lon, presence: true + validates :reference, presence: true, if: :validate_reference? + validates :lat, presence: true, if: :validate_lat? + validates :lon, presence: true, if: :validate_lon? has_many :nearby_places, class_name: 'SpreeCmCommissioner::VendorPlace', dependent: :destroy has_many :vendors, through: :nearby_places, source: :vendor, class_name: 'Spree::Vendor' + + def validate_reference? + true + end + + def validate_lat? + true + end + + def validate_lon? + true + end end end diff --git a/app/models/spree_cm_commissioner/product_decorator.rb b/app/models/spree_cm_commissioner/product_decorator.rb index 2fa030ccd..579f1ab6f 100644 --- a/app/models/spree_cm_commissioner/product_decorator.rb +++ b/app/models/spree_cm_commissioner/product_decorator.rb @@ -2,6 +2,7 @@ module SpreeCmCommissioner module ProductDecorator def self.prepended(base) base.include SpreeCmCommissioner::ProductType + base.include SpreeCmCommissioner::RouteType base.has_many :variant_kind_option_types, -> { where(kind: :variant).order(:position) }, through: :product_option_types, source: :option_type @@ -18,6 +19,7 @@ def self.prepended(base) }, source: :prices, through: :variants_including_master base.has_one :default_state, through: :vendor + base.has_one :trip, class_name: 'SpreeCmCommissioner::Trip', dependent: :destroy base.scope :min_price, lambda { |vendor| joins(:prices_including_master) @@ -31,6 +33,10 @@ def self.prepended(base) .maximum('spree_prices.price').to_f } base.scope :subscribable, -> { where(subscribable: 1) } + + base.whitelisted_ransackable_attributes |= %w[short_name route_type] + + base.accepts_nested_attributes_for :trip, allow_destroy: true end end end diff --git a/app/models/spree_cm_commissioner/service_calendar.rb b/app/models/spree_cm_commissioner/service_calendar.rb index bf66ea00a..859743c97 100644 --- a/app/models/spree_cm_commissioner/service_calendar.rb +++ b/app/models/spree_cm_commissioner/service_calendar.rb @@ -1,17 +1,18 @@ require_dependency 'spree_cm_commissioner' module SpreeCmCommissioner - class ServiceCalendar < ApplicationRecord + class ServiceCalendar < SpreeCmCommissioner::Base # exception_rules = [ # { 'from' => Date.parse('2023-1-01'), 'to'=> Date.parse('2023-1-31'), 'type' => 'exclusion', 'reason' => 'Company retreat' } # ] # inclusion: Service has been added for the specified date (default). # exclusion: Service has been removed for the specified date. - + include SpreeCmCommissioner::ServiceCalendarType EXCEPTION_RULE_JSON_SCHEMA = Pathname.new("#{COMMISSIONER_ROOT}/config/schemas/service_calendar_exception_rule.json") - belongs_to :calendarable, polymorphic: true + belongs_to :calendarable, polymorphic: true, optional: false validates :exception_rules, json: { schema: EXCEPTION_RULE_JSON_SCHEMA } + self.whitelisted_ransackable_attributes = %w[name] ## Callbacks before_save :set_dates diff --git a/app/models/spree_cm_commissioner/state_decorator.rb b/app/models/spree_cm_commissioner/state_decorator.rb index ae6cfb480..b741f0a76 100644 --- a/app/models/spree_cm_commissioner/state_decorator.rb +++ b/app/models/spree_cm_commissioner/state_decorator.rb @@ -1,7 +1,9 @@ module SpreeCmCommissioner module StateDecorator def self.prepended(base) + base.whitelisted_ransackable_attributes |= %w[name abbr country_id] base.has_many :vendors, foreign_key: 'default_state_id', class_name: 'Spree::Vendor', inverse_of: :default_state, dependent: :nullify + base.has_many :stops, class_name: 'SpreeCmCommissioner::Place' def update_total_inventory update(total_inventory: vendors.pluck(:total_inventory).sum) diff --git a/app/models/spree_cm_commissioner/stop.rb b/app/models/spree_cm_commissioner/stop.rb new file mode 100644 index 000000000..e75687cf6 --- /dev/null +++ b/app/models/spree_cm_commissioner/stop.rb @@ -0,0 +1,23 @@ +require_dependency 'spree_cm_commissioner' + +module SpreeCmCommissioner + class Stop < SpreeCmCommissioner::Place + belongs_to :branch, class_name: 'SpreeCmCommissioner::Branch' + belongs_to :state, class_name: 'Spree::State', optional: true + belongs_to :vendor, class_name: 'Spree::Vendor' + + after_save :add_to_option_value + + def validate_reference? + false + end + + def add_to_option_value + origin = Spree::OptionType.find_by(attr_type: 'origin')&.id + destination = Spree::OptionType.find_by(attr_type: 'destination')&.id + + Spree::OptionValue.create(option_type_id: origin, name: name, presentation: id) + Spree::OptionValue.create(option_type_id: destination, name: name, presentation: id) + end + end +end diff --git a/app/models/spree_cm_commissioner/taxon_decorator.rb b/app/models/spree_cm_commissioner/taxon_decorator.rb index 9bea8b47c..703a07d37 100644 --- a/app/models/spree_cm_commissioner/taxon_decorator.rb +++ b/app/models/spree_cm_commissioner/taxon_decorator.rb @@ -1,6 +1,8 @@ module SpreeCmCommissioner module TaxonDecorator def self.prepended(base) + base.include SpreeCmCommissioner::Transit::TaxonBitwise + base.has_one :category_icon, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::TaxonCategoryIcon' base.has_one :web_banner, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::TaxonWebBanner' diff --git a/app/models/spree_cm_commissioner/taxonomy_decorator.rb b/app/models/spree_cm_commissioner/taxonomy_decorator.rb new file mode 100644 index 000000000..27cdd38ec --- /dev/null +++ b/app/models/spree_cm_commissioner/taxonomy_decorator.rb @@ -0,0 +1,17 @@ +module SpreeCmCommissioner + module TaxonomyDecorator + def self.prepended(base) + base.include SpreeCmCommissioner::TaxonomyKind + + def base.place + ActiveRecord::Base.connected_to(role: :writing) do + Spree::Taxonomy.find_or_create_by(name: 'Place', kind: 'transit', store: Spree::Store.default) + end + end + end + end +end + +unless Spree::Taxonomy.included_modules.include?(SpreeCmCommissioner::TaxonomyDecorator) + Spree::Taxonomy.prepend(SpreeCmCommissioner::TaxonomyDecorator) +end diff --git a/app/models/spree_cm_commissioner/trip.rb b/app/models/spree_cm_commissioner/trip.rb new file mode 100644 index 000000000..714093b6b --- /dev/null +++ b/app/models/spree_cm_commissioner/trip.rb @@ -0,0 +1,50 @@ +require_dependency 'spree_cm_commissioner' +module SpreeCmCommissioner + class Trip < SpreeCmCommissioner::Base + attr_accessor :hours, :minutes, :seconds + + before_validation :convert_duration_to_seconds + + belongs_to :route, class_name: 'Spree::Product' + belongs_to :vehicle, class_name: 'SpreeCmCommissioner::Vehicle' + validates :departure_time, presence: true + + validates :departure_time, presence: true + validates :duration, numericality: { greater_than: 0 } + validate :origin_and_destination_cannot_be_the_same + + has_many :trip_stops, class_name: 'SpreeCmCommissioner::TripStop', dependent: :destroy + + after_commit :create_trip_stops + + accepts_nested_attributes_for :trip_stops, allow_destroy: true + + def convert_duration_to_seconds + return if hours.blank? && minutes.blank? && seconds.blank? + + self.duration = (hours.to_i * 3600) + (minutes.to_i * 60) + seconds.to_i + end + + def create_trip_stops + trip_stops.create(stop_type: :boarding, stop_id: origin_id) + trip_stops.create(stop_type: :drop_off, stop_id: destination_id) + end + + def duration_in_hms + return { hours: 0, minutes: 0, seconds: 0 } if duration.nil? + + hours = duration / 3600 + minutes = (duration % 3600) / 60 + seconds = duration % 60 + { hours: hours, minutes: minutes, seconds: seconds } + end + + private + + def origin_and_destination_cannot_be_the_same + return unless origin_id == destination_id + + errors.add(:base, 'Origin and destination cannot be the same') + end + end +end diff --git a/app/models/spree_cm_commissioner/trip_stop.rb b/app/models/spree_cm_commissioner/trip_stop.rb new file mode 100644 index 000000000..954d8f52d --- /dev/null +++ b/app/models/spree_cm_commissioner/trip_stop.rb @@ -0,0 +1,29 @@ +require_dependency 'spree_cm_commissioner' +module SpreeCmCommissioner + class TripStop < SpreeCmCommissioner::Base + acts_as_list column: :sequence, scope: :trip_id + enum stop_type: { boarding: 0, drop_off: 1 } + + belongs_to :trip, class_name: 'SpreeCmCommissioner::Trip' + belongs_to :stop, class_name: 'Spree::Taxon' + + before_validation :set_stop_name + after_create :create_vendor_stop + + validates :stop_id, uniqueness: { scope: :trip_id } + + def set_stop_name + self.stop_name = stop.name + end + + def create_vendor_stop + vendor.vendor_stops.where(stop_id: stop_id, stop_type: stop_type).first_or_create + end + + private + + def vendor + Spree::Product.find(trip.product_id).vendor + end + end +end diff --git a/app/models/spree_cm_commissioner/variant_decorator.rb b/app/models/spree_cm_commissioner/variant_decorator.rb index e822d1eb6..8946f5f83 100644 --- a/app/models/spree_cm_commissioner/variant_decorator.rb +++ b/app/models/spree_cm_commissioner/variant_decorator.rb @@ -19,6 +19,10 @@ def display_variant "#{display_sku} - $#{display_price}" end + def transit? + product.product_type == 'transit' + end + private def update_vendor_price diff --git a/app/models/spree_cm_commissioner/vehicle.rb b/app/models/spree_cm_commissioner/vehicle.rb new file mode 100644 index 000000000..d9a5e5ecb --- /dev/null +++ b/app/models/spree_cm_commissioner/vehicle.rb @@ -0,0 +1,34 @@ +require_dependency 'spree_cm_commissioner' + +module SpreeCmCommissioner + class Vehicle < SpreeCmCommissioner::Base + include SpreeCmCommissioner::RouteType + + belongs_to :vehicle_type, class_name: 'SpreeCmCommissioner::VehicleType' + has_one :primary_photo, -> { order(position: :asc) }, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy + belongs_to :vendor, class_name: 'Spree::Vendor' + + before_save :set_attributes + after_commit :create_vehicle_option_value + + has_many :vehicle_photo, class_name: 'SpreeCmCommissioner::VehiclePhoto', as: :viewable, dependent: :destroy + has_many :vehicle_seats, class_name: 'SpreeCmCommissioner::VehicleSeat', through: :vehicle_type + + validates :code, uniqueness: { scope: :vendor_id }, presence: true + validates :license_plate, uniqueness: {}, allow_blank: true + validates :vehicle_type, presence: true + + def set_attributes + self.route_type = vehicle_type.route_type + self.number_of_seats = vehicle_type.vehicle_seats_count + self.allow_seat_selection = vehicle_type.allow_seat_selection + end + + def create_vehicle_option_value + SpreeCmCommissioner::VehicleOptionValueCreator.call(self) + end + + self.whitelisted_ransackable_attributes = %w[license_plate code] + self.whitelisted_ransackable_associations = %w[vehicle_type] + end +end diff --git a/app/models/spree_cm_commissioner/vehicle_photo.rb b/app/models/spree_cm_commissioner/vehicle_photo.rb new file mode 100644 index 000000000..85f04d6f2 --- /dev/null +++ b/app/models/spree_cm_commissioner/vehicle_photo.rb @@ -0,0 +1,13 @@ +module SpreeCmCommissioner + class VehiclePhoto < Asset + + # 16x9 + def asset_styles + { + mini: '160x90>', + small: '480x270>', + medium: '960x540>' + } + end + end +end \ No newline at end of file diff --git a/app/models/spree_cm_commissioner/vehicle_seat.rb b/app/models/spree_cm_commissioner/vehicle_seat.rb new file mode 100644 index 000000000..89871b8c6 --- /dev/null +++ b/app/models/spree_cm_commissioner/vehicle_seat.rb @@ -0,0 +1,11 @@ +module SpreeCmCommissioner + class VehicleSeat < SpreeCmCommissioner::Base + belongs_to :vehicle_type, class_name: 'SpreeCmCommissioner::VehicleType', dependent: :destroy + has_many :line_item_seats, class_name: 'SpreeCmCommissioner::LineItemSeat', dependent: :destroy + counter_culture :vehicle_type, column_name: proc { |model| + model.seat_type.in?(%w[normal vip]) && model.vehicle_type.allow_seat_selection == true ? 'vehicle_seats_count' : nil # rubocop:disable Layout/LineLength + }, + column_names: { ['cm_vehicle_seats.seat_type IN (?)', %i[0 2]] => 'vehicle_seats_count' } + enum seat_type: %i[normal empty vip driver].freeze + end +end diff --git a/app/models/spree_cm_commissioner/vehicle_type.rb b/app/models/spree_cm_commissioner/vehicle_type.rb new file mode 100644 index 000000000..43b089a2f --- /dev/null +++ b/app/models/spree_cm_commissioner/vehicle_type.rb @@ -0,0 +1,76 @@ +module SpreeCmCommissioner + class VehicleType < SpreeCmCommissioner::Base + include SpreeCmCommissioner::RouteType + + after_commit :recalculate_vehicle_seats_count, if: -> { saved_change_to_allow_seat_selection? && allow_seat_selection } + after_commit :set_vehicles_attributes + + has_many :vehicle_seats, class_name: 'SpreeCmCommissioner::VehicleSeat', dependent: :destroy + has_many :option_value_vehicle_types, class_name: 'SpreeCmCommissioner::OptionValueVehicleType' + has_many :option_values, through: :option_value_vehicle_types, class_name: 'Spree::OptionValue' + has_many :vehicles, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy + belongs_to :vendor, class_name: 'Spree::Vendor' + + validates :code, presence: true + validates :code, uniqueness: true + validates :name, presence: true + validates :name, uniqueness: true + accepts_nested_attributes_for :vehicle_seats, allow_destroy: true + + state_machine :status, initial: :draft do + event :draft do + transition to: :draft + end + after_transition to: :draft, do: :after_draft + + event :activate do + transition to: :active + end + after_transition to: :active, do: :after_activate + + event :archive do + transition to: :archived + end + after_transition to: :archived, do: :after_archive + end + + self.whitelisted_ransackable_attributes = %w[name code route_type id status] + + def after_activate; end + def after_archive; end + def after_draft; end + + def set_vehicles_attributes + vehicles.each(&:set_attributes) + vehicles.each(&:save) + end + + def recalculate_vehicle_seats_count + self.vehicle_seats_count = vehicle_seats.where(seat_type: %w[normal vip]).count + save + end + + def seat_layers + grouped_seats = SpreeCmCommissioner::VehicleSeat.where(vehicle_type_id: id).group_by(&:layer).transform_values do |seats| + seats.group_by(&:row).transform_values do |row_seats| + row_seats.sort_by(&:column).map do |seat| + { + row: seat.row, + column: seat.column, + label: seat.label, + layer: seat.layer, + seat_type: seat.seat_type, + created_at: seat.created_at, + vehicle_type_id: seat.vehicle_type_id + } + end + end + end + grouped_seats.map do |_layer, layer_seats| + layer_seats.map do |_row, row_seats| + row_seats + end + end + end + end +end diff --git a/app/models/spree_cm_commissioner/vendor_decorator.rb b/app/models/spree_cm_commissioner/vendor_decorator.rb index d9f8ce30e..49edf8e66 100644 --- a/app/models/spree_cm_commissioner/vendor_decorator.rb +++ b/app/models/spree_cm_commissioner/vendor_decorator.rb @@ -27,9 +27,18 @@ def self.prepended(base) base.has_many :vendor_kind_option_values, through: :option_value_vendors, source: :option_value + base.has_many :branches, class_name: 'SpreeCmCommissioner::Branch' + base.has_many :stops, class_name: 'SpreeCmCommissioner::Stop' + base.has_many :places, through: :nearby_places, source: :place, class_name: 'SpreeCmCommissioner::Place' + base.has_many :vendor_stops, class_name: 'SpreeCmCommissioner::VendorStop', dependent: :destroy + base.has_many :boarding_points, -> { where(cm_vendor_stops: { stop_type: 0 }) }, + through: :vendor_stops, source: :stop, class_name: 'Spree::Taxon' + base.has_many :drop_off_points, -> { where(cm_vendor_stops: { stop_type: 1 }) }, + through: :vendor_stops, source: :stop, class_name: 'Spree::Taxon' + base.has_one :logo, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::VendorLogo' base.has_one :payment_qrcode, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::VendorPaymentQrcode' base.has_one :web_promotion_banner, as: :viewable, dependent: :destroy, class_name: 'SpreeCmCommissioner::VendorWebPromotionBanner' @@ -55,6 +64,9 @@ def self.prepended(base) base.has_many :subscriptions, through: :customers, class_name: 'SpreeCmCommissioner::Subscription' base.has_many :subscription_orders, through: :subscriptions, class_name: 'Spree::Order', source: :orders + base.has_many :vehicle_types, class_name: 'SpreeCmCommissioner::VehicleType', dependent: :destroy + base.has_many :vehicles, through: :vehicle_types, class_name: 'SpreeCmCommissioner::Vehicle', dependent: :destroy + # TODO: we will need searchkick later # unless Rails.env.test? # base.searchkick( diff --git a/app/models/spree_cm_commissioner/vendor_stop.rb b/app/models/spree_cm_commissioner/vendor_stop.rb new file mode 100644 index 000000000..39be4113d --- /dev/null +++ b/app/models/spree_cm_commissioner/vendor_stop.rb @@ -0,0 +1,9 @@ +require_dependency 'spree_cm_commissioner' +module SpreeCmCommissioner + class VendorStop < ApplicationRecord + belongs_to :vendor, class_name: 'Spree::Vendor' + belongs_to :stop, class_name: 'Spree::Taxon' + + enum stop_type: { boarding: 0, drop_off: 1 } + end +end diff --git a/app/overrides/spree/admin/option_types/_option_value_fields/option_value_replacer.html.erb.deface b/app/overrides/spree/admin/option_types/_option_value_fields/option_value_replacer.html.erb.deface index 01fc10a03..7f84b3f1d 100644 --- a/app/overrides/spree/admin/option_types/_option_value_fields/option_value_replacer.html.erb.deface +++ b/app/overrides/spree/admin/option_types/_option_value_fields/option_value_replacer.html.erb.deface @@ -15,10 +15,14 @@ <%= f.number_field :presentation, step: 1, class: "form-control", required: true %> <% when "string" %> <%= f.text_field :presentation, class: "form-control", required: true %> + <% when "origin" %> + <%= f.text_field :presentation, class: "form-control", required: true %> + <% when "destination" %> + <%= f.text_field :presentation, class: "form-control", required: true %> <% when "boolean" %>
- <%= f.radio_button :presentation, '1' %> + <%= f.radio_button :presentation, '1' %> <%= f.label :presentation, 'true', :value => '1' %> <%= f.radio_button :presentation, '0' %> @@ -31,6 +35,10 @@ <%= f.text_field :presentation, class: "form-control", required: true %> <% when "state_selection" %> <%= f.text_field :presentation, class: "form-control", required: true %> + <% when "amenity" %> + <%= f.text_field :presentation, class: "form-control", required: true %> + <% when "vehicle" %> + <%= f.text_field :presentation, class: "form-control", required: true %> <% end %> diff --git a/app/overrides/spree/admin/option_types/index/option_row.html.erb.deface b/app/overrides/spree/admin/option_types/index/option_row.html.erb.deface index 84f7d07bf..9e0b93e3b 100644 --- a/app/overrides/spree/admin/option_types/index/option_row.html.erb.deface +++ b/app/overrides/spree/admin/option_types/index/option_row.html.erb.deface @@ -11,6 +11,7 @@ <% @badge_class = 'badge-primary' if option_type.kind == "variant" %> <% @badge_class = 'badge-secondary' if option_type.kind == "product" %> <% @badge_class = 'badge-dark' if option_type.kind == "vendor" %> + <% @badge_class = 'badge-info' if option_type.kind == "vehicle_type" %> <%= content_tag(:strong, class: "badge #{@badge_class} text-uppercase ") do; option_type.kind ; end %> <%= active_badge(option_type.filterable) %> diff --git a/app/queries/spree_cm_commissioner/trip_search_query.rb b/app/queries/spree_cm_commissioner/trip_search_query.rb new file mode 100644 index 000000000..47b76a871 --- /dev/null +++ b/app/queries/spree_cm_commissioner/trip_search_query.rb @@ -0,0 +1,76 @@ +module SpreeCmCommissioner + class TripSearchQuery + attr_reader :origin_id, :destination_id, :vendor_id, :date + + def initialize(origin_id:, destination_id:, date:, vendor_id: nil) + @origin_id = origin_id + @destination_id = destination_id + @vendor_id = vendor_id + @date = date + end + + def call + trips_info.map do |trip| + trip_result_options = { + trip_id: trip.trip_id, + vendor_id: trip.vendor_id, + vendor_name: trip.vendor_name, + route_name: trip.route_name, + short_name: trip.short_name, + detail: trip.trip_id, + origin_id: trip.origin_id, + origin: trip.origin, + destination_id: trip.destination_id, + destination: trip.destination, + total_sold: trip.total_sold, + total_seats: trip.total_seats, + vehicle_id: trip.vehicle_id, + departure_time: trip.departure_time.strftime('%H:%M'), + duration: trip.duration + } + SpreeCmCommissioner::TripResult.new(trip_result_options) + end + end + + def trips_info + result = Spree::Variant.select('spree_variants.id as trip_id, + spree_vendors.id as vendor_id, + spree_vendors.name as vendor_name, + routes.name as route_name, + routes.short_name as short_name, + details.id as detail_id, + boarding.stop_id as origin_id, + drop_off.stop_id as destination_id, + details.departure_time as departure_time, + details.duration as duration, + boarding.stop_name as origin, + drop_off.stop_name as destination, + COALESCE(ts.total_sold, 0) as total_sold, + cm_vehicles.number_of_seats as total_seats, + cm_vehicles.id as vehicle_id + ' + ) + .joins('INNER JOIN spree_products routes ON routes.id = spree_variants.product_id') + .joins('INNER JOIN cm_trips details on details.product_id = routes.id') + .joins('INNER JOIN cm_vehicles ON cm_vehicles.id = details.vehicle_id') + .joins("INNER JOIN cm_trip_stops boarding ON boarding.trip_id = details.id AND boarding.stop_type = '0'") + .joins("INNER JOIN cm_trip_stops drop_off ON drop_off.trip_id = details.id AND drop_off.stop_type = '1'") + .joins('INNER JOIN spree_vendors ON spree_vendors.id = spree_variants.vendor_id') + .joins("LEFT JOIN (#{total_sold.to_sql}) ts ON spree_variants.id = ts.trip_id") + + # TODO: migrat to new table: vehicle, orign and destination + result = result.where(boarding: { stop_id: origin_id }, drop_off: { stop_id: destination_id }) + result = result.where(vendor_id: vendor_id) if vendor_id.present? + result + end + + def total_sold + Spree::Variant.select('spree_variants.id as trip_id, SUM(spree_line_items.quantity) as total_sold') + .joins('INNER JOIN spree_line_items ON spree_line_items.variant_id = spree_variants.id') + .joins('INNER JOIN spree_orders ON spree_orders.id = spree_line_items.order_id') + .where(spree_orders: { state: 'complete' }) + .where(spree_line_items: { date: date }) + .group('spree_variants.id') + end + end +end diff --git a/app/queries/spree_cm_commissioner/trip_seat_layout_query.rb b/app/queries/spree_cm_commissioner/trip_seat_layout_query.rb new file mode 100644 index 000000000..9072cbc14 --- /dev/null +++ b/app/queries/spree_cm_commissioner/trip_seat_layout_query.rb @@ -0,0 +1,76 @@ +module SpreeCmCommissioner + class TripSeatLayoutQuery + attr_reader :trip_id, :date, :vehicle_type, :vehicle, :route + + def initialize(trip_id:, date:) + @trip_id = trip_id + @date = date + @route = Spree::Variant.find_by(id: trip_id).product + @vehicle = SpreeCmCommissioner::Vehicle.find_by(id: route.trip.vehicle_id) + @vehicle_type = SpreeCmCommissioner::VehicleType.find_by(id: vehicle.vehicle_type_id) + end + + def call + allow_seat_selection = @route.trip.allow_seat_selection + total_sold, remaining_seats, layout = process_seat_selection(allow_seat_selection) + + SpreeCmCommissioner::TripSeatLayoutResult.new({ trip_id: trip_id, total_sold: total_sold, + total_seats: vehicle.number_of_seats, + remaining_seats: remaining_seats, + layout: layout, + allow_seat_selection: allow_seat_selection + } + ) + end + + def process_seat_selection(allow_seat_selection) + # layout_structure + # {"First_Layer" => {"Row_1" => [{seat1}, {seat2}, {seat3}, {seat4}], + # {"Row_2" => [{seat5}, {seat6}, {seat7}, {seat8}], + # } + if allow_seat_selection + vehicle_seats = seats.to_a + total_sold = vehicle_seats.count { |s| s.seat_id.present? } + remaining_seats = vehicle.number_of_seats - total_sold + layout = vehicle_seats.group_by(&:layer).transform_values do |s| + s.group_by(&:row).transform_values do |r| + r.sort_by(&:column).map do |seat| + { + row: seat.row, + column: seat.column, + label: seat.label, + layer: seat.layer, + seat_type: seat.seat_type, + created_at: seat.created_at, + seat_id: seat.seat_id, + vehicle_type_id: seat.vehicle_type_id + } + end + end + end + else + total_sold = Spree::LineItem.joins(:order) + .where(variant_id: trip_id, date: date, spree_orders: { state: 'complete' }) + .sum(:quantity) + remaining_seats = vehicle.number_of_seats - total_sold + layout = nil + end + + [total_sold, remaining_seats, layout] + end + + def seats + SpreeCmCommissioner::VehicleSeat.select('cm_vehicle_seats.*, os.seat_id as seat_id') + .joins("LEFT JOIN (#{ordered_seats.to_sql}) os ON cm_vehicle_seats.id = os.seat_id") + .where('cm_vehicle_seats.vehicle_type_id = ? ', vehicle_type.id) + end + + def ordered_seats + SpreeCmCommissioner::LineItemSeat.select('cm_line_item_seats.seat_id') + .joins('INNER JOIN spree_line_items ON cm_line_item_seats.line_item_id = spree_line_items.id') + .joins('INNER JOIN spree_orders ON spree_orders.id = spree_line_items.order_id') + .where('spree_orders.state = ? ', 'complete') + .where('cm_line_item_seats.variant_id = ? AND cm_line_item_seats.date = ?', trip_id, date) + end + end +end diff --git a/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb b/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb new file mode 100644 index 000000000..31eeee8e9 --- /dev/null +++ b/app/services/spree_cm_commissioner/vehicle_option_value_creator.rb @@ -0,0 +1,11 @@ +module SpreeCmCommissioner + class VehicleOptionValueCreator + def self.call(vehicle) + vehicle_option_type = Spree::OptionType.vehicle + + value = vehicle_option_type.option_values.where(name: vehicle.id).first_or_initialize + value.presentation = vehicle.code + value.save + end + end +end diff --git a/app/views/spree/admin/vendor_service_calendars/_form.html.erb b/app/views/spree/admin/vendor_service_calendars/_form.html.erb index 59d6ae14d..4993faf19 100644 --- a/app/views/spree/admin/vendor_service_calendars/_form.html.erb +++ b/app/views/spree/admin/vendor_service_calendars/_form.html.erb @@ -1,4 +1,4 @@ -
+
diff --git a/app/views/spree/admin/vendor_service_calendars/index.html.erb b/app/views/spree/admin/vendor_service_calendars/index.html.erb index 78d23057c..7575de307 100644 --- a/app/views/spree/admin/vendor_service_calendars/index.html.erb +++ b/app/views/spree/admin/vendor_service_calendars/index.html.erb @@ -1,73 +1,70 @@ <%= render partial: 'spree/admin/shared/vendor_tabs', locals: {current: :service_calendars} %> - <% content_for :page_actions do %> <%= yield :page_actions %> <%= button_link_to(t('vendor.service_calendars.new_calendar'), new_admin_vendor_vendor_service_calendar_url( @vendor), { class: "btn-success", icon: 'add.svg', id: 'new_service_calendar_link' }) if can? :create, SpreeCmCommissioner:: ServiceCalendar %> <% end %> - <% unless @objects.empty? %> -
- - - - - - - - - - - - - - <% @objects.each do |service_calendar| %> - - - - - - - +
+
- <%= Spree.t(:id) %> - - <%= t('vendor.service_calendars.start_date') %> - - <%= t('vendor.service_calendars.end_date') %> - - <%= t('vendor.service_calendars.operations_day') %> - - <%= t('vendor.service_calendars.exception_rules') %> -
<%= service_calendar.id %><%= service_calendar.start_date %><%= service_calendar.end_date %> - <%= check_box_tag :monday, service_calendar.monday, service_calendar.monday, disabled: true %> Monday - <%= check_box_tag :tuesday, service_calendar.tuesday, service_calendar.tuesday, disabled: true %> Tuesday - <%= check_box_tag :wednesday, service_calendar.wednesday, service_calendar.wednesday, disabled: true %> Wednesday - <%= check_box_tag :thursday, service_calendar.thursday, service_calendar.thursday, disabled: true %> Thursday - <%= check_box_tag :friday, service_calendar.friday, service_calendar.friday, disabled: true %> Friday - <%= check_box_tag :saturday, service_calendar.saturday, service_calendar.saturday, disabled: true %> Saturday - <%= check_box_tag :sunday, service_calendar.sunday, service_calendar.sunday, disabled: true %> Sunday - - <% service_calendar.exception_rules.each do |exception_rule| %> -
-
Date: <%= exception_rule['from'] %> - <%= exception_rule['to'] %>
-
Type: <%= exception_rule['type'] %>
-
Reason: <%= exception_rule['reason'] %>
-
- <%end%> -
- - <%= toggle_status_btn(service_calendar) %> - <%= link_to_delete(service_calendar, { url: admin_vendor_vendor_service_calendar_url(@vendor, service_calendar), no_text: true }) if can? :destroy, service_calendar %> - -
+ + + + + + + + - <% end %> - -
+ <%= Spree.t(:id) %> + + <%= t('vendor.service_calendars.start_date') %> + + <%= t('vendor.service_calendars.end_date') %> + + <%= t('vendor.service_calendars.operations_day') %> + + <%= t('vendor.service_calendars.exception_rules') %> +
-
+ + + <% @objects.each do |service_calendar| %> + + <%= service_calendar.id %> + <%= service_calendar.start_date %> + <%= service_calendar.end_date %> + + <%= check_box_tag :monday, service_calendar.monday, service_calendar.monday, disabled: true %> Monday + <%= check_box_tag :tuesday, service_calendar.tuesday, service_calendar.tuesday, disabled: true %> Tuesday + <%= check_box_tag :wednesday, service_calendar.wednesday, service_calendar.wednesday, disabled: true %> Wednesday + <%= check_box_tag :thursday, service_calendar.thursday, service_calendar.thursday, disabled: true %> Thursday + <%= check_box_tag :friday, service_calendar.friday, service_calendar.friday, disabled: true %> Friday + <%= check_box_tag :saturday, service_calendar.saturday, service_calendar.saturday, disabled: true %> Saturday + <%= check_box_tag :sunday, service_calendar.sunday, service_calendar.sunday, disabled: true %> Sunday + + + <% service_calendar.exception_rules.each do |exception_rule| %> +
+
Date: <%= exception_rule['from'] %> - <%= exception_rule['to'] %>
+
Type: <%= exception_rule['type'] %>
+
Reason: <%= exception_rule['reason'] %>
+
+ <%end%> + + + + <%= toggle_status_btn(service_calendar) %> + <%= link_to_delete(service_calendar, { url: admin_vendor_vendor_service_calendar_url(@vendor, service_calendar), no_text: true }) if can? :destroy, service_calendar %> + + + + <% end %> + + +
<% else %> - - <%= raw I18n.t('vendor.service_calendars.empty_info') %> - -<% end %> \ No newline at end of file + + <%= raw I18n.t('vendor.service_calendars.empty_info') %> + +<% end %> diff --git a/app/views/spree/layouts/transit.html.erb b/app/views/spree/layouts/transit.html.erb new file mode 100644 index 000000000..208b60acf --- /dev/null +++ b/app/views/spree/layouts/transit.html.erb @@ -0,0 +1,74 @@ + + + + <%= render partial: 'spree/admin/shared/head' %> + <%= yield :extra_head if content_for?(:extra_head) %> + + + <%#-------------------------------------------------%> + <%# Header navbar %> + <%#-------------------------------------------------%> + <%= render partial: 'spree/transit/shared/header' %> + <%#-------------------------------------------------%> + <%# Main content %> + <%#-------------------------------------------------%> +
+
+ <%#-------------------------------------------------%> + <%# Sidebar %> + <%#-------------------------------------------------%> + + <%#-------------------------------------------------%> + <%# Content %> + <%#-------------------------------------------------%> +
+
+ <%#-------------------------------------------------%> + <%# Content header (page title/actions) %> + <%#-------------------------------------------------%> + <%= render partial: 'spree/admin/shared/content_header' %> +
+ <%#-------------------------------------------------%> + <%# Main content %> + <%#-------------------------------------------------%> +
+
+ <%= render partial: 'spree/admin/shared/table_filter' if content_for?(:table_filter) %> + <%= yield %> +
+
+ <%#-------------------------------------------------%> + <%# Inner aside %> + <%#-------------------------------------------------%> + <% if content_for?(:sidebar) || content_for?(:page_actions) %> +
+ <%= render partial: 'spree/admin/shared/sidebar' %> +
+ <% end %> +
+
+
+
+
+ <%#-------------------------------------------------%> + <%# Insert footer scripts here %> + <%#-------------------------------------------------%> + +
+ <%#-------------------------------------------------%> + <%# Alerts %> + <%#-------------------------------------------------%> +
+ <%= flash_alert(flash) %> +
+ + diff --git a/app/views/spree/transit/amenities/_form.html.erb b/app/views/spree/transit/amenities/_form.html.erb new file mode 100644 index 000000000..ef8127cc0 --- /dev/null +++ b/app/views/spree/transit/amenities/_form.html.erb @@ -0,0 +1,50 @@ +
+
+ <%= f.field_container :name do %> + <%= f.label :name, raw(Spree.t(:name) + required_span_tag) %> + <%= f.text_field :name, value: "amenities" ,readonly: :true, class: "form-control" %> + <%= f.error_message_on :name %> + <% end %> +
+ +
+ <%= f.field_container :presentation do %> + <%= f.label :presentation, raw(Spree.t(:presentation) + required_span_tag) %> + <%= f.text_field :presentation, value: "Amenities", readonly: :true, class: "form-control" %> + <%= f.error_message_on :presentation %> + <% end %> +
+ +
+ <% if @amenity.new_record? %> + <%= f.field_container :kind, class: ['form-group'] do %> + <%= f.label :kind, Spree.t(:kind) %> * + <%= f.text_field :kind, readonly: true, class: 'form-control' %> + <% end %> + <% else %> + <%= f.field_container :kind do %> + <%= f.label :kind, Spree.t(:kind) %> * + <%= f.text_field :kind, disabled: true, class: "form-control" %> + + <%= raw I18n.t('option_type.kind_info') %> + + <% end %> + <% end %> +
+ +
+ <% if @amenity.new_record? %> + <%= f.field_container :attr_type, class: ['form-group'] do %> + <%= f.label :attr_type, Spree.t(:attribute_type) %> * + <%= f.text_field :attr_type, readonly: :true, class: "form-control" %> + <%= f.error_message_on :attr_type %> + <% end %> + <% else %> + <%= f.field_container :attr_type, class: ['form-group'] do %> + <%= f.label :attr_type, Spree.t(:attribute_type) %> * + <%= f.text_field :attr_type, disabled: true, class: "form-control" %> + <%= f.error_message_on :attr_type %> + <% end %> + <% end %> +
+
diff --git a/app/views/spree/transit/amenities/_option_value_fields.html.erb b/app/views/spree/transit/amenities/_option_value_fields.html.erb new file mode 100644 index 000000000..6d98b96e8 --- /dev/null +++ b/app/views/spree/transit/amenities/_option_value_fields.html.erb @@ -0,0 +1,15 @@ + + + + <%= svg_icon name: "grip-vertical.svg", width: '18', height: '18' %> + <%= f.hidden_field :id %> + + <%= f.text_field :name, class: "form-control", required: true %> + <%= f.text_field :presentation, class: "form-control", required: true %> + + + + <%= link_to_icon_remove_fields f %> + + + diff --git a/app/views/spree/transit/amenities/edit.html.erb b/app/views/spree/transit/amenities/edit.html.erb new file mode 100644 index 000000000..d832c619c --- /dev/null +++ b/app/views/spree/transit/amenities/edit.html.erb @@ -0,0 +1,53 @@ +<% content_for :page_title do %> + <%= Spree.t(:amenities) %> +<% end %> +<%= form_with model: @amenity, url: { action: 'update' } do |f| %> +
+
+
+ <%= Spree.t(:add_amenity_values) %> +
+
+
+ <%= render partial: 'form', locals: { f: f } %> +
+
+
+ + + + + + + + + + <% if @amenity.option_values.empty? %> + + + + + <% else %> + <%= f.fields_for :option_values do |option_value_form| %> + <%= render partial: 'option_value_fields', locals: { f: option_value_form } %> + <% end %> + <% end %> + + + + + + +
<%= raw(Spree.t(:name) + required_span_tag) %><%= raw(Spree.t(:display) + required_span_tag) %>
<%= Spree.t(:none) %>
+ <%= button_link_to Spree.t(:add_amenity_values_icon), transit_vectors_amenity_values_url, { icon: 'spree-icon.svg', id: 'admin-option-values-icons-index' } %> + <%= button_link_to Spree.t(:add_amenity_values), "javascript:;", { icon: 'add.svg', :'data-target' => "tbody#sortVert", class: 'btn-success mx-auto spree_add_fields' } %> + +
+
+
+ +
+<% end %> + diff --git a/app/views/spree/transit/branches/_filter_form.erb b/app/views/spree/transit/branches/_filter_form.erb new file mode 100644 index 000000000..2347faee9 --- /dev/null +++ b/app/views/spree/transit/branches/_filter_form.erb @@ -0,0 +1,23 @@ +<% content_for :table_filter do %> +
+ <%= search_form_for [:branch, @search], url: spree.transit_branches_path do |f| %> +
+
+
+ <%= f.label :name_cont, I18n.t('spree.name') %> + <%= f.text_field :name_cont, class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :q_state_id_eq, I18n.t('spree.transit.location') %> + <%= f.select :state_id_eq, Spree::State.all.map{|s| [s.name, s.id]}, { prompt: "Select a location" }, class: "form-control select2 js-quick-search-target js-filterable" %> +
+
+
+
+ <%= button Spree.t(:search), 'search.svg' %> +
+ <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/branches/_form.html.erb b/app/views/spree/transit/branches/_form.html.erb new file mode 100644 index 000000000..4d91c1001 --- /dev/null +++ b/app/views/spree/transit/branches/_form.html.erb @@ -0,0 +1,36 @@ +
+
+ <%= f.field_container :name do %> + <%= f.label :name, Spree.t(:name) %> + <%= f.text_field :name, class: 'form-control' %> + <%= f.error_message_on :name %> + <% end %> + <%= f.hidden_field :vendor_id, value: current_vendor.id %> + <%= f.field_container :lat do %> + <%= f.label :lat, Spree.t("transit.branch.lat.label") %> + <%= f.text_field :lat, class: 'form-control', placeholder: Spree.t("transit.branch.lat.hint") %> + <%= f.error_message_on :lat %> + <% end %> + <%= f.field_container :lon do %> + <%= f.label :lon, Spree.t("transit.branch.lon.label") %> + <%= f.text_field :lon, class: 'form-control', placeholder: Spree.t("transit.branch.lon.hint") %> + <%= f.error_message_on :lon %> + <% end %> + <%= f.field_container :formatted_phone_number do %> + <%= f.label :formatted_phone_number, Spree.t("phone_number") %> + <%= f.text_field :formatted_phone_number, class: 'form-control', placeholder: Spree.t("phone_number") %> + <%= f.error_message_on :formatted_phone_number %> + <% end %> + <%= f.field_container :formatted_address do %> + <%= f.label :formatted_address, Spree.t("transit.branch.address") %> + <%= f.text_field :formatted_address, class: 'form-control', placeholder: ("Google Map Address") %> + <%= f.error_message_on :formatted_address %> + <% end %> + <%= f.field_container :state_id do %> + <%= f.label :state_id, Spree.t("transit.location") %> + <%= f.collection_select(:state_id, Spree::State.all, :id, :name, { prompt: "Select a location" }, { class: 'form-control select2' }) %> + <%= f.error_message_on :state_id %> + <% end %> + <%= render 'shared/map', lat: f.object.lat, lon: f.object.lon %> +
+
diff --git a/app/views/spree/transit/branches/edit.html.erb b/app/views/spree/transit/branches/edit.html.erb new file mode 100644 index 000000000..9ebccc6af --- /dev/null +++ b/app/views/spree/transit/branches/edit.html.erb @@ -0,0 +1,15 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_branches_path %> + <%= I18n.t('spree.transit.branch.branches') %> +<% end %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+
+ <%= form_with model: @object, url: { action: 'update' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.transit_branches_path } %> +
+ <% end %> +
diff --git a/app/views/spree/transit/branches/index.html.erb b/app/views/spree/transit/branches/index.html.erb new file mode 100644 index 000000000..c49580c15 --- /dev/null +++ b/app/views/spree/transit/branches/index.html.erb @@ -0,0 +1,43 @@ +<% content_for :page_title do %> + <%= I18n.t('spree.transit.branch.branches') %> +<% end %> + +<% content_for :page_actions do %> + <%= button_link_to Spree.t(:add_branch), new_transit_branch_path, class: "btn-success", icon: 'add.svg', id: 'admin_new_customer_link' %> +<% end %> + +<% render 'filter_form' %> + +<% if @branches.any? %> +
+ + + + + + + + + + + + <% @branches.each do |branch| %> + + + + + + + + <% end %> + +
+ <%= sort_link @search, :name, Spree.t(:name), {}, {title:"id_title"} %> + <%= Spree.t(:phone_number) %><%= Spree.t(:address) %><%= sort_link @search, :state_id, Spree.t(:location) %>
<%= branch.name %><%= branch.formatted_phone_number %><%= branch.formatted_address %><%= branch.state.name %> + + <%= link_to_edit branch, url: edit_transit_branch_path(branch), no_text: true, class: 'edit' if can?(:edit, branch) %> + <%= link_to_delete branch, url: transit_branch_path(branch), no_text: true if can?(:delete, branch) %> + +
+
+<% end %> diff --git a/app/views/spree/transit/branches/new.html.erb b/app/views/spree/transit/branches/new.html.erb new file mode 100644 index 000000000..40d9df1ff --- /dev/null +++ b/app/views/spree/transit/branches/new.html.erb @@ -0,0 +1,19 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_branches_path %> + <%= I18n.t('spree.transit.branch.branches') %> +<% end %> + +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +
+ <%= form_with model: @object, url: { action: 'create' } do |f| %> +
+ <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/new_resource_links', locals: { collection_url: spree.transit_branches_path } %> +
+
+ <% end %> +
diff --git a/app/views/spree/transit/locations/_filter_form.erb b/app/views/spree/transit/locations/_filter_form.erb new file mode 100644 index 000000000..4bb728f2c --- /dev/null +++ b/app/views/spree/transit/locations/_filter_form.erb @@ -0,0 +1,30 @@ +<% content_for :table_filter do %> +
+ <%= search_form_for [:transit, @search], method: :get, url: spree.transit_locations_path do |f| %> +
+
+
+ <%= f.label :name_cont, I18n.t('spree.name') %> + <%= f.text_field :name_cont, placeholder: 'Location Name' , class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :abbr_cont, I18n.t('spree.transit.abbreviation') %> + <%= f.text_field :abbr_cont, placeholder: 'Location Abbreviation' , class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :country_id_eq, Spree.t(:country) %> + <%= f.select :country_id_eq, Spree::Country.joins(:states).distinct.map{|s| [s.name, s.id]}, { prompt: 'Select a Country' }, class: 'form-control select2 js-quick-search-target js-filterable'%> +
+
+ +
+
+ <%= button Spree.t(:search), 'search.svg' %> +
+ <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/locations/_form.html.erb b/app/views/spree/transit/locations/_form.html.erb new file mode 100644 index 000000000..36f050b66 --- /dev/null +++ b/app/views/spree/transit/locations/_form.html.erb @@ -0,0 +1,19 @@ +
+
+ <%= f.field_container :name do %> + <%= f.label :name, Spree.t(:name) %> + <%= f.text_field :name, class: 'form-control' %> + <%= f.error_message_on :name %> + <% end %> + <%= f.field_container :abbr do %> + <%= f.label :abbreviation, Spree.t(:abbreviation) %> + <%= f.text_field :abbr, class: 'form-control', required: true %> + <%= f.error_message_on :abbr %> + <% end %> + <%= f.field_container :country_id do %> + <%= f.label :country_id %> + <%= f.collection_select(:country_id, Spree::Country.all, :id, :name, {prompt: "Select a country"}, { class: 'form-control select2'} )%> + <%= f.error_message_on :country_id %> + <% end %> +
+
diff --git a/app/views/spree/transit/locations/edit.html.erb b/app/views/spree/transit/locations/edit.html.erb new file mode 100644 index 000000000..fef45e0ce --- /dev/null +++ b/app/views/spree/transit/locations/edit.html.erb @@ -0,0 +1,15 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_locations_path %> + <%= I18n.t('spree.transit.locations') %> +<% end %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+
+ <%= form_with model: @object, url: { action: 'update' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.transit_locations_path } %> +
+ <% end %> +
diff --git a/app/views/spree/transit/locations/index.html.erb b/app/views/spree/transit/locations/index.html.erb new file mode 100644 index 000000000..a6a1f3279 --- /dev/null +++ b/app/views/spree/transit/locations/index.html.erb @@ -0,0 +1,49 @@ +<% content_for :page_title do %> + <%= I18n.t('spree.transit.locations') %> +<% end %> + +<% content_for :page_actions do %> + <%= button_link_to Spree.t(:add_location), new_transit_location_path, class: "btn-success", icon: 'add.svg', id: 'admin_new_customer_link' %> +<% end %> + +<% render 'filter_form' %> +<% if @locations.any? %> +
+ + + + + + + + + + + <% @locations.each do |location| %> + + + + + + + <% end %> + +
+ <%= sort_link @search, :name, Spree.t(:name), {}, {title:"id_title"} %> + + <%= sort_link @search, :abbr, Spree.t(:abbreviation), {}, {title: 'fullname_title'} %> + + <%= Spree.t(:Country) %> +
+ <%= location.name %> + + <%= location.abbr %> + + <%= location.country.name %> + + + <%= link_to_edit location, url: edit_transit_location_path(location), no_text: true, class: 'edit' if can?(:edit, location) %> + +
+
+<% end %> diff --git a/app/views/spree/transit/locations/new.html.erb b/app/views/spree/transit/locations/new.html.erb new file mode 100644 index 000000000..86a5ba1db --- /dev/null +++ b/app/views/spree/transit/locations/new.html.erb @@ -0,0 +1,17 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_locations_path %> + <%= I18n.t('spree.transit.locations') %> +<% end %> + +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +
+ <%= form_with model: @object, url: { action: 'create' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/new_resource_links', locals: { collection_url: spree.transit_locations_path } %> +
+ <% end %> +
diff --git a/app/views/spree/transit/places/_assets_form.html.erb b/app/views/spree/transit/places/_assets_form.html.erb new file mode 100644 index 000000000..0d8d4aa88 --- /dev/null +++ b/app/views/spree/transit/places/_assets_form.html.erb @@ -0,0 +1,8 @@ +<%= f.field_container :icon do %> + <%= f.label :icon, Spree.t(:header_banner), class: 'mb-3' %> + <%= image_tag(main_app.cdn_image_url(@taxon.icon.try(:attachment)), class: 'w-100') if @taxon.icon %> + <%= f.file_field :icon, class: 'mt-3' %> + <% if @taxon.icon.present? %> + <%= link_to Spree.t(:remove_image), remove_icon_admin_taxonomy_taxon_url(@taxonomy.id, @taxon.id), method: :delete %> + <% end %> +<% end %> diff --git a/app/views/spree/transit/places/_form.html.erb b/app/views/spree/transit/places/_form.html.erb new file mode 100644 index 000000000..2cb5c0d1e --- /dev/null +++ b/app/views/spree/transit/places/_form.html.erb @@ -0,0 +1,128 @@ +
+
+
+ <%= f.field_container :name do %> + <%= f.label :name, raw(Spree.t(:name) + required_span_tag) %> + <%= text_field :taxon, :name, class: 'form-control', required: true %> + <%= f.error_message_on :name, class: 'error-message' %> + <% end %> +
+ + <% if @taxon.root.present? %> +
+
+ <%= f.label :data_type, raw(Spree.t(:data_type) + required_span_tag) %> +
+ +
+ <%= f.field_container :stop, class: ['custom-control', 'custom-checkbox'] do %> + <%= f.check_box :stop, class: 'custom-control-input stop checkboxGroup', checked: f.object.persisted? && f.object.stop? %> + <%= f.label :stop, Spree.t(:stop), class: 'custom-control-label' %> + <%= f.error_message_on :stop %> + <% end %> +
+ +
+ <%= f.field_container :station, class: ['custom-control', 'custom-checkbox'] do %> + <%= f.check_box :station, class: 'custom-control-input station checkboxGroup', checked: f.object.persisted? && f.object.station? %> + <%= f.label :station, Spree.t(:station), class: 'custom-control-label' %> + <%= f.error_message_on :station %> + <% end %> +
+ +
+ <%= f.field_container :branch, class: ['custom-control', 'custom-checkbox'] do %> + <%= f.check_box :branch, class: 'custom-control-input branch checkboxGroup', checked: f.object.persisted? && f.object.branch? %> + <%= f.label :branch, Spree.t(:branch), class: 'custom-control-label' %> + <%= f.error_message_on :branch %> + <% end %> +
+ +
+ <%= f.field_container :location, class: ['custom-control', 'custom-checkbox'] do %> + <%= f.check_box :location, class: 'custom-control-input location checkboxGroup', checked: f.object.persisted? && f.object.location? %> + <%= f.label :location, Spree.t(:location), class: 'custom-control-label' %> + <%= f.error_message_on :location %> + <% end %> +
+
+ <% end %> + +
+ <%= f.field_container :hide_from_nav, class: ['custom-control', 'custom-checkbox'] do %> + <%= f.check_box :hide_from_nav, class: 'custom-control-input' %> + <%= f.label :hide_from_nav, Spree.t(:hide_from_subcategories_nav), class: 'custom-control-label' %> + <%= f.error_message_on :hide_from_nav %> + <% end %> +
+
+ <% unless @taxon.new_record? %> + <%= f.field_container :permalink do %> + <%= label_tag :permalink_part, raw(Spree.t(:permalink) + required_span_tag) %> +
+
+ + <%= [Spree::Config[:storefront_taxons_path] , @parent_permalink].join('/') %> + +
+ <%= text_field_tag :permalink_part, @permalink_part, class: 'form-control', required: true %> +
+ <% end %> + <% end %> +
+
+ <% if @taxon.new_record? %> + <% disabled = false %> + <% else %> + <% disabled = @taxon.root? %> + <% end %> + <%= f.field_container :parent_id do %> + <%= f.label :parent_id, Spree.t('admin.navigation.nested_under') %> + <%= f.select :parent_id, nested_set_options(@place_taxonomy.taxons, @taxon) {|i| "#{'-' * i.level} #{i.name}" }, { include_blank: false }, { class: 'select2', disabled: disabled } %> + <%= f.error_message_on :parent_id %> + + <%= Spree.t('admin.taxon.nested_under_info') %> + + <% end %> +
+
+ <% unless @taxon.new_record? %> +
+
+
+
+
+
+
+ <%= f.field_container :description do %> + <%= f.label :description, Spree.t(:description) %> + <%= f.text_area :description, class: "form-control #{'spree-rte' if taxon_wysiwyg_editor_enabled? }", rows: 14 %> + <% end %> +
+
+ <%= render 'assets_form', f: f %> +
+
+
+
+
+
+
+
+
+ <%= f.field_container :meta_title do %> + <%= f.label :meta_title, Spree.t(:meta_title) %> + <%= f.text_field :meta_title, class: 'form-control', rows: 6 %> + <% end %> + <%= f.field_container :meta_description do %> + <%= f.label :meta_description, Spree.t(:meta_description) %> + <%= f.text_area :meta_description, class: 'form-control', rows: 6 %> + <% end %> + <%= f.field_container :meta_keywords do %> + <%= f.label :meta_keywords, Spree.t(:meta_keywords) %> + <%= f.text_field :meta_keywords, class: 'form-control', rows: 6 %> + <% end %> +
+
+ <% end %> +
diff --git a/app/views/spree/transit/places/edit.html.erb b/app/views/spree/transit/places/edit.html.erb new file mode 100644 index 000000000..2a3f02cf8 --- /dev/null +++ b/app/views/spree/transit/places/edit.html.erb @@ -0,0 +1,21 @@ + +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_places_path %> + <%= @place_taxonomy.name %>/ + <%= @taxon.name %> +<% end %> + +<%# Because otherwise the form would attempt to use to_param of @taxon %> + + +<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @taxon } %> + +<%= form_with model: @object, url: { action: 'update'}, id: "checkbox-form" do |f| %> + <%= render 'form', f: f %> + +
+ <%= button Spree.t('actions.update'), 'save.svg' %> + <%= Spree.t(:or) %> + <%= button_link_to Spree.t('actions.cancel'), transit_places_url(), icon: "cancel.svg" %> +
+<% end %> diff --git a/app/views/spree/transit/places/index.html.erb b/app/views/spree/transit/places/index.html.erb new file mode 100644 index 000000000..2e7e4a10a --- /dev/null +++ b/app/views/spree/transit/places/index.html.erb @@ -0,0 +1,32 @@ +<% content_for :page_title do %> + <%= @place_taxonomy&.name %> +<% end %> + +<% content_for :page_actions do %> + <%= button_link_to Spree.t('transit.places.add_new_place'), spree.new_transit_place_path(), icon: 'add.svg', class: 'no-wrap btn btn-success align-self-center' %> +<% end %> + + +
+ +
+ <% if @place_taxonomy.root.present? %> +
+
+ + <% if @place_taxonomy.root.children.present? %> + <% @place_taxonomy.root.children.each do |root_children| %> + <%= build_sortable_tree(@place_taxonomy, root_children) %> + <% end %> + <% else %> +
+ <%= raw Spree.t('transit.taxonomies.no_taxons', taxonomy: @place_taxonomy.root.name ) %> +
+ <% end %> + +
+
+ <% end %> +
+
+ diff --git a/app/views/spree/transit/places/new.html.erb b/app/views/spree/transit/places/new.html.erb new file mode 100644 index 000000000..3fb222ea7 --- /dev/null +++ b/app/views/spree/transit/places/new.html.erb @@ -0,0 +1,16 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_places_url %> + <%= @place_taxonomy.name %> / <%= Spree.t(:new_place) %> +<% end %> + +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +<%= form_with model: @object, url: { action: 'create' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/new_resource_links', locals: { collection_url: spree.transit_places_url } %> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/spree/transit/reservations/index.html.erb b/app/views/spree/transit/reservations/index.html.erb new file mode 100644 index 000000000..7dca7d7e2 --- /dev/null +++ b/app/views/spree/transit/reservations/index.html.erb @@ -0,0 +1,5 @@ +<% content_for :page_title do %> + <%= I18n.t('spree.transit.reservations') %> +<% end %> +<% content_for :page_actions do %> +<% end %> diff --git a/app/views/spree/transit/routes/_filter_form.erb b/app/views/spree/transit/routes/_filter_form.erb new file mode 100644 index 000000000..733e4956a --- /dev/null +++ b/app/views/spree/transit/routes/_filter_form.erb @@ -0,0 +1,24 @@ +<% content_for :table_filter do %> +
+ <%= search_form_for [:transit, @search], url: spree.transit_routes_path do |f| %> +
+
+
+ <%= f.label :name_cont, I18n.t('spree.name') %> + <%= f.text_field :name_cont, placeholder: I18n.t('spree.name'), class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :short_name_cont, I18n.t('spree.transit.abbreviation') %> + <%= f.text_field :short_name_cont, placeholder: I18n.t('spree.transit.abbreviation'), class: "form-control js-filterable" %> +
+
+
+ +
+ <%= button Spree.t(:search), 'search.svg' %> +
+ <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/routes/_form.html.erb b/app/views/spree/transit/routes/_form.html.erb new file mode 100644 index 000000000..4a072164c --- /dev/null +++ b/app/views/spree/transit/routes/_form.html.erb @@ -0,0 +1,74 @@ +
+
+ <%= f.field_container :name do %> + <%= f.label :long_name, Spree.t(:long_name) %> + <%= f.text_field :name, class: 'form-control' %> + <%= f.error_message_on :name %> + <% end %> +
+
+ <%= f.field_container :short_name do %> + <%= f.label :short_name, Spree.t(:short_name) %> + <%= f.text_field :short_name, class: 'form-control' %> + <%= f.error_message_on :short_name %> + <% end %> +
+
+
+ <%= f.hidden_field :status,value: 'active' %> +
+ <%= f.field_container :url do %> + <%= f.label :url, Spree.t(:url) %> + <%= f.text_field :url, class: 'form-control' %> + <%= f.error_message_on :url %> + <% end %> + <%= f.field_container :description do %> + <%= f.label :description, Spree.t(:description) %> + <%= f.text_field :description, class: 'form-control' %> + <%= f.error_message_on :description %> + <% end %> + +
+
+ <%= f.field_container :price do %> + <%= f.label :price, raw(Spree.t(:master_price) + required_span_tag) %> +
+
+ <%= currency_symbol(current_currency) %> +
+ <%= f.text_field :price, value: number_to_currency(@product.price, unit: ''), class: 'form-control', required: :required %> +
+ <%= f.error_message_on :price %> + <% end %> +
+
+ <%= f.field_container :route_type, :class => ['form-group'] do %> + <%= f.label :route_type, Spree.t('route_type') %>
+ <%= f.collection_select :route_type, SpreeCmCommissioner::RouteType::ROUTE_TYPES, :to_s, :capitalize, + {:include_blank => false}, + {:class => 'select2 fullwidth', + :disabled => defined?(disabled) && disabled == true ? 'disable' : nil }.compact %> + <% end %> +
+
+ + <%= f.hidden_field(:vendor_id,value: @current_vendor.id, class: ['form-group']) do %> + <%= f.label :vendor_id, Spree.t(:vendor) %> + <%= f.collection_select(:vendor_id, @vendors, :id, :name, { include_blank: false}, { class: 'select2' }) %> + <%= f.error_message_on :vendor %> + <% end %> +
+ <%= f.hidden_field(:shipping_category_id, value: @shipping_categories.first.id) do%> + <%= f.label :shipping_category_id, raw(Spree.t(:shipping_categories) + required_span_tag) %> + <%= f.collection_select(:shipping_category_id, @shipping_categories, :id, :name, { class: 'select2 w-100', include_blank: true }) %> + <%= f.error_message_on :shipping_category_id %> + <% end %> +
+ + <%= f.hidden_field :product_type, :class => ['form-group'] do %> + <%= f.label :product_type, Spree.t('product_type') %>
+ <%= f.collection_select :product_type, SpreeCmCommissioner::ProductType::PRODUCT_TYPES, :to_s, :capitalize, + {:include_blank => true}, + {:class => 'select2 fullwidth', + :disabled => defined?(disabled) && disabled == true ? 'disable' : nil }.compact %> + <% end %> diff --git a/app/views/spree/transit/routes/edit.html.erb b/app/views/spree/transit/routes/edit.html.erb new file mode 100644 index 000000000..1706d0af4 --- /dev/null +++ b/app/views/spree/transit/routes/edit.html.erb @@ -0,0 +1,13 @@ +<%= render partial: 'spree/transit/shared/route_tabs', locals: {current: :route} %> + +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+
+ <%= form_with model: @object, url: { action: 'update' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.transit_routes_path } %> +
+ <% end %> +
diff --git a/app/views/spree/transit/routes/index.html.erb b/app/views/spree/transit/routes/index.html.erb new file mode 100644 index 000000000..ff1830165 --- /dev/null +++ b/app/views/spree/transit/routes/index.html.erb @@ -0,0 +1,79 @@ +<% content_for :page_title do %> + <%= I18n.t('spree.transit.routes') %> +<% end %> +<% content_for :page_actions do %> + <%= button_link_to I18n.t('spree.transit.new.route'), new_transit_route_path, { class: "btn-success", icon: 'add.svg', id: 'admin_new_product' } %> +<% end %> +<% render 'filter_form' %> +<% if @collection.any? %> +
+ + + + + + + + + + + + + + + <% @collection.includes(:trip).each do |route| %> + + + + + + + + + + <% end %> + +
+ <%= Spree.t(:name)%> + + <%= Spree.t(:short_name) %> + + <%= Spree.t(:route_type) %> + + <%= Spree.t(:departure_time) %> + + <%= Spree.t(:duration) %> + + <%= Spree.t(:price) %> +
+ <%= link_to route.name, edit_transit_route_path(route) %> + + <%= route.short_name %> + + <% if route.route_type.present? %> + <%= route.route_type.capitalize %> + <% end %> + + <%= route.trip.departure_time.strftime('%H:%M') if route.trip.present? %> + + <% duration = route.trip.duration_in_hms if route.trip.present?%> + <%= "#{duration[:hours]}hrs-#{duration[:minutes]}mns" if duration %> + + <%= display_price(route) %> + + + <% route_trip = (route.trip ? edit_transit_route_trip_path(route, route.trip) : new_transit_route_trip_path(route)) %> + <%= link_to_edit route, url: edit_transit_route_path(route.id), no_text: true, class: 'edit' if can?(:edit, route.id) && !route.deleted? %> + <%= link_to_with_icon 'location.svg', 'Edit Trip' ,route_trip, class:'edit btn btn-light', no_text: true %> + +
+
+<% else %> +
+ <%= Spree.t(:no_resource_found, resource: plural_resource_name(Spree::Product)) %>, + <%= link_to Spree.t(:add_one), new_object_url if can?(:create, Spree::Product) %>! +
+<% end %> + +<%= render partial: 'spree/admin/shared/index_table_options', locals: { collection: @collection } %> + diff --git a/app/views/spree/transit/routes/new.html.erb b/app/views/spree/transit/routes/new.html.erb new file mode 100644 index 000000000..c16f22210 --- /dev/null +++ b/app/views/spree/transit/routes/new.html.erb @@ -0,0 +1,18 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_routes_path %> + <%= I18n.t('spree.transit.routes') %> +<% end %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +<%= form_with model: @object, url: { action: 'create' } do |f| %> +
+
+
+ <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/new_resource_links',locals: { collection_url: spree.transit_routes_path } %> +
+
+
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/service_calendars/_exception_rules.html.erb b/app/views/spree/transit/service_calendars/_exception_rules.html.erb new file mode 100644 index 000000000..748eba1ed --- /dev/null +++ b/app/views/spree/transit/service_calendars/_exception_rules.html.erb @@ -0,0 +1,44 @@ +
+ + + + + + + + + + <% @exception_rules&.each_with_index do |exception_rule, index| %> + <%= f.fields_for 'exception_rules' do |exception_rule_fm|%> + + + + + + <% end %> + <% end %> + + + + + + +
+ <%= raw(t('vendor.service_calendars.from_date') + required_span_tag) %> + + <%= raw(t('vendor.service_calendars.to_date') + required_span_tag) %> +
+ <%= exception_rule_fm.date_field "#{[index]}[from]", class: 'form-control' %> + + <%= exception_rule_fm.date_field "#{[index]}[to]", class: 'form-control' %> + + + <%= link_to_icon_remove_fields f%> + +
+
+ <%= button_link_to Spree.t(:add_exception_rule), "javascript:;" , { icon: 'add.svg' , :'data-target'=> + "tbody#exceptionRules", class: 'btn-success mx-auto spree_add_fields' } %> +
+
+
diff --git a/app/views/spree/transit/service_calendars/_filters.html.erb b/app/views/spree/transit/service_calendars/_filters.html.erb new file mode 100644 index 000000000..3af708717 --- /dev/null +++ b/app/views/spree/transit/service_calendars/_filters.html.erb @@ -0,0 +1,17 @@ +
+ <%= search_form_for [:transit, @search], url: spree.transit_vendor_service_calendars_path do |f| %> +
+
+
+
+ <%= f.label :name_cont, Spree.t(:name) %> + <%= f.text_field :name_cont, placeholder: "Service Calendar Name", class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= button Spree.t(:search), 'search.svg' %> +
+
+ <% end %> +
diff --git a/app/views/spree/transit/service_calendars/_form.html.erb b/app/views/spree/transit/service_calendars/_form.html.erb new file mode 100644 index 000000000..61e1a5ce8 --- /dev/null +++ b/app/views/spree/transit/service_calendars/_form.html.erb @@ -0,0 +1,107 @@ +
+
+
+
+
+
+ <%= f.label :name, raw(Spree.t(:name) + required_span_tag) %> + <%= f.text_field :name, class: 'form-control', required: true %> +
+
+
+
+ <%= f.label :service_type, raw(Spree.t(:service_type) + required_span_tag) %> + <%= f.select :service_type, options_for_select(SpreeCmCommissioner::ServiceCalendarType::SERVICE_TYPES), {}, class: 'form-control select2 fullwidth' %> +
+
+
+
+
+
+ <%= f.label :not_available_reason %> + <%= f.text_area :not_available_reason, class: 'form-control', placeholder: "Reason when serivce is not available" %> +
+
+
+
+
+
+ <%= f.label :start_date %> +
+ <%= f.date_field :start_date, + placeholder: Spree.t(:select_a_date), + class: 'form-control ', + 'data-input':'' %> + <%= render partial: 'spree/admin/shared/cal_close' %> +
+
+
+
+
+ <%= f.label :end_date %> +
+ <%= f.date_field :end_date, + placeholder: Spree.t(:select_a_date), + class: 'form-control ', + 'data-input':'' %> + <%= render partial: 'spree/admin/shared/cal_close' %> +
+
+
+
+
+ <%= f.label :service_day %> +
+
+ <%= f.check_box :monday, class: 'custom-control-input' %> + <%= f.label :monday, class: 'custom-control-label' %> +
+
+ <%= f.check_box :tuesday, class: 'custom-control-input' %> + <%= f.label :tuesday, class: 'custom-control-label' %> +
+
+ <%= f.check_box :wednesday, class: 'custom-control-input' %> + <%= f.label :wednesday, class: 'custom-control-label' %> +
+
+ <%= f.check_box :thursday, class: 'custom-control-input' %> + <%= f.label :thursday, class: 'custom-control-label' %> +
+
+ <%= f.check_box :friday, class: 'custom-control-input' %> + <%= f.label :friday, class: 'custom-control-label' %> +
+
+ <%= f.check_box :saturday, class: 'custom-control-input' %> + <%= f.label :saturday, class: 'custom-control-label' %> +
+
+ <%= f.check_box :sunday, class: 'custom-control-input' %> + <%= f.label :sunday, class: 'custom-control-label' %> +
+
+
+
+
+
+
+
Execption Rules
+
+
+
+ <%= render partial: 'exception_rules', locals: { f: f, object: @object } %> +
+
+
+
diff --git a/app/views/spree/transit/service_calendars/index.html.erb b/app/views/spree/transit/service_calendars/index.html.erb new file mode 100644 index 000000000..4bbc81929 --- /dev/null +++ b/app/views/spree/transit/service_calendars/index.html.erb @@ -0,0 +1,77 @@ +<%= render partial: 'spree/transit/vendors/tabs', locals: {current: :service_calendars} %> +<% content_for :page_title do %> + <%= I18n.t('spree.transit.service_calendar.title') %> +<% end %> +<% content_for :page_actions do %> + <%= button_link_to I18n.t('spree.transit.service_calendar.new'), new_transit_vendor_service_calendar_path, { class: "btn-success", icon: 'add.svg', id: 'admin_new_product' } %> +<% end %> +<% content_for :table_filter do %> + <%= render partial: 'filters' %> +<% end %> +<% unless @collection.empty? %> +
+ + + + + + + + + + + + + + <% @collection.each do |service_calendar| %> + + + + + + + + + + <% end %> + +
+ <%= Spree.t(:id) %> + + <%= t('vendor.service_calendars.name') %> + + <%= t('vendor.service_calendars.start_date') %> + + <%= t('vendor.service_calendars.end_date') %> + + <%= t('vendor.service_calendars.operations_day') %> + + <%= t('vendor.service_calendars.exception_rules') %> +
<%= service_calendar.id %><%= service_calendar.name %><%= service_calendar.start_date %><%= service_calendar.end_date %> + <%= check_box_tag :monday, service_calendar.monday, service_calendar.monday, disabled: true %> Monday + <%= check_box_tag :tuesday, service_calendar.tuesday, service_calendar.tuesday, disabled: true %> Tuesday + <%= check_box_tag :wednesday, service_calendar.wednesday, service_calendar.wednesday, disabled: true %> Wednesday + <%= check_box_tag :thursday, service_calendar.thursday, service_calendar.thursday, disabled: true %> Thursday + <%= check_box_tag :friday, service_calendar.friday, service_calendar.friday, disabled: true %> Friday + <%= check_box_tag :saturday, service_calendar.saturday, service_calendar.saturday, disabled: true %> Saturday + <%= check_box_tag :sunday, service_calendar.sunday, service_calendar.sunday, disabled: true %> Sunday + + <% service_calendar.exception_rules.each do |exception_rule| %> +
+
Date: <%= exception_rule['from'] %> - <%= exception_rule['to'] %>
+
Type: <%= exception_rule['type'] %>
+
Reason: <%= exception_rule['reason'] %>
+
+ <%end%> +
+ + <%= toggle_status_btn(service_calendar) %> + <%= link_to_delete(service_calendar, { url: admin_vendor_vendor_service_calendar_url(@vendor, service_calendar), no_text: true }) if can? :destroy, service_calendar %> + +
+
+<% else %> + + <%= raw I18n.t('vendor.service_calendars.empty_info') %> + +<% end %> diff --git a/app/views/spree/transit/service_calendars/new.html.erb b/app/views/spree/transit/service_calendars/new.html.erb new file mode 100644 index 000000000..89a7e7b2d --- /dev/null +++ b/app/views/spree/transit/service_calendars/new.html.erb @@ -0,0 +1,13 @@ +<%= render partial: 'spree/transit/vendors/tabs', locals: {current: :service_calendars} %> +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_vendor_service_calendars_path %> + <%= Spree.t(:service_calendar) %> +<% end %> +<%= form_with model: @object, url: { action: 'create' } do |f| %> +
+
+ <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/new_resource_links',locals: { collection_url: spree.transit_vendor_service_calendars_path } %> +
+
+<% end %> diff --git a/app/views/spree/transit/shared/_amenity_tabs.html.erb b/app/views/spree/transit/shared/_amenity_tabs.html.erb new file mode 100644 index 000000000..615a84164 --- /dev/null +++ b/app/views/spree/transit/shared/_amenity_tabs.html.erb @@ -0,0 +1,21 @@ +<% +=begin%> + <% content_for(:page_tabs) do %> + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'edit.svg', + Spree.t(:details), + spree.edit_transit_amenity_path(@option_type), + class: "nav-link #{'active' if current == :details}" %> + + <% end if can?(:transit, Spree::OptionType) %> + + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'translate.svg', + Spree.t(:amenity_values_icon), + spree.transit_vectors_option_values_url, + class: "nav-link #{'active' if current == :amenity_value_icons}" %> + + <% end if can?(:transit, Spree::OptionType) %> +<% end %> +<% +=end%> diff --git a/app/views/spree/transit/shared/_header.html.erb b/app/views/spree/transit/shared/_header.html.erb new file mode 100644 index 000000000..1fc819be5 --- /dev/null +++ b/app/views/spree/transit/shared/_header.html.erb @@ -0,0 +1,20 @@ +
+ +
diff --git a/app/views/spree/transit/shared/_main_menu.html.erb b/app/views/spree/transit/shared/_main_menu.html.erb new file mode 100644 index 000000000..57e7d6b3b --- /dev/null +++ b/app/views/spree/transit/shared/_main_menu.html.erb @@ -0,0 +1,34 @@ + diff --git a/app/views/spree/transit/shared/_route_tabs.html.erb b/app/views/spree/transit/shared/_route_tabs.html.erb new file mode 100644 index 000000000..e5a9ccba9 --- /dev/null +++ b/app/views/spree/transit/shared/_route_tabs.html.erb @@ -0,0 +1,25 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_routes_path %> + <%= @product.name %> +<% end %> + +<% content_for(:page_tabs) do %> + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'edit.svg', + Spree.t(:route), + edit_transit_route_path(@product), + class: "nav-link #{'active' if current == :route}" %> + <% end %> + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'edit.svg', + Spree.t(:trip), + (@product.trip ? edit_transit_route_trip_path(@product, @product.trip) : new_transit_route_trip_path(@product)), + class: "nav-link #{'active' if current == :trip}" %> + <% end %> + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'edit.svg', + Spree.t(:trip_stops), + (@product.trip && transit_route_trip_trip_stops_path(@product, @product.trip)), + class: "nav-link #{'active' if current == :trip_stop} #{'disabled' if !@product.trip } "%> + <% end %> +<% end %> diff --git a/app/views/spree/transit/shared/_vehicle_tabs.html.erb b/app/views/spree/transit/shared/_vehicle_tabs.html.erb new file mode 100644 index 000000000..e7cd63c03 --- /dev/null +++ b/app/views/spree/transit/shared/_vehicle_tabs.html.erb @@ -0,0 +1,15 @@ +<% content_for(:page_tabs) do %> + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'edit.svg', + Spree.t(:details), + edit_transit_vehicle_path(@vehicle), + class: "nav-link #{'active' if current == :details}" %> + <% end %> + + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'images.svg', + Spree.t(:images), + spree.transit_vehicle_vehicle_photos_path(@vehicle), + class: "nav-link #{'active' if current == :images}" %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/shared/_vendor_switcher.html.erb b/app/views/spree/transit/shared/_vendor_switcher.html.erb new file mode 100644 index 000000000..cf17c761e --- /dev/null +++ b/app/views/spree/transit/shared/_vendor_switcher.html.erb @@ -0,0 +1,40 @@ +<% if can?(:admin, current_store) %> + + <% if current_vendor.logo %> + qrcode + <% else %> + <%= svg_icon name: "building.svg", width: '25', height: '25' %> + <% end %> + + + <% if vendors.size <= 1 %> + + <% end %> + +<% end %> + diff --git a/app/views/spree/transit/shared/sortable_tree/_taxonomy.html.erb b/app/views/spree/transit/shared/sortable_tree/_taxonomy.html.erb new file mode 100644 index 000000000..10c1300ba --- /dev/null +++ b/app/views/spree/transit/shared/sortable_tree/_taxonomy.html.erb @@ -0,0 +1,29 @@ +<% taxonomy = parent_resource %> +<% taxon = child_resource %> + +
+
+ <%= svg_icon(name: 'grip-vertical.svg', width: '20', height: '20') %> +
+ +
+
+ <%= taxon.name %> + + <%= Spree.t('admin.taxonomies.submenu') %>: <%= active_badge(!taxon.hide_from_nav) %> + +
+ <% if taxon.icon.present? %> +
+
+ <%= image_tag main_app.cdn_image_url(taxon.icon.attachment) %> +
+
+ <% end %> +
+ +
+ <%= link_to_edit(taxon, no_text: true, url: edit_transit_place_path(taxon.id)) %> + <%= link_to_delete(taxon, no_text: true, url: spree.transit_place_path(taxon.id)) if can?(:destroy, taxon) %> +
+
diff --git a/app/views/spree/transit/shared/sub_menu/_setting.html.erb b/app/views/spree/transit/shared/sub_menu/_setting.html.erb new file mode 100644 index 000000000..34271c594 --- /dev/null +++ b/app/views/spree/transit/shared/sub_menu/_setting.html.erb @@ -0,0 +1,3 @@ + diff --git a/app/views/spree/transit/stops/_filters.html.erb b/app/views/spree/transit/stops/_filters.html.erb new file mode 100644 index 000000000..ad8613542 --- /dev/null +++ b/app/views/spree/transit/stops/_filters.html.erb @@ -0,0 +1,33 @@ +
+ + <%= search_form_for [:transit, @search], url: spree.transit_stops_path do |f| %> +
+
+
+
+ <%= f.label :name_cont, Spree.t(:name) %> + <%= f.text_field :name_cont, placeholder: "Stop Name", class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :branch_id, I18n.t('spree.transit.branch') %> + <%= f.select :branch_id_eq, SpreeCmCommissioner::Branch.where(vendor_id: current_vendor.id).map{|s| [s.name, s.id]}, { prompt: "Select a branch" } , class: "form-control select2 js-filterable" %> +
+
+
+
+
+
+ <%= f.label :state_id_eq, I18n.t('spree.transit.location') %> + <%= f.select :state_id_eq, Spree::State.all.map{|s| [s.name, s.id]}, { prompt: "Select a location" } , class: "form-control select2 js-filterable" %> +
+
+
+
+
+ <%= button Spree.t(:search), 'search.svg' %> +
+ <% end %> + +
diff --git a/app/views/spree/transit/stops/_form.html.erb b/app/views/spree/transit/stops/_form.html.erb new file mode 100644 index 000000000..87ff06296 --- /dev/null +++ b/app/views/spree/transit/stops/_form.html.erb @@ -0,0 +1,68 @@ +
+
+ <%= f.field_container :code do %> + <%= f.label :code, Spree.t(:code) %> + <%= f.text_field :code, class: 'form-control', required: true %> + <%= f.error_message_on :code %> + <% end %> + + <%= f.hidden_field :vendor_id, value: current_vendor.id %> + + <%= f.field_container :name do %> + <%= f.label :name, Spree.t(:name) %> + <%= f.text_field :name, class: 'form-control', required: true %> + <%= f.error_message_on :name %> + <% end %> + + <%= f.field_container :formatted_phone_number do %> + <%= f.label :formatted_phone_number, Spree.t(:phone_number) %> + <%= f.text_field :formatted_phone_number, class: 'form-control', required: true %> + <%= f.error_message_on :formatted_phone_number %> + <% end %> + + <%= f.field_container :formatted_address do %> + <%= f.label :formatted_address, Spree.t(:address) %> + <%= f.text_field :formatted_address, class: 'form-control', required: true %> + <%= f.error_message_on :formatted_address %> + <% end %> + + <%= f.field_container :state_id do %> + <%= f.label :location %> + <%= f.collection_select(:state_id, @locations, :id, :name, {prompt: "Select a Location"}, { class: 'form-control select2'} )%> + <%= f.error_message_on :state_id %> + <% end %> + + <%= f.field_container :branch_id do %> + <%= f.label :branch_id %> + <%= f.collection_select(:branch_id, SpreeCmCommissioner::Branch.where(vendor_id: current_vendor.id), :id, :name, {prompt: "Select a Branch"}, { class: 'form-control select2'} )%> + <%= f.error_message_on :branch_id %> + <% end %> +
+ +
+
+
+
+ <%= f.field_container :lat do %> + <%= f.label :lat, t("stock_location.lat.label") %> + <%= f.text_field :lat, class: 'form-control', placeholder: t("stock_location.lat.hint"), required: true, id: "stock_location_lat" %> + <% end %> +
+
+
+
+ <%= f.field_container :lon do %> + <%= f.label :lon, t("stock_location.lon.label") %> + <%= f.text_field :lon, class: 'form-control', placeholder: t("stock_location.lon.hint"), required: true, id: "stock_location_lon" %> + <% end %> +
+
+
+
+
+ <%= render 'shared/map', lat: f.object.lat, lon: f.object.lon %> +
+
+
+
+ diff --git a/app/views/spree/transit/stops/edit.html.erb b/app/views/spree/transit/stops/edit.html.erb new file mode 100644 index 000000000..d7c8c57a2 --- /dev/null +++ b/app/views/spree/transit/stops/edit.html.erb @@ -0,0 +1,16 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_stops_path %> + <%= I18n.t('spree.transit.stops') %> +<% end %> + +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+
+ <%= form_with model: @object, url: { action: 'update' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.transit_stops_path } %> +
+ <% end %> +
diff --git a/app/views/spree/transit/stops/index.html.erb b/app/views/spree/transit/stops/index.html.erb new file mode 100644 index 000000000..011aad8cd --- /dev/null +++ b/app/views/spree/transit/stops/index.html.erb @@ -0,0 +1,58 @@ +<% content_for :page_title do %> + <%= I18n.t('spree.transit.stops') %> +<% end %> + +<% content_for :page_actions do %> + <%= button_link_to Spree.t(:add_stop), new_transit_stop_path, class: "btn-success", icon: 'add.svg', id: 'admin_new_customer_link' %> +<% end %> + +<% content_for :table_filter do %> + <%= render partial: 'filters' %> +<% end %> + +<% if @stops.any? %> +
+ + + + + + + + + + + + <% @stops.each do |stop| %> + + + + + + + + <% end %> + +
+ <%= Spree.t(:name) %> + + <%= Spree.t(:branch) %> + + <%= Spree.t(:location) %> + + <%= Spree.t(:address) %> +
+ <%= stop.name %> + + <%= stop.branch.name %> + + <%= stop.state %> + + <%= stop.formatted_address %> + + + <%= link_to_edit stop, url: edit_transit_stop_path(stop), no_text: true, class: 'edit' if can?(:edit, stop) %> + +
+
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/stops/new.html.erb b/app/views/spree/transit/stops/new.html.erb new file mode 100644 index 000000000..87127c88c --- /dev/null +++ b/app/views/spree/transit/stops/new.html.erb @@ -0,0 +1,16 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_stops_path %> + <%= I18n.t('spree.transit.stops') %> +<% end %> + +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +<%= form_with model: @object, url: { action: 'create' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/new_resource_links', locals: { collection_url: spree.transit_stops_path } %> +
+ <% end %> +
diff --git a/app/views/spree/transit/trip_stops/index.html.erb b/app/views/spree/transit/trip_stops/index.html.erb new file mode 100644 index 000000000..d395a51dd --- /dev/null +++ b/app/views/spree/transit/trip_stops/index.html.erb @@ -0,0 +1,36 @@ +<%= render partial: 'spree/transit/shared/route_tabs', locals: {current: :trip_stop} %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+<% if @trip_stops.any? %> +
+ + + + + + + + + + + + <% @trip_stops.each do |stop| %> + + + + + + + + <% end %> + +
<%= Spree.t(:name) %><%= Spree.t(:stop_type) %><%= Spree.t(:sequence) %><%= Spree.t(:created_at) %>
+ <%= svg_icon name: "grip-vertical.svg", width: '18', height: '18' %> + <%= stop.stop_name %> <%= stop.stop_type %> <%= stop.sequence %> <%= stop.created_at.to_date %>
+
+<% else %> +
+ <%= Spree.t(:no_trip_stop_found) %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/trips/_form.html.erb b/app/views/spree/transit/trips/_form.html.erb new file mode 100644 index 000000000..972bf11a9 --- /dev/null +++ b/app/views/spree/transit/trips/_form.html.erb @@ -0,0 +1,82 @@ +
+
+ <%= f.field_container :origin_id do %> + <%= f.label :Origin, Spree.t(:origin) %> + <%= f.select :origin_id, options_from_collection_for_select(@places, 'id', 'name', @object.origin_id), {},{class: 'select2 fullwidth', id:'product_origin_id'} %> + <%= f.error_message_on :origin_id %> + <% end %> +
+
+ <%= f.field_container :destination_id do %> + <%= f.label :Destination, Spree.t(:destination) %> + <%= f.select :destination_id, options_from_collection_for_select(@places, 'id', 'name', @object.destination_id), {},{class: 'select2 fullwidth', id:'product_destination_id'} %> + <%= f.error_message_on :destination_id %> + <% end %> +
+
+ <%= f.field_container :allow_seat_selection, :class => ['form_group'] do %> + <%= f.label :seat_selection, Spree.t(:seat_selection) %> + <%= f.select(:allow_seat_selection, options_for_select([[ 'True', true ], [ 'False', false ]], @object.allow_seat_selection), {}, {class: 'form-control select2', id:'allow_seat_selection'})%> + <%= f.error_message_on :allow_seat_selection%> + <%end%> +
+
+ <%= f.field_container :vehicle_id do %> + <%= f.label :vehicle, Spree.t(:vehicle) %> + <%= f.collection_select :vehicle_id, @vehicles, :id, ->(vehicle) { "#{vehicle.code} - #{vehicle.vehicle_type.name} - #{vehicle.number_of_seats}seats" },{},{class: 'select2'} %> + <%= f.error_message_on :name %> + <% end %> +
+
+ <%= f.field_container :duration, :class => ['form-group'] do %> + <%= f.label :duration, Spree.t(:duration) %> +
+
+
+ <%= f.number_field :hours, class: 'form-control', placeholder: 'Hours', value: @object.duration_in_hms[:hours] || 0, min: 0, required: true %> + h +
+ <%= f.error_message_on :hours %> +
+
+
+ <%= f.number_field :minutes, class: 'form-control', placeholder: 'Minutes', value: @object.duration_in_hms[:minutes] || 0, min: 0, required: true %> + m +
+ <%= f.error_message_on :minutes %> +
+
+
+ <%= f.number_field :seconds, class: 'form-control', placeholder: 'Seconds', value: @object.duration_in_hms[:seconds] || 0, min: 0, required: true %> + s +
+ <%= f.error_message_on :seconds %> +
+
+ <% end %> +
+
+ <%= f.field_container :departure_time do %> + <%= f.label :departure_time, Spree.t(:departure_time) %> +
+ <%= f.time_select :departure_time, { default: Time.now}, {class: 'form-control', style: 'display: inline-block; width: auto;'} %> +
+ <%= f.error_message_on :departure_time %> + <% end %> +
+
+ <%= f.field_container :boarding_points do %> + <%= f.label :boarding_stops, Spree.t(:boarding_stops)%> + <%= f.select(:boarding_points, options_from_collection_for_select( @stops, 'id', 'name', @boarding_stops),{include_blank: false},{class: "select2", multiple:true, id: "boarding_stops"}) %> + <%end%> +
+
+ <%= f.field_container :drop_off_points do %> + <%= f.label :drop_off_stops, Spree.t(:drop_off_stops)%> + <%= f.select(:drop_off_points, options_from_collection_for_select( @stops, 'id','name', @drop_off_stops),{include_blank: false},{class: "select2", multiple:true, id: "drop-off_stops"}) %> + <%end%> +
+
+ <%= f.hidden_field :product_id,value: @product.id %> +
+
\ No newline at end of file diff --git a/app/views/spree/transit/trips/edit.html.erb b/app/views/spree/transit/trips/edit.html.erb new file mode 100644 index 000000000..61d43217d --- /dev/null +++ b/app/views/spree/transit/trips/edit.html.erb @@ -0,0 +1,15 @@ +<%= render partial: 'spree/transit/shared/route_tabs', locals: {current: :trip} %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +<%= form_with model: @object, url: { action: 'update' } do |f| %> +
+
+
+ <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/edit_resource_links',locals: { collection_url: spree.transit_routes_path } %> +
+
+
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/trips/new.html.erb b/app/views/spree/transit/trips/new.html.erb new file mode 100644 index 000000000..aa741a921 --- /dev/null +++ b/app/views/spree/transit/trips/new.html.erb @@ -0,0 +1,15 @@ +<%= render partial: 'spree/transit/shared/route_tabs', locals: {current: :trip} %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +<%= form_with model: @object, url: { action: 'create' } do |f| %> +
+
+
+ <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/new_resource_links',locals: { collection_url: spree.transit_routes_path } %> +
+
+
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/vectors/amenity_values/index.html.erb b/app/views/spree/transit/vectors/amenity_values/index.html.erb new file mode 100644 index 000000000..818c551b0 --- /dev/null +++ b/app/views/spree/transit/vectors/amenity_values/index.html.erb @@ -0,0 +1,111 @@ +<% content_for :page_title do %> + <%= Spree.t(:amenty_values_icon) %> +<% end %> + +<% unless @collection.empty? %> +
+ + <% @collection.each do |option_type| %> + + + + + + + + <% option_type.option_values.each_slice(3).each do |page| %> + + <% page.each do |value| %> + + <% end %> + + <% end %> + + + + <% end %> +
+ <%= link_to option_type.presentation, edit_transit_amenity_path %> +
+ +
+ <%= render_vector_icon value.display_icon %> + +
+
+
+<% else %> + + <%= raw I18n.t('option_type.empty_info') %> + +<% end %> + + + + \ No newline at end of file diff --git a/app/views/spree/transit/vehicle_photos/_form.html.erb b/app/views/spree/transit/vehicle_photos/_form.html.erb new file mode 100644 index 000000000..c42859abf --- /dev/null +++ b/app/views/spree/transit/vehicle_photos/_form.html.erb @@ -0,0 +1,22 @@ +
+
+ <% if defined?(preview) and preview == true %> +
+ <%= + link_to image_tag(main_app.url_for(@object.url(:vehicle)), :class => "rounded border mb-4"), + main_app.rails_blob_url(@object.attachment) + %> +
+ <% end %> +
+ <%= f.label :attachment, Spree.t(:filename) %> + <%= f.file_field :attachment %> +
+
+
+
+ <%= f.label :alt, Spree.t(:alt_text) %> + <%= f.text_area :alt, rows: 4, class: 'form-control' %> +
+
+
diff --git a/app/views/spree/transit/vehicle_photos/edit.html.erb b/app/views/spree/transit/vehicle_photos/edit.html.erb new file mode 100644 index 000000000..4c9aad5bb --- /dev/null +++ b/app/views/spree/transit/vehicle_photos/edit.html.erb @@ -0,0 +1,9 @@ +<%= render partial: 'spree/transit/shared/vehicle_tabs', locals: { current: :images } %> +<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> + +<%= form_with model: @object, url: { action: 'update' }, html: { multipart: true } do |f| %> +
+ <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/new_resource_links' %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/vehicle_photos/index.html.erb b/app/views/spree/transit/vehicle_photos/index.html.erb new file mode 100644 index 000000000..a8620d8f1 --- /dev/null +++ b/app/views/spree/transit/vehicle_photos/index.html.erb @@ -0,0 +1,61 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_vehicles_path %> + <%= "Vehicle/#{@vehicle.code}" %> +<% end %> + +<%= render partial: 'spree/transit/shared/vehicle_tabs', locals: {current: :images} %> + +<% content_for :page_actions do %> + <%= yield :page_actions %> + <%= button_link_to(Spree.t(:new_photo), new_transit_vehicle_vehicle_photo_url(@vehicle), { class: "btn-success", icon: 'add.svg', id: 'new_image_link' }) if can? :create, Spree::Image %> +<% end %> + +<% unless @objects.empty? %> +
+ + + + + + + + + + + + <% (@objects).each do |image| %> + + + + + + + + <% end %> + +
+ <%= Spree.t(:id) %> + + <%= Spree.t(:thumbnail) %> + + <%= Spree.t(:alt_text) %> +
<%= image.id %> + <% if can? :edit, image %> + <%= svg_icon name: "sort.svg", width: '18', height: '18' %> + <% end %> + +
+ <%= link_to image_tag(main_app.url_for(image.url(:vehicle))), main_app.rails_blob_url(image.attachment) %> +
+
<%= image.alt %> + + <%= link_to_with_icon('edit.svg', Spree.t(:edit), edit_transit_vehicle_vehicle_photo_url(@vehicle, image), class: 'btn btn-primary btn-sm', no_text: true, data: { action: 'edit' }) if can? :edit, image %> + <%= link_to_delete(image, { url: transit_vehicle_vehicle_photo_url(@vehicle, image), no_text: true }) if can? :destroy, image %> + +
+
+<% else %> + + <%= raw I18n.t('photo.empty_info') %> + +<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/vehicle_photos/new.html.erb b/app/views/spree/transit/vehicle_photos/new.html.erb new file mode 100644 index 000000000..5721e6285 --- /dev/null +++ b/app/views/spree/transit/vehicle_photos/new.html.erb @@ -0,0 +1,9 @@ +<%= render partial: 'spree/transit/shared/vehicle_tabs', locals: { current: :images } %> +<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> + +<%= form_with model: @object, url: { action: 'create' }, html: { multipart: true } do |f| %> +
+ <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/new_resource_links' %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/vehicle_seats/_form.html.erb b/app/views/spree/transit/vehicle_seats/_form.html.erb new file mode 100644 index 000000000..ca589c37c --- /dev/null +++ b/app/views/spree/transit/vehicle_seats/_form.html.erb @@ -0,0 +1,48 @@ +
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
diff --git a/app/views/spree/transit/vehicle_seats/_seats.html.erb b/app/views/spree/transit/vehicle_seats/_seats.html.erb new file mode 100644 index 000000000..b8b7f603c --- /dev/null +++ b/app/views/spree/transit/vehicle_seats/_seats.html.erb @@ -0,0 +1,63 @@ +
+ <% @seats.each do |rows| %> +
+ <% rows.each do |seat| %> +
+ <% if seat['seat_type'] == 0 %> +
+
+ <%= inline_svg_tag "cm-normal-seat-icons.svg", size: "35px" %> +
+
+ +
+
+ <% elsif seat['seat_type'] == 1 %> +
+
+
+
+
+ +
+
+ <% elsif seat['seat_type'] == 2 %> +
+
+ <%= inline_svg_tag "cm-vip-seat-icons.svg", size: "35px" %> +
+
+ +
+
+ <% else%> +
+
+ <%= inline_svg_tag "cm-driver-seat-icons.svg", size: "35px" %> +
+
+ +
+
+ <% end %> +
+ <% end %> +
+ <% end %> +
diff --git a/app/views/spree/transit/vehicle_types/_filter_form.html.erb b/app/views/spree/transit/vehicle_types/_filter_form.html.erb new file mode 100644 index 000000000..90a3f4797 --- /dev/null +++ b/app/views/spree/transit/vehicle_types/_filter_form.html.erb @@ -0,0 +1,29 @@ +<% content_for :table_filter do %> +
+ <%= search_form_for [:vehicle_type, @search], url: spree.transit_vehicle_types_path do |f| %> +
+
+
+ <%= f.label :name_cont, I18n.t('spree.name') %> + <%= f.text_field :name_cont, placeholder: I18n.t('spree.transit.vehicle_type_name'), class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :code_cont, I18n.t('spree.code') %> + <%= f.text_field :code_cont, placeholder: I18n.t('spree.transit.vehicle_type_code'), class: "form-control js-filterable" %> +
+
+
+
+ <%= f.label :route_type_eq, I18n.t('spree.transit.route_type') %> + <%= f.select :route_type_eq, SpreeCmCommissioner::RouteType::ROUTE_TYPES.each_with_index.map { |type, i| [type.to_s.capitalize, i] }, { include_blank: true, prompt: I18n.t('spree.transit.select_route_type') }, { class: "form-control select2 js-filterable" } %> +
+
+
+
+ <%= button Spree.t(:search), 'search.svg' %> +
+ <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/vehicle_types/_form.html.erb b/app/views/spree/transit/vehicle_types/_form.html.erb new file mode 100644 index 000000000..6fcb9adfa --- /dev/null +++ b/app/views/spree/transit/vehicle_types/_form.html.erb @@ -0,0 +1,65 @@ +
+
+ <%= f.hidden_field :vendor_id, value: current_vendor.id %> + <%= f.field_container :name do %> + <%= f.label :name, Spree.t(:name) %> + <%= f.text_field :name, class: 'form-control', required: true %> + <%= f.error_message_on :name %> + <% end %> +
+
+ <%= f.field_container :code, :class =>['form-group'] do %> + <%= f.label :code, Spree.t(:code) %> + <%= f.text_field :code, class: 'form-control', required: true%> + <%= f.error_message_on :code %> + <%end%> +
+
+ <%= f.field_container :option_value_ids, :class => ['form-group'] do %> + <%=f.label :amenities%> + <%= f.select(:option_value_ids, options_for_select( @amenities, @selected_option_value_ids),{include_blank:false},{class: "select2", multiple:true}) %> + <% end %> +
+
+ <%= f.field_container :route_type, :class => ['form-group'] do %> + <%= f.label :route_type, Spree.t('route_type') %>
+ <%= f.collection_select :route_type, SpreeCmCommissioner::RouteType::ROUTE_TYPES, :to_s, :capitalize, + {:include_blank => false}, + {:class => 'select2 fullwidth', + :disabled => defined?(disabled) && disabled == true ? 'disable' : nil }.compact %> + <% end %> +
+
+ <%= f.field_container :status, :class => ['form-group'] do %> + <%= f.label :status %> + <%= f.select(:status, options_for_select(@statuses, @object.status),{include_blank:false},{class: "select2"})%> + <%= f.error_message_on :status %> + <% end %> +
+
+ <%= f.field_container :allow_seat_selection, :class => ['form_group'] do %> + <%= f.label :seat_selection, Spree.t(:seat_selection) %> + <%= f.select(:allow_seat_selection, options_for_select([[ 'True', true ], [ 'False', false ]], @object.allow_seat_selection), {}, {class: 'form-control select2', id:'allow_seat_selection',onchange: 'toggleVehicleSeatsCount();' })%> + <%= f.error_message_on :allow_seat_selection%> + <% end %> +
+
+ <%= f.field_container :vehicle_seats_count do %> + <%= f.label :seats_amount %> + <%= f.text_field :vehicle_seats_count, class: 'form-control', id: 'vehicle_seats_count' %> + <%= f.error_message_on :vehicle_seats_count %> + <%end%> +
+
+ + \ No newline at end of file diff --git a/app/views/spree/transit/vehicle_types/_seat_view.erb b/app/views/spree/transit/vehicle_types/_seat_view.erb new file mode 100644 index 000000000..52c7c9b84 --- /dev/null +++ b/app/views/spree/transit/vehicle_types/_seat_view.erb @@ -0,0 +1,83 @@ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ <% @seats.each do |rows| %> +
+ <% rows.each do |seat| %> +
+ <% if seat['seat_type'] == 'normal' %> +
+
+ <%= inline_svg_tag "cm-normal-seat-icons.svg", size: "35px" %> +
+
+ +
+
+ <% elsif seat['seat_type'] == 'empty' %> +
+
+
+
+
+ +
+
+ <% elsif seat['seat_type'] == 'vip' %> +
+
+ <%= inline_svg_tag "cm-vip-seat-icons.svg", size: "35px" %> +
+
+ +
+
+ <% else%> +
+
+ <%= inline_svg_tag "cm-driver-seat-icons.svg", size: "35px" %> +
+
+ +
+
+ <% end %> +
+ <% end %> +
+ <% end %> +
+
+
+
diff --git a/app/views/spree/transit/vehicle_types/edit.html.erb b/app/views/spree/transit/vehicle_types/edit.html.erb new file mode 100644 index 000000000..acd74d5b3 --- /dev/null +++ b/app/views/spree/transit/vehicle_types/edit.html.erb @@ -0,0 +1,93 @@ +`<% content_for :page_title do %> + <%= page_header_back_button spree.transit_vehicle_types_path %> + <%= I18n.t('spree.transit.vehicle_types') %> +<% end %> +<% content_for :page_actions do %> + <%= button Spree.t(:add_layer), "add.svg", nil, { class: "btn-success", "data-toggle": "modal", "data-target": "#createSeatModal" } %> +<% end %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+
+ <%= form_with model: @object, url: { action: 'update' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> + <% if @seats.any? %> +
+ + + + + + + + + + + + <% @seats.each do | layer| %> + + + + + + + + <% end %> + +
+ <%= Spree.t(:layer_name)%> + + <%= Spree.t(:row) %> + + <%= Spree.t(:column) %> + + <%= Spree.t(:created_at) %> +
+ <%= layer.first.first[:layer] %> + + <%= layer.size %> + + <%= layer.first.size %> + + <%= layer.first.first[:created_at] %> + + + <%= button Spree.t(:view), nil, "button", {class: "view-layer-btn btn-primary", "data-toggle": "modal", "data-target": "#viewSeatModal", "data-layer": layer.to_json, "data-layer-name": layer.first.first[:layer], "data-layer-row": layer.size, "data-layer-column": layer.first.size} %> + +
+
+ <% end %> +
+ <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.transit_vehicle_types_path } %> +
+ <% end %> +
+ + diff --git a/app/views/spree/transit/vehicle_types/index.html.erb b/app/views/spree/transit/vehicle_types/index.html.erb new file mode 100644 index 000000000..5898cacd7 --- /dev/null +++ b/app/views/spree/transit/vehicle_types/index.html.erb @@ -0,0 +1,94 @@ +<% content_for :page_title do %> + <%= I18n.t('spree.transit.vehicle_types') %> +<% end %> +<% content_for :page_actions do %> + <%= button_link_to I18n.t('spree.transit.new.vehicle_type'), new_transit_vehicle_type_path, { class: "btn-success", icon: 'add.svg', id: 'admin_new_product' } %> +<% end %> + +<% content_for :page_tabs do %> + + + + +<% end %> + +<% render 'filter_form' %> +<% if @collection.any? %> +
+ + + + + + + + + + + + + + <% @collection.each do | type| %> + + + + + + + + + + <% end %> + +
+ <%= Spree.t(:id) %> + + <%= Spree.t(:name)%> + + <%= Spree.t(:code) %> + + <%= Spree.t(:route_type) %> + + <%= Spree.t(:status) %> + + <%= Spree.t(:created_at) %> +
+ <%= type.id %> + + <%= link_to type.name, edit_transit_vehicle_type_path(type) %> + + <%= type.code %> + + <%= type.route_type.capitalize %> + + <% @badge_class = "badge-active" if type.status == 'active' %> + <% @badge_class = "badge-inactive" if type.status == 'draft' %> + <% @badge_class = "badge-dark" if type.status == 'archived' %> + <%= content_tag(:strong, class: "badge #{@badge_class} ") do %> + <%= type.status %> + <% end %> + + <%= type.created_at.to_date %> + + + <%= link_to_edit type, url: edit_transit_vehicle_type_path(type), no_text: true, class: 'edit' if can?(:edit, type) %> + +
+
+<% end %> diff --git a/app/views/spree/transit/vehicle_types/new.html.erb b/app/views/spree/transit/vehicle_types/new.html.erb new file mode 100644 index 000000000..36bdfae9e --- /dev/null +++ b/app/views/spree/transit/vehicle_types/new.html.erb @@ -0,0 +1,14 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_vehicle_types_path %> + <%= I18n.t('spree.transit.vehicle_types') %> +<% end %> +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+
+ <%= form_with model: @object, url: { action: 'create' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/new_resource_links', locals: { collection_url: spree.transit_vehicle_types_path } %> +
+ <% end %> diff --git a/app/views/spree/transit/vehicles/_filter_form.html.erb b/app/views/spree/transit/vehicles/_filter_form.html.erb new file mode 100644 index 000000000..4dbbaf2b3 --- /dev/null +++ b/app/views/spree/transit/vehicles/_filter_form.html.erb @@ -0,0 +1,23 @@ +<% content_for :table_filter do %> +
+ <%= search_form_for [:vehicle, @search], url: spree.transit_vehicles_path do |f| %> +
+
+
+ <%= f.label :code_cont, I18n.t('spree.code') %> + <%= f.text_field :code_cont, placeholder: 'Vehicle Code', class: "form-control js-quick-search-target js-filterable" %> +
+
+
+
+ <%= f.label :vehicle_type_id_eq, I18n.t('spree.transit.vehicle_type') %> + <%= f.select :vehicle_type_id_eq, SpreeCmCommissioner::VehicleType.all.map{|s| [s.name, s.id]}, { prompt: "Select a Vehicle Type" }, class: "form-control select2 js-filterable" %> +
+
+
+
+ <%= button Spree.t(:search), 'search.svg' %> +
+ <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/spree/transit/vehicles/_form.html.erb b/app/views/spree/transit/vehicles/_form.html.erb new file mode 100644 index 000000000..b16c607ed --- /dev/null +++ b/app/views/spree/transit/vehicles/_form.html.erb @@ -0,0 +1,19 @@ +
+
+ <%= f.field_container :code do %> + <%= f.label :code, Spree.t(:code) %> + <%= f.text_field :code, class: 'form-control', placeholder: Spree.t("transit.vehicle.code") %> + <%= f.error_message_on :code %> + <% end %> + <%= f.field_container :license_plate do %> + <%= f.label :license_plate, Spree.t("transit.vehicle.license_plate") %> + <%= f.text_field :license_plate, class: 'form-control', placeholder: Spree.t("transit.vehicle.license_plate") %> + <%= f.error_message_on :license_plate %> + <% end %> + <%= f.field_container :vehicle_type do %> + <%= f.label :vehicle_type, Spree.t("transit.vehicle_type") %> + <%= f.collection_select(:vehicle_type_id, SpreeCmCommissioner::VehicleType.where(status: 'active'), :id, :name, { prompt: "Select a Vehicle Type" }, { class: 'form-control select2' }) %> + <%= f.error_message_on :vehicle_type %> + <% end %> +
+
diff --git a/app/views/spree/transit/vehicles/edit.html.erb b/app/views/spree/transit/vehicles/edit.html.erb new file mode 100644 index 000000000..b7c3931b4 --- /dev/null +++ b/app/views/spree/transit/vehicles/edit.html.erb @@ -0,0 +1,19 @@ +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_vehicles_path %> + <%= "Vehicle/#{@object.code}" %> +<% end %> + +<%= render partial: 'spree/transit/shared/vehicle_tabs', locals: { current: :details } %> + +
+ <%= form_with model: @object, url: { action: 'update' } do |f| %> + <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/edit_resource_links', locals: { collection_url: spree.transit_vehicles_path } %> +
+ <% end %> +
diff --git a/app/views/spree/transit/vehicles/index.html.erb b/app/views/spree/transit/vehicles/index.html.erb new file mode 100644 index 000000000..eedc68e66 --- /dev/null +++ b/app/views/spree/transit/vehicles/index.html.erb @@ -0,0 +1,57 @@ +<% content_for :page_title do %> + <%= I18n.t('spree.transit.vehicles') %> +<% end %> + +<% content_for :page_actions do %> + <%= button_link_to Spree.t(:add_vehicle), new_transit_vehicle_path, class: "btn-success", icon: 'add.svg', id: 'transit_new_vehicle_link' %> +<% end %> + +<% render 'filter_form' %> + +<% if @vehicles.any? %> +
+ + + + + + + + + + + + + <% @vehicles.each do |vehicle| %> + + + + + + + + + <% end %> + +
<%= Spree.t('transit.primary_photo')%> + <%= sort_link @search, :code, Spree.t('transit.vehicle.code') %> + <%= Spree.t('transit.vehicle_type') %><%= Spree.t('transit.vehicle.license_plate') %><%= Spree.t("transit.vehicle.route_type") %>
+ <%= link_to spree.transit_vehicle_vehicle_photos_path(vehicle) do %> + <% if vehicle.primary_photo.present? %> + <%= image_tag main_app.url_for(vehicle.primary_photo.attachment.variant(resize_to_fill: [60, 60])), :class => "rounded border" %> + <% else %> +
<%= Spree.t('transit.no_image') %>
+ <% end %> + <% end %> +
<%= vehicle.code %><%= link_to vehicle.vehicle_type.name, edit_transit_vehicle_type_path(vehicle.vehicle_type) %><%= vehicle.license_plate %> + <% if vehicle.route_type.present? %> + <%= vehicle.route_type.capitalize %> + <% end %> + + + <%= link_to_edit vehicle, url: edit_transit_vehicle_path(vehicle), no_text: true, class: 'edit' if can?(:edit, vehicle) %> + <%= link_to_delete vehicle, url: transit_vehicle_path(vehicle), no_text: true if can?(:delete, vehicle) %> + +
+
+<% end %> diff --git a/app/views/spree/transit/vehicles/new.html.erb b/app/views/spree/transit/vehicles/new.html.erb new file mode 100644 index 000000000..35ee4c003 --- /dev/null +++ b/app/views/spree/transit/vehicles/new.html.erb @@ -0,0 +1,19 @@ +<% content_for :page_title do %> + <%= page_header_back_button spree.transit_vehicles_path %> + <%= I18n.t('spree.transit.vehicles') %> +<% end %> + +
+ <%= render partial: 'spree/admin/shared/error_messages', locals: { target: @object } %> +
+ +
+ <%= form_with model: @object, url: { action: 'create' } do |f| %> +
+ <%= render partial: 'form', locals: { f: f } %> +
+ <%= render partial: 'spree/admin/shared/new_resource_links', locals: { collection_url: spree.transit_vehicles_path } %> +
+
+ <% end %> +
diff --git a/app/views/spree/transit/vendors/_form.html.erb b/app/views/spree/transit/vendors/_form.html.erb new file mode 100644 index 000000000..895b63bec --- /dev/null +++ b/app/views/spree/transit/vendors/_form.html.erb @@ -0,0 +1,34 @@ +<%= render partial: 'spree/admin/shared/error_messages', locals: { target: @vendor } %> +
+ <%= f.field_container :name do %> + <%= f.label Spree.t('name') %> + <%= f.text_field :name, class: 'form-control' %> + <% end %> + <%= f.field_container :code do %> + <%= f.label Spree.t('code') %> + <%= f.text_field :code, class: 'form-control' %> + <% end %> + <% if Spree.version.to_f >= 3.6 %> +
+ <%= render 'shared/asset_field', + field: :logo, + label: Spree.t("logo"), + asset: @vendor.logo, + classes: ['col m-2'], + form:f + %> + <%= render 'shared/asset_field', + field: :image, + label: Spree.t('image'), + asset: @vendor.image, + classes: ['col m-2'], + form:f + %> +
+ <% end %> + <%= f.field_container :default_country do %> + <%= f.label :default_country %> + <%= f.collection_select(:default_country, Spree::Country.all, :id, :name, {prompt: "Select a country"}, { class: 'form-control select2'} )%> + <%= f.error_message_on :default_country %> + <% end %> +
diff --git a/app/views/spree/transit/vendors/_tabs.html.erb b/app/views/spree/transit/vendors/_tabs.html.erb new file mode 100644 index 000000000..be5d6976e --- /dev/null +++ b/app/views/spree/transit/vendors/_tabs.html.erb @@ -0,0 +1,16 @@ +<% content_for(:page_tabs) do %> + <%= content_tag :li, class: 'nav-item' do %> + <%= link_to_with_icon 'edit.svg', + Spree.t(:details), + spree.edit_transit_vendor_url(@vendor.id), + class: "nav-link #{'active' if current == :details}" %> + <% end if can?(:transit, Spree::Vendor) %> + + <%= content_tag :li do %> + <%= link_to_with_icon 'images.svg', + Spree.t(:service_calendars), + transit_vendor_service_calendars_url(@vendor.id), + class: "nav-link #{'active' if current == :service_calendars}" + %> + <% end if can?(:transit, SpreeCmCommissioner::ServiceCalendar) %> +<% end %> diff --git a/app/views/spree/transit/vendors/edit.html.erb b/app/views/spree/transit/vendors/edit.html.erb new file mode 100644 index 000000000..98c4e004a --- /dev/null +++ b/app/views/spree/transit/vendors/edit.html.erb @@ -0,0 +1,10 @@ +<% content_for :page_title do %> + <%= @vendor.name %> +<% end %> +<%= render partial: 'spree/transit/vendors/tabs', locals: { current: :details } %> +<%= form_for [:transit, @vendor] do |f| %> +
+ <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/edit_resource_links',locals: { collection_url: spree.edit_transit_vendor_url(@current_vendor.id) } %> +
+<% end %> diff --git a/config/initializers/spree_permitted_attributes.rb b/config/initializers/spree_permitted_attributes.rb index 7cc1ff088..ce2b2b91c 100644 --- a/config/initializers/spree_permitted_attributes.rb +++ b/config/initializers/spree_permitted_attributes.rb @@ -1,7 +1,7 @@ module Spree module PermittedAttributes @@vendor_attributes << :logo - @@line_item_attributes += %i[from_date to_date] + @@line_item_attributes += %i[from_date to_date line_item_seats_attributes date] @@user_attributes += %i[first_name last_name dob gender profile] @@taxon_attributes += %i[category_icon] @@checkout_attributes += %i[phone_number country_code] diff --git a/config/locales/en.yml b/config/locales/en.yml index ec178a472..df42c7cf3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,23 +1,18 @@ # Sample localization file for English. Add more files in this directory for other locales. # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. - en: account_checker: verify: already_exist: "%{login} already exist" - sms: to: blank: "recipient can not be blank" body: blank: "sms content can not be blank" - already_exist: "%{login} already exist" - pincode_creator: invalid_phone_number: "Invalid phone number" invalid_email_address: "Invalid email address" - pincode_checker: error_type: not_found: "Verification Code not found!" @@ -25,33 +20,26 @@ en: reached_max_attempt: "Verification Code already reach max attempt!" not_match: "Verification code is not valid" ok: "Verification code is valid" - authenticator: incorrect_login: "Incorrect login" incorrect_password: "Incorrect password" invalid_or_missing_params: "Invalid or missing params" - hello: "Hello world" - taxon: icon: Icon (Square) category_icon: Category icon (Square) app_banner: App Banner (16x9) web_banner: Web banner (10x2) - user_update_contact: phone_number_is_already_used: "Phone number is already used" email_is_already_used: "Email is already used" - calendar: event: empty_info: "No Events found." - option_type: kind_info: "Once set, it can't be updated." kind_validation: "Attribute can't be updated for %{option_type_name}" empty_info: "No Option Type found for this kind" - stock_location: lat: label: "Latitude" @@ -61,10 +49,8 @@ en: hint: "Enter between 180 to -180" reference: label: "Reference" - user_identity_provider: not_found: "User identity provider for %{identity_type} not found in the system" - variant: validation: option_type_is_not_variant: "%{option_type_name} / %{option_value_name} is not kind of variant" @@ -73,27 +59,21 @@ en: out_of_stock: "%{variant_name} is out of stock" product_kind_option_types: empty_info: "Note: Set service option types to service first. To edit, go to Details tab > Option Types" - vectors: icons: info_rules: 'Only allow %{allow_extensions} files. Icons are stored inside "images/" folder, eg. app/assets/images/backend-adjust.svg.' total: "Total: %{total_in_page} / %{total_count} icons" - product: validation: not_active: "%{product_name} is not active" - photo: empty_info: "No Photos found." - nearby_place: empty_info: "No Nearby places found." - vendor: validation: vendor_kind_option_types: empty_info: "Note: Set vendor option types to vendor first. To edit, go to Details tab > Option Types" - service_calendars: empty_info: "No Service calendars found." start_date: "Start date" @@ -103,10 +83,8 @@ en: new_calendar: "New Service calendar" operations_day: "Operations day" exception_rules: "Exception Rules" - orders: total: "Total: %{orders_count} invoices" - account_deletion: title: have_another_account: "I have another account" @@ -116,11 +94,11 @@ en: reason_description: sth_broken: "Can you please share us what was not working? We will fix them as soon as possible as we spot them" other: "Can you please share us your account deletion reason. If something slipped through our fingers, we'd be so grateful to be aware and fix it." - user: invalid_password: "Invalid Password" - spree: + logo: "Logo" + image: "Image" en: "English" date_of_birth: "Date of Birth" kind: "Kind" @@ -146,6 +124,7 @@ en: new_customer: "New Customer" listing_price: "Listing Price" length: "Length" + name: "Name" promotion_rule_types: fixed_date: name: "Fixed Date" @@ -204,6 +183,56 @@ en: other: "Only %{count} rooms available on %{date}" auto_apply: "Auto apply" auto_apply_info: "Path & code will be removed" + transit: + no_places: "'Places' taxonomy is missing" + new: + route: "New Route" + vehicle_type: "New Vehicle Type" + abbreviation: "Abbreviation" + reservations: "Reservations" + locations: "Locations" + location: "Location" + settings: "Setting" + vendor: "Vendor" + service_calendar: + title: "Sevice Calendar" + new: "New Sevice Calendar" + branch: + branches: "Branches" + address: "Address" + lat: + label: "Latitude" + hint: "Enter between -90 to 90" + lon: + label: "Longitude" + hint: "Enter between -180 to 180" + reference: + label: "Reference" + stops: "Stops" + routes: "Routes" + + places: + places: "Places" + add_new_place: "Add a new place" + + taxonomies: + edit_root_taxonomy: Edit %{name} Taxonomy + taxonomies: Taxonomies + taxons_belonging_to: "Taxons Belonging To %{taxonomy_name}" + submenu: Submenu + add_new_taxon: Add a new Taxon + no_taxons: "%{taxonomy} has no Taxons. Click the Add a new Taxon button, to begin adding Taxons." + vehicle_types: "Vehicle Types" + vehicle_type: "Vehicle Type" + vehicles: "Vehicles" + vehicle: + code: "Code" + license_plate: "License Plate" + route_type: "Route Type" + route_type: "Route Type" + select_route_type: "Select Route Type" + vehicle_type_name: "Vehicle Type Name" + vehicle_type_code: "Vehicle Type Code" device_tokens: "Device Tokens" user_identity_providers: "Identity Providers" new_user_identity_provider: "New Identity Providers" @@ -233,7 +262,6 @@ en: attributes: exception_rules: invalid_json: "is not a valid format" - mail: order_mailer: booking_confirm: "Your booking is now confirmed!" @@ -250,11 +278,9 @@ en: empty_info: "No User identity providers found" notification: send_test_success: "Test Send Successfully" - pincode_sender: sms: body: "%{code} is your %{readable_type}" - pincode: readable_type: login: "login code" @@ -263,7 +289,6 @@ en: confirmation_code: "confirmation code" update_user_login: "update user code" contact_update: "update contact code" - notifications: spree_cm_commissioner: order_complete_notification: @@ -280,7 +305,6 @@ en: message: "[%{vendor_name}] is reviewing your booking request." actions: login: "Login" - spree_user: invalid_password: "Current password is not valid" cannot_delete_account: "You cannot delete this account because you own a special role" diff --git a/config/routes.rb b/config/routes.rb index ea0e865e4..04be77f25 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -149,6 +149,53 @@ end end + namespace :transit do + namespace :vectors do + resources :icons, only: [:index] + resources :amenity_values, only: %i[index update] do + collection do + post :update + end + end + end + + resources :reservations + resources :locations + resources :vendors do + resources :service_calendars, except: %i[update] do + member do + patch :update_status + end + end + end + resources :branches + resources :stops + resources :routes do + resources :trips do + resources :trip_stops do + post :update_sequences, on: :collection + end + end + end + resources :places + resource :amenity, only: %i[new create edit update] + post '/amenity/update_positions' + post '/amenity/update_values_positions' + resources :vehicle_types do + resources :vehicle_seats + end + resources :vehicles do + resources :vehicle_photos do + collection do + post :update_positions + end + end + end + root to: redirect('/transit/reservations') + post '/vehicle_types/vehicle_seats/load_seat', to: 'vehicle_seats#load_seat' + post '/vehicle_types/layer', to: 'vehicle_types#layer' + end + namespace :api, defaults: { format: 'json' } do namespace :v2 do namespace :storefront do diff --git a/db/migrate/20230906072540_add_type_to_spree_cm_commissioner_place.rb b/db/migrate/20230906072540_add_type_to_spree_cm_commissioner_place.rb new file mode 100644 index 000000000..504813186 --- /dev/null +++ b/db/migrate/20230906072540_add_type_to_spree_cm_commissioner_place.rb @@ -0,0 +1,5 @@ +class AddTypeToSpreeCmCommissionerPlace < ActiveRecord::Migration[7.0] + def change + add_column :cm_places, :type, :string, null: true, if_not_exists: true + end +end diff --git a/db/migrate/20230906090926_add_default_country_to_spree_vendor.rb b/db/migrate/20230906090926_add_default_country_to_spree_vendor.rb new file mode 100644 index 000000000..3e16b9e54 --- /dev/null +++ b/db/migrate/20230906090926_add_default_country_to_spree_vendor.rb @@ -0,0 +1,5 @@ +class AddDefaultCountryToSpreeVendor < ActiveRecord::Migration[7.0] + def change + add_column :spree_vendors, :default_country, :int, if_not_exists: true + end +end diff --git a/db/migrate/20230907094354_add_state_id_to_spree_cm_commissioner_place.rb b/db/migrate/20230907094354_add_state_id_to_spree_cm_commissioner_place.rb new file mode 100644 index 000000000..71dcafbd5 --- /dev/null +++ b/db/migrate/20230907094354_add_state_id_to_spree_cm_commissioner_place.rb @@ -0,0 +1,5 @@ +class AddStateIdToSpreeCmCommissionerPlace < ActiveRecord::Migration[7.0] + def change + add_column :cm_places, :state_id, :int, if_not_exists: true + end +end diff --git a/db/migrate/20230911080543_add_vendor_id_to_cm_place.rb b/db/migrate/20230911080543_add_vendor_id_to_cm_place.rb new file mode 100644 index 000000000..a27c05fb1 --- /dev/null +++ b/db/migrate/20230911080543_add_vendor_id_to_cm_place.rb @@ -0,0 +1,5 @@ +class AddVendorIdToCmPlace < ActiveRecord::Migration[7.0] + def change + add_column :cm_places, :vendor_id, :int, optional: true, if_not_exists: true + end +end diff --git a/db/migrate/20230913050549_add_code_to_cm_place.rb b/db/migrate/20230913050549_add_code_to_cm_place.rb new file mode 100644 index 000000000..17249f39a --- /dev/null +++ b/db/migrate/20230913050549_add_code_to_cm_place.rb @@ -0,0 +1,5 @@ +class AddCodeToCmPlace < ActiveRecord::Migration[7.0] + def change + add_column :cm_places, :code, :string + end +end diff --git a/db/migrate/20230913050637_add_branch_id_to_cm_place.rb b/db/migrate/20230913050637_add_branch_id_to_cm_place.rb new file mode 100644 index 000000000..2d6922ba7 --- /dev/null +++ b/db/migrate/20230913050637_add_branch_id_to_cm_place.rb @@ -0,0 +1,5 @@ +class AddBranchIdToCmPlace < ActiveRecord::Migration[7.0] + def change + add_column :cm_places, :branch_id, :integer + end +end diff --git a/db/migrate/20230914040528_add_short_name_and_url_to_spree_product.rb b/db/migrate/20230914040528_add_short_name_and_url_to_spree_product.rb new file mode 100644 index 000000000..d536a6f41 --- /dev/null +++ b/db/migrate/20230914040528_add_short_name_and_url_to_spree_product.rb @@ -0,0 +1,6 @@ +class AddShortNameAndUrlToSpreeProduct < ActiveRecord::Migration[7.0] + def change + add_column :spree_products, :short_name, :string, if_not_exists: true + add_column :spree_products, :url, :string, if_not_exists: true + end +end diff --git a/db/migrate/20230918034100_add_route_type_to_product.rb b/db/migrate/20230918034100_add_route_type_to_product.rb new file mode 100644 index 000000000..843e47c16 --- /dev/null +++ b/db/migrate/20230918034100_add_route_type_to_product.rb @@ -0,0 +1,5 @@ +class AddRouteTypeToProduct < ActiveRecord::Migration[7.0] + def change + add_column :spree_products, :route_type, :integer, if_not_exists: true + end +end diff --git a/db/migrate/20230918041231_create_spree_cm_commissioner_vehicle_type.rb b/db/migrate/20230918041231_create_spree_cm_commissioner_vehicle_type.rb new file mode 100644 index 000000000..343a22d4c --- /dev/null +++ b/db/migrate/20230918041231_create_spree_cm_commissioner_vehicle_type.rb @@ -0,0 +1,11 @@ +class CreateSpreeCmCommissionerVehicleType < ActiveRecord::Migration[7.0] + def change + create_table :cm_vehicle_types do |t| + t.string :name + t.integer :route_type + t.integer :vendor_id + t.string :code + t.timestamps + end + end +end diff --git a/db/migrate/20230925033135_create_spree_cm_commissioner_vehicle_seat.rb b/db/migrate/20230925033135_create_spree_cm_commissioner_vehicle_seat.rb new file mode 100644 index 000000000..cf9fd45ce --- /dev/null +++ b/db/migrate/20230925033135_create_spree_cm_commissioner_vehicle_seat.rb @@ -0,0 +1,13 @@ +class CreateSpreeCmCommissionerVehicleSeat < ActiveRecord::Migration[7.0] + def change + create_table :cm_vehicle_seats do |t| + t.string :label + t.integer :seat_type + t.integer :row + t.integer :column + t.string :layer + t.integer :vehicle_type_id + t.timestamps + end + end +end diff --git a/db/migrate/20231121080048_add_service_type_to_cm_service_calendar.rb b/db/migrate/20231121080048_add_service_type_to_cm_service_calendar.rb new file mode 100644 index 000000000..c3de0b53d --- /dev/null +++ b/db/migrate/20231121080048_add_service_type_to_cm_service_calendar.rb @@ -0,0 +1,5 @@ +class AddServiceTypeToCmServiceCalendar < ActiveRecord::Migration[7.0] + def change + add_column :cm_service_calendars, :service_type, :boolean + end +end diff --git a/db/migrate/20231121080107_add_not_available_reason_to_cm_service_calendar.rb b/db/migrate/20231121080107_add_not_available_reason_to_cm_service_calendar.rb new file mode 100644 index 000000000..3e8afc87d --- /dev/null +++ b/db/migrate/20231121080107_add_not_available_reason_to_cm_service_calendar.rb @@ -0,0 +1,5 @@ +class AddNotAvailableReasonToCmServiceCalendar < ActiveRecord::Migration[7.0] + def change + add_column :cm_service_calendars, :not_available_reason, :text + end +end diff --git a/db/migrate/20231211034859_create_cm_vehicles.rb b/db/migrate/20231211034859_create_cm_vehicles.rb new file mode 100644 index 000000000..e564dfc73 --- /dev/null +++ b/db/migrate/20231211034859_create_cm_vehicles.rb @@ -0,0 +1,11 @@ +class CreateCmVehicles < ActiveRecord::Migration[7.0] + def change + create_table :cm_vehicles, if_not_exists: true do |t| + t.string :code + t.string :license_plate + t.references :vehicle_type, foreign_key: { to_table: :cm_vehicle_types } + + t.timestamps + end + end +end diff --git a/db/migrate/20231212045710_create_spree_cm_commissioner_option_value_vehicle_type.rb b/db/migrate/20231212045710_create_spree_cm_commissioner_option_value_vehicle_type.rb new file mode 100644 index 000000000..ef38d1e2c --- /dev/null +++ b/db/migrate/20231212045710_create_spree_cm_commissioner_option_value_vehicle_type.rb @@ -0,0 +1,9 @@ +class CreateSpreeCmCommissionerOptionValueVehicleType < ActiveRecord::Migration[7.0] + def change + create_table :cm_option_value_vehicle_types do |t| + t.references :vehicle_type + t.references :option_value + t.timestamps + end + end +end diff --git a/db/migrate/20231219111615_add_data_type_to_spree_taxon.rb b/db/migrate/20231219111615_add_data_type_to_spree_taxon.rb new file mode 100644 index 000000000..ad1e13557 --- /dev/null +++ b/db/migrate/20231219111615_add_data_type_to_spree_taxon.rb @@ -0,0 +1,5 @@ +class AddDataTypeToSpreeTaxon < ActiveRecord::Migration[7.0] + def change + add_column :spree_taxons, :data_type, :integer, null: false, default: 0 + end +end diff --git a/db/migrate/20231220073035_add_name_to_cm_service_calendar.rb b/db/migrate/20231220073035_add_name_to_cm_service_calendar.rb new file mode 100644 index 000000000..e3dce65d4 --- /dev/null +++ b/db/migrate/20231220073035_add_name_to_cm_service_calendar.rb @@ -0,0 +1,5 @@ +class AddNameToCmServiceCalendar < ActiveRecord::Migration[7.0] + def change + add_column :cm_service_calendars, :name, :string, if_not_exists: true + end +end diff --git a/db/migrate/20240103095327_add_origin_destination_to_product.rb b/db/migrate/20240103095327_add_origin_destination_to_product.rb new file mode 100644 index 000000000..ed3c9a036 --- /dev/null +++ b/db/migrate/20240103095327_add_origin_destination_to_product.rb @@ -0,0 +1,6 @@ +class AddOriginDestinationToProduct < ActiveRecord::Migration[7.0] + def change + add_column :spree_products, :origin_id, :integer + add_column :spree_products, :destination_id, :integer + end +end diff --git a/db/migrate/20240108074029_add_status_to_cm_vehicle_type.rb b/db/migrate/20240108074029_add_status_to_cm_vehicle_type.rb new file mode 100644 index 000000000..6795b0241 --- /dev/null +++ b/db/migrate/20240108074029_add_status_to_cm_vehicle_type.rb @@ -0,0 +1,5 @@ +class AddStatusToCmVehicleType < ActiveRecord::Migration[7.0] + def change + add_column :cm_vehicle_types, :status, :string + end +end diff --git a/db/migrate/20240108093344_add_route_type_to_cm_vehicle.rb b/db/migrate/20240108093344_add_route_type_to_cm_vehicle.rb new file mode 100644 index 000000000..6266da2ea --- /dev/null +++ b/db/migrate/20240108093344_add_route_type_to_cm_vehicle.rb @@ -0,0 +1,5 @@ +class AddRouteTypeToCmVehicle < ActiveRecord::Migration[7.0] + def change + add_column :cm_vehicles, :route_type, :integer + end +end diff --git a/db/migrate/20240108101521_add_vendor_to_cm_vehicle.rb b/db/migrate/20240108101521_add_vendor_to_cm_vehicle.rb new file mode 100644 index 000000000..4e86f1ed8 --- /dev/null +++ b/db/migrate/20240108101521_add_vendor_to_cm_vehicle.rb @@ -0,0 +1,5 @@ +class AddVendorToCmVehicle < ActiveRecord::Migration[7.0] + def change + add_column :cm_vehicles, :vendor_id, :integer + end +end diff --git a/db/migrate/20240116030006_add_kind_to_spree_taxonomy.rb b/db/migrate/20240116030006_add_kind_to_spree_taxonomy.rb new file mode 100644 index 000000000..5664f637b --- /dev/null +++ b/db/migrate/20240116030006_add_kind_to_spree_taxonomy.rb @@ -0,0 +1,6 @@ +# This migration comes from spree_cm_commissioner (originally 20231005021329) +class AddKindToSpreeTaxonomy < ActiveRecord::Migration[7.0] + def change + add_column :spree_taxonomies, :kind, :integer, null: false, default: 0, if_not_exists: true + end +end diff --git a/db/migrate/20240125041358_create_cm_line_item_seats.rb b/db/migrate/20240125041358_create_cm_line_item_seats.rb new file mode 100644 index 000000000..e5d329431 --- /dev/null +++ b/db/migrate/20240125041358_create_cm_line_item_seats.rb @@ -0,0 +1,10 @@ +class CreateCmLineItemSeats < ActiveRecord::Migration[7.0] + def change + create_table :cm_line_item_seats, if_not_exists: true do |t| + t.integer :line_item_id + t.integer :seat_id + + t.timestamps + end + end +end diff --git a/db/migrate/20240202041845_add_vehicle_seats_count_to_vehicle_type.rb b/db/migrate/20240202041845_add_vehicle_seats_count_to_vehicle_type.rb new file mode 100644 index 000000000..8542996c9 --- /dev/null +++ b/db/migrate/20240202041845_add_vehicle_seats_count_to_vehicle_type.rb @@ -0,0 +1,5 @@ +class AddVehicleSeatsCountToVehicleType < ActiveRecord::Migration[7.0] + def change + add_column :cm_vehicle_types, :vehicle_seats_count, :integer, default: 0 + end +end diff --git a/db/migrate/20240202080531_add_column_number_of_seats_to_cm_vehicles.rb b/db/migrate/20240202080531_add_column_number_of_seats_to_cm_vehicles.rb new file mode 100644 index 000000000..fc0a6c195 --- /dev/null +++ b/db/migrate/20240202080531_add_column_number_of_seats_to_cm_vehicles.rb @@ -0,0 +1,5 @@ +class AddColumnNumberOfSeatsToCmVehicles < ActiveRecord::Migration[7.0] + def change + add_column :cm_vehicles, :number_of_seats, :integer, default: 0 + end +end diff --git a/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb b/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb new file mode 100644 index 000000000..000dbf1ea --- /dev/null +++ b/db/migrate/20240202080634_update_counter_cache_of_vehicle_type.rb @@ -0,0 +1,5 @@ +class UpdateCounterCacheOfVehicleType < ActiveRecord::Migration[7.0] + def up + SpreeCmCommissioner::VehicleSeat.counter_culture_fix_counts + end +end diff --git a/db/migrate/20240202080713_update_number_of_seats_of_vehicle.rb b/db/migrate/20240202080713_update_number_of_seats_of_vehicle.rb new file mode 100644 index 000000000..e6c3e7649 --- /dev/null +++ b/db/migrate/20240202080713_update_number_of_seats_of_vehicle.rb @@ -0,0 +1,8 @@ +class UpdateNumberOfSeatsOfVehicle < ActiveRecord::Migration[7.0] + def change + SpreeCmCommissioner::Vehicle.find_each do |vehicle| + vehicle.set_attributes + vehicle.save + end + end +end diff --git a/db/migrate/20240205102507_add_date_column_to_spree_line_items.rb b/db/migrate/20240205102507_add_date_column_to_spree_line_items.rb new file mode 100644 index 000000000..aca3d73e3 --- /dev/null +++ b/db/migrate/20240205102507_add_date_column_to_spree_line_items.rb @@ -0,0 +1,6 @@ +class AddDateColumnToSpreeLineItems < ActiveRecord::Migration[7.0] + def change + add_column :spree_line_items, :date, :date + add_index :spree_line_items, :date + end +end diff --git a/db/migrate/20240220041859_add_origin_destination_index_to_product.rb b/db/migrate/20240220041859_add_origin_destination_index_to_product.rb new file mode 100644 index 000000000..a2cf936e6 --- /dev/null +++ b/db/migrate/20240220041859_add_origin_destination_index_to_product.rb @@ -0,0 +1,5 @@ +class AddOriginDestinationIndexToProduct < ActiveRecord::Migration[7.0] + def change + add_index :spree_products, [:origin_id, :destination_id], name: 'index_spree_products_on_origin_id_and_destination_id' + end +end diff --git a/db/migrate/20240222041403_add_vehicle_id_to_products.rb b/db/migrate/20240222041403_add_vehicle_id_to_products.rb new file mode 100644 index 000000000..996b1034a --- /dev/null +++ b/db/migrate/20240222041403_add_vehicle_id_to_products.rb @@ -0,0 +1,6 @@ +class AddVehicleIdToProducts < ActiveRecord::Migration[7.0] + def change + add_column :spree_products, :vehicle_id, :integer + add_index :spree_products, :vehicle_id + end +end diff --git a/db/migrate/20240229043606_add_date_and_variant_id_to_cm_line_item_seats.rb b/db/migrate/20240229043606_add_date_and_variant_id_to_cm_line_item_seats.rb new file mode 100644 index 000000000..bbd8f8582 --- /dev/null +++ b/db/migrate/20240229043606_add_date_and_variant_id_to_cm_line_item_seats.rb @@ -0,0 +1,7 @@ +class AddDateAndVariantIdToCmLineItemSeats < ActiveRecord::Migration[7.0] + def change + add_column :cm_line_item_seats, :variant_id, :integer + add_column :cm_line_item_seats, :date, :date + add_index :cm_line_item_seats, [:variant_id, :date], name: 'index_cm_line_item_seats_on_variant_id_and_date' + end +end diff --git a/db/migrate/20240307080330_create_cm_trips.rb b/db/migrate/20240307080330_create_cm_trips.rb new file mode 100644 index 000000000..4f3c6b426 --- /dev/null +++ b/db/migrate/20240307080330_create_cm_trips.rb @@ -0,0 +1,13 @@ +class CreateCmTrips < ActiveRecord::Migration[7.0] + def change + create_table :cm_trips, if_not_exists: true do |t| + t.integer :product_id + t.integer :vehicle_id + t.integer :origin_id + t.integer :destination_id + t.time :departure_time + t.integer :duration + t.timestamps + end + end +end diff --git a/db/migrate/20240313044245_add_origin_destination_index_to_cm_trips.rb b/db/migrate/20240313044245_add_origin_destination_index_to_cm_trips.rb new file mode 100644 index 000000000..fade984fe --- /dev/null +++ b/db/migrate/20240313044245_add_origin_destination_index_to_cm_trips.rb @@ -0,0 +1,6 @@ +class AddOriginDestinationIndexToCmTrips < ActiveRecord::Migration[7.0] + def change + add_index :cm_trips, [:origin_id, :destination_id], name: 'index_cm_trips_on_origin_id_and_destination_id' + add_index :cm_trips, :product_id, name: 'index_cm_trips_on_product_id' + end +end diff --git a/db/migrate/20240313044455_remove_origin_destintaion_vehicle_from_product.rb b/db/migrate/20240313044455_remove_origin_destintaion_vehicle_from_product.rb new file mode 100644 index 000000000..a809d76f7 --- /dev/null +++ b/db/migrate/20240313044455_remove_origin_destintaion_vehicle_from_product.rb @@ -0,0 +1,7 @@ +class RemoveOriginDestintaionVehicleFromProduct < ActiveRecord::Migration[7.0] + def change + remove_column :spree_products, :origin_id, :integer + remove_column :spree_products, :destination_id, :integer + remove_column :spree_products, :vehicle_id, :integer + end +end diff --git a/db/migrate/20240313080154_add_vehicle_type_id_index_to_cm_vehicle_seats.rb b/db/migrate/20240313080154_add_vehicle_type_id_index_to_cm_vehicle_seats.rb new file mode 100644 index 000000000..1c9eeafdd --- /dev/null +++ b/db/migrate/20240313080154_add_vehicle_type_id_index_to_cm_vehicle_seats.rb @@ -0,0 +1,5 @@ +class AddVehicleTypeIdIndexToCmVehicleSeats < ActiveRecord::Migration[7.0] + def change + add_index :cm_vehicle_seats, :vehicle_type_id, name: 'index_cm_vehicle_seats_on_vehicle_type_id' + end +end diff --git a/db/migrate/20240314035607_add_allow_seat_selection_to_cm_trips.rb b/db/migrate/20240314035607_add_allow_seat_selection_to_cm_trips.rb new file mode 100644 index 000000000..325197dbc --- /dev/null +++ b/db/migrate/20240314035607_add_allow_seat_selection_to_cm_trips.rb @@ -0,0 +1,5 @@ +class AddAllowSeatSelectionToCmTrips < ActiveRecord::Migration[7.0] + def change + add_column :cm_trips, :allow_seat_selection, :boolean, default: true, if_not_exists: true + end +end diff --git a/db/migrate/20240318080243_add_allow_seat_selection_to_cm_vehicle_types.rb b/db/migrate/20240318080243_add_allow_seat_selection_to_cm_vehicle_types.rb new file mode 100644 index 000000000..99b0cb2c0 --- /dev/null +++ b/db/migrate/20240318080243_add_allow_seat_selection_to_cm_vehicle_types.rb @@ -0,0 +1,5 @@ +class AddAllowSeatSelectionToCmVehicleTypes < ActiveRecord::Migration[7.0] + def change + add_column :cm_vehicle_types, :allow_seat_selection, :boolean, default: true, if_not_exists: true + end +end diff --git a/db/migrate/20240320042530_add_allow_seat_selection_to_cm_vehicles.rb b/db/migrate/20240320042530_add_allow_seat_selection_to_cm_vehicles.rb new file mode 100644 index 000000000..61507d742 --- /dev/null +++ b/db/migrate/20240320042530_add_allow_seat_selection_to_cm_vehicles.rb @@ -0,0 +1,5 @@ +class AddAllowSeatSelectionToCmVehicles < ActiveRecord::Migration[7.0] + def change + add_column :cm_vehicles, :allow_seat_selection, :boolean, default: true + end +end diff --git a/db/migrate/20240408033939_create_cm_trip_stops.rb b/db/migrate/20240408033939_create_cm_trip_stops.rb new file mode 100644 index 000000000..ffd20aca5 --- /dev/null +++ b/db/migrate/20240408033939_create_cm_trip_stops.rb @@ -0,0 +1,11 @@ +class CreateCmTripStops < ActiveRecord::Migration[7.0] + def change + create_table :cm_trip_stops do |t| + t.integer :trip_id + t.integer :stop_id + t.integer :stop_type + t.string :stop_name + t.timestamps + end + end +end diff --git a/db/migrate/20240510103536_add_sequence_to_cm_trip_stops.rb b/db/migrate/20240510103536_add_sequence_to_cm_trip_stops.rb new file mode 100644 index 000000000..fccff5713 --- /dev/null +++ b/db/migrate/20240510103536_add_sequence_to_cm_trip_stops.rb @@ -0,0 +1,5 @@ +class AddSequenceToCmTripStops < ActiveRecord::Migration[7.0] + def change + add_column :cm_trip_stops, :sequence, :integer, default: 0, if_not_exists: true + end +end diff --git a/db/migrate/20240626073702_create_cm_vendor_stops.rb b/db/migrate/20240626073702_create_cm_vendor_stops.rb new file mode 100644 index 000000000..ef9f3ee1a --- /dev/null +++ b/db/migrate/20240626073702_create_cm_vendor_stops.rb @@ -0,0 +1,10 @@ +class CreateCmVendorStops < ActiveRecord::Migration[7.0] + def change + create_table :cm_vendor_stops do |t| + t.integer :vendor_id + t.integer :stop_id + t.integer :stop_type + t.timestamps + end + end +end diff --git a/lib/spree_cm_commissioner.rb b/lib/spree_cm_commissioner.rb index 743c680d3..5ec1f4589 100644 --- a/lib/spree_cm_commissioner.rb +++ b/lib/spree_cm_commissioner.rb @@ -9,6 +9,8 @@ require 'spree_cm_commissioner/version' require 'spree_cm_commissioner/passenger_option' require 'spree_cm_commissioner/calendar_event' +require 'spree_cm_commissioner/trip_result' +require 'spree_cm_commissioner/trip_seat_layout_result' require 'searchkick' require 'elasticsearch' @@ -25,5 +27,6 @@ require 'googleauth' require 'dry-validation' require 'font-awesome-sass' +require 'counter_culture' require 'byebug' if Rails.env.development? || Rails.env.test? diff --git a/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb new file mode 100644 index 000000000..48283ba3c --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/branch_factory.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + + factory :cm_branch , class: SpreeCmCommissioner::Branch do + name { 'testBranch'} + lat { 11.11 } + lon { 11.11} + formatted_phone_number { generate(:phone_number) } + formatted_address { "test_address" } + state + vendor + end +end \ No newline at end of file diff --git a/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb new file mode 100644 index 000000000..ed440fbba --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/departure_time_option_type_factory.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :departure_time, class: Spree::OptionType do + name { 'departure_time' } + attr_type { 'departure_time' } + kind {'variant'} + presentation { '' } + end +end diff --git a/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb new file mode 100644 index 000000000..d3b77e247 --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/duration_option_type_factory.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :duration, class: Spree::OptionType do + name { 'duration' } + attr_type { 'duration' } + kind {'variant'} + presentation { '' } + end +end diff --git a/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb index 2755325c3..b581d9287 100644 --- a/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb +++ b/lib/spree_cm_commissioner/test_helper/factories/line_item_factory.rb @@ -4,7 +4,7 @@ quantity { 1 } price { BigDecimal('10.00') } currency { order.currency } - + product do if order&.store&.present? create(:cm_product_with_product_kind_option_types, stores: [order.store]) @@ -12,8 +12,5 @@ create(:cm_product_with_product_kind_option_types) end end - - variant { product.master } end end - \ No newline at end of file diff --git a/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb new file mode 100644 index 000000000..dfb681da1 --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/line_item_seat_factory.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :line_item_seat, class: SpreeCmCommissioner::LineItemSeat do + line_item_id {} + seat_id {} + date { Date.today } + end +end diff --git a/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb new file mode 100644 index 000000000..7e590a7a8 --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/order_factory.rb @@ -0,0 +1,38 @@ +FactoryBot.define do + factory :transit_order, class: Spree::Order do + bill_address + ship_address + state {'complete'} + payment_state {'paid'} + + transient do + line_items_count { 1 } + shipment_cost { 100 } + shipping_method_filter { Spree::ShippingMethod::DISPLAY_ON_FRONT_END } + seats {} + variant {} + quantity {} + date { Date.today } + selected_seats {[]} + line_item_seats_attributes{[]} + end + + after(:create) do |order, evaluator| + if evaluator.quantity.nil? && evaluator.seats.present? + @line_item_seats_attributes = evaluator.seats.map do |seat| + {seat_id: seat.id, date: evaluator.date, variant_id: evaluator.variant.id} + end + create(:line_item, order: order, variant: evaluator.variant, date: evaluator.date, line_item_seats_attributes: @line_item_seats_attributes) + order.line_items.reload + else + create(:line_item, order: order, variant: evaluator.variant, date: evaluator.date, quantity: evaluator.quantity) + end + + stock_location = order.line_items&.first&.variant&.stock_items&.first&.stock_location || create(:stock_location) + create(:shipment, order: order, cost: evaluator.shipment_cost, stock_location: stock_location) + order.shipments.reload + + order.update_with_updater! + end + end +end diff --git a/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb index 30de0ea92..e985a6875 100644 --- a/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb +++ b/lib/spree_cm_commissioner/test_helper/factories/product_factory.rb @@ -33,6 +33,26 @@ end end + factory :route do + route_type {:automobile} + product_type { :transit } + before(:create) do |route, evaluator| + create(:stock_location) unless Spree::StockLocation.any? + if route.stores.empty? + default_store = Spree::Store.default.persisted? ? Spree::Store.default : nil + store = default_store || create(:store) + + route.stores << [store] + end + end + after(:create) do |route, evaluator| + trip = route.master + trip.permanent_stock = 10 + trip.stock_items = [create(:stock_item, variant: trip, stock_location: route.vendor.stock_locations.first)] + trip.stock_items.first.adjust_count_on_hand(10) + end + end + factory :cm_subscribable_product do option_types { [ create(:cm_option_type, :month), diff --git a/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb new file mode 100644 index 000000000..118d895ed --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/stop_factory.rb @@ -0,0 +1,14 @@ +FactoryBot.define do + + factory :cm_stop , class: SpreeCmCommissioner::Place do + code { 'TS'} + name { 'testStop'} + formatted_phone_number { generate(:phone_number) } + formatted_address { "test_address" } + lat { 11.11 } + lon { 11.11} + branch + state + vendor + end +end \ No newline at end of file diff --git a/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb new file mode 100644 index 000000000..cfcf84cfc --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/transit_place_factory.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :transit_place, class: Spree::Taxon do + transient do + taxonomy { Spree::Taxonomy.place} + end + parent{ taxonomy.root } + end +end diff --git a/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb index 0ac864f07..77979eb01 100644 --- a/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb +++ b/lib/spree_cm_commissioner/test_helper/factories/variant_factory.rb @@ -8,4 +8,23 @@ product end end + + factory :trip, class: Spree::Variant do + permanent_stock { 5 } + transient do + departure_time { "10:00" } + duration { "1" } + route { } + end + before(:create) do |trip, evaluator| + trip.product = evaluator.route + + trip.option_values = [evaluator.departure_time, evaluator.duration] + + end + after(:create) do |trip, evaluator| + trip.stock_items = [create(:stock_item, variant: trip, stock_location: evaluator.product.vendor.stock_locations.first)] + trip.stock_items.first.adjust_count_on_hand(10) + end + end end diff --git a/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb new file mode 100644 index 000000000..128e86d14 --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/vehicle_factory.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :vehicle, class: SpreeCmCommissioner::Vehicle do + sequence(:code) { |n| "vehicle_#{n}" } + sequence(:license_plate) { |n| "2KH-#{'%04d' % (n % 10000)}" } + end +end diff --git a/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb new file mode 100644 index 000000000..74830d8bb --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/vehicle_option_type_factory.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :vehicle_option_type, class: Spree::OptionType do + name { 'vehicle_option_type' } + attr_type { 'vehicle' } + kind {'variant'} + presentation { '' } + end +end diff --git a/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb b/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb new file mode 100644 index 000000000..1161ba116 --- /dev/null +++ b/lib/spree_cm_commissioner/test_helper/factories/vehicle_type_factory.rb @@ -0,0 +1,96 @@ +FactoryBot.define do + factory :vehicle_type, class: SpreeCmCommissioner::VehicleType do + name { FFaker::Name.name } + route_type {"automobile"} + status {"active"} + end + + trait :with_layers do + transient do + layers {[ + {row: 1, column: 1, empty: [[]], label: 'F', layer_name: "First Layer"}, + {row: 1, column: 1, empty: [[]], label: 'S', layer_name: "Second Layer"} + ]} + end + after(:create) do |vehicle_type, evaluator| + layers = evaluator.layers || [{}] + layers.each_with_index do |layer, index| + + row = layer[:row] + column = layer[:column] + empty_seats = layer[:empty] || [] + label_counter = 1 + layer_name = layer[:layer_name] + row.times do |r| + column.times do |c| + label = "#{layer[:label]}#{label_counter}" + seat_type = 0 + if empty_seats.include?([r, c]) + seat_type = 1 + label = "NA" + label_counter -= 1 + end + if (r == 0 && c == 0 && index == 0) + seat_type = 3 + label = "NA" + label_counter -= 1 + end + seat = { + "row": r + 1, + "column": c + 1, + "label": label, + "layer": layer_name, + "seat_type": seat_type, + "vehicle_type_id": vehicle_type.id + } + + label_counter += 1 + SpreeCmCommissioner::VehicleSeat.create(seat) + end + end + vehicle_type.reload + end + end + end + + trait :with_seats do + transient do + row { 1 } + column { 1 } + empty { [[]] } + end + + after(:create) do |vehicle_type, evaluator| + label_counter = 1 + empty_seats = evaluator.empty || [] + evaluator.row.times do |r| + evaluator.column.times do |c| + seat_type = 0 + label = "F#{label_counter}" + if empty_seats.include?([r, c]) + seat_type = 1 + label = "NA" + label_counter -= 1 + end + if (r == 0 && c == 0) + seat_type = 3 + label = "NA" + label_counter -= 1 + end + seat = { + "row": r + 1, + "column": c + 1, + "label": label, + "layer": "First Layer", + "seat_type": seat_type, + "vehicle_type_id": vehicle_type.id + } + + label_counter += 1 + SpreeCmCommissioner::VehicleSeat.create(seat) + end + end + vehicle_type.reload + end + end +end diff --git a/lib/spree_cm_commissioner/trip_result.rb b/lib/spree_cm_commissioner/trip_result.rb new file mode 100644 index 000000000..47bb94421 --- /dev/null +++ b/lib/spree_cm_commissioner/trip_result.rb @@ -0,0 +1,31 @@ +module SpreeCmCommissioner + class TripResult + attr_accessor :trip_id, :vendor_id, :vendor_name, :short_name, :route_name, + :origin_id, :origin, :destination_id, :destination, + :total_sold, :total_seats, + :duration, :vehicle_id, :departure_time + + def initialize(options = {}) + options.each do |key, value| + instance_variable_set("@#{key}", value) + end + end + + def remaining_seats + total_seats - total_sold + end + + def arrival_time + (departure_time.to_time + duration.to_i.seconds).strftime('%H:%M') + end + + def duration_in_hms + return 0 if duration.nil? + + hours = duration / 3600 + minutes = (duration % 3600) / 60 + seconds = duration % 60 + "#{hours}h #{minutes}m #{seconds}s" + end + end +end diff --git a/lib/spree_cm_commissioner/trip_seat_layout_result.rb b/lib/spree_cm_commissioner/trip_seat_layout_result.rb new file mode 100644 index 000000000..0133f5340 --- /dev/null +++ b/lib/spree_cm_commissioner/trip_seat_layout_result.rb @@ -0,0 +1,11 @@ +module SpreeCmCommissioner + class TripSeatLayoutResult + attr_accessor :trip_id, :total_sold, :total_seats, :remaining_seats, :layout, :allow_seat_selection + + def initialize(options = {}) + options.each do |key, value| + instance_variable_set("@#{key}", value) + end + end + end +end diff --git a/spec/models/spree/line_item_spec.rb b/spec/models/spree/line_item_spec.rb index 91b59ceeb..1a4ec6025 100644 --- a/spec/models/spree/line_item_spec.rb +++ b/spec/models/spree/line_item_spec.rb @@ -21,7 +21,7 @@ let(:product) { create(:cm_accommodation_product, price: BigDecimal('10.0'), permanent_stock: 4) } let(:line_item) { create(:line_item, price: BigDecimal('10.0'), quantity: 2, product: product, from_date: '2023-01-10'.to_date, to_date: '2023-01-13'.to_date) } - it 'caculate amount base on price, quantity & number of nights' do + it 'calculate amount base on price, quantity & number of nights' do expect(line_item.amount).to eq BigDecimal('60.0') # 10.0 * 2 * 3 nights end end @@ -35,4 +35,123 @@ end end end -end \ No newline at end of file + + context 'transit?' do + let!(:vet) {create(:vendor, name: 'Vet', code:"VET")} + let!(:phnom_penh) { create(:transit_place, name: 'Phnom Penh', data_type:4) } + let!(:siem_reap) { create(:transit_place, name: 'Siem Reap', data_type:4) } + let!(:sihanoukville) { create(:transit_place, name: 'Sihanoukville', data_type:4) } + let!(:koh_rong) { create(:transit_place, name: 'Koh Rong', data_type:4) } + let!(:airbus) {create(:vehicle_type, + :with_seats, + code: "AIRBUS", + vendor: vet, + row: 10, + column: 5, + empty: [[0,2],[1,2],[2,2],[3,2],[4,2],[5,0],[5,1],[5,2],[6,0],[6,1],[6,2],[7,2],[8,2]] + )} + let!(:ferry) {create(:vehicle_type, + code: 'ferry', + vendor: vet, + allow_seat_selection: false, + vehicle_seats_count: 20 + )} + let!(:arb_f1_seat) {airbus.vehicle_seats.find_by(label: "F1")} + let!(:arb_f2_seat) {airbus.vehicle_seats.find_by(label: "F2")} + let!(:arb_f3_seat) {airbus.vehicle_seats.find_by(label: "F3")} + let!(:arb_f4_seat) {airbus.vehicle_seats.find_by(label: "F4")} + let!(:bus1) {create(:vehicle, vehicle_type: airbus, vendor: vet)} + let!(:ferry1) {create(:vehicle, vehicle_type: ferry, vendor: vet) } + let!(:today) {Date.today} + let!(:phnom_penh_to_siem_reap_by_airbus) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '10:00', + duration: 18000, + vehicle_id: bus1.id + }, + name: 'Phnom Penh to Siem Reap by Vet Airbus', + short_name:"PP-SR", + vendor: vet) } + let!(:sihanoukville_to_koh_rong_by_ferry) {create(:route, + trip_attributes: { + origin_id: sihanoukville.id, + destination_id: koh_rong.id, + departure_time: '10:00', + duration: 21600, + vehicle_id: ferry1.id, + allow_seat_selection: false + }, + name: "Buva Sea Sihanoukville to Koh Rong 10", + short_name: "SV-KR10-00-6", + vendor: vet)} + describe '#validate_seats_reservation' do + context "allow seat selection" do + let!(:order1) { create(:order, state: :complete) } + let!(:line_item1) {create(:line_item, quantity: 1, product:phnom_penh_to_siem_reap_by_airbus, order: order1, date: today)} + let!(:line_item_seat_1) {create(:line_item_seat, line_item: line_item1, seat: arb_f1_seat, date: today, variant: phnom_penh_to_siem_reap_by_airbus.master)} + let!(:order2) { create(:order, state: :cart) } + let!(:order3) { create(:order, state: :payment) } + let!(:order4) { create(:order, state: :complete, canceled_at: today) } + let!(:line_item4) {create(:line_item, quantity: 1, product:phnom_penh_to_siem_reap_by_airbus, order: order4, date: today)} + let!(:line_item_seat_4) {create(:line_item_seat, line_item: line_item4, seat: arb_f2_seat, date: today, variant: phnom_penh_to_siem_reap_by_airbus.master)} + let(:selected_seats) {[]} + before do + @line_item_seats_attributes = selected_seats.map do |seat| + {seat_id: seat.id, date:today, variant_id: phnom_penh_to_siem_reap_by_airbus.master.id} + end + end + context "error when at least one seat is already booked" do + let(:selected_seats) {[arb_f1_seat,arb_f2_seat]} + it "error when at least one seat is already booked when adding to cart" do + result = Spree::Cart::AddItem.call(order: order2, variant: phnom_penh_to_siem_reap_by_airbus.master, quantity: 2, options:{line_item_seats_attributes: @line_item_seats_attributes, date: today}) + error = result.error.value.errors.first + expect(result.success?).to eq false + expect(error.options[:message]).to eq "Some seats are already booked" + end + it "error when at least one seat is already booked when checking out" do + order1.update(state: :cart) + Spree::Cart::AddItem.call(order: order3, variant: phnom_penh_to_siem_reap_by_airbus.master, quantity: 2, options:{line_item_seats_attributes: @line_item_seats_attributes, date: today}) + order1.update(state: :complete) + expect { order3.next! }.to raise_error(StateMachines::InvalidTransition) + end + end + + context "success when all seats are available" do + let(:selected_seats) {[ arb_f2_seat, arb_f3_seat, arb_f4_seat]} + it "success when all seats are available" do + result = Spree::Cart::AddItem.call(order: order2, variant: phnom_penh_to_siem_reap_by_airbus.master, quantity: 3, options:{line_item_seats_attributes: @line_item_seats_attributes, date: today}) + expect(result.success?).to eq true + expect(result.error).to eq nil + end + end + end + + context "not allow seat selection" do + let!(:order1) { create(:order, state: :complete) } + let!(:line_item1) {create(:line_item, quantity: 10, product:sihanoukville_to_koh_rong_by_ferry, order: order1, date: today)} + let!(:order2) { create(:order, state: :cart) } + let!(:order3) { create(:order, state: :payment) } + it "error when quantity is more than available seats" do + result = Spree::Cart::AddItem.call(order: order2, variant: sihanoukville_to_koh_rong_by_ferry.master, quantity: 11, options:{date: today}) + error = result.error.value.errors.first + expect(result.success?).to eq false + expect(error.options[:message]).to eq "exceeded available quantity" + end + it "success when quantity is less than or equal available seats" do + result = Spree::Cart::AddItem.call(order: order2, variant: sihanoukville_to_koh_rong_by_ferry.master, quantity: 10, options:{date: today}) + + expect(result.success?).to eq true + expect(result.error).to eq nil + end + it "error when quantity is more than available seats when checking out" do + Spree::Cart::AddItem.call(order: order2, variant: sihanoukville_to_koh_rong_by_ferry.master, quantity: 10, options:{date: today}) + Spree::Cart::AddItem.call(order: order3, variant: sihanoukville_to_koh_rong_by_ferry.master, quantity: 10, options:{date: today}) + order2.update(state: :complete) + expect { order3.next! }.to raise_error(StateMachines::InvalidTransition) + end + end + end + end +end diff --git a/spec/models/spree/stock/availability_validator_spec.rb b/spec/models/spree/stock/availability_validator_spec.rb index 1c64b245b..eddae9c5e 100644 --- a/spec/models/spree/stock/availability_validator_spec.rb +++ b/spec/models/spree/stock/availability_validator_spec.rb @@ -32,4 +32,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/models/spree/taxonomy_spec.rb b/spec/models/spree/taxonomy_spec.rb new file mode 100644 index 000000000..5d00fa06f --- /dev/null +++ b/spec/models/spree/taxonomy_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +RSpec.describe Spree::Taxonomy, type: :model do + describe '.place' do + context "when there is place taxonomy" do + let!(:place_taxonomy) { create(:taxonomy, name: 'Place', kind: 'transit') } + it "should return the existed place taxonomy" do + expect(Spree::Taxonomy.place).to eq(place_taxonomy) + end + end + context "When Place taxonomy does not exist" do + it "create and returns a new Place taxonomy" do + expect { Spree::Taxonomy.place }.to change { Spree::Taxonomy.count }.by(1) + expect(Spree::Taxonomy.last.name).to eq('Place') + end + end + end +end diff --git a/spec/models/spree_cm_commissioner/stop_spec.rb b/spec/models/spree_cm_commissioner/stop_spec.rb new file mode 100644 index 000000000..6238562ca --- /dev/null +++ b/spec/models/spree_cm_commissioner/stop_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +RSpec.describe SpreeCmCommissioner::Stop, type: :model do + describe '#add_to_option_value' do + let!(:vendor) { create(:cm_vendor_with_product, name: 'Phnom Penh Hotel', code: 'PPH') } + + it 'auto create optoin_value after save ' do + location = create(:state) + branch = create(:cm_branch, state: location, vendor: vendor) + destination = Spree::OptionType.create(name: 'destination', presentation: 'destination',attr_type: 'destination') + origin = Spree::OptionType.create(name: 'origin', presentation: 'origin',attr_type: 'origin') + + expect(Spree::OptionValue.where(option_type_id: destination.id).count).to eq 0 + expect(Spree::OptionValue.where(option_type_id: origin.id).count).to eq 0 + + stop = described_class.create(name: 'testStop', branch_id: branch.id, state_id: location.id, vendor_id: vendor.id,code: 'TS',lat: 11.11, lon: 11.11, formatted_address: 'test_address', formatted_phone_number: 12345678) + + expect(Spree::OptionValue.where(option_type_id: destination.id).count).to eq 1 + expect(Spree::OptionValue.where(option_type_id: origin.id).count).to eq 1 + + place = SpreeCmCommissioner::Place.create(name: 'testPlace', branch_id: branch.id, state_id: location.id, vendor_id: vendor.id,code: 'TP',lat: 11.11, lon: 11.11, formatted_address: 'test_address', formatted_phone_number: 12345678) + + expect(Spree::OptionValue.where(option_type_id: destination.id).count).to eq 1 + expect(Spree::OptionValue.where(option_type_id: origin.id).count).to eq 1 + end + end +end \ No newline at end of file diff --git a/spec/models/spree_cm_commissioner/trip_stop_spec.rb b/spec/models/spree_cm_commissioner/trip_stop_spec.rb new file mode 100644 index 000000000..a0d06bae7 --- /dev/null +++ b/spec/models/spree_cm_commissioner/trip_stop_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +RSpec.describe SpreeCmCommissioner::TripStop, type: :model do + let!(:vet_airbus) {create(:vendor, name: 'Vet Airbus', code:"VET")} + let!(:larryta) {create(:vendor, name: 'Larryta', code: 'LTA')} + + #location + let!(:phnom_penh) { create(:transit_place, name: 'Phnom Penh', data_type:8) } + let!(:siem_reap) { create(:transit_place, name: 'Siem Reap', data_type:8) } + let!(:sihanoukville) { create(:transit_place, name: 'Sihanoukville', data_type:8) } + let!(:aeon1) {create(:transit_place, name: 'Aeon Mall 1', data_type:1)} + let!(:angkor_wat) {create(:transit_place, name: 'Angkor Wat', data_type:1)} + + #Vehicle + let!(:airbus) {create(:vehicle_type, + :with_seats, + code: "AIRBUS", + vendor: vet_airbus, + row: 4, + column: 4)} + let!(:bus1) {create(:vehicle, vehicle_type: airbus, vendor: vet_airbus)} + + let!(:vet_phnom_penh_siem_reap) { create(:route, name: 'VET Airbus Phnom Penh Siem Reap 10:00', short_name:"PP-SR-10:00-6", vendor: vet_airbus ) } + let!(:vet_phnom_penh_sihanoukville) { create(:route,name: 'VET Airbus Phnom Penh Sihanoukville 10:00', short_name:"PP-SV-10:00-6", vendor: vet_airbus) } + describe '#create_vendor_stop' do + context "Trip isn't existed" do + it "should create vendor stop with trip's origin and destination for vet_airbus" do + SpreeCmCommissioner::Trip.create(origin_id: phnom_penh.id, destination_id: siem_reap.id, departure_time: '10:00', duration: 3600, vehicle_id: bus1.id, product_id: vet_phnom_penh_siem_reap.id) + vendor = vet_airbus + expect(vendor.boarding_points.count).to eq(1) + expect(vendor.drop_off_points.count).to eq(1) + expect(vendor.boarding_points.first.name).to eq 'Phnom Penh' + expect(vendor.drop_off_points.first.name).to eq 'Siem Reap' + expect(larryta.boarding_points.count).to eq(0) + expect(larryta.drop_off_points.count).to eq(0) + end + it "should not create duplicate vendor stop for PhnomPenh for vet_airbus" do + SpreeCmCommissioner::Trip.create(origin_id: phnom_penh.id, destination_id: siem_reap.id, departure_time: '10:00', duration: 3600, vehicle_id: bus1.id, product_id: vet_phnom_penh_siem_reap.id) + SpreeCmCommissioner::Trip.create(origin_id: phnom_penh.id, destination_id: sihanoukville.id, departure_time: '10:00', duration: 3600, vehicle_id: bus1.id, product_id: vet_phnom_penh_siem_reap.id) + vendor = vet_airbus + expect(vendor.boarding_points.count).to eq(1) + expect(vendor.drop_off_points.count).to eq(2) + expect(vendor.boarding_points).to contain_exactly(phnom_penh) + expect(vendor.drop_off_points).to contain_exactly(siem_reap, sihanoukville) + expect(larryta.boarding_points.count).to eq(0) + expect(larryta.drop_off_points.count).to eq(0) + end + end + context "add more stops on existing trip" do + before do + trip = SpreeCmCommissioner::Trip.create(origin_id: phnom_penh.id, destination_id: siem_reap.id, departure_time: '10:00', duration: 3600, vehicle_id: bus1.id, product_id: vet_phnom_penh_siem_reap.id) + SpreeCmCommissioner::TripStop.create(trip_id: trip.id, stop_id: aeon1.id, stop_type: 'boarding') + SpreeCmCommissioner::TripStop.create(trip_id: trip.id, stop_id: angkor_wat.id, stop_type: 'drop_off') + end + it "should create vendor stop with the new added stops for vet_airbus" do + vendor = vet_airbus + expect(vendor.boarding_points.count).to eq(2) + expect(vendor.drop_off_points.count).to eq(2) + expect(vendor.boarding_points).to contain_exactly(phnom_penh, aeon1) + expect(vendor.drop_off_points).to contain_exactly(siem_reap, angkor_wat) + expect(larryta.boarding_points.count).to eq(0) + expect(larryta.drop_off_points.count).to eq(0) + end + it "should not create duplicate vendor stop for aeon1 for vet_airbus" do + trip2 = SpreeCmCommissioner::Trip.create(origin_id: phnom_penh.id, destination_id: sihanoukville.id, departure_time: '10:00', duration: 3600, vehicle_id: bus1.id, product_id: vet_phnom_penh_siem_reap.id) + SpreeCmCommissioner::TripStop.create(trip_id: trip2.id, stop_id: aeon1.id, stop_type: 'boarding') + vendor = vet_airbus + expect(vendor.boarding_points.count).to eq(2) + expect(vendor.drop_off_points.count).to eq(3) + expect(vendor.boarding_points).to contain_exactly(phnom_penh, aeon1) + expect(vendor.drop_off_points).to contain_exactly(siem_reap, angkor_wat, sihanoukville) + expect(larryta.boarding_points.count).to eq(0) + expect(larryta.drop_off_points.count).to eq(0) + end + end + end +end diff --git a/spec/queries/spree_cm_commissioner/trip_result_spec.rb b/spec/queries/spree_cm_commissioner/trip_result_spec.rb new file mode 100644 index 000000000..61527e068 --- /dev/null +++ b/spec/queries/spree_cm_commissioner/trip_result_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +RSpec.describe SpreeCmCommissioner::TripResult do + #vendor + describe 'initialize' do + it ' initialize with options' do + options = {trip_id: 1, vendor_id: 1, vendor_name: 'Vet Airbus', route_name: 'Phnom Penh to Siem Reap by Vet Airbus'} + result = described_class.new(options) + p result.vendor_name + expect(result.vendor_name).to eq('Vet Airbus') + expect(result.trip_id).to eq(1) + end + end + describe 'remaining_seats' do + it 'returns the remaining seats' do + options = {trip_id: 1, vendor_id: 1, total_sold: 10, total_seats: 20} + result = described_class.new(options) + expect(result.remaining_seats).to eq(10) + end + end +end diff --git a/spec/queries/spree_cm_commissioner/trip_search_query_spec.rb b/spec/queries/spree_cm_commissioner/trip_search_query_spec.rb new file mode 100644 index 000000000..81ced368e --- /dev/null +++ b/spec/queries/spree_cm_commissioner/trip_search_query_spec.rb @@ -0,0 +1,306 @@ +require 'spec_helper' + +RSpec.describe SpreeCmCommissioner::TripSearchQuery do + #vendor + let!(:vet_airbus) {create(:vendor, name: 'Vet Airbus', code:"VET")} + let!(:larryta) {create(:vendor, name: 'Larryta', code: 'LTA')} + let!(:buva_sea) {create(:vendor, name:'Buva Sea', code:"BS")} + + #location + let!(:phnom_penh) { create(:transit_place, name: 'Phnom Penh', data_type:8) } + let!(:siem_reap) { create(:transit_place, name: 'Siem Reap', data_type:8) } + let!(:sihanoukville) { create(:transit_place, name: 'Sihanoukville', data_type:8) } + let!(:koh_rong) {create(:transit_place, name: 'Koh Rong', data_type:8)} + let!(:aeon2) {create(:transit_place, name: 'Aeon Mall 2', data_type:1)} + let!(:aeon1) {create(:transit_place, name: 'Aeon Mall 1', data_type:1)} + let!(:phsar_kandal) {create(:transit_place, name: 'Phsar Kandal', data_type:1)} + let!(:angkor_aquarium) {create(:transit_place, name: 'Angkor Aquarium', data_type:1)} + let!(:angkor_wat) {create(:transit_place, name: 'Angkor Wat', data_type:1)} + + #Vehicle Type + let!(:airbus) {create(:vehicle_type, + :with_seats, + code: "AIRBUS", + vendor: vet_airbus, + row: 4, + column: 4)} + + let!(:minivan) {create(:vehicle_type, + :with_seats, + code: "minivan", + vendor: larryta, + row: 5, + column: 5)} + + let!(:ferry) {create(:vehicle_type, + code: 'ferry', + vendor: buva_sea, + allow_seat_selection: false, + vehicle_seats_count: 100 + )} + + let!(:bus1) {create(:vehicle, vehicle_type: airbus, vendor: vet_airbus)} + let!(:bus2) {create(:vehicle, vehicle_type: airbus, vendor: vet_airbus)} + let!(:minivan1) {create(:vehicle, vehicle_type: minivan, vendor: larryta)} + let!(:minivan2) {create(:vehicle, vehicle_type: minivan, vendor: larryta)} + let!(:ferry1) {create(:vehicle, vehicle_type: ferry, vendor: buva_sea)} + let!(:ferry2) {create(:vehicle, vehicle_type: ferry, vendor: buva_sea)} + + #routes + let!(:vet_phnom_penh_siem_reap_1) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '10:00', + duration: 3600, + vehicle_id: bus1.id, + trip_stops_attributes:[ + {stop_id: aeon2.id, stop_type: "boarding"}, + {stop_id: phsar_kandal.id, stop_type: "boarding"}, + {stop_id: angkor_aquarium.id, stop_type: "drop_off"}, + {stop_id: angkor_wat.id, stop_type: "drop_off"} + ] + }, + name: 'VET Airbus Phnom Penh Siem Reap 10:00', + short_name:"PP-SR-10:00-6", + vendor: vet_airbus + ) } + let!(:vet_phnom_penh_siem_reap_2) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '15:00', + duration: 7, + vehicle_id: bus2.id, + trip_stops_attributes:[ + {stop_id: aeon2.id, stop_type: "boarding"}, + {stop_id: phsar_kandal.id, stop_type: "boarding"}, + {stop_id: angkor_aquarium.id, stop_type: "drop_off"} + ] + }, + name: 'VET Airbus Phnom Penh Siem Reap 15:00', + short_name:"PP-SR-15:00-7", + vendor: vet_airbus + ) } + + let!(:larryta_phnom_penh_siemreap_1) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '13:00', + duration: 4, + vehicle_id: minivan1.id, + trip_stops_attributes:[ + {stop_id: aeon2.id, stop_type: "boarding"}, + ] + }, + name: 'Larryta Airbus Phnom Penh Siem Reap 13:00', + short_name:"PP-SR-13:00-4", + vendor: larryta + ) } + + let!(:larryta_phnom_penh_siemreap_2) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '20:00', + duration: 5, + vehicle_id: minivan2.id, + trip_stops_attributes:[ + {stop_id: angkor_wat.id, stop_type: "drop_off"}, + ] + }, + name: 'Larryta Airbus Phnom Penh Siem Reap 20:00', + short_name:"PP-SR-20:00-5", + vendor: larryta + ) } + let!(:sihanoukville_to_koh_rong_by_ferry) {create(:route, + trip_attributes: { + origin_id: sihanoukville.id, + destination_id: koh_rong.id, + departure_time: '10:00', + duration: 21600, + vehicle_id: ferry1.id, + allow_seat_selection: false, + }, + name: "Buva Sea Sihanoukville to Koh Rong 10", + short_name: "SV-KR10-00-6", + vendor: buva_sea)} + + let!(:sihanoukville_to_koh_rong_by_ferry_2) {create(:route, + trip_attributes: { + origin_id: sihanoukville.id, + destination_id: koh_rong.id, + departure_time: '15:00', + duration: 10800, + vehicle_id: ferry2.id, + allow_seat_selection: false + }, + name: "Buva Sea Sihanoukville to Koh Rong 15", + short_name: "SV-KR-15-00-3", + vendor: buva_sea)} +#date +let!(:today) {Date.today} +let!(:tomorrow) {today + 1.day} + + #airbus_seats + let!(:arb_f1_seat) {airbus.vehicle_seats.find_by(label: "F1")} + let!(:arb_f2_seat) {airbus.vehicle_seats.find_by(label: "F2")} + let!(:arb_f3_seat) {airbus.vehicle_seats.find_by(label: "F3")} + let!(:arb_f4_seat) {airbus.vehicle_seats.find_by(label: "F4")} + let!(:arb_f5_seat) {airbus.vehicle_seats.find_by(label: "F5")} + let!(:arb_f6_seat) {airbus.vehicle_seats.find_by(label: "F6")} + let!(:arb_f7_seat) {airbus.vehicle_seats.find_by(label: "F7")} + let!(:arb_f8_seat) {airbus.vehicle_seats.find_by(label: "F8")} + let!(:arb_f9_seat) {airbus.vehicle_seats.find_by(label: "F9")} + + #minivan_seats + let!(:mvn_f1_seat) {minivan.vehicle_seats.find_by(label: "F1")} + let!(:mvn_f2_seat) {minivan.vehicle_seats.find_by(label: "F2")} + let!(:mvn_f3_seat) {minivan.vehicle_seats.find_by(label: "F3")} + let!(:mvn_f4_seat) {minivan.vehicle_seats.find_by(label: "F4")} + let!(:mvn_f5_seat) {minivan.vehicle_seats.find_by(label: "F5")} + let!(:mvn_f6_seat) {minivan.vehicle_seats.find_by(label: "F6")} + let!(:mvn_f7_seat) {minivan.vehicle_seats.find_by(label: "F7")} + let!(:mvn_f8_seat) {minivan.vehicle_seats.find_by(label: "F8")} + let!(:mvn_f9_seat) {minivan.vehicle_seats.find_by(label: "F9")} + + + + #order + let!(:order1) {create(:transit_order, variant: vet_phnom_penh_siem_reap_1.master, seats: [arb_f1_seat,arb_f2_seat,arb_f4_seat], date: today)} + let!(:order2) {create(:transit_order, variant: vet_phnom_penh_siem_reap_1.master, seats: [arb_f5_seat,arb_f6_seat,arb_f7_seat,arb_f8_seat,arb_f9_seat], date: today)} + let!(:order3) {create(:transit_order, variant: larryta_phnom_penh_siemreap_1.master, seats: [mvn_f1_seat,mvn_f2_seat], date: today)} + let!(:order4) {create(:transit_order, variant: larryta_phnom_penh_siemreap_1.master, seats: [mvn_f3_seat,mvn_f4_seat], date: today)} + let!(:order5) {create(:transit_order, variant: vet_phnom_penh_siem_reap_2.master, seats: [arb_f1_seat,arb_f6_seat,arb_f4_seat], date: today)} + let!(:failed_order1) {create(:transit_order, variant: vet_phnom_penh_siem_reap_2.master, seats: [arb_f2_seat,arb_f3_seat,arb_f5_seat], date: today, state: 'payment', payment_state: nil)} + let!(:order6) {create(:transit_order, variant: larryta_phnom_penh_siemreap_2.master, seats: [mvn_f7_seat,mvn_f8_seat], date: today)} + let!(:order8) {create(:transit_order, variant: sihanoukville_to_koh_rong_by_ferry.master, date: tomorrow, quantity: 4)} + + describe"#trips_info" do + context "display table" do + let(:records) {described_class.new(origin_id: aeon2, destination_id: siem_reap, date: today)} + it "return trips table" do + result = records.trips_info + table = Terminal::Table.new :headings => ['Trip ID', 'Name', 'Vendor', 'Short Name', + 'Origin', 'Destination', 'Total Seat', + 'Total Sold', 'Remaining Seats', 'Departure Time', 'Duration', 'Vehicle'] + + result.each do |r| + table.add_row [r["trip_id"], r["route_name"], r["vendor_name"], r['short_name'], + r["origin"] + " - " + r['origin_id'].to_s, r["destination"] + " - " + r['destination_id'].to_s, r["total_seats"], r['total_sold'], + r['total_seats'] - r['total_sold'], r['departure_time'].strftime("%H:%M"), r['duration'], r['vehicle_id']] + table.add_separator unless r.equal?(result.last) # Avoid adding separator after the last row + end + table.style = {:alignment => :center} + puts table + end + end + + context "without vendor context" do + let(:result) {described_class.new(origin_id: phnom_penh, destination_id: siem_reap, date: today)} + it "return all matching trip" do + search_result = result.trips_info.to_a.sort_by(&:trip_id) + expect(search_result.count).to eq(4) + expect(search_result.first["vendor_name"]).to eq("Vet Airbus") + expect(search_result.last["vendor_name"]).to eq("Larryta") + end + end + + context "with vendor context" do + let(:result) {described_class.new(origin_id: phnom_penh, destination_id: siem_reap, date: today, vendor_id: vet_airbus.id)} + it "return only vet-airbus's trip " do + search_result = result.trips_info.to_a.sort_by(&:trip_id) + expect(search_result.count).to eq(2) + expect(search_result.first["vendor_name"]).to eq("Vet Airbus") + expect(search_result.last["vendor_name"]).to eq("Vet Airbus") + end + end + + context "no matching trip" do + let(:result) {described_class.new(origin_id: phnom_penh, destination_id: sihanoukville, date: today)} + it " return empty array" do + search_result = result.trips_info.to_a + expect(search_result.count).to eq(0) + end + end + end + + describe"#call" do + context "display table" do + let(:result) {described_class.new(origin_id: phnom_penh, destination_id: siem_reap, date: today)} + it "return trips table" do + search_result = result.call.sort_by(&:trip_id) + table = Terminal::Table.new :headings => ['Trip ID', 'Name', 'Vendor', 'Short Name', + 'Origin', 'Destination', 'Departure Time', 'Arrival Time', + 'Duration', 'Vehicle' , 'Total Seat', + 'Sold', 'Remaining Seats'] + search_result.each do |r| + table.add_row [r.trip_id, r.route_name, r.vendor_name, r.short_name, + r.origin + " - " + r.origin_id.to_s, r.destination + " - " + r.destination_id.to_s, r.departure_time, + r.arrival_time, r.duration_in_hms, r.vehicle_id, r.total_seats, r.total_sold, r.remaining_seats] + table.add_separator unless r.equal?(search_result.last) # Avoid adding separator after the last row + end + table.style = {:alignment => :center} + puts table + end + end + + context "return trip result for today" do + let(:result) {described_class.new(origin_id: phnom_penh, destination_id: siem_reap, date: today)} + it "return trip result for today" do + search_result = result.call.sort_by(&:trip_id) + expect(search_result.count).to eq(4) + expect(search_result.first.remaining_seats).to eq(7) + expect(search_result[1].remaining_seats).to eq(12) + expect(search_result[2].remaining_seats).to eq(20) + expect(search_result.last.remaining_seats).to eq(22) + end + end + context "return trip result of SV-KR for tomorrow " do + let(:result) {described_class.new(origin_id: sihanoukville, destination_id: koh_rong, date: tomorrow)} + it "return trip result for today" do + search_result = result.call.sort_by(&:trip_id) + expect(search_result.count).to eq(2) + expect(search_result.first.remaining_seats).to eq(96) + expect(search_result.last.remaining_seats).to eq(100) + end + end + context "return trip result for tomorrow" do + let(:result) {described_class.new(origin_id: phnom_penh, destination_id: siem_reap, date: tomorrow)} + let!(:tmr_order1) {create(:transit_order, variant: vet_phnom_penh_siem_reap_1.master, seats: [arb_f1_seat,arb_f2_seat,arb_f4_seat], date: tomorrow)} + let!(:tmr_order2) {create(:transit_order, variant: larryta_phnom_penh_siemreap_2.master, seats: [mvn_f3_seat,mvn_f4_seat], date: tomorrow)} + it "return trip result for tomorrow" do + search_result = result.call.sort_by(&:trip_id) + expect(search_result.count).to eq(4) + expect(search_result.first.remaining_seats).to eq(12) + expect(search_result[1].remaining_seats).to eq(15) + expect(search_result[2].remaining_seats).to eq(24) + expect(search_result.last.remaining_seats).to eq(22) + end + end + context "search for trip result using stop" do + let(:result1) {described_class.new(origin_id: aeon2, destination_id: siem_reap, date: tomorrow)} + let(:result2) {described_class.new(origin_id: phnom_penh, destination_id: angkor_wat, date: tomorrow)} + let(:result3) {described_class.new(origin_id: aeon2, destination_id: angkor_aquarium , date: tomorrow)} + it "only return trips that have boarding stop at aeon2" do + search = result1.call.sort_by(&:trip_id) + expect(search.count).to eq(3) + expect(search.first.trip_id).to eq(vet_phnom_penh_siem_reap_1.master.id) + expect(search.last.trip_id).to eq(larryta_phnom_penh_siemreap_1.master.id) + end + it "only return trips that have drop off stop at angkor wat" do + search = result2.call.sort_by(&:trip_id) + expect(search.count).to eq(2) + expect(search.first.trip_id).to eq(vet_phnom_penh_siem_reap_1.master.id) + expect(search.last.trip_id).to eq(larryta_phnom_penh_siemreap_2.master.id) + end + it "only return trips that have boarding and drop off stop at aeon2 and angkor aquarium" do + search = result3.call.sort_by(&:trip_id) + expect(search.count).to eq(2) + expect(search.first.trip_id).to eq(vet_phnom_penh_siem_reap_1.master.id) + expect(search.last.trip_id).to eq(vet_phnom_penh_siem_reap_2.master.id) + end + end + end +end diff --git a/spec/queries/spree_cm_commissioner/trip_seat_layout_query_spec.rb b/spec/queries/spree_cm_commissioner/trip_seat_layout_query_spec.rb new file mode 100644 index 000000000..96922b379 --- /dev/null +++ b/spec/queries/spree_cm_commissioner/trip_seat_layout_query_spec.rb @@ -0,0 +1,288 @@ +require 'spec_helper' + +RSpec.describe SpreeCmCommissioner::TripSeatLayoutQuery do + #vendor + let!(:vet) {create(:vendor, name: 'Vet', code:"VET")} + let!(:buva_sea) {create(:vendor, name:'Buva Sea', code:"BS")} + #location + let!(:phnom_penh) { create(:transit_place, name: 'Phnom Penh', data_type:4) } + let!(:siem_reap) { create(:transit_place, name: 'Siem Reap', data_type:4) } + let!(:sihanoukville) { create(:transit_place, name: 'Sihanoukville', data_type:4) } + let!(:koh_rong) {create(:transit_place, name: 'Koh Rong', data_type:4)} + + #Vehicle Type + let!(:airbus) {create(:vehicle_type, + :with_seats, + code: "AIRBUS", + vendor: vet, + row: 10, + column: 5, + empty: [[0,2],[1,2],[2,2],[3,2],[4,2],[5,0],[5,1],[5,2],[6,0],[6,1],[6,2],[7,2],[8,2]] + )} + + let!(:minivan) {create(:vehicle_type, + :with_seats, + code: "minivan", + vendor: vet, + row: 5, + column: 4, + empty: [[0,1],[1,2],[1,3],[2,2],[3,2]] + )} + + let!(:sleeping_bus) {create(:vehicle_type, + :with_layers, + code: "sleeping_bus", + vendor: vet, + layers: [ + { row: 6, + column: 4, + label:"F", + layer_name:"First Layer", + empty: [[0,1],[0,2],[0,3],[1,2],[1,3],[2,2],[3,2],[4,2],[5,2],[5,3]] + }, + { row: 6, + column: 4, + label:"S", + layer_name:"Second Layer", + empty: [[0,2],[0,3],[1,2],[2,2],[3,2],[4,2],[5,2]] + }] + )} + let!(:ferry) {create(:vehicle_type, + :with_seats, + code: 'ferry', + vendor: buva_sea, + vehicle_seats_count: 100, + )} + + let!(:bus1) {create(:vehicle, vehicle_type: airbus, vendor: vet)} + let!(:minivan1) {create(:vehicle, vehicle_type: minivan, vendor: vet)} + let!(:sleeping_bus1) {create(:vehicle, vehicle_type: sleeping_bus, vendor: vet)} + let!(:ferry1) {create(:vehicle, vehicle_type: ferry, vendor: buva_sea)} + + #routes + let!(:phnom_penh_to_siem_reap_by_airbus) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '10:00', + duration: 6, + vehicle_id: bus1.id + }, + name: 'Phnom Penh to Siem Reap by Vet Airbus', + short_name:"PP-SR", + vendor: vet) } + + let!(:phnom_penh_to_siem_reap_by_minivan) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '13:00', + duration: 6, + vehicle_id: minivan1.id + }, + name: 'Phnom Penh to Siem Reap by Larryta', + short_name:"PP-SR", + vendor: vet) } + + let!(:phnom_penh_to_siem_reap_by_sleeping_bus) { create(:route, + trip_attributes: { + origin_id: phnom_penh.id, + destination_id: siem_reap.id, + departure_time: '10:00', + duration: 6, + vehicle_id: sleeping_bus1.id + }, + name: 'Phnom Penh to Siem Reap by Vet Sleeping Bus', + short_name:"PP-SR", + vendor: vet) } + let!(:sihanoukville_to_koh_rong_by_ferry) {create(:route, + trip_attributes: { + origin_id: sihanoukville.id, + destination_id: koh_rong.id, + departure_time: '10:00', + duration: 6, + vehicle_id: ferry1.id, + allow_seat_selection: false + }, + name: "Sihanoukville to Koh Rong by Buva Sea Ferry", + short_name: "SV-KR", + vendor: buva_sea)} +#date +let!(:today) {Date.today} +let!(:tomorrow) {today + 1.day} + +#airbus_seats + let!(:arb_f1_seat) {airbus.vehicle_seats.find_by(label: "F1")} + let!(:arb_f2_seat) {airbus.vehicle_seats.find_by(label: "F2")} + let!(:arb_f3_seat) {airbus.vehicle_seats.find_by(label: "F3")} + let!(:arb_f4_seat) {airbus.vehicle_seats.find_by(label: "F4")} + let!(:arb_f5_seat) {airbus.vehicle_seats.find_by(label: "F5")} + let!(:arb_f6_seat) {airbus.vehicle_seats.find_by(label: "F6")} + let!(:arb_f7_seat) {airbus.vehicle_seats.find_by(label: "F7")} + let!(:arb_f8_seat) {airbus.vehicle_seats.find_by(label: "F8")} + let!(:arb_f9_seat) {airbus.vehicle_seats.find_by(label: "F9")} + + #minivan_seats + let!(:slb_f1_seat) {sleeping_bus.vehicle_seats.find_by(label: "F1")} + let!(:slb_f2_seat) {sleeping_bus.vehicle_seats.find_by(label: "F2")} + let!(:slb_f3_seat) {sleeping_bus.vehicle_seats.find_by(label: "F3")} + let!(:slb_f4_seat) {sleeping_bus.vehicle_seats.find_by(label: "F4")} + let!(:slb_f5_seat) {sleeping_bus.vehicle_seats.find_by(label: "F5")} + let!(:slb_s6_seat) {sleeping_bus.vehicle_seats.find_by(label: "S6")} + let!(:slb_s7_seat) {sleeping_bus.vehicle_seats.find_by(label: "S7")} + let!(:slb_s16_seat) {sleeping_bus.vehicle_seats.find_by(label: "S16")} + let!(:slb_s17_seat) {sleeping_bus.vehicle_seats.find_by(label: "S17")} + + #minivan_seats + let!(:mvn_f1_seat) {minivan.vehicle_seats.find_by(label: "F1")} + let!(:mvn_f2_seat) {minivan.vehicle_seats.find_by(label: "F2")} + let!(:mvn_f3_seat) {minivan.vehicle_seats.find_by(label: "F3")} + let!(:mvn_f4_seat) {minivan.vehicle_seats.find_by(label: "F4")} + let!(:mvn_f5_seat) {minivan.vehicle_seats.find_by(label: "F5")} + let!(:mvn_f6_seat) {minivan.vehicle_seats.find_by(label: "F6")} + let!(:mvn_f7_seat) {minivan.vehicle_seats.find_by(label: "F7")} + let!(:mvn_f8_seat) {minivan.vehicle_seats.find_by(label: "F8")} + let!(:mvn_f9_seat) {minivan.vehicle_seats.find_by(label: "F9")} + + #order + let!(:order1) {create(:transit_order, variant: phnom_penh_to_siem_reap_by_airbus.master, seats: [arb_f1_seat,arb_f4_seat], date: today)} + let!(:order2) {create(:transit_order, variant: phnom_penh_to_siem_reap_by_airbus.master, seats: [arb_f5_seat,arb_f6_seat,arb_f7_seat,arb_f8_seat,arb_f9_seat])} + let!(:order3) {create(:transit_order, variant: phnom_penh_to_siem_reap_by_minivan.master, seats: [mvn_f1_seat,mvn_f2_seat], date: today)} + let!(:order4) {create(:transit_order, variant: phnom_penh_to_siem_reap_by_minivan.master, seats: [mvn_f5_seat,mvn_f6_seat], date: today)} + let!(:order5) {create(:transit_order, variant: phnom_penh_to_siem_reap_by_sleeping_bus.master, seats: [slb_f1_seat,slb_f2_seat], date: today)} + let!(:order6) {create(:transit_order, variant: phnom_penh_to_siem_reap_by_sleeping_bus.master, seats: [slb_s16_seat,slb_s17_seat], date: today)} + let!(:order7) {create(:transit_order, variant: sihanoukville_to_koh_rong_by_ferry.master, date: tomorrow, quantity: 3, state: 'payment', payment_state: nil)} + let!(:order8) {create(:transit_order, variant: sihanoukville_to_koh_rong_by_ferry.master, date: tomorrow, quantity: 5)} + + + + + + describe"#call" do + context "display seats layout table for Sleeping Bus" do + let(:records) {described_class.new(trip_id: phnom_penh_to_siem_reap_by_sleeping_bus.master.id, date: today)} + it "return Sleeping Bus seats layout" do + result = records.call + layout = result.layout + puts "Total Seats: #{result.total_seats}" + puts "Sold: #{result.total_sold}" + puts "Remaining: #{result.remaining_seats}" + layout.each do |layer, rows| + rows_table = Terminal::Table.new do |t| + rows.each do |row_num, seats| + row = seats.map do |seat| + if seat[:seat_type] == "normal" + seat[:seat_id] == nil ? " #{seat[:label]}" : "x#{seat[:label]}" + else + seat[:seat_type] == "driver" ? "D " : " - " + end + end + t.add_row row + end + end + rows_table.style = {:alignment => :center} + puts "#{layer}\n#{rows_table}" + end + end + end + + context "display seats layout table for Airbus Bus" do + let(:records) {described_class.new(trip_id: phnom_penh_to_siem_reap_by_airbus.master.id, date: today)} + it "return Air Bus seats layout" do + result = records.call + layout = result.layout + expect(result.allow_seat_selection).to eq(true) + expect(result.total_sold).to eq(7) + puts "Total Seats: #{result.total_seats}" + puts "Sold: #{result.total_sold}" + puts "Remaining: #{result.remaining_seats}" + layout.each do |layer, rows| + rows_table = Terminal::Table.new do |t| + rows.each do |row_num, seats| + row = seats.map do |seat| + if seat[:seat_type] == "normal" + seat[:seat_id] == nil ? " #{seat[:label]}" : "x#{seat[:label]}" + else + seat[:seat_type] == "driver" ? "D " : " - " + end + end + t.add_row row + end + end + rows_table.style = {:alignment => :center} + puts "#{layer}\n#{rows_table}" + end + end + end + + context "display seats layout table for ferry" do + let(:records) {described_class.new(trip_id: sihanoukville_to_koh_rong_by_ferry.master.id, date: tomorrow)} + it "return ferry seat layout" do + result = records.call + layout = result.layout + puts "Total Seats: #{result.total_seats}" + puts "Sold: #{result.total_sold}" + puts "Remaining: #{result.remaining_seats}" + puts "Allow Seat Selection: #{result.allow_seat_selection}" + expect(result.allow_seat_selection).to eq(false) + expect(result.layout).to eq(nil) + expect(result.total_sold).to eq(5) + end + end + + context "display seats layout table for Minivan" do + let(:records) {described_class.new(trip_id: phnom_penh_to_siem_reap_by_minivan.master.id, date: today)} + it "return Minivan seats layout" do + result = records.call + layout = result.layout + puts "Total Seats: #{result.total_seats}" + puts "Sold: #{result.total_sold}" + puts "Remaining: #{result.remaining_seats}" + layout.each do |layer, rows| + rows_table = Terminal::Table.new do |t| + rows.each do |row_num, seats| + row = seats.map do |seat| + if seat[:seat_type] == "normal" + seat[:seat_id] == nil ? " #{seat[:label]}" : "x#{seat[:label]}" + else + seat[:seat_type] == "driver" ? "D " : " - " + end + end + t.add_row row + end + end + rows_table.style = {:alignment => :center} + puts "#{layer}\n#{rows_table}" + end + end + end + end + + describe "#seats" do + context "return all seats for sleeping bus" do + let(:records) {described_class.new(trip_id: phnom_penh_to_siem_reap_by_sleeping_bus.master.id, date: today)} + it "return all seats for sleeping bus" do + result = records.seats + order_seats = [slb_f1_seat,slb_f2_seat,slb_s16_seat,slb_s17_seat] + ordered_seats_from_result = result.select {|seat| seat.seat_id != nil} + bookable_seats = result.select {|seat| %w[normal vip].include?(seat.seat_type)} + available_seats = result.select {|seat| seat.seat_id == nil && %w[normal vip].include?(seat.seat_type)} + expect(ordered_seats_from_result).to eq(order_seats) + expect(bookable_seats.count).to eq(sleeping_bus.vehicle_seats_count) + expect(available_seats.count).to eq(sleeping_bus.vehicle_seats_count - order_seats.count) + end + end + end + describe '#ordered_seats_sql' do + context "return ordered seats for airbus" do + let(:records) {described_class.new(trip_id: phnom_penh_to_siem_reap_by_airbus.master.id, date: today)} + let(:failed_order) {create(:transit_order, variant: phnom_penh_to_siem_reap_by_airbus.master, seats: [arb_f2_seat,arb_f3_seat], date: today, state: 'payment', payment_state: nil)} + it "return all seats for sleeping bus" do + result = records.ordered_seats + ordered_seats_ids = result.pluck(:seat_id) + expect(result.count).to eq(7) + expect(ordered_seats_ids).not_to include [arb_f2_seat.id,arb_f3_seat.id] + end + end + end +end diff --git a/spec/serializers/spree/v2/storefront/line_item_serializer_spec.rb b/spec/serializers/spree/v2/storefront/line_item_serializer_spec.rb index 80b36312c..4c4856da1 100644 --- a/spec/serializers/spree/v2/storefront/line_item_serializer_spec.rb +++ b/spec/serializers/spree/v2/storefront/line_item_serializer_spec.rb @@ -2,7 +2,8 @@ RSpec.describe Spree::V2::Storefront::LineItemSerializer, type: :serializer do describe '#serializable_hash' do - let(:line_item) { create(:cm_line_item) } + product = Spree::Product.create(name: 'Test Product', price: 10.00, product_type: :accommodation) + line_item = Spree::LineItem.new(quantity: 1, price: 10.00, currency: 'USD', product: product) subject { described_class.new(line_item, include: [ diff --git a/spree_cm_commissioner.gemspec b/spree_cm_commissioner.gemspec index a0426c048..53303d7de 100644 --- a/spree_cm_commissioner.gemspec +++ b/spree_cm_commissioner.gemspec @@ -45,8 +45,8 @@ Gem::Specification.new do |s| s.add_dependency 'googleauth' s.add_dependency 'telegram-bot' s.add_dependency 'exception_notification' - + s.add_dependency 'counter_culture', '~> 3.2' s.add_development_dependency 'pg' s.add_development_dependency 'spree_dev_tools' s.metadata['rubygems_mfa_required'] = 'true' -end \ No newline at end of file +end diff --git a/vendor/assets/javascripts/map/map-picker.js b/vendor/assets/javascripts/map/map-picker.js index 4053c0717..c6349ccec 100644 --- a/vendor/assets/javascripts/map/map-picker.js +++ b/vendor/assets/javascripts/map/map-picker.js @@ -22,17 +22,17 @@ const MapPicker = { }, registerEventHandler: function () { self = this; - $("#stock_location_lat").on("change", function () { + $("[id$='_lat']").on("change", function () { self.refreshMarker(); }); - $("#stock_location_lon").on("change", function () { + $("[id$='_lon']").on("change", function () { self.refreshMarker(); }); }, refreshFields: function () { const position = this.marker.getPosition(); - $("#stock_location_lat").val(position.lat); - $("#stock_location_lon").val(position.lng); + $("[id$='_lat']").val(position.lat); + $("[id$='_lon']").val(position.lng); }, initMap: function () { this.map = new google.maps.Map(document.getElementById("map"), { @@ -47,8 +47,8 @@ const MapPicker = { }); }, refreshMarker: function () { - const lat = $("#stock_location_lat").val(); - const lon = $("#stock_location_lon").val(); + const lat = $("[id$='_lat']").val(); + const lon = $("[id$='_lon']").val(); this.marker.setPosition(new google.maps.LatLng(lat, lon)); this.map.setCenter(this.marker.getPosition()); },