This commit is contained in:
Joey Schluchter 2021-06-09 15:06:12 -04:00
Родитель c4c1128a8a
Коммит f255e9bfab
14 изменённых файлов: 193 добавлений и 145 удалений

Просмотреть файл

@ -17,5 +17,6 @@ namespace RedDog.AccountingModel
[Column(TypeName = "nvarchar(50)")]
[Required]
public string LastName { get; set; }
}
}

Просмотреть файл

@ -123,41 +123,26 @@ namespace RedDog.AccountingService.Controllers
}
// [HttpGet("/Corp/SalesProfit/PerStore")]
// public async Task<List<SalesProfitMetric>> GetCorpSalesAndProfitPerStore([FromServices] AccountingContext dbContext){
[HttpGet("/Profit/{period}/{timeSpan}")]
public OrdersTimeSeries GetProfitOverTime(string storeId, string period, string timeSpan, [FromServices] AccountingContext dbContext)
{
TimeSpan spanLength = XmlConvert.ToTimeSpan(timeSpan);
var fromDate = DateTime.UtcNow.Subtract(spanLength);
// var totalOrders = from o in dbContext.Orders
// join oi in dbContext.OrderItems on o.OrderId equals oi.OrderId
// orderby o.PlacedDate descending
// group o by new SalesProfitMetric{
// StoreId = o.StoreId,
// OrderYear = o.PlacedDate.Year,
// OrderMonth = o.PlacedDate.Month,
// OrderDay = o.PlacedDate.Day,
// TotalProfit = (oi.UnitPrice - oi.UnitCost) * oi.Quantity,
// TotalSales = oi.UnitPrice * oi.Quantity
// };
var totalOrders = from o in dbContext.Orders
where o.StoreId == storeId && o.PlacedDate > fromDate
orderby o.PlacedDate descending
group o by new StoreTimeSegmentMinute{
StoreId = storeId,
Year = o.PlacedDate.Year,
Month = o.PlacedDate.Month,
Day = o.PlacedDate.Day,
Hour = o.PlacedDate.Hour,
Minute = o.PlacedDate.Minute
};
var orderData = from oi in totalOrders
select new TimeSeries<int>
{
PointInTime = new DateTime(oi.Key.Year,oi.Key.Month, oi.Key.Day, oi.Key.Hour, oi.Key.Minute,0),
Value = oi.Count()
};
// return await totalOrders.ToListAsync();
// }
var totalOrdersByMinute = new OrdersTimeSeries{
StoreId = storeId,
Values = orderData.ToList()
};
return totalOrdersByMinute;
}
[HttpGet("/OrderMetrics")]
public async Task<List<OrderMetric>> GetOrderMetricsAsync(string storeId, [FromServices] AccountingContext dbContext)
@ -228,8 +213,11 @@ namespace RedDog.AccountingService.Controllers
TotalPrice = oi.TotalPrice
};
return await metrics.ToListAsync();
if(!string.IsNullOrEmpty(storeId)){
metrics = metrics.Where(m=>m.StoreId == storeId);
}
return await metrics.OrderByDescending(m=>m.OrderDate).ToListAsync();
}

Просмотреть файл

@ -0,0 +1,26 @@
using System;
using System.Text.Json.Serialization;
namespace RedDog.AccountingService.Models
{
public class SalesProfitMetric
{
[JsonPropertyName("storeId")]
public string StoreId { get; set; }
[JsonPropertyName("orderYear")]
public int OrderYear { get; set; }
[JsonPropertyName("orderMonth")]
public int OrderMonth { get; set; }
[JsonPropertyName("orderDay")]
public int OrderDay { get; set; }
[JsonPropertyName("totalSales")]
public decimal TotalSales { get; set; }
[JsonPropertyName("totalProfit")]
public decimal TotalProfit { get; set; }
}
}

Просмотреть файл

@ -1,4 +1,4 @@
![Red Dog :: Bodega](./public/img/android-chrome-192x192.png)
![Red Dog :: Contoso](./public/img/android-chrome-192x192.png)
## RedDog.UI :: RedDog single site location user interface
> Prerequisites
@ -7,11 +7,27 @@
- [NPM](https://npm.org)
### Quickstart
> To run locally :
### QUI
> To run locally with env vars
- add a .env file to the root with the following in it, changing said variables as needed:
```shell
npm install
VUE_APP_IS_CORP=false
NODE_ENV=development
VUE_APP_STORE_ID="Austin"
VUE_APP_SITE_TYPE="Pharmacy"
VUE_APP_SITE_TITLE="Contoso :: BODEGA"
VUE_APP_MAKELINE_BASE_URL="http://austin.makeline.brianredmond.io"
VUE_APP_ACCOUNTING_BASE_URL="http://austin.accounting.brianredmond.io"
```
> Then run
```shell
npm ci ## this will overwrite any and all node_modules
npm run serve
```

Двоичные данные
RedDog.UI/public/img/contoso.psd

Двоичный файл не отображается.

Просмотреть файл

@ -42,7 +42,7 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-rtl/3.4.0/css/bootstrap-rtl.css" rel="stylesheet" />
</head>
<body>
<body class="corp">
<div class="wrapper" id="app">
</div>
</body>

Просмотреть файл

@ -45,25 +45,23 @@ html {
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
// stylelint-enable selector-list-comma-newline-after
// Body
//
// 1. Remove the margin in all browsers.
// 2. As a best practice, apply a default `background-color`.
// 3. Set an explicit initial text-align value so that we can later use the
// the `inherit` value on things like `<th>` elements.
body {
body{
margin: 0; // 1
font-family: "Quicksand", sans-serif;
font-size: $font-size-base;
font-weight: $font-weight-base;
line-height: $line-height-base;
color: #DAE2DF; // GAINSBORO
color: #DAE2DF;
text-align: left;
background-color: #0d152b; // RAISIN BLACK
// background: url('/img/hex-back-pat.png');
}
//branch look and feel
body.branch {
background-color: #0d152b;
}
//corp look and feel
body.corp {
background-color: #25292c;
}
// Suppress the focus outline on elements that cannot be accessed via keyboard.

Просмотреть файл

@ -231,10 +231,14 @@
.card-trans-base {
.card-trans-branch {
border-radius: 8px;
background: #101b34;
}
.card-trans-corp {
border-radius: 8px;
background: #222222;
}
.card-big-category {
text-transform: uppercase;

Просмотреть файл

@ -420,13 +420,14 @@
&[data="vue"] {
background: #101b34;
// #21182a, #000
// @include linear-gradient(
// #18151a,
// #070207b1);
// ackground: linear-gradient(
// 0deg
// , #18151a 0%, #070207b1 100%);
}
&[data="branch"] {
background: #101b34;
}
&[data="corp"] {
background: #222222;
}
.user{

Просмотреть файл

@ -1,6 +1,6 @@
<template>
<div class="wrapper">
<side-bar>
<side-bar :backgroundColor="sideBarColor">
<template slot="links">
<sidebar-link to="/dashboard" :name="$t('sidebar.dashboard')" icon="tim-icons icon-chart-pie-36"/>
<sidebar-link to="/maps" :name="$t('sidebar.maps')" icon="tim-icons icon-globe-2"/>
@ -28,6 +28,8 @@ export default {
},
data() {
return {
isCorp: (process.env.VUE_APP_IS_CORP || false),
sideBarColor: "branch"
}
},
methods: {
@ -40,6 +42,14 @@ export default {
beforeDestroy() {
},
created() {
if(this.isCorp === true || this.isCorp === 'true'){
this.sideBarColor = "corp"
}
else{
this.sideBarColor = "branch"
}
},
};
</script>
<style>
</style>

Просмотреть файл

@ -15,7 +15,8 @@
</div>
<div class="logo-content">
<div class="left-logo"><img src="img/contoso-pharmacy-tl-logo.png" width="184px"/></div>
<div class="right-logo logo-simple-text">{{ storeId }}</div>
<div class="right-logo logo-simple-text" v-if="isCorp === true || isCorp === 'true'">CORP</div>
<div class="right-logo logo-simple-text" v-else>{{ storeId }}</div>
</div>
</div>
<button class="navbar-toggler" type="button"
@ -89,8 +90,9 @@
return {
activeNotifications: false,
showMenu: false,
siteType: (process.env.VUE_APP_SITE_TYPE || 'PHARMACY-Local'),
storeId: (process.env.VUE_APP_STORE_ID || 'Redmond-Local'),
isCorp: (process.env.VUE_APP_IS_CORP || false),
siteType: (process.env.VUE_APP_SITE_TYPE || 'PHARMACY-NOENV'),
storeId: (process.env.VUE_APP_STORE_ID || 'REDMOND-NOENV'),
};
},
methods: {
@ -113,8 +115,18 @@
this.showMenu = !this.showMenu;
}
},
mounted(){
// SET PAGE TITLE
document.title = process.env.VUE_APP_SITE_TITLE
// SET BODY STYLE
if (process.env.VUE_APP_IS_CORP === true || process.env.VUE_APP_IS_CORP === 'true'){
document.body.className = 'corp'
}else{
document.body.className = 'branch'
}
},
created() {
// console.log(process.env.VUE_APP_STORE_ID)
}
};
</script>

Просмотреть файл

@ -4,7 +4,7 @@
<div class="col-lg-6">
<div class="row">
<div class="col-lg-12">
<div class="card card-trans-base">
<div class="card" :class="[cardClass]">
<div class="card-header-title">
ORDERS OVER TIME
</div>
@ -16,16 +16,35 @@
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="row">
<div class="col-lg-12">
<div class="card card-trans-base">
<div class="card" :class="[cardClass]">
<div class="card-header-title">
P & L OVER TIME
</div>
<div class="card-body chart-body">
<StreamChart v-if="salesChartLoaded" :chartData="salesChartData" :options="salesChartOptions"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-lg-left">
<div class="col-lg-6">
<div class="row">
<div class="col-lg-12" v-if="isCorp === true || isCorp === 'true'">
Need to put something for corp here
</div>
<div class="col-lg-12" v-else>
<div class="card" :class="[cardClass]">
<div class="card-header-title">
ORDER QUEUE
</div>
<div class="card-table-par">
<div class="table-responsive">
<table id="tblInflight" class="table table-striped table-dark table-fixed">
<thead>
<thead>
<tr>
<th scope="col" class="col-1 th-order">#</th>
<th scope="col" class="col-2 th-order">TIME</th>
@ -34,7 +53,6 @@
</tr>
</thead>
<tbody>
<!-- {{ this.inflight.forEach((item, index)) }} -->
<tr v-for="(order, i) in inflight" class="tr-item-queue">
<td scope="row" class="col-1 td-queue-position">{{ i+1 }}</td>
<td class="col-2 td-time">{{ order.timeIn }}</td>
@ -52,27 +70,11 @@
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-lg-left">
<div class="col-lg-6">
<div class="row">
<div class="col-lg-12">
<div class="card card-trans-base">
<div class="card-header-title">
P & L OVER TIME
</div>
<div class="card-body chart-body">
<StreamChart v-if="salesChartLoaded" :chartData="salesChartData" :options="salesChartOptions"/>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="row">
<div class="col-lg-12">
<div class="card card-trans-base">
<div class="card" :class="[cardClass]">
<div class="card-header-title">
AVG PROFIT
</div>
@ -86,7 +88,7 @@
<div class="col-lg-3">
<div class="row">
<div class="col-lg-12">
<div class="card card-trans-base">
<div class="card" :class="[cardClass]">
<div class="card-header-title">
AVG ORDER FILL TIME
</div>
@ -120,6 +122,8 @@ export default {
},
data() {
return {
isCorp: (process.env.VUE_APP_IS_CORP || false),
cardClass: "card-trans-branch",
orderChartSegment: 1,
orderChartSegmentName: 'MINUTES',
orderChartInterval: null,
@ -156,7 +160,7 @@ export default {
},
},
methods: {
fillOrderChart(data){
fillBranchOrderChart(data){
let minuteLabels = [], dataValues = [], dataValuesPrev = [], previousArr = [], lastArr = []
previousArr = data.values.slice(data.values.length-20, data.values.length-10)
@ -175,6 +179,7 @@ export default {
}
});
},
fillCorpOrderChart(data){},
getCurrentDateTime() {
var current = new Date();
this.currentDateTime = current.toLocaleString();
@ -211,7 +216,6 @@ export default {
this.totalProfitFormatted = currency(this.totalProfit, {precision:0}).format(); /// TOTAL PROFIT FORMATTEd
this.profitPerOrder = (this.totalProfit / this.fulfilledOrders).toFixed(2) /// PROFIT PER ORDER
this.profitPerOrderFormatted = currency(this.profitPerOrder, {precision:2}).format(); /// PROFIT PER ORDER FORMATTED
// this.avgFulfillmentSec = (this.totalFulfillmentTime / this.fulfilledOrders).toFixed(0); /// AVG FULFILLMENT TIME
this.avgFulfillmentSec = moment.duration((this.totalFulfillmentTime / this.fulfilledOrders).toFixed(0), "seconds").minutes();
this.totalSales = this.totalSales.toFixed(0); /// TOTAL SALES
this.totalSalesFormatted = currency(this.totalSales, {precision:0}).format(); /// TOTAL SALES FORMATTED
@ -222,9 +226,8 @@ export default {
}
}
);
}, 3000);
}, 10000);
},
getOrderChart(){
clearInterval(this.orderChartInterval)
let orderChartUrl = '/orders/count/minute'
@ -233,12 +236,12 @@ export default {
.then((response) => response.json())
.then((data) => {
if (data.e === 0 ) {
this.fillOrderChart(data.payload);
this.fillBranchOrderChart(data.payload);
}else{
console.log('some kind of connection issue - you might want to get that looked at')
}
});
}, 3000);
}, 10000);
},
getCurrentOrders(){
clearInterval(this.pollingInflight)
@ -261,7 +264,7 @@ export default {
}
})
})
}, 3000);
}, 10000);
},
createOrderLineChart(labels, totals, prevTotals, segment){
this.chartData= {
@ -377,15 +380,6 @@ export default {
};
this.salesChartLoaded = true;
},
orderChartUpdate(segment){
console.log('updating chart to ', segment )
if(this.orderChartSegment != segment){
this.orderChartSegment = segment;
this.getOrderChart(this.orderChartSegment)
}
}
},
@ -397,20 +391,33 @@ export default {
}
},
beforeDestroy() {
clearInterval(this.pollingOrderMetrics);
clearInterval(this.pollingInflight);
clearInterval(this.orderChartInterval);
if (this.$rtl.isRTL) {
this.i18n.locale = "en";
this.$rtl.disableRTL();
}
},
created() {
document.title = process.env.VUE_APP_SITE_TITLE
this.getOrderChart();
this.getAccountingOrderMetrics();
this.getCurrentOrders();
var corpVal = JSON.stringify(process.env.VUE_APP_IS_CORP)
console.log(`corp to string is ${corpVal}`)
console.log()
if (process.env.VUE_APP_IS_CORP === true || process.env.VUE_APP_IS_CORP === 'true'){
this.isCorp = true;
this.getAccountingOrderMetrics();
this.cardClass="card-trans-corp"
}
else{
this.isCorp = false;
this.getOrderChart();
this.getAccountingOrderMetrics();
this.getCurrentOrders();
this.cardClass="card-trans-branch"
}
},
};
</script>

