Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2145_e2e_linux_fix

This commit is contained in:
Bernat 2020-03-13 12:03:57 +01:00
commit 91b58eb56b
31 changed files with 312 additions and 74 deletions

View File

@ -823,7 +823,7 @@ export default {
travelThermograph: { travelThermograph: {
add: 'vn-travel-thermograph-index vn-float-button[icon="add"]', add: 'vn-travel-thermograph-index vn-float-button[icon="add"]',
thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]', thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]',
uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="attach_file"]', uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="icon-attach"]',
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]'
}, },

View File

@ -13,7 +13,7 @@ export default class Datalist extends Textfield {
this._selection = null; this._selection = null;
this.buildInput('text'); this.buildInput('text');
this.input.setAttribute('autocomplete', 'off'); this.autocomplete = 'off';
} }
get field() { get field() {
@ -258,7 +258,8 @@ export default class Datalist extends Textfield {
fragment.appendChild(option); fragment.appendChild(option);
} }
list.appendChild(fragment); this.$.$applyAsync(() =>
list.appendChild(fragment));
} }
} }

View File

@ -100,7 +100,9 @@ export default class Watcher extends Component {
*/ */
submit() { submit() {
try { try {
this.check(); if (this.requestMethod() !== 'post')
this.check();
else this.isInvalid();
} catch (err) { } catch (err) {
return this.$q.reject(err); return this.$q.reject(err);
} }
@ -120,12 +122,12 @@ export default class Watcher extends Component {
if (this.form) if (this.form)
this.form.$setSubmitted(); this.form.$setSubmitted();
if (!this.dataChanged()) { const isPost = (this.requestMethod() === 'post');
if (!this.dataChanged() && !isPost) {
this.updateOriginalData(); this.updateOriginalData();
return this.$q.resolve(); return this.$q.resolve();
} }
let isPost = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post');
let changedData = isPost let changedData = isPost
? this.data ? this.data
: getModifiedData(this.data, this.orgData); : getModifiedData(this.data, this.orgData);
@ -158,7 +160,6 @@ export default class Watcher extends Component {
}); });
} }
return this.$q((resolve, reject) => { return this.$q((resolve, reject) => {
this.$http.post(this.url, changedData).then( this.$http.post(this.url, changedData).then(
json => this.writeData(json, resolve), json => this.writeData(json, resolve),
@ -166,6 +167,13 @@ export default class Watcher extends Component {
); );
}); });
} }
/**
* return the request method.
*/
requestMethod() {
return this.$attrs.save && this.$attrs.save.toLowerCase();
}
/** /**
* Checks if data is ready to send. * Checks if data is ready to send.
@ -177,6 +185,14 @@ export default class Watcher extends Component {
throw new UserError('No changes to save'); throw new UserError('No changes to save');
} }
/**
* Checks if the form is valid.
*/
isInvalid() {
if (this.form && this.form.$invalid)
throw new UserError('Some fields are invalid');
}
/** /**
* Notifies the user that the data has been saved. * Notifies the user that the data has been saved.
*/ */

View File

@ -60,7 +60,7 @@ describe('Directive http-click', () => {
}).finally(() => { }).finally(() => {
expect(finalValue).toEqual('called!'); expect(finalValue).toEqual('called!');
}).catch(err => { }).catch(err => {
console.log(err); throw err;
}); });
}); });
}); });

View File

@ -2,10 +2,11 @@ import ngModule from '../module';
import getMainRoute from '../lib/get-main-route'; import getMainRoute from '../lib/get-main-route';
export default class Modules { export default class Modules {
constructor(aclService, $window) { constructor(aclService, $window, $translate) {
Object.assign(this, { Object.assign(this, {
aclService, aclService,
$window $window,
$translate
}); });
} }
@ -17,7 +18,7 @@ export default class Modules {
if (this.modules) if (this.modules)
return this.modules; return this.modules;
this.modules = []; const modules = [];
for (let mod of this.$window.routes) { for (let mod of this.$window.routes) {
if (!mod || !mod.routes) continue; if (!mod || !mod.routes) continue;
@ -31,7 +32,7 @@ export default class Modules {
if (res) keyBind = res.key.toUpperCase(); if (res) keyBind = res.key.toUpperCase();
} }
this.modules.push({ modules.push({
name: mod.name || mod.module, name: mod.name || mod.module,
icon: mod.icon || null, icon: mod.icon || null,
route, route,
@ -39,9 +40,16 @@ export default class Modules {
}); });
} }
const sortedModules = modules.sort((a, b) => {
const translatedNameA = this.$translate.instant(a.name);
const translatedNameB = this.$translate.instant(b.name);
return translatedNameA.localeCompare(translatedNameB);
});
this.modules = sortedModules;
return this.modules; return this.modules;
} }
} }
Modules.$inject = ['aclService', '$window']; Modules.$inject = ['aclService', '$window', '$translate'];
ngModule.service('vnModules', Modules); ngModule.service('vnModules', Modules);

View File

@ -8,7 +8,7 @@
<div> <div>
<vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon> <vn-icon icon="{{::mod.icon || 'photo'}}"></vn-icon>
</div> </div>
<h4 ng-bind-html="::$ctrl.getModuleName(mod)"></h4> <h4 ng-bind-html="$ctrl.getModuleName(mod)"></h4>
<span <span
ng-show="::mod.keyBind" ng-show="::mod.keyBind"
vn-tooltip="Ctrl + Alt + {{::mod.keyBind}}"> vn-tooltip="Ctrl + Alt + {{::mod.keyBind}}">

View File

@ -128,5 +128,6 @@
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}",
"ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
"This ticket is deleted": "Este ticket está eliminado" "This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos"
} }

View File

@ -175,5 +175,12 @@
"model": "ItemNiche", "model": "ItemNiche",
"foreignKey": "itemFk" "foreignKey": "itemFk"
} }
},
"scope": {
"where": {
"name": {
"neq": ""
}
}
} }
} }

View File

@ -16,7 +16,8 @@ class Controller extends Component {
{name: 'Send Delivery Note', callback: this.confirmDeliveryNote}, {name: 'Send Delivery Note', callback: this.confirmDeliveryNote},
{name: 'Delete ticket', callback: this.showDeleteTicketDialog}, {name: 'Delete ticket', callback: this.showDeleteTicketDialog},
{name: 'Change shipped hour', callback: this.showChangeShipped}, {name: 'Change shipped hour', callback: this.showChangeShipped},
{name: 'Send SMS', callback: this.showSMSDialog}, {name: 'SMS Pending payment', callback: this.sendPaymentSms},
{name: 'SMS Minimum import', callback: this.sendImportSms},
{ {
name: 'Add stowaway', name: 'Add stowaway',
callback: this.showAddStowaway, callback: this.showAddStowaway,
@ -240,17 +241,30 @@ class Controller extends Component {
); );
} }
sendImportSms() {
const params = {
ticketId: this.ticket.id,
created: this.ticket.created
};
const message = this.$params.message || this.$translate.instant('Minimum is needed', params);
this.newSMS = {message};
this.showSMSDialog();
}
sendPaymentSms() {
const message = this.$params.message || this.$translate.instant('Make a payment');
this.newSMS = {message};
this.showSMSDialog();
}
showSMSDialog() { showSMSDialog() {
const address = this.ticket.address; const address = this.ticket.address;
const client = this.ticket.client; const client = this.ticket.client;
const phone = this.$params.phone || address.mobile || address.phone || const phone = this.$params.phone || address.mobile || address.phone ||
client.mobile || client.phone; client.mobile || client.phone;
const message = this.$params.message || this.$translate.instant('SMSPayment');
this.newSMS = { this.newSMS.destinationFk = this.ticket.clientFk;
destinationFk: this.ticket.clientFk, this.newSMS.destination = phone;
destination: phone,
message: message
};
this.$.sms.open(); this.$.sms.open();
} }

View File

