Merge pull request '#7353 takeout monitor' (!2866) from 7353-takeoutMonitor into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #2866
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
This commit is contained in:
Jorge Penadés 2024-10-04 14:11:10 +00:00
commit 49305f77df
23 changed files with 13 additions and 1564 deletions

View File

@ -1,8 +1,3 @@
export * from './module';
import './main';
import './index/';
import './index/tickets';
import './index/clients';
import './index/orders';
import './index/search-panel';

View File

@ -1,110 +0,0 @@
<vn-crud-model
vn-id="model"
url="SalesMonitors/clientsFilter"
limit="6"
filter="$ctrl.filter"
order="dated DESC, hour DESC"
auto-load="true">
</vn-crud-model>
<vn-horizontal class="header">
<vn-one translate>
Clients on website
</vn-one>
<vn-none>
<vn-icon class="arrow"
icon="keyboard_arrow_up"
vn-tooltip="Minimize/Maximize"
ng-click="$ctrl.main.toggle()">
</vn-icon>
</vn-none>
</vn-horizontal>
<vn-card vn-id="card">
<smart-table
model="model"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)"
disabled-table-filter="true"
disabled-table-order="true"
class="scrollable sm">
<slot-actions>
<vn-horizontal>
<vn-date-picker
class="vn-pa-xs"
label="From"
ng-model="$ctrl.dateFrom"
on-change="$ctrl.addFilterDate()">
</vn-date-picker>
<vn-date-picker
class="vn-pa-xs"
label="To"
ng-model="$ctrl.dateTo"
on-change="$ctrl.addFilterDate()">
</vn-date-picker>
</vn-horizontal>
</slot-actions>
<slot-table>
<table>
<thead>
<tr>
<th field="dated">
<span translate>Date</span>
</th>
<th field="hour">
<span translate>Hour</span>
</th>
<th field="salesPersonFk" class="expendable">
<span translate>Salesperson</span>
</th>
<th field="clientFk">
<span translate>Client</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="visit in model.data track by visit.id">
<td shrink-date>
<span class="chip">
{{::visit.dated | date:'dd/MM/yy'}}
</span>
</td>
<td shrink-date>
<span class="chip">
{{::visit.hour | date: 'HH:mm'}}
</span>
</td>
<td class="shrink expendable">
<span
title="{{::visit.salesPerson}}"
vn-click-stop="workerDescriptor.show($event, visit.salesPersonFk)"
class="link">
{{::visit.salesPerson | dashIfEmpty}}
</span>
</td>
<td>
<span
title="{{::visit.clientName}}"
vn-click-stop="clientDescriptor.show($event, visit.clientFk)"
class="link">
{{::visit.clientName}}
</span>
</td>
</tr>
</tbody>
</table>
</slot-table>
<slot-pagination>
<vn-pagination
model="model"
class="vn-pt-xs"
scroll-selector="vn-monitor-sales-clients smart-table"
scroll-offset="100">
</vn-pagination>
</slot-pagination>
</smart-table>
</vn-card>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>

View File

@ -1,96 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
const date = Date.vnNew();
this.dateFrom = date;
this.dateTo = date;
this.filter = {
where: {
'v.stamp': {
between: this.dateRange()
}
}
};
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'clientFk',
autocomplete: {
url: 'Clients',
showField: 'name',
valueField: 'id'
}
},
{
field: 'salesPersonFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'dated',
searchable: false
},
{
field: 'hour',
searchable: false
}
]
};
}
exprBuilder(param, value) {
switch (param) {
case 'clientFk':
return {[`c.id`]: value};
case 'salesPersonFk':
return {[`c.${param}`]: value};
}
}
dateRange() {
let from = this.dateFrom;
let to = this.dateTo;
if (!from)
from = Date.vnNew();
if (!to)
to = Date.vnNew();
const minHour = new Date(from);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(to);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
addFilterDate() {
this.$.model.filter = {
where: {
'v.stamp': {
between: this.dateRange()
}
}
};
this.$.model.refresh();
}
}
ngModule.vnComponent('vnMonitorSalesClients', {
template: require('./index.html'),
controller: Controller,
require: {
main: '^vnMonitorIndex'
}
});

