Merge with upstream
This commit is contained in:
commit
53003246c3
|
@ -9,25 +9,31 @@ module.exports = Self => {
|
|||
{
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: 'The warehouse id'
|
||||
description: 'The warehouse id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
description: 'The company id'
|
||||
description: 'The company id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'dmsTypeId',
|
||||
type: 'Number',
|
||||
description: 'The dms type id'
|
||||
description: 'The dms type id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'reference',
|
||||
type: 'String'
|
||||
type: 'String',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'String'
|
||||
type: 'String',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'hasFile',
|
||||
type: 'Boolean',
|
||||
description: 'True if has an attached file'
|
||||
description: 'True if has an attached file',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
|
|
|
@ -52,6 +52,15 @@
|
|||
},
|
||||
"Postcode": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserPhoneType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserPhone": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserLog": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
let UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_DUP_ENTRY')
|
||||
return new UserError(`This phone already exists`);
|
||||
return err;
|
||||
});
|
||||
};
|
|
@ -2,7 +2,8 @@
|
|||
"name": "UserPhone",
|
||||
"base": "Loggable",
|
||||
"log": {
|
||||
"model":"UserLog"
|
||||
"model":"UserLog",
|
||||
"relation": "user"
|
||||
},
|
||||
"options": {
|
||||
"mysql": {
|
||||
|
@ -12,11 +13,15 @@
|
|||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "String"
|
||||
"type": "Number"
|
||||
},
|
||||
"phone": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"typeFk": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"relations": {
|
|
@ -1,6 +1,6 @@
|
|||
CREATE TABLE `vn`.`userLog` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`originFk` int(11) NOT NULL,
|
||||
`originFk` int(10) unsigned NOT NULL,
|
||||
`userFk` int(10) unsigned DEFAULT NULL,
|
||||
`action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL,
|
||||
`creationDate` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
@ -13,6 +13,6 @@ CREATE TABLE `vn`.`userLog` (
|
|||
PRIMARY KEY (`id`),
|
||||
KEY `originFk` (`originFk`),
|
||||
KEY `userFk` (`userFk`),
|
||||
CONSTRAINT `userLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `client` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `userLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `userLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
|
@ -2,7 +2,7 @@ CREATE TABLE `vn`.`userPhone` (
|
|||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`userFk` INT(10) UNSIGNED NOT NULL,
|
||||
`typeFk` VARCHAR(45) NOT NULL,
|
||||
`phone` INT(15) NOT NULL,
|
||||
`phone` VARCHAR(15) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `UserFK_Phone` (`userFk` ASC, `phone` ASC));
|
||||
|
||||
|
@ -22,7 +22,7 @@ ADD CONSTRAINT `fgnUserFk`
|
|||
ON UPDATE CASCADE;
|
||||
|
||||
insert into vn.userPhone(userFk,typeFk,phone)
|
||||
select id,'PersonalPhone', phone
|
||||
select id,'personalPhone', phone
|
||||
from vn.client
|
||||
where phone is not null;
|
||||
|
||||
|
|
|
@ -471,7 +471,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
|
|||
(11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 102, 'NY roofs', 122, NULL, 0, 3, CURDATE()),
|
||||
(12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 1, CURDATE()),
|
||||
(13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 103, 'Phone Box', 123, NULL, 0, 3, CURDATE()),
|
||||
(14, 1, 2, 1, NULL, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()),
|
||||
(14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 104, 'Malibu Point', 4, NULL, 0, 9, CURDATE()),
|
||||
(15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 105, 'Plastic Cell', 125, NULL, 0, 3, CURDATE()),
|
||||
(16, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
|
||||
(17, 1, 7, 2, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 106, 'Many Places', 126, NULL, 0, 3, CURDATE()),
|
||||
|
@ -1862,4 +1862,67 @@ INSERT INTO `vn`.`queuePriority`(`id`, `priority`)
|
|||
VALUES
|
||||
(1, 'Alta'),
|
||||
(2, 'Normal'),
|
||||
(3, 'Baja');
|
||||
(3, 'Baja');
|
||||
|
||||
INSERT INTO `vn`.`userPhone`(`id`, `userFk`, `typeFk`, `phone`)
|
||||
VALUES
|
||||
(1, 101, 'personalPhone', 1111111111),
|
||||
(2, 102, 'personalPhone', 1111111111),
|
||||
(3, 103, 'personalPhone', 1111111111),
|
||||
(4, 104, 'personalPhone', 1111111111),
|
||||
(5, 105, 'personalPhone', 1111111111),
|
||||
(6, 106, 'personalPhone', 1111111111),
|
||||
(7, 107, 'personalPhone', 1111111111),
|
||||
(8, 108, 'personalPhone', 1111111111),
|
||||
(9, 109, 'personalPhone', 1111111111),
|
||||
(10, 110, 'personalPhone', 1111111111),
|
||||
(11, 111, 'personalPhone', 1111111111),
|
||||
(12, 112, 'personalPhone', 1111111111),
|
||||
(13, 1, 'personalPhone', 623111111),
|
||||
(14, 2, 'personalPhone', 623111111),
|
||||
(15, 3, 'personalPhone', 623111111),
|
||||
(16, 5, 'personalPhone', 623111111),
|
||||
(17, 6, 'personalPhone', 623111111),
|
||||
(18, 9, 'personalPhone', 623111111),
|
||||
(19, 13, 'personalPhone', 623111111),
|
||||
(20, 15, 'personalPhone', 623111111),
|
||||
(21, 16, 'personalPhone', 623111111),
|
||||
(22, 17, 'personalPhone', 623111111),
|
||||
(23, 18, 'personalPhone', 623111111),
|
||||
(24, 19, 'personalPhone', 623111111),
|
||||
(25, 20, 'personalPhone', 623111111),
|
||||
(26, 21, 'personalPhone', 623111111),
|
||||
(27, 22, 'personalPhone', 623111111),
|
||||
(28, 30, 'personalPhone', 623111111),
|
||||
(29, 31, 'personalPhone', 623111111),
|
||||
(30, 32, 'personalPhone', 623111111),
|
||||
(31, 34, 'personalPhone', 623111111),
|
||||
(32, 35, 'personalPhone', 623111111),
|
||||
(33, 36, 'personalPhone', 623111111),
|
||||
(34, 37, 'personalPhone', 623111111),
|
||||
(35, 38, 'personalPhone', 623111111),
|
||||
(36, 39, 'personalPhone', 623111111),
|
||||
(37, 40, 'personalPhone', 623111111),
|
||||
(38, 41, 'personalPhone', 623111111),
|
||||
(39, 42, 'personalPhone', 623111111),
|
||||
(40, 43, 'personalPhone', 623111111),
|
||||
(41, 44, 'personalPhone', 623111111),
|
||||
(42, 45, 'personalPhone', 623111111),
|
||||
(43, 47, 'personalPhone', 623111111),
|
||||
(44, 48, 'personalPhone', 623111111),
|
||||
(45, 50, 'personalPhone', 623111111),
|
||||
(46, 51, 'personalPhone', 623111111),
|
||||
(47, 52, 'personalPhone', 623111111),
|
||||
(48, 54, 'personalPhone', 623111111),
|
||||
(49, 55, 'personalPhone', 623111111),
|
||||
(50, 56, 'personalPhone', 623111111),
|
||||
(51, 57, 'personalPhone', 623111111),
|
||||
(52, 58, 'personalPhone', 623111111),
|
||||
(53, 59, 'personalPhone', 623111111),
|
||||
(54, 60, 'personalPhone', 623111111),
|
||||
(55, 61, 'personalPhone', 623111111),
|
||||
(56, 65, 'personalPhone', 623111111),
|
||||
(57, 66, 'personalPhone', 623111111),
|
||||
(65, 107, 'businessPhone', 700987987),
|
||||
(67, 106, 'businessPhone', 1111111112),
|
||||
(68, 106, 'personalPhone', 1111111113);
|
||||
|
|
|
@ -61,7 +61,7 @@ export default {
|
|||
fiscalDataButton: 'vn-left-menu a[ui-sref="client.card.fiscalData"]',
|
||||
socialNameInput: `vn-textfield input[name="socialName"]`,
|
||||
fiscalIdInput: `vn-textfield input[name="fi"]`,
|
||||
equalizationTaxCheckbox: 'vn-check[label="Is equalizated"]',
|
||||
equalizationTaxCheckbox: 'vn-check[ng-model="$ctrl.client.isEqualizated"]',
|
||||
acceptPropagationButton: 'vn-client-fiscal-data > vn-confirm button[response=ACCEPT]',
|
||||
addressInput: `vn-textfield input[name="street"]`,
|
||||
postcodeInput: `vn-textfield input[name="postcode"]`,
|
||||
|
@ -482,7 +482,7 @@ export default {
|
|||
addRequestButton: 'vn-ticket-request-index > a > vn-float-button > button',
|
||||
request: 'vn-ticket-request-index vn-table vn-tr',
|
||||
descriptionInput: 'vn-ticket-request-create > form > div > vn-card > vn-horizontal:nth-child(1) > vn-textfield input',
|
||||
atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.atenderFk"]',
|
||||
atenderAutocomplete: 'vn-ticket-request-create vn-autocomplete[ng-model="$ctrl.ticketRequest.attenderFk"]',
|
||||
quantityInput: 'vn-ticket-request-create vn-input-number input[name=quantity]',
|
||||
priceInput: 'vn-ticket-request-create vn-input-number input[name=price]',
|
||||
firstRemoveRequestButton: 'vn-ticket-request-index vn-icon[icon="delete"]:nth-child(1)',
|
||||
|
@ -534,7 +534,7 @@ export default {
|
|||
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
|
||||
itemDescriptorPopoverItemDiaryButton: '.vn-popover.shown vn-item-descriptor a[href="#!/item/2/diary"]',
|
||||
firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span',
|
||||
firstDevelopmentWorkerGoToClientButton: '.vn-popover.shown vn-worker-descriptor div.quicklinks > a[href="#!/client/21/summary"]',
|
||||
firstDevelopmentWorkerGoToClientButton: '.vn-popover.shown vn-worker-descriptor vn-quick-links > a[href="#!/client/21/summary"]',
|
||||
firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(6) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span',
|
||||
firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor'
|
||||
},
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('Ticket Summary path', () => {
|
|||
|
||||
it(`should display details from the ticket and it's client on the top of the header`, async() => {
|
||||
let result = await nightmare
|
||||
.waitForSpinnerLoad()
|
||||
.waitForTextInElement(selectors.ticketSummary.header, 'Bruce Banner')
|
||||
.waitToGetProperty(selectors.ticketSummary.header, 'innerText');
|
||||
|
||||
expect(result).toContain(`Ticket #${ticketId}`);
|
||||
|
|
|
@ -3,7 +3,7 @@ import ngModule from '../module';
|
|||
const regex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i;
|
||||
export const isMobile = regex.test(navigator.userAgent);
|
||||
|
||||
export function focus(input) {
|
||||
export function focus($scope, input) {
|
||||
if (isMobile) return;
|
||||
|
||||
const element = input;
|
||||
|
@ -23,9 +23,9 @@ export function focus(input) {
|
|||
}
|
||||
|
||||
input.focus();
|
||||
|
||||
if (input.select)
|
||||
$scope.$applyAsync(() => {
|
||||
input.select();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ export function directive() {
|
|||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element) {
|
||||
$scope.$watch('', () => focus($element[0]));
|
||||
$scope.$watch('', () => focus($scope, $element[0]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ describe('Directive focus', () => {
|
|||
it('should call select function on the element', () => {
|
||||
let html = `<input vn-focus></input>`;
|
||||
compile(html);
|
||||
$scope.$apply();
|
||||
|
||||
expect($element[0].select).toHaveBeenCalledWith();
|
||||
});
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
@import "variables";
|
||||
|
||||
html, body {
|
||||
html {
|
||||
background-color: $color-bg;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
height: 100%;
|
||||
font-family: vn-font;
|
||||
color: $color-font;
|
||||
font-size: $font-size;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<a ng-if="$ctrl.links.btnOne"
|
||||
vn-tooltip="{{::$ctrl.links.btnOne.tooltip}}"
|
||||
class="vn-button colored"
|
||||
ui-sref="{{::$ctrl.links.btnOne.state}}" target="_blank">
|
||||
ui-sref="{{::$ctrl.links.btnOne.state}}">
|
||||
<vn-icon
|
||||
icon="{{::$ctrl.links.btnOne.icon}}">
|
||||
</vn-icon>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<a ng-if="$ctrl.links.btnTwo"
|
||||
vn-tooltip="{{::$ctrl.links.btnTwo.tooltip}}"
|
||||
class="vn-button colored"
|
||||
ui-sref="{{::$ctrl.links.btnTwo.state}}" target="_blank">
|
||||
ui-sref="{{::$ctrl.links.btnTwo.state}}">
|
||||
<vn-icon
|
||||
icon="{{::$ctrl.links.btnTwo.icon}}">
|
||||
</vn-icon>
|
||||
|
@ -17,7 +17,7 @@
|
|||
<a ng-if="$ctrl.links.btnThree"
|
||||
vn-tooltip="{{::$ctrl.links.btnThree.tooltip}}"
|
||||
class="vn-button colored"
|
||||
ui-sref="{{::$ctrl.links.btnThree.state}}" target="_blank">
|
||||
ui-sref="{{::$ctrl.links.btnThree.state}}">
|
||||
<vn-icon
|
||||
icon="{{::$ctrl.links.btnThree.icon}}">
|
||||
</vn-icon>
|
||||
|
|
|
@ -109,6 +109,7 @@
|
|||
"is invalid": "is invalid",
|
||||
"The postcode doesn't exists. Ensure you put the correct format": "El código postal no existe. Asegúrate de ponerlo con el formato correcto",
|
||||
"The department name can't be repeated": "El nombre del departamento no puede repetirse",
|
||||
"This phone already exists": "Este teléfono ya existe",
|
||||
"You cannot move a parent to any of its sons": "You cannot move a parent to any of its sons",
|
||||
"You cannot move a parent to its own sons": "You cannot move a parent to its own sons",
|
||||
"You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado"
|
||||
|
|
|
@ -120,16 +120,19 @@ module.exports = Self => {
|
|||
}
|
||||
|
||||
async function getTicketId(params, options) {
|
||||
const currentDate = new Date();
|
||||
currentDate.setHours(null, null, null);
|
||||
const minDate = new Date();
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
|
||||
const maxDate = new Date();
|
||||
maxDate.setHours(23, 59, 59, 59);
|
||||
|
||||
let ticket = await Self.app.models.Ticket.findOne({
|
||||
where: {
|
||||
addressFk: params.addressFk,
|
||||
companyFk: params.companyFk,
|
||||
warehouseFk: params.warehouseFk,
|
||||
shipped: currentDate,
|
||||
landed: currentDate
|
||||
shipped: {between: [minDate, maxDate]},
|
||||
landed: {between: [minDate, maxDate]}
|
||||
}
|
||||
}, options);
|
||||
|
||||
|
|
|
@ -7,36 +7,34 @@ module.exports = Self => {
|
|||
type: 'Number',
|
||||
description: 'The claim id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The warehouse id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The company id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'dmsTypeId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The dms type id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'reference',
|
||||
type: 'String',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
required: true
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'String',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
required: true
|
||||
}, {
|
||||
arg: 'hasFile',
|
||||
type: 'Boolean',
|
||||
description: ''
|
||||
description: 'True if has an attached file',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
<vn-crud-model auto-load="true"
|
||||
vn-id="model"
|
||||
<vn-crud-model vn-id="model" auto-load="true" auto-save="true"
|
||||
url="claim/api/ClaimEnds"
|
||||
filter="$ctrl.filter"
|
||||
data="$ctrl.salesClaimed">
|
||||
</vn-crud-model>
|
||||
|
||||
<vn-crud-model auto-load="true"
|
||||
url="/claim/api/ClaimDestinations"
|
||||
data="claimDestinations">
|
||||
</vn-crud-model>
|
||||
|
||||
<vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right"
|
||||
ng-if="$ctrl.salesClaimed.length > 0">
|
||||
<vn-label-value label="Total claimed"
|
||||
|
@ -28,7 +32,6 @@
|
|||
translate-attr="{title: 'Imports ticket lines'}">
|
||||
</vn-button>
|
||||
<vn-range
|
||||
vn-one
|
||||
label="Responsability"
|
||||
min-label="Company"
|
||||
max-label="Sales/Client"
|
||||
|
@ -82,14 +85,12 @@
|
|||
</span>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<vn-autocomplete vn-one
|
||||
id="claimDestinationFk"
|
||||
<vn-autocomplete vn-one id="claimDestinationFk"
|
||||
ng-model="saleClaimed.claimDestinationFk"
|
||||
url="/claim/api/ClaimDestinations"
|
||||
data="claimDestinations"
|
||||
fields="['id','description']"
|
||||
value-field="id"
|
||||
show-field="description"
|
||||
on-change="$ctrl.setClaimDestination(saleClaimed.id, value)">
|
||||
show-field="description">
|
||||
</vn-autocomplete>
|
||||
</vn-td>
|
||||
<vn-td>{{::saleClaimed.sale.ticket.landed | date: 'dd/MM/yyyy'}}</vn-td>
|
||||
|
|
|
@ -19,7 +19,8 @@ class Controller {
|
|||
}
|
||||
}
|
||||
},
|
||||
{relation: 'claimBeggining'}
|
||||
{relation: 'claimBeggining'},
|
||||
{relation: 'claimDestination'}
|
||||
]
|
||||
};
|
||||
this.resolvedState = 3;
|
||||
|
@ -82,16 +83,6 @@ class Controller {
|
|||
this.calculateTotals();
|
||||
}
|
||||
|
||||
setClaimDestination(id, claimDestinationFk) {
|
||||
if (claimDestinationFk) {
|
||||
let params = {id: id, claimDestinationFk: claimDestinationFk};
|
||||
let query = `claim/api/ClaimEnds/`;
|
||||
this.$http.patch(query, params).then(() => {
|
||||
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
calculateTotals() {
|
||||
this.claimedTotal = 0;
|
||||
this.salesClaimed.forEach(sale => {
|
||||
|
|
|
@ -82,17 +82,6 @@ describe('claim', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('setClaimDestination(id, claimDestinationFk)', () => {
|
||||
it('should make a patch and call refresh and showSuccess', () => {
|
||||
spyOn(controller.vnApp, 'showSuccess');
|
||||
$httpBackend.expectPATCH(`claim/api/ClaimEnds/`).respond({});
|
||||
controller.setClaimDestination(1, 1);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateTotals()', () => {
|
||||
it('should calculate the total price of the items claimed', () => {
|
||||
controller.salesClaimed = [
|
||||
|
|
|
@ -7,36 +7,34 @@ module.exports = Self => {
|
|||
type: 'Number',
|
||||
description: 'The client id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The warehouse id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The company id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'dmsTypeId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The dms type id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'reference',
|
||||
type: 'String',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
required: true
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'String',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
required: true
|
||||
}, {
|
||||
arg: 'hasFile',
|
||||
type: 'Boolean',
|
||||
description: ''
|
||||
description: 'True if has an attached file',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
|
|
|
@ -13,6 +13,10 @@ class Controller {
|
|||
callback.call(this);
|
||||
}
|
||||
|
||||
get client() {
|
||||
return this._client;
|
||||
}
|
||||
|
||||
set client(value) {
|
||||
this._client = value;
|
||||
|
||||
|
@ -32,10 +36,6 @@ class Controller {
|
|||
};
|
||||
}
|
||||
|
||||
get client() {
|
||||
return this._client;
|
||||
}
|
||||
|
||||
set quicklinks(value = {}) {
|
||||
this._quicklinks = Object.assign(value, this._quicklinks);
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ module.exports = Self => {
|
|||
case 'companyFk':
|
||||
case 'issued':
|
||||
case 'dued':
|
||||
param = `i.${param}`;
|
||||
return {[param]: value};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -105,15 +105,18 @@ module.exports = Self => {
|
|||
|
||||
|
||||
async function getTicketId(params, options) {
|
||||
const currentDate = new Date();
|
||||
currentDate.setHours(null, null, null);
|
||||
const minDate = new Date();
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
|
||||
const maxDate = new Date();
|
||||
maxDate.setHours(23, 59, 59, 59);
|
||||
|
||||
let ticket = await Self.app.models.Ticket.findOne({
|
||||
where: {
|
||||
addressFk: params.addressFk,
|
||||
warehouseFk: params.warehouseFk,
|
||||
shipped: currentDate,
|
||||
landed: currentDate
|
||||
shipped: {between: [minDate, maxDate]},
|
||||
landed: {between: [minDate, maxDate]}
|
||||
}
|
||||
}, options);
|
||||
|
||||
|
|
|
@ -52,24 +52,31 @@ class Controller {
|
|||
|
||||
get freeLineIndex() {
|
||||
let lines = this.$scope.model.data;
|
||||
let currentDate = new Date();
|
||||
currentDate.setHours(0, 0, 0);
|
||||
let minDate = new Date();
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
|
||||
let maxDate = new Date();
|
||||
maxDate.setHours(23, 59, 59, 59);
|
||||
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let isFutureDate = new Date(lines[i].date) >= currentDate;
|
||||
const dated = new Date(lines[i].date);
|
||||
|
||||
if (isFutureDate)
|
||||
let isForFuture = dated > maxDate;
|
||||
let isForToday = (dated >= minDate && dated <= maxDate);
|
||||
|
||||
if (isForFuture || isForToday)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
get onPreparationLineIndex() {
|
||||
let lines = this.$scope.model.data;
|
||||
|
||||
for (let i = this.freeLineIndex; i >= 0; i--) {
|
||||
let line = lines[i];
|
||||
|
||||
let currentDate = new Date();
|
||||
currentDate.setHours(0, 0, 0);
|
||||
currentDate.setHours(0, 0, 0, 0);
|
||||
|
||||
let isPastDate = new Date(lines[i].date) < currentDate;
|
||||
let isPicked = line.alertLevel == 1 && line.isPicked;
|
||||
|
@ -95,6 +102,7 @@ class Controller {
|
|||
let selectedTicketLineIndex = this.givenTicketIndex;
|
||||
let lineIndex = this.onPreparationLineIndex;
|
||||
|
||||
|
||||
let lines = body.querySelector('vn-tbody').children;
|
||||
|
||||
if (lineIndex == undefined || !lines.length) return;
|
||||
|
@ -120,7 +128,6 @@ class Controller {
|
|||
offsetTop = onPreparationLine.offsetTop - headerHeight;
|
||||
|
||||
this.$window.scrollTo(0, offsetTop);
|
||||
|
||||
this.ticketFk = null;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,11 @@
|
|||
<div class="content-block">
|
||||
<vn-card class="vn-pa-md vn-w-sm">
|
||||
<vn-horizontal style="align-items: center;">
|
||||
<vn-searchbar
|
||||
<vn-searchbar vn-focus
|
||||
panel="vn-item-search-panel"
|
||||
on-search="$ctrl.onSearch($params)"
|
||||
info="Search items by id, name or barcode"
|
||||
suggested-filter="{isActive: true}"
|
||||
vn-focus>
|
||||
suggested-filter="{isActive: true}">
|
||||
</vn-searchbar>
|
||||
<vn-icon-menu
|
||||
vn-id="more-button"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
ng-model="filter.atenderFk"
|
||||
ng-model="filter.attenderFk"
|
||||
url="/client/api/Clients/activeWorkersWithRole"
|
||||
search-function="{firstName: $search}"
|
||||
value-field="id"
|
||||
|
|
|
@ -1,121 +1,116 @@
|
|||
<vn-crud-model
|
||||
<vn-crud-model auto-load="true"
|
||||
vn-id="model"
|
||||
url="/ticket/api/TicketRequests/filter"
|
||||
limit="20"
|
||||
data="requests"
|
||||
order="isOk ASC"
|
||||
auto-load="false">
|
||||
order="shipped DESC, isOk ASC">
|
||||
</vn-crud-model>
|
||||
<form name="form">
|
||||
<div class="vn-ma-md">
|
||||
<vn-card class="vn-pa-md vn-list">
|
||||
<vn-horizontal>
|
||||
<vn-searchbar
|
||||
auto-load="false"
|
||||
panel="vn-request-search-panel"
|
||||
on-search="$ctrl.onSearch($params)"
|
||||
info="Search request by id or alias"
|
||||
vn-one
|
||||
vn-focus>
|
||||
</vn-searchbar>
|
||||
</vn-horizontal>
|
||||
<vn-searchbar vn-one vn-focus
|
||||
auto-load="false"
|
||||
panel="vn-request-search-panel"
|
||||
on-search="$ctrl.onSearch($params)"
|
||||
info="Search request by id or alias"
|
||||
suggested-filter="$ctrl.filter.where">
|
||||
</vn-searchbar>
|
||||
</vn-card>
|
||||
<vn-card class="vn-my-md">
|
||||
<vn-table model="model" auto-load="false">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="ticketFk" number>Ticket ID</vn-th>
|
||||
<vn-th field="shipped">Shipped</vn-th>
|
||||
<vn-th field="warehouse">Warehouse</vn-th>
|
||||
<vn-th field="salesPersonNickname">SalesPerson</vn-th>
|
||||
<vn-th field="description">Description</vn-th>
|
||||
<vn-th field="quantity" number editable>Quantity</vn-th>
|
||||
<vn-th field="price" number>Price</vn-th>
|
||||
<vn-th field="atenderNickname">Atender</vn-th>
|
||||
<vn-th field="itemFk">Item</vn-th>
|
||||
<vn-th field="description">Concept</vn-th>
|
||||
<vn-th field="saleQuantity" number>Sale quantity</vn-th>
|
||||
<vn-th field="isOk">State</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="request in requests">
|
||||
<vn-td number>
|
||||
<span class="link"
|
||||
ng-click="$ctrl.showTicketDescriptor($event, request.ticketFk)">
|
||||
{{request.ticketFk}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<span title="{{::request.shipped | date: 'dd/MM/yyyy'}}"
|
||||
class="chip {{$ctrl.compareDate(request.shipped)}}">
|
||||
{{::request.shipped | date: 'dd/MM/yyyy'}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>{{::request.warehouse}}</vn-td>
|
||||
<vn-td>
|
||||
<span
|
||||
class="link"
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, request.salesPersonFk)">
|
||||
{{::request.salesPersonNickname}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td title="{{::request.description}}">{{::request.description}}</vn-td>
|
||||
<vn-td number>{{::request.quantity}}</vn-td>
|
||||
<vn-td number>{{::request.price | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td>
|
||||
<span
|
||||
class="link"
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, request.atenderFk)">
|
||||
{{::request.atenderNickname}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td-editable disabled="request.isOk === 0" number>
|
||||
<text>{{request.itemFk}}</text>
|
||||
<field>
|
||||
<vn-input-number
|
||||
ng-model="request.itemFk"
|
||||
on-change="$ctrl.confirmRequest(request)">
|
||||
</vn-input-number>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td>
|
||||
<span
|
||||
class="link"
|
||||
ng-click="$ctrl.showItemDescriptor($event, request.itemFk)"
|
||||
title="{{::request.itemDescription}}">
|
||||
{{::request.itemDescription}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td-editable disabled="request.isOk === 0" number>
|
||||
<text number>{{request.saleQuantity}}</text>
|
||||
<field>
|
||||
<vn-input-number
|
||||
ng-model="request.saleQuantity"
|
||||
on-change="$ctrl.changeQuantity(request)">
|
||||
</vn-input-number>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td>{{::$ctrl.getState(request.isOk)}}</vn-td>
|
||||
<vn-td>
|
||||
<vn-icon
|
||||
ng-if="request.response.length"
|
||||
vn-tooltip="{{request.response}}"
|
||||
icon="insert_drive_file">
|
||||
</vn-icon>
|
||||
<vn-icon-button
|
||||
ng-if="request.isOk != 0"
|
||||
number
|
||||
icon="thumb_down"
|
||||
ng-click="$ctrl.showDenyReason($event, request.id)"
|
||||
vn-tooltip="Discard">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
<vn-pagination model="model"></vn-pagination>
|
||||
<vn-data-viewer model="model" class="vn-my-md">
|
||||
<vn-card>
|
||||
<vn-table model="model">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="ticketFk" number>Ticket ID</vn-th>
|
||||
<vn-th field="shipped">Shipped</vn-th>
|
||||
<vn-th field="warehouse">Warehouse</vn-th>
|
||||
<vn-th field="salesPersonNickname">SalesPerson</vn-th>
|
||||
<vn-th field="description">Description</vn-th>
|
||||
<vn-th field="quantity" number editable>Requested</vn-th>
|
||||
<vn-th field="price" number>Price</vn-th>
|
||||
<vn-th field="atenderNickname">Atender</vn-th>
|
||||
<vn-th field="itemFk">Item</vn-th>
|
||||
<vn-th field="saleQuantity">Achieved</vn-th>
|
||||
<vn-th field="description">Concept</vn-th>
|
||||
<vn-th field="isOk">State</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="request in requests">
|
||||
<vn-td number>
|
||||
<span class="link"
|
||||
ng-click="$ctrl.showTicketDescriptor($event, request.ticketFk)">
|
||||
{{request.ticketFk}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<span title="{{::request.shipped | date: 'dd/MM/yyyy'}}"
|
||||
class="chip {{$ctrl.compareDate(request.shipped)}}">
|
||||
{{::request.shipped | date: 'dd/MM/yyyy'}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>{{::request.warehouse}}</vn-td>
|
||||
<vn-td>
|
||||
<span
|
||||
class="link"
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, request.salesPersonFk)">
|
||||
{{::request.salesPersonNickname}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td title="{{::request.description}}">{{::request.description}}</vn-td>
|
||||
<vn-td number>{{::request.quantity}}</vn-td>
|
||||
<vn-td number>{{::request.price | currency: 'EUR':2}}</vn-td>
|
||||
<vn-td>
|
||||
<span
|
||||
class="link"
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, request.attenderFk)">
|
||||
{{::request.atenderNickname}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td-editable disabled="request.isOk != null" number>
|
||||
<text>{{request.itemFk}}</text>
|
||||
<field>
|
||||
<vn-input-number class="dense" vn-focus
|
||||
ng-model="request.itemFk">
|
||||
</vn-input-number>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td-editable disabled="!request.itemFk || request.isOk != null" number>
|
||||
<text number>{{request.saleQuantity}}</text>
|
||||
<field>
|
||||
<vn-input-number class="dense" vn-focus
|
||||
ng-model="request.saleQuantity"
|
||||
on-change="$ctrl.changeQuantity(request)">
|
||||
</vn-input-number>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td>
|
||||
<span
|
||||
class="link"
|
||||
ng-click="$ctrl.showItemDescriptor($event, request.itemFk)"
|
||||
title="{{request.itemDescription}}">
|
||||
{{request.itemDescription}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>{{$ctrl.getState(request.isOk)}}</vn-td>
|
||||
<vn-td>
|
||||
<vn-icon
|
||||
ng-if="request.response.length"
|
||||
ranslate-attr="{title: request.response}"
|
||||
icon="insert_drive_file">
|
||||
</vn-icon>
|
||||
<vn-icon-button
|
||||
ng-if="request.isOk != 0"
|
||||
icon="thumb_down"
|
||||
ng-click="$ctrl.showDenyReason($event, request)"
|
||||
translate-attr="{title: 'Discard'}">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
</vn-data-viewer>
|
||||
</div>
|
||||
</form>
|
||||
<vn-worker-descriptor-popover
|
||||
|
@ -129,21 +124,17 @@
|
|||
</vn-item-descriptor-popover>
|
||||
<vn-dialog
|
||||
vn-id="denyReason"
|
||||
class="modal-form">
|
||||
on-response="$ctrl.denyRequest(response)">
|
||||
<tpl-body>
|
||||
<vn-horizontal class="header">
|
||||
<h5><span translate>Indicate the reasons to deny this request</span></h5>
|
||||
</vn-horizontal>
|
||||
<h5 class="vn-pa-md" translate>Specify the reasons to deny this request</h5>
|
||||
<vn-horizontal class="vn-pa-md">
|
||||
<vn-textarea
|
||||
ng-model="$ctrl.denyObservation">
|
||||
</vn-textarea>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-pa-md">
|
||||
<vn-button
|
||||
label="Save"
|
||||
ng-click="$ctrl.denyRequest()">
|
||||
</vn-button>
|
||||
</vn-horizontal>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="ACCEPT" translate>Save</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
|
@ -9,8 +9,23 @@ export default class Controller {
|
|||
this.$ = $;
|
||||
this.vnApp = vnApp;
|
||||
this._ = $translate;
|
||||
if (!$stateParams.q)
|
||||
this.filter = {isOk: false, mine: true};
|
||||
if (!$stateParams.q) {
|
||||
const today = new Date();
|
||||
today.setHours(23, 59, 59, 59);
|
||||
|
||||
const lastWeek = new Date();
|
||||
lastWeek.setHours(0, 0, 0, 0);
|
||||
lastWeek.setDate(lastWeek.getDate() - 7);
|
||||
|
||||
this.filter = {
|
||||
where: {
|
||||
isOk: false,
|
||||
mine: true,
|
||||
from: lastWeek,
|
||||
to: today
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$postLink() {
|
||||
|
@ -21,7 +36,7 @@ export default class Controller {
|
|||
getState(isOk) {
|
||||
if (isOk === null)
|
||||
return 'Nueva';
|
||||
else if (isOk === -1 || isOk === 1)
|
||||
else if (isOk === -1 || isOk)
|
||||
return 'Aceptada';
|
||||
else
|
||||
return 'Denegada';
|
||||
|
@ -34,14 +49,12 @@ export default class Controller {
|
|||
quantity: request.saleQuantity
|
||||
};
|
||||
|
||||
let endpoint = `/api/TicketRequests/${request.id}/confirm`;
|
||||
let query = `/api/TicketRequests/${request.id}/confirm`;
|
||||
this.$http.post(query, params).then(res => {
|
||||
request.itemDescription = res.data.concept;
|
||||
request.isOk = true;
|
||||
|
||||
this.$http.post(endpoint, params).then(() => {
|
||||
this.vnApp.showSuccess(this._.instant('Data saved!'));
|
||||
this.$.model.refresh();
|
||||
}).catch( e => {
|
||||
this.$.model.refresh();
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -56,10 +69,7 @@ export default class Controller {
|
|||
|
||||
this.$http.patch(endpoint, params).then(() => {
|
||||
this.vnApp.showSuccess(this._.instant('Data saved!'));
|
||||
}).catch( e => {
|
||||
this.$.model.refresh();
|
||||
throw e;
|
||||
});
|
||||
}).then(() => this.confirmRequest(request));
|
||||
} else
|
||||
this.confirmRequest(request);
|
||||
}
|
||||
|
@ -86,7 +96,7 @@ export default class Controller {
|
|||
}
|
||||
|
||||
showDenyReason(event, requestId) {
|
||||
this.denyRequestId = requestId;
|
||||
this.selectedRequest = requestId;
|
||||
this.$.denyReason.parent = event.target;
|
||||
this.$.denyReason.show();
|
||||
document.querySelector('vn-item-request vn-textarea textArea').focus();
|
||||
|
@ -96,17 +106,21 @@ export default class Controller {
|
|||
delete this.denyRequestId;
|
||||
}
|
||||
|
||||
denyRequest() {
|
||||
denyRequest(response) {
|
||||
if (response !== 'ACCEPT') return;
|
||||
|
||||
let params = {
|
||||
observation: this.denyObservation
|
||||
};
|
||||
|
||||
let endpoint = `/api/TicketRequests/${this.denyRequestId}/deny`;
|
||||
let query = `/api/TicketRequests/${this.selectedRequest.id}/deny`;
|
||||
this.$http.post(query, params).then(res => {
|
||||
const request = res.data;
|
||||
this.selectedRequest.isOk = request.isOk;
|
||||
this.selectedRequest.attenderFk = request.attenderFk;
|
||||
this.selectedRequest.response = request.response;
|
||||
|
||||
this.$http.post(endpoint, params).then(() => {
|
||||
this.vnApp.showSuccess(this._.instant('Data saved!'));
|
||||
this.$.model.refresh();
|
||||
this.$.denyReason.hide();
|
||||
this.denyObservation = null;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -53,15 +53,15 @@ describe('Item', () => {
|
|||
let model = controller.$.model;
|
||||
spyOn(model, 'refresh');
|
||||
|
||||
const expectedResult = {concept: 'Melee Weapon'};
|
||||
let request = {itemFk: 1, saleQuantity: 1, id: 1};
|
||||
|
||||
$httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond();
|
||||
$httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond();
|
||||
$httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond(expectedResult);
|
||||
$httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond(expectedResult);
|
||||
controller.confirmRequest(request);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
||||
expect($scope.model.refresh).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -110,20 +110,17 @@ describe('Item', () => {
|
|||
describe('denyRequest()', () => {
|
||||
it(`should perform a query and call vnApp.showSuccess(), refresh(), hide() and set denyObservation to null in the controller`, () => {
|
||||
spyOn(controller.vnApp, 'showSuccess');
|
||||
let model = controller.$.model;
|
||||
spyOn(model, 'refresh');
|
||||
spyOn(controller.$.denyReason, 'hide');
|
||||
|
||||
controller.denyRequestId = 1;
|
||||
const request = {id: 1};
|
||||
const expectedResult = {isOk: false, attenderFk: 106, response: 'Denied!'};
|
||||
controller.selectedRequest = request;
|
||||
|
||||
$httpBackend.when('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond();
|
||||
$httpBackend.expect('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond();
|
||||
controller.denyRequest();
|
||||
$httpBackend.when('POST', `/api/TicketRequests/${request.id}/deny`).respond(expectedResult);
|
||||
$httpBackend.expect('POST', `/api/TicketRequests/${request.id}/deny`).respond(expectedResult);
|
||||
controller.denyRequest('ACCEPT');
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
||||
expect($scope.model.refresh).toHaveBeenCalledWith();
|
||||
expect($scope.denyReason.hide).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
Discard: Descartar
|
||||
Indicate the reasons to deny this request: Indique las razones para descartar esta peticion
|
||||
Specify the reasons to deny this request: Especifica las razones para descartar la petición
|
||||
Buy requests: Peticiones de compra
|
||||
Search request by id or alias: Buscar peticiones por identificador o alias
|
||||
Sale quantity: C. conseguida
|
||||
Requested: Solicitado
|
||||
Achieved: Conseguido
|
|
@ -66,7 +66,7 @@ class Controller {
|
|||
this.$scope.watcher.check();
|
||||
this.$scope.model.save().then(() => {
|
||||
this.$scope.watcher.notifySaved();
|
||||
this.$scope.model.refresh();
|
||||
this.$scope.watcher.updateOriginalData();
|
||||
this.card.reload();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -85,10 +85,13 @@ module.exports = Self => {
|
|||
return {'r.m3': value};
|
||||
case 'description':
|
||||
return {'r.description': {like: `%${value}%`}};
|
||||
case 'workerFk':
|
||||
case 'warehouseFk':
|
||||
param = `v.${param}`;
|
||||
return {[param]: value};
|
||||
case 'workerFk':
|
||||
case 'vehicleFk':
|
||||
case 'agencyModeFk':
|
||||
param = `r.${param}`;
|
||||
return {[param]: value};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -28,12 +28,6 @@ module.exports = Self => {
|
|||
fields: ['id', 'packages', 'warehouseFk', 'nickname', 'clientFk', 'priority', 'addressFk'],
|
||||
order: 'priority',
|
||||
include: [
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: ['id', 'street', 'postcode'],
|
||||
}
|
||||
},
|
||||
{
|
||||
relation: 'state',
|
||||
scope: {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Route getSelectedItems() should return the selected items 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"checked": true,
|
||||
"id": 1,
|
||||
},
|
||||
Object {
|
||||
"checked": true,
|
||||
"id": 3,
|
||||
},
|
||||
Object {
|
||||
"checked": true,
|
||||
"id": 5,
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -1,104 +1,105 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="/api/Routes/{{$ctrl.$stateParams.id}}/getTickets"
|
||||
url="api/Routes/{{$ctrl.$stateParams.id}}/getTickets"
|
||||
order="priority ASC"
|
||||
data="$ctrl.tickets"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<form name="form">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-tool-bar class="vn-mb-sm">
|
||||
<vn-button
|
||||
icon="icon-wand"
|
||||
ng-click="$ctrl.guessPriority()"
|
||||
vn-tooltip="Sort routes">
|
||||
</vn-button>
|
||||
<vn-button
|
||||
disabled="!$ctrl.isChecked"
|
||||
ng-click="$ctrl.goToBuscaman()"
|
||||
vn-tooltip="Open buscaman"
|
||||
tooltip-position="up"
|
||||
icon="icon-buscaman">
|
||||
</vn-button>
|
||||
</vn-tool-bar>
|
||||
<vn-icon-button
|
||||
vn-tooltip="Load more"
|
||||
ng-click="$ctrl.goToBuscaman()">
|
||||
</vn-icon-button>
|
||||
<vn-table model="model" auto-load="false">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th shrink>
|
||||
<vn-multi-check
|
||||
model="model">
|
||||
</vn-multi-check>
|
||||
</vn-th>
|
||||
<vn-th>Order</vn-th>
|
||||
<vn-th number>Ticket</vn-th>
|
||||
<vn-th>Client</vn-th>
|
||||
<vn-th number shrink>Packages</vn-th>
|
||||
<vn-th shrink>m³</vn-th>
|
||||
<vn-th shrink>Warehouse</vn-th>
|
||||
<vn-th shrink>PC</vn-th>
|
||||
<vn-th>Street</vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="ticket in $ctrl.tickets">
|
||||
<vn-td shrink>
|
||||
<vn-check
|
||||
ng-model="ticket.checked">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<vn-input-number
|
||||
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
|
||||
ng-model="ticket.priority"
|
||||
rule="Ticket">
|
||||
</vn-input-number>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
<span
|
||||
ng-click="$ctrl.showTicketDescriptor($event, ticket.id)"
|
||||
class="link">
|
||||
{{ticket.id}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<span
|
||||
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
|
||||
class="link">
|
||||
{{ticket.nickname}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number shrink>{{ticket.packages}}</vn-td>
|
||||
<vn-td shrink>{{ticket.volume}}</vn-td>
|
||||
<vn-td shrink>{{ticket.warehouse.name}}</vn-td>
|
||||
<vn-td shrink>{{ticket.client.postcode}}</vn-td>
|
||||
<vn-td expand title="{{ticket.client.street}}">{{ticket.client.street}}</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button
|
||||
ng-if="ticket.notes.length"
|
||||
title="::{{ticket.notes[0].description}}"
|
||||
icon="insert_drive_file">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<vn-icon-button
|
||||
translate-attr="::{title: 'Remove ticket'}"
|
||||
icon="delete"
|
||||
ng-click="$ctrl.showDeleteConfirm(ticket.id)"
|
||||
tabindex="-1">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
</form>
|
||||
|
||||
<vn-data-viewer model="model">
|
||||
<form name="form">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-tool-bar class="vn-mb-sm">
|
||||
<vn-button
|
||||
icon="icon-wand"
|
||||
ng-click="$ctrl.guessPriority()"
|
||||
vn-tooltip="Sort routes">
|
||||
</vn-button>
|
||||
<vn-button
|
||||
disabled="!$ctrl.isChecked"
|
||||
ng-click="$ctrl.goToBuscaman()"
|
||||
vn-tooltip="Open buscaman"
|
||||
tooltip-position="up"
|
||||
icon="icon-buscaman">
|
||||
</vn-button>
|
||||
</vn-tool-bar>
|
||||
<vn-icon-button
|
||||
vn-tooltip="Load more"
|
||||
ng-click="$ctrl.goToBuscaman()">
|
||||
</vn-icon-button>
|
||||
<vn-table model="model" auto-load="false">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th shrink>
|
||||
<vn-multi-check
|
||||
model="model">
|
||||
</vn-multi-check>
|
||||
</vn-th>
|
||||
<vn-th>Order</vn-th>
|
||||
<vn-th number>Ticket</vn-th>
|
||||
<vn-th>Client</vn-th>
|
||||
<vn-th number shrink>Packages</vn-th>
|
||||
<vn-th shrink>m³</vn-th>
|
||||
<vn-th shrink>Warehouse</vn-th>
|
||||
<vn-th expand>Postcode</vn-th>
|
||||
<vn-th>Street</vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
<vn-th shrink></vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="ticket in $ctrl.tickets">
|
||||
<vn-td shrink>
|
||||
<vn-check
|
||||
ng-model="ticket.checked">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<vn-input-number
|
||||
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
|
||||
ng-model="ticket.priority"
|
||||
rule="Ticket">
|
||||
</vn-input-number>
|
||||
</vn-td>
|
||||
<vn-td number>
|
||||
<span
|
||||
ng-click="$ctrl.showTicketDescriptor($event, ticket.id)"
|
||||
class="link">
|
||||
{{ticket.id}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<span
|
||||
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
|
||||
class="link">
|
||||
{{ticket.nickname}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number shrink>{{ticket.packages}}</vn-td>
|
||||
<vn-td shrink>{{ticket.volume}}</vn-td>
|
||||
<vn-td shrink>{{ticket.warehouse.name}}</vn-td>
|
||||
<vn-td number shrink>{{ticket.address.postalCode}}</vn-td>
|
||||
<vn-td expand title="{{ticket.address.street}}">{{ticket.address.street}}</vn-td>
|
||||
<vn-td shrink>
|
||||
<vn-icon-button
|
||||
ng-if="ticket.notes.length"
|
||||
title="::{{ticket.notes[0].description}}"
|
||||
icon="insert_drive_file">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<vn-icon-button
|
||||
translate-attr="::{title: 'Remove ticket'}"
|
||||
icon="delete"
|
||||
ng-click="$ctrl.showDeleteConfirm(ticket.id)"
|
||||
tabindex="-1">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
</form>
|
||||
</vn-data-viewer>
|
||||
<vn-ticket-descriptor-popover
|
||||
vn-id="ticketDescriptor">
|
||||
</vn-ticket-descriptor-popover>
|
||||
|
@ -109,4 +110,72 @@
|
|||
vn-id="confirm"
|
||||
question="Delete ticket from route?"
|
||||
on-response="$ctrl.removeTicketFromRoute(response)">
|
||||
</vn-confirm>
|
||||
</vn-confirm>
|
||||
<vn-crud-model
|
||||
vn-id="possibleTicketsModel"
|
||||
url="api/Tickets"
|
||||
filter="$ctrl.possibleTicketsFilter"
|
||||
data="$ctrl.possibleTickets">
|
||||
</vn-crud-model>
|
||||
<vn-dialog
|
||||
vn-id="possibleTicketsDialog"
|
||||
on-response="$ctrl.setTicketsRoute(response)">
|
||||
<tpl-body>
|
||||
<section class="header vn-pa-md">
|
||||
<h5><span translate>Tickets to add</span></h5>
|
||||
</section>
|
||||
<vn-data-viewer class="vn-pa-md" model="possibleTicketsModel">
|
||||
<vn-table model="possibleTicketsModel" auto-load="false">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th shrink>
|
||||
<vn-multi-check
|
||||
model="possibleTicketsModel">
|
||||
</vn-multi-check>
|
||||
</vn-th>
|
||||
<vn-th number>Ticket</vn-th>
|
||||
<vn-th>Client</vn-th>
|
||||
<vn-th number shrink>Packages</vn-th>
|
||||
<vn-th shrink>Warehouse</vn-th>
|
||||
<vn-th expand>Postcode</vn-th>
|
||||
<vn-th>Address</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="ticket in $ctrl.possibleTickets">
|
||||
<vn-td shrink>
|
||||
<vn-check
|
||||
ng-model="ticket.checked">
|
||||
</vn-check>
|
||||
</vn-td>
|
||||
<vn-td number>{{ticket.id}}</vn-td>
|
||||
<vn-td number>
|
||||
<span
|
||||
ng-click="$ctrl.showClientDescriptor($event, ticket.clientFk)"
|
||||
class="link">
|
||||
{{ticket.nickname}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td number shrink>{{ticket.packages}}</vn-td>
|
||||
<vn-td expand>{{ticket.warehouse.name}}</vn-td>
|
||||
<vn-td number shrink>{{ticket.address.postalCode}}</vn-td>
|
||||
<vn-td expand title="{{ticket.address.street}}">{{ticket.address.street}}</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-data-viewer>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<input type="button" response="CANCEL" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="ACCEPT" translate>Add</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
||||
<vn-float-button
|
||||
icon="add"
|
||||
ng-click="$ctrl.openPossibleTicketsDialog()"
|
||||
vn-tooltip="Add ticket"
|
||||
vn-acl="delivery"
|
||||
vn-acl-action="remove"
|
||||
vn-bind="+"
|
||||
fixed-bottom-right>
|
||||
</vn-float-button>
|
|
@ -2,12 +2,23 @@ import ngModule from '../module';
|
|||
import './style.scss';
|
||||
|
||||
class Controller {
|
||||
constructor($stateParams, $, $translate, $http, vnApp) {
|
||||
constructor($stateParams, $scope, $translate, $http, vnApp, $filter) {
|
||||
this.$translate = $translate;
|
||||
this.$stateParams = $stateParams;
|
||||
this.$ = $;
|
||||
this.$ = $scope;
|
||||
this.$http = $http;
|
||||
this.vnApp = vnApp;
|
||||
this.$filter = $filter;
|
||||
}
|
||||
|
||||
set route(value) {
|
||||
this._route = value;
|
||||
if (value)
|
||||
this.buildPossibleTicketsFilter();
|
||||
}
|
||||
|
||||
get route() {
|
||||
return this._route;
|
||||
}
|
||||
|
||||
get isChecked() {
|
||||
|
@ -19,13 +30,37 @@ class Controller {
|
|||
return false;
|
||||
}
|
||||
|
||||
buildPossibleTicketsFilter() {
|
||||
let minDate = new Date(this.route.finished);
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
|
||||
let maxDate = new Date(this.route.finished);
|
||||
maxDate.setHours(23, 59, 59, 59);
|
||||
|
||||
this.possibleTicketsFilter = {
|
||||
where: {
|
||||
zoneFk: this.route.zoneFk,
|
||||
routeFk: null,
|
||||
landed: {between: [minDate, maxDate]},
|
||||
},
|
||||
include: [
|
||||
{
|
||||
relation: 'warehouse',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
},
|
||||
}, {
|
||||
relation: 'address'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
getHighestPriority() {
|
||||
let max = 0;
|
||||
this.$.model.data.forEach(tag => {
|
||||
if (tag.priority > max)
|
||||
max = tag.priority;
|
||||
});
|
||||
return max + 1;
|
||||
let highestPriority = Math.max(...this.$.model.data.map(tag => {
|
||||
return tag.priority;
|
||||
}));
|
||||
return highestPriority + 1;
|
||||
}
|
||||
|
||||
setPriority(id, priority) {
|
||||
|
@ -37,16 +72,16 @@ class Controller {
|
|||
});
|
||||
}
|
||||
|
||||
getCheckedLines() {
|
||||
let lines = [];
|
||||
let data = this.tickets;
|
||||
if (data) {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].checked)
|
||||
lines.push(data[i]);
|
||||
getSelectedItems(items) {
|
||||
const selectedItems = [];
|
||||
|
||||
if (items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].checked)
|
||||
selectedItems.push(items[i]);
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
return selectedItems;
|
||||
}
|
||||
|
||||
goToBuscaman() {
|
||||
|
@ -54,7 +89,7 @@ class Controller {
|
|||
let firstAddress = `46460 Av Espioca 100-46460 Silla`;
|
||||
let addresses = firstAddress;
|
||||
|
||||
let lines = this.getCheckedLines();
|
||||
let lines = this.getSelectedItems(this.tickets);
|
||||
|
||||
let url = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=';
|
||||
lines.forEach(line => {
|
||||
|
@ -64,8 +99,8 @@ class Controller {
|
|||
window.open(url + addresses, '_blank');
|
||||
}
|
||||
|
||||
showDeleteConfirm(ticket) {
|
||||
this.selectedTicket = ticket;
|
||||
showDeleteConfirm(id) {
|
||||
this.selectedTicket = id;
|
||||
this.$.confirm.show();
|
||||
}
|
||||
|
||||
|
@ -109,14 +144,38 @@ class Controller {
|
|||
this.$.clientDescriptor.show();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
openPossibleTicketsDialog() {
|
||||
this.$.possibleTicketsModel.refresh();
|
||||
this.$.possibleTicketsDialog.show();
|
||||
}
|
||||
|
||||
setTicketsRoute(response) {
|
||||
if (response === 'ACCEPT') {
|
||||
let tickets = this.getSelectedItems(this.possibleTickets);
|
||||
|
||||
for (let i = 0; i < tickets.length; i++) {
|
||||
delete tickets[i].checked;
|
||||
tickets[i].routeFk = this.route.id;
|
||||
}
|
||||
|
||||
return this.$.possibleTicketsModel.save().then(() => {
|
||||
this.$.model.data = this.$.model.data.concat(tickets);
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp'];
|
||||
Controller.$inject = ['$stateParams', '$scope', '$translate', '$http', 'vnApp', '$filter'];
|
||||
|
||||
ngModule.component('vnRouteTickets', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
require: {
|
||||
card: '^vnRouteCard'
|
||||
},
|
||||
controller: Controller
|
||||
bindings: {
|
||||
route: '<'
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,288 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Route', () => {
|
||||
let controller;
|
||||
let $httpBackend;
|
||||
|
||||
beforeEach(angular.mock.module('route', $translateProvider => {
|
||||
$translateProvider.translations('en', {});
|
||||
}));
|
||||
|
||||
beforeEach(angular.mock.inject(($componentController, _$httpBackend_) => {
|
||||
$httpBackend = _$httpBackend_;
|
||||
controller = $componentController('vnRouteTickets');
|
||||
}));
|
||||
|
||||
describe('route setter/getter', () => {
|
||||
it('should return the route id', () => {
|
||||
controller.route = 2;
|
||||
|
||||
expect(controller.route).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isChecked getter', () => {
|
||||
it('should return false if none of the tickets is checked or there are no tickets', () => {
|
||||
expect(controller.isChecked).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true if any of the tickets is checked', () => {
|
||||
controller.tickets = [{checked: true}];
|
||||
|
||||
expect(controller.isChecked).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildPossibleTicketsFilter()', () => {
|
||||
it('should build the possible tickets filter', () => {
|
||||
let expectedFilter = {
|
||||
include: [
|
||||
{
|
||||
relation: 'warehouse',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}, {
|
||||
relation: 'address'
|
||||
}
|
||||
],
|
||||
where: {
|
||||
landed: {
|
||||
between: [
|
||||
jasmine.any(Date),
|
||||
jasmine.any(Date)
|
||||
]
|
||||
},
|
||||
routeFk: null,
|
||||
zoneFk: 67
|
||||
}
|
||||
};
|
||||
controller.route = {
|
||||
finished: new Date(),
|
||||
routeFk: null,
|
||||
zoneFk: 67
|
||||
};
|
||||
|
||||
controller.buildPossibleTicketsFilter();
|
||||
|
||||
expect(controller.possibleTicketsFilter).toEqual(expectedFilter);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHighestPriority()', () => {
|
||||
it('should return the highest value found in priorities plus 1', () => {
|
||||
controller.$.model = {data: [
|
||||
{priority: 99},
|
||||
{priority: 1},
|
||||
{priority: 2},
|
||||
{priority: 3},
|
||||
{priority: 4},
|
||||
{priority: 5},
|
||||
]};
|
||||
|
||||
let result = controller.getHighestPriority();
|
||||
|
||||
expect(result).toEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPriority()', () => {
|
||||
it('should set a ticket priority', () => {
|
||||
controller.$.model = {refresh: () => {}};
|
||||
spyOn(controller.$.model, 'refresh');
|
||||
spyOn(controller.vnApp, 'showSuccess');
|
||||
const ticketId = 1;
|
||||
const priority = 999;
|
||||
|
||||
$httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok');
|
||||
controller.setPriority(ticketId, priority);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
||||
expect(controller.$.model.refresh).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSelectedItems()', () => {
|
||||
it('should return the selected items', () => {
|
||||
let items = [
|
||||
{id: 1, checked: true},
|
||||
{id: 2, checked: false},
|
||||
{id: 3, checked: true},
|
||||
{id: 4, checked: false},
|
||||
{id: 5, checked: true},
|
||||
];
|
||||
|
||||
let selectedItems = controller.getSelectedItems(items);
|
||||
|
||||
expect(selectedItems).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('goToBuscaman()', () => {
|
||||
it('should open buscaman with the given arguments', () => {
|
||||
spyOn(window, 'open');
|
||||
const expectedUrl = 'http://gps.buscalia.com/usuario/localizar.aspx?bmi=true&addr=46460 Av Espioca 100-46460 Silla+to:n19 my street-n19 London';
|
||||
controller.tickets = [
|
||||
{
|
||||
id: 1,
|
||||
checked: true,
|
||||
address: {
|
||||
street: 'my street',
|
||||
postalCode: 'n19',
|
||||
city: 'London'
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
controller.goToBuscaman();
|
||||
|
||||
expect(window.open).toHaveBeenCalledWith(expectedUrl, '_blank');
|
||||
});
|
||||
});
|
||||
|
||||
describe('showDeleteConfirm()', () => {
|
||||
it('should open a confirm dialog after setting the selected ticket into the controller', () => {
|
||||
controller.$.confirm = {show: () => {}};
|
||||
spyOn(controller.$.confirm, 'show');
|
||||
let ticketId = 1;
|
||||
|
||||
controller.showDeleteConfirm(ticketId);
|
||||
|
||||
expect(controller.selectedTicket).toEqual(ticketId);
|
||||
expect(controller.$.confirm.show).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeTicketFromRoute()', () => {
|
||||
it('should perform a patch query then call showSuccess and updateVolume methods', () => {
|
||||
spyOn(controller, 'updateVolume');
|
||||
spyOn(controller.vnApp, 'showSuccess');
|
||||
let ticketId = 1;
|
||||
controller.selectedTicket = ticketId;
|
||||
|
||||
$httpBackend.expectPATCH(`/api/Tickets/${ticketId}/`).respond('ok');
|
||||
controller.removeTicketFromRoute('ACCEPT');
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Ticket removed from route');
|
||||
expect(controller.updateVolume).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateVolume()', () => {
|
||||
it('should perform a POST query then call both reload and refresh methods', () => {
|
||||
controller.$.model = {refresh: () => {}};
|
||||
controller.card = {reload: () => {}};
|
||||
controller.$stateParamds = {id: 999};
|
||||
spyOn(controller.$.model, 'refresh');
|
||||
spyOn(controller.card, 'reload');
|
||||
|
||||
let ticketId = 1;
|
||||
controller.selectedTicket = ticketId;
|
||||
|
||||
const url = `/route/api/Routes/${controller.$stateParams.id}/updateVolume`;
|
||||
$httpBackend.expectPOST(url).respond('ok');
|
||||
controller.updateVolume();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$.model.refresh).toHaveBeenCalledWith();
|
||||
expect(controller.card.reload).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('guessPriority()', () => {
|
||||
it('should perform a GET query then call both refresh and showSuccess methods', () => {
|
||||
controller.$.model = {refresh: () => {}};
|
||||
spyOn(controller.$.model, 'refresh');
|
||||
spyOn(controller.vnApp, 'showSuccess');
|
||||
controller.$stateParams = {id: 99};
|
||||
|
||||
const url = `/api/Routes/${controller.$stateParams.id}/guessPriority/`;
|
||||
$httpBackend.expectGET(url).respond('ok');
|
||||
controller.guessPriority();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Order changed');
|
||||
expect(controller.$.model.refresh).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('showTicketDescriptor()', () => {
|
||||
it('should call the descriptor show function after setting the parent and the ticket id', () => {
|
||||
controller.$.ticketDescriptor = {show: () => {}};
|
||||
spyOn(controller.$.ticketDescriptor, 'show');
|
||||
const event = {target: {}, preventDefault: () => {}};
|
||||
spyOn(event, 'preventDefault');
|
||||
const ticketId = 999;
|
||||
controller.showTicketDescriptor(event, ticketId);
|
||||
|
||||
expect(controller.$.ticketDescriptor.ticketFk).toEqual(ticketId);
|
||||
expect(controller.$.ticketDescriptor.show).toHaveBeenCalledWith();
|
||||
expect(event.preventDefault).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('showClientDescriptor()', () => {
|
||||
it('should call the descriptor show method after setting the parent and the client id', () => {
|
||||
controller.$.clientDescriptor = {show: () => {}};
|
||||
spyOn(controller.$.clientDescriptor, 'show');
|
||||
const event = {target: {}, preventDefault: () => {}};
|
||||
spyOn(event, 'preventDefault');
|
||||
const clientId = 999;
|
||||
controller.showClientDescriptor(event, clientId);
|
||||
|
||||
expect(controller.$.clientDescriptor.clientFk).toEqual(clientId);
|
||||
expect(controller.$.clientDescriptor.show).toHaveBeenCalledWith();
|
||||
expect(event.preventDefault).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('openPossibleTicketsDialog()', () => {
|
||||
it('should call both refresh and show methods in posible tickets model and dialog', () => {
|
||||
controller.$.possibleTicketsModel = {refresh: () => {}};
|
||||
spyOn(controller.$.possibleTicketsModel, 'refresh');
|
||||
controller.$.possibleTicketsDialog = {show: () => {}};
|
||||
spyOn(controller.$.possibleTicketsDialog, 'show');
|
||||
|
||||
controller.openPossibleTicketsDialog();
|
||||
|
||||
expect(controller.$.possibleTicketsModel.refresh).toHaveBeenCalledWith();
|
||||
expect(controller.$.possibleTicketsDialog.show).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTicketsRoute()', () => {
|
||||
it('should perform a POST query to add tickets to the route', done => {
|
||||
controller.$.possibleTicketsModel = {save: () => {}};
|
||||
spyOn(controller.$.possibleTicketsModel, 'save').and.returnValue(Promise.resolve());
|
||||
controller.$.model = {data: [
|
||||
{id: 1, checked: false}
|
||||
]};
|
||||
|
||||
controller.route = {id: 111};
|
||||
|
||||
controller.possibleTickets = [
|
||||
{id: 2, checked: false},
|
||||
{id: 3, checked: true},
|
||||
{id: 4, checked: false},
|
||||
{id: 5, checked: true},
|
||||
];
|
||||
|
||||
let expectedResult = [
|
||||
{checked: false, id: 1},
|
||||
{id: 3, routeFk: 111},
|
||||
{id: 5, routeFk: 111}
|
||||
];
|
||||
|
||||
controller.setTicketsRoute('ACCEPT').then(() => {
|
||||
expect(controller.$.model.data).toEqual(expectedResult);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('should just return a promise', () => {
|
||||
expect(controller.setTicketsRoute('CANCEL')).toEqual(jasmine.any(Promise));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3,4 +3,6 @@ Open buscaman: Abrir buscaman
|
|||
Ticket removed from route: Ticket borrado de la ruta
|
||||
Order changed: Orden cambiado
|
||||
Delete ticket from route?: ¿Borrar ticket de la ruta?
|
||||
Sort routes: Ordenar rutas
|
||||
Sort routes: Ordenar rutas
|
||||
Add ticket: Añadir ticket
|
||||
Tickets to add: Tickets a añadir
|
|
@ -38,27 +38,27 @@ module.exports = Self => {
|
|||
try {
|
||||
let options = {transaction: tx};
|
||||
|
||||
let item = await models.Item.findById(ctx.args.itemFk);
|
||||
let item = await models.Item.findById(ctx.args.itemFk, null, options);
|
||||
if (!item)
|
||||
throw new UserError(`That item doesn't exists`);
|
||||
|
||||
let request = await models.TicketRequest.findById(ctx.args.id, {
|
||||
include: {relation: 'ticket'}
|
||||
});
|
||||
}, options);
|
||||
|
||||
let [[stock]] = await Self.rawSql(`CALL vn.getItemVisibleAvailable(?,?,?,?)`, [
|
||||
ctx.args.itemFk,
|
||||
request.ticket().shipped,
|
||||
request.ticket().warehouseFk,
|
||||
false
|
||||
]);
|
||||
], options);
|
||||
|
||||
if (stock.available < 0)
|
||||
throw new UserError(`This item is not available`);
|
||||
|
||||
|
||||
if (request.saleFk) {
|
||||
sale = await models.Sale.findById(request.saleFk);
|
||||
sale = await models.Sale.findById(request.saleFk, null, options);
|
||||
sale.updateAttributes({
|
||||
itemFk: ctx.args.itemFk,
|
||||
quantity: ctx.args.quantity,
|
||||
|
@ -71,7 +71,11 @@ module.exports = Self => {
|
|||
quantity: ctx.args.quantity,
|
||||
concept: item.name
|
||||
}, options);
|
||||
request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk, isOk: true}, options);
|
||||
request.updateAttributes({
|
||||
saleFk: sale.id,
|
||||
itemFk: sale.itemFk,
|
||||
isOk: true
|
||||
}, options);
|
||||
}
|
||||
|
||||
query = `CALL vn.ticketCalculateSale(?)`;
|
||||
|
@ -86,6 +90,8 @@ module.exports = Self => {
|
|||
}, options);
|
||||
|
||||
await tx.commit();
|
||||
|
||||
return sale;
|
||||
} catch (error) {
|
||||
await tx.rollback();
|
||||
throw error;
|
||||
|
|
|
@ -29,7 +29,7 @@ module.exports = Self => {
|
|||
|
||||
let params = {
|
||||
isOk: false,
|
||||
atenderFk: worker.id,
|
||||
attenderFk: worker.id,
|
||||
response: ctx.args.observation,
|
||||
};
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ module.exports = Self => {
|
|||
type: 'Number',
|
||||
description: `Search by warehouse`
|
||||
}, {
|
||||
arg: 'atenderFk',
|
||||
arg: 'attenderFk',
|
||||
type: 'Number',
|
||||
description: `Search requests atended by the given worker`
|
||||
}, {
|
||||
|
@ -65,7 +65,7 @@ module.exports = Self => {
|
|||
let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}});
|
||||
|
||||
if (ctx.args.mine)
|
||||
ctx.args.atenderFk = worker.id;
|
||||
ctx.args.attenderFk = worker.id;
|
||||
|
||||
let where = buildFilter(ctx.args, (param, value) => {
|
||||
switch (param) {
|
||||
|
@ -75,7 +75,7 @@ module.exports = Self => {
|
|||
: {'t.nickname': {like: `%${value}%`}};
|
||||
case 'ticketFk':
|
||||
return {'t.id': value};
|
||||
case 'atenderFk':
|
||||
case 'attenderFk':
|
||||
return {'tr.atenderFk': value};
|
||||
case 'isOk':
|
||||
return {'tr.isOk': value};
|
||||
|
@ -106,13 +106,13 @@ module.exports = Self => {
|
|||
tr.ticketFk,
|
||||
tr.quantity,
|
||||
tr.price,
|
||||
tr.atenderFk,
|
||||
tr.atenderFk attenderFk,
|
||||
tr.description,
|
||||
tr.response,
|
||||
tr.saleFk,
|
||||
tr.isOk,
|
||||
s.quantity AS saleQuantity,
|
||||
s.itemFK,
|
||||
s.itemFk,
|
||||
i.name AS itemDescription,
|
||||
t.shipped,
|
||||
t.nickname,
|
||||
|
|
|
@ -1,27 +1,14 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('ticket-request confirm()', () => {
|
||||
let request;
|
||||
let sale;
|
||||
let originalRequest;
|
||||
let originalSale;
|
||||
let createdSaleId;
|
||||
|
||||
afterAll(async done => {
|
||||
const paramsForRequest = {
|
||||
saleFk: request.saleFk,
|
||||
isOk: request.isOk,
|
||||
itemFk: request.itemFk,
|
||||
ticketFk: request.ticketFk
|
||||
};
|
||||
|
||||
const paramsForSale = {
|
||||
itemFk: sale.itemFk,
|
||||
quantity: sale.quantity,
|
||||
concept: sale.concept,
|
||||
};
|
||||
|
||||
await request.updateAttributes(paramsForRequest);
|
||||
await sale.updateAttributes(paramsForSale);
|
||||
app.models.Sale.destroyById(createdSaleId);
|
||||
await originalRequest.updateAttributes(originalRequest);
|
||||
await originalSale.updateAttributes(originalSale);
|
||||
await app.models.Sale.destroyById(createdSaleId);
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -65,10 +52,11 @@ describe('ticket-request confirm()', () => {
|
|||
const itemId = 1;
|
||||
const quantity = 10;
|
||||
|
||||
request = await app.models.TicketRequest.findById(requestId);
|
||||
sale = await app.models.Sale.findById(saleId);
|
||||
originalRequest = await app.models.TicketRequest.findById(requestId);
|
||||
originalSale = await app.models.Sale.findById(saleId);
|
||||
|
||||
request.updateAttributes({saleFk: saleId});
|
||||
const request = await app.models.TicketRequest.findById(requestId);
|
||||
await request.updateAttributes({saleFk: saleId});
|
||||
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {
|
||||
itemFk: itemId,
|
||||
|
@ -89,7 +77,8 @@ describe('ticket-request confirm()', () => {
|
|||
const itemId = 1;
|
||||
const quantity = 10;
|
||||
|
||||
request.updateAttributes({saleFk: null});
|
||||
const request = await app.models.TicketRequest.findById(requestId);
|
||||
await request.updateAttributes({saleFk: null});
|
||||
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {
|
||||
itemFk: itemId,
|
||||
|
|
|
@ -5,7 +5,7 @@ describe('ticket-request deny()', () => {
|
|||
afterAll(async done => {
|
||||
let params = {
|
||||
isOk: null,
|
||||
atenderFk: request.atenderFk,
|
||||
attenderFk: request.attenderFk,
|
||||
response: null,
|
||||
};
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('ticket-request filter()', () => {
|
|||
});
|
||||
|
||||
it('should return the ticket request matching the atender ID', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {atenderFk: 35}};
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {attenderFk: 35}};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
|
|
|
@ -40,7 +40,8 @@ module.exports = function(Self) {
|
|||
}, options);
|
||||
|
||||
const hasPurchaseRequests = await models.TicketRequest.count({
|
||||
ticketFk: id
|
||||
ticketFk: id,
|
||||
isOk: true
|
||||
}, options);
|
||||
|
||||
const isEmpty = !hasSales && !hasPackages &&
|
||||
|
|
|
@ -7,36 +7,34 @@ module.exports = Self => {
|
|||
type: 'Number',
|
||||
description: 'The ticket id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The warehouse id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The company id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'dmsTypeId',
|
||||
type: 'Number',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
description: 'The dms type id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'reference',
|
||||
type: 'String',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
required: true
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'String',
|
||||
description: ''
|
||||
},
|
||||
{
|
||||
required: true
|
||||
}, {
|
||||
arg: 'hasFile',
|
||||
type: 'Boolean',
|
||||
description: ''
|
||||
description: 'True if has an attached file',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
|
|
|
@ -33,9 +33,12 @@
|
|||
"isOk": {
|
||||
"type": "Boolean"
|
||||
},
|
||||
"atenderFk": {
|
||||
"attenderFk": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
"required": true,
|
||||
"mysql": {
|
||||
"columnName": "atenderFk"
|
||||
}
|
||||
},
|
||||
"response": {
|
||||
"type": "String"
|
||||
|
@ -55,7 +58,7 @@
|
|||
"atender": {
|
||||
"type": "belongsTo",
|
||||
"model": "Worker",
|
||||
"foreignKey": "atenderFk"
|
||||
"foreignKey": "attenderFk"
|
||||
},
|
||||
"requester": {
|
||||
"type": "belongsTo",
|
||||
|
|
|
@ -176,7 +176,7 @@ class Controller {
|
|||
links.btnTwo = {
|
||||
icon: 'icon-stowaway',
|
||||
state: `ticket.card.summary({id: ${value.stowaway.shipFk}})`,
|
||||
tooltip: 'Ship'
|
||||
tooltip: 'Ship stowaways'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
ng-model="$ctrl.ticketRequest.atenderFk"
|
||||
ng-model="$ctrl.ticketRequest.attenderFk"
|
||||
url="/client/api/Clients/activeWorkersWithRole"
|
||||
show-field="nickname"
|
||||
search-function="{firstName: $search}"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="/ticket/api/TicketRequests"
|
||||
fields="['id', 'description', 'created', 'requesterFk', 'atenderFk', 'quantity', 'price', 'saleFk', 'isOk']"
|
||||
fields="['id', 'description', 'created', 'requesterFk', 'attenderFk', 'quantity', 'price', 'saleFk', 'isOk']"
|
||||
order="created ASC"
|
||||
link="{ticketFk: $ctrl.$stateParams.id}"
|
||||
filter="::$ctrl.filter"
|
||||
|
@ -43,7 +43,7 @@
|
|||
<vn-td expand>
|
||||
<span
|
||||
class="link"
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, request.atenderFk)">
|
||||
ng-click="$ctrl.showWorkerDescriptor($event, request.attenderFk)">
|
||||
{{::request.atender.user.nickname | dashIfEmpty}}
|
||||
</span>
|
||||
</vn-td>
|
||||
|
|
|
@ -127,7 +127,7 @@
|
|||
<vn-td-editable ng-if="sale.id" disabled="!$ctrl.isEditable" number>
|
||||
<text>{{sale.quantity}}</text>
|
||||
<field>
|
||||
<vn-input-number
|
||||
<vn-input-number class="dense"
|
||||
vn-focus
|
||||
ng-model="sale.quantity"
|
||||
on-change="$ctrl.onChangeQuantity(sale)">
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
Author : Enrique Blasco BLanquer
|
||||
Date: 27 de mayo de 2019
|
||||
*/
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('addAutoTime', {
|
||||
description: 'Adds a new hour registry by app in manual 0',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'data',
|
||||
type: 'object',
|
||||
required: true,
|
||||
description: 'timed',
|
||||
http: {source: 'body'}
|
||||
}],
|
||||
returns: [{
|
||||
type: 'Object',
|
||||
root: true
|
||||
}],
|
||||
http: {
|
||||
path: `/addAutoTime`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.addAutoTime = async(ctx, data) => {
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
|
||||
// get all worked time control, needed to calculate order
|
||||
let hours = await Self.rawSql(`SELECT * FROM vn.workerTimeControl
|
||||
WHERE userFk = ?
|
||||
AND DATE(timed) = CURDATE()
|
||||
ORDER BY timed DESC LIMIT 1`, [myUserId]);
|
||||
|
||||
// 1 get next order
|
||||
let order = 0;
|
||||
if (hours.length > 0)
|
||||
order = hours[hours.length - 1].order;
|
||||
|
||||
// 2 create element in db
|
||||
return Self.create({
|
||||
userFk: myUserId,
|
||||
timed: data.timed,
|
||||
order: order + 1,
|
||||
manual: 0
|
||||
});
|
||||
};
|
||||
};
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
Author : Enrique Blasco BLanquer
|
||||
Date: 28 de mayo de 2019
|
||||
*/
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('getHoursWorked', {
|
||||
description: 'Get worked hours in current week, month and year',
|
||||
accessType: 'WRITE',
|
||||
returns: [{
|
||||
type: 'Object',
|
||||
root: true
|
||||
}],
|
||||
http: {
|
||||
path: `/getHoursWorked`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getHoursWorked = async(ctx, data) => {
|
||||
let totalHours = 0; // total hours worked in one year
|
||||
let totalMinutes = 0; // total minutes worked in one year
|
||||
let totalHoursMonth = 0; // total hours worked in one month
|
||||
let totalMinutesMonth = 0; // total minutes worked in one month
|
||||
let totalHoursWeek = 0; // total hours worked in one week
|
||||
let totalMinutesWeek = 0; // total minutes worked in one week
|
||||
const myUserId = ctx.req.accessToken.userId; // user id
|
||||
let today = new Date(); // needed to calculate total hours worked to current date
|
||||
let fromDate = today.getFullYear() + '-01-01'; // from date, current year
|
||||
let toDate = today.getFullYear() + '-12-31'; // to date, current year
|
||||
|
||||
|
||||
// 1 hours worked in a year
|
||||
let hoursYear = await Self.rawSql(`SELECT wtc.userFk, DATE(wtc.timed) dated,
|
||||
UNIX_TIMESTAMP(MIN(timed))timedStart,
|
||||
SEC_TO_TIME(SUM(if( mod(wtc.order,2)=1,
|
||||
UNIX_TIMESTAMP(timed) *-1,
|
||||
UNIX_TIMESTAMP(timed)))) timeWorkDay
|
||||
FROM vn.workerTimeControl wtc
|
||||
WHERE wtc.timed BETWEEN ? AND ? AND userFk = ?
|
||||
GROUP BY wtc.userFk,dated ORDER BY dated DESC`, [fromDate, toDate, myUserId]);
|
||||
|
||||
|
||||
// 2 Get days of week
|
||||
let week = [];
|
||||
// Starting Monday not Sunday
|
||||
let current = new Date();
|
||||
current.setDate((current.getDate() - current.getDay() + 1));
|
||||
for (let i = 0; i < 7; i++) {
|
||||
week.push(
|
||||
new Date(current)
|
||||
);
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
|
||||
// 3 I have all timed control for one year... NOW I CALCULATE TOTAL HOURS IN YEAR, MONTH, WEEK, Let's GO!
|
||||
for (hour of hoursYear) {
|
||||
if (parseInt(hour.timeWorkDay.split(':')[0]) > 0) {
|
||||
// YEAR
|
||||
totalHours += parseInt(hour.timeWorkDay.split(':')[0]);
|
||||
totalMinutes += parseInt(hour.timeWorkDay.split(':')[1]);
|
||||
// If it exceeds 5 hours we add 20 minutes of breakfast.
|
||||
if (parseInt(hour.timeWorkDay.split(':')[0]) >= 5)
|
||||
totalMinutes += 20;
|
||||
// MONTH
|
||||
|
||||
if ((new Date(hour.dated)).getMonth() == today.getMonth()) {
|
||||
totalHoursMonth += parseInt(hour.timeWorkDay.split(':')[0]);
|
||||
totalMinutesMonth += parseInt(hour.timeWorkDay.split(':')[1]);
|
||||
// If it exceeds 5 hours we add 20 minutes of breakfast.
|
||||
if (parseInt(hour.timeWorkDay.split(':')[0]) >= 5)
|
||||
totalMinutesMonth += 20;
|
||||
}
|
||||
// WEEK
|
||||
for (day of week) {
|
||||
let dayOfWeek = new Date(day);
|
||||
let dayOfCurrentWeek = new Date(hour.dated);
|
||||
if (dayOfWeek.getMonth() == dayOfCurrentWeek.getMonth() && dayOfWeek.getDate() == dayOfCurrentWeek.getDate()) {
|
||||
totalHoursWeek += parseInt(hour.timeWorkDay.split(':')[0]);
|
||||
totalMinutesWeek += parseInt(hour.timeWorkDay.split(':')[1]);
|
||||
// If it exceeds 5 hours we add 20 minutes of breakfast.
|
||||
if (parseInt(hour.timeWorkDay.split(':')[0]) >= 5)
|
||||
totalMinutesWeek += 20;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TOTAL WORKED HOURS IN THE YEAR
|
||||
totalHours += totalMinutes / 60;
|
||||
totalHours = decimalToHour(totalHours);
|
||||
|
||||
// TOTAL WORKED HOURS IN THE MONTH
|
||||
totalHoursMonth += totalMinutesMonth / 60;
|
||||
totalHoursMonth = decimalToHour(totalHoursMonth);
|
||||
|
||||
// TOTAL WORKED HOURS IN THE WEEK
|
||||
totalHoursWeek += totalMinutesWeek / 60;
|
||||
totalHoursWeek = decimalToHour(totalHoursWeek);
|
||||
|
||||
return {
|
||||
'totalWorekdYear': totalHours,
|
||||
'totalWorekdMonth': totalHoursMonth,
|
||||
'totalWorkedWeek': totalHoursWeek
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
function to calculate hours and minutes from decimal value
|
||||
*/
|
||||
function decimalToHour(value) {
|
||||
let decimalTime = parseFloat(value);
|
||||
decimalTime = decimalTime * 60 * 60;
|
||||
let hoursDay = Math.floor((decimalTime / (60 * 60)));
|
||||
decimalTime = decimalTime - (hoursDay * 60 * 60);
|
||||
let minutesDay = Math.floor((decimalTime / 60));
|
||||
return hoursDay + ':' + minutesDay;
|
||||
}
|
||||
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
Author : Enrique Blasco BLanquer
|
||||
Date: 29 de mayo de 2019
|
||||
*/
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('getWorkedWeek', {
|
||||
description: 'get worked week info',
|
||||
accessType: 'WRITE',
|
||||
returns: [{
|
||||
type: 'Object',
|
||||
root: true
|
||||
}],
|
||||
http: {
|
||||
path: `/getWorkedWeek`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getWorkedWeek = async(ctx, data) => {
|
||||
const myUserId = ctx.req.accessToken.userId; // user id
|
||||
let lastDate = new Date('1986-09-24'); // reference date
|
||||
let diff = 0; // difference of value between two dates
|
||||
let total = 0; // total hours
|
||||
|
||||
// 1 Get days of week
|
||||
let week = [];
|
||||
// 2 Starting Monday not Sunday
|
||||
let current = new Date();
|
||||
current.setDate((current.getDate() - current.getDay() + 1));
|
||||
for (let i = 0; i < 7; i++) {
|
||||
week.push(
|
||||
new Date(current)
|
||||
);
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
|
||||
let fromDate = week[0].getFullYear() + '-' + (week[0].getMonth() + 1) + '-' + week[0].getDate();
|
||||
let toDate = week[week.length - 1].getFullYear() + '-' + (week[week.length - 1].getMonth() + 1) + '-' + week[week.length - 1].getDate();
|
||||
|
||||
|
||||
// 3 hours worked in a current week
|
||||
let hoursWeek = await Self.rawSql(`SELECT wtc.timed ,wtc.order
|
||||
FROM vn.workerTimeControl wtc
|
||||
WHERE userFk = ?
|
||||
AND DATE(timed) BETWEEN ? AND ? ORDER BY timed DESC;`, [myUserId, fromDate, toDate]);
|
||||
|
||||
// 4 treat data
|
||||
let isFirst = true;
|
||||
for (let i = hoursWeek.length - 1; i >= 0; i--) {
|
||||
let d = new Date(hoursWeek[i].timed);
|
||||
if (isFirst) {
|
||||
lastDate = d;
|
||||
isFirst = false;
|
||||
} else {
|
||||
if (lastDate.getDate() === d.getDate()) {
|
||||
diff += Math.abs(d.getTime() - lastDate.getTime());
|
||||
lastDate = d;
|
||||
} else {
|
||||
total += diff;
|
||||
diff = 0;
|
||||
lastDate = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
total += diff;
|
||||
|
||||
// 5 calculate hours and minutes
|
||||
let decimalTime = total / 1000 / 3600;
|
||||
decimalTime = decimalTime * 60 * 60;
|
||||
let hours = Math.floor((decimalTime / (60 * 60)));
|
||||
decimalTime = decimalTime - (hours * 60 * 60);
|
||||
let minutes = Math.floor((decimalTime / 60));
|
||||
|
||||
return {'timeds': hoursWeek, 'totalWorked': hours + ':' + minutes};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('workerTimeControl addAutoTime()', () => {
|
||||
it('should return an undefined value', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}};
|
||||
let data = {'timed': new Date()};
|
||||
let result = await app.models.WorkerTimeControl.addAutoTime(ctx, data);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('workerTimeControl getHoursWorked()', () => {
|
||||
it('should return an totalWorkedYear to be defined', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}};
|
||||
let result = await app.models.WorkerTimeControl.getHoursWorked(ctx, null);
|
||||
|
||||
expect(result.totalWorekdYear).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('workerTimeControl getWorkedWeek()', () => {
|
||||
it('should return an timeds to be defined', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}};
|
||||
let result = await app.models.WorkerTimeControl.getWorkedWeek(ctx, null);
|
||||
|
||||
expect(result.timeds).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -49,14 +49,5 @@
|
|||
},
|
||||
"Device": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserPhoneType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserPhone": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserLog": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,6 @@ const UserError = require('vn-loopback/util/user-error');
|
|||
module.exports = Self => {
|
||||
require('../methods/worker-time-control/filter')(Self);
|
||||
require('../methods/worker-time-control/addTime')(Self);
|
||||
require('../methods/worker-time-control/addAutoTime')(Self);
|
||||
require('../methods/worker-time-control/getHoursWorked')(Self);
|
||||
require('../methods/worker-time-control/getWorkedWeek')(Self);
|
||||
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_DUP_ENTRY')
|
||||
|
|
|
@ -54,6 +54,12 @@
|
|||
"type": "hasMany",
|
||||
"model": "WorkerTeamCollegues",
|
||||
"foreignKey": "workerFk"
|
||||
},
|
||||
"phones": {
|
||||
"type": "hasMany",
|
||||
"model": "UserPhone",
|
||||
"foreignKey": "userFk",
|
||||
"primaryKey": "userFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,14 +23,6 @@
|
|||
rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Phone"
|
||||
ng-model="$ctrl.worker.phone"
|
||||
rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
|
|
|
@ -40,6 +40,12 @@ class Controller {
|
|||
relation: 'department'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
relation: 'phones',
|
||||
scope: {
|
||||
fields: ['phone'],
|
||||
order: 'typeFk ASC'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -26,20 +26,16 @@
|
|||
<vn-label-value label="Department"
|
||||
value="{{$ctrl.worker.department.department.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Phone"
|
||||
value="{{$ctrl.worker.phone}}">
|
||||
<vn-label-value ng-repeat ="phone in $ctrl.worker.phones"
|
||||
label="Phone"
|
||||
value="{{phone.phone}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Extension"
|
||||
value="{{$ctrl.worker.sip.extension}}">
|
||||
</vn-label-value>
|
||||
</div>
|
||||
<div class="quicklinks">
|
||||
<a
|
||||
ui-sref="client.card.summary({id: $ctrl.worker.userFk})"
|
||||
vn-tooltip="Go to client"
|
||||
class="vn-button">
|
||||
<vn-icon icon="person"></vn-icon>
|
||||
</a>
|
||||
</div>
|
||||
<vn-quick-links
|
||||
links="$ctrl.quicklinks">
|
||||
</vn-quick-links>
|
||||
</div>
|
||||
</div>
|
|
@ -1,7 +1,41 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
class Controller {
|
||||
constructor($http, $state) {
|
||||
this.$state = $state;
|
||||
this.$http = $http;
|
||||
}
|
||||
|
||||
get worker() {
|
||||
return this._worker;
|
||||
}
|
||||
|
||||
set worker(value) {
|
||||
this._worker = value;
|
||||
|
||||
if (!value) return;
|
||||
|
||||
this._quicklinks = {
|
||||
btnOne: {
|
||||
icon: 'person',
|
||||
state: `client.card.summary({id: ${value.userFk}})`,
|
||||
tooltip: 'Go to client'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
set quicklinks(value = {}) {
|
||||
this._quicklinks = Object.assign(value, this._quicklinks);
|
||||
}
|
||||
|
||||
get quicklinks() {
|
||||
return this._quicklinks;
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnWorkerDescriptor', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
worker: '<'
|
||||
}
|
||||
|
|
|
@ -12,3 +12,4 @@ import './department';
|
|||
import './calendar';
|
||||
import './time-control';
|
||||
import './log';
|
||||
import './phones';
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<vn-crud-model
|
||||
url="/api/UserPhoneTypes"
|
||||
data="phoneTypes"
|
||||
auto-load="true">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="/api/UserPhones"
|
||||
data="$ctrl.phones">
|
||||
</vn-crud-model>
|
||||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
data="$ctrl.phones">
|
||||
</vn-watcher>
|
||||
<form name="form" ng-submit="$ctrl.onSubmit()" compact>
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal ng-repeat="workerPhone in $ctrl.phones">
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
ng-model="workerPhone.typeFk"
|
||||
initial-data="workerPhone.typeFk"
|
||||
data ="phoneTypes"
|
||||
show-field="code"
|
||||
value-field="code"
|
||||
label="Type"
|
||||
vn-focus>
|
||||
</vn-autocomplete>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Phone"
|
||||
ng-model="workerPhone.phone">
|
||||
</vn-textfield>
|
||||
<vn-none>
|
||||
<vn-icon-button
|
||||
vn-tooltip="Remove phone"
|
||||
icon="delete"
|
||||
ng-click="model.remove($index)"
|
||||
tabindex="-1">
|
||||
</vn-icon-button>
|
||||
</vn-none>
|
||||
</vn-horizontal>
|
||||
<vn-one>
|
||||
<vn-icon-button
|
||||
vn-bind="+"
|
||||
vn-tooltip="Add phone"
|
||||
icon="add_circle"
|
||||
ng-click="$ctrl.add()">
|
||||
</vn-icon-button>
|
||||
</vn-one>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit label="Save"></vn-submit>
|
||||
</vn-button-bar>
|
||||
</form>
|
|
@ -0,0 +1,48 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
class Controller {
|
||||
constructor($scope) {
|
||||
this.$scope = $scope;
|
||||
}
|
||||
|
||||
get worker() {
|
||||
return this._worker;
|
||||
}
|
||||
|
||||
set worker(value) {
|
||||
this._worker = value;
|
||||
if (value)
|
||||
this.setLink(value);
|
||||
}
|
||||
|
||||
setLink(value) {
|
||||
this.$scope.$applyAsync(()=> {
|
||||
this.$scope.model.link = {userFk: value.userFk};
|
||||
this.$scope.model.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
this.$scope.watcher.check();
|
||||
this.$scope.model.save().then(() => {
|
||||
this.$scope.watcher.updateOriginalData();
|
||||
this.$scope.watcher.notifySaved();
|
||||
this.card.reload();
|
||||
});
|
||||
}
|
||||
|
||||
add() {
|
||||
this.$scope.model.insert();
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$scope'];
|
||||
|
||||
ngModule.component('vnWorkerPhones', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller,
|
||||
require: {card: '^vnWorkerCard'},
|
||||
bindings: {
|
||||
worker: '<'
|
||||
}
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
import './index';
|
||||
|
||||
describe('Component vnWorkerPhones', () => {
|
||||
let controller;
|
||||
|
||||
beforeEach(angular.mock.module('worker', $translateProvider => {
|
||||
$translateProvider.translations('en', {});
|
||||
}));
|
||||
|
||||
beforeEach(angular.mock.inject(($componentController, $rootScope) => {
|
||||
let $scope = $rootScope.$new();
|
||||
controller = $componentController('vnWorkerPhones', $scope);
|
||||
controller.$scope.model = {link: 1};
|
||||
controller.$scope.$applyAsync = () => {};
|
||||
}));
|
||||
|
||||
describe('setLink()', () => {
|
||||
it('set the link in the model and refreshes it', () => {
|
||||
spyOn(controller.$scope, '$applyAsync');
|
||||
let value = {userFk: 106};
|
||||
controller.setLink(value);
|
||||
|
||||
expect(controller.$scope.$applyAsync).toHaveBeenCalledWith(jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
Phones: Teléfonos
|
||||
Type: Tipo
|
||||
Remove phone: Eliminar teléfono
|
||||
Add phone: Añadir teléfono
|
|
@ -7,7 +7,8 @@
|
|||
{"state": "worker.card.basicData", "icon": "settings"},
|
||||
{"state": "worker.card.pbx", "icon": "icon-pbx"},
|
||||
{"state": "worker.card.calendar", "icon": "icon-calendar"},
|
||||
{"state": "worker.card.timeControl", "icon": "access_time"}
|
||||
{"state": "worker.card.timeControl", "icon": "access_time"},
|
||||
{"state": "worker.card.phones", "icon": "contact_phone"}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
|
@ -79,6 +80,15 @@
|
|||
"state": "worker.department",
|
||||
"component": "vn-worker-department",
|
||||
"description": "Departments"
|
||||
},
|
||||
{
|
||||
"url": "/phones",
|
||||
"state": "worker.card.phones",
|
||||
"component": "vn-worker-phones",
|
||||
"description": "Phones",
|
||||
"params": {
|
||||
"worker": "$ctrl.worker"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -12,8 +12,9 @@
|
|||
<vn-label-value label="Department"
|
||||
value="{{worker.department.department.name}}">
|
||||
</vn-label-value>
|
||||
<vn-label-value label="Phone"
|
||||
value="{{worker.phone}}">
|
||||
<vn-label-value ng-repeat = "phone in worker.phones"
|
||||
label="Phone"
|
||||
value="{{phone.phone}}">
|
||||
</vn-label-value>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
|
|
|
@ -50,6 +50,12 @@ class Controller {
|
|||
relation: 'department'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
relation: 'phones',
|
||||
scope: {
|
||||
fields: ['phone'],
|
||||
order: 'typeFk ASC'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
"senderMail": "nocontestar@verdnatura.es",
|
||||
"senderName": "Verdnatura"
|
||||
},
|
||||
"pdf": {
|
||||
"format": "A4",
|
||||
"border": "1.5cm",
|
||||
"footer": {
|
||||
"height": "55px"
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"host": "localhost",
|
||||
"port": 3306,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
{"type": "report", "name": "rpt-zone"},
|
||||
{"type": "report", "name": "rpt-route"},
|
||||
{"type": "report", "name": "rpt-lcr"},
|
||||
{"type": "report", "name": "rpt-item-label"},
|
||||
{"type": "static", "name": "email-header"},
|
||||
{"type": "static", "name": "email-footer"},
|
||||
{"type": "static", "name": "report-header"},
|
||||
|
|
|
@ -2,9 +2,9 @@ const Vue = require('vue');
|
|||
const VueI18n = require('vue-i18n');
|
||||
const renderer = require('vue-server-renderer').createRenderer();
|
||||
const fs = require('fs-extra');
|
||||
// const pdf = require('phantom-html2pdf');
|
||||
const pdf = require('html-pdf');
|
||||
const juice = require('juice');
|
||||
const config = require('./config');
|
||||
|
||||
Vue.use(VueI18n);
|
||||
|
||||
|
@ -104,13 +104,11 @@ module.exports = {
|
|||
|
||||
async toPdf(name, ctx) {
|
||||
const html = await this.render(name, ctx);
|
||||
const options = {
|
||||
format: 'A4',
|
||||
border: '1.5cm',
|
||||
footer: {
|
||||
height: '55px',
|
||||
}
|
||||
};
|
||||
let options = config.pdf;
|
||||
|
||||
const optionsPath = `${this.path}/${name}/options.json`;
|
||||
if (fs.existsSync(optionsPath))
|
||||
options = Object.assign(options, require(optionsPath));
|
||||
|
||||
return new Promise(resolve => {
|
||||
pdf.create(html, options).toStream((err, stream) => {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"juice": "^5.0.1",
|
||||
"mysql2": "^1.6.5",
|
||||
"nodemailer": "^4.7.0",
|
||||
"qrcode": "^1.4.2",
|
||||
"strftime": "^0.10.0",
|
||||
"vue": "^2.6.7",
|
||||
"vue-i18n": "^8.8.2",
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
const CssReader = require(`${appPath}/lib/cssReader`);
|
||||
|
||||
module.exports = new CssReader([
|
||||
`${appPath}/common/css/layout.css`,
|
||||
`${appPath}/common/css/report.css`,
|
||||
`${appPath}/common/css/misc.css`,
|
||||
`${__dirname}/style.css`])
|
||||
.mergeStyles();
|
|
@ -0,0 +1,88 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.label {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.barcode {
|
||||
float: left;
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.barcode h1 {
|
||||
text-align: center;
|
||||
font-size: 1.8em;
|
||||
margin: 0 0 10px 0
|
||||
}
|
||||
|
||||
.barcode .image {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.barcode .image img {
|
||||
width: 170px
|
||||
}
|
||||
|
||||
.data {
|
||||
float: left;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.data .header {
|
||||
background-color: #000;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
margin-bottom: 25px;
|
||||
text-align: right;
|
||||
font-size: 1.2em;
|
||||
padding: 0.2em;
|
||||
color: #FFF
|
||||
}
|
||||
|
||||
.data .color,
|
||||
.data .producer {
|
||||
text-transform: uppercase;
|
||||
text-align: right;
|
||||
font-size: 1.5em;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data .producer {
|
||||
text-justify: inter-character;
|
||||
}
|
||||
|
||||
.data .details {
|
||||
border-top: 4px solid #000;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.data .details .package {
|
||||
padding-right: 5px;
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.package .packing,
|
||||
.package .dated,
|
||||
.package .labelNumber {
|
||||
text-align: right
|
||||
}
|
||||
|
||||
.package .packing {
|
||||
font-size: 1.8em;
|
||||
font-weight: 400
|
||||
}
|
||||
|
||||
.data .details .size {
|
||||
background-color: #000;
|
||||
text-align: center;
|
||||
font-size: 3em;
|
||||
padding: 0.2em 0;
|
||||
float: left;
|
||||
width: 50%;
|
||||
color: #FFF
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<body>
|
||||
<section class="container" id="report">
|
||||
<section class="label">
|
||||
<section class="barcode">
|
||||
<h1>{{item.id}}</h1>
|
||||
<section class="image">
|
||||
<img v-bind:src="barcode"/>
|
||||
</section>
|
||||
</section>
|
||||
<section class="data">
|
||||
<section class="header">{{item.name}}</section>
|
||||
<section class="color">{{tags.color}}</section>
|
||||
<section class="producer">{{tags.producer}}</section>
|
||||
<section class="details">
|
||||
<section class="package">
|
||||
<section class="packing">{{packing()}}</section>
|
||||
<section class="dated">{{dated}}</section>
|
||||
<section class="labelNumber">{{labelPage()}}</section>
|
||||
</section>
|
||||
<section class="size">{{item.size}}</section>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,83 @@
|
|||
const database = require(`${appPath}/lib/database`);
|
||||
const UserException = require(`${appPath}/lib/exceptions/userException`);
|
||||
const strftime = require('strftime');
|
||||
const qrcode = require('qrcode');
|
||||
|
||||
module.exports = {
|
||||
name: 'rpt-item-label',
|
||||
async asyncData(ctx, params) {
|
||||
Object.assign(this, this.methods);
|
||||
|
||||
if (!params.itemId)
|
||||
throw new UserException('No item id specified');
|
||||
|
||||
if (!params.warehouseId)
|
||||
throw new UserException('No warehouse id specified');
|
||||
|
||||
const data = {
|
||||
item: await this.fetchItem(params.itemId, params.warehouseId),
|
||||
tags: await this.fetchItemTags(params.itemId),
|
||||
barcode: await this.getBarcodeBase64(params.itemId),
|
||||
labelNumber: params.labelNumber,
|
||||
totalLabels: params.totalLabels
|
||||
};
|
||||
|
||||
return data;
|
||||
},
|
||||
computed: {
|
||||
dated() {
|
||||
return strftime('%W/%d', new Date());
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchItem(id, warehouseId) {
|
||||
return database.pool.query(
|
||||
`SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.stems,
|
||||
i.size,
|
||||
b.packing
|
||||
FROM vn.item i
|
||||
JOIN cache.last_buy clb ON clb.item_id = i.id
|
||||
JOIN vn.buy b ON b.id = clb.buy_id
|
||||
JOIN vn.entry e ON e.id = b.entryFk
|
||||
WHERE i.id = ? AND clb.warehouse_id = ?`, [id, warehouseId])
|
||||
.then(([rows]) => {
|
||||
if (rows.length == 0)
|
||||
throw new UserException(`Item #${id} not found on warehouse #${warehouseId}`);
|
||||
return rows[0];
|
||||
});
|
||||
},
|
||||
fetchItemTags(itemId) {
|
||||
return database.pool.query(
|
||||
`SELECT t.code, t.name, it.value
|
||||
FROM vn.itemTag it
|
||||
JOIN vn.tag t ON t.id = it.tagFk
|
||||
WHERE it.itemFk = ?`, [itemId])
|
||||
.then(([rows]) => {
|
||||
const tags = {};
|
||||
rows.forEach(row => tags[row.code] = row.value);
|
||||
|
||||
return tags;
|
||||
});
|
||||
},
|
||||
getBarcodeBase64(itemId) {
|
||||
return qrcode.toDataURL(itemId, {margin: 0});
|
||||
},
|
||||
packing() {
|
||||
const stems = this.item.stems ? this.item.stems : 1;
|
||||
return `${this.item.packing}x${stems}`;
|
||||
},
|
||||
labelPage() {
|
||||
const labelNumber = this.labelNumber ? this.labelNumber : 1;
|
||||
const totalLabels = this.totalLabels ? this.totalLabels : 1;
|
||||
|
||||
return `${labelNumber}/${totalLabels}`;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
'report-header': require('../report-header'),
|
||||
'report-footer': require('../report-footer'),
|
||||
},
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
module.exports = {
|
||||
messages: {
|
||||
es: {
|
||||
title: 'Recibo',
|
||||
date: 'Fecha',
|
||||
payed: 'En {0}, a {1} de {2} de {3}',
|
||||
client: 'Cliente {0}',
|
||||
months: [
|
||||
'Enero',
|
||||
'Febrero',
|
||||
'Marzo',
|
||||
'Abril',
|
||||
'Mayo',
|
||||
'Junio',
|
||||
'Julio',
|
||||
'Agosto',
|
||||
'Septiembre',
|
||||
'Octubre',
|
||||
'Noviembre',
|
||||
'Diciembre'
|
||||
]
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"format": "A4",
|
||||
"orientation": "landscape",
|
||||
"width": "10.4cm",
|
||||
"height": "4.8cm",
|
||||
"border": "0cm",
|
||||
"footer": {
|
||||
"height": "0"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue