This commit is contained in:
Juan Ferrer 2019-02-13 14:01:38 +01:00
commit f3e3c1bbae
60 changed files with 762 additions and 333 deletions

View File

@ -0,0 +1,32 @@
module.exports = function(Self) {
Self.remoteMethodCtx('getConfig', {
description: 'returns the information from UserConfig model for the active user',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}, {
arg: 'tableCode',
type: 'String',
description: `Code of the table you ask its configuration`
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/getConfig`,
verb: 'get'
}
});
Self.getConfig = async ctx => {
let userView = await Self.app.models.UserConfigView.findOne({
where: {tableCode: ctx.args.tableCode, userFk: ctx.req.accessToken.userId}
});
return userView;
};
};

View File

@ -0,0 +1,34 @@
module.exports = function(Self) {
Self.remoteMethodCtx('save', {
description: 'returns the information from UserConfig model for the active user',
accepts: [{
arg: 'config',
type: 'Object',
required: true,
description: `Code of the table you ask its configuration`,
http: {source: 'body'}
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/save`,
verb: 'post'
}
});
Self.save = async(ctx, config) => {
let userView = await Self.app.models.UserConfigView.findOne({
where: {tableCode: config.tableCode, userFk: ctx.req.accessToken.userId}
});
if (userView)
return userView.updateAttributes(config);
config.userFk = ctx.req.accessToken.userId;
return await Self.app.models.UserConfigView.create(config);
};
};

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/user-config-view/getConfig')(Self);
require('../methods/user-config-view/save')(Self);
};

View File