@ -20,7 +20,13 @@ describe('Ticket Component vnTicketDescriptor', () => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_; $httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnTicketDescriptor', {$element}); controller = $componentController('vnTicketDescriptor', {$element});
controller._ticket = {id: 2, invoiceOut: {id: 1}, client: {id: 101, email: 'client@email'}}; controller._ticket = {
id: 2,
clientFk: 101,
invoiceOut: {id: 1},
client: {id: 101, email: 'client@email'},
address: {id: 101, mobile: 111111111, phone: 2222222222}
};
controller.cardReload = ()=> { controller.cardReload = ()=> {
return true; return true;
}; };
@ -161,7 +167,6 @@ describe('Ticket Component vnTicketDescriptor', () => {
}); });
}); });
describe('showAddStowaway()', () => { describe('showAddStowaway()', () => {
it('should show a dialog with a list of tickets available for an stowaway', () => { it('should show a dialog with a list of tickets available for an stowaway', () => {
controller.$.addStowaway = {}; controller.$.addStowaway = {};
@ -223,4 +228,20 @@ describe('Ticket Component vnTicketDescriptor', () => {
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
}); });
}); });
describe('showSMSDialog()', () => {
it('should set the destionationFk and destination properties and then call the sms open() method', () => {
controller.$.sms = {open: () => {}};
jest.spyOn(controller.$.sms, 'open');
const clientId = 101;
const expectedPhone = 111111111;
controller.newSMS = {};
controller.showSMSDialog();
expect(controller.newSMS.destinationFk).toEqual(clientId);
expect(controller.newSMS.destination).toEqual(expectedPhone);
expect(controller.$.sms.open).toHaveBeenCalledWith();
});
});
}); });

View File

@ -1,3 +1,2 @@
SMSPayment: >- Make a payment: "Verdnatura communicates:\rYour order is pending of payment.\rPlease, enter the web page and make the payment with card.\rThank you."
Verdnatura communicates: Your order is pending of payment. Minimum is needed: "Verdnatura communicates:\rA minimum import of 50€ (Without BAT) is needed for your order {{ticketId}} from date {{created | date: 'dd/MM/yyyy'}} to receive it with no extra fees."
Please, enter the web page and make the payment with card. Thank you.

View File

@ -13,7 +13,8 @@ Send Delivery Note: Enviar albarán
Show pallet report: Ver hoja de pallet Show pallet report: Ver hoja de pallet
Change shipped hour: Cambiar hora de envío Change shipped hour: Cambiar hora de envío
Shipped hour: Hora de envío Shipped hour: Hora de envío
SMSPayment: "Verdnatura le comunica:\rSu pedido está pendiente de pago.\rPor favor, entre en la página web y efectue el pago con tarjeta.\rMuchas gracias." Make a payment: "Verdnatura le comunica:\rSu pedido está pendiente de pago.\rPor favor, entre en la página web y efectue el pago con tarjeta.\rMuchas gracias."
Minimum is needed: "Verdnatura le recuerda:\rEs necesario llegar a un importe mínimo de 50€ (Sin IVA) en su pedido {{ticketId}} del día {{created | date: 'dd/MM/yyyy'}} para recibirlo sin portes adicionales."
Ticket invoiced: Ticket facturado Ticket invoiced: Ticket facturado
Make invoice: Crear factura Make invoice: Crear factura
Regenerate invoice: Regenerar factura Regenerate invoice: Regenerar factura
@ -25,4 +26,6 @@ Invoice sent for a regeneration, will be available in a few minutes: La factura
Shipped hour updated: Hora de envio modificada Shipped hour updated: Hora de envio modificada
Deleted ticket: Ticket eliminado Deleted ticket: Ticket eliminado
Recalculate components: Recalcular componentes Recalculate components: Recalcular componentes
Are you sure you want to recalculate the components?: ¿Seguro que quieres recalcular los componentes? Are you sure you want to recalculate the components?: ¿Seguro que quieres recalcular los componentes?
SMS Minimum import: 'SMS Importe minimo'
SMS Pending payment: 'SMS Pago pendiente'

View File

@ -2,4 +2,5 @@ Weekly tickets: Tickets programados
Go to lines: Ir a lineas Go to lines: Ir a lineas
Not available: No disponible Not available: No disponible
Payment on account...: Pago a cuenta... Payment on account...: Pago a cuenta...
Closure: Cierre Closure: Cierre
You cannot make a payment on account from multiple clients: No puedes realizar un pago a cuenta de clientes diferentes

View File

