Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5561-ticket.sale-tracking_cambiandoCrudModel
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Vicent Llopis 2023-07-20 11:42:10 +02:00
commit 8f7caa1183
67 changed files with 687 additions and 370 deletions

View File

@ -14,6 +14,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
## [2330.01] - 2023-07-27
### Added
- (Artículos -> Vista Previa) Añadido campo "Plástico reciclado"
- (Rutas -> Troncales) Nueva sección
- (Tickets -> Opciones) Opción establecer peso
- (Clientes -> SMS) Nueva sección
### Changed
- (General -> Iconos) Añadidos nuevos iconos
- (Clientes -> Razón social) Nuevas restricciones por pais
### Fixed
## [2328.01] - 2023-07-13 ## [2328.01] - 2023-07-13
### Added ### Added

View File

@ -1,68 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('addAlias', {
description: 'Add an alias if the user has the grant',
accessType: 'WRITE',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
},
{
arg: 'id',
type: 'number',
required: true,
description: 'The user id',
http: {source: 'path'}
},
{
arg: 'mailAlias',
type: 'number',
description: 'The new alias for user',
required: true
}
],
http: {
path: `/:id/addAlias`,
verb: 'POST'
}
});
Self.addAlias = async function(ctx, id, mailAlias, options) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
if (!user.hasGrant)
throw new UserError(`You don't have grant privilege`);
const account = await models.Account.findById(userId, {
fields: ['id'],
include: {
relation: 'aliases',
scope: {
fields: ['mailAlias']
}
}
}, myOptions);
const aliases = account.aliases().map(alias => alias.mailAlias);
const hasAlias = aliases.includes(mailAlias);
if (!hasAlias)
throw new UserError(`You cannot assign an alias that you are not assigned to`);
return models.MailAliasAccount.create({
mailAlias: mailAlias,
account: id
}, myOptions);
};
};

View File

@ -1,55 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('removeAlias', {
description: 'Remove alias if the user has the grant',
accessType: 'WRITE',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
},
{
arg: 'id',
type: 'number',
required: true,
description: 'The user id',
http: {source: 'path'}
},
{
arg: 'mailAlias',
type: 'number',
description: 'The alias to delete',
required: true
}
],
http: {
path: `/:id/removeAlias`,
verb: 'POST'
}
});
Self.removeAlias = async function(ctx, id, mailAlias, options) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const canRemoveAlias = await models.ACL.checkAccessAcl(ctx, 'VnUser', 'canRemoveAlias', 'WRITE');
if (userId != id && !canRemoveAlias) throw new UserError(`You don't have grant privilege`);
const mailAliasAccount = await models.MailAliasAccount.findOne({
where: {
mailAlias: mailAlias,
account: id
}
}, myOptions);
await mailAliasAccount.destroy(myOptions);
};
};

View File

@ -18,6 +18,9 @@
}, },
"expired": { "expired": {
"type": "date" "type": "date"
},
"supplierAccountFk": {
"type": "number"
} }
}, },
"scope": { "scope": {

View File

@ -22,6 +22,9 @@
}, },
"isUeeMember": { "isUeeMember": {
"type": "boolean" "type": "boolean"
},
"isSocialNameUnique": {
"type": "boolean"
} }
}, },
"relations": { "relations": {

View File

@ -12,8 +12,6 @@ module.exports = function(Self) {
require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/privileges')(Self);
require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/validate-auth')(Self);
require('../methods/vn-user/renew-token')(Self); require('../methods/vn-user/renew-token')(Self);
require('../methods/vn-user/addAlias')(Self);
require('../methods/vn-user/removeAlias')(Self);
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');

View File

@ -1,11 +0,0 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('VnUser', 'addAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('VnUser', 'removeAlias', 'WRITE', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('VnUser', 'canRemoveAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');

View File

@ -0,0 +1,8 @@
DELETE FROM `salix`.`ACL` WHERE model = 'MailAliasAccount';
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('MailAliasAccount', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
('MailAliasAccount', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('MailAliasAccount', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('MailAliasAccount', 'canEditAlias', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('WorkerDisableExcluded', '*', 'READ', 'ALLOW', 'ROLE', 'itManagement'),
('WorkerDisableExcluded', '*', 'WRITE', 'ALLOW', 'ROLE', 'itManagement');

View File

@ -0,0 +1,15 @@
CREATE TABLE `vn`.`clientSms` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`clientFk` int(11) NOT NULL,
`smsFk` mediumint(8) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `clientSms_FK` (`clientFk`),
KEY `clientSms_FK_1` (`smsFk`),
CONSTRAINT `clientSms_FK` FOREIGN KEY (`clientFk`) REFERENCES `client` (`id`) ON UPDATE CASCADE,
CONSTRAINT `clientSms_FK_1` FOREIGN KEY (`smsFk`) REFERENCES `sms` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('ClientSms', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('ClientSms', 'create', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`company` MODIFY COLUMN sage200Company int(2) DEFAULT 10 NOT NULL;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('Vehicle','sorted','WRITE','ALLOW','employee');

View File

@ -0,0 +1,64 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelving_inventory`(vParkingFromFk VARCHAR(8), vParkingToFk VARCHAR(8))
BEGIN
/**
* Devuelve un listado de ubicaciones a revisar
*
* @param vParkingFromFk Parking de partida, identificador de parking
* @param vParkingToFk Parking de llegada, identificador de parking
*/
DECLARE vSectorFk INT;
DECLARE vPickingOrderFrom INT;
DECLARE vPickingOrderTo INT;
SELECT p.sectorFk, p.pickingOrder INTO vSectorFk, vPickingOrderFrom
FROM parking p
WHERE p.code = vParkingFromFk COLLATE 'utf8mb3_general_ci';
SELECT p.pickingOrder INTO vPickingOrderTo
FROM parking p
WHERE p.code = vParkingToFk COLLATE 'utf8mb3_general_ci';
CALL visible_getMisfit(vSectorFk);
SELECT ish.id,
p.pickingOrder,
p.code parking,
ish.shelvingFk,
ish.itemFk,
i.longName,
ish.visible,
p.sectorFk,
it.workerFk buyer,
CONCAT('http:',ic.url, '/catalog/1600x900/',i.image) urlImage,
ish.isChecked,
CASE
WHEN s.notPrepared > sm.parked THEN 0
WHEN sm.visible > sm.parked THEN 1
ELSE 2
END priority
FROM itemShelving ish
JOIN item i ON i.id = ish.itemFk
JOIN itemType it ON it.id = i.typeFk
JOIN tmp.stockMisfit sm ON sm.itemFk = ish.itemFk
JOIN shelving sh ON sh.code = ish.shelvingFk
JOIN parking p ON p.id = sh.parkingFk
JOIN (SELECT s.itemFk, sum(s.quantity) notPrepared
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
JOIN warehouse w ON w.id = t.warehouseFk
JOIN config c ON c.mainWarehouseFk = w.id
WHERE t.shipped BETWEEN util.VN_CURDATE()
AND util.dayEnd(util.VN_CURDATE())
AND s.isPicked = FALSE
GROUP BY s.itemFk) s ON s.itemFk = i.id
JOIN hedera.imageConfig ic
WHERE p.pickingOrder BETWEEN vPickingOrderFrom AND vPickingOrderTo
AND p.sectorFk = vSectorFk
ORDER BY p.pickingOrder;
END$$
DELIMITER ;

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`country`
ADD COLUMN `isSocialNameUnique` tinyint(1) NOT NULL DEFAULT 1;

View File

@ -6,5 +6,3 @@ ALTER TABLE `vn`.`roadmap` CHANGE name name varchar(45) CHARACTER SET utf8mb3 CO
ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL; ALTER TABLE `vn`.`roadmap` MODIFY COLUMN etd datetime NOT NULL;
ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales'; ALTER TABLE `vn`.`expeditionTruck` COMMENT='Distintas paradas que hacen los trocales';
ALTER TABLE `vn`.`expeditionTruck` DROP FOREIGN KEY expeditionTruck_FK_2;
ALTER TABLE `vn`.`expeditionTruck` ADD CONSTRAINT expeditionTruck_FK_2 FOREIGN KEY (roadmapFk) REFERENCES vn.roadmap(id) ON DELETE CASCADE ON UPDATE CASCADE;

View File

View File

@ -37,7 +37,7 @@ ALTER TABLE `vn`.`ticket` AUTO_INCREMENT = 1;
INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`) INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES VALUES
('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66); ('DEFAULT_TOKEN', '1209600', CURDATE(), 66);
INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`) INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
VALUES VALUES
@ -2953,3 +2953,8 @@ INSERT INTO `vn`.`invoiceInSerial` (`code`, `description`, `cplusTerIdNifFk`, `t
('E', 'Midgard', 1, 'CEE'), ('E', 'Midgard', 1, 'CEE'),
('R', 'Jotunheim', 1, 'NATIONAL'), ('R', 'Jotunheim', 1, 'NATIONAL'),
('W', 'Vanaheim', 1, 'WORLD'); ('W', 'Vanaheim', 1, 'WORLD');
INSERT INTO `hedera`.`imageConfig` (`id`, `maxSize`, `useXsendfile`, `url`)
VALUES
(1, 0, 0, 'marvel.com');

View File

@ -77831,7 +77831,7 @@ BEGIN
LEAVE cur1Loop; LEAVE cur1Loop;
END IF; END IF;
CALL zone_getLeaves2(vZoneFk, NULL, NULL); CALL zone_getLeaves(vZoneFk, NULL, NULL, TRUE);
myLoop: LOOP myLoop: LOOP
SET vGeoFk = NULL; SET vGeoFk = NULL;
@ -77844,7 +77844,7 @@ BEGIN
LEAVE myLoop; LEAVE myLoop;
END IF; END IF;
CALL zone_getLeaves2(vZoneFk, vGeoFk, NULL); CALL zone_getLeaves(vZoneFk, vGeoFk, NULL, TRUE);
UPDATE tmp.zoneNodes UPDATE tmp.zoneNodes
SET isChecked = TRUE SET isChecked = TRUE
WHERE geoFk = vGeoFk; WHERE geoFk = vGeoFk;
@ -78130,19 +78130,25 @@ DELIMITER ;
/*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ;
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ; /*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
DELIMITER ;; DELIMITER ;;
CREATE DEFINER=`root`@`localhost` PROCEDURE `zone_getLeaves`(vSelf INT, vParentFk INT, vSearch VARCHAR(255)) CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getLeaves`(
vSelf INT,
vParentFk INT,
vSearch VARCHAR(255),
vHasInsert BOOL
)
BEGIN BEGIN
/** /**
* Devuelve las ubicaciones incluidas en la ruta y que sean hijos de parentFk. * Devuelve las ubicaciones incluidas en la ruta y que sean hijos de parentFk.
* @param vSelf Id de la zona * @param vSelf Id de la zona
* @param vParentFk Id del geo a calcular * @param vParentFk Id del geo a calcular
* @param vSearch cadena a buscar * @param vSearch Cadena a buscar
* @param vHasInsert Indica si inserta en tmp.zoneNodes
* Optional @table tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
*/ */
DECLARE vIsNumber BOOL; DECLARE vIsNumber BOOL;
DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch != ''; DECLARE vIsSearch BOOL DEFAULT vSearch IS NOT NULL AND vSearch <> '';
DROP TEMPORARY TABLE IF EXISTS tNodes; CREATE OR REPLACE TEMPORARY TABLE tNodes
CREATE TEMPORARY TABLE tNodes
(UNIQUE (id)) (UNIQUE (id))
ENGINE = MEMORY ENGINE = MEMORY
SELECT id SELECT id
@ -78167,18 +78173,15 @@ BEGIN
END IF; END IF;
IF vParentFk IS NULL THEN IF vParentFk IS NULL THEN
DROP TEMPORARY TABLE IF EXISTS tChilds; CREATE OR REPLACE TEMPORARY TABLE tChilds
CREATE TEMPORARY TABLE tChilds (INDEX(id))
ENGINE = MEMORY ENGINE = MEMORY
SELECT id SELECT id FROM tNodes;
FROM tNodes;
DROP TEMPORARY TABLE IF EXISTS tParents; CREATE OR REPLACE TEMPORARY TABLE tParents
CREATE TEMPORARY TABLE tParents (INDEX(id))
ENGINE = MEMORY ENGINE = MEMORY
SELECT id SELECT id FROM zoneGeo LIMIT 0;
FROM zoneGeo
LIMIT 0;
myLoop: LOOP myLoop: LOOP
DELETE FROM tParents; DELETE FROM tParents;
@ -78189,40 +78192,64 @@ BEGIN
WHERE g.parentFk IS NOT NULL; WHERE g.parentFk IS NOT NULL;
INSERT IGNORE INTO tNodes INSERT IGNORE INTO tNodes
SELECT id SELECT id FROM tParents;
FROM tParents;
IF ROW_COUNT() = 0 THEN IF NOT ROW_COUNT() THEN
LEAVE myLoop; LEAVE myLoop;
END IF; END IF;
DELETE FROM tChilds; DELETE FROM tChilds;
INSERT INTO tChilds INSERT INTO tChilds
SELECT id SELECT id FROM tParents;
FROM tParents;
END LOOP; END LOOP;
DROP TEMPORARY TABLE tChilds, tParents; DROP TEMPORARY TABLE tChilds, tParents;
END IF; END IF;
IF !vIsSearch THEN IF NOT vIsSearch THEN
INSERT IGNORE INTO tNodes INSERT IGNORE INTO tNodes
SELECT id SELECT id
FROM zoneGeo FROM zoneGeo
WHERE parentFk <=> vParentFk; WHERE parentFk <=> vParentFk;
END IF; END IF;
CREATE OR REPLACE TEMPORARY TABLE tZones
SELECT g.id, SELECT g.id,
g.name, g.name,
g.parentFk, g.parentFk,
g.sons, g.sons,
isIncluded selected NOT g.sons OR `type` = 'country' isChecked,
i.isIncluded selected,
g.`depth`,
vSelf
FROM zoneGeo g FROM zoneGeo g
JOIN tNodes n ON n.id = g.id JOIN tNodes n ON n.id = g.id
LEFT JOIN zoneIncluded i ON i.geoFk = g.id AND i.zoneFk = vSelf LEFT JOIN zoneIncluded i ON i.geoFk = g.id
ORDER BY `depth`, selected DESC, name; AND i.zoneFk = vSelf
ORDER BY g.`depth`, selected DESC, g.name;
DROP TEMPORARY TABLE tNodes; IF vHasInsert THEN
INSERT IGNORE INTO tmp.zoneNodes(geoFk, name, parentFk, sons, isChecked, zoneFk)
SELECT id,
name,
parentFk,
sons,
isChecked,
vSelf
FROM tZones
WHERE selected
OR (selected IS NULL AND vParentFk IS NOT NULL);
ELSE
SELECT id,
name,
parentFk,
sons,
selected
FROM tZones
ORDER BY `depth`, selected DESC, name;
END IF;
DROP TEMPORARY TABLE tNodes, tZones;
END ;; END ;;
DELIMITER ; DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET sql_mode = @saved_sql_mode */ ;
@ -78540,7 +78567,7 @@ BEGIN
INDEX(geoFk)) INDEX(geoFk))
ENGINE = MEMORY; ENGINE = MEMORY;
CALL zone_getLeaves2(vSelf, NULL , NULL); CALL zone_getLeaves(vSelf, NULL , NULL, TRUE);
UPDATE tmp.zoneNodes zn UPDATE tmp.zoneNodes zn
SET isChecked = 0 SET isChecked = 0
@ -78553,7 +78580,7 @@ BEGIN
WHERE NOT isChecked WHERE NOT isChecked
LIMIT 1; LIMIT 1;
CALL zone_getLeaves2(vSelf, vGeoFk, NULL); CALL zone_getLeaves(vSelf, vGeoFk, NULL, TRUE);
UPDATE tmp.zoneNodes UPDATE tmp.zoneNodes
SET isChecked = TRUE SET isChecked = TRUE
WHERE geoFk = vGeoFk; WHERE geoFk = vGeoFk;

View File

@ -305,5 +305,7 @@
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d", "Valid priorities": "Prioridades válidas: %d",
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado" "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado"
} }

View File

@ -0,0 +1,55 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.observe('before save', async ctx => {
const changes = ctx.currentInstance || ctx.instance;
await Self.hasGrant(ctx, changes.mailAlias);
});
Self.observe('before delete', async ctx => {
const mailAliasAccount = await Self.findById(ctx.where.id);
await Self.hasGrant(ctx, mailAliasAccount.mailAlias);
});
/**
* Checks if current user has
* grant to add/remove alias
*
* @param {Object} ctx - Request context
* @param {Interger} mailAlias - mailAlias id
* @return {Boolean} True for user with grant
*/
Self.hasGrant = async function(ctx, mailAlias) {
const models = Self.app.models;
const accessToken = {req: {accessToken: ctx.options.accessToken}};
const userId = accessToken.req.accessToken.userId;
const canEditAlias = await models.ACL.checkAccessAcl(accessToken, 'MailAliasAccount', 'canEditAlias', 'WRITE');
if (canEditAlias) return true;
const user = await models.VnUser.findById(userId, {fields: ['hasGrant']});
if (!user.hasGrant)
throw new UserError(`You don't have grant privilege`);
const account = await models.Account.findById(userId, {
fields: ['id'],
include: {
relation: 'aliases',
scope: {
fields: ['mailAlias']
}
}
});
const aliases = account.aliases().map(alias => alias.mailAlias);
const hasAlias = aliases.includes(mailAlias);
if (!hasAlias)
throw new UserError(`You cannot assign/remove an alias that you are not assigned to`);
return true;
};
};

View File

@ -21,11 +21,12 @@ export default class Controller extends Section {
} }
onAddClick() { onAddClick() {
this.addData = {account: this.$params.id};
this.$.dialog.show(); this.$.dialog.show();
} }
onAddSave() { onAddSave() {
return this.$http.post(`VnUsers/${this.$params.id}/addAlias`, this.addData) return this.$http.post(`MailAliasAccounts`, this.addData)
.then(() => this.refresh()) .then(() => this.refresh())
.then(() => this.vnApp.showSuccess( .then(() => this.vnApp.showSuccess(
this.$t('Subscribed to alias!')) this.$t('Subscribed to alias!'))
@ -33,12 +34,11 @@ export default class Controller extends Section {
} }
onRemove(row) { onRemove(row) {
const params = { return this.$http.delete(`MailAliasAccounts/${row.id}`)
mailAlias: row.mailAlias .then(() => {
}; this.$.data.splice(this.$.data.indexOf(row), 1);
return this.$http.post(`VnUsers/${this.$params.id}/removeAlias`, params) this.vnApp.showSuccess(this.$t('Unsubscribed from alias!'));
.then(() => this.refresh()) });
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
} }
} }

View File

@ -25,9 +25,8 @@ describe('component vnUserAliases', () => {
describe('onAddSave()', () => { describe('onAddSave()', () => {
it('should add the new row', () => { it('should add the new row', () => {
controller.addData = {account: 1}; controller.addData = {account: 1};
controller.$params = {id: 1};
$httpBackend.expectPOST('VnUsers/1/addAlias').respond(); $httpBackend.expectPOST('MailAliasAccounts').respond();
$httpBackend.expectGET('MailAliasAccounts').respond('foo'); $httpBackend.expectGET('MailAliasAccounts').respond('foo');
controller.onAddSave(); controller.onAddSave();
$httpBackend.flush(); $httpBackend.flush();
@ -42,14 +41,12 @@ describe('component vnUserAliases', () => {
{id: 1, alias: 'foo'}, {id: 1, alias: 'foo'},
{id: 2, alias: 'bar'} {id: 2, alias: 'bar'}
]; ];
controller.$params = {id: 1};
$httpBackend.expectPOST('VnUsers/1/removeAlias').respond(); $httpBackend.expectDELETE('MailAliasAccounts/1').respond();
$httpBackend.expectGET('MailAliasAccounts').respond(controller.$.data[1]);
controller.onRemove(controller.$.data[0]); controller.onRemove(controller.$.data[0]);
$httpBackend.flush(); $httpBackend.flush();
expect(controller.$.data).toEqual({id: 2, alias: 'bar'}); expect(controller.$.data).toEqual([{id: 2, alias: 'bar'}]);
expect(controller.vnApp.showSuccess).toHaveBeenCalled(); expect(controller.vnApp.showSuccess).toHaveBeenCalled();
}); });
}); });

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) { module.exports = function(Self) {
Self.remoteMethodCtx('canBeInvoiced', { Self.remoteMethodCtx('canBeInvoiced', {
description: 'Change property isEqualizated in all client addresses', description: 'Change property isEqualizated in all client addresses',
@ -9,6 +11,12 @@ module.exports = function(Self) {
required: true, required: true,
description: 'Client id', description: 'Client id',
http: {source: 'path'} http: {source: 'path'}
},
{
arg: 'companyFk',
description: 'The company id',
type: 'number',
required: true
} }
], ],
returns: { returns: {
@ -22,18 +30,29 @@ module.exports = function(Self) {
} }
}); });
Self.canBeInvoiced = async(id, options) => { Self.canBeInvoiced = async(id, companyFk, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const client = await models.Client.findById(id, { const client = await models.Client.findById(id, {
fields: ['id', 'isTaxDataChecked', 'hasToInvoice'] fields: ['id', 'isTaxDataChecked', 'hasToInvoice', 'payMethodFk'],
include:
{
relation: 'payMethod',
scope: {
fields: ['code']
}
}
}, myOptions); }, myOptions);
const company = await models.Company.findById(companyFk, {fields: ['supplierAccountFk']}, myOptions);
if (client.payMethod().code === 'wireTransfer' && !company.supplierAccountFk)
throw new UserError('The company has not informed the supplier account for bank transfers');
if (client.isTaxDataChecked && client.hasToInvoice) if (client.isTaxDataChecked && client.hasToInvoice)
return true; return true;

View File

@ -7,7 +7,7 @@ module.exports = Self => {
arg: 'id', arg: 'id',
type: 'number', type: 'number',
required: true, required: true,
description: 'The ticket id', description: 'The client id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
@ -33,6 +33,12 @@ module.exports = Self => {
Self.sendSms = async(ctx, id, destination, message) => { Self.sendSms = async(ctx, id, destination, message) => {
const models = Self.app.models; const models = Self.app.models;
const sms = await models.Sms.send(ctx, destination, message); const sms = await models.Sms.send(ctx, destination, message);
await models.ClientSms.create({
clientFk: id,
smsFk: sms.id
});
return sms; return sms;
}; };
}; };

View File

@ -4,6 +4,7 @@ const LoopBackContext = require('loopback-context');
describe('client canBeInvoiced()', () => { describe('client canBeInvoiced()', () => {
const userId = 19; const userId = 19;
const clientId = 1101; const clientId = 1101;
const companyId = 442;
const activeCtx = { const activeCtx = {
accessToken: {userId: userId} accessToken: {userId: userId}
}; };
@ -23,7 +24,7 @@ describe('client canBeInvoiced()', () => {
const client = await models.Client.findById(clientId, null, options); const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('isTaxDataChecked', false, options); await client.updateAttribute('isTaxDataChecked', false, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(false); expect(canBeInvoiced).toEqual(false);
@ -43,7 +44,7 @@ describe('client canBeInvoiced()', () => {
const client = await models.Client.findById(clientId, null, options); const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('hasToInvoice', false, options); await client.updateAttribute('hasToInvoice', false, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(false); expect(canBeInvoiced).toEqual(false);
@ -60,7 +61,7 @@ describe('client canBeInvoiced()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options); const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(true); expect(canBeInvoiced).toEqual(true);

View File

@ -70,6 +70,7 @@ module.exports = Self => {
c.creditInsurance, c.creditInsurance,
d.defaulterSinced, d.defaulterSinced,
cn.country, cn.country,
c.countryFk,
pm.name payMethod pm.name payMethod
FROM vn.defaulter d FROM vn.defaulter d
JOIN vn.client c ON c.id = d.clientFk JOIN vn.client c ON c.id = d.clientFk

View File

@ -95,6 +95,9 @@
"ClientSample": { "ClientSample": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ClientSms": {
"dataSource": "vn"
},
"Sms": { "Sms": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,26 @@
{
"name": "ClientSms",
"base": "VnModel",
"options": {
"mysql": {
"table": "clientSms"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"clientFk": {
"type": "number"
}
},
"relations": {
"sms": {
"type": "belongsTo",
"model": "Sms",
"foreignKey": "smsFk"
}
}
}

View File

@ -41,7 +41,18 @@ module.exports = Self => {
}); });
async function socialNameIsUnique(err, done) { async function socialNameIsUnique(err, done) {
if (!this.countryFk)
return done();
const filter = { const filter = {
include: {
relation: 'country',
scope: {
fields: {
isSocialNameUnique: true,
},
},
},
where: { where: {
and: [ and: [
{socialName: this.socialName}, {socialName: this.socialName},
@ -50,9 +61,13 @@ module.exports = Self => {
] ]
} }
}; };
const client = await Self.app.models.Client.findOne(filter);
if (client) const client = await Self.app.models.Country.findById(this.countryFk, {fields: ['isSocialNameUnique']});
const existingClient = await Self.findOne(filter);
if (existingClient && (existingClient.country().isSocialNameUnique || client.isSocialNameUnique))
err(); err();
done(); done();
} }

View File

@ -181,6 +181,11 @@
"model": "Country", "model": "Country",
"foreignKey": "countryFk" "foreignKey": "countryFk"
}, },
"isSocialNameUnique": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "countryFk"
},
"contactChannel": { "contactChannel": {
"type": "belongsTo", "type": "belongsTo",
"model": "ContactChannel", "model": "ContactChannel",

View File

@ -33,7 +33,7 @@
"country": { "country": {
"type": "belongsTo", "type": "belongsTo",
"model": "Country", "model": "Country",
"foreignKey": "country" "foreignKey": "countryFk"
}, },
"payMethod": { "payMethod": {
"type": "belongsTo", "type": "belongsTo",

View File

@ -60,7 +60,7 @@
<th field="salesPersonFk"> <th field="salesPersonFk">
<span translate>Comercial</span> <span translate>Comercial</span>
</th> </th>
<th field="country"> <th field="countryFk">
<span translate>Country</span> <span translate>Country</span>
</th> </th>
<th field="payMethod" <th field="payMethod"

View File

@ -31,10 +31,11 @@ export default class Controller extends Section {
valueField: 'id', valueField: 'id',
} }
}, { }, {
field: 'country', field: 'countryFk',
autocomplete: { autocomplete: {
url: 'Countries',
showField: 'country', showField: 'country',
valueField: 'country' valueField: 'id'
} }
}, { }, {
field: 'payMethodFk', field: 'payMethodFk',
@ -167,7 +168,7 @@ export default class Controller extends Section {
case 'amount': case 'amount':
case 'clientFk': case 'clientFk':
case 'workerFk': case 'workerFk':
case 'country': case 'countryFk':
case 'payMethod': case 'payMethod':
case 'salesPersonFk': case 'salesPersonFk':
return {[`d.${param}`]: value}; return {[`d.${param}`]: value};

View File

@ -48,4 +48,5 @@ import './notification';
import './unpaid'; import './unpaid';
import './extended-list'; import './extended-list';
import './credit-management'; import './credit-management';
import './sms';

View File

@ -23,6 +23,7 @@
{"state": "client.card.recovery.index", "icon": "icon-recovery"}, {"state": "client.card.recovery.index", "icon": "icon-recovery"},
{"state": "client.card.webAccess", "icon": "cloud"}, {"state": "client.card.webAccess", "icon": "cloud"},
{"state": "client.card.log", "icon": "history"}, {"state": "client.card.log", "icon": "history"},
{"state": "client.card.sms", "icon": "sms"},
{ {
"description": "Credit management", "description": "Credit management",
"icon": "monetization_on", "icon": "monetization_on",
@ -373,6 +374,12 @@
"component": "vn-client-log", "component": "vn-client-log",
"description": "Log" "description": "Log"
}, },
{
"url" : "/sms",
"state": "client.card.sms",
"component": "vn-client-sms",
"description": "Sms"
},
{ {
"url": "/dms", "url": "/dms",
"state": "client.card.dms", "state": "client.card.dms",

View File

@ -0,0 +1,40 @@
<vn-crud-model
vn-id="model"
url="ClientSms"
link="{clientFk: $ctrl.$params.id}"
filter="::$ctrl.filter"
data="clientSmsList"
limit="20"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card class="vn-w-md">
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th field="senderFk">Sender</vn-th>
<vn-th field="destination" number>Destination</vn-th>
<vn-th field="message">Message</vn-th>
<vn-th field="status">Status</vn-th>
<vn-th field="created" expand>Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="clientSms in clientSmsList">
<vn-td>
<span class="link" ng-click="workerDescriptor.show($event, clientSms.sms.senderFk)">
{{::clientSms.sms.sender.name}}
</span>
</vn-td>
<vn-td number expand>{{::clientSms.sms.destination}}</vn-td>
<vn-td>{{::clientSms.sms.message}}</vn-td>
<vn-td>{{::clientSms.sms.status}}</vn-td>
<vn-td shrink-datetime>{{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>

View File

@ -0,0 +1,39 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
fields: ['id', 'smsFk'],
include: {
relation: 'sms',
scope: {
fields: [
'senderFk',
'sender',
'destination',
'message',
'statusCode',
'status',
'created'],
include: {
relation: 'sender',
scope: {
fields: ['name']
}
}
}
}
};
}
}
ngModule.vnComponent('vnClientSms', {
template: require('./index.html'),
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -0,0 +1,2 @@
Sender: Remitente
Number sender: Número remitente

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
require('../methods/invoice-in/filter')(Self); require('../methods/invoice-in/filter')(Self);
require('../methods/invoice-in/summary')(Self); require('../methods/invoice-in/summary')(Self);
@ -7,4 +9,9 @@ module.exports = Self => {
require('../methods/invoice-in/invoiceInPdf')(Self); require('../methods/invoice-in/invoiceInPdf')(Self);
require('../methods/invoice-in/invoiceInEmail')(Self); require('../methods/invoice-in/invoiceInEmail')(Self);
require('../methods/invoice-in/getSerial')(Self); require('../methods/invoice-in/getSerial')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn'))
return new UserError(`This invoice has a linked vehicle.`);
return err;
});
}; };

View File

@ -0,0 +1,37 @@
module.exports = Self => {
Self.remoteMethod('getInventory', {
description: 'Get list of itemShelving to review between two parking code',
accessType: 'WRITE',
accepts: [{
arg: 'parkingFrom',
type: 'string',
required: true,
description: 'Parking code from'
},
{
arg: 'parkingTo',
type: 'string',
required: true,
description: 'Parking code to'
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getInventory`,
verb: 'POST'
}
});
Self.getInventory = async(parkingFrom, parkingTo, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const [result] = await Self.rawSql(`CALL vn.itemShelving_inventory(?, ?)`, [parkingFrom, parkingTo], myOptions);
return result;
};
};

View File

@ -0,0 +1,24 @@
const models = require('vn-loopback/server/server').models;
describe('itemShelving getInventory()', () => {
it('should return a list of itemShelvings', async() => {
const tx = await models.ItemShelving.beginTransaction({});
let response;
try {
const options = {transaction: tx};
await models.ItemShelving.rawSql(`
UPDATE vn.config
SET mainWarehouseFk=1
WHERE id=1
`, null, options);
response = await models.ItemShelving.getInventory('100-01', 'LR-02-3', options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(response.length).toEqual(2);
});
});

View File

@ -1,3 +1,4 @@
module.exports = Self => { module.exports = Self => {
require('../methods/item-shelving/deleteItemShelvings')(Self); require('../methods/item-shelving/deleteItemShelvings')(Self);
require('../methods/item-shelving/getInventory')(Self);
}; };

View File

@ -23,6 +23,9 @@
}, },
"isChecked": { "isChecked": {
"type": "boolean" "type": "boolean"
},
"visible": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -85,7 +85,7 @@
show-field="id" show-field="id"
value-field="id" value-field="id"
search-function="$ctrl.itemSearchFunc($search)" search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.upsertPrice(price, true)" ng-change="$ctrl.upsertPrice(price, true)"
order="id DESC" order="id DESC"
tabindex="1"> tabindex="1">
<tpl-item> <tpl-item>

View File

@ -0,0 +1,27 @@
module.exports = Self => {
Self.remoteMethod('sorted', {
description: 'Sort the vehicles by warehouse',
accessType: 'WRITE',
accepts: [{
arg: 'warehouseFk',
type: 'number'
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/sorted`,
verb: `POST`
}
});
Self.sorted = async warehouseFk => {
return Self.rawSql(`
SELECT v.id, v.warehouseFk, v.numberPlate, w.name
FROM vehicle v
JOIN warehouse w ON w.id = v.warehouseFk
ORDER BY v.warehouseFk = ? DESC, w.id, v.numberPlate ASC;
`, [warehouseFk]);
};
};

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/vehicle/sorted')(Self);
};

View File

@ -23,12 +23,14 @@
</tpl-item> </tpl-item>
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
label="Vehicle"
ng-model="$ctrl.route.vehicleFk" ng-model="$ctrl.route.vehicleFk"
url="Vehicles" data="$ctrl.vehicles"
show-field="numberPlate" show-field="numberPlate"
value-field="id" value-field="id"
label="Vehicle" order="false"
vn-name="vehicle"> vn-name="vehicle">
<tpl-item>{{::numberPlate}} - {{::name}}</tpl-item>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>

View File

@ -2,6 +2,13 @@ import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
class Controller extends Section { class Controller extends Section {
$onInit() {
this.$http.post(`Vehicles/sorted`, {warehouseFk: this.vnConfig.warehouseFk})
.then(res => {
this.vehicles = res.data;
});
}
onSubmit() { onSubmit() {
this.$.watcher.submit().then(() => this.$.watcher.submit().then(() =>
this.card.reload() this.card.reload()

View File

@ -68,9 +68,9 @@ module.exports = Self => {
}; };
const country = await Self.app.models.Country.findOne(filter); const country = await Self.app.models.Country.findOne(filter);
const code = country ? country.code.toLowerCase() : null; const code = country ? country.code.toLowerCase() : null;
const countryCode = this.nif.toLowerCase().substring(0, 2); const countryCode = this.nif?.toLowerCase().substring(0, 2);
if (!this.nif || !validateTin(this.nif, code) || (this.isVies && countryCode == code)) if (!validateTin(this.nif, code) || (this.isVies && countryCode == code))
err(); err();
done(); done();
} }
@ -122,7 +122,7 @@ module.exports = Self => {
}); });
async function hasSupplierSameName(err, done) { async function hasSupplierSameName(err, done) {
if (!this.name || !this.countryFk) done(); if (!this.name || !this.countryFk) return done();
const supplier = await Self.app.models.Supplier.findOne( const supplier = await Self.app.models.Supplier.findOne(
{ {
where: { where: {

View File

@ -14,7 +14,7 @@ module.exports = function(Self) {
{ {
arg: 'companyFk', arg: 'companyFk',
description: 'The company id', description: 'The company id',
type: 'string', type: 'number',
required: true required: true
}, },
{ {
@ -67,7 +67,7 @@ module.exports = function(Self) {
const [firstTicket] = tickets; const [firstTicket] = tickets;
const clientId = firstTicket.clientFk; const clientId = firstTicket.clientFk;
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, myOptions); const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, companyFk, myOptions);
if (!clientCanBeInvoiced) if (!clientCanBeInvoiced)
throw new UserError(`This client can't be invoiced`); throw new UserError(`This client can't be invoiced`);

View File

@ -141,6 +141,7 @@ describe('ticket filter()', () => {
}); });
it('should return the tickets that are not pending', async() => { it('should return the tickets that are not pending', async() => {
pending('#6010 test intermitente');
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
try { try {

View File

@ -38,10 +38,12 @@ class Controller extends SearchPanel {
applyFilters(param) { applyFilters(param) {
if (typeof this.filter.scopeDays === 'number') { if (typeof this.filter.scopeDays === 'number') {
const shippedFrom = Date.vnNew(); const today = Date.vnNew();
const shippedFrom = new Date(today.getTime());
shippedFrom.setDate(today.getDate() - 30);
shippedFrom.setHours(0, 0, 0, 0); shippedFrom.setHours(0, 0, 0, 0);
const shippedTo = new Date(shippedFrom.getTime()); const shippedTo = new Date(today.getTime());
shippedTo.setDate(shippedTo.getDate() + this.filter.scopeDays); shippedTo.setDate(shippedTo.getDate() + this.filter.scopeDays);
shippedTo.setHours(23, 59, 59, 999); shippedTo.setHours(23, 59, 59, 999);
Object.assign(this.filter, {shippedFrom, shippedTo}); Object.assign(this.filter, {shippedFrom, shippedTo});

View File

@ -3,7 +3,7 @@
data="absenceTypes" data="absenceTypes"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<div ng-if="$ctrl.worker.hasWorkCenter"> <div ng-if="$ctrl.card.hasWorkCenter">
<div class="vn-w-lg"> <div class="vn-w-lg">
<vn-card class="vn-pa-sm calendars"> <vn-card class="vn-pa-sm calendars">
<vn-icon ng-if="::$ctrl.isSubordinate" icon="info" color-marginal <vn-icon ng-if="::$ctrl.isSubordinate" icon="info" color-marginal
@ -23,7 +23,7 @@
</div> </div>
</div> </div>
<div <div
ng-if="!$ctrl.worker.hasWorkCenter" ng-if="!$ctrl.card.hasWorkCenter"
class="bg-title" class="bg-title"
translate> translate>
Autonomous worker Autonomous worker
@ -82,10 +82,8 @@
</vn-autocomplete> </vn-autocomplete>
</div> </div>
<div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;"> <div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;">
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" <vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" ng-click="$ctrl.pick(absenceType)">
ng-click="$ctrl.pick(absenceType)"> <vn-avatar ng-style="{backgroundColor: absenceType.rgb}">
<vn-avatar
ng-style="{backgroundColor: absenceType.rgb}">
<vn-icon icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon> <vn-icon icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
</vn-avatar> </vn-avatar>
{{absenceType.name}} {{absenceType.name}}
@ -105,9 +103,9 @@
</div> </div>
</div> </div>
</vn-side-menu> </vn-side-menu>
<vn-confirm <vn-confirm
vn-id="confirm" vn-id="confirm"
message="This item will be deleted" message="This item will be deleted"
question="Are you sure you want to continue?"> question="Are you sure you want to continue?">
</vn-confirm> </vn-confirm>

View File

@ -31,6 +31,8 @@ class Controller extends Section {
} }
set businessId(value) { set businessId(value) {
if (!this.card.hasWorkCenter) return;
this._businessId = value; this._businessId = value;
if (value) { if (value) {
this.refresh() this.refresh()
@ -64,7 +66,7 @@ class Controller extends Section {
set worker(value) { set worker(value) {
this._worker = value; this._worker = value;
if (value && value.hasWorkCenter) { if (value) {
this.getIsSubordinate(); this.getIsSubordinate();
this.getActiveContract(); this.getActiveContract();
} }
@ -293,5 +295,8 @@ ngModule.vnComponent('vnWorkerCalendar', {
controller: Controller, controller: Controller,
bindings: { bindings: {
worker: '<' worker: '<'
},
require: {
card: '^vnWorkerCard'
} }
}); });

View File

@ -20,6 +20,9 @@ describe('Worker', () => {
controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'}; controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'};
controller.$params.id = 1106; controller.$params.id = 1106;
controller._worker = {id: 1106}; controller._worker = {id: 1106};
controller.card = {
hasWorkCenter: true
};
})); }));
describe('year() getter', () => { describe('year() getter', () => {
@ -74,7 +77,7 @@ describe('Worker', () => {
let yesterday = new Date(today.getTime()); let yesterday = new Date(today.getTime());
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
controller.worker = {id: 1107, hasWorkCenter: true}; controller.worker = {id: 1107};
expect(controller.getIsSubordinate).toHaveBeenCalledWith(); expect(controller.getIsSubordinate).toHaveBeenCalledWith();
expect(controller.getActiveContract).toHaveBeenCalledWith(); expect(controller.getActiveContract).toHaveBeenCalledWith();

View File

@ -3,7 +3,7 @@ import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard { class Controller extends ModuleCard {
reload() { reload() {
let filter = { const filter = {
include: [ include: [
{ {
relation: 'user', relation: 'user',
@ -32,13 +32,12 @@ class Controller extends ModuleCard {
] ]
}; };
return Promise.all([
this.$http.get(`Workers/${this.$params.id}`, {filter}) this.$http.get(`Workers/${this.$params.id}`, {filter})
.then(res => this.worker = res.data) .then(res => this.worker = res.data),
.then(() =>
this.$http.get(`Workers/${this.$params.id}/activeContract`) this.$http.get(`Workers/${this.$params.id}/activeContract`)
.then(res => { .then(res => this.hasWorkCenter = res.data.workCenterFk)
if (res.data) this.worker.hasWorkCenter = res.data.workCenterFk; ]);
}));
} }
} }

View File

@ -15,17 +15,10 @@
</div> </div>
</slot-before> </slot-before>
<slot-menu> <slot-menu>
<vn-item <vn-item ng-click="$ctrl.handleExcluded()" translate>
ng-click="$ctrl.handleExcluded()" {{$ctrl.workerExcluded
translate ? 'Click to allow the user to be disabled'
ng-if="!$ctrl.excluded"> : 'Click to exclude the user from getting disabled'}}
Click to exclude the user from getting disabled
</vn-item>
<vn-item
ng-click="$ctrl.handleExcluded()"
translate
ng-if="$ctrl.excluded">
Click to allow the user to be disabled
</vn-item> </vn-item>
</slot-menu> </slot-menu>
<slot-body> <slot-body>

View File

@ -18,28 +18,19 @@ class Controller extends Descriptor {
this.getIsExcluded(); this.getIsExcluded();
} }
get excluded() {
return this.entity.excluded;
}
set excluded(value) {
this.entity.excluded = value;
}
getIsExcluded() { getIsExcluded() {
this.$http.get(`workerDisableExcludeds/${this.entity.id}/exists`).then(data => { this.$http.get(`WorkerDisableExcludeds/${this.entity.id}/exists`).then(data => {
this.excluded = data.data.exists; this.workerExcluded = data.data.exists;
}); });
} }
handleExcluded() { handleExcluded() {
if (this.excluded) { if (this.workerExcluded)
this.$http.delete(`workerDisableExcludeds/${this.entity.id}`); this.$http.delete(`WorkerDisableExcludeds/${this.entity.id}`);
this.excluded = false; else
} else { this.$http.post(`WorkerDisableExcludeds`, {workerFk: this.entity.id, dated: new Date});
this.$http.post(`workerDisableExcludeds`, {workerFk: this.entity.id, dated: new Date});
this.excluded = true; this.workerExcluded = !this.workerExcluded;
}
} }
loadData() { loadData() {

View File

@ -4,7 +4,7 @@
filter="::$ctrl.filter" filter="::$ctrl.filter"
data="$ctrl.hours"> data="$ctrl.hours">
</vn-crud-model> </vn-crud-model>
<div ng-if="$ctrl.worker.hasWorkCenter"> <div ng-if="$ctrl.card.hasWorkCenter">
<vn-card class="vn-pa-lg vn-w-lg"> <vn-card class="vn-pa-lg vn-w-lg">
<vn-table model="model" auto-load="false"> <vn-table model="model" auto-load="false">
<vn-thead> <vn-thead>
@ -107,7 +107,7 @@
</vn-button-bar> </vn-button-bar>
</div> </div>
<div <div
ng-if="!$ctrl.worker.hasWorkCenter" ng-if="!$ctrl.card.hasWorkCenter"
class="bg-title" class="bg-title"
translate> translate>
Autonomous worker Autonomous worker
@ -136,6 +136,7 @@
</vn-calendar> </vn-calendar>
</div> </div>
</vn-side-menu> </vn-side-menu>
<vn-dialog <vn-dialog
vn-id="addTimeDialog" vn-id="addTimeDialog"
on-accept="$ctrl.addTime()" on-accept="$ctrl.addTime()"

View File

@ -141,6 +141,8 @@ class Controller extends Section {
]} ]}
}; };
this.$.model.applyFilter(filter, params).then(() => { this.$.model.applyFilter(filter, params).then(() => {
if (!this.card.hasWorkCenter) return;
this.getWorkedHours(this.started, this.ended); this.getWorkedHours(this.started, this.ended);
this.getAbsences(); this.getAbsences();
}); });
@ -151,7 +153,6 @@ class Controller extends Section {
} }
getAbsences() { getAbsences() {
if (!this.worker.hasWorkerCenter) return;
const fullYear = this.started.getFullYear(); const fullYear = this.started.getFullYear();
let params = { let params = {
workerFk: this.$params.id, workerFk: this.$params.id,
@ -486,5 +487,8 @@ ngModule.vnComponent('vnWorkerTimeControl', {
controller: Controller, controller: Controller,
bindings: { bindings: {
worker: '<' worker: '<'
},
require: {
card: '^vnWorkerCard'
} }
}); });

View File

@ -16,9 +16,8 @@ describe('Component vnWorkerTimeControl', () => {
$scope = $rootScope.$new(); $scope = $rootScope.$new();
$element = angular.element('<vn-worker-time-control></vn-worker-time-control>'); $element = angular.element('<vn-worker-time-control></vn-worker-time-control>');
controller = $componentController('vnWorkerTimeControl', {$element, $scope}); controller = $componentController('vnWorkerTimeControl', {$element, $scope});
controller.worker = { controller.card = {
hasWorkerCenter: true hasWorkCenter: true
}; };
})); }));

View File

@ -38,7 +38,7 @@ module.exports = Self => {
Object.assign(myOptions, options); Object.assign(myOptions, options);
const [res] = await Self.rawSql( const [res] = await Self.rawSql(
`CALL zone_getLeaves(?, ?, ?)`, `CALL zone_getLeaves(?, ?, ?, FALSE)`,
[id, parentId, search], [id, parentId, search],
myOptions myOptions
); );

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.30.01", "version": "23.32.01",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "23.30.01", "version": "23.32.01",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",

View File

@ -10,16 +10,17 @@ module.exports = {
async send(options) { async send(options) {
options.from = `${config.app.senderName} <${config.app.senderEmail}>`; options.from = `${config.app.senderName} <${config.app.senderEmail}>`;
if (!process.env.NODE_ENV) const env = process.env.NODE_ENV;
options.to = config.app.senderEmail; const canSend = env === 'production' || !env || options.force;
if (process.env.NODE_ENV !== 'production' && !options.force) { if (!canSend || !config.smtp.auth.user) {
const notProductionError = {message: 'This not production, this email not sended'}; const notProductionError = {message: 'This not production, this email not sended'};
await this.mailLog(options, notProductionError); await this.mailLog(options, notProductionError);
return Promise.resolve(true);
} }
if (!config.smtp.auth.user) if (!env)
return Promise.resolve(true); options.to = config.app.senderEmail;
let res; let res;
let error; let error;

View File

@ -1,25 +1,13 @@
SELECT SELECT
io.ref, io.ref,
c.socialName,
sa.iban,
pm.name AS payMethod,
t.clientFk,
t.shipped,
t.nickname,
s.ticketFk, s.ticketFk,
s.itemFk, ib.ediBotanic botanical,
s.concept,
s.quantity, s.quantity,
s.price, s.price,
s.discount, s.discount,
i.tag5, s.itemFk,
i.value5, s.concept,
i.tag6, tc.code vatType
i.value6,
i.tag7,
i.value7,
tc.code AS vatType,
ib.ediBotanic botanical
FROM vn.invoiceOut io FROM vn.invoiceOut io
JOIN vn.ticket t ON t.refFk = io.ref JOIN vn.ticket t ON t.refFk = io.ref
JOIN vn.supplier su ON su.id = io.companyFk JOIN vn.supplier su ON su.id = io.companyFk
@ -41,26 +29,14 @@ SELECT
UNION ALL UNION ALL
SELECT SELECT
io.ref, io.ref,
c.socialName, t.id ticketFk,
sa.iban, NULL botanical,
pm.name AS payMethod,
t.clientFk,
t.shipped,
t.nickname,
t.id AS ticketFk,
'',
ts.description concept,
ts.quantity, ts.quantity,
ts.price, ts.price,
0 discount, 0 discount,
NULL AS tag5, '',
NULL AS value5, ts.description concept,
NULL AS tag6, tc.code vatType
NULL AS value6,
NULL AS tag7,
NULL AS value7,
tc.code AS vatType,
NULL AS botanical
FROM vn.invoiceOut io FROM vn.invoiceOut io
JOIN vn.ticket t ON t.refFk = io.ref JOIN vn.ticket t ON t.refFk = io.ref
JOIN vn.ticketService ts ON ts.ticketFk = t.id JOIN vn.ticketService ts ON ts.ticketFk = t.id