Merge branch '2056-entry_descriptor' of verdnatura/salix into dev
gitea/salix/dev This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2020-02-25 10:49:40 +00:00 committed by Gitea
commit 0bf0ed78ee
30 changed files with 537 additions and 41 deletions

View File

@ -1103,11 +1103,11 @@ INSERT INTO `vn`.`annualAverageInvoiced`(`clientFk`, `invoiced`)
(104, 500),
(105, 5000);
INSERT INTO `vn`.`supplier`(`id`, `name`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`)
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`)
VALUES
(1, 'Plants SL', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1),
(2, 'Flower King', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2),
(442, 'Verdnatura Levante SL', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2);
(1, 'Plants SL', 'Plants nick', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1),
(2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2),
(442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2);
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
VALUES

View File

@ -799,5 +799,10 @@ export default {
inflation: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.inflation"]',
volumetric: 'vn-zone-basic-data vn-check[ng-model="$ctrl.zone.isVolumetric"]',
saveButton: 'vn-zone-basic-data vn-submit > button',
},
entrySummary: {
header: 'vn-entry-summary > vn-card > h5',
reference: 'vn-entry-summary vn-label-value[label="Reference"]',
confirmed: 'vn-entry-summary vn-check[label="Confirmed"]',
}
};

View File

@ -0,0 +1,43 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.waitToClick('vn-entry-index vn-tbody > a:nth-child(2)');
});
afterAll(async() => {
await browser.close();
});
it('should reach the second entry summary section', async() => {
let url = await page.expectURL('#!/entry/2/summary');
expect(url).toBe(true);
});
it(`should display details from the entry on the header`, async() => {
await page.waitForTextInElement(selectors.entrySummary.header, 'The king');
const result = await page.waitToGetProperty(selectors.entrySummary.header, 'innerText');
expect(result).toContain('The king');
});
it('should display some entry details like the reference', async() => {
const result = await page.waitToGetProperty(selectors.entrySummary.reference, 'innerText');
expect(result).toContain('Movement 2');
});
it('should display other entry details like the confirmed', async() => {
const result = await page.checkboxState(selectors.entrySummary.confirmed, 'innerText');
expect(result).toContain('unchecked');
});
});

View File

@ -23,6 +23,12 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-net:before {
content: "\e95b";
}
.icon-anonymous:before {
content: "\e95c";
}
.icon-buyrequest:before {
content: "\e914";
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -44,12 +44,6 @@ describe('Client', () => {
});
describe('company setter/getter', () => {
it('should return the company', () => {
controller.companyId = null;
expect(controller._companyId).toEqual(jasmine.any(Object));
});
it('should return the company and then call getData()', () => {
spyOn(controller, 'getData');
controller.companyId = 442;

View File

@ -77,18 +77,15 @@
<tpl-body>
<div>
<h5 style="text-align: center">
<span translate>From date</span>
<span translate>Send consumer report</span>
</h5>
<vn-date-picker
vn-id="from"
vn-one
ng-model="$ctrl.from"
label="From hour"
label="From date"
vn-focus>
</vn-date-picker>
<h5 style="text-align: center">
<span translate>To date</span>
</h5>
<vn-date-picker
vn-id="to"
vn-one

View File

@ -1,2 +1,4 @@
Simple ticket: Ticket simple
Send consumer report: Enviar informe de consumo
From date: Fecha desde
To date: Fecha hasta

View File

@ -68,6 +68,14 @@ module.exports = Self => {
type: 'Number',
description: 'The currency id to filter',
http: {source: 'query'}
}, {
arg: 'from',
type: 'Date',
description: `The from date filter`
}, {
arg: 'to',
type: 'Date',
description: `The to date filter`
}
],
returns: {
@ -91,6 +99,10 @@ module.exports = Self => {
return {[param]: {like: `%${value}%`}};
case 'created':
return {'e.created': {gte: value}};
case 'from':
return {'t.landed': {gte: value}};
case 'to':
return {'t.landed': {lte: value}};
case 'id':
case 'isBooked':
case 'isConfirmed':

View File

@ -0,0 +1,76 @@
module.exports = Self => {
Self.remoteMethod('getEntry', {
description: 'Returns an entry',
accessType: 'READ',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The entry id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getEntry`,
verb: 'GET'
}
});
Self.getEntry = async id => {
let filter = {
where: {id: id},
include: [
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
},
{
relation: 'travel',
scope: {
fields: ['id', 'name', 'shipped', 'landed', 'agencyFk', 'warehouseOutFk', 'warehouseInFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
},
{
relation: 'currency',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
}
],
};
let entry = await Self.app.models.Entry.findOne(filter);
return entry;
};
};

View File

@ -0,0 +1,31 @@
const app = require('vn-loopback/server/server');
describe('travel getEntry()', () => {
const entryId = 1;
it('should check the entry contains the id', async() => {
const entry = await app.models.Entry.getEntry(entryId);
expect(entry.id).toEqual(entryId);
});
it('should check the entry contains the supplier name', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const supplierName = entry.supplier().nickname;
expect(supplierName).toEqual('Plants nick');
});
it('should check the entry contains the receiver warehouse name', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const receiverWarehouseName = entry.travel().warehouseIn().name;
expect(receiverWarehouseName).toEqual('Warehouse One');
});
it('should check the entry contains the company code', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const companyCode = entry.company().code;
expect(companyCode).toEqual('VNL');
});
});

View File

@ -1,4 +1,5 @@
module.exports = Self => {
require('../methods/entry/filter')(Self);
require('../methods/entry/getEntry')(Self);
};

View File

@ -39,6 +39,9 @@
"commission": {
"type": "Number"
},
"isOrdered": {
"type": "Boolean"
},
"created": {
"type": "date"
},

View File

@ -11,11 +11,34 @@ class Controller extends ModuleCard {
fields: ['id', 'code']
}
}, {
relation: 'travel'
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
}, {
relation: 'supplier',
scope: {
fields: ['id', 'name']
fields: ['id', 'nickname']
}
}, {
relation: 'currency'
@ -27,7 +50,7 @@ class Controller extends ModuleCard {
}
}
ngModule.component('vnEntry Card', {
ngModule.component('vnEntryCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -6,16 +6,37 @@
<a translate-attr="{title: 'Preview'}" ui-sref="entry.card.summary({id: $ctrl.entry.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<span></span>
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-icon-menu>
</div>
<div class="body">
<div class="attributes">
<vn-label-value label="Id"
value="{{$ctrl.entry.id}}">
</vn-label-value>
<vn-label-value label="Reference"
value="{{$ctrl.entry.ref}}">
<vn-label-value label="Supplier"
value="{{$ctrl.entry.supplier.nickname}}">
</vn-label-value>
<vn-label-value label="Agency "
value="{{$ctrl.entry.travel.agency.name}}">
</vn-label-value>
<vn-label-value label="Landed"
value="{{$ctrl.entry.travel.landed | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse Out"
value="{{$ctrl.entry.travel.warehouseOut.name}}">
</vn-label-value>
</div>
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
</div>
</div>

View File

@ -1,17 +1,80 @@
import ngModule from '../module';
import Component from 'core/lib/component';
class Controller {
constructor($scope) {
this.$ = $scope;
class Controller extends Component {
constructor($element, $, $httpParamSerializer, vnConfig) {
super($element, $);
this.vnConfig = vnConfig;
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{name: 'Show entry report', callback: this.showEntryReport}
];
}
onMoreChange(callback) {
callback.call(this);
}
get entry() {
return this._entry;
}
set entry(value) {
this._entry = value;
if (!value) return;
const date = value.travel.landed;
let to = new Date(date);
let from = new Date(date);
to.setDate(to.getDate() + 10);
to.setHours(0, 0, 0, 0);
from.setDate(from.getDate() - 10);
from.setHours(0, 0, 0, 0);
let links = {
btnOne: {
icon: 'local_airport',
state: `travel.index({q: '{"agencyFk": ${value.travel.agencyFk}}'})`,
tooltip: 'All travels with current agency'
}};
links.btnTwo = {
icon: 'icon-entry',
state: `entry.index({q: '{"supplierFk": ${value.supplierFk}, "to": "${to}", "from": "${from}"}'})`,
tooltip: 'All entries with current supplier'
};
this._quicklinks = links;
}
get quicklinks() {
return this._quicklinks;
}
set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks);
}
showEntryReport() {
const params = {
clientId: this.vnConfig.storage.currentUserWorkerId,
entryId: this.entry.id
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/entry-order?${serializedParams}`;
window.open(url);
}
}
Controller.$inject = ['$scope'];
Controller.$inject = ['$element', '$scope', '$httpParamSerializer', 'vnConfig'];
ngModule.component('vnEntryDescriptor', {
template: require('./index.html'),
bindings: {
entry: '<'
entry: '<',
quicklinks: '<'
},
require: {
card: '^?vnEntryCard'

View File

@ -0,0 +1,35 @@
import './index.js';
describe('Entry Component vnEntryDescriptor', () => {
let $httpParamSerializer;
let controller;
let $element;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, _$httpParamSerializer_) => {
$httpParamSerializer = _$httpParamSerializer_;
$element = angular.element(`<vn-entry-descriptor></vn-entry-descriptor>`);
controller = $componentController('vnEntryDescriptor', {$element});
controller._entry = {id: 2};
controller.vnConfig.storage = {currentUserWorkerId: 9};
controller.cardReload = ()=> {
return true;
};
}));
describe('showEntryReport()', () => {
it('should open a new window showing a delivery note PDF document', () => {
const params = {
clientId: controller.vnConfig.storage.currentUserWorkerId,
entryId: controller.entry.id
};
const serializedParams = $httpParamSerializer(params);
let expectedPath = `api/report/entry-order?${serializedParams}`;
spyOn(window, 'open');
controller.showEntryReport();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
});

View File

@ -1 +1,4 @@
Reference: Referencia
All travels with current agency: Todos los envios con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual
Show entry report: Ver informe del pedido

View File

@ -5,7 +5,4 @@ import './index/';
import './search-panel';
import './descriptor';
import './card';
// import './summary';
// import './basic-data';
// import './log';
// import './create';
import './summary';

View File

@ -16,7 +16,7 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th shrink></vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="landed" center>Landed</vn-th>
<vn-th>Reference</vn-th>
@ -33,18 +33,18 @@
<a ng-repeat="entry in entries"
class="clickable vn-tr search-result"
ui-sref="entry.card.summary({id: {{::entry.id}}})">
<vn-td>
<vn-td shrink>
<vn-icon
ng-show="entry.isInventory"
class="bright"
vn-tooltip="Inventory entry"
icon="icon-unavailable">
icon="icon-anonymous">
</vn-icon>
<vn-icon
ng-show="entry.isRaid"
class="bright"
vn-tooltip="Virtual entry"
icon="icon-100">
icon="icon-net">
</vn-icon>
</vn-td>
<vn-td number>{{::entry.id}}</vn-td>

View File

@ -28,6 +28,14 @@
"state": "entry.card",
"abstract": true,
"component": "vn-entry-card"
}, {
"url": "/summary",
"state": "entry.card.summary",
"component": "vn-entry-summary",
"description": "Summary",
"params": {
"entry": "$ctrl.entry"
}
}
]
}

View File

@ -54,6 +54,18 @@
ng-model="filter.created">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="From"
ng-model="filter.from">
</vn-date-picker>
<vn-date-picker
vn-one
label="To"
ng-model="filter.to">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-check
vn-one

View File

@ -0,0 +1,68 @@
<vn-card class="summary">
<h5><span translate>Entry</span> #{{$ctrl.entryData.id}} - {{$ctrl.entryData.supplier.nickname}}</h5>
<vn-horizontal>
<vn-one>
<vn-label-value label="Commission"
value="{{$ctrl.entryData.commission}}">
</vn-label-value>
<vn-label-value label="Currency"
value="{{$ctrl.entryData.currency.name}}">
</vn-label-value>
<vn-label-value label="Company"
value="{{$ctrl.entryData.company.code}}">
</vn-label-value>
<vn-label-value label="Reference"
value="{{$ctrl.entryData.ref}}">
</vn-label-value>
<vn-label-value label="Notes"
value="{{$ctrl.entryData.notes}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-label-value label="Agency"
value="{{$ctrl.entryData.travel.agency.name}}">
</vn-label-value>
<vn-label-value label="Shipped"
value="{{$ctrl.entryData.travel.shipped | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse Out"
value="{{$ctrl.entryData.travel.warehouseOut.name}}">
</vn-label-value>
<vn-label-value label="Landed"
value="{{$ctrl.entryData.travel.landed | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse In"
value="{{$ctrl.entryData.travel.warehouseIn.name}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-vertical>
<vn-check
label="Ordered"
value="{{$ctrl.entryData.isOrdered}}"
disabled="true">
</vn-check>
<vn-check
label="Confirmed"
value="{{$ctrl.entryData.isConfirmed}}"
disabled="true">
</vn-check>
<vn-check
label="Booked"
value="{{$ctrl.entryData.isBooked}}"
disabled="true">
</vn-check>
<vn-check
label="Virtual"
value="{{$ctrl.entryData.isVirtual}}"
disabled="true">
</vn-check>
<vn-check
label="Inventory"
value="{{$ctrl.entryData.isInventory}}"
disabled="true">
</vn-check>
</vn-vertical>
</vn-one>
</vn-horizontal>
</vn-card>

View File

@ -0,0 +1,37 @@
import ngModule from '../module';
import './style.scss';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $, $httpParamSerializer) {
super($element, $);
this.$httpParamSerializer = $httpParamSerializer;
}
get entry() {
return this._entry;
}
set entry(value) {
this._entry = value;
if (value && value.id)
this.getEntryData();
}
getEntryData() {
return this.$http.get(`/api/Entries/${this.entry.id}/getEntry`).then(response => {
this.entryData = response.data;
});
}
}
Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnEntrySummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -0,0 +1,50 @@
import './index';
describe('component vnEntrySummary', () => {
let controller;
let $httpBackend;
let $scope;
let $element;
beforeEach(angular.mock.module('entry', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$element = angular.element(`<vn-entry-summary></vn-entry-summary>`);
controller = $componentController('vnEntrySummary', {$element, $scope});
}));
describe('entry setter/getter', () => {
it('should check if value.id is defined', () => {
spyOn(controller, 'getEntryData');
controller.entry = {id: 1};
expect(controller.getEntryData).toHaveBeenCalledWith();
});
it('should return the entry and then call getEntryData()', () => {
spyOn(controller, 'getEntryData');
controller.entry = {id: 99};
expect(controller._entry.id).toEqual(99);
expect(controller.getEntryData).toHaveBeenCalledWith();
});
});
describe('getEntryData()', () => {
it('should perform a get and then store data on the controller', () => {
controller._entry = {id: 999};
const query = `/api/Entries/${controller._entry.id}/getEntry`;
$httpBackend.expectGET(query).respond('I am the entryData');
controller.getEntryData();
$httpBackend.flush();
expect(controller.entryData).toEqual('I am the entryData');
});
});
});

View File

@ -0,0 +1,3 @@
Inventory: Inventario
Virtual: Redada
Entry: Entrada

View File

@ -0,0 +1,10 @@
@import "variables";
vn-entry-summary .summary {
max-width: $width-lg;
vn-icon[icon=insert_drive_file]{
color: $color-font-secondary;
}
}

View File

@ -20,12 +20,6 @@ describe('component vnTravelSummary', () => {
}));
describe('travel setter/getter', () => {
it('should return the travel', () => {
controller.travel = {id: null};
expect(controller.travel).toEqual(jasmine.any(Object));
});
it('should return the travel and then call both getTravel() and getEntries()', () => {
spyOn(controller, 'getTravel');
spyOn(controller, 'getEntries');