Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2575-image_download
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2020-11-18 10:44:00 +01:00
commit 9f5ba5bb85
16 changed files with 277 additions and 20 deletions

View File

@ -815,6 +815,10 @@ export default {
ticketOne: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(1)', ticketOne: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(1)',
ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)' ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)'
}, },
travelIndex: {
anySearchResult: 'vn-travel-index vn-tbody > a',
firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)'
},
travelBasicDada: { travelBasicDada: {
reference: 'vn-travel-basic-data vn-textfield[ng-model="$ctrl.travel.ref"]', reference: 'vn-travel-basic-data vn-textfield[ng-model="$ctrl.travel.ref"]',
agency: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]', agency: 'vn-travel-basic-data vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]',
@ -841,6 +845,11 @@ export default {
createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr', createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr',
upload: 'vn-travel-thermograph-create button[type=submit]' upload: 'vn-travel-thermograph-create button[type=submit]'
}, },
travelDescriptor: {
filterByAgencyButton: 'vn-descriptor-content .quicklinks > div:nth-child(1) > vn-quick-link > a[vn-tooltip="All travels with current agency"]',
dotMenu: 'vn-travel-descriptor vn-icon-button[icon="more_vert"]',
dotMenuClone: '#clone'
},
zoneIndex: { zoneIndex: {
searchResult: 'vn-zone-index a.vn-tr', searchResult: 'vn-zone-index a.vn-tr',
}, },

View File

@ -41,7 +41,6 @@ describe('Client balance path', () => {
it('should click the new payment button', async() => { it('should click the new payment button', async() => {
await page.closePopup(); await page.closePopup();
await page.reloadSection('client.card.balance.index'); await page.reloadSection('client.card.balance.index');
await page.waitForState('client.card.balance.index');
}); });
it('should create a new payment that clears the debt', async() => { it('should create a new payment that clears the debt', async() => {

View File

@ -0,0 +1,45 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Travel descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'travel');
await page.accessToSearchResult('1');
await page.waitForState('travel.card.summary');
});
afterAll(async() => {
await browser.close();
});
it('should click the descriptor button to navigate to the travel index showing all travels with current agency', async() => {
await page.waitToClick(selectors.travelDescriptor.filterByAgencyButton);
await page.waitForState('travel.index');
const result = await page.countElement(selectors.travelIndex.anySearchResult);
expect(result).toBeGreaterThanOrEqual(7);
});
it('should navigate to the first search result', async() => {
await page.waitToClick(selectors.travelIndex.firstSearchResult);
await page.waitForState('travel.card.summary');
const state = await page.getState();
expect(state).toBe('travel.card.summary');
});
it('should be redirected to the create travel when using the clone option of the dot menu', async() => {
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuClone);
await page.respondToDialog('accept');
await page.waitForState('travel.create');
const state = await page.getState();
expect(state).toBe('travel.create');
});
});

View File

@ -55,8 +55,8 @@
</vn-td> </vn-td>
<vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td> <vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number> <vn-td number>
<span ng-class="{'link': $ctrl.isEditable}" <span ng-class="{'link': $ctrl.isRewritable && $ctrl.isClaimManager}"
title="{{$ctrl.isEditable ? 'Edit discount' : ''}}" translate-attr="{title: $ctrl.isRewritable && $ctrl.isClaimManager ? 'Edit discount' : ''}"
ng-click="$ctrl.showEditPopover($event, saleClaimed)"> ng-click="$ctrl.showEditPopover($event, saleClaimed)">
{{saleClaimed.sale.discount}} % {{saleClaimed.sale.discount}} %
</span> </span>

View File

@ -54,6 +54,10 @@ class Controller extends Section {
this.updateNewPrice(); this.updateNewPrice();
} }
get isClaimManager() {
return this.aclService.hasAny(['claimManager']);
}
openAddSalesDialog() { openAddSalesDialog() {
this.getClaimableFromTicket(); this.getClaimableFromTicket();
this.$.addSales.show(); this.$.addSales.show();
@ -131,17 +135,6 @@ class Controller extends Section {
return total; return total;
} }
showEditPopover(event, saleClaimed) {
if (this.isEditable) {
if (!this.aclService.hasAny(['claimManager']))
return this.vnApp.showError(this.$t('Insuficient permisos'));
this.saleClaimed = saleClaimed;
this.$.editPopover.parent = event.target;
this.$.editPopover.show();
}
}
getSalespersonMana() { getSalespersonMana() {
this.$http.get(`Tickets/${this.claim.ticketFk}/getSalesPersonMana`).then(res => { this.$http.get(`Tickets/${this.claim.ticketFk}/getSalesPersonMana`).then(res => {
this.mana = res.data; this.mana = res.data;
@ -164,6 +157,14 @@ class Controller extends Section {
}); });
} }
showEditPopover(event, saleClaimed) {
if (this.aclService.hasAny(['claimManager'])) {
this.saleClaimed = saleClaimed;
this.$.editPopover.parent = event.target;
this.$.editPopover.show();
}
}
updateDiscount() { updateDiscount() {
const claimedSale = this.saleClaimed.sale; const claimedSale = this.saleClaimed.sale;
if (this.newDiscount != claimedSale.discount) { if (this.newDiscount != claimedSale.discount) {
@ -176,8 +177,6 @@ class Controller extends Section {
this.clearDiscount(); this.clearDiscount();
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
}).catch(err => {
this.vnApp.showError(err.message);
}); });
} }

View File

@ -50,7 +50,13 @@
</vn-td> </vn-td>
<vn-td title="{{::entry.warehouse| dashIfEmpty}}">{{::entry.warehouse| dashIfEmpty}}</vn-td> <vn-td title="{{::entry.warehouse| dashIfEmpty}}">{{::entry.warehouse| dashIfEmpty}}</vn-td>
<vn-td expand>{{::entry.landed | date:'dd/MM/yyyy HH:mm'}}</vn-td> <vn-td expand>{{::entry.landed | date:'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td number>{{::entry.entryFk | dashIfEmpty}}</vn-td> <vn-td shrink>
<span
vn-click-stop="entryDescriptor.show($event, entry.entryFk)"
class="link">
{{::entry.entryFk | dashIfEmpty}}
</span>
</vn-td>
<vn-td number>{{::entry.price2 | dashIfEmpty}}</vn-td> <vn-td number>{{::entry.price2 | dashIfEmpty}}</vn-td>
<vn-td number>{{::entry.price3 | dashIfEmpty}}</vn-td> <vn-td number>{{::entry.price3 | dashIfEmpty}}</vn-td>
<vn-td number class="expendable">{{entry.stickers | dashIfEmpty}}</vn-td> <vn-td number class="expendable">{{entry.stickers | dashIfEmpty}}</vn-td>
@ -77,6 +83,10 @@
</vn-card> </vn-card>
</vn-data-viewer> </vn-data-viewer>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model" <vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)"> expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu> <slot-menu>

View File

@ -70,13 +70,17 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const isLocked = await models.Ticket.isLocked(id); const isLocked = await models.Ticket.isLocked(id);
const isSalesPerson = await models.Account.hasRole(userId, 'salesPerson'); const roles = await models.Account.getRoles(userId);
const hasAllowedRoles = roles.filter(role =>
role == 'salesPerson' || role == 'claimManager'
);
const state = await Self.app.models.TicketState.findOne({ const state = await Self.app.models.TicketState.findOne({
where: {ticketFk: id} where: {ticketFk: id}
}); });
const alertLevel = state ? state.alertLevel : null; const alertLevel = state ? state.alertLevel : null;
if (isLocked || (!isSalesPerson && alertLevel > 0)) if (isLocked || (!hasAllowedRoles && alertLevel > 0))
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
const usesMana = await models.WorkerMana.findOne({ const usesMana = await models.WorkerMana.findOne({

View File

@ -37,6 +37,9 @@
"m3": { "m3": {
"type": "Number" "type": "Number"
}, },
"cargoSupplierFk": {
"type": "Number"
},
"agencyModeFk": { "agencyModeFk": {
"type": "Number", "type": "Number",
"mysql": { "mysql": {

View File

@ -0,0 +1,22 @@
<vn-icon-button
icon="more_vert"
vn-popover="menu">
</vn-icon-button>
<vn-menu vn-id="menu">
<vn-list>
<vn-item
id="clone"
ng-click="clone.show()"
translate>
Clone travel
</vn-item>
</vn-list>
</vn-menu>
<!-- Clone travel popup -->
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept()"
question="Do you want to clone this travel?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -0,0 +1,72 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
}
get travelId() {
return this._travelId;
}
set travelId(value) {
this._travelId = value;
if (value) this.loadData();
}
loadData() {
const filter = {
fields: [
'id',
'ref',
'shipped',
'landed',
'totalEntries',
'agencyFk',
'warehouseInFk',
'warehouseOutFk',
'cargoSupplierFk'
],
include: [
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}, {
relation: 'warehouseOut',
scope: {
fields: ['name']
}
}
]
};
return this.$http.get(`Travels/${this.travelId}`, {filter})
.then(res => this.travel = res.data);
}
onCloneAccept() {
const params = JSON.stringify({
ref: this.travel.ref,
agencyModeFk: this.travel.agencyFk,
shipped: this.travel.shipped,
landed: this.travel.landed,
warehouseInFk: this.travel.warehouseInFk,
warehouseOutFk: this.travel.warehouseOutFk
});
this.$state.go('travel.create', {q: params});
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnTravelDescriptorMenu', {
template: require('./index.html'),
controller: Controller,
bindings: {
travelId: '<',
}
});

View File

@ -0,0 +1,39 @@
import './index.js';
describe('Travel Component vnTravelDescriptorMenu', () => {
let controller;
beforeEach(ngModule('travel'));
beforeEach(inject(($componentController, $state,) => {
const $element = angular.element('<vn-travel-descriptor-menu></vn-travel-descriptor-menu>');
controller = $componentController('vnTravelDescriptorMenu', {$element});
}));
describe('onCloneAccept()', () => {
it('should call state.go with the travel data', () => {
jest.spyOn(controller.$state, 'go').mockReturnValue('ok');
controller.travel = {
ref: 'the ref',
agencyFk: 'the agency',
shipped: 'the shipped date',
landed: 'the landing date',
warehouseInFk: 'the receiver warehouse',
warehouseOutFk: 'the sender warehouse'
};
controller.onCloneAccept();
const params = JSON.stringify({
ref: controller.travel.ref,
agencyModeFk: controller.travel.agencyFk,
shipped: controller.travel.shipped,
landed: controller.travel.landed,
warehouseInFk: controller.travel.warehouseInFk,
warehouseOutFk: controller.travel.warehouseOutFk
});
expect(controller.$state.go).toHaveBeenCalledWith('travel.create', {'q': params});
});
});
});

View File

@ -0,0 +1 @@
Clone travel: Clonar envío

View File

@ -0,0 +1,24 @@
@import "./effects";
@import "variables";
vn-travel-descriptor-menu {
& > vn-icon-button[icon="more_vert"] {
display: flex;
min-width: 45px;
height: 45px;
box-sizing: border-box;
align-items: center;
justify-content: center;
}
& > vn-icon-button[icon="more_vert"] {
@extend %clickable;
color: inherit;
& > vn-icon {
padding: 10px;
}
vn-icon {
font-size: 1.75rem;
}
}
}

View File

@ -1,6 +1,9 @@
<vn-descriptor-content <vn-descriptor-content
module="travel" module="travel"
description="$ctrl.travel.ref"> description="$ctrl.travel.ref">
<slot-dot-menu>
<vn-travel-descriptor-menu travel-id="$ctrl.travel.id"/>
</slot-dot-menu>
<slot-body> <slot-body>
<div class="attributes"> <div class="attributes">
<vn-label-value <vn-label-value
@ -24,5 +27,18 @@
value="{{$ctrl.travel.totalEntries}}"> value="{{$ctrl.travel.totalEntries}}">
</vn-label-value> </vn-label-value>
</div> </div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="All travels with current agency"
state="['travel.index', {q: $ctrl.travelFilter}]"
icon="local_airport">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
</div>
<div ng-transclude="btnThree">
</div>
</div>
</slot-body> </slot-body>
</vn-descriptor-content> </vn-descriptor-content>

View File

@ -10,6 +10,18 @@ class Controller extends Descriptor {
this.entity = value; this.entity = value;
} }
get travelFilter() {
let travelFilter;
const travel = this.travel;
if (travel && travel.agencyFk) {
travelFilter = this.travel && JSON.stringify({
agencyFk: this.travel.agencyFk
});
}
return travelFilter;
}
loadData() { loadData() {
const filter = { const filter = {
fields: [ fields: [
@ -19,7 +31,8 @@ class Controller extends Descriptor {
'landed', 'landed',
'totalEntries', 'totalEntries',
'warehouseInFk', 'warehouseInFk',
'warehouseOutFk' 'warehouseOutFk',
'cargoSupplierFk'
], ],
include: [ include: [
{ {

View File

@ -13,3 +13,4 @@ import './thermograph/index/';
import './thermograph/create/'; import './thermograph/create/';
import './thermograph/edit/'; import './thermograph/edit/';
import './descriptor-popover'; import './descriptor-popover';
import './descriptor-menu';