@ -9,7 +9,10 @@
"properties": {
"id": {
"id": true,
"type": "Number",
"type": "Number"
},
"userFk": {
"type": "String",
"required": true
},
"tableCode": {
@ -17,7 +20,7 @@
"required": true
},
"configuration": {
"type": "String"
"type": "Object"
}
},
"relations": {

View File

@ -266,11 +266,11 @@ let actions = {
});
},
waitForTextInElement: function(selector, name, done) {
waitForTextInElement: function(selector, text, done) {
this.wait(selector)
.wait((selector, name) => {
return document.querySelector(selector).innerText.toLowerCase().includes(name.toLowerCase());
}, selector, name)
.wait((selector, text) => {
return document.querySelector(selector).innerText.toLowerCase().includes(text.toLowerCase());
}, selector, text)
.then(done)
.catch(done);
},

View File

@ -171,9 +171,9 @@ export default {
itemsIndex: {
goBackToModuleIndexButton: `vn-ticket-descriptor a[href="#!/ticket/index"]`,
createItemButton: `${components.vnFloatButton}`,
searchResult: `vn-item-product a`,
searchResultPreviewButton: `vn-item-product .buttons > [icon="desktop_windows"]`,
searchResultCloneButton: `vn-item-product .buttons > [icon="icon-clone"]`,
searchResult: `vn-item-index a.vn-tr`,
searchResultPreviewButton: `vn-item-index .buttons > [icon="desktop_windows"]`,
searchResultCloneButton: `vn-item-index .buttons > [icon="icon-clone"]`,
acceptClonationAlertButton: `vn-item-index [vn-id="clone"] [response="ACCEPT"]`,
searchItemInput: `vn-searchbar vn-textfield input`,
searchButton: `vn-searchbar vn-icon[icon="search"]`,

View File

@ -1,4 +1,4 @@
<div>
<div class="table">
<vn-empty-rows ng-if="$ctrl.model && !$ctrl.model.data" translate>
Enter a new search
</vn-empty-rows>

View File

@ -10,7 +10,7 @@ export default class Table {
this.autoLoad = true;
$transclude($scope.$parent, clone => {
angular.element($element[0].querySelector('div')).append(clone);
angular.element($element[0].querySelector('.table')).append(clone);
});
}

View File

@ -10,3 +10,4 @@ import './visible-by';
import './bind';
import './repeat-last';
import './title';
import './uvc';

View File

@ -0,0 +1,30 @@
<div style="position: relative;">
<div style="position: absolute; top: 0; left: 0; padding: .5em; z-index: 1">
<vn-icon-button
icon="menu"
style="font-size: .4em;"
ng-click="$ctrl.showUvc($event)">
</vn-icon-button>
<vn-dialog
class="modal-form"
vn-id="uvc">
<tpl-body>
<vn-horizontal pad-medium class="header">
<h5><span translate>Fields to show</span></h5>
</vn-horizontal>
<div pad-medium>
<vn-horizontal ng-repeat="field in fields">
<vn-check
vn-one label="{{field}}"
field="tableConfiguration.configuration[field]">
</vn-check>
</vn-horizontal>
<vn-button
label="Save"
ng-click="$ctrl.saveConfiguration(tableConfiguration)">
</vn-button>
</div>
</tpl-body>
</vn-dialog>
</div>
</div>

View File

@ -0,0 +1,95 @@
import ngModule from '../module';
import template from './uvc.html';
directive.$inject = ['$http', '$compile', 'vnApp', '$translate'];
export function directive($http, $compile, vnApp, $translate) {
function getHeaderList($element, $scope) {
let allHeaders = $element[0].querySelectorAll(`vn-th[field], vn-th[th-id]`);
let headerList = Array.from(allHeaders);
let ids = [];
headerList.forEach(header => {
ids.push(header.getAttribute('th-id') || header.getAttribute('field'));
});
$scope.fields = ids;
return headerList;
}
function getTableConfig(tableCode) {
return $http.get(`/api/UserConfigViews/getConfig?tableCode=${tableCode}`);
}
function createViewConfig(config, fields) {
fields.forEach(field => {
if (config.configuration[field] == null)
config.configuration[field] = true;
});
}
function addHideClass($scope, headerList, userConfig) {
let selectors = [];
Object.keys(userConfig.configuration).forEach(key => {
let index;
if (userConfig.configuration[key] === false) {
index = headerList.findIndex(el => {
return (el.getAttribute('th-id') == key || el.getAttribute('field') == key);
});
let baseSelector = `vn-table[vn-uvc=${userConfig.tableCode}] > div`;
selectors.push(`${baseSelector} vn-thead > vn-tr > vn-th:nth-child(${index + 1})`);
selectors.push(`${baseSelector} vn-tbody > vn-tr > vn-td:nth-child(${index + 1})`);
selectors.push(`${baseSelector} vn-tbody > .vn-tr > vn-td:nth-child(${index + 1})`);
}
});
let rule = selectors.join(', ') + '{display: none;}';
if ($scope.css)
document.head.removeChild($scope.css);
$scope.css = document.createElement('style');
document.head.appendChild($scope.css);
$scope.css.appendChild(document.createTextNode(rule));
}
function saveConfiguration(tableConfiguration) {
tableConfiguration.configuration = JSON.parse(JSON.stringify(tableConfiguration.configuration));
return $http.post(`/api/UserConfigViews/save`, tableConfiguration);
}
return {
restrict: 'A',
link: async function($scope, $element, $attrs) {
let cTemplate = $compile(template)($scope)[0];
$scope.$ctrl.showUvc = event => {
event.preventDefault();
event.stopImmediatePropagation();
$scope.uvc.show();
};
let tableCode = $attrs.vnUvc.trim();
let headerList = getHeaderList($element, $scope);
let defaultConfigView = {tableCode: tableCode, configuration: {}};
let userView = await getTableConfig(tableCode);
let config = userView.data || defaultConfigView;
$scope.tableConfiguration = config;
createViewConfig(config, $scope.fields);
addHideClass($scope, headerList, config);
let table = document.querySelector(`vn-table[vn-uvc=${tableCode}]`);
table.insertBefore(cTemplate, table.firstChild);
$scope.$ctrl.saveConfiguration = async tableConfiguration => {
let newConfig = await saveConfiguration(tableConfiguration);
vnApp.showSuccess($translate.instant('Data saved!'));
addHideClass($scope, headerList, newConfig.data);
$scope.uvc.hide();
};
}
};
}
ngModule.directive('vnUvc', directive);

View File

@ -19,7 +19,7 @@ export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $t
if (loaded[moduleName] instanceof Promise)
return loaded[moduleName];
if (loaded[moduleName] === false)
return $q.reject(new Error(`Module dependency loop detected: ${moduleName}`));
return Promise.resolve(true);
loaded[moduleName] = false;
@ -37,10 +37,7 @@ export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $t
$translatePartialLoader.addPart(moduleName);
promises.push(new Promise(resolve => {
$translate.refresh().then(
() => resolve(),
() => resolve()
);
$translate.refresh().then(resolve, resolve);
}));
if (validations) {

View File

@ -28,19 +28,5 @@ describe('factory vnModuleLoader', () => {
expect(result).toEqual(jasmine.any(Promise));
});
it('should return an error if the module wasnt loaded', done => {
vnModuleLoader._loaded.myModule = false;
vnModuleLoader.load('myModule', {myValidations: () => {}})
.then(() => {
done.fail('this must fail');
})
.catch(error => {
expect(error.toString()).toEqual(`Error: Module dependency loop detected: myModule`);
done();
});
$scope.$apply();
});
});
});

View File

@ -39,3 +39,4 @@ November: Noviembre
December: Diciembre
Has delivery: Hay reparto
Loading: Cargando
Fields to show: Campos a mostrar

View File

@ -92,7 +92,7 @@ class Controller {
this.company = value;
if (value &&
(!window.localStorage.localCompanyFk || window.localStorage.localCompanyFk === 'null'))
window.localStorage.defaultCompanyFk = value;
window.localStorage.setItem('localCompanyFk', value);
this.setUserConfig('companyFk', value);
}
@ -118,13 +118,13 @@ class Controller {
if (res.data && res.data.warehouseFk) {
this.warehouse = res.data.warehouseFk;
if (res.data.warehouseFk && !window.localStorage.localWarehouseFk)
window.localStorage.defaultWarehouseFk = res.data.warehouseFk;
window.localStorage.setItem('localWarehouseFk', res.data.warehouseFk);
}
if (res.data && res.data.companyFk) {
this.company = res.data.companyFk;
if (res.data.companyFk && !window.localStorage.localCompanyFk)
window.localStorage.defaultCompanyFk = res.data.companyFk;
window.localStorage.setItem('defaultCompanyFk', res.data.companyFk);
}
});
}

View File

@ -74,7 +74,7 @@ defaultTask.description = `Starts all application services`;
// Backend tests
async function backTestOnly() {
async function backTestOnce() {
let bootOptions;
if (argv['random'])
@ -107,11 +107,19 @@ async function backTestOnly() {
await app.disconnect();
}
backTestOnly.description = `Runs the backend tests only, can receive --junit arg to save reports on a xml file`;
backTestOnce.description = `Runs the backend tests once, can receive --junit arg to save reports on a xml file`;
async function backTestDockerOnce() {
let containerId = await docker();
await backTestOnce();
if (argv['random'])
await execP(`docker rm -fv ${containerId}`);
}
backTestDockerOnce.description = `Runs backend tests using in site container once`;
async function backTestDocker() {
let containerId = await docker();
await backTestOnly();
await backTest();
if (argv['random'])
await execP(`docker rm -fv ${containerId}`);
}
@ -122,7 +130,7 @@ function backTest(done) {
nodemon({
exec: ['node ./node_modules/gulp/bin/gulp.js'],
args: ['backTestOnly'],
args: ['backTestOnce'],
watch: backSources,
done: done
});
@ -484,8 +492,9 @@ module.exports = {
back,
backOnly,
backWatch,
backTestOnce,
backTestDockerOnce,
backTest,
backTestOnly,
backTestDocker,
e2e,
e2eOnly,

View File

@ -10,7 +10,7 @@ class Controller {
}
onSearch() {
this.$scope.$$postDigest(() => {
this.$scope.$applyAsync(() => {
this.$scope.treeview.refresh();
});
}

View File

@ -32,12 +32,19 @@ module.exports = Self => {
let stmt = new ParameterizedSQL(
`SELECT i.id, i.image, i.name, i.description,
i.size, i.tag5, i.value5, i.tag6, i.value6,
i.tag7, i.value7, i.tag8, i.value8,
t.name type, u.nickname userNickname
i.tag7, i.value7, i.tag8, i.value8, i.isActive,
t.name type, u.nickname userNickname,
intr.description AS intrastat, i.stems,
ori.code AS origin, t.name AS type,
ic.name AS category
FROM item i
JOIN itemType t ON t.id = i.typeFk
LEFT JOIN itemCategory ic ON ic.id = t.categoryFk
JOIN worker w ON w.id = t.workerFk
JOIN account.user u ON u.id = w.userFk`
JOIN account.user u ON u.id = w.userFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN producer pr ON pr.id = i.producerFk
LEFT JOIN origin ori ON ori.id = i.originFk`
);
if (tags) {

View File

@ -92,7 +92,7 @@
<tpl-body>
<div>
<h5 style="text-align: center">
<span translate>Regularize</span>
<span translate>Regularize stock</span>
</h5>
<vn-textfield
label="Quantity"

View File

@ -9,7 +9,7 @@ class Controller {
this.vnApp = vnApp;
this.$translate = $translate;
this.moreOptions = [
{callback: this.showRegularizeDialog, name: 'Regularize'}
{callback: this.showRegularizeDialog, name: 'Regularize stock'}
];
}
@ -22,6 +22,7 @@ class Controller {
}
set warehouseFk(value) {
if (value)
this._warehouseFk = value;
}

View File

@ -1 +1 @@
Regularize: Regularizar
Regularize stock: Regularizar stock

View File

@ -22,7 +22,7 @@ class Controller {
where: {itemFk: this.$stateParams.id}
};
this.$scope.$$postDigest(() => {
this.$scope.$applyAsync(() => {
if (this.$stateParams.warehouseFk)
this.warehouseFk = this.$stateParams.warehouseFk;
else if (value)

View File

@ -69,23 +69,23 @@ describe('Item', () => {
describe('set item()', () => {
it(`should set warehouseFk property based on itemType warehouseFk`, () => {
spyOn(controller.$scope, '$$postDigest').and.callThrough();
spyOn(controller.$scope, '$applyAsync').and.callThrough();
controller.item = {id: 1, itemType: {warehouseFk: 1}};
expect(controller.$scope.$$postDigest).toHaveBeenCalledWith(jasmine.any(Function));
$scope.$digest();
expect(controller.$scope.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
$scope.$apply();
expect(controller.warehouseFk).toEqual(1);
expect(controller.item.id).toEqual(1);
});
it(`should set warehouseFk property based on url query warehouseFk`, () => {
spyOn(controller.$scope, '$$postDigest').and.callThrough();
spyOn(controller.$scope, '$applyAsync').and.callThrough();
controller.$stateParams.warehouseFk = 4;
controller.item = {id: 1, itemType: {warehouseFk: 1}};
expect(controller.$scope.$$postDigest).toHaveBeenCalledWith(jasmine.any(Function));
$scope.$digest();
expect(controller.$scope.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
$scope.$apply();
expect(controller.warehouseFk).toEqual(4);
expect(controller.item.id).toEqual(1);

View File

@ -7,10 +7,6 @@ import './create';
import './card';
import './descriptor';
import './descriptor-popover';
import './ticket-descriptor';
import './ticket-descriptor/addStowaway';
import './ticket-descriptor/removeStowaway';
import './ticket-descriptor-popover';
import './data';
import './tags';
import './tax';

View File

@ -1,10 +1,10 @@
<vn-crud-model
vn-id="model"
url="/item/api/Items/filter"
limit="8"
limit="12"
order="isActive DESC, name, id"
data="items"
auto-load="false">
auto-load="true">
</vn-crud-model>
<div class="content-block">
<div class="vn-list">
@ -17,24 +17,77 @@
vn-focus>
</vn-searchbar>
</vn-card>
</div>
<vn-card margin-medium-v>
<vn-item-product
class="searchResult"
ng-repeat="item in items track by item.id"
item="::item">
</vn-item-product>
<vn-empty-rows class="vn-list-item" style="text-align: center"
ng-if="model.data.length === 0" translate>
<vn-table model="model" show-fields="$ctrl.showFields" vn-uvc="itemIndex">
<vn-thead>
<vn-tr>
<vn-th th-id="picture"></vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th th-id="description" style="text-align: center">Description</vn-th>
<vn-th th-id="stems">Stems</vn-th>
<vn-th th-id="type">Type</vn-th>
<vn-th th-id="category">Category</vn-th>
<vn-th th-id="intrastat">Intrastat</vn-th>
<vn-th th-id="origin">Origin</vn-th>
<vn-th th-id="salesperson">Sales person</vn-th>
<vn-th th-id="active">Active</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="item in items"
class="clickable vn-tr searchResult"
ui-sref="item.card.summary({id: item.id})">
<vn-td shrink>
<img
ng-src="{{::$ctrl.imagesPath}}/50x50/{{::item.image}}"
zoom-image="{{::$ctrl.imagesPath}}/1600x900/{{::item.image}}"
on-error-src/>
</vn-td>
<vn-td number>{{::item.id | zeroFill:6}}</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
item="item"
title="item.name">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::item.stems}}</vn-td>
<vn-td>{{::item.type}}</vn-td>
<vn-td>{{::item.category}}</vn-td>
<vn-td>{{::item.intrastat}}</vn-td>
<vn-td>{{::item.origin}}</vn-td>
<vn-td>{{::item.userNickname}}</vn-td>
<vn-td>
<vn-check
disabled="true"
field="item.isActive">
</vn-check>
</vn-td>
<vn-td shrink>
<vn-horizontal class="buttons">
<vn-icon-button
ng-click="$ctrl.cloneItem($event, item)"
vn-tooltip="Clone"
icon="icon-clone">
</vn-icon-button>
<vn-icon-button
ng-click="$ctrl.preview($event, item)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</vn-horizontal>
</vn-td>
</a>
</vn-tbody>
<vn-empty-rows ng-if="items.length === 0" translate>
No results
</vn-empty-rows>
<vn-empty-rows class="vn-list-item" style="text-align: center"
ng-if="model.data === null" translate>
Enter a new search
</vn-empty-rows>
</vn-card>
</vn-table>
<vn-card margin-medium-v>
<vn-pagination model="model"></vn-pagination>
</div>
</div>
<a ui-sref="item.create" vn-tooltip="New item" vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -8,6 +8,12 @@ class Controller {
this.$state = $state;
this.$ = $scope;
this.itemSelected = null;
this.imagesPath = '//verdnatura.es/vn-image-data/catalog';
this.showFields = {
id: false,
actions: false
};
}
exprBuilder(param, value) {
@ -25,6 +31,23 @@ class Controller {
}
}
showDescriptor(event, itemFk) {
this.quicklinks = {
btnThree: {
icon: 'icon-transaction',
state: `item.card.diary({
id: ${itemFk},
warehouseFk: ${this.ticket.warehouseFk},
ticketFk: ${this.ticket.id}
})`,
tooltip: 'Item diary'
}
};
this.$scope.descriptor.itemFk = itemFk;
this.$scope.descriptor.parent = event.target;
this.$scope.descriptor.show();
}
paramBuilder(param, value) {
switch (param) {
case 'tags':
@ -32,7 +55,9 @@ class Controller {
}
}
cloneItem(item) {
cloneItem(event, item) {
event.preventDefault();
event.stopImmediatePropagation();
this.itemSelected = item;
this.$.clone.show();
}
@ -49,7 +74,9 @@ class Controller {
this.itemSelected = null;
}
showItemPreview(item) {
preview(event, item) {
event.preventDefault();
event.stopImmediatePropagation();
this.itemSelected = item;
this.$.preview.show();
}

View File

@ -25,3 +25,10 @@ vn-item-product {
margin-top: 0.9em;
}
}
vn-table {
img {
border-radius: 50%;
max-width: 50px;
}
}

View File

@ -3,7 +3,7 @@
"name": "Items",
"icon": "inbox",
"validations" : true,
"dependencies": ["client"],
"dependencies": ["client", "ticket"],
"menu": [
{"state": "item.card.data", "icon": "settings"},
{"state": "item.card.tags", "icon": "icon-tags"},

View File

@ -1,126 +0,0 @@
import './index.js';
describe('Item', () => {
describe('Component vnTicketDescriptorPopover', () => {
let $httpBackend;
let $scope;
let controller;
let $element;
let $timeout;
beforeEach(ngModule('item'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$timeout_) => {
$httpBackend = _$httpBackend_;
$timeout = _$timeout_;
$element = angular.element(`<div></div>`);
$scope = $rootScope.$new();
$scope.popover = {relocate: () => {}, show: () => {}};
controller = $componentController('vnTicketDescriptorPopover', {$scope, $element});
}));
describe('ticketFk()', () => {
it(`should not apply any changes if the received id is the same stored in _ticketFk`, () => {
controller.ticket = 'I exist!';
controller._ticketFk = 1;
spyOn(controller, 'getCard');
controller.ticketFk = 1;
expect(controller.ticket).toEqual('I exist!');
expect(controller._ticketFk).toEqual(1);
expect(controller.getCard).not.toHaveBeenCalled();
});
it(`should set the received id into _ticketFk, set the ticket to null and then call getCard()`, () => {
controller.ticket = `Please don't`;
controller._ticketFk = 1;
spyOn(controller, 'getCard');
controller.ticketFk = 999;
expect(controller.ticket).toBeNull();
expect(controller._ticketFk).toEqual(999);
expect(controller.getCard).toHaveBeenCalledWith();
});
});
describe('ticket()', () => {
it(`should save the ticket into _ticket and then call relocate()`, () => {
spyOn(controller.$.popover, 'relocate');
controller.ticket = `i'm the ticket!`;
$timeout.flush();
expect(controller._ticket).toEqual(`i'm the ticket!`);
expect(controller.$.popover.relocate).toHaveBeenCalledWith();
});
});
describe('show()', () => {
it(`should call the show()`, () => {
spyOn(controller.$.popover, 'show');
controller.show();
expect(controller.$.popover.show).toHaveBeenCalledWith();
});
});
describe('getCard()', () => {
it(`should perform a get query to store the ticket data into the controller`, () => {
controller.ticketFk = 1;
controller.canceler = null;
let response = {};
let filter = {
include: [
{
relation: 'warehouse',
scope: {
fields: ['name']
}
},
{
relation: 'agencyMode',
scope: {
fields: ['name']
}
},
{
relation: 'client',
scope: {
fields: ['salesPersonFk', 'name', 'isActive', 'isFreezed', 'isTaxDataChecked'],
include: {
relation: 'salesPerson',
scope: {
fields: ['userFk'],
include: {
relation: 'user',
scope: {
fields: ['nickname']
}
}
}
}
}
},
{
relation: 'state',
scope: {
fields: ['stateFk'],
include: {
relation: 'state',
fields: ['id', 'name'],
}
}
}
]
};
let json = encodeURIComponent(JSON.stringify(filter));
$httpBackend.when('GET', `/ticket/api/Tickets/${controller._ticketFk}?filter=${json}`).respond(response);
$httpBackend.expect('GET', `/ticket/api/Tickets/${controller._ticketFk}?filter=${json}`);
controller.getCard();
$httpBackend.flush();
expect(controller.ticket).toEqual(response);
});
});
});
});

View File

@ -3,16 +3,16 @@ const app = require('vn-loopback/server/server');
describe('order addToOrder()', () => {
let rowToDelete;
afterAll(async() => {
await app.models.OrderRow.removes({rows: [rowToDelete], actualOrderId: 16});
await app.models.OrderRow.removes({rows: [rowToDelete], actualOrderId: 20});
});
it('should add a row to a given order', async() => {
let unmodifiedRows = await app.models.OrderRow.find({where: {orderFk: 16}});
let unmodifiedRows = await app.models.OrderRow.find({where: {orderFk: 20}});
expect(unmodifiedRows.length).toBe(4);
expect(unmodifiedRows.length).toBe(1);
let params = {
orderFk: 16,
orderFk: 20,
items: [{
itemFk: 1,
quantity: 1,
@ -22,10 +22,10 @@ describe('order addToOrder()', () => {
await app.models.OrderRow.addToOrder(params);
let modifiedRows = await app.models.OrderRow.find({where: {orderFk: 16}});
let modifiedRows = await app.models.OrderRow.find({where: {orderFk: 20}});
rowToDelete = modifiedRows[modifiedRows.length - 1].id;
expect(modifiedRows.length).toBe(5);
expect(modifiedRows.length).toBe(2);
});
});

View File

@ -28,7 +28,7 @@ class Controller {
this._order = value;
this.$scope.$$postDigest(() => {
this.$scope.$applyAsync(() => {
let category;
let type;

View File

@ -6,10 +6,12 @@ describe('Order', () => {
let $scope;
let $state;
let controller;
let $httpBackend;
beforeEach(ngModule('order'));
beforeEach(angular.mock.inject(($componentController, _$state_, $rootScope) => {
beforeEach(angular.mock.inject(($componentController, _$state_, _$httpBackend_, $rootScope) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$scope.model = crudModel;
$scope.search = {};
@ -27,12 +29,14 @@ describe('Order', () => {
}));
describe('order() setter', () => {
it(`should call scope $$postDigest() method and apply filters from state params`, () => {
spyOn(controller.$scope, '$$postDigest').and.callThrough();
it(`should call scope $applyAsync() method and apply filters from state params`, () => {
$httpBackend.expect('GET', `/item/api/ItemCategories/1/itemTypes`).respond();
spyOn(controller.$scope, '$applyAsync').and.callThrough();
controller.order = {id: 4};
expect(controller.$scope.$$postDigest).toHaveBeenCalledWith(jasmine.any(Function));
$scope.$digest();
expect(controller.$scope.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
$scope.$apply();
expect(controller.category).toEqual({id: 1, value: 'My Category'});
expect(controller.type).toEqual({id: 1, value: 'My type'});
@ -96,28 +100,38 @@ describe('Order', () => {
});
describe('applyFilters()', () => {
it(`should set type property to null, call updateStateParams() method and not call applyFilters()`, () => {
spyOn(controller.catalog.$scope.model, 'applyFilter');
controller.order = {id: 4};
$scope.$digest();
it(`should call model applyFilter() method with a new filter`, () => {
let model = controller.catalog.$scope.model;
spyOn(model, 'applyFilter');
controller._category = {id: 1, value: 'My Category'};
controller._type = {id: 1, value: 'My type'};
controller._order = {id: 4};
controller.applyFilters();
expect(controller.catalog.$scope.model.applyFilter).toHaveBeenCalledWith(
expect(model.applyFilter).toHaveBeenCalledWith(
{where: {categoryFk: 1, typeFk: 1}},
{orderFk: 4, orderBy: controller.catalog.getOrderBy(), tags: []});
});
});
describe('remove()', () => {
it(`should remove a filter from tags property and then call applyFilters()`, () => {
spyOn(controller, 'applyFilters');
controller.order = {id: 4};
it(`should remove a tag from tags property`, () => {
controller.tags = [{tagFk: 1, value: 'Blue'}, {tagFk: 2, value: '70'}];
$scope.$digest();
controller.remove(0);
expect(controller.tags.length).toEqual(1);
expect(controller.tags[0].tagFk).toEqual(2);
});
it(`should remove a tag from tags property and call applyFilters() if there's no more tags`, () => {
spyOn(controller, 'applyFilters');
controller._category = {id: 1, value: 'My Category'};
controller._type = {id: 1, value: 'My type'};
controller.tags = [{tagFk: 1, value: 'Blue'}];
controller.remove(0);
expect(controller.tags.length).toEqual(0);
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
@ -125,10 +139,10 @@ describe('Order', () => {
describe('updateStateParams()', () => {
it(`should call state go() method passing category and type state params`, () => {
spyOn(controller.$state, 'go');
controller.order = {id: 4};
$scope.$digest();
controller._category = {id: 1, value: 'My Category'};
controller._type = {id: 1, value: 'My type'};
let result = {category: '{"id":1,"value":"My Category"}', type: '{"id":1,"value":"My type"}'};
controller.updateStateParams();
expect(controller.$state.go).toHaveBeenCalledWith('my.current.state', result);
});

View File

@ -35,6 +35,9 @@ class Controller {
};
this.$http.get(`/item/api/ItemTags?filter=${JSON.stringify(filter)}`).then(response => {
this.tags = response.data;
this.$.$applyAsync(() => {
this.$.popover.relocate();
});
});
}
show(event, item) {
@ -42,8 +45,8 @@ class Controller {
this.prices = this.item.prices;
this.getTags();
this.$.popover.parent = event.target;
this.$.popover.relocate();
this.$.popover.show();
this.$.popover.relocate();
}
clear() {
this.item = {};

View File

@ -0,0 +1,125 @@
import './index.js';
describe('ticket Component vnTicketDescriptorPopover', () => {
let $httpBackend;
let $scope;
let controller;
let $element;
let $timeout;
beforeEach(ngModule('ticket'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$timeout_) => {
$httpBackend = _$httpBackend_;
$timeout = _$timeout_;
$element = angular.element(`<div></div>`);
$scope = $rootScope.$new();
$scope.popover = {relocate: () => {}, show: () => {}};
controller = $componentController('vnTicketDescriptorPopover', {$scope, $element});
}));
describe('ticketFk()', () => {
it(`should not apply any changes if the received id is the same stored in _ticketFk`, () => {
controller.ticket = 'I exist!';
controller._ticketFk = 1;
spyOn(controller, 'getCard');
controller.ticketFk = 1;
expect(controller.ticket).toEqual('I exist!');
expect(controller._ticketFk).toEqual(1);
expect(controller.getCard).not.toHaveBeenCalled();
});
it(`should set the received id into _ticketFk, set the ticket to null and then call getCard()`, () => {
controller.ticket = `Please don't`;
controller._ticketFk = 1;
spyOn(controller, 'getCard');
controller.ticketFk = 999;
expect(controller.ticket).toBeNull();
expect(controller._ticketFk).toEqual(999);
expect(controller.getCard).toHaveBeenCalledWith();
});
});
describe('ticket()', () => {
it(`should save the ticket into _ticket and then call relocate()`, () => {
spyOn(controller.$.popover, 'relocate');
controller.ticket = `i'm the ticket!`;
$timeout.flush();
expect(controller._ticket).toEqual(`i'm the ticket!`);
expect(controller.$.popover.relocate).toHaveBeenCalledWith();
});
});
describe('show()', () => {
it(`should call the show()`, () => {
spyOn(controller.$.popover, 'show');
controller.show();
expect(controller.$.popover.show).toHaveBeenCalledWith();
});
});
describe('getCard()', () => {
it(`should perform a get query to store the ticket data into the controller`, () => {
controller.ticketFk = 1;
controller.canceler = null;
let response = {};
let filter = {
include: [
{
relation: 'warehouse',
scope: {
fields: ['name']
}
},
{
relation: 'agencyMode',
scope: {
fields: ['name']
}
},
{
relation: 'client',
scope: {
fields: ['salesPersonFk', 'name', 'isActive', 'isFreezed', 'isTaxDataChecked'],
include: {
relation: 'salesPerson',
scope: {
fields: ['userFk'],
include: {
relation: 'user',
scope: {
fields: ['nickname']
}
}
}
}
}
},
{
relation: 'state',
scope: {
fields: ['stateFk'],
include: {
relation: 'state',
fields: ['id', 'name'],
}
}
}
]
};
let json = encodeURIComponent(JSON.stringify(filter));
$httpBackend.when('GET', `/ticket/api/Tickets/${controller._ticketFk}?filter=${json}`).respond(response);
$httpBackend.expect('GET', `/ticket/api/Tickets/${controller._ticketFk}?filter=${json}`);
controller.getCard();
$httpBackend.flush();
expect(controller.ticket).toEqual(response);
});
});
});

View File

@ -1,12 +1,10 @@
import './index.js';
describe('Item Component vnTicketDescriptor', () => {
describe('Ticket Component vnTicketDescriptor', () => {
let $httpBackend;
let controller;
beforeEach(() => {
ngModule('item');
});
beforeEach(ngModule('ticket'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;

View File

@ -3,6 +3,10 @@ export * from './module';
import './index/';
import './search-panel';
import './card';
import './descriptor';
import './descriptor/addStowaway';
import './descriptor/removeStowaway';
import './descriptor-popover';
import './create/card';
import './create/index';
import './summary';

View File

@ -83,7 +83,6 @@
"yaml-loader": "^0.5.0"
},
"scripts": {
"test": "nodemon -q back/tests.js -w modules",
"dbtest": "nodemon -q services/db/tests.js -w services/db/tests",
"back": "nodemon --inspect -w modules ./node_modules/gulp/bin/gulp.js back",
"lint": "eslint ./ --cache --ignore-pattern .gitignore",

View File

@ -0,0 +1,18 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`workerDepartment` AS
SELECT
`p`.`id_trabajador` AS `workerFk`,
`d`.`id` AS `departmentFk`
FROM
(((`postgresql`.`person` `p`
JOIN `postgresql`.`profile` `pr` ON ((`pr`.`person_id` = `p`.`person_id`)))
LEFT JOIN (`postgresql`.`business` `b`
LEFT JOIN `postgresql`.`business_labour` `bl` ON ((`b`.`business_id` = `bl`.`business_id`))) ON ((`pr`.`profile_id` = `b`.`client_id`)))
JOIN `vn`.`department` `d` ON ((`d`.`id` = `bl`.`department_id`)))
WHERE
(ISNULL(`b`.`date_end`)
OR (`b`.`date_end` > CURDATE()));

View File

@ -0,0 +1,108 @@
USE `hedera`;
DROP procedure IF EXISTS `orderAddItem`;
DELIMITER $$
USE `hedera`$$
CREATE DEFINER=`root`@`%` PROCEDURE `orderAddItem`(IN `vOrder` INT, IN `vWarehouse` INT, IN `vItem` INT, IN `vAmount` INT)
BEGIN
DECLARE vRow INT;
DECLARE vAdd INT;
DECLARE vAvailable INT;
DECLARE vDone BOOL;
DECLARE vGrouping INT;
DECLARE vRate INT;
DECLARE vShipment DATE;
DECLARE vPrice DECIMAL(10,2);
DECLARE vDate DATE;
DECLARE vAddress INT;
DECLARE vAgencyMode INT;
DECLARE cur CURSOR FOR
SELECT grouping, price, rate
FROM tmp.bionic_price
WHERE warehouse_id = vWarehouse
AND item_id = vItem
ORDER BY grouping DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
SELECT date_send, address_id, agency_id
INTO vDate, vAddress, vAgencyMode
FROM `order`
WHERE id = vOrder;
CALL vn2008.bionic_from_item(vDate, vAddress, vAgencyMode, vItem);
START TRANSACTION;
SELECT shipped INTO vShipment
FROM tmp.travel_tree
WHERE warehouseFk = vWarehouse;
SELECT available INTO vAvailable
FROM tmp.bionic_lot
WHERE warehouse_id = vWarehouse
AND item_id = vItem;
IF vAmount > IFNULL(vAvailable, 0)
THEN
CALL util.throw ('ORDER_ROW_UNAVAILABLE');
END IF;
OPEN cur;
l: LOOP
SET vDone = FALSE;
FETCH cur INTO vGrouping, vPrice, vRate;
IF vDone THEN
LEAVE l;
END IF;
SET vAdd = vAmount - MOD(vAmount, vGrouping);
SET vAmount = vAmount - vAdd;
IF vAdd = 0 THEN
ITERATE l;
END IF;
INSERT INTO order_row SET
order_id = vOrder,
item_id = vItem,
warehouse_id = vWarehouse,
shipment = vShipment,
rate = vRate,
amount = vAdd,
price = vPrice;
SET vRow = LAST_INSERT_ID();
INSERT INTO order_component (order_row_id, component_id, price)
SELECT vRow, c.component_id, c.cost
FROM tmp.bionic_component c
JOIN bi.tarifa_componentes t
ON t.Id_Componente = c.component_id
AND (t.tarifa_class IS NULL OR t.tarifa_class = vRate)
WHERE c.warehouse_id = vWarehouse
AND c.item_id = vItem;
END LOOP;
CLOSE cur;
IF vAmount > 0
THEN
CALL util.throw ('AMOUNT_NOT_MATCH_GROUPING');
END IF;
COMMIT;
CALL vn2008.bionic_free ();
END$$
DELIMITER ;

View File

@ -0,0 +1 @@
INSERT INTO salix.ACL(id,model, property, accessType, permission, principalType, principalId)VALUES(147,'UserConfigView', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -879,7 +879,7 @@ INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_
(17, CURDATE(), 106, 2, 4, 126, 442, NULL, 'SALIX', 0, CURDATE() , CURDATE() , CURDATE() ),
(18, CURDATE(), 107, 3, 4, 127, 442, NULL, 'SALIX', 0, CURDATE() , CURDATE() , CURDATE() ),
(19, CURDATE(), 108, 1, 5, 128, 442, NULL, 'SALIX', 0, CURDATE() , CURDATE() , CURDATE() ),
(20, CURDATE() , 109, 2, 5, 119, 442, NULL, 'SALIX', 0, CURDATE() , CURDATE() , CURDATE() ),
(20, CURDATE(), 109, 2, 1, 119, 442, NULL, 'SALIX', 0, CURDATE() , CURDATE() , CURDATE() ),
(21, CURDATE(), 110, 3, 5, 129, 442, NULL, 'SALIX', 0, CURDATE() , CURDATE() , CURDATE() );
INSERT INTO `hedera`.`orderRow`(`id`, `orderFk`, `itemFk`, `warehouseFk`, `shipment`, `amount`, `price`, `rate`, `created`, `saleFk`)
@ -895,7 +895,8 @@ INSERT INTO `hedera`.`orderRow`(`id`, `orderFk`, `itemFk`, `warehouseFk`, `shipm
(9, 16, 1, 1, CURDATE(), 5, 9.10, 0, CURDATE(), 9),
(10, 16, 2, 1, CURDATE(), 10, 1.07, 0, CURDATE(), 10),
(11, 16, 1, 1, CURDATE(), 2, 9.10, 0, CURDATE(), 11),
( 12, 16, 4, 1, CURDATE(), 20, 3.06, 0, CURDATE(), 12);
(12, 16, 4, 1, CURDATE(), 20, 3.06, 0, CURDATE(), 12),
(13, 20, 1, 1, CURDATE(), 2, 9.10, 0, CURDATE(), NULL);
INSERT INTO `hedera`.`orderRowComponent`(`rowFk`, `componentFk`, `price`)
VALUES
@ -956,7 +957,12 @@ INSERT INTO `hedera`.`orderRowComponent`(`rowFk`, `componentFk`, `price`)
(12, 28, 20.72),
(12, 29, -19.72),
(12, 37, 2),
( 12, 39, 0.01);
(12, 39, 0.01),
(13, 15, 0.58),
(13, 23, 6.5),
(13, 28, 20.72),
(13, 29, -18.72),
(13, 39, 0.02);
INSERT INTO `vn`.`clientContact`(`id`, `clientFk`, `name`, `phone`)
VALUES
@ -1042,13 +1048,7 @@ INSERT INTO `vn`.`orderTicket`(`orderFk`, `ticketFk`)
(12, 12),
(13, 13),
(14, 14),
(15, 15),
(16, 16),
(17, 17),
(18, 18),
(19, 19),
(20, 20),
(21, 21);
(15, 15);
INSERT INTO `vn`.`userConfig` (`userFk`, `warehouseFk`, `companyFk`)
VALUES

View File

@ -9,7 +9,9 @@ let verbose = false;
if (process.argv[2] === '--v')
verbose = true;
loopbackApp = `vn-loopback/server/server`;
let app = require(`vn-loopback/server/server`);
app.boot();
loopbackApp = 'vn-loopback/server/server';
let Jasmine = require('jasmine');
let jasmine = new Jasmine();

View File

@ -1,4 +1,4 @@
const app = require(`${loopbackApp}`);
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('buyUltimate()', () => {

View File

@ -1,4 +1,4 @@
const app = require(`${loopbackApp}`);
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('buyUltimateFromInterval()', () => {

View File

@ -1,4 +1,4 @@
const app = require(`${loopbackApp}`);
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('logAddWithUser()', () => {

View File

@ -1,4 +1,4 @@
const app = require(`${loopbackApp}`);
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('ticket ticketCalculateClon()', () => {

View File

@ -1,4 +1,4 @@
const app = require(`${loopbackApp}`);
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('ticketComponentUpdateSale()', () => {

View File

@ -1,4 +1,4 @@
const app = require(`${loopbackApp}`);
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('ticket ticketCreateWithUser()', () => {