Просмотреть файл

@ -2,7 +2,9 @@ const webpack = require("webpack");
const fetch = require("node-fetch");
const Dotenv = require("dotenv-webpack");
const IS_CORP = (process.env.VUE_APP_IS_CORP || false);
const IS_CORP_TMP = (process.env.VUE_APP_IS_CORP || false);
const IS_CORP = JSON.stringify(IS_CORP_TMP);
const STORE_ID = (process.env.VUE_APP_STORE_ID || "Redmond");
const SITE_TYPE = (process.env.VUE_APP_SITE_TYPE || "Pharmacy");
const SITE_TITLE = (process.env.VUE_APP_SITE_TITLE || "Contoso :: Pharmacy & Convenience Store");
@ -19,16 +21,12 @@ let variables = {
}
let MAKELINE_SERVICE = MAKELINE_BASE_URL + "/orders/" + STORE_ID
let ACCOUNTING_SERVICE = ACCOUNTING_BASE_URL
if (process.env.NODE_ENV === "production"){
console.log("setting PROD environment variables")
// MAKELINE_SERVICE = "http://0.0.0.0:3500/v1.0/invoke/make-line-service/method/orders/" + STORE_ID
// ACCOUNTING_SERVICE = "http://0.0.0.0:3500/v1.0/invoke/accounting-service/method/"
}else{
console.log("setting DEV environment variables")
// console.log(MAKELINE_SERVICE)
// console.log(ACCOUNTING_SERVICE)
}
@ -88,8 +86,12 @@ module.exports = {
})
app.get("/orders/metrics", (req, res)=>{
fetch(ACCOUNTING_SERVICE + "/OrderMetrics?StoreId=" + STORE_ID)
var metricsUrl = ACCOUNTING_BASE_URL + "/OrderMetrics?StoreId=" + STORE_ID
if(IS_CORP === true || IS_CORP === "true"){
metricsUrl = ACCOUNTING_BASE_URL + "/OrderMetrics"
}
fetch(metricsUrl)
.then(response => response.json())
.then(data => {
res.json({e: 0, payload:data}).status(200)
@ -103,7 +105,7 @@ module.exports = {
app.get("/orders/count/minute", (req, res)=>{
fetch(ACCOUNTING_SERVICE + "/Orders/Minute/PT20M?StoreId=" + STORE_ID)
fetch(ACCOUNTING_BASE_URL + "/Orders/Minute/PT20M?StoreId=" + STORE_ID)
.then(response => response.json())
.then(data => {
res.json({e: 0, payload:data}).status(200)
@ -114,35 +116,6 @@ module.exports = {
})
})
// app.get("/orders/count/hour", (req, res)=>{
// fetch(ACCOUNTING_SERVICE + "Orders/Hour/P1D?StoreId=" + STORE_ID)
// .then(response => response.json())
// .then(data => {
// res.json({e: 0, payload:data}).status(200)
// })
// .catch(error=>{
// console.log("error", error)
// res.json({e: -1, payload: {}})
// })
// })
// app.get("/orders/count/day", (req, res)=>{
// fetch(ACCOUNTING_SERVICE + "Orders/Day/P2D?StoreId=" + STORE_ID)
// .then(response => response.json())
// .then(data => {
// res.json({e: 0, payload:data}).status(200)
// })
// .catch(error=>{
// console.log("error", error)
// res.json({e: -1, payload: {}})
// })
// })
}
// proxy: devUrls,

Просмотреть файл

@ -3,12 +3,22 @@
### Order metrics
GET http://{{accounting-service}}/OrderMetrics?StoreId=Redmond
http://20.81.98.23/v1.0/invoke/accounting-service/method/OrderMetrics?StoreId=NYC
### Order Counts over the last 20 minutes
http://{{accounting-service}}/Orders/Minute/PT20M?StoreId=Redmond
### NYC Brian
http://20.81.98.23/OrderMetrics?StoreId=NYC
# /20.81.98.23
### Corp Brian
http://20.81.98.23/OrderMetrics
### Remote Order Counts
http://austin.accounting.brianredmond.io/Orders/Minute/PT20M?StoreId=Austin
@ -21,6 +31,8 @@ http://{{accounting-service}}/Orders/Hour/PT72H?StoreId=Redmond
http://{{accounting-service}}/Orders/Day/P14D?StoreId=Redmond
### Brian's Austin
http://austin.accounting.brianredmond.io/OrderMetrics