@ -105,20 +105,22 @@
on-error-src/> on-error-src/>
</vn-td> </vn-td>
<vn-td vn-focus number> <vn-td vn-focus number>
<span class="link" ng-if="sale.itemFk" <span class="link" ng-if="sale.id"
ng-click="$ctrl.showDescriptor($event, sale.itemFk)"> ng-click="$ctrl.showDescriptor($event, sale.itemFk)">
{{sale.itemFk | zeroFill:6}} {{sale.itemFk}}
</span> </span>
<vn-autocomplete <vn-autocomplete
ng-if="!sale.itemFk" ng-if="!sale.id"
vn-focus vn-focus
vn-one vn-one
url="Items" url="Items"
ng-model="sale.itemFk" ng-model="sale.itemFk"
show-field="name" show-field="name"
value-field="id" value-field="id"
search-function="{or: [{id: $search}, {name: {like: '%' + $search + '%'}}]}" search-function="$ctrl.itemSearchFunc($search)"
order="id DESC"> on-change="$ctrl.onChangeQuantity(sale)"
order="id DESC"
tabindex="1">
<tpl-item> <tpl-item>
{{id}} - {{name}} {{id}} - {{name}}
</tpl-item> </tpl-item>
@ -137,7 +139,8 @@
<vn-td ng-if="!sale.id" number> <vn-td ng-if="!sale.id" number>
<vn-input-number <vn-input-number
ng-model="sale.quantity" ng-model="sale.quantity"
on-change="$ctrl.onChangeQuantity(sale)"> on-change="$ctrl.onChangeQuantity(sale)"
tabindex="2">
</vn-input-number> </vn-input-number>
</vn-td> </vn-td>
<vn-td-editable disabled="!sale.id || !$ctrl.isEditable" expand> <vn-td-editable disabled="!sale.id || !$ctrl.isEditable" expand>
@ -150,7 +153,7 @@
</vn-fetched-tags> </vn-fetched-tags>
</text> </text>
<field> <field>
<vn-textfield <vn-textfield class="dense"
vn-id="concept" vn-id="concept"
ng-model="sale.concept" ng-model="sale.concept"
on-change="$ctrl.updateConcept(sale)"> on-change="$ctrl.updateConcept(sale)">
@ -167,7 +170,8 @@
<vn-td number> <vn-td number>
<span ng-class="{'link': !$ctrl.isLocked}" <span ng-class="{'link': !$ctrl.isLocked}"
title="{{!$ctrl.isLocked ? 'Edit discount' : ''}}" title="{{!$ctrl.isLocked ? 'Edit discount' : ''}}"
ng-click="$ctrl.showEditDiscountPopover($event, sale)"> ng-click="$ctrl.showEditDiscountPopover($event, sale)"
ng-if="sale.id">
{{(sale.discount / 100) | percentage}} {{(sale.discount / 100) | percentage}}
</span> </span>
</vn-td> </vn-td>

View File

