Проект: CRM/ERP-система управления автомойками
Компонент: мониторинг статуса заказов для клиентов
Технологический стек проекта: Postgres, ElasticSearch; Rails, Grape; AngularJS (v1)
angular.module('CarWash').controller('ClientMonitorController', function ( | |
$scope, $http, $interval, $timeout, $stateParams, $sce, Reservation, | |
) { | |
$scope.TIME_INTERVALS = { | |
RESERVATIONS_UPDATE: 15 * 1000, | |
} | |
const notification = new Audio('monitor_notifications/default_notification.mp3'); | |
const announcementPanel = $('#announcementPanel'); | |
const clientMonitorUUID = $stateParams.monitorUUID; | |
// Получаем данные настроек нужного монитора заказов | |
$http({ | |
url: '/client_monitor/settings', | |
method: 'GET', | |
params: { | |
client_monitor_uuid: clientMonitorUUID, | |
}, | |
}).then((response) => { | |
let playlistID = response.data['youtube_playlist'].split(/list=/)[1]; | |
let bgImage = response.data['bg_image']; | |
$scope.TIME_INTERVALS.RESERVATION_ANNOUNCEMENT = response.data['announcement_period'] * 1000; | |
if (bgImage) { | |
$('body.client_monitor').css({ | |
'background-image': `url('${bgImage}')` | |
}); | |
} | |
if (playlistID) { | |
$scope.youtubeUrl = $sce.trustAsResourceUrl(`https://www.youtube.com/embed/videoseries?list=${playlistID}&enablejsapi=1&autoplay=1&loop=1`); | |
}; | |
}); | |
$scope.announcementInProgress = false; | |
$scope.reservations = { | |
toAnnounce: [], | |
queue: [], | |
} | |
// Fullscreen announcement of finished reservations in | |
// a popup | |
const announceFullscreenReservation = (reservations) => { | |
$scope.announcementInProgress = true; | |
const r = reservations.pop(); | |
notification.play().catch((error) => { | |
console.log('Caught DOMExc., trying to reload'); | |
}).then(() => { | |
notification.play(); | |
}); | |
$timeout(() => { | |
notification.play() | |
}, $scope.TIME_INTERVALS.RESERVATION_ANNOUNCEMENT / 2); | |
announcementPanel.removeClass('hidden'); | |
$scope.announcedReservation = r; | |
return $timeout($scope.TIME_INTERVALS.RESERVATION_ANNOUNCEMENT).then(() => { | |
$scope.announcementInProgress = false; | |
announcementPanel.addClass('hidden'); | |
}); | |
}; | |
$scope.announceFullscreenReservations = () => { | |
announceFullscreenReservation($scope.reservations.toAnnounce).then(() => { | |
if ($scope.reservations.toAnnounce.length != 0) { | |
$scope.announceFullscreenReservations(); | |
} | |
}) | |
}; | |
// Получаем список готовых заказов с заданной периодичностью | |
// и строим очередь для выведения большого поп-апа на весь экран по одному | |
$scope.updateReservations = () => { | |
if ($scope.announcementInProgress) { | |
return | |
}; | |
return $http({ | |
url: '/client_monitor', | |
method: 'GET', | |
params: { | |
client_monitor_uuid: clientMonitorUUID, | |
}, | |
}).then((response) => { | |
let newQueue = response.data['reservations_to_announce']; | |
$scope.reservations.toAnnounce = reservationQueuesDiff(newQueue, | |
$scope.reservations.queue); | |
$scope.reservations.queue = newQueue; | |
console.log('Finished reservations were fetched'); | |
if ($scope.reservations.toAnnounce.length > 0) { | |
$scope.announceFullscreenReservations(); | |
} | |
}); | |
}; | |
$scope.updateReservations(); | |
$interval($scope.updateReservations, $scope.TIME_INTERVALS.RESERVATIONS_UPDATE); | |
const reservationQueuesDiff = (newQueue, oldQueue) => { | |
return newQueue.filter((newRes) => { | |
return !oldQueue.some((oldRes) => { | |
return (newRes.id == oldRes.id) && (newRes.type == oldRes.type); | |
}); | |
}); | |
}; | |
$scope.manualInfo = (r) => { | |
let parts = r.announcement_text.split('|'); | |
return { | |
tag: parts[0], | |
make: parts[1], | |
model: parts[2] | |
} | |
} | |
}, ); |
class ClientMonitorController < ApplicationController | |
# Устанавливаем, какая точка сети автомоек должна быть контекстом | |
before_action :set_service_location | |
MANUAL_RESERVATION_ANNOUNCEMENT_PERIOD = 3.minutes | |
def index | |
car_attributes = { | |
car: { | |
include: { | |
car_make: { only: :name }, | |
car_model: { only: :name } | |
}, | |
only: :tag | |
} | |
} | |
# Собираем все заказы, которые уже готовы (waiting_for_client) | |
reservations_to_announce = Reservation | |
.where(service_location_id: @service_location.id) | |
.order(:time_end) | |
.select { |r| r.business_process_status =~ /waiting_for_client/ } | |
.as_json(include: car_attributes) | |
.map { |item| item.merge(type: 'reservation') } | |
# Отдельно собираем объевления заказов, добавляемые вручную сотрудниками | |
manual_reservations_to_announce = ManualReservationAnnouncement.where( | |
service_location_id: @service_location.id, | |
organization_id: @service_location.organization_id | |
).where('created_at > ?', Time.now - MANUAL_RESERVATION_ANNOUNCEMENT_PERIOD) | |
.order(:created_at) | |
.as_json(methods: [:time_end]) | |
.map { |item| item.merge(type: 'manual_reservation') } | |
all_reservations = [*reservations_to_announce, *manual_reservations_to_announce].sort_by do |item| | |
item['time_end'] | |
end | |
render json: { reservations_to_announce: all_reservations } | |
end | |
# Настройки внешнего экрана (в данном случае используется плейлист с YouTube для, иначе фолбэк на картинку; | |
# так же время, которое заказ показывается на экране) | |
def settings | |
ytpl = @service_location.setting(:client_monitor_youtube_playlist_url).value | |
announcement_period = @service_location.setting(:client_monitor_announcement_period).value | |
render json: { | |
youtube_playlist: ytpl, | |
bg_image: @service_location.client_monitor_image.url, | |
announcement_period: announcement_period | |
} | |
end | |
private | |
def set_service_location | |
@service_location = ServiceLocation.find_by(client_monitor_uuid: params[:client_monitor_uuid]) | |
end | |
end |