Jelajahi Sumber

Inbound / Delivered

Alan Colon 7 tahun lalu
induk
melakukan
2fefaaf2bb

+ 54 - 13
app/components/dashboard-page.js

@@ -3,37 +3,76 @@ const app = require('../app')
 app.component('appDashboardPage', {
   template: html`
     <app-user-area title-text="Dashboard">
+
       <div flex layout="row">
         <md-card flex ng-repeat="serviceCategory in $ctrl.statistics.serviceCategories">
           <md-card-content>
             <h2>{{::$ctrl.serviceCategories[serviceCategory.serviceCategory].name}}</h2>
+            <div flex layout="row">
+              <md-card flex="30" md-whiteframe="15">
+                <md-card-title><md-card-title-text style="text-align: center">
+                  Cartons
+                </md-card-title-text></md-card-title>
+                <md-card-content>
+                  <h1 style="text-align: center">{{serviceCategory.lastMetrics[serviceCategory.serviceColumn]}}</h1>
+                </md-card-content>
+              </md-card>
+              <md-card flex="30" md-whiteframe="15">
+                <md-card-title><md-card-title-text style="text-align: center">
+                  Labor Cost
+                </md-card-title-text></md-card-title>
+                <md-card-content>
+                  <h1 ng-if="serviceCategory.lastMetrics.laborCost" style="text-align: center">$\{{Math.floor(serviceCategory.lastMetrics.laborCost) || null}}</h1>
+                </md-card-content>
+              </md-card>
+              <md-card flex="30" md-whiteframe="15">
+                <md-card-title><md-card-title-text style="text-align: center">
+                  Cost per Carton
+                </md-card-title-text></md-card-title>
+                <md-card-content>
+                  <h1 style="text-align: center">{{serviceCategory.lastMetrics.costPer | currency}}</h1>
+                </md-card-content>
+              </md-card>
+            </div>            
             <div ng-repeat="terminal in serviceCategory.terminals">
-              <h3>{{::$ctrl.terminals[terminal.terminal].name}}</h3>
               <table md-table>
+                <thead>
+                  <th align="left">
+                    <h3>{{::$ctrl.terminals[terminal.terminal].name}}</h3>
+                  </th>
+                  <th align="right">
+                    <span ng-if="$index == 0">Cost per Carton</span>
+                  </th>
+                </thead>
                 <tbody>
                   <tr ng-repeat="laborCategory in terminal.laborCategories">
                     <th align="left">{{::$ctrl.laborCategories[laborCategory.laborCategory].name}}</th>
-                    <td align="right">{{laborCategory.costPerCarton | currency}}</td>
+                    <td align="right">{{laborCategory.costPer | currency}}</td>
                   </tr>
                   <tr ng-if="terminal.laborCategories.length > 1">
                     <th align="left">All</th>
-                    <td align="right">{{terminal.costPerCarton | currency}}</td>
+                    <td align="right">{{terminal.costPer | currency}}</td>
                   </tr>
                 </tbody>
               </table>
               <hr />
             </div>
             <div ng-if="serviceCategory.terminals.length > 1">
-              <h3>Overall</h3>
               <table md-table>
+                <thead>
+                  <th align="left">
+                    <h3>Overall</h3>
+                  </th>
+                  <th align="right"></th>
+                </thead>
                 <tbody>
                   <tr ng-repeat="laborCategory in serviceCategory.laborCategories">
                     <th align="left">{{::$ctrl.laborCategories[laborCategory.laborCategory].name}}</th>
-                    <td align="right">{{laborCategory.costPerCarton | currency}}</td>
+                    <td align="right">{{laborCategory.costPer | currency}}</td>
                   </tr>
                   <tr ng-if="serviceCategory.laborCategories.length > 1">
                     <th align="left">All</th>
-                    <td align="right">{{serviceCategory.costPerCarton | currency}}</td>
+                    <td align="right">{{serviceCategory.costPer | currency}}</td>
                   </tr>
                 </tbody>
               </table>
@@ -64,29 +103,31 @@ app.component('appDashboardPage', {
         <md-card flex="50" class="mg-margin md-padding">
           <md-card-title>
             <md-card-title-text>
-              <span class="md-headline">Efficiency</span>
+              <span class="md-headline">Cost per Carton</span>
               <span class="md-subhead"></span>
             </md-card-title-text>
           </md-card-title>
           <md-card-content>
             <div style="width: 520px; height: 260px;" >
-              <canvas class="chart chart-line" chart-data="$ctrl.charts.efficiency.data" chart-colors="$ctrl.charts.efficiency.colors"
-                chart-labels="$ctrl.charts.efficiency.labels" chart-series="$ctrl.charts.efficiency.series" chart-options="$ctrl.charts.efficiency.options">
+              <canvas class="chart chart-line" chart-data="$ctrl.charts.costPerCarton.data" chart-colors="$ctrl.charts.costPerCarton.colors"
+                chart-labels="$ctrl.charts.costPerCarton.labels" chart-series="$ctrl.charts.costPerCarton.series" chart-options="$ctrl.charts.costPerCarton.options">
               </canvas>
             </div>
-            <div ng-repeat="color in $ctrl.charts.efficiency.colors">
+            <div ng-repeat="color in $ctrl.charts.costPerCarton.colors">
               <div style="background-color: {{color}}; width: 16px; height: 16px; display: inline-block; margin: 0 4px;"></div>
-              {{$ctrl.charts.efficiency.series[$index]}}
+              {{$ctrl.charts.costPerCarton.series[$index]}}
             </div>
           </md-card-content>
         </md-card>
       </div>
     </app-user-area>
   `,
-  controller: function(api, statistics) {
+  controller: function(api, statistics, $scope) {
+    $scope.Math = Math
     api.statistics().then(stats => {
       this.statistics = stats
       this.charts = statistics.charts(stats.metricsOverTime)
+      this.last = stats.last
     })
     api.terminals().then(terminals => {
       this.terminals = terminals
@@ -98,7 +139,7 @@ app.component('appDashboardPage', {
       this.serviceCategories = serviceCategories
     })
 
-  //   statistics.efficiency().then(statistics => {
+  //   statistics.costPerCarton().then(statistics => {
   //     Object.assign(this, statistics)
   //   })
   }

+ 2 - 2
app/services/statistics.js

@@ -84,10 +84,10 @@ app.service('statistics', function(api, $mdColors) {
         labelsField: 'date',
         format: (label, value) => `${label}: ${value.toLocaleString()} cartons`
       }),
-      efficiency: chart({
+      costPerCarton: chart({
         rows,
         seriesField: 'key',
-        dataField: 'efficiency',
+        dataField: 'costPerCarton',
         labelsField: 'date',
         format: (label, value) => `${label}: $${value.toFixed(2)} per carton`
       }),

+ 1 - 0
auto-crud/service-category.js

@@ -14,6 +14,7 @@ register({
       type: Sequelize.STRING,
       unique: true
     },
+    serviceColumn: Sequelize.STRING,
     displayOrder: Sequelize.INTEGER
   },
   options: {

+ 1 - 1
auto-crud/service.js

@@ -13,7 +13,7 @@ register({
     clientId: Sequelize.UUID,
     date: Sequelize.DATEONLY, // Copy from Workday
     delivered: Sequelize.INTEGER,
-    scanned: Sequelize.INTEGER
+    inbound: Sequelize.INTEGER
   },
   options: {
     paranoid: true,

+ 6 - 6
lib/controllers/services.js

@@ -29,7 +29,7 @@ const list = async (req, res) => {
       
       days = _.range(0, 7).map(d => days[d] || null)
         .map(day => day && {
-          cartons: day.cartons
+          inbound: day.inbound
         })
 
       return {
@@ -102,7 +102,7 @@ const get = async (req, res) => {
       .map(sm => serviceByClient[sm.id] || {clientId: sm.id})
       .map(sm => ({
         clientId: sm.clientId,
-        cartons: sm.cartons || null
+        inbound: sm.inbound || null
       }))
     // Restore any staffMembers that are no longer assigned this terminal
     services.forEach(l => {
@@ -196,17 +196,17 @@ const patch = async (req, res) => {
       workday.services.forEach(service => {
         const modelService = modelServicesById[service.clientId]
 
-        service.cartons = modelService.cartons || 0
+        service.inbound = modelService.inbound || 0
       })
 
-      workday.cartons = workday.services.map(l => l.cartons).reduce((a, b) => a + b, 0)
+      workday.inbound = workday.services.map(l => l.inbound).reduce((a, b) => a + b, 0)
       
-      if (workday.cartons || !workday.isNewRecord) {
+      if (workday.inbound || !workday.isNewRecord) {
         await workday.save({ transaction })
       }
 
       await Promise.all(workday.services.map(async service => {
-        if (service.cartons || !service.isNewRecord) {
+        if (service.inbound || !service.isNewRecord) {
           await service.save({ transaction })
         }
       }))

+ 104 - 28
lib/controllers/statistics.js

@@ -1,5 +1,5 @@
 const _ = require('lodash')
-const { Terminal } = require('../database')
+const { Terminal, ServiceCategory } = require('../database')
 const sequelize = require('../database/sequelize')
 const { sanitize } = require('@alancnet/material-framework/lib/util')
 
@@ -13,8 +13,8 @@ const get = async (req, res) => {
       loc.key,
       wd.date,
       wd.laborCost,
-      SUM(distinct svc.cartons) as cartons,
-      cast(SUM(distinct wd.laborCost) as double) / SUM(distinct svc.cartons) as efficiency
+      SUM(distinct svc.inbound) as inbound,
+      cast(SUM(distinct wd.laborCost) as double) / SUM(distinct svc.inbound) as costPerCarton
     FROM workdays wd
     JOIN terminals loc on wd.terminalId = loc.id
     LEFT JOIN services svc on svc.workdayId = wd.id
@@ -24,15 +24,15 @@ const get = async (req, res) => {
     replacements: { terminalIds }
   })
 
-  const metricsSql = `
+  const metricsSql = (serviceColumn) => `
     select
         terminals.key as terminal,
         serviceCategories.key as serviceCategory,
         serviceCategories.displayOrder,
         laborCategories.key as laborCategory,
         sum(distinct labors.laborCost) as laborCost,
-        sum(distinct services.cartons) as cartons,
-        cast(sum(distinct labors.laborCost) as double) / sum(distinct services.cartons) as costPerCarton
+        sum(distinct services.${serviceColumn}) as ${serviceColumn},
+        cast(sum(distinct labors.laborCost) as double) / sum(distinct services.${serviceColumn}) as costPer
 
     -- Start with Terminals
     from terminals
@@ -49,62 +49,138 @@ const get = async (req, res) => {
     -- Link to ServiceCategory
     join laborServiceCategories on laborServiceCategories.laborCategoryId = laborCategories.id
     join serviceCategories on laborServiceCategories.serviceCategoryId = serviceCategories.id
+
+    where terminals.id in(:terminalIds)
+    and serviceCategories.key = :serviceCategory
     group by terminals.key, serviceCategories.key, laborCategories.key
   `
 
-  const metricsPerServiceCategorySql = `
+  const metricsPerServiceCategorySql = (serviceColumn) => `
     select
       serviceCategory,
       displayOrder,
-      cast(sum(distinct laborCost) as double) / sum(distinct cartons) as costPerCarton
-    from (${metricsSql})
+      cast(sum(distinct laborCost) as double) / sum(distinct ${serviceColumn}) as costPer
+    from (${metricsSql(serviceColumn)})
     group by serviceCategory
     order by displayOrder
   `
 
-  const metricsPerLaborCategorySql = `
+  const metricsPerLaborCategorySql = (serviceColumn) => `
     select
       serviceCategory,
       laborCategory,
-      cast(sum(distinct laborCost) as double) / sum(distinct cartons) as costPerCarton
-    from (${metricsSql})
+      cast(sum(distinct laborCost) as double) / sum(distinct ${serviceColumn}) as costPer
+    from (${metricsSql(serviceColumn)})
     group by laborCategory
   `
 
-  const metricsPerTerminalAndServiceCategorySql = `
+  const metricsPerTerminalAndServiceCategorySql = (serviceColumn) => `
     select
       terminal,
       serviceCategory,
-      cast(sum(distinct laborCost) as double) / sum(distinct cartons) as costPerCarton
-    from (${metricsSql})
+      cast(sum(distinct laborCost) as double) / sum(distinct ${serviceColumn}) as costPer
+    from (${metricsSql(serviceColumn)})
     group by terminal, serviceCategory
   `
 
-  const metricsPerTerminalAndLaborCategorySql = `
+  const metricsPerTerminalAndLaborCategorySql = (serviceColumn) => `
     select
       terminal,
       serviceCategory,
       laborCategory,
-      cast(sum(distinct laborCost) as double) / sum(distinct cartons) as costPerCarton
-    from (${metricsSql})
+      cast(sum(distinct laborCost) as double) / sum(distinct ${serviceColumn}) as costPer
+    from (${metricsSql(serviceColumn)})
     group by terminal, laborCategory
   `
-  const metricsPerServiceCategory = await sanitize(req, (await sequelize.query(metricsPerServiceCategorySql))[0])
-  const metricsPerLaborCategory = await sanitize(req, (await sequelize.query(metricsPerLaborCategorySql))[0])
-  const metricsPerTerminalAndServiceCategory = await sanitize(req, (await sequelize.query(metricsPerTerminalAndServiceCategorySql))[0])
-  const metricsPerTerminalAndLaborCategory = await sanitize(req, (await sequelize.query(metricsPerTerminalAndLaborCategorySql))[0])
+
+  const lastMetricsSql = (serviceColumn) => `
+      select
+        workdays.date,
+        terminals.key as terminal,
+        sum(distinct labors.laborCost) as laborCost,
+        sum(distinct services.${serviceColumn}) as ${serviceColumn},
+        cast(sum(distinct labors.laborCost) as double) / sum(distinct services.${serviceColumn}) as costPer
+
+      -- Start with Terminals
+      from terminals
+
+      -- Link to labor cost
+      join workdays on workdays.terminalId = terminals.id
+      join services on services.workdayId = workdays.id
+      join labors on labors.workdayId = workdays.id
+
+      -- Link to LaborCategory
+      join staffMembers on labors.staffMemberId = staffMembers.id
+      join laborCategories on staffMembers.laborCategoryId = laborCategories.id
+
+      -- Link to ServiceCategory
+      join laborServiceCategories on laborServiceCategories.laborCategoryId = laborCategories.id
+      join serviceCategories on laborServiceCategories.serviceCategoryId = serviceCategories.id
+
+      where workdays.date = (select max(date) from workdays where laborCost > 0)
+      and serviceCategories.key = :serviceCategory
+      and terminals.id in(:terminalIds)
+
+  `
+
+
+  const serviceCategories = (await ServiceCategory.findAll())
+  //const opts = {replacements: {serviceCategory: sc.key, terminalIds}}
+  const ret = {
+    serviceCategories: await Promise.all(
+      (await ServiceCategory.findAll()).map(async sc => {
+        const opts = {
+          replacements: {
+            terminalIds,
+            serviceCategory: sc.key
+          }
+        }
+        const [mpsc] = await sequelize.query(metricsPerServiceCategorySql(sc.serviceColumn), opts)
+        const [mplc] = await sequelize.query(metricsPerLaborCategorySql(sc.serviceColumn), opts)
+        const [mptsc] = await sequelize.query(metricsPerTerminalAndServiceCategorySql(sc.serviceColumn), opts)
+        const [mptlc] = await sequelize.query(metricsPerTerminalAndLaborCategorySql(sc.serviceColumn), opts)
+        const [lastMetrics] = await sequelize.query(lastMetricsSql(sc.serviceColumn), opts)
+        return {
+          ...mpsc[0],
+          key: sc.key,
+          serviceColumn: sc.serviceColumn,
+          displayOrder: sc.displayOrder,
+          laborCategories: mplc,
+          terminals: mptsc.map(t => ({
+            ...t,
+            key: t.terminal,
+            laborCategories: mptlc.filter(l => l.terminal === t.terminal)
+          })),
+          lastMetrics: lastMetrics[0]
+        }
+      })
+    ),
+    metricsOverTime
+  }
+
+  return res.status(200).send(ret)
+
+  
+  const queries = await Promise.all(serviceCategories.map(async sc => ({
+    metricsPerServiceCategory: await sanitize(req, (await sequelize.query(metricsPerServiceCategorySql(sc.serviceColumn), {replacements: {serviceCategory: sc.key, terminalIds}}))[0]),
+    metricsPerLaborCategory: await sanitize(req, (await sequelize.query(metricsPerLaborCategorySql(sc.serviceColumn), {replacements: {serviceCategory: sc.key, terminalIds}}))[0]),
+    metricsPerTerminalAndServiceCategory: await sanitize(req, (await sequelize.query(metricsPerTerminalAndServiceCategorySql(sc.serviceColumn), {replacements: {serviceCategory: sc.key, terminalIds}}))[0]),
+    metricsPerTerminalAndLaborCategory: await sanitize(req, (await sequelize.query(metricsPerTerminalAndLaborCategorySql(sc.serviceColumn), {replacements: {serviceCategory: sc.key, terminalIds}}))[0]),
+    lastMetrics: await sanitize(req, (await sequelize.query(lastMetricsSql(sc.serviceColumn), {replacements: {serviceCategory: sc.key, terminalIds}}))[0])
+
+  })))
 
   const results = {
-    serviceCategories: metricsPerServiceCategory.map(mpsc => Object.assign(mpsc, {
-      laborCategories: metricsPerLaborCategory.filter(mplc => mplc.serviceCategory === mpsc.serviceCategory),
-      terminals: metricsPerTerminalAndServiceCategory.filter(mpt => mpt.serviceCategory === mpsc.serviceCategory).map(mpt => Object.assign(mpt, {
-        laborCategories: metricsPerTerminalAndLaborCategory.filter(mptlc => mptlc.serviceCategory === mpsc.serviceCategory && mptlc.terminal === mpt.terminal)
-      }))
+    serviceCategories: serviceCategories.map((sc, i) => Object.assign(queries[i].metricsPerServiceCategory[0], {
+      laborCategories: queries[i].metricsPerLaborCategory.filter(mplc => mplc.serviceCategory === sc.key),
+      terminals: queries[i].metricsPerTerminalAndServiceCategory.filter(mpt => mpt.serviceCategory === sc.key).map(mpt => Object.assign(mpt, {
+        laborCategories: queries[i].metricsPerTerminalAndLaborCategory.filter(mptlc => mptlc.serviceCategory === sc.key && mptlc.terminal === mpt.terminal)
+      })),
+      lastMetrics: queries[i].lastMetrics
     })),
     metricsOverTime
   }
   res.status(200).send(results)
-
 }
 
 module.exports = {

+ 15 - 0
lib/database/migrations/1.05-inbound-delivered.js

@@ -0,0 +1,15 @@
+module.exports = {
+  version: 1.05,
+  name: 'Inbound / Delivered',
+  description: 'Removes `cartons`, refactors `scanned` to `inbound`.',
+  up: async (queryInterface, Sequelize) => {
+    await queryInterface.renameColumn('services', 'scanned', 'inbound')
+    await queryInterface.renameColumn('workdays', 'cartons', 'inbound')
+    await queryInterface.addColumn('workdays', 'delivered', Sequelize.INTEGER)
+    await queryInterface.sequelize.query(`
+      update services set inbound = cartons where cartons > 0
+    `)
+    await queryInterface.removeColumn('services', 'cartons')
+    return 1.05
+  }
+}

+ 2 - 1
lib/database/migrations/index.js

@@ -4,7 +4,8 @@ module.exports = Object.assign(migrations, {
     require('./1.01-location-to-terminal.js'),
     require('./1.02-retailer-to-client.js'),
     require('./1.03-service-categories.js'),
-    require('./1.04-labor-hours.js')
+    require('./1.04-labor-hours.js'),
+    require('./1.05-inbound-delivered.js')
   ]
 })
 

+ 14 - 14
lib/database/seeds/dev.js

@@ -220,25 +220,25 @@ const users = async (db) => {
 
   const lasServices = {
     workdays: [
-      { services: [ { clientId: las.id, cartons: null } ] },
-      { services: [ { clientId: las.id, cartons: 1000 } ] },
-      { services: [ { clientId: las.id, cartons: 1000 } ] },
-      { services: [ { clientId: las.id, cartons: 1000 } ] },
-      { services: [ { clientId: las.id, cartons: 1000 } ] },
-      { services: [ { clientId: las.id, cartons: 1000 } ] },
-      { services: [ { clientId: las.id, cartons: null } ] }
+      { services: [ { clientId: las.id, inbound: null } ] },
+      { services: [ { clientId: las.id, inbound: 1000 } ] },
+      { services: [ { clientId: las.id, inbound: 1000 } ] },
+      { services: [ { clientId: las.id, inbound: 1000 } ] },
+      { services: [ { clientId: las.id, inbound: 1000 } ] },
+      { services: [ { clientId: las.id, inbound: 1000 } ] },
+      { services: [ { clientId: las.id, inbound: null } ] }
     ]
   }
 
   const laxServices = {
     workdays: [
-      { services: [ { clientId: lax.id, cartons: null } ] },
-      { services: [ { clientId: lax.id, cartons: 1000 } ] },
-      { services: [ { clientId: lax.id, cartons: 1000 } ] },
-      { services: [ { clientId: lax.id, cartons: 1000 } ] },
-      { services: [ { clientId: lax.id, cartons: 1000 } ] },
-      { services: [ { clientId: lax.id, cartons: 1000 } ] },
-      { services: [ { clientId: lax.id, cartons: null } ] }
+      { services: [ { clientId: lax.id, inbound: null } ] },
+      { services: [ { clientId: lax.id, inbound: 1000 } ] },
+      { services: [ { clientId: lax.id, inbound: 1000 } ] },
+      { services: [ { clientId: lax.id, inbound: 1000 } ] },
+      { services: [ { clientId: lax.id, inbound: 1000 } ] },
+      { services: [ { clientId: lax.id, inbound: 1000 } ] },
+      { services: [ { clientId: lax.id, inbound: null } ] }
     ]
   }
 

+ 2 - 0
lib/database/seeds/prod.js

@@ -48,6 +48,7 @@ const serviceCategories = [
       'ADMIN',
       'OPS'
     ],
+    serviceColumn: 'inbound',
     displayOrder: 1
   },
   {
@@ -56,6 +57,7 @@ const serviceCategories = [
     laborCategories: [
       'DELIVERY'
     ],
+    serviceColumn: 'delivered',
     displayOrder: 2
   }
 ]

+ 1 - 2
lib/database/service.js

@@ -11,8 +11,7 @@ const Service = sequelize.define('service', {
   clientId: Sequelize.UUID,
   date: Sequelize.DATEONLY, // Copy from Workday
   delivered: Sequelize.INTEGER,
-  scanned: Sequelize.INTEGER,
-  cartons: Sequelize.INTEGER
+  inbound: Sequelize.INTEGER
 }, {
   paranoid: true,
   indexes: [

+ 2 - 1
lib/database/workday.js

@@ -14,7 +14,8 @@ const Workday = sequelize.define('workday', {
   overtimeHours: Sequelize.DOUBLE,
   doubletimeHours: Sequelize.DOUBLE,
   laborCost: Sequelize.DECIMAL(19, 4),
-  cartons: Sequelize.INTEGER
+  inbound: Sequelize.INTEGER,
+  delivered: Sequelize.INTEGER
 
 }, {
   paranoid: true,