@ -12,7 +12,7 @@ class Controller {
this.edit = {}; this.edit = {};
this.moreOptions = [ this.moreOptions = [
{ {
name: 'Send SMS', name: 'Send shortage SMS',
callback: this.showSMSDialog callback: this.showSMSDialog
}, { }, {
name: 'Mark as reserved', name: 'Mark as reserved',
@ -182,7 +182,6 @@ class Controller {
return checkedLines.length; return checkedLines.length;
} }
removeCheckedLines() { removeCheckedLines() {
const sales = this.checkedLines(); const sales = this.checkedLines();
@ -448,7 +447,7 @@ class Controller {
this.newSMS = { this.newSMS = {
destinationFk: this.ticket.clientFk, destinationFk: this.ticket.clientFk,
destination: phone, destination: phone,
message: this.$translate.instant('SMSAvailability', params) message: this.$translate.instant('Product not available', params)
}; };
this.$scope.sms.open(); this.$scope.sms.open();
} }
@ -465,10 +464,12 @@ class Controller {
* Updates the sale quantity for existing instance * Updates the sale quantity for existing instance
*/ */
onChangeQuantity(sale) { onChangeQuantity(sale) {
if (!sale.quantity) return;
if (!sale.id) if (!sale.id)
this.addSale(sale); return this.addSale(sale);
else
this.updateQuantity(sale); this.updateQuantity(sale);
} }
/* /*
@ -561,6 +562,12 @@ class Controller {
this.$scope.model.refresh(); this.$scope.model.refresh();
}); });
} }
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
} }
Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate']; Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate'];

View File

@ -1,3 +1,3 @@
SMSAvailability: >- Product not available: >-
Verdnatura communicates: Your order {{ticketFk}} created on {{created | date: "dd/MM/yyyy"}}. Verdnatura communicates: Your order {{ticketFk}} created on {{created | date: "dd/MM/yyyy"}}.
{{notAvailables}} not available. Sorry for the inconvenience. {{notAvailables}} not available. Sorry for the inconvenience.

View File

@ -24,7 +24,8 @@ Sales to transfer: Líneas a transferir
Destination ticket: Ticket destinatario Destination ticket: Ticket destinatario
Change ticket state to 'Ok': Cambiar estado del ticket a 'Ok' Change ticket state to 'Ok': Cambiar estado del ticket a 'Ok'
Reserved: Reservado Reserved: Reservado
SMSAvailability: "Verdnatura le comunica:\rPedido {{ticketFk}} día {{created | date: 'dd/MM/yyyy'}}.\r{{notAvailables}} no disponible/s.\rDisculpe las molestias." Send shortage SMS: Enviar SMS faltas
Product not available: "Verdnatura le comunica:\rPedido {{ticketFk}} día {{created | date: 'dd/MM/yyyy'}}.\r{{notAvailables}} no disponible/s.\rDisculpe las molestias."
Continue anyway?: ¿Continuar de todas formas? Continue anyway?: ¿Continuar de todas formas?
This ticket is now empty: El ticket ha quedado vacio This ticket is now empty: El ticket ha quedado vacio
Do you want to delete it?: ¿Quieres eliminarlo? Do you want to delete it?: ¿Quieres eliminarlo?

View File

@ -207,6 +207,39 @@ describe('Ticket', () => {
}); });
}); });
describe('onChangeQuantity()', () => {
it('should not call addSale() or updateQuantity() methods', () => {
jest.spyOn(controller, 'addSale');
jest.spyOn(controller, 'updateQuantity');
const sale = {itemFk: 4};
controller.onChangeQuantity(sale);
expect(controller.addSale).not.toHaveBeenCalled();
expect(controller.updateQuantity).not.toHaveBeenCalled();
});
it('should call addSale() method', () => {
jest.spyOn(controller, 'addSale');
const sale = {itemFk: 4, quantity: 5};
controller.onChangeQuantity(sale);
expect(controller.addSale).toHaveBeenCalledWith(sale);
});
it('should call updateQuantity() method', () => {
jest.spyOn(controller, 'updateQuantity');
jest.spyOn(controller, 'addSale');
const sale = {id: 1, itemFk: 4, quantity: 5};
controller.onChangeQuantity(sale);
expect(controller.addSale).not.toHaveBeenCalled();
expect(controller.updateQuantity).toHaveBeenCalledWith(sale);
});
});
describe('updateQuantity()', () => { describe('updateQuantity()', () => {
it('should make a POST query saving sale quantity', () => { it('should make a POST query saving sale quantity', () => {
jest.spyOn(controller.$scope.watcher, 'updateOriginalData'); jest.spyOn(controller.$scope.watcher, 'updateOriginalData');

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
require('../methods/travel/getTravel')(Self); require('../methods/travel/getTravel')(Self);
require('../methods/travel/getEntries')(Self); require('../methods/travel/getEntries')(Self);
@ -5,4 +7,10 @@ module.exports = Self => {
require('../methods/travel/createThermograph')(Self); require('../methods/travel/createThermograph')(Self);
require('../methods/travel/deleteThermograph')(Self); require('../methods/travel/deleteThermograph')(Self);
require('../methods/travel/updateThermograph')(Self); require('../methods/travel/updateThermograph')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError('A travel with this data already exists');
return err;
});
}; };

View File

@ -2,6 +2,16 @@ import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
class Controller extends Section { class Controller extends Section {
constructor($element, $, $stateParams) {
super($element, $);
this.$stateParams = $stateParams;
}
$onChanges() {
if (this.$stateParams && this.$stateParams.q)
this.travel = JSON.parse(this.$stateParams.q);
}
onSubmit() { onSubmit() {
return this.$.watcher.submit().then( return this.$.watcher.submit().then(
res => this.$state.go('travel.card.summary', {id: res.data.id}) res => this.$state.go('travel.card.summary', {id: res.data.id})
@ -9,6 +19,8 @@ class Controller extends Section {
} }
} }
Controller.$inject = ['$element', '$scope', '$stateParams'];
ngModule.component('vnTravelCreate', { ngModule.component('vnTravelCreate', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller controller: Controller

View File

@ -26,5 +26,18 @@ describe('Travel Component vnTravelCreate', () => {
expect(controller.$state.go).toHaveBeenCalledWith('travel.card.summary', {id: 1234}); expect(controller.$state.go).toHaveBeenCalledWith('travel.card.summary', {id: 1234});
}); });
}); });
describe('$onChanges()', () => {
it('should update the travel data when stateParams.q is defined', () => {
controller.$stateParams = {q: '{"ref": 1,"agencyModeFk": 1}'};
const params = {q: '{"ref": 1, "agencyModeFk": 1}'};
const json = JSON.parse(params.q);
controller.$onChanges();
expect(controller.travel).toEqual(json);
});
});
}); });

View File

@ -41,12 +41,19 @@
<vn-td expand>{{::travel.warehouseInName}}</vn-td> <vn-td expand>{{::travel.warehouseInName}}</vn-td>
<vn-td center>{{::travel.landed | date:'dd/MM/yyyy'}}</vn-td> <vn-td center>{{::travel.landed | date:'dd/MM/yyyy'}}</vn-td>
<vn-td center><vn-check ng-model="travel.isReceived" disabled="true"></vn-check></vn-td> <vn-td center><vn-check ng-model="travel.isReceived" disabled="true"></vn-check></vn-td>
<vn-td> <vn-td shrink>
<vn-icon-button <vn-horizontal class="buttons">
ng-click="$ctrl.preview($event, travel)" <vn-icon-button
vn-tooltip="Preview" ng-click="$ctrl.cloneTravel($event, travel)"
icon="desktop_windows"> vn-tooltip="Clone"
</vn-icon-button> icon="icon-clone">
</vn-icon-button>
<vn-icon-button
ng-click="$ctrl.preview($event, travel)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon-button>
</vn-horizontal>
</vn-td> </vn-td>
</a> </a>
</vn-tbody> </vn-tbody>
@ -65,4 +72,10 @@
fixed-bottom-right> fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button> <vn-float-button icon="add"></vn-float-button>
</a> </a>
<vn-confirm
vn-id="clone"
on-response="$ctrl.onCloneAccept($response)"
question="Do you want to clone this travel?"
message="All it's properties will be copied">
</vn-confirm>
<vn-scroll-up></vn-scroll-up> <vn-scroll-up></vn-scroll-up>

View File

@ -1,16 +1,10 @@
import ngModule from '../module'; import ngModule from '../module';
export default class Controller { export default class Controller {
constructor($scope) { constructor($scope, $state) {
this.$ = $scope; this.$ = $scope;
this.ticketSelected = null; this.ticketSelected = null;
} this.$state = $state;
preview(event, travel) {
this.travelSelected = travel;
this.$.summary.show();
event.preventDefault();
event.stopImmediatePropagation();
} }
getScopeDates(days) { getScopeDates(days) {
@ -35,9 +29,44 @@ export default class Controller {
} else } else
this.$.model.clear(); this.$.model.clear();
} }
stopEvent(event) {
event.preventDefault();
event.stopImmediatePropagation();
}
cloneTravel(event, travel) {
this.stopEvent(event);
this.travelSelected = travel;
this.$.clone.show();
}
onCloneAccept(response) {
if (!(response == 'accept' && this.travelSelected))
return;
if (this.travelSelected) {
const travel = {
ref: this.travelSelected.ref,
agencyModeFk: this.travelSelected.agencyFk,
shipped: this.travelSelected.shipped,
landed: this.travelSelected.landed,
warehouseInFk: this.travelSelected.warehouseInFk,
warehouseOutFk: this.travelSelected.warehouseOutFk
};
const queryParams = JSON.stringify(travel);
this.$state.go('travel.create', {q: queryParams});
}
this.travelSelected = null;
}
preview(event, travel) {
this.stopEvent(event);
this.travelSelected = travel;
this.$.summary.show();
}
} }
Controller.$inject = ['$scope']; Controller.$inject = ['$scope', '$state'];
ngModule.component('vnTravelIndex', { ngModule.component('vnTravelIndex', {
template: require('./index.html'), template: require('./index.html'),

View File

@ -61,4 +61,48 @@ describe('Travel Component vnTravelIndex', () => {
expect(range - dayInMilliseconds).toEqual(dayInMilliseconds + millisecondsPerAddedDay); expect(range - dayInMilliseconds).toEqual(dayInMilliseconds + millisecondsPerAddedDay);
}); });
}); });
describe('onCloneAccept()', () => {
it('should do nothing if response is not accept', () => {
jest.spyOn(controller.$state, 'go');
let response = 'ERROR!';
controller.travelSelected = 'check me';
controller.onCloneAccept(response);
expect(controller.$state.go).not.toHaveBeenCalledWith();
expect(controller.travelSelected).toEqual('check me');
});
it('should do nothing if response is accept but travelSelected is not defined in the controller', () => {
jest.spyOn(controller.$state, 'go');
let response = 'accept';
controller.travelSelected = undefined;
controller.onCloneAccept(response);
expect(controller.$state.go).not.toHaveBeenCalledWith();
expect(controller.travelSelected).toBeUndefined();
});
it('should call go() then update travelSelected in the controller', () => {
jest.spyOn(controller.$state, 'go');
let response = 'accept';
controller.travelSelected = {
ref: 1,
agencyFk: 1};
const travel = {
ref: controller.travelSelected.ref,
agencyModeFk: controller.travelSelected.agencyFk
};
const queryParams = JSON.stringify(travel);
controller.onCloneAccept(response);
expect(controller.$state.go).toHaveBeenCalledWith('travel.create', {q: queryParams});
expect(controller.travelSelected).toBeNull();
});
});
}); });

View File

@ -0,0 +1,3 @@
Do you want to clone this travel?: ¿Desea clonar este envio?
All it's properties will be copied: Todas sus propiedades serán copiadas
Clone: Clonar

View File

@ -54,7 +54,7 @@
"component": "vn-travel-log", "component": "vn-travel-log",
"description": "Log" "description": "Log"
}, { }, {
"url": "/create", "url": "/create?q",
"state": "travel.create", "state": "travel.create",
"component": "vn-travel-create", "component": "vn-travel-create",
"description": "New travel" "description": "New travel"

View File

@ -23,12 +23,12 @@ module.exports = Self => {
beginningYear.setHours(0, 0, 0, 0); beginningYear.setHours(0, 0, 0, 0);
let holidays = await Self.rawSql( let holidays = await Self.rawSql(
`SELECT lh.dated, chn.name, cht.name, w.id `SELECT clh.dated, chn.name, cht.name, w.id
FROM vn.holiday lh FROM vn.calendarHolidays clh
JOIN vn.workCenter w ON w.id = lh.workcenterFk JOIN vn.workCenter w ON w.id = clh.workcenterFk
LEFT JOIN vn.calendarHolidaysName chn ON chn.id = lh.holidayDetailFk LEFT JOIN vn.calendarHolidaysName chn ON chn.id = clh.calendarHolidaysNameFk
LEFT JOIN vn.calendarHolidaysType cht ON cht.id = lh.holidayTypeFk LEFT JOIN vn.calendarHolidaysType cht ON cht.id = clh.calendarHolidaysTypeFk
WHERE w.warehouseFk = ? AND lh.dated >= ?`, [warehouseFk, beginningYear]); WHERE w.warehouseFk = ? AND clh.dated >= ?`, [warehouseFk, beginningYear]);
return holidays.map(holiday => { return holidays.map(holiday => {
holiday.dated = new Date(holiday.dated); holiday.dated = new Date(holiday.dated);

View File

@ -5,7 +5,7 @@
"Department": { "Department": {
"dataSource": "vn" "dataSource": "vn"
}, },
"Holiday": { "CalendarHoliday": {
"dataSource": "vn" "dataSource": "vn"
}, },
"CalendarHolidaysName": { "CalendarHolidaysName": {

View File

@ -1,17 +1,17 @@
{ {
"name": "Holiday", "name": "CalendarHoliday",
"base": "VnModel", "base": "VnModel",
"options": { "options": {
"mysql": { "mysql": {
"table": "holiday" "table": "calendarHolidays"
} }
}, },
"properties": { "properties": {
"holidayDetailFk": { "calendarHolidaysNameFk": {
"id": true, "id": true,
"type": "Number" "type": "Number"
}, },
"holidayTypeFk": { "calendarHolidaysTypeFk": {
"id": true, "id": true,
"type": "Number" "type": "Number"
}, },

View File

@ -23,7 +23,7 @@
}, },
"holidays": { "holidays": {
"type": "hasMany", "type": "hasMany",
"model": "Holiday", "model": "CalendarHoliday",
"foreignKey": "workCenterFk" "foreignKey": "workCenterFk"
} }
}, },