View File

@ -1,5 +0,0 @@
<vn-horizontal ng-class="{'hidden': $ctrl.isTopPanelHidden}">
<vn-monitor-sales-clients class="vn-mb-sm"></vn-monitor-sales-clients>
<vn-monitor-sales-orders></vn-monitor-sales-orders>
</vn-horizontal>
<vn-monitor-sales-tickets></vn-monitor-sales-tickets>

View File

@ -1,34 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
const isTopPanelHidden = localStorage.getItem('ticketTopPanelHidden');
if (isTopPanelHidden === 'true')
this.isTopPanelHidden = true;
}
toggle() {
const monitor = this.element.querySelector('vn-horizontal');
const isHidden = monitor.classList.contains('hidden');
if (!isHidden) {
monitor.classList.add('hidden');
localStorage.setItem('ticketTopPanelHidden', true);
} else {
monitor.classList.remove('hidden');
localStorage.setItem('ticketTopPanelHidden', false);
}
}
}
ngModule.vnComponent('vnMonitorIndex', {
template: require('./index.html'),
controller: Controller,
require: {
main: '^vnMonitorIndex'
}
});

View File

@ -1,38 +0,0 @@
import './index.js';
describe('Component vnMonitorIndex', () => {
let controller;
let $element;
beforeEach(ngModule('monitor'));
beforeEach(inject(($compile, $rootScope) => {
$element = $compile('<vn-monitor-index></vn-monitor-index>')($rootScope);
controller = $element.controller('vnMonitorIndex');
}));
describe('toggle()', () => {
it('should add the hidden class to the horizontal contaning the panel to hide', () => {
controller.toggle();
const targetElement = $element[0].querySelector('vn-horizontal');
const firstClass = targetElement.classList[0];
const isTopPanelHidden = localStorage.getItem('ticketTopPanelHidden');
expect(firstClass).toEqual('hidden');
expect(isTopPanelHidden).toEqual('true');
});
it('should remove the hidden class to the horizontal contaning the panel to hide', () => {
const targetElement = $element[0].querySelector('vn-horizontal');
targetElement.classList.add('hidden');
controller.toggle();
const firstClass = targetElement.classList[0];
const isTopPanelHidden = localStorage.getItem('ticketTopPanelHidden');
expect(firstClass).toBeUndefined();
expect(isTopPanelHidden).toEqual('false');
});
});
});

View File

@ -1,16 +0,0 @@
Tickets monitor: Monitor de tickets
Clients on website: Clientes activos en la web
Recent order actions: Acciones recientes en pedidos
Search tickets: Buscar tickets
Delete selected elements: Eliminar los elementos seleccionados
All the selected elements will be deleted. Are you sure you want to continue?: Todos los elementos seleccionados serán eliminados. ¿Seguro que quieres continuar?
Component lack: Faltan componentes
Ticket too little: Ticket demasiado pequeño
Minimize/Maximize: Minimizar/Maximizar
Problems: Problemas
Theoretical: Teórica
Practical: Práctica
Preparation: Preparación
Auto-refresh: Auto-refresco
Toggle auto-refresh every 2 minutes: Conmuta el refresco automático cada 2 minutos
Is fragile: Es frágil

View File

