Skip to content

Instantly share code, notes, and snippets.

@vladyio
Last active April 27, 2023 14:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vladyio/4f01ce3ffbaac7efda5a73237c1c2d1f to your computer and use it in GitHub Desktop.
Save vladyio/4f01ce3ffbaac7efda5a73237c1c2d1f to your computer and use it in GitHub Desktop.
Мониторинг заказов для клиентов автомойки

Проект: 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment