Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 1798-e2e-extensions
gitea/salix/1798-e2e-extensions This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2020-02-05 07:08:29 +01:00
commit 973436bfa6
38 changed files with 969 additions and 800 deletions

View File

@ -6,14 +6,14 @@ services:
context: . context: .
dockerfile: front/Dockerfile dockerfile: front/Dockerfile
ports: ports:
- ${FRONT_PORT:?}:80 - 80
deploy: deploy:
replicas: 3 replicas: 3
back: back:
image: registry.verdnatura.es/salix-back:${BRANCH_NAME:?} image: registry.verdnatura.es/salix-back:${BRANCH_NAME:?}
build: . build: .
ports: ports:
- ${BACK_PORT:?}:3000 - 3000
environment: environment:
- NODE_ENV - NODE_ENV
configs: configs:

View File

@ -74,6 +74,7 @@ export default {
invoiceByMailCheckbox: 'vn-client-fiscal-data vn-check[label="Invoice by mail"]', invoiceByMailCheckbox: 'vn-client-fiscal-data vn-check[label="Invoice by mail"]',
viesCheckbox: 'vn-client-fiscal-data vn-check[label="Vies"]', viesCheckbox: 'vn-client-fiscal-data vn-check[label="Vies"]',
saveButton: 'button[type=submit]', saveButton: 'button[type=submit]',
acceptDuplicationButton: '.vn-confirm.shown button[response=accept]',
watcher: 'vn-client-fiscal-data vn-watcher' watcher: 'vn-client-fiscal-data vn-watcher'
}, },
clientBillingData: { clientBillingData: {
@ -595,14 +596,14 @@ export default {
orderCatalog: { orderCatalog: {
plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]', plantRealmButton: 'vn-order-catalog > vn-side-menu vn-icon[icon="icon-plant"]',
type: 'vn-autocomplete[data="$ctrl.itemTypes"]', type: 'vn-autocomplete[data="$ctrl.itemTypes"]',
itemId: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.itemId"]', itemId: 'vn-order-catalog > vn-side-menu vn-textfield[ng-model="$ctrl.itemId"]',
itemTagValue: 'vn-catalog-filter vn-textfield[ng-model="$ctrl.value"]', itemTagValue: 'vn-order-catalog > vn-side-menu vn-textfield[ng-model="$ctrl.value"]',
openTagSearch: 'vn-catalog-filter > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i', openTagSearch: 'vn-order-catalog > vn-side-menu > div > vn-vertical > vn-textfield[ng-model="$ctrl.value"] .append i',
tag: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]', tag: 'vn-order-catalog-search-panel vn-autocomplete[ng-model="filter.tagFk"]',
tagValue: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"]', tagValue: 'vn-order-catalog-search-panel vn-textfield[ng-model="filter.value"]',
searchTagButton: 'vn-order-catalog-search-panel button[type=submit]', searchTagButton: 'vn-order-catalog-search-panel button[type=submit]',
thirdFilterRemoveButton: 'vn-catalog-filter .chips > vn-chip:nth-child(3) vn-icon[icon=cancel]', thirdFilterRemoveButton: 'vn-order-catalog > vn-side-menu .chips > vn-chip:nth-child(3) vn-icon[icon=cancel]',
fourthFilterRemoveButton: 'vn-catalog-filter .chips > vn-chip:nth-child(4) vn-icon[icon=cancel]', fourthFilterRemoveButton: 'vn-order-catalog > vn-side-menu .chips > vn-chip:nth-child(4) vn-icon[icon=cancel]',
}, },
orderBasicData: { orderBasicData: {
client: 'vn-autocomplete[label="Client"]', client: 'vn-autocomplete[label="Client"]',

View File

@ -79,6 +79,7 @@ describe('Client Edit fiscalData path', () => {
await page.waitToClick(selectors.clientFiscalData.equalizationTax); await page.waitToClick(selectors.clientFiscalData.equalizationTax);
await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox); await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox);
await page.waitToClick(selectors.clientFiscalData.saveButton); await page.waitToClick(selectors.clientFiscalData.saveButton);
await page.waitToClick(selectors.clientFiscalData.acceptDuplicationButton);
const result = await page.waitForLastSnackbar(); const result = await page.waitForLastSnackbar();
expect(result).toEqual('Invalid Tax number'); expect(result).toEqual('Invalid Tax number');
@ -88,6 +89,7 @@ describe('Client Edit fiscalData path', () => {
await page.clearInput(selectors.clientFiscalData.fiscalId); await page.clearInput(selectors.clientFiscalData.fiscalId);
await page.write(selectors.clientFiscalData.fiscalId, '94980061C'); await page.write(selectors.clientFiscalData.fiscalId, '94980061C');
await page.waitToClick(selectors.clientFiscalData.saveButton); await page.waitToClick(selectors.clientFiscalData.saveButton);
await page.waitToClick(selectors.clientFiscalData.acceptDuplicationButton);
const result = await page.waitForLastSnackbar(); const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!'); expect(result).toEqual('Data saved!');
@ -149,7 +151,8 @@ describe('Client Edit fiscalData path', () => {
it('should navigate back to fiscal data and uncheck EQtax then check VIES', async() => { it('should navigate back to fiscal data and uncheck EQtax then check VIES', async() => {
await page.waitToClick(selectors.clientFiscalData.fiscalDataButton); await page.waitToClick(selectors.clientFiscalData.fiscalDataButton);
await page.waitToClick(selectors.clientFiscalData.viesCheckbox); await page.waitToClick(selectors.clientFiscalData.viesCheckbox);
await page.waitToClick(selectors.clientFiscalData.equalizationTax); await page.waitToClick(selectors.clientFiscalData.invoiceByAddressCheckbox);
await page.waitToClick(selectors.clientFiscalData.equalizationTaxCheckbox);
await page.waitToClick(selectors.clientFiscalData.saveButton); await page.waitToClick(selectors.clientFiscalData.saveButton);
const result = await page.waitForLastSnackbar(); const result = await page.waitForLastSnackbar();
@ -237,10 +240,10 @@ describe('Client Edit fiscalData path', () => {
expect(result).toBe('unchecked'); expect(result).toBe('unchecked');
}); });
it('should confirm invoice by address checkbox is unchecked', async() => { it('should confirm invoice by address checkbox is checked', async() => {
const result = await page.checkboxState(selectors.clientFiscalData.invoiceByAddressCheckbox); const result = await page.checkboxState(selectors.clientFiscalData.invoiceByAddressCheckbox);
expect(result).toBe('unchecked'); expect(result).toBe('checked');
}); });
it('should confirm Equalization tax checkbox is unchecked', async() => { it('should confirm Equalization tax checkbox is unchecked', async() => {

View File

@ -59,6 +59,7 @@ describe('Client lock verified data path', () => {
it('should check the Verified data checkbox', async() => { it('should check the Verified data checkbox', async() => {
await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox); await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox);
await page.waitToClick(selectors.clientFiscalData.saveButton); await page.waitToClick(selectors.clientFiscalData.saveButton);
await page.waitToClick(selectors.clientFiscalData.acceptDuplicationButton);
const result = await page.waitForLastSnackbar(); const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!'); expect(result).toEqual('Data saved!');

View File

@ -11,4 +11,8 @@ server {
location / { location / {
autoindex on; autoindex on;
} }
location /index.html {
expires -1;
add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
}
} }

View File

@ -63,5 +63,6 @@
"MESSAGE_CHANGED_PAYMETHOD": "I have changed the pay method for client [{{clientName}} (#{{clientId}})]({{{url}}})", "MESSAGE_CHANGED_PAYMETHOD": "I have changed the pay method for client [{{clientName}} (#{{clientId}})]({{{url}}})",
"MESSAGE_CLAIM_ITEM_REGULARIZE": "I sent *{{quantity}}* units of [{{concept}} (#{{itemId}})]({{{itemUrl}}}) to {{nickname}} coming from ticket id [#{{ticketId}}]({{{ticketUrl}}})", "MESSAGE_CLAIM_ITEM_REGULARIZE": "I sent *{{quantity}}* units of [{{concept}} (#{{itemId}})]({{{itemUrl}}}) to {{nickname}} coming from ticket id [#{{ticketId}}]({{{ticketUrl}}})",
"Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", "Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member",
"Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member" "Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member",
"Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}"
} }

View File

@ -124,5 +124,6 @@
"MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} (#{{itemId}}) para el ticket id [#{{ticketId}}]({{{url}}})", "MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} (#{{itemId}}) para el ticket id [#{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} (#{{clientId}})]({{{url}}}) a *{{credit}} €*", "MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} (#{{clientId}})]({{{url}}}) a *{{credit}} €*",
"MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} (#{{clientId}})]({{{url}}})", "MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} (#{{clientId}})]({{{url}}})",
"MESSAGE_CLAIM_ITEM_REGULARIZE": "Envio *{{quantity}}* unidades de [{{concept}} (#{{itemId}})]({{{itemUrl}}}) a {{nickname}} provenientes del ticket id [#{{ticketId}}]({{{ticketUrl}}})" "MESSAGE_CLAIM_ITEM_REGULARIZE": "Envio *{{quantity}}* unidades de [{{concept}} (#{{itemId}})]({{{itemUrl}}}) a {{nickname}} provenientes del ticket id [#{{ticketId}}]({{{ticketUrl}}})",
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}"
} }

View File

@ -1,6 +1,6 @@
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('lastActiveTickets', { Self.remoteMethod('lastActiveTickets', {
description: 'Returns the last three tickets of a client that have the alertLevel at 0 and the shiped day is gt today', description: 'Returns the last three active tickets of a client',
accessType: 'READ', accessType: 'READ',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
@ -24,16 +24,18 @@ module.exports = Self => {
}); });
Self.lastActiveTickets = async(id, ticketId) => { Self.lastActiveTickets = async(id, ticketId) => {
const ticket = await Self.app.models.Ticket.findById(ticketId);
const query = ` const query = `
SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName
FROM vn.ticket t FROM vn.ticket t
JOIN vn.ticketState ts ON t.id = ts.ticketFk JOIN vn.ticketState ts ON t.id = ts.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.id JOIN vn.agencyMode a ON t.agencyModeFk = a.id
JOIN vn.warehouse w ON t.warehouseFk = w.id JOIN vn.warehouse w ON t.warehouseFk = w.id
WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 AND t.id <> ? WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0
AND t.id <> ? AND t.warehouseFk = ?
ORDER BY t.shipped ORDER BY t.shipped
LIMIT 3`; LIMIT 3`;
return Self.rawSql(query, [id, ticketId]); return Self.rawSql(query, [id, ticketId, ticket.warehouseFk]);
}; };
}; };

View File

@ -1,56 +1,40 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Client updateFiscalData', () => { describe('Client updateFiscalData', () => {
const clientId = 101;
afterAll(async done => { afterAll(async done => {
let ctxOfAdmin = {req: {accessToken: {userId: 5}}}; const clientId = 101;
let validparams = {postcode: 46460}; const ctx = {req: {accessToken: {userId: 5}}};
let idWithDataChecked = 101; ctx.args = {postcode: 46460};
await app.models.Client.updateFiscalData(ctxOfAdmin, validparams, idWithDataChecked); await app.models.Client.updateFiscalData(ctx, clientId);
done(); done();
}); });
it('should return an error if the user is not administrative and the isTaxDataChecked value is true', async() => { it('should return an error if the user is not administrative and the isTaxDataChecked value is true', async() => {
const ctx = {req: {accessToken: {userId: 1}}};
ctx.args = {};
let error; let error;
await app.models.Client.updateFiscalData(ctx, clientId)
let ctxOfNoAdmin = {req: {accessToken: {userId: 1}}};
let params = [];
let idWithDataChecked = 101;
await app.models.Client.updateFiscalData(ctxOfNoAdmin, params, idWithDataChecked)
.catch(e => { .catch(e => {
error = e; error = e;
}); });
expect(error.toString()).toContain(`You can't make changes on a client with verified data`); expect(error.message).toBeDefined();
});
it('should return an error if the user is administrative and the isTaxDataChecked value is true BUT the params aint valid', async() => {
let error;
let ctxOfAdmin = {req: {accessToken: {userId: 5}}};
let invalidparams = {invalid: 'param for update'};
let idWithDataChecked = 101;
await app.models.Client.updateFiscalData(ctxOfAdmin, invalidparams, idWithDataChecked)
.catch(e => {
error = e;
});
expect(error.toString()).toContain(`You don't have enough privileges to do that`);
}); });
it('should update the client fiscal data and return the count if changes made', async() => { it('should update the client fiscal data and return the count if changes made', async() => {
let ctxOfAdmin = {req: {accessToken: {userId: 5}}}; const ctx = {req: {accessToken: {userId: 5}}};
let validparams = {postcode: 46680}; ctx.args = {postcode: 46680};
let idWithDataChecked = 101;
let client = await app.models.Client.findById(idWithDataChecked);
const client = await app.models.Client.findById(clientId);
expect(client.postcode).toEqual('46460'); expect(client.postcode).toEqual('46460');
let result = await app.models.Client.updateFiscalData(ctxOfAdmin, validparams, idWithDataChecked); const result = await app.models.Client.updateFiscalData(ctx, clientId);
expect(result.postcode).toEqual('46680'); expect(result.postcode).toEqual('46680');
}); });

View File

@ -1,21 +1,87 @@
let UserError = require('vn-loopback/util/user-error'); let UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('updateFiscalData', { Self.remoteMethod('updateFiscalData', {
description: 'Updates billing data of a client', description: 'Updates fiscal data of a client',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'data', arg: 'ctx',
type: 'Object', type: 'Object',
required: true, http: {source: 'context'}
description: 'Params to update', },
http: {source: 'body'} {
}, {
arg: 'id', arg: 'id',
type: 'string', type: 'Number',
required: true, description: 'The client id',
description: 'Model id',
http: {source: 'path'} http: {source: 'path'}
},
{
arg: 'socialName',
type: 'String'
},
{
arg: 'fi',
type: 'String'
},
{
arg: 'street',
type: 'String'
},
{
arg: 'postcode',
type: 'String'
},
{
arg: 'city',
type: 'String'
},
{
arg: 'countryFk',
type: 'Number'
},
{
arg: 'provinceFk',
type: 'Number'
},
{
arg: 'hasToInvoiceByAddress',
type: 'Boolean'
},
{
arg: 'hasToInvoice',
type: 'Boolean'
},
{
arg: 'isActive',
type: 'Boolean'
},
{
arg: 'isFreezed',
type: 'Boolean'
},
{
arg: 'isVies',
type: 'Boolean'
},
{
arg: 'isToBeMailed',
type: 'Boolean'
},
{
arg: 'isEqualizated',
type: 'Boolean'
},
{
arg: 'isTaxDataVerified',
type: 'Boolean'
},
{
arg: 'isTaxDataChecked',
type: 'Boolean'
},
{
arg: 'despiteOfClient',
type: 'Number'
}], }],
returns: { returns: {
arg: 'res', arg: 'res',
@ -28,41 +94,37 @@ module.exports = Self => {
} }
}); });
Self.updateFiscalData = async(ctx, params, id) => { Self.updateFiscalData = async(ctx, clientId) => {
let userId = ctx.req.accessToken.userId; const models = Self.app.models;
let isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant'); const args = ctx.args;
let [taxData] = await Self.app.models.Client.find({where: {id: id}, fields: ['isTaxDataChecked']}); const userId = ctx.req.accessToken.userId;
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant');
const $t = ctx.req.__;
if (!isSalesAssistant && taxData.isTaxDataChecked) const client = await models.Client.findById(clientId);
if (!isSalesAssistant && client.isTaxDataChecked)
throw new UserError(`You can't make changes on a client with verified data`); throw new UserError(`You can't make changes on a client with verified data`);
let validUpdateParams = [ if (args.despiteOfClient) {
'socialName', const logRecord = {
'fi', originFk: clientId,
'street', userFk: userId,
'postcode', action: 'update',
'city', changedModel: 'Client',
'countryFk', changedModelId: clientId,
'provinceFk', description: $t(`Client checked as validated despite of duplication`, {
'isActive', clientId: args.despiteOfClient
'isFreezed', })
'hasToInvoice', };
'isVies',
'isToBeMailed',
'hasToInvoiceByAddress',
'isEqualizated',
'isTaxDataVerified',
'isTaxDataChecked'
];
for (const key in params) { await models.ClientLog.create(logRecord);
if (validUpdateParams.indexOf(key) === -1)
throw new UserError(`You don't have enough privileges to do that`);
} }
params.id = id; // Remove unwanted properties
delete args.ctx;
delete args.id;
let client = await Self.app.models.Client.findById(id); return client.updateAttributes(args);
return await client.updateAttributes(params);
}; };
}; };

View File

@ -2,6 +2,7 @@
<vn-watcher <vn-watcher
vn-id="watcher" vn-id="watcher"
data="$ctrl.client" data="$ctrl.client"
id-field="id"
form="form" form="form"
save="patch"> save="patch">
</vn-watcher> </vn-watcher>
@ -129,7 +130,8 @@
vn-one vn-one
label="Is equalizated" label="Is equalizated"
ng-model="$ctrl.client.isEqualizated" ng-model="$ctrl.client.isEqualizated"
info="In order to invoice, this field is not consulted, but the consignee's ET. When modifying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not."> info="In order to invoice, this field is not consulted, but the consignee's ET. When modifying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not."
on-change="$ctrl.onChangeEqualizated(value)">
</vn-check> </vn-check>
<vn-check <vn-check
vn-one vn-one
@ -148,5 +150,10 @@
vn-id="propagate-isEqualizated" vn-id="propagate-isEqualizated"
question="You changed the equalization tax" question="You changed the equalization tax"
message="Do you want to spread the change?" message="Do you want to spread the change?"
on-response="$ctrl.returnDialogEt($response)"> on-accept="$ctrl.onAcceptEt()">
</vn-confirm>
<vn-confirm
vn-id="confirm-duplicatedClient"
message="Found a client with this data"
on-accept="$ctrl.onAcceptDuplication()">
</vn-confirm> </vn-confirm>

View File

@ -1,62 +1,83 @@
import ngModule from '../module'; import ngModule from '../module';
import Component from 'core/lib/component';
export default class Controller { export default class Controller extends Component {
constructor($scope, $http, vnApp, $translate) {
this.$ = $scope;
this.$http = $http;
this.vnApp = vnApp;
this.translate = $translate;
this.isEqualizated = undefined;
this.copyData();
}
$onChanges() {
this.copyData();
}
copyData() {
if (this.client)
this.isEqualizated = this.client.isEqualizated;
}
buyerHaspermissions() {
if (!this.client) return true;
return !this.client.isTaxDataChecked;
}
onSubmit() { onSubmit() {
if (this.isEqualizated != this.client.isEqualizated) { const orgData = this.$.watcher.orgData;
this.oldHasToInvoiceByAddress = this.client.hasToInvoiceByAddress; delete this.client.despiteOfClient;
this.client.hasToInvoiceByAddress = false; if (!orgData.isTaxDataChecked && this.client.isTaxDataChecked)
} this.checkExistingClient();
else this.save();
return this.$.watcher.submit().then(
() => this.checkEtChanges());
} }
checkEtChanges() { checkExistingClient() {
let equals = this.isEqualizated == this.client.isEqualizated; const filterObj = {
this.isEqualizated = this.client.isEqualizated; where: {
and: [
{or: [{email: this.client.email}, {phone: this.client.phone}]},
{id: {neq: this.client.id}}
]
}
};
if (!equals && !this.oldHasToInvoiceByAddress) const $t = this.$translate.instant;
const filter = encodeURIComponent(JSON.stringify(filterObj));
const query = `Clients/findOne?filter=${filter}`;
this.$http.get(query).then(res => {
if (res.data.id) {
const params = {clientId: res.data.id};
const question = $t('Found a client with this phone or email', params, null, null, 'sanitizeParameters');
this.client.despiteOfClient = params.clientId;
this.$.confirmDuplicatedClient.question = question;
this.$.confirmDuplicatedClient.show();
}
});
}
checkEtChanges(orgData) {
const equalizatedHasChanged = orgData.isEqualizated != this.client.isEqualizated;
const hasToInvoiceByAddress = orgData.hasToInvoiceByAddress || this.client.hasToInvoiceByAddress;
if (equalizatedHasChanged && hasToInvoiceByAddress)
this.$.propagateIsEqualizated.show(); this.$.propagateIsEqualizated.show();
else if (!equals) else if (equalizatedHasChanged)
this.returnDialogEt('accept'); return this.onAcceptEt();
delete this.oldHasToInvoiceByAddress; return this.$q.resolve();
} }
returnDialogEt(response) { onAcceptEt() {
if (response === 'accept') { const query = `Clients/${this.client.id}/addressesPropagateRe`;
this.$http.patch(`Clients/${this.client.id}/addressesPropagateRe`, {isEqualizated: this.client.isEqualizated}).then( return this.$http.patch(query, {isEqualizated: this.client.isEqualizated}).then(
() => this.vnApp.showMessage(this.translate.instant('Equivalent tax spreaded')) () => this.vnApp.showMessage(this.$translate.instant('Equivalent tax spreaded'))
); );
} }
onAcceptDuplication() {
this.save();
return true;
}
save() {
const orgData = this.$.watcher.orgData;
const clonedOrgData = JSON.parse(JSON.stringify(orgData));
return this.$.watcher.submit().then(
() => this.checkEtChanges(clonedOrgData));
}
onChangeEqualizated(value) {
const orgData = this.$.watcher.orgData;
if (value === true)
this.client.hasToInvoiceByAddress = false;
else if (orgData.hasToInvoiceByAddress)
this.client.hasToInvoiceByAddress = true;
this.$.$apply();
} }
} }
Controller.$inject = ['$scope', '$http', 'vnApp', '$translate'];
ngModule.component('vnClientFiscalData', { ngModule.component('vnClientFiscalData', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,

View File

@ -1,9 +1,11 @@
import './index'; import './index';
import watcher from 'core/mocks/watcher';
describe('Client', () => { describe('Client', () => {
describe('Component vnClientFiscalData', () => { describe('Component vnClientFiscalData', () => {
let $httpBackend; let $httpBackend;
let $scope; let $scope;
let $element;
let controller; let controller;
beforeEach(ngModule('client')); beforeEach(ngModule('client'));
@ -11,16 +13,97 @@ describe('Client', () => {
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => { beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
controller = $componentController('vnClientFiscalData', {$scope}); $scope.watcher = watcher;
$scope.watcher.orgData = {id: 101, isEqualizated: false, isTaxDataChecked: false};
$element = angular.element('<vn-client-fiscal-data></client-fiscal-data>');
controller = $componentController('vnClientFiscalData', {$element, $scope});
controller.card = {reload: () => {}}; controller.card = {reload: () => {}};
controller.client = {
id: 101,
email: 'batman@gothamcity.com',
phone: '1111111111',
isEqualizated: false,
isTaxDataChecked: false
};
})); }));
describe('returnDialogEt()', () => { describe('onSubmit()', () => {
it('should call the save() method directly', () => {
spyOn(controller, 'save');
controller.onSubmit();
expect(controller.save).toHaveBeenCalledWith();
});
it('should call the checkExistingClient() if the isTaxDataChecked property is checked', () => {
spyOn(controller, 'save');
spyOn(controller, 'checkExistingClient');
controller.client.isTaxDataChecked = true;
controller.onSubmit();
expect(controller.save).not.toHaveBeenCalledWith();
expect(controller.checkExistingClient).toHaveBeenCalledWith();
});
});
describe('checkExistingClient()', () => {
it('should show a save confirmation when a duplicated client is found and then set the despiteOfClient property', () => {
controller.$.confirmDuplicatedClient = {show: () => {}};
spyOn(controller.$.confirmDuplicatedClient, 'show');
const filterObj = {
where: {
and: [
{or: [{email: controller.client.email}, {phone: controller.client.phone}]},
{id: {neq: controller.client.id}}
]
}
};
const expectedClient = {id: 102};
const filter = encodeURIComponent(JSON.stringify(filterObj));
$httpBackend.expect('GET', `Clients/findOne?filter=${filter}`).respond(expectedClient);
controller.checkExistingClient();
$httpBackend.flush();
expect(controller.$.confirmDuplicatedClient.show).toHaveBeenCalledWith();
expect(controller.client.despiteOfClient).toEqual(102);
});
});
describe('checkEtChanges()', () => {
it(`should show a propagation confirmation if isEqualizated property is changed and invoice by address is checked`, () => {
controller.$.propagateIsEqualizated = {show: () => {}};
spyOn(controller.$.propagateIsEqualizated, 'show');
const orgData = $scope.watcher.orgData;
orgData.hasToInvoiceByAddress = true;
controller.client.isEqualizated = true;
controller.checkEtChanges(orgData);
expect(controller.$.propagateIsEqualizated.show).toHaveBeenCalledWith();
});
it(`should call to the onAcceptEt() method if isEqualizated property is changed and invoice by address isn't checked`, () => {
spyOn(controller, 'onAcceptEt');
const orgData = $scope.watcher.orgData;
orgData.hasToInvoiceByAddress = false;
controller.client.isEqualizated = true;
controller.checkEtChanges(orgData);
expect(controller.onAcceptEt).toHaveBeenCalledWith();
});
});
describe('onAcceptEt()', () => {
it('should request to patch the propagation of tax status', () => { it('should request to patch the propagation of tax status', () => {
controller.client = {id: 123, isEqualizated: false}; controller.client = {id: 123, isEqualizated: false};
$httpBackend.when('PATCH', `Clients/${controller.client.id}/addressesPropagateRe`, {isEqualizated: controller.client.isEqualizated}).respond('done'); $httpBackend.when('PATCH', `Clients/${controller.client.id}/addressesPropagateRe`, {isEqualizated: controller.client.isEqualizated}).respond('done');
$httpBackend.expectPATCH(`Clients/${controller.client.id}/addressesPropagateRe`, {isEqualizated: controller.client.isEqualizated}); $httpBackend.expectPATCH(`Clients/${controller.client.id}/addressesPropagateRe`, {isEqualizated: controller.client.isEqualizated});
controller.returnDialogEt('accept'); controller.onAcceptEt();
$httpBackend.flush(); $httpBackend.flush();
}); });
}); });

View File

@ -0,0 +1 @@
Found a client with this phone or email: The client with id <a href="#!/client/{{clientId}}/summary" target="_blank">{{clientId}}</a> already has this phone or email. <br/> ¿Do you want to continue?

View File

@ -4,3 +4,5 @@ Do you want to spread the change?: ¿Deseas propagar el cambio a sus consignatar
Frozen: Congelado Frozen: Congelado
In order to invoice, this field is not consulted, but the consignee's ET. When modifying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not.: Para facturar no se consulta este campo, sino el RE de consignatario. Al modificar este campo si no esta marcada la casilla Facturar por consignatario, se propagará automáticamente el cambio a todos los consignatarios, en caso contrario preguntará al usuario si quiere o no propagar. In order to invoice, this field is not consulted, but the consignee's ET. When modifying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not.: Para facturar no se consulta este campo, sino el RE de consignatario. Al modificar este campo si no esta marcada la casilla Facturar por consignatario, se propagará automáticamente el cambio a todos los consignatarios, en caso contrario preguntará al usuario si quiere o no propagar.
You can use letters and spaces: Se pueden utilizar letras y espacios You can use letters and spaces: Se pueden utilizar letras y espacios
Found a client with this data: Se ha encontrado un cliente con estos datos
Found a client with this phone or email: El cliente con id <a href="#!/client/{{clientId}}/summary" target="_blank">{{clientId}}</a> ya tiene este teléfono o email. <br/> ¿Quieres continuar?

View File

@ -0,0 +1,61 @@
<vn-data-viewer
model="$ctrl.model">
<vn-horizontal class="catalog-list">
<section class="product" ng-repeat="item in $ctrl.model.data">
<vn-card>
<div class="image">
<img
ng-src="//verdnatura.es/vn-image-data/catalog/200x200/{{::item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::item.image}}"
on-error-src/>
</div>
<div class="description">
<h3>
{{::item.name}}
</h3>
<h4 class="ellipsize">
<span translate-attr="::{title: item.subName}">{{::item.subName}}</span>
</h4>
<div class="tags">
<vn-label-value
ng-if="::item.value5"
label="{{::item.tag5}}"
value="{{::item.value5}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value6"
label="{{::item.tag6}}"
value="{{::item.value6}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value7"
label="{{::item.tag7}}"
value="{{::item.value7}}">
</vn-label-value>
</div>
<div class="footer">
<div class="price">
<vn-one>
<span>{{::item.available}}</span>
<span translate>from</span>
<span>{{::item.price | currency:'EUR':2}}</span>
</vn-one>
<vn-icon-button vn-none
icon="add_circle"
ng-click="$ctrl.preview($event, item)"
vn-tooltip="Add">
</vn-icon-button>
</div>
<div class="priceKg" ng-show="::item.priceKg">
<span>Precio por kilo {{::item.priceKg | currency: 'EUR'}}</span>
</div>
</div>
</div>
</vn-card>
</section>
</vn-horizontal>
</vn-data-viewer>
<vn-order-prices-popover
vn-id="pricesPopover"
order="$ctrl.order">
</vn-order-prices-popover>

View File

@ -0,0 +1,24 @@
import ngModule from '../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
preview(event, item) {
event.preventDefault();
this.$.pricesPopover.show(event, item);
}
onDescriptorLoad() {
this.$.popover.relocate();
}
}
ngModule.component('vnOrderCatalogView', {
template: require('./index.html'),
controller: Controller,
bindings: {
order: '<',
model: '<'
}
});

View File

@ -0,0 +1,31 @@
@import "variables";
vn-order-catalog {
.catalog-header {
border-bottom: $border-thin;
padding: $spacing-md;
align-items: center;
& > vn-one {
display: flex;
flex: 1;
span {
color: $color-font-secondary
}
}
& > vn-auto {
width: 28em;
display: flex;
overflow: hidden;
& > * {
padding-left: $spacing-md;
}
}
}
.catalog-list {
padding-top: $spacing-sm;
}
}

View File

@ -1,72 +1,140 @@
<vn-crud-model
url="ItemCategories"
data="categories"
auto-load="true">
</vn-crud-model>
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="Orders/CatalogFilter" url="Orders/CatalogFilter"
params="{orderFk: $ctrl.$state.params.id}" params="{orderFk: $ctrl.$state.params.id}"
limit="50" limit="50"
data="items" data="$ctrl.items">
on-data-change="$ctrl.onDataChange()">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer
model="model"> <vn-order-catalog-view model="model"
<vn-horizontal class="catalog-list">
<section class="product" ng-repeat="item in items">
<vn-card>
<div class="image">
<img
ng-src="//verdnatura.es/vn-image-data/catalog/200x200/{{::item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::item.image}}"
on-error-src/>
</div>
<div class="description">
<h3>
{{::item.name}}
</h3>
<h4 class="ellipsize">
<span translate-attr="::{title: item.subName}">{{::item.subName}}</span>
</h4>
<div class="tags">
<vn-label-value
ng-if="::item.value5"
label="{{::item.tag5}}"
value="{{::item.value5}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value6"
label="{{::item.tag6}}"
value="{{::item.value6}}">
</vn-label-value>
<vn-label-value
ng-if="::item.value7"
label="{{::item.tag7}}"
value="{{::item.value7}}">
</vn-label-value>
</div>
<div class="footer">
<div class="price">
<vn-one>
<span>{{::item.available}}</span>
<span translate>from</span>
<span>{{::item.price | currency:'EUR':2}}</span>
</vn-one>
<vn-icon-button vn-none
icon="add_circle"
ng-click="$ctrl.preview($event, item)"
vn-tooltip="Add">
</vn-icon-button>
</div>
<div class="priceKg" ng-show="::item.priceKg">
<span>Precio por kilo {{::item.priceKg | currency: 'EUR'}}</span>
</div>
</div>
</div>
</vn-card>
</section>
</vn-horizontal>
</vn-data-viewer>
<vn-side-menu side="right">
<vn-catalog-filter order="$ctrl.order"></vn-catalog-filter>
</vn-side-menu>
<vn-order-prices-popover
vn-id="pricesPopover"
order="$ctrl.order"> order="$ctrl.order">
</vn-order-prices-popover> </vn-order-catalog-view>
<vn-side-menu side="right">
<vn-horizontal class="item-category">
<vn-autocomplete vn-id="category"
data="categories"
ng-model="$ctrl.categoryId"
show-field="name"
value-field="id"
label="Category">
</vn-autocomplete>
<vn-one ng-repeat="category in categories">
<vn-icon
ng-class="{'active': $ctrl.categoryId == category.id}"
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.categoryId = category.id">
</vn-icon>
</vn-one>
</vn-horizontal>
<vn-vertical class="input">
<vn-autocomplete vn-id="type"
data="$ctrl.itemTypes"
ng-model="$ctrl.typeId"
show-field="name"
value-field="id"
label="Type"
fields="['categoryFk']"
include="'category'">
<tpl-item>
<div>{{name}}</div>
<div class="text-caption text-secondary">
{{categoryName}}
</div>
</tpl-item>
</vn-autocomplete>
</vn-vertical>
<vn-vertical class="input vn-pt-md">
<vn-autocomplete
vn-id="field"
data="$ctrl.orderFields"
ng-model="$ctrl.orderField"
selection="$ctrl.orderSelection"
translate-fields="['name']"
order="name"
show-field="name"
value-field="field"
label="Order by"
disabled="!model.data">
</vn-autocomplete>
<vn-autocomplete
data="$ctrl.orderWays"
ng-model="$ctrl.orderWay"
translate-fields="['name']"
show-field="name"
value-field="way"
label="Order"
disabled="!model.data">
</vn-autocomplete>
<div ng-if="false && model.moreRows">
<span translate>More than</span> {{model.limit}} <span translate>results</span>
</div>
</vn-vertical>
<vn-vertical class="input vn-pt-md">
<vn-textfield
ng-keyUp="$ctrl.onSearchById($event)"
label="Item id"
ng-model="$ctrl.itemId">
<prepend>
<vn-icon icon="icon-item"></vn-icon>
</prepend>
</vn-textfield>
<vn-textfield
vn-one
vn-id="search"
ng-keyUp="$ctrl.onSearchByTag($event)"
label="Search tag"
ng-model="$ctrl.value">
<prepend>
<vn-icon icon="search"></vn-icon>
</prepend>
<append>
<vn-icon
icon="keyboard_arrow_down"
ng-click="$ctrl.openPanel($event)"
style="cursor: pointer;">
</vn-icon>
</append>
</vn-textfield>
</vn-vertical>
<vn-popover
vn-id="popover"
on-close="$ctrl.onPopoverClose()">
<vn-order-catalog-search-panel
filter="panelFilter"
on-submit="$ctrl.onPanelSubmit($filter)">
</vn-order-catalog-search-panel>
</vn-popover>
<div class="chips">
<vn-chip
ng-if="category.selection"
removable="true"
translate-attr="{title: 'Category'}"
on-remove="$ctrl.categoryId = null"
class="colored">
<span translate>{{category.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="type.selection"
removable="true"
translate-attr="{title: 'Type'}"
on-remove="$ctrl.typeId = null"
class="colored">
<span translate>{{type.selection.name}}</span>
</vn-chip>
<vn-chip
ng-repeat="tag in $ctrl.tags"
removable="true"
translate-attr="{title: 'Tag'}"
on-remove="$ctrl.remove($index)"
class="colored">
<span translate>{{::tag.value}}</span>
</vn-chip>
</div>
</vn-side-menu>

View File

@ -2,10 +2,15 @@ import ngModule from '../module';
import './style.scss'; import './style.scss';
class Controller { class Controller {
constructor($scope, $state) { constructor($http, $scope, $state, $compile, $transitions) {
this.$scope = $scope; this.$http = $http;
this.$ = $scope;
this.$state = $state; this.$state = $state;
this.$stateParams = $state.params; this.$stateParams = $state.params;
this.$compile = $compile;
this.$transitions = $transitions;
this.itemTypes = [];
this.tags = [];
// Static autocomplete data // Static autocomplete data
this.orderWays = [ this.orderWays = [
@ -23,16 +28,49 @@ class Controller {
this.orderField = this.orderFields[0].field; this.orderField = this.orderFields[0].field;
} }
$onChanges() {
if (this.order && this.order.isConfirmed)
this.$state.go('order.card.line');
}
/** /**
* Fills order autocomplete with tags * Fills order autocomplete with tags
* obtained from last filtered * obtained from last filtered
*/ */
onDataChange() { get order() {
const items = this.$scope.model.data; return this._order;
const newFilterList = []; }
if (!items) return;
items.forEach(item => { /**
* Sets filter values from state params
*
* @param {Object} value - Order data
*/
set order(value) {
this._order = value;
if (!value) return;
this.$.$applyAsync(() => {
if (this.$stateParams.categoryId)
this.categoryId = this.$stateParams.categoryId;
if (this.$stateParams.typeId)
this.typeId = this.$stateParams.typeId;
});
}
get items() {
return this._items;
}
set items(value) {
this._items = value;
if (!value) return;
const newFilterList = [];
value.forEach(item => {
// Add new tag filters // Add new tag filters
item.tags.forEach(itemTag => { item.tags.forEach(itemTag => {
const alreadyAdded = newFilterList.findIndex(filter => { const alreadyAdded = newFilterList.findIndex(filter => {
@ -64,6 +102,40 @@ class Controller {
this.orderFields = newFilterList; this.orderFields = newFilterList;
} }
get categoryId() {
return this._categoryId;
}
set categoryId(value) {
if (!value || (this.categoryId == value))
value = null;
this._categoryId = value;
this.itemTypes = [];
this.typeId = null;
this.updateStateParams();
if (this.tags.length > 0)
this.applyFilters();
if (value)
this.updateItemTypes();
}
get typeId() {
return this._typeId;
}
set typeId(value) {
this._typeId = value;
this.updateStateParams();
if (value || this.tags.length > 0)
this.applyFilters();
}
/** /**
* Get order way ASC/DESC * Get order way ASC/DESC
*/ */
@ -76,6 +148,9 @@ class Controller {
if (value) this.applyOrder(); if (value) this.applyOrder();
} }
/**
* Returns the order way selection
*/
get orderSelection() { get orderSelection() {
return this._orderSelection; return this._orderSelection;
} }
@ -86,6 +161,14 @@ class Controller {
if (value) this.applyOrder(); if (value) this.applyOrder();
} }
/**
* Apply order to model
*/
applyOrder() {
if (this.typeId || this.tags.length > 0)
this.$.model.addFilter(null, {orderBy: this.getOrderBy()});
}
/** /**
* Returns order param * Returns order param
* *
@ -101,33 +184,101 @@ class Controller {
} }
/** /**
* Apply order to model * Refreshes item type dropdown data
*/ */
applyOrder() { updateItemTypes() {
this.$scope.model.addFilter(null, {orderBy: this.getOrderBy()}); let params = {
itemCategoryId: this.categoryId
};
const query = `Orders/${this.order.id}/getItemTypeAvailable`;
this.$http.get(query, {params}).then(res =>
this.itemTypes = res.data);
} }
preview(event, item) { onSearchById(event) {
const hasValue = this.tags.length > 0 || this.itemId || this.typeId;
if (event.key === 'Enter' && hasValue)
this.applyFilters();
}
onSearchByTag(event) {
if (event.key !== 'Enter' || !this.value) return;
this.tags.push({
value: this.value,
});
this.$.search.value = null;
this.applyFilters();
}
remove(index) {
this.tags.splice(index, 1);
if (this.tags.length >= 0 || this.itemId || this.typeId)
this.applyFilters();
}
applyFilters() {
let newParams = {};
let newFilter = {};
const model = this.$.model;
if (this.categoryId)
newFilter.categoryFk = this.categoryId;
if (this.typeId)
newFilter.typeFk = this.typeId;
if (this.itemId)
newFilter = {'i.id': this.itemId};
newParams = {
orderFk: this.order.id,
orderBy: this.getOrderBy(),
tags: this.tags,
};
model.applyFilter({where: newFilter}, newParams);
}
openPanel(event) {
if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
this.$scope.pricesPopover.show(event, item);
this.panelFilter = {};
this.$.popover.show(this.$.search.element);
} }
onDescriptorLoad() { onPanelSubmit(filter) {
this.$scope.popover.relocate(); this.$.popover.hide();
this.tags.push(filter);
this.applyFilters();
} }
$onChanges() { /**
if (this.order && this.order.isConfirmed) * Updates url state params from filter values
this.$state.go('order.card.line'); */
updateStateParams() {
const params = {};
if (this.categoryId)
params.categoryId = this.categoryId;
else params.categoryId = undefined;
if (this.typeId)
params.typeId = this.typeId;
else params.typeId = undefined;
this.$state.go(this.$state.current.name, params);
} }
} }
Controller.$inject = ['$scope', '$state']; Controller.$inject = ['$http', '$scope', '$state', '$compile', '$transitions'];
ngModule.component('vnOrderCatalog', { ngModule.component('vnOrderCatalog', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
order: '<', order: '<'
} }
}); });

View File

@ -4,26 +4,44 @@ import crudModel from 'core/mocks/crud-model';
describe('Order', () => { describe('Order', () => {
describe('Component vnOrderCatalog', () => { describe('Component vnOrderCatalog', () => {
let $scope; let $scope;
let $state;
let controller; let controller;
let $httpBackend;
beforeEach(ngModule('order')); beforeEach(ngModule('order'));
beforeEach(angular.mock.inject(($componentController, $rootScope) => { beforeEach(angular.mock.inject(($componentController, _$state_, _$httpBackend_, $rootScope) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$scope.model = crudModel; $scope.model = crudModel;
$scope.field = {}; $scope.search = {};
controller = $componentController('vnOrderCatalog', {$scope: $scope}); $state = _$state_;
$state.params.categoryId = 1;
$state.params.typeId = 2;
$state.current.name = 'my.current.state';
controller = $componentController('vnOrderCatalog', {$scope, $state});
})); }));
describe('onDataChange()', () => { describe('order() setter', () => {
it(`should call scope $applyAsync() method and apply filters from state params`, () => {
$httpBackend.expect('GET', `Orders/4/getItemTypeAvailable?itemCategoryId=1`).respond();
controller.order = {id: 4};
$scope.$apply();
expect(controller.categoryId).toEqual(1);
expect(controller.typeId).toEqual(2);
});
});
describe('items() setter', () => {
it(`should return an object with order params`, () => { it(`should return an object with order params`, () => {
$scope.model.data = [{id: 1, name: 'My Item', tags: [ let expectedResult = [{field: 'showOrder, price', name: 'Color'}];
let unexpectedResult = [{tagFk: 5, name: 'Color'}];
controller.items = [{id: 1, name: 'My Item', tags: [
{tagFk: 4, name: 'Length'}, {tagFk: 4, name: 'Length'},
{tagFk: 5, name: 'Color'} {tagFk: 5, name: 'Color'}
]}]; ]}];
let expectedResult = [{field: 'showOrder, price', name: 'Color'}];
let unexpectedResult = [{tagFk: 5, name: 'Color'}];
controller.onDataChange();
expect(controller.orderFields.length).toEqual(5); expect(controller.orderFields.length).toEqual(5);
expect(controller.orderFields).toEqual(jasmine.arrayContaining(expectedResult)); expect(controller.orderFields).toEqual(jasmine.arrayContaining(expectedResult));
@ -31,6 +49,134 @@ describe('Order', () => {
}); });
}); });
describe('categoryId() setter', () => {
it(`should set category property to null, call updateStateParams() method and not call applyFilters()`, () => {
spyOn(controller, 'updateStateParams');
controller.categoryId = null;
expect(controller.updateStateParams).toHaveBeenCalledWith();
});
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
spyOn(controller, 'updateStateParams');
controller._order = {id: 4};
controller.categoryId = 2;
expect(controller.updateStateParams).toHaveBeenCalledWith();
});
});
describe('typeId() setter', () => {
it(`should set type property to null, call updateStateParams() method and not call applyFilters()`, () => {
spyOn(controller, 'updateStateParams');
spyOn(controller, 'applyFilters');
controller.typeId = null;
expect(controller.updateStateParams).toHaveBeenCalledWith();
expect(controller.applyFilters).not.toHaveBeenCalledWith();
});
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
spyOn(controller, 'updateStateParams');
spyOn(controller, 'applyFilters');
controller.typeId = 2;
expect(controller.updateStateParams).toHaveBeenCalledWith();
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('onSearchByTag()', () => {
it(`should not add a new tag if the event key code doesn't equals to 'Enter'`, () => {
spyOn(controller, 'applyFilters');
controller.order = {id: 4};
controller.value = 'Color';
controller.onSearchByTag({key: 'Tab'});
expect(controller.applyFilters).not.toHaveBeenCalledWith();
});
it(`should add a new tag if the event key code equals to 'Enter' an then call applyFilters()`, () => {
spyOn(controller, 'applyFilters');
controller.order = {id: 4};
controller.value = 'Color';
controller.onSearchByTag({key: 'Enter'});
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('onSearchById()', () => {
it(`should not filter by id if the event key code doesn't equals to 'Enter'`, () => {
spyOn(controller, 'applyFilters');
controller.itemId = 1;
controller.onSearchById({key: 'Tab'});
expect(controller.applyFilters).not.toHaveBeenCalledWith();
});
it(`should filter by id if the event key code equals to 'Enter' an then call applyFilters()`, () => {
spyOn(controller, 'applyFilters');
controller.itemId = 1;
controller.onSearchById({key: 'Enter'});
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('applyFilters()', () => {
it(`should call model applyFilter() method with a new filter`, () => {
let model = controller.$.model;
spyOn(model, 'applyFilter');
controller._categoryId = 2;
controller._typeId = 4;
controller._order = {id: 4};
controller.applyFilters();
expect(model.applyFilter).toHaveBeenCalledWith(
{where: {categoryFk: 2, typeFk: 4}},
{orderFk: 4, orderBy: controller.getOrderBy(), tags: []});
});
});
describe('remove()', () => {
it(`should remove a tag from tags property`, () => {
spyOn(controller, 'applyFilters');
controller.tags = [{tagFk: 1, value: 'Blue'}, {tagFk: 2, value: '70'}];
controller.remove(0);
expect(controller.tags.length).toEqual(1);
expect(controller.tags[0].tagFk).toEqual(2);
expect(controller.applyFilters).toHaveBeenCalledWith();
});
it(`should remove a tag from tags property and call applyFilters() if there's no more tags`, () => {
spyOn(controller, 'applyFilters');
controller._categoryId = 1;
controller._typeId = 1;
controller.tags = [{tagFk: 1, value: 'Blue'}];
controller.remove(0);
expect(controller.tags.length).toEqual(0);
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('updateStateParams()', () => {
it(`should call state go() method passing category and type state params`, () => {
spyOn(controller.$state, 'go');
controller._categoryId = 2;
controller._typeId = 4;
let result = {categoryId: 2, typeId: 4};
controller.updateStateParams();
expect(controller.$state.go).toHaveBeenCalledWith('my.current.state', result);
});
});
describe('getOrderBy()', () => { describe('getOrderBy()', () => {
it(`should return an object with order params`, () => { it(`should return an object with order params`, () => {
controller.orderField = 'relevancy DESC, name'; controller.orderField = 'relevancy DESC, name';
@ -50,13 +196,15 @@ describe('Order', () => {
it(`should apply order param to model calling getOrderBy()`, () => { it(`should apply order param to model calling getOrderBy()`, () => {
controller.field = 'relevancy DESC, name'; controller.field = 'relevancy DESC, name';
controller.way = 'ASC'; controller.way = 'ASC';
controller._categoryId = 1;
controller._typeId = 1;
let expectedOrder = {orderBy: controller.getOrderBy()}; let expectedOrder = {orderBy: controller.getOrderBy()};
spyOn(controller, 'getOrderBy').and.callThrough(); spyOn(controller, 'getOrderBy').and.callThrough();
spyOn(controller.$scope.model, 'addFilter'); spyOn(controller.$.model, 'addFilter');
controller.applyOrder(); controller.applyOrder();
expect(controller.getOrderBy).toHaveBeenCalledWith(); expect(controller.getOrderBy).toHaveBeenCalledWith();
expect(controller.$scope.model.addFilter).toHaveBeenCalledWith(null, expectedOrder); expect(controller.$.model.addFilter).toHaveBeenCalledWith(null, expectedOrder);
}); });
}); });
}); });

View File

@ -1,31 +1,54 @@
@import "variables"; @import "variables";
vn-order-catalog { vn-order-catalog vn-side-menu div {
.catalog-header { & > .input {
padding-left: $spacing-md;
padding-right: $spacing-md;
border-color: $color-spacer;
border-bottom: $border-thin; border-bottom: $border-thin;
padding: $spacing-md; }
align-items: center; .item-category {
padding: $spacing-sm;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
vn-autocomplete[vn-id="category"] {
display: none
}
& > vn-one { & > vn-one {
display: flex; padding: $spacing-sm;
flex: 1; min-width: 33.33%;
text-align: center;
box-sizing: border-box;
span { & > vn-icon {
color: $color-font-secondary padding: $spacing-sm;
} background-color: $color-font-secondary;
} border-radius: 50%;
& > vn-auto { cursor: pointer;
width: 28em;
display: flex;
overflow: hidden;
& > * { &.active {
padding-left: $spacing-md; background-color: $color-main;
color: #FFF
}
& > i:before {
font-size: 32pt;
width: 1em;
height: 1em;
}
} }
} }
} }
.catalog-list { .chips {
padding-top: $spacing-sm; display: flex;
flex-wrap: wrap;
padding: $spacing-md;
overflow: hidden;
max-width: 100%;
}
vn-autocomplete[vn-id="type"] .list {
max-height: 20em
} }
} }

View File

@ -1,129 +0,0 @@
<vn-crud-model
vn-id="model"
url="ItemCategories"
data="categories"
auto-load="true">
</vn-crud-model>
<div>
<vn-horizontal class="item-category">
<vn-autocomplete vn-id="category"
data="categories"
ng-model="$ctrl.categoryId"
show-field="name"
value-field="id"
label="Category">
</vn-autocomplete>
<vn-one ng-repeat="category in categories">
<vn-icon
ng-class="{'active': $ctrl.categoryId == category.id}"
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.categoryId = category.id">
</vn-icon>
</vn-one>
</vn-horizontal>
<vn-vertical class="input">
<vn-autocomplete vn-id="type"
data="$ctrl.itemTypes"
ng-model="$ctrl.typeId"
show-field="name"
value-field="id"
label="Type"
fields="['categoryFk']"
include="'category'">
<tpl-item>
<div>{{name}}</div>
<div class="text-caption text-secondary">
{{categoryName}}
</div>
</tpl-item>
</vn-autocomplete>
</vn-vertical>
<vn-vertical class="input vn-pt-md">
<vn-autocomplete
vn-id="field"
data="$ctrl.catalog.orderFields"
ng-model="$ctrl.catalog.orderField"
selection="$ctrl.catalog.orderSelection"
translate-fields="['name']"
order="name"
show-field="name"
value-field="field"
label="Order by"
disabled="!model.data">
</vn-autocomplete>
<vn-autocomplete
data="$ctrl.catalog.orderWays"
ng-model="$ctrl.catalog.orderWay"
translate-fields="['name']"
show-field="name"
value-field="way"
label="Order"
disabled="!model.data">
</vn-autocomplete>
<div ng-if="false && model.moreRows">
<span translate>More than</span> {{model.limit}} <span translate>results</span>
</div>
</vn-vertical>
<vn-vertical class="input vn-pt-md">
<vn-textfield
ng-keyUp="$ctrl.onSearchById($event)"
label="Item id"
ng-model="$ctrl.itemId">
<prepend>
<vn-icon icon="icon-item"></vn-icon>
</prepend>
</vn-textfield>
<vn-textfield
vn-one
vn-id="search"
ng-keyUp="$ctrl.onSearchByTag($event)"
label="Search tag"
ng-model="$ctrl.value">
<prepend>
<vn-icon icon="search"></vn-icon>
</prepend>
<append>
<vn-icon
icon="keyboard_arrow_down"
ng-click="$ctrl.openPanel($event)"
style="cursor: pointer;">
</vn-icon>
</append>
</vn-textfield>
</vn-vertical>
<vn-popover
vn-id="popover"
on-close="$ctrl.onPopoverClose()">
<vn-order-catalog-search-panel
filter="panelFilter"
on-submit="$ctrl.onPanelSubmit($filter)">
</vn-order-catalog-search-panel>
</vn-popover>
<div class="chips">
<vn-chip
ng-if="category.selection"
removable="true"
translate-attr="{title: 'Category'}"
on-remove="$ctrl.categoryId = null"
class="colored">
<span translate>{{category.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="type.selection"
removable="true"
translate-attr="{title: 'Type'}"
on-remove="$ctrl.typeId = null"
class="colored">
<span translate>{{type.selection.name}}</span>
</vn-chip>
<vn-chip
ng-repeat="tag in $ctrl.tags"
removable="true"
translate-attr="{title: 'Tag'}"
on-remove="$ctrl.remove($index)"
class="colored">
<span translate>{{::tag.value}}</span>
</vn-chip>
</div>
</div>

View File

@ -1,175 +0,0 @@
import ngModule from '../module';
import './style.scss';
class Controller {
constructor($element, $http, $scope, $state, $compile, $transitions) {
this.$element = $element;
this.$http = $http;
this.$ = $scope;
this.$state = $state;
this.$stateParams = $state.params;
this.$compile = $compile;
this.$transitions = $transitions;
this.itemTypes = [];
this.tags = [];
}
get order() {
return this._order;
}
/**
* Sets filter values from state params
*
* @param {Object} value - Order data
*/
set order(value) {
this._order = value;
if (!value) return;
this.$.$applyAsync(() => {
if (this.$stateParams.categoryId)
this.categoryId = this.$stateParams.categoryId;
if (this.$stateParams.typeId)
this.typeId = this.$stateParams.typeId;
});
}
get categoryId() {
return this._categoryId;
}
set categoryId(value) {
if (!value || (this.categoryId == value))
value = null;
this._categoryId = value;
this.itemTypes = [];
this.typeId = null;
this.updateStateParams();
if (this.tags.length > 0)
this.applyFilters();
if (value)
this.updateItemTypes();
}
get typeId() {
return this._typeId;
}
set typeId(value) {
this._typeId = value;
this.updateStateParams();
if ((value) || this.tags.length > 0)
this.applyFilters();
}
/**
* Refreshes item type dropdown data
*/
updateItemTypes() {
let params = {
itemCategoryId: this.categoryId
};
const query = `Orders/${this.order.id}/getItemTypeAvailable`;
this.$http.get(query, {params}).then(res =>
this.itemTypes = res.data);
}
onSearchById(event) {
const hasValue = this.tags.length > 0 || this.itemId || this.typeId;
if (event.key === 'Enter' && hasValue)
this.applyFilters();
}
onSearchByTag(event) {
if (event.key !== 'Enter' || !this.value) return;
this.tags.push({
value: this.value,
});
this.$.search.value = null;
this.applyFilters();
}
remove(index) {
this.tags.splice(index, 1);
if (this.tags.length >= 0 || this.itemId || this.typeId)
this.applyFilters();
}
applyFilters() {
let newParams = {};
let newFilter = {};
const model = this.catalog.$scope.model;
if (this.categoryId)
newFilter.categoryFk = this.categoryId;
if (this.typeId)
newFilter.typeFk = this.typeId;
if (this.itemId)
newFilter = {'i.id': this.itemId};
newParams = {
orderFk: this.order.id,
orderBy: this.catalog.getOrderBy(),
tags: this.tags,
};
model.applyFilter({where: newFilter}, newParams);
}
openPanel(event) {
if (event.defaultPrevented) return;
event.preventDefault();
this.panelFilter = {};
this.$.popover.show(this.$.search.element);
}
onPanelSubmit(filter) {
this.$.popover.hide();
this.tags.push(filter);
this.applyFilters();
}
/**
* Updates url state params from filter values
*/
updateStateParams() {
const params = {};
if (this.categoryId)
params.categoryId = this.categoryId;
else params.categoryId = undefined;
if (this.typeId)
params.typeId = this.typeId;
else params.typeId = undefined;
this.$state.go(this.$state.current.name, params);
}
}
Controller.$inject = ['$element', '$http', '$scope', '$state', '$compile', '$transitions'];
ngModule.component('vnCatalogFilter', {
template: require('./index.html'),
controller: Controller,
require: {
catalog: '^vnOrderCatalog'
},
bindings: {
order: '<'
}
});

View File

@ -1,172 +0,0 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('Order', () => {
describe('Component vnCatalogFilter', () => {
let $scope;
let $state;
let controller;
let $httpBackend;
beforeEach(ngModule('order'));
beforeEach(angular.mock.inject(($componentController, _$state_, _$httpBackend_, $rootScope) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$scope.model = crudModel;
$scope.search = {};
$state = _$state_;
$state.params.categoryId = 1;
$state.params.typeId = 2;
$state.current.name = 'my.current.state';
controller = $componentController('vnCatalogFilter', {$element: null, $scope, $state});
controller.catalog = {
$scope: $scope,
getOrderBy: () => {
return {field: 'relevancy DESC, name', way: 'DESC'};
}
};
}));
describe('order() setter', () => {
it(`should call scope $applyAsync() method and apply filters from state params`, () => {
$httpBackend.expect('GET', `Orders/4/getItemTypeAvailable?itemCategoryId=1`).respond();
controller.order = {id: 4};
$scope.$apply();
expect(controller.categoryId).toEqual(1);
expect(controller.typeId).toEqual(2);
});
});
describe('categoryId() setter', () => {
it(`should set category property to null, call updateStateParams() method and not call applyFilters()`, () => {
spyOn(controller, 'updateStateParams');
controller.categoryId = null;
expect(controller.updateStateParams).toHaveBeenCalledWith();
});
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
spyOn(controller, 'updateStateParams');
controller._order = {id: 4};
controller.categoryId = 2;
expect(controller.updateStateParams).toHaveBeenCalledWith();
});
});
describe('typeId() setter', () => {
it(`should set type property to null, call updateStateParams() method and not call applyFilters()`, () => {
spyOn(controller, 'updateStateParams');
spyOn(controller, 'applyFilters');
controller.typeId = null;
expect(controller.updateStateParams).toHaveBeenCalledWith();
expect(controller.applyFilters).not.toHaveBeenCalledWith();
});
it(`should set category property and then call updateStateParams() and applyFilters() methods`, () => {
spyOn(controller, 'updateStateParams');
spyOn(controller, 'applyFilters');
controller.typeId = 2;
expect(controller.updateStateParams).toHaveBeenCalledWith();
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('onSearchByTag()', () => {
it(`should not add a new tag if the event key code doesn't equals to 'Enter'`, () => {
spyOn(controller, 'applyFilters');
controller.order = {id: 4};
controller.value = 'Color';
controller.onSearchByTag({key: 'Tab'});
expect(controller.applyFilters).not.toHaveBeenCalledWith();
});
it(`should add a new tag if the event key code equals to 'Enter' an then call applyFilters()`, () => {
spyOn(controller, 'applyFilters');
controller.order = {id: 4};
controller.value = 'Color';
controller.onSearchByTag({key: 'Enter'});
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('onSearchById()', () => {
it(`should not filter by id if the event key code doesn't equals to 'Enter'`, () => {
spyOn(controller, 'applyFilters');
controller.itemId = 1;
controller.onSearchById({key: 'Tab'});
expect(controller.applyFilters).not.toHaveBeenCalledWith();
});
it(`should filter by id if the event key code equals to 'Enter' an then call applyFilters()`, () => {
spyOn(controller, 'applyFilters');
controller.itemId = 1;
controller.onSearchById({key: 'Enter'});
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('applyFilters()', () => {
it(`should call model applyFilter() method with a new filter`, () => {
let model = controller.catalog.$scope.model;
spyOn(model, 'applyFilter');
controller._categoryId = 2;
controller._typeId = 4;
controller._order = {id: 4};
controller.applyFilters();
expect(model.applyFilter).toHaveBeenCalledWith(
{where: {categoryFk: 2, typeFk: 4}},
{orderFk: 4, orderBy: controller.catalog.getOrderBy(), tags: []});
});
});
describe('remove()', () => {
it(`should remove a tag from tags property`, () => {
spyOn(controller, 'applyFilters');
controller.tags = [{tagFk: 1, value: 'Blue'}, {tagFk: 2, value: '70'}];
controller.remove(0);
expect(controller.tags.length).toEqual(1);
expect(controller.tags[0].tagFk).toEqual(2);
expect(controller.applyFilters).toHaveBeenCalledWith();
});
it(`should remove a tag from tags property and call applyFilters() if there's no more tags`, () => {
spyOn(controller, 'applyFilters');
controller._categoryId = 1;
controller._typeId = 1;
controller.tags = [{tagFk: 1, value: 'Blue'}];
controller.remove(0);
expect(controller.tags.length).toEqual(0);
expect(controller.applyFilters).toHaveBeenCalledWith();
});
});
describe('updateStateParams()', () => {
it(`should call state go() method passing category and type state params`, () => {
spyOn(controller.$state, 'go');
controller._categoryId = 2;
controller._typeId = 4;
let result = {categoryId: 2, typeId: 4};
controller.updateStateParams();
expect(controller.$state.go).toHaveBeenCalledWith('my.current.state', result);
});
});
});
});

View File

@ -1,55 +0,0 @@
@import "variables";
@import "variables";
vn-catalog-filter > div {
& > .input {
padding-left: $spacing-md;
padding-right: $spacing-md;
border-color: $color-spacer;
border-bottom: $border-thin;
}
.item-category {
padding: $spacing-sm;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
vn-autocomplete[vn-id="category"] {
display: none
}
& > vn-one {
padding: $spacing-sm;
min-width: 33.33%;
text-align: center;
box-sizing: border-box;
& > vn-icon {
padding: $spacing-sm;
background-color: $color-font-secondary;
border-radius: 50%;
cursor: pointer;
&.active {
background-color: $color-main;
color: #FFF
}
& > i:before {
font-size: 32pt;
width: 1em;
height: 1em;
}
}
}
}
.chips {
display: flex;
flex-wrap: wrap;
padding: $spacing-md;
overflow: hidden;
max-width: 100%;
}
vn-autocomplete[vn-id="type"] .list {
max-height: 20em
}
}

View File

@ -6,9 +6,9 @@ import './card';
import './descriptor'; import './descriptor';
import './search-panel'; import './search-panel';
import './catalog-search-panel'; import './catalog-search-panel';
import './filter'; import './catalog-view';
import './summary';
import './catalog'; import './catalog';
import './summary';
import './line'; import './line';
import './prices-popover'; import './prices-popover';
import './volume'; import './volume';

View File

@ -32,6 +32,13 @@
, {{::street}}, {{::city}}, {{::province.name}} - {{::agencyMode.name}} , {{::street}}, {{::city}}, {{::province.name}} - {{::agencyMode.name}}
</span> </span>
</tpl-item> </tpl-item>
<append>
<vn-icon-button
ui-sref="client.card.address.edit({id: $ctrl.clientId, addressId: $ctrl.addressId})"
icon="edit"
vn-tooltip="Edit address">
</vn-icon-button>
</append>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
url="Warehouses" url="Warehouses"

View File

@ -47,12 +47,11 @@ class Controller {
}}; }};
this.$http.get('DmsTypes/findOne', {params}).then(res => { this.$http.get('DmsTypes/findOne', {params}).then(res => {
const dmsTypeId = res.data && res.data.id; const dmsTypeId = res.data && res.data.id;
const companyId = this.vnConfig.companyFk;
const warehouseId = this.vnConfig.warehouseFk; const warehouseId = this.vnConfig.warehouseFk;
const defaultParams = { const defaultParams = {
reference: this.ticket.id, reference: this.ticket.id,
warehouseId: warehouseId, warehouseId: warehouseId,
companyId: companyId, companyId: this.ticket.companyFk,
dmsTypeId: dmsTypeId, dmsTypeId: dmsTypeId,
description: this.$translate.instant('FileDescription', { description: this.$translate.instant('FileDescription', {
ticketId: this.ticket.id, ticketId: this.ticket.id,

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('Worker absences()', () => { // 2066
xdescribe('Worker absences()', () => {
it('should get the absence calendar for a full year contract', async() => { it('should get the absence calendar for a full year contract', async() => {
let ctx = {req: {accessToken: {userId: 106}}}; let ctx = {req: {accessToken: {userId: 106}}};
let workerFk = 106; let workerFk = 106;
@ -133,6 +134,7 @@ describe('Worker absences()', () => {
let remainingDays = 0; let remainingDays = 0;
for (let i = today.getMonth(); i < 12; i++) { for (let i = today.getMonth(); i < 12; i++) {
today.setDate(1);
today.setMonth(i + 1); today.setMonth(i + 1);
today.setDate(0); today.setDate(0);

View File

@ -36,8 +36,8 @@ module.exports = Self => {
Self.getWorkedHours = async(id, started, ended) => { Self.getWorkedHours = async(id, started, ended) => {
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const stmts = []; const stmts = [];
const startedMinusOne = new Date(); const startedMinusOne = new Date(started);
const endedPlusOne = new Date(); const endedPlusOne = new Date(ended);
let worker = await Self.app.models.Worker.findById(id); let worker = await Self.app.models.Worker.findById(id);
let userId = worker.userFk; let userId = worker.userFk;
@ -61,7 +61,6 @@ module.exports = Self => {
tmp.timeControlCalculate, tmp.timeControlCalculate,
tmp.timeBusinessCalculate tmp.timeBusinessCalculate
`); `);
let sql = ParameterizedSQL.join(stmts, ';'); let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql); let result = await conn.executeStmt(sql);

View File

@ -8,10 +8,10 @@
"Holiday": { "Holiday": {
"dataSource": "vn" "dataSource": "vn"
}, },
"HolidayDetail": { "CalendarHolidaysName": {
"dataSource": "vn" "dataSource": "vn"
}, },
"HolidayType": { "CalendarHolidaysType": {
"dataSource": "vn" "dataSource": "vn"
}, },
"WorkCenter": { "WorkCenter": {

View File

@ -1,9 +1,9 @@
{ {
"name": "HolidayDetail", "name": "CalendarHolidaysName",
"base": "VnModel", "base": "VnModel",
"options": { "options": {
"mysql": { "mysql": {
"table": "holidayDetail" "table": "calendarHolidaysName"
} }
}, },
"properties": { "properties": {
@ -11,7 +11,7 @@
"id": true, "id": true,
"type": "Number" "type": "Number"
}, },
"description": { "name": {
"type": "String" "type": "String"
} }
}, },

View File

@ -1,9 +1,9 @@
{ {
"name": "HolidayType", "name": "CalendarHolidaysType",
"base": "VnModel", "base": "VnModel",
"options": { "options": {
"mysql": { "mysql": {
"table": "holidayType" "table": "calendarHolidaysType"
} }
}, },
"properties": { "properties": {

View File

@ -22,12 +22,12 @@
"relations": { "relations": {
"detail": { "detail": {
"type": "belongsTo", "type": "belongsTo",
"model": "HolidayDetail", "model": "CalendarHolidaysName",
"foreignKey": "holidayDetailFk" "foreignKey": "holidayDetailFk"
}, },
"type": { "type": {
"type": "belongsTo", "type": "belongsTo",
"model": "HolidayType", "model": "CalendarHolidaysType",
"foreignKey": "holidayTypeFk" "foreignKey": "holidayTypeFk"
}, },
"workCenter": { "workCenter": {

View File

@ -34,6 +34,11 @@
</vn-label-value> </vn-label-value>
</vn-item-section> </vn-item-section>
<vn-item-section side> <vn-item-section side>
<vn-icon-button
ng-click="$ctrl.goToTimeControl($event, worker.id)"
vn-tooltip="Preview"
icon="access_time">
</vn-icon-button>
<vn-icon-button <vn-icon-button
ng-click="$ctrl.preview($event, worker)" ng-click="$ctrl.preview($event, worker)"
vn-tooltip="Preview" vn-tooltip="Preview"

View File

@ -18,6 +18,7 @@ export default class Controller {
preview(event, worker) { preview(event, worker) {
if (event.defaultPrevented) return; if (event.defaultPrevented) return;
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -25,6 +26,14 @@ export default class Controller {
this.$.preview.show(); this.$.preview.show();
} }
goToTimeControl(event, workerId) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
this.$state.go('worker.card.timeControl', {id: workerId}, {absolute: true});
}
onMoreChange(callback) { onMoreChange(callback) {
callback.call(this); callback.call(this);
} }