@ -1,141 +0,0 @@
<vn-crud-model auto-load="true"
vn-id="model"
url="SalesMonitors/ordersFilter"
limit="6"
order="date_make DESC">
</vn-crud-model>
<vn-horizontal class="header">
<vn-one translate>
Recent order actions
</vn-one>
<vn-none>
<vn-icon
icon="delete"
vn-tooltip="Delete"
ng-if="$ctrl.totalChecked > 0"
ng-click="delete.show()">
</vn-icon>
<vn-icon class="arrow"
icon="keyboard_arrow_up"
vn-tooltip="Minimize/Maximize"
ng-click="$ctrl.main.toggle()">
</vn-icon>
<vn-icon
icon="refresh"
vn-tooltip="Refresh"
ng-click="model.refresh()">
</vn-icon>
</vn-none>
</vn-horizontal>
<vn-card>
<vn-table model="model" class="scrollable">
<vn-thead>
<vn-tr>
<vn-th shrink>
<vn-multi-check
model="model">
</vn-multi-check>
</vn-th>
<vn-th field="date_send" shrink-datetime>Date</vn-th>
<vn-th field="clientFk">Client</vn-th>
<vn-th field="salesPersonFk" shrink>SalesPerson</vn-th>
</vn-tr>
</vn-thead>
<a ng-repeat="order in model.data track by order.id"
class="clickable vn-tbody"
ui-sref="order.card.summary({id: {{::order.id}}})" target="_blank">
<vn-tr>
<vn-td>
<vn-check
ng-model="order.checked"
vn-click-stop>
</vn-check>
</vn-td>
<vn-td>
<span class="chip {{::$ctrl.chipColor(order.date_send)}}">
{{::order.date_send | date: 'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td>
<span
title="{{::order.clientName}}"
vn-click-stop="clientDescriptor.show($event, order.clientFk)"
class="link">
{{::order.clientName}}
</span>
</vn-td>
<vn-td>
<span
title="{{::order.salesPerson}}"
vn-click-stop="workerDescriptor.show($event, order.salesPersonFk)"
class="link">
{{::order.salesPerson | dashIfEmpty}}
</span>
</vn-td>
</vn-tr>
<vn-tr>
<vn-td></vn-td>
<vn-td>
<span>
{{::order.date_make | date: 'dd/MM/yyyy HH:mm'}}
</span>
</vn-td>
<vn-td>
<span title="{{::order.agencyName}}">
{{::order.agencyName | dashIfEmpty}}
</span>
</vn-td>
<vn-td>
{{::order.import | currency: 'EUR':2}}
</vn-td>
</vn-tr>
</a>
</vn-table>
<vn-pagination
model="model"
class="vn-pt-xs"
scroll-selector="vn-monitor-sales-orders vn-table"
scroll-offset="100">
</vn-pagination>
</vn-card>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-contextmenu vn-id="contextmenu" targets="['vn-monitor-sales-orders vn-table']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
ng-click="contextmenu.copyValue()">
Copy value
</vn-item>
</slot-menu>
</vn-contextmenu>
<vn-confirm
vn-id="delete"
on-accept="$ctrl.onDelete()"
question="All the selected elements will be deleted. Are you sure you want to continue?"
message="Delete selected elements">
</vn-confirm>

View File

@ -1,74 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
get checked() {
const rows = this.$.model.data || [];
const checkedRows = [];
for (let row of rows) {
if (row.checked)
checkedRows.push(row.id);
}
return checkedRows;
}
get totalChecked() {
return this.checked.length;
}
onDelete() {
const params = {deletes: this.checked};
const query = `SalesMonitors/deleteOrders`;
this.$http.post(query, params).then(
() => this.$.model.refresh());
}
chipColor(date) {
const today = Date.vnNew();
today.setHours(0, 0, 0, 0);
const orderLanded = new Date(date);
orderLanded.setHours(0, 0, 0, 0);
const difference = today - orderLanded;
if (difference == 0)
return 'warning';
if (difference < 0)
return 'success';
if (difference > 0)
return 'alert';
}
exprBuilder(param, value) {
switch (param) {
case 'date_send':
return {[`o.date_send`]: {
between: this.dateRange(value)}
};
case 'clientFk':
return {[`c.id`]: value};
case 'salesPersonFk':
return {[`c.${param}`]: value};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
}
ngModule.vnComponent('vnMonitorSalesOrders', {
template: require('./index.html'),
controller: Controller,
require: {
main: '^vnMonitorIndex'
}
});

View File

@ -1,50 +0,0 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('Component vnMonitorSalesOrders', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('monitor'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
const $element = angular.element('<vn-monitor-sales-orders></vn-monitor-sales-orders>');
controller = $componentController('vnMonitorSalesOrders', {$element});
controller.$.model = crudModel;
controller.$.model.data = [
{id: 1, name: 'My item 1'},
{id: 2, name: 'My item 2'},
{id: 3, name: 'My item 3'}
];
}));
describe('checked() getter', () => {
it('should return a the selected rows', () => {
const modelData = controller.$.model.data;
modelData[0].checked = true;
modelData[1].checked = true;
const result = controller.checked;
expect(result).toEqual(expect.arrayContaining([1, 2]));
});
});
describe('onDelete()', () => {
it('should make a query and then call to the model refresh() method', () => {
jest.spyOn(controller.$.model, 'refresh');
const modelData = controller.$.model.data;
modelData[0].checked = true;
modelData[1].checked = true;
const expectedParams = {deletes: [1, 2]};
$httpBackend.expect('POST', 'SalesMonitors/deleteOrders', expectedParams).respond(200);
controller.onDelete();
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalledWith();
});
});
});

View File

@ -1,19 +0,0 @@
@import "variables";
vn-monitor-sales-orders {
vn-table.scrollable {
max-height: 350px;
overflow-x: hidden
}
vn-table a.vn-tbody {
& > vn-tr:nth-child(2) {
color: gray;
& > vn-td {
border-bottom: $border;
font-size: 13px;
}
}
}
}

View File

@ -1,120 +0,0 @@
<div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield
vn-one
label="General search"
ng-model="filter.search"
info="Search ticket by id or alias"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-textfield
vn-one
label="Client id"
ng-model="filter.clientFk">
</vn-textfield>
<vn-textfield
vn-one
label="Order id"
ng-model="filter.orderFk">
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-input-number
vn-one
min="0"
step="1"
label="Days onward"
ng-model="filter.scopeDays"
on-change="$ctrl.scopeDays = value"
display-controls="true">
</vn-input-number>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-textfield
vn-one
label="Nickname"
ng-model="filter.nickname">
</vn-textfield>
<vn-worker-autocomplete
vn-one
ng-model="filter.salesPersonFk"
departments="['VT']"
label="Sales person">
</vn-worker-autocomplete>
<vn-textfield
vn-one
label="Invoice"
ng-model="filter.refFk">
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-autocomplete
vn-one
label="Agency"
ng-model="filter.agencyModeFk"
url="AgencyModes/isActive">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="State"
ng-model="filter.stateFk"
url="States">
</vn-autocomplete>
<vn-autocomplete vn-one
data="$ctrl.groupedStates"
label="Grouped States"
value-field="id"
show-field="name"
ng-model="filter.alertLevel">
<tpl-item>
{{name}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-autocomplete
vn-one
label="Warehouse"
ng-model="filter.warehouseFk"
data="warehouses">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Province"
ng-model="filter.provinceFk"
url="Provinces">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-check
vn-one
label="My team"
ng-model="filter.myTeam"
triple-state="true">
</vn-check>
<vn-check
vn-one
label="With problems"
ng-model="filter.problems"
triple-state="true">
</vn-check>
<vn-check
vn-one
label="Pending"
ng-model="filter.pending"
triple-state="true">
</vn-check>
</vn-horizontal>
<vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,59 +0,0 @@
import ngModule from '../../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
constructor($, $element) {
super($, $element);
this.filter = this.$.filter;
this.getGroupedStates();
}
getGroupedStates() {
let groupedStates = [];
this.$http.get('AlertLevels').then(res => {
for (let state of res.data) {
groupedStates.push({
id: state.id,
code: state.code,
name: this.$t(state.code)
});
}
this.groupedStates = groupedStates;
});
}
get from() {
return this._from;
}
set from(value) {
this._from = value;
this.filter.scopeDays = null;
}
get to() {
return this._to;
}
set to(value) {
this._to = value;
this.filter.scopeDays = null;
}
get scopeDays() {
return this._scopeDays;
}
set scopeDays(value) {
this._scopeDays = value;
this.filter.from = null;
this.filter.to = null;
}
}
ngModule.vnComponent('vnMonitorSalesSearchPanel', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,71 +0,0 @@
import './index';
describe('Monitor Component vnMonitorSalesSearchPanel', () => {
let $httpBackend;
let controller;
beforeEach(ngModule('monitor'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnMonitorSalesSearchPanel', {$element: null});
controller.$t = () => {};
controller.filter = {};
}));
describe('getGroupedStates()', () => {
it('should set an array of groupedStates with the adition of a name translation', () => {
jest.spyOn(controller, '$t').mockReturnValue('miCodigo');
const data = [
{
id: 9999,
code: 'myCode'
}
];
$httpBackend.whenGET('AlertLevels').respond(data);
controller.getGroupedStates();
$httpBackend.flush();
expect(controller.groupedStates).toEqual([{
id: 9999,
code: 'myCode',
name: 'miCodigo'
}]);
});
});
describe('from() setter', () => {
it('should clear the scope days when setting the from property', () => {
controller.filter.scopeDays = 1;
controller.from = Date.vnNew();
expect(controller.filter.scopeDays).toBeNull();
expect(controller.from).toBeDefined();
});
});
describe('to() setter', () => {
it('should clear the scope days when setting the to property', () => {
controller.filter.scopeDays = 1;
controller.to = Date.vnNew();
expect(controller.filter.scopeDays).toBeNull();
expect(controller.to).toBeDefined();
});
});
describe('scopeDays() setter', () => {
it('should clear the date range when setting the scopeDays property', () => {
controller.filter.from = Date.vnNew();
controller.filter.to = Date.vnNew();
controller.scopeDays = 1;
expect(controller.filter.from).toBeNull();
expect(controller.filter.to).toBeNull();
expect(controller.scopeDays).toBeDefined();
});
});
});

View File

@ -1,85 +0,0 @@
@import "variables";
@import "effects";
vn-monitor-index {
.header {
padding: 12px 0 5px 0;
color: gray;
font-size: 1.2rem;
border-bottom: $border;
margin-bottom: 10px;
& > vn-none > vn-icon {
@extend %clickable-light;
color: $color-button;
font-size: 1.4rem;
}
vn-none > .arrow {
transition: transform 200ms;
}
}
vn-monitor-sales-clients {
vn-card {
margin-right: 15px;
}
.header {
padding-right: 15px;
& > vn-none > .arrow {
display: none
}
}
}
vn-table.scrollable {
height: 300px
}
vn-horizontal {
flex-wrap: wrap
}
.hidden {
vn-card {
display: none
}
.header > vn-none > .arrow {
transform: rotate(180deg);
}
}
}
@media (max-width:1150px) {
vn-monitor-index {
& > vn-horizontal {
flex-direction: column;
vn-monitor-sales-clients,
vn-monitor-sales-orders {
width: 100%
}
vn-monitor-sales-clients {
margin-bottom: 15px
}
}
vn-monitor-sales-clients {
vn-card {
margin-right: 0
}
.header {
padding-right: 0;
& > vn-none > .arrow {
display: inline-block
}
}
}
}
}

View File

@ -1,270 +0,0 @@
<vn-crud-model
vn-id="model"
url="SalesMonitors/salesFilter"
auto-load="false"
limit="20">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-monitor-sales-search-panel"
placeholder="Search tickets"
info="Search ticket by id or alias"
model="model"
fetch-params="$ctrl.fetchParams($params)"
suggested-filter="$ctrl.filterParams"
auto-state="false">
</vn-searchbar>
</vn-portal>
<vn-horizontal class="header">
<vn-one translate>
Tickets monitor
</vn-one>
</vn-horizontal>
<vn-card>
<smart-table
model="model"
view-config-id="ticketsMonitor"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions>
<vn-check
label="Auto-refresh"
vn-tooltip="Toggle auto-refresh every 2 minutes"
on-change="$ctrl.autoRefresh(value)">
</vn-check>
</slot-actions>
<slot-table>
<table>
<thead>
<tr>
<th field="totalProblems" menu-enabled="false">
<span translate>Problems</span>
</th>
<th field="id">
<span translate>Identifier</span>
</th>
<th field="nickname">
<span translate>Client</span>
</th>
<th field="salesPersonFk">
<span translate>Salesperson</span>
</th>
<th field="shippedDate" shrink-date filter-enabled="false">
<span translate>Date</span>
</th>
<th field="theoreticalHour" filter-enabled="false">
<span translate>Theoretical</span>
</th>
<th field="practicalHour" filter-enabled="false">
<span translate>Practical</span>
</th>
<th field="preparationHour" filter-enabled="false">
<span translate>Preparation</span>
</th>
<th field="provinceFk">
<span translate>Province</span>
</th>
<th field="stateFk">
<span translate>State</span>
</th>
<th field="isFragile" number>
<span translate>Fragile</span>
</th>
<th field="zoneFk">
<span translate>Zone</span>
</th>
<th field="totalWithVat" shrink>
<span translate>Total</span>
</th>
<th shrink></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ticket in model.data track by ticket.id"
vn-anchor="{
state: 'ticket.card.summary',
params: {id: ticket.id},
target: '_blank'
}">
<td>
<vn-icon
ng-show="ticket.isTaxDataChecked === 0"
translate-attr="{title: 'No verified data'}"
class="bright"
icon="icon-no036">
</vn-icon>
<vn-icon
ng-show="ticket.hasTicketRequest"
translate-attr="{title: 'Purchase request'}"
class="bright"
icon="icon-buyrequest">
</vn-icon>
<vn-icon
ng-show="ticket.itemShortage"
translate-attr="{title: 'Not visible'}"
class="bright"
icon="icon-unavailable">
</vn-icon>
<vn-icon
ng-show="ticket.isFreezed"
translate-attr="{title: 'Client frozen'}"
class="bright"
icon="icon-frozen">
</vn-icon>
<vn-icon
ng-show="ticket.risk"
ng-class="{'highRisk': ticket.hasHighRisk}"
title="{{$ctrl.$t('Risk')}}: {{ticket.risk}}"
class="bright"
icon="icon-risk">
</vn-icon>
<vn-icon
ng-show="ticket.hasComponentLack"
translate-attr="{title: 'Component lack'}"
class="bright"
icon="icon-components">
</vn-icon>
<vn-icon
ng-show="ticket.isTooLittle"
translate-attr="{title: 'Ticket too little'}"
class="bright"
icon="icon-isTooLittle">
</vn-icon>
</td>
<td>
<span
vn-click-stop="ticketDescriptor.show($event, ticket.id)"
class="link">
{{ticket.id}}
</span>
</td>
<td name="nickname">
<span
title="{{ticket.nickname}}"
vn-click-stop="clientDescriptor.show($event, ticket.clientFk)"
class="link">
{{ticket.nickname}}
</span>
</td>
<td>
<span
title="{{ticket.userName}}"
vn-click-stop="workerDescriptor.show($event, ticket.salesPersonFk)"
class="link">
{{ticket.userName | dashIfEmpty}}
</span>
</td>
<td>
<span class="chip {{$ctrl.compareDate(ticket.shippedDate)}}">
{{ticket.shippedDate | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{ticket.zoneLanding | date: 'HH:mm'}}</td>
<td>{{ticket.practicalHour | date: 'HH:mm'}}</td>
<td>{{ticket.shipped | date: 'HH:mm'}}</td>
<td>{{ticket.province}}</td>
<td>
<span
ng-show="ticket.refFk"
title="{{ticket.refFk}}"
vn-click-stop="invoiceOutDescriptor.show($event, ticket.invoiceOutId)"
class="link">
{{ticket.refFk}}
</span>
<span
ng-show="!ticket.refFk"
class="chip {{ticket.classColor}}">
{{ticket.state}}
</span>
</td>
<td number>
<vn-icon
ng-show="ticket.isFragile"
translate-attr="{title: 'Is fragile'}"
class="bright"
icon="local_bar">
</vn-icon>
</td>
<td name="zone">
<span
title="{{ticket.zoneName}}"
vn-click-stop="zoneDescriptor.show($event, ticket.zoneFk)"
class="link">
{{ticket.zoneName | dashIfEmpty}}
</span>
</td>
<td number>
<span class="chip {{$ctrl.totalPriceColor(ticket)}}">
{{(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
</span>
</td>
<td actions>
<vn-icon-button
vn-anchor="{
state: 'ticket.card.sale',
params: {id: ticket.id},
target: '_blank'
}"
vn-tooltip="Go to lines"
icon="icon-lines">
</vn-icon-button>
<vn-icon-button
vn-click-stop="$ctrl.preview(ticket)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-zone-descriptor-popover
vn-id="zoneDescriptor">
</vn-zone-descriptor-popover>
<vn-popup vn-id="summary">
<vn-ticket-summary
ticket="$ctrl.selectedTicket"
model="model">
</vn-ticket-summary>
</vn-popup>
<vn-contextmenu vn-id="contextmenu" targets="['vn-monitor-sales-tickets smart-table']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
ng-click="contextmenu.copyValue()">
Copy value
</vn-item>
</slot-menu>
</vn-contextmenu>

View File

@ -1,178 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filterParams = this.fetchParams();
this.smartTableOptions = {
activeButtons: {
search: true,
shownColumns: true,
},
columns: [
{
field: 'totalProblems',
searchable: false
},
{
field: 'salesPersonFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'provinceFk',
autocomplete: {
url: 'Provinces',
}
},
{
field: 'stateFk',
autocomplete: {
url: 'States',
}
},
{
field: 'zoneFk',
autocomplete: {
url: 'Zones',
}
},
{
field: 'warehouseFk',
autocomplete: {
url: 'Warehouses',
}
},
{
field: 'shippedDate',
datepicker: true
},
{
field: 'theoreticalHour',
searchable: false
},
{
field: 'preparationHour',
searchable: false
}
]
};
}
$onInit() {
if (!this.$params.q) {
this.$.$applyAsync(
() => this.$.model.applyFilter(null, this.filterParams));
}
}
fetchParams($params = {}) {
const excludedParams = [
'search',
'clientFk',
'orderFk',
'refFk',
'scopeDays'
];
const hasExcludedParams = excludedParams.some(param => {
return $params && $params[param] != undefined;
});
const hasParams = Object.entries($params).length;
if (!hasParams || !hasExcludedParams)
$params.scopeDays = 1;
if (typeof $params.scopeDays === 'number') {
const from = Date.vnNew();
from.setHours(0, 0, 0, 0);
const to = new Date(from.getTime());
to.setDate(to.getDate() + $params.scopeDays);
to.setHours(23, 59, 59, 999);
Object.assign($params, {from, to});
}
return $params;
}
compareDate(date) {
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0);
let comparation = today - timeTicket;
if (comparation == 0)
return 'warning';
if (comparation < 0)
return 'success';
}
totalPriceColor(ticket) {
const total = parseInt(ticket.totalWithVat);
if (total > 0 && total < 50)
return 'warning';
}
exprBuilder(param, value) {
switch (param) {
case 'stateFk':
return {'ts.stateFk': value};
case 'salesPersonFk':
return {'c.salesPersonFk': value};
case 'provinceFk':
return {'a.provinceFk': value};
case 'theoreticalHour':
return {'z.hour': value};
case 'practicalHour':
return {'zed.etc': value};
case 'shippedDate':
return {'t.shipped': {
between: this.dateRange(value)}
};
case 'nickname':
return {[`t.nickname`]: {like: `%${value}%`}};
case 'zoneFk':
case 'totalWithVat':
return {[`t.${param}`]: value};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
preview(ticket) {
this.selectedTicket = ticket;
this.$.summary.show();
}
autoRefresh(value) {
if (value)
this.refreshTimer = setInterval(() => this.$.model.refresh(), 120000);
else {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
}
}
ngModule.vnComponent('vnMonitorSalesTickets', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,133 +0,0 @@
import './index.js';
describe('Component vnMonitorSalesTickets', () => {
let controller;
let $window;
let tickets = [{
id: 1,
clientFk: 1,
checked: false,
totalWithVat: 10.5
}, {
id: 2,
clientFk: 1,
checked: true,
totalWithVat: 20.5
}, {
id: 3,
clientFk: 1,
checked: true,
totalWithVat: 30
}];
beforeEach(ngModule('monitor'));
beforeEach(inject(($componentController, _$window_) => {
$window = _$window_;
const $element = angular.element('<vn-monitor-sales-tickets></vn-monitor-sales-tickets>');
controller = $componentController('vnMonitorSalesTickets', {$element});
}));
describe('fetchParams()', () => {
it('should return a range of dates with passed scope days', () => {
let params = controller.fetchParams({
scopeDays: 2
});
const from = Date.vnNew();
from.setHours(0, 0, 0, 0);
const to = new Date(from.getTime());
to.setDate(to.getDate() + params.scopeDays);
to.setHours(23, 59, 59, 999);
const expectedParams = {
from,
scopeDays: params.scopeDays,
to
};
expect(params).toEqual(expectedParams);
});
it('should return default value for scope days', () => {
let params = controller.fetchParams({
scopeDays: 1
});
expect(params.scopeDays).toEqual(1);
});
it('should return the given scope days', () => {
let params = controller.fetchParams({
scopeDays: 2
});
expect(params.scopeDays).toEqual(2);
});
});
describe('compareDate()', () => {
it('should return warning when the date is the present', () => {
let today = Date.vnNew();
let result = controller.compareDate(today);
expect(result).toEqual('warning');
});
it('should return sucess when the date is in the future', () => {
let futureDate = Date.vnNew();
futureDate = futureDate.setDate(futureDate.getDate() + 10);
let result = controller.compareDate(futureDate);
expect(result).toEqual('success');
});
it('should return undefined when the date is in the past', () => {
let pastDate = Date.vnNew();
pastDate = pastDate.setDate(pastDate.getDate() - 10);
let result = controller.compareDate(pastDate);
expect(result).toEqual(undefined);
});
});
describe('totalPriceColor()', () => {
it('should return "warning" when the ticket amount is less than 50€', () => {
const result = controller.totalPriceColor({totalWithVat: '8.50'});
expect(result).toEqual('warning');
});
});
describe('dateRange()', () => {
it('should return two dates with the hours at the start and end of the given date', () => {
const now = Date.vnNew();
const today = now.getDate();
const dateRange = controller.dateRange(now);
const start = dateRange[0].toString();
const end = dateRange[1].toString();
expect(start).toContain(today);
expect(start).toContain('00:00:00');
expect(end).toContain(today);
expect(end).toContain('23:59:59');
});
});
describe('preview()', () => {
it('should show the dialog summary', () => {
controller.$.summary = {show: () => {}};
jest.spyOn(controller.$.summary, 'show');
let event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
controller.preview(event, tickets[0]);
expect(controller.$.summary.show).toHaveBeenCalledWith();
});
});
});

View File

@ -1,47 +0,0 @@
@import "variables";
vn-monitor-sales-tickets {
@media screen and (max-width: 1440px) {
.expendable {
display: none;
}
}
vn-th.icon-field,
vn-th.icon-field *,
vn-td.icon-field,
vn-td.icon-field * {
padding: 0;
max-width: 50px
}
vn-th[field="nickname"],
vn-td[name="nickname"] {
min-width: 250px
}
vn-td.icon-field > vn-icon {
margin-left: 3px;
margin-right: 3px;
}
vn-table.scrollable.lg {
height: 736px
}
tbody tr[ng-repeat]:focus {
background-color: $color-primary-light
}
.highRisk i {
color: $color-alert
}
td[name="nickname"] {
max-width: 200px
}
td[name="zone"] {
max-width: 150px
}
}

View File

@ -1 +0,0 @@
Sales monitor: Monitor de ventas

View File

@ -1,4 +0,0 @@
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -1,7 +1,16 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Monitor extends ModuleMain {}
export default class Monitor extends ModuleMain {
constructor($element, $) {
super($element, $);
}
async $onInit() {
this.$state.go('home');
window.location.href = await this.vnApp.getUrl(`monitor/`);
}
}
ngModule.vnComponent('vnMonitor', {
controller: Monitor,

View File

@ -1,13 +1,9 @@
{
"module": "monitor",
"name": "Monitors",
"icon" : "grid_view",
"dependencies": ["ticket", "worker", "client"],
"validations" : true,
"icon": "grid_view",
"menus": {
"main": [
{"state": "monitor.index", "icon": "grid_view"}
],
"main": [],
"card": []
},
"keybindings": [