fixes #5633 Reutilizar accountShortToStandard en un nuevo componente #1541

Closed
alexandre wants to merge 20 commits from 5633-accountShortToStandard into dev
118 changed files with 855 additions and 399 deletions
Showing only changes of commit 27145ae083 - Show all commits

View File

@ -5,18 +5,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2322.01] - 2023-06-08 ## [2324.01] - 2023-06-08
### Added
-
### Changed
-
### Fixed
-
## [2322.01] - 2023-06-01
### Added ### Added
- (Tickets -> Crear Factura) Al facturar se envia automáticamente el pdf al cliente - (Tickets -> Crear Factura) Al facturar se envia automáticamente el pdf al cliente
- (Artículos -> Histórico) Filtro para mostrar lo anterior al inventario
### Changed ### Changed
- (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador' - (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador'
### Fixed ### Fixed
- - (Tickets -> Líneas) Se permite hacer split de líneas al mismo ticket
@ -39,10 +53,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- (Usuarios -> Histórico) Nueva sección - (Usuarios -> Histórico) Nueva sección
- (Roles -> Histórico) Nueva sección - (Roles -> Histórico) Nueva sección
- (General -> Traducciones) Correo de bienvenida a clientes al portugués y al francés - (Trabajadores -> Dar de alta) Permite elegir el método de pago
### Changed ### Changed
- (Artículo -> Precio fijado) Modificado el buscador superior por uno lateral - (Artículo -> Precio fijado) Modificado el buscador superior por uno lateral
- (Trabajadores -> Dar de alta) Quitada obligatoriedad del iban
### Fixed ### Fixed
- (Ticket -> Boxing) Arreglado selección de horas - (Ticket -> Boxing) Arreglado selección de horas

View File

@ -58,7 +58,10 @@ module.exports = Self => {
for (const param in args) for (const param in args)
params[param] = args[param]; params[param] = args[param];
if (!recipient) params.recipient = models.Client.findById(recipientId, {fields: ['email']}); if (!recipient) {
client = await models.Client.findById(recipientId, {fields: ['email']});
params.recipient = client.email;
}
const email = new Email('delivery-note', params); const email = new Email('delivery-note', params);

View File

@ -67,7 +67,7 @@ module.exports = Self => {
if (!image) return false; if (!image) return false;
const hasReadRole = models.ImageCollection.hasReadRole(ctx, collection); const hasReadRole = await models.ImageCollection.hasReadRole(ctx, collection);
if (!hasReadRole) if (!hasReadRole)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);

View File

@ -9,10 +9,11 @@ module.exports = Self => {
* @return {boolean} True for user with read privileges * @return {boolean} True for user with read privileges
*/ */
Self.hasReadRole = async(ctx, name, options) => { Self.hasReadRole = async(ctx, name, options) => {
const collection = await Self.findOne({where: {name}}, { const collection = await Self.findOne({
include: { include: {
relation: 'readRole' relation: 'readRole'
} },
where: {name}
}, options); }, options);
return await hasRole(ctx, collection, options); return await hasRole(ctx, collection, options);

View File

@ -27,7 +27,6 @@ INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalTyp
('Client', 'summary', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'summary', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateAddress', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'updateAddress', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateFiscalData', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'updateFiscalData', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'uploadFile', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'uploadFile', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsPdf', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'campaignMetricsPdf', '*', 'ALLOW', 'ROLE', 'employee'),
('Client', 'campaignMetricsEmail', '*', 'ALLOW', 'ROLE', 'employee'), ('Client', 'campaignMetricsEmail', '*', 'ALLOW', 'ROLE', 'employee'),

View File

@ -0,0 +1,20 @@
-- vn.defaulter source
CREATE OR REPLACE
ALGORITHM = UNDEFINED VIEW `vn`.`defaulter` AS
select
`d`.`clientFk` AS `clientFk`,
`d`.`created` AS `created`,
`d`.`amount` AS `amount`,
`d`.`defaulterSinced` AS `defaulterSinced`,
`d`.`hasChanged` AS `hasChanged`,
`c`.`countryFk` AS `country`,
`c`.`payMethodFk` AS `payMethod`
from
(((`bs`.`defaulter` `d`
join `vn`.`client` `c` on
(`c`.`id` = `d`.`clientFk`))
join `vn`.`country` `co` on
(`co`.`id` = `c`.`countryFk`))
join `vn`.`payMethod` `pm` on
(`pm`.`id` = `c`.`payMethodFk`));

View File

@ -0,0 +1,7 @@
ALTER TABLE `vn`.`workerConfig` ADD payMethodFk tinyint(3) unsigned NULL;
ALTER TABLE `vn`.`workerConfig` ADD CONSTRAINT workerConfig_FK FOREIGN KEY (roleFk) REFERENCES account.`role`(id) ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE `vn`.`workerConfig` ADD CONSTRAINT workerConfig_FK_1 FOREIGN KEY (payMethodFk) REFERENCES `vn`.`payMethod`(id) ON DELETE SET NULL ON UPDATE CASCADE;
-- Cuando se apruebe el PR quitar y poner en redmine para hacerse manualmente
UPDATE `vn`.`workerConfig`
SET payMethodFk = 4
WHERE id=1;

View File

@ -0,0 +1,121 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'salesPerson'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'salesAssistant'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'deliveryBoss'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'buyer'),
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'claimManager'),
('Ticket', 'deleteTicketWithPartPrepared', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Ticket', 'editZone', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'),
('State', 'editableStates', 'READ', 'ALLOW', 'ROLE', 'employee'),
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'production'),
('State', 'isSomeEditable', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('State', 'isAllEditable', 'READ', 'ALLOW', 'ROLE', 'production'),
('State', 'isAllEditable', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('Agency', 'seeExpired', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('Agency', 'seeExpired', 'READ', 'ALLOW', 'ROLE', 'productionBoss'),
('Claim', 'createAfterDeadline', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Client', 'editAddressLogifloraAllowed', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Client', 'editFiscalDataWithoutTaxDataCheck', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Client', 'editVerifiedDataWithoutTaxDataCheck', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
('Client', 'editCredit', 'WRITE', 'ALLOW', 'ROLE', 'financialBoss'),
('Client', 'isNotEditableCredit', 'WRITE', 'ALLOW', 'ROLE', 'financialBoss'),
('InvoiceOut', 'canCreatePdf', 'WRITE', 'ALLOW', 'ROLE', 'invoicing'),
('Supplier', 'editPayMethodCheck', 'WRITE', 'ALLOW', 'ROLE', 'financial'),
('Worker', 'isTeamBoss', 'WRITE', 'ALLOW', 'ROLE', 'teamBoss'),
('Worker', 'forceIsSubordinate', 'READ', 'ALLOW', 'ROLE', 'hr'),
('Claim', 'editState', 'WRITE', 'ALLOW', 'ROLE', 'claimManager');
DELETE FROM `salix`.`ACL`
WHERE
model = 'Claim'
AND property = '*'
AND accessType = '*';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Claim', 'find', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'findById', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'findOne', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'getSummary', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'updateClaim', 'WRITE', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'regularizeClaim', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'updateClaimDestination', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'downloadFile', 'READ', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'deleteById', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
('Claim', 'filter', 'READ', 'ALLOW', 'ROLE', 'salesPerson'),
('Claim', 'logs', 'READ', 'ALLOW', 'ROLE', 'claimManager');
DELETE FROM `salix`.`ACL`
WHERE
model = 'Ticket'
AND property = '*'
AND accessType = '*';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Ticket', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getVolume', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getTotalVolume', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'summary', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'priceDifference', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'componentUpdate', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'new', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'isEditable', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'setDeleted', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'restore', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getSales', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getSalesPersonMana', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'makeInvoice', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'updateEditableTicket', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'updateDiscount', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'transferSales', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'sendSms', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'isLocked', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'freightCost', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'getComponentsSum', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Ticket', 'updateAttributes', 'WRITE', 'ALLOW', 'ROLE', 'delivery'), -- Change Priority in Route tickets
('Ticket', 'deliveryNoteCsv', 'READ', 'ALLOW', 'ROLE', 'employee');
DELETE FROM `salix`.`ACL`
WHERE
model = 'State'
AND property = '*'
AND accessType = 'READ';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('State', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('State', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('State', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee');
DELETE FROM `salix`.`ACL`
WHERE
model = 'Worker'
AND property = '*'
AND accessType = 'READ';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Worker', 'find', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'findById', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'findOne', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'filter', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'getWorkedHours', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'active', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'activeWithRole', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('Worker', 'contracts', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'holidays', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'activeContract', 'READ', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'activeWithInheritedRole', 'READ', 'ALLOW', 'ROLE', 'employee');
DELETE FROM `salix`.`ACL`
WHERE model = 'Client'
AND property = 'updateUser'
AND accessType = '*';

View File

@ -2332,26 +2332,26 @@ INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`, `
INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `code`) INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `code`)
VALUES VALUES
(1, 'Facturas Recibidas', 'recibidas', NULL, NULL, 'invoiceIn'), (1, 'Facturas Recibidas', 'recibidas', NULL, NULL, 'invoiceIn'),
(2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'), (2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'),
(3, 'Laboral', 'laboral', 37, 37, 'hhrrData'), (3, 'Laboral', 'laboral', 37, 37, 'hhrrData'),
(4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'), (4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'),
(5, 'Otros', 'otros', 1, 1, 'miscellaneous'), (5, 'Otros', 'otros', 1, 1, 'miscellaneous'),
(6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'), (6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'),
(7, 'IAE Clientes', 'IAE_Clientes', 1, 1, 'economicActivitiesTax'), (7, 'IAE Clientes', 'IAE_Clientes', 1, 1, 'economicActivitiesTax'),
(8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'), (8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'),
(9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'), (9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'),
(10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'), (10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'),
(11, 'Contratos', 'contratos', NULL, NULL, 'contracts'), (11, 'Contratos', 'contratos', NULL, NULL, 'contracts'),
(12, 'ley de pagos', 'ley pagos', 1, 1, 'paymentsLaw'), (12, 'ley de pagos', 'ley pagos', 1, 1, 'paymentsLaw'),
(13, 'Basura', 'basura', 1, 1, 'trash'), (13, 'Basura', 'basura', 1, 1, 'trash'),
(14, 'Ticket', 'tickets', 1, 1, 'ticket'), (14, 'Ticket', 'tickets', 1, 1, 'ticket'),
(15, 'Presupuestos', 'Presupuestos', NULL, NULL, 'budgets'), (15, 'Presupuestos', 'Presupuestos', NULL, NULL, 'budgets'),
(16, 'Logistica', 'logistica', NULL, NULL, 'logistics'), (16, 'Logistica', 'logistica', NULL, NULL, 'logistics'),
(17, 'cmr', 'cmr', NULL, NULL, 'cmr'), (17, 'cmr', 'cmr', NULL, NULL, 'cmr'),
(18, 'dua', 'dua', NULL, NULL, 'dua'), (18, 'dua', 'dua', NULL, NULL, 'dua'),
(19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'), (19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'),
(20, 'Reclamación', 'reclamacion', 1, 1, 'claim'); (20, 'Reclamación', 'reclamacion', 1, 1, 'claim');
INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`) INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`)
VALUES VALUES
@ -2824,9 +2824,9 @@ INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES VALUES
(1, 1); (1, 1);
INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `businessTypeFk`) INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk`, `businessTypeFk`)
VALUES VALUES
(1, NULL, 1, 'worker'); (1, NULL, 1, 4, 'worker');
INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`) INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`)
VALUES VALUES

View File

@ -61943,141 +61943,205 @@ 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 `item_getBalance`(IN vItemId int, IN vWarehouse int) CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`item_getBalance`(vItemFk int, vWarehouseFk int, vDate DATETIME)
BEGIN BEGIN
DECLARE vDateInventory DATETIME; /**
DECLARE vCurdate DATE DEFAULT util.VN_CURDATE(); * @vItemFk item a buscar
DECLARE vDayEnd DATETIME DEFAULT util.dayEnd(vCurdate); * @vWarehouseFk almacen donde buscar
* @vDate Si la fecha es null, muestra el histórico desde el inventario. Si la fecha no es null, muestra histórico desde la fecha pasada.
*/
SELECT inventoried INTO vDateInventory FROM config; DECLARE vDateInventory DATETIME;
SET @a = 0; DECLARE vInvCalculated INT;
SET @currentLineFk = 0;
SET @shipped = '';
SELECT DATE(@shipped:= shipped) shipped, IF vDate IS NULL THEN
alertLevel, SELECT inventoried INTO vDateInventory
stateName, FROM config;
origin, ELSE
reference, SELECT mockUtcTime INTO vDateInventory
clientFk, FROM util.config;
name, END IF;
`in` AS invalue,
`out`,
@a := @a + IFNULL(`in`,0) - IFNULL(`out`,0) as balance,
@currentLineFk := IF (@shipped < util.VN_CURDATE()
OR (@shipped = util.VN_CURDATE() AND (isPicked OR alertLevel >= 2)),
lineFk,@currentLineFk) lastPreparedLineFk,
isTicket,
lineFk,
isPicked,
clientType,
claimFk
FROM
( SELECT tr.landed AS shipped,
b.quantity AS `in`,
NULL AS `out`,
al.id AS alertLevel,
st.name AS stateName,
s.name AS name,
e.invoiceNumber AS reference,
e.id AS origin,
s.id AS clientFk,
IF(al.id = 3, TRUE, FALSE) isPicked,
FALSE AS isTicket,
b.id lineFk,
NULL `order`,
NULL AS clientType,
NULL AS claimFk
FROM buy b
JOIN entry e ON e.id = b.entryFk
JOIN travel tr ON tr.id = e.travelFk
JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.id =
CASE
WHEN tr.landed < util.VN_CURDATE() THEN 3
WHEN tr.landed = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 3
ELSE 0
END
JOIN state st ON st.code = al.code
WHERE tr.landed >= vDateInventory
AND vWarehouse = tr.warehouseInFk
AND b.itemFk = vItemId
AND e.isExcludedFromAvailable = FALSE
AND e.isRaid = FALSE
UNION ALL
SELECT tr.shipped, CREATE OR REPLACE TEMPORARY TABLE itemDiary(
NULL, shipped DATE,
b.quantity, `in` INT(11),
al.id, `out` INT(11),
st.name, alertLevel INT(11),
s.name, stateName VARCHAR(20),
e.invoiceNumber, `name` VARCHAR(50),
e.id, reference VARCHAR(50),
s.id, origin INT(11),
IF(al.id = 3, TRUE, FALSE), clientFk INT(11),
FALSE, isPicked INT(11),
b.id, isTicket TINYINT(1),
NULL, lineFk INT(11),
NULL, `order` TINYINT(3) UNSIGNED,
NULL clientType VARCHAR(20),
FROM buy b claimFk INT(10) UNSIGNED
JOIN entry e ON e.id = b.entryFk );
JOIN travel tr ON tr.id = e.travelFk
JOIN warehouse w ON w.id = tr.warehouseOutFk
JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.id =
CASE
WHEN tr.shipped < util.VN_CURDATE() THEN 3
WHEN tr.shipped = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 3
ELSE 0
END
JOIN state st ON st.code = al.code
WHERE tr.shipped >= vDateInventory
AND vWarehouse =tr.warehouseOutFk
AND s.id <> 4
AND b.itemFk = vItemId
AND e.isExcludedFromAvailable = FALSE
AND w.isFeedStock = FALSE
AND e.isRaid = FALSE
UNION ALL
SELECT DATE(t.shipped), INSERT INTO itemDiary
NULL, SELECT tr.landed shipped,
s.quantity, b.quantity `in`,
al.id, NULL `out`,
st.name, al.id alertLevel,
t.nickname, st.name stateName,
t.refFk, s.name `name`,
t.id, e.invoiceNumber reference,
t.clientFk, e.id origin,
stk.id, s.id clientFk,
TRUE, IF(al.code = 'DELIVERED', TRUE, FALSE) isPicked,
s.id, FALSE isTicket,
st.`order`, b.id lineFk,
ct.code, NULL `order`,
cb.claimFk NULL clientType,
FROM sale s NULL claimFk
JOIN ticket t ON t.id = s.ticketFk FROM buy b
LEFT JOIN ticketState ts ON ts.ticket = t.id JOIN entry e ON e.id = b.entryFk
LEFT JOIN state st ON st.code = ts.code JOIN travel tr ON tr.id = e.travelFk
JOIN client c ON c.id = t.clientFk JOIN supplier s ON s.id = e.supplierFk
JOIN clientType ct ON ct.id = c.clientTypeFk JOIN alertLevel al ON al.code =
JOIN alertLevel al ON al.id = CASE
CASE WHEN tr.landed < util.VN_CURDATE() THEN 'DELIVERED'
WHEN t.shipped < util.VN_CURDATE() THEN 3 WHEN tr.landed = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 'DELIVERED'
WHEN t.shipped > util.dayEnd(util.VN_CURDATE()) THEN 0 ELSE 'FREE'
ELSE IFNULL(ts.alertLevel, 0) END
END JOIN state st ON st.code = al.code
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED' WHERE tr.landed >= vDateInventory
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id AND vWarehouseFk = tr.warehouseInFk
LEFT JOIN claimBeginning cb ON s.id = cb.saleFk AND b.itemFk = vItemFk
WHERE t.shipped >= vDateInventory AND e.isExcludedFromAvailable = FALSE
AND s.itemFk = vItemId AND e.isRaid = FALSE
AND vWarehouse =t.warehouseFk UNION ALL
ORDER BY shipped, alertLevel DESC, isTicket, `order` DESC, isPicked DESC, `in` DESC, `out` DESC SELECT tr.shipped,
) AS itemDiary; NULL,
b.quantity,
al.id,
st.name,
s.name,
e.invoiceNumber,
e.id,
s.id,
IF(al.code = 'DELIVERED', TRUE, FALSE),
FALSE,
b.id,
NULL,
NULL,
NULL
FROM buy b
JOIN entry e ON e.id = b.entryFk
JOIN travel tr ON tr.id = e.travelFk
JOIN warehouse w ON w.id = tr.warehouseOutFk
JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.code =
CASE
WHEN tr.shipped < util.VN_CURDATE() THEN 'DELIVERED'
WHEN tr.shipped = util.VN_CURDATE() AND tr.isReceived = TRUE THEN 'DELIVERED'
ELSE 'FREE'
END
JOIN state st ON st.code = al.code
JOIN entryConfig ec
WHERE tr.shipped >= vDateInventory
AND vWarehouseFk =tr.warehouseOutFk
AND s.id <> ec.inventorySupplierFk
AND b.itemFk = vItemFk
AND e.isExcludedFromAvailable = FALSE
AND w.isFeedStock = FALSE
AND e.isRaid = FALSE
UNION ALL
SELECT DATE(t.shipped),
NULL,
s.quantity,
al3.id,
st.name,
t.nickname,
t.refFk,
t.id,
t.clientFk,
stk.id,
TRUE,
s.id,
st.`order`,
ct.code,
cb.claimFk
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
LEFT JOIN ticketState ts ON ts.ticket = t.id
LEFT JOIN state st ON st.code = ts.code
JOIN client c ON c.id = t.clientFk
JOIN clientType ct ON ct.id = c.clientTypeFk
JOIN alertLevel al ON al.code = 'DELIVERED'
JOIN alertLevel al2 ON al2.code = 'FREE'
JOIN alertLevel al3 ON al3.id =
CASE
WHEN t.shipped < util.VN_CURDATE() THEN al.code
WHEN t.shipped > util.dayEnd(util.VN_CURDATE()) THEN al2.code
ELSE IFNULL(ts.alertLevel, al2.code)
END
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
LEFT JOIN claimBeginning cb ON s.id = cb.saleFk
WHERE t.shipped >= vDateInventory
AND s.itemFk = vItemFk
AND vWarehouseFk =t.warehouseFk
ORDER BY shipped, alertLevel DESC, isTicket, `order` DESC, isPicked DESC, `in` DESC, `out` DESC;
IF vDate IS NULL THEN
SET @a = 0;
SET @currentLineFk = 0;
SET @shipped = '';
SELECT DATE(@shipped:= shipped) shipped,
alertLevel,
stateName,
origin,
reference,
clientFk,
name,
`in` AS invalue,
`out`,
@a := @a + IFNULL(`in`,0) - IFNULL(`out`,0) as balance,
@currentLineFk := IF (@shipped < util.VN_CURDATE()
OR (@shipped = util.VN_CURDATE() AND (isPicked OR a.code >= 'ON_PREPARATION')),
lineFk, @currentLineFk) lastPreparedLineFk,
isTicket,
lineFk,
isPicked,
clientType,
claimFk
FROM itemDiary
JOIN alertLevel a ON a.id = itemDiary.alertLevel;
ELSE
SELECT sum(`in`) - sum(`out`) INTO vInvCalculated
FROM itemDiary
WHERE shipped < vDate;
SELECT p1.*
FROM(
SELECT vDate shipped,
0 alertLevel,
0 stateName,
0 origin,
'' reference,
0 clientFk,
'Inventario calculado',
vInvCalculated invalue,
NULL `out`,
0 balance,
0 lastPreparedLineFk,
0 isTicket,
0 lineFk,
0 isPicked,
0 clientType,
0 claimFk
UNION ALL
SELECT shipped, alertlevel, stateName, origin, reference, clientFk, name, `in`, `out`, 0,0, isTicket, lineFk, isPicked, clientType, claimFk
FROM itemDiary
WHERE shipped >= vDate
)as p1;
END IF;
DROP TEMPORARY TABLE itemDiary;
END ;; END ;;
DELIMITER ; DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET sql_mode = @saved_sql_mode */ ;

View File

@ -313,7 +313,7 @@ export default {
anyClient: 'vn-client-defaulter tbody > tr', anyClient: 'vn-client-defaulter tbody > tr',
firstClientName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(2) > span', firstClientName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(2) > span',
firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(3) > span', firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(3) > span',
firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]', firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(8) > vn-textarea[ng-model="defaulter.observation"]',
allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check', allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check',
addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]', addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]',
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',

View File

@ -17,7 +17,7 @@ describe('Claim summary path', () => {
}); });
it('should navigate to the target claim summary section', async() => { it('should navigate to the target claim summary section', async() => {
await page.loginAndModule('employee', 'claim'); await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult(claimId); await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary'); await page.waitForState('claim.card.summary');
}); });

View File

@ -16,7 +16,7 @@ describe('Claim descriptor path', () => {
}); });
it('should now navigate to the target claim summary section', async() => { it('should now navigate to the target claim summary section', async() => {
await page.loginAndModule('employee', 'claim'); await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult(claimId); await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary'); await page.waitForState('claim.card.summary');
}); });

View File

@ -1,5 +1,20 @@
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
const $ = {
saveButton: 'vn-supplier-fiscal-data button[type="submit"]',
};
const $inputs = {
province: 'vn-supplier-fiscal-data [name="province"]',
country: 'vn-supplier-fiscal-data [name="country"]',
postcode: 'vn-supplier-fiscal-data [name="postcode"]',
city: 'vn-supplier-fiscal-data [name="city"]',
socialName: 'vn-supplier-fiscal-data [name="socialName"]',
taxNumber: 'vn-supplier-fiscal-data [name="taxNumber"]',
account: 'vn-supplier-fiscal-data [name="account"]',
sageWithholding: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageWithholdingFk"]',
sageTaxType: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageTaxTypeFk"]'
};
describe('Supplier fiscal data path', () => { describe('Supplier fiscal data path', () => {
let browser; let browser;
let page; let page;

View File

@ -203,7 +203,7 @@ export default class Searchbar extends Component {
doSearch(filter, source) { doSearch(filter, source) {
if (filter === this.filter && !this.isIndex) return; if (filter === this.filter && !this.isIndex) return;
let promise = this.onSearch({$params: filter}); let promise = this.onSearch({$params: filter}, source);
promise = promise || this.$q.resolve(); promise = promise || this.$q.resolve();
promise.then(data => this.onFilter(filter, source, data)); promise.then(data => this.onFilter(filter, source, data));
this.toBar(filter); this.toBar(filter);
@ -259,12 +259,6 @@ export default class Searchbar extends Component {
this.filter = filter; this.filter = filter;
if (source == 'removeBar') {
delete params[this.toRemove];
delete this.model.userParams[this.toRemove];
this.model.refresh();
}
if (!filter && this.model) if (!filter && this.model)
this.model.clear(); this.model.clear();
if (source != 'state') if (source != 'state')
@ -279,7 +273,7 @@ export default class Searchbar extends Component {
return {id: params.$row.id}; return {id: params.$row.id};
} }
onSearch(args) { onSearch(args, source) {
if (!this.model) return; if (!this.model) return;
let filter = args.$params; let filter = args.$params;
@ -325,6 +319,12 @@ export default class Searchbar extends Component {
for (let param in stateFilter.tableQ) for (let param in stateFilter.tableQ)
params[param] = stateFilter.tableQ[param]; params[param] = stateFilter.tableQ[param];
if (source == 'removeBar') {
delete params[this.toRemove];
delete this.model.userParams[this.toRemove];
delete stateFilter[this.toRemove];
}
const newParams = Object.assign(stateFilter, params); const newParams = Object.assign(stateFilter, params);
return this.model.applyParams(newParams) return this.model.applyParams(newParams)
.then(() => this.model.data); .then(() => this.model.data);

View File

@ -197,7 +197,7 @@ describe('Component vnSearchbar', () => {
controller.doSearch(filter, 'any'); controller.doSearch(filter, 'any');
$scope.$apply(); $scope.$apply();
expect(controller.onSearch).toHaveBeenCalledWith({$params: filter}); expect(controller.onSearch).toHaveBeenCalledWith({$params: filter}, 'any');
expect(controller.onFilter).toHaveBeenCalledWith(filter, 'any', undefined); expect(controller.onFilter).toHaveBeenCalledWith(filter, 'any', undefined);
}); });
}); });

View File

@ -7,11 +7,13 @@ vn-descriptor-content {
.photo { .photo {
position: relative; position: relative;
width: 100%;
text-align: center;
overflow: hidden;
& > img[ng-src] { & > img[ng-src] {
min-height: 16em; min-height: 16em;
display: block; display: block;
max-width: 100%;
height: 256px; height: 256px;
} }

View File

@ -171,5 +171,6 @@
"Added observation": "Added observation", "Added observation": "Added observation",
"Comment added to client": "Comment added to client", "Comment added to client": "Comment added to client",
"This ticket is already a refund": "This ticket is already a refund", "This ticket is already a refund": "This ticket is already a refund",
"A claim with that sale already exists": "A claim with that sale already exists" "A claim with that sale already exists": "A claim with that sale already exists",
"ASSIGN_ZONE_FIRST": "Assign zone first"
} }

View File

@ -84,6 +84,7 @@
"The current ticket can't be modified": "El ticket actual no puede ser modificado", "The current ticket can't be modified": "El ticket actual no puede ser modificado",
"The current claim can't be modified": "La reclamación actual no puede ser modificada", "The current claim can't be modified": "La reclamación actual no puede ser modificada",
"The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas", "The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas",
"The sales do not exists": "La(s) línea(s) seleccionada(s) no existe(n)",
"Please select at least one sale": "Por favor selecciona al menos una linea", "Please select at least one sale": "Por favor selecciona al menos una linea",
"All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket", "All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket",
"NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada", "NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada",
@ -290,5 +291,7 @@
"isTaxDataChecked": "Datos comprobados", "isTaxDataChecked": "Datos comprobados",
"comercialId": "Id comercial", "comercialId": "Id comercial",
"comercialName": "Comercial", "comercialName": "Comercial",
"Invalid NIF for VIES": "Invalid NIF for VIES" "Invalid NIF for VIES": "Invalid NIF for VIES",
"Ticket does not exist": "Este ticket no existe",
"Ticket is already signed": "Este ticket ya ha sido firmado"
} }

View File

@ -59,12 +59,14 @@ module.exports = Self => {
const landedPlusWeek = new Date(ticket.landed); const landedPlusWeek = new Date(ticket.landed);
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7); landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
const hasClaimManagerRole = await models.VnUser.hasRole(userId, 'claimManager', myOptions);
const isClaimable = landedPlusWeek >= Date.vnNew(); const isClaimable = landedPlusWeek >= Date.vnNew();
const canCreateClaimAfterDeadline =
await models.ACL.checkAccessAcl(ctx, 'Claim', 'createAfterDeadline', 'WRITE');
if (ticket.isDeleted) if (ticket.isDeleted)
throw new UserError(`You can't create a claim for a removed ticket`); throw new UserError(`You can't create a claim for a removed ticket`);
if (!isClaimable && !hasClaimManagerRole) if (!isClaimable && !canCreateClaimAfterDeadline)
throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`); throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`);
const newClaim = await Self.create({ const newClaim = await Self.create({

View File

@ -46,7 +46,6 @@ module.exports = Self => {
Self.updateClaim = async(ctx, id, options) => { Self.updateClaim = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const args = ctx.args; const args = ctx.args;
let tx; let tx;
const myOptions = {}; const myOptions = {};
@ -81,9 +80,9 @@ module.exports = Self => {
if (args.claimStateFk) { if (args.claimStateFk) {
const canEditOldState = await models.ClaimState.isEditable(ctx, claim.claimStateFk, myOptions); const canEditOldState = await models.ClaimState.isEditable(ctx, claim.claimStateFk, myOptions);
const canEditNewState = await models.ClaimState.isEditable(ctx, args.claimStateFk, myOptions); const canEditNewState = await models.ClaimState.isEditable(ctx, args.claimStateFk, myOptions);
const isClaimManager = await models.VnUser.hasRole(userId, 'claimManager', myOptions); const canEditState = await models.ACL.checkAccessAcl(ctx, 'Claim', 'editState', 'WRITE');
if (!canEditOldState || !canEditNewState || changedHasToPickUp && !isClaimManager) if (!canEditOldState || !canEditNewState || changedHasToPickUp && !canEditState)
throw new UserError(`You don't have enough privileges to change that field`); throw new UserError(`You don't have enough privileges to change that field`);
} }

View File

@ -87,15 +87,15 @@ module.exports = function(Self) {
Self.updateAddress = async(ctx, clientId, addressId, options) => { Self.updateAddress = async(ctx, clientId, addressId, options) => {
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
const userId = ctx.req.accessToken.userId;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', myOptions); const canEditAddressLogifloraAllowed =
await models.ACL.checkAccessAcl(ctx, 'Client', 'editAddressLogifloraAllowed');
if (args.isLogifloraAllowed && !isSalesAssistant) if (args.isLogifloraAllowed && !canEditAddressLogifloraAllowed)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const address = await models.Address.findOne({ const address = await models.Address.findOne({

View File

@ -131,9 +131,10 @@ module.exports = Self => {
myOptions.transaction = tx; myOptions.transaction = tx;
} }
try { try {
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', myOptions); const canEditNotTaxDataChecked =
await models.ACL.checkAccessAcl(ctx, 'Client', 'editFiscalDataWithoutTaxDataCheck', 'WRITE');
const client = await models.Client.findById(clientId, null, myOptions); const client = await models.Client.findById(clientId, null, myOptions);
if (!isSalesAssistant && client.isTaxDataChecked) if (!canEditNotTaxDataChecked && client.isTaxDataChecked)
throw new UserError(`Not enough privileges to edit a client with verified data`); throw new UserError(`Not enough privileges to edit a client with verified data`);
// Sage data validation // Sage data validation
const taxDataChecked = args.isTaxDataChecked; const taxDataChecked = args.isTaxDataChecked;

View File

@ -2,6 +2,7 @@ const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('updateUser', { Self.remoteMethodCtx('updateUser', {
description: 'Updates the user information', description: 'Updates the user information',
accessType: 'WRITE',
accepts: [ accepts: [
{ {
arg: 'id', arg: 'id',
@ -32,7 +33,6 @@ module.exports = Self => {
Self.updateUser = async function(ctx, id, options) { Self.updateUser = async function(ctx, id, options) {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let tx; let tx;
const myOptions = {}; const myOptions = {};
@ -45,9 +45,8 @@ module.exports = Self => {
} }
try { try {
const isSalesPerson = await models.VnUser.hasRole(userId, 'salesPerson', myOptions); const canEdit = await models.ACL.checkAccessAcl(ctx, 'Client', 'updateUser', 'WRITE');
if (!canEdit)
if (!isSalesPerson)
throw new UserError(`Not enough privileges to edit a client`); throw new UserError(`Not enough privileges to edit a client`);
const isClient = await models.Client.findById(id, null, myOptions); const isClient = await models.Client.findById(id, null, myOptions);

View File

@ -67,9 +67,13 @@ module.exports = Self => {
uw.id workerFk, uw.id workerFk,
uw.name workerName, uw.name workerName,
c.creditInsurance, c.creditInsurance,
d.defaulterSinced d.defaulterSinced,
cn.country,
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
JOIN vn.country cn ON cn.id = c.countryFk
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
LEFT JOIN vn.clientObservation co ON co.clientFk = c.id LEFT JOIN vn.clientObservation co ON co.clientFk = c.id
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN account.user uw ON uw.id = co.workerFk LEFT JOIN account.user uw ON uw.id = co.workerFk

View File

@ -218,9 +218,9 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const loopBackContext = LoopBackContext.getCurrentContext(); const loopBackContext = LoopBackContext.getCurrentContext();
const userId = loopBackContext.active.accessToken.userId; const accessToken = {req: loopBackContext.active.accessToken};
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', ctx.options); const editVerifiedDataWithoutTaxDataChecked = models.ACL.checkAccessAcl(accessToken, 'Client', 'editVerifiedDataWithoutTaxDataCheck', 'WRITE');
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked); const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked);
@ -232,8 +232,8 @@ module.exports = Self => {
const sageTransactionType = hasChanges && (changes.sageTransactionTypeFk || orgData.sageTransactionTypeFk); const sageTransactionType = hasChanges && (changes.sageTransactionTypeFk || orgData.sageTransactionTypeFk);
const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType; const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType;
const cantEditVerifiedData = isTaxDataCheckedChanged && !isSalesAssistant; const cantEditVerifiedData = isTaxDataCheckedChanged && !editVerifiedDataWithoutTaxDataChecked;
const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !isSalesAssistant; const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !editVerifiedDataWithoutTaxDataChecked;
if (cantEditVerifiedData || cantChangeSageData) if (cantEditVerifiedData || cantChangeSageData)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
@ -401,9 +401,10 @@ module.exports = Self => {
Self.changeCredit = async function changeCredit(ctx, finalState, changes) { Self.changeCredit = async function changeCredit(ctx, finalState, changes) {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.options.accessToken.userId; const userId = ctx.options.accessToken.userId;
const accessToken = {req: {accessToken: ctx.options.accessToken} };
const isFinancialBoss = await models.VnUser.hasRole(userId, 'financialBoss', ctx.options); const canEditCredit = await models.ACL.checkAccessAcl(accessToken, 'Client', 'editCredit', 'WRITE');
if (!isFinancialBoss) { if (!canEditCredit) {
const lastCredit = await models.ClientCredit.findOne({ const lastCredit = await models.ClientCredit.findOne({
where: { where: {
clientFk: finalState.id clientFk: finalState.id
@ -412,10 +413,9 @@ module.exports = Self => {
}, ctx.options); }, ctx.options);
const lastAmount = lastCredit && lastCredit.amount; const lastAmount = lastCredit && lastCredit.amount;
const lastWorkerId = lastCredit && lastCredit.workerFk; const lastCreditIsNotEditable = !await models.ACL.checkAccessAcl(accessToken, 'Client', 'isNotEditableCredit', 'WRITE');
const lastWorkerIsFinancialBoss = await models.VnUser.hasRole(lastWorkerId, 'financialBoss', ctx.options);
if (lastAmount == 0 && lastWorkerIsFinancialBoss) if (lastAmount == 0 && lastCreditIsNotEditable)
throw new UserError(`You can't change the credit set to zero from a financialBoss`); throw new UserError(`You can't change the credit set to zero from a financialBoss`);
const creditLimits = await models.ClientCreditLimit.find({ const creditLimits = await models.ClientCreditLimit.find({

View File

@ -29,6 +29,16 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Client", "model": "Client",
"foreignKey": "clientFk" "foreignKey": "clientFk"
},
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "country"
},
"payMethod": {
"type": "belongsTo",
"model": "PayMethod",
"foreignKey": "payMethod"
} }
} }
} }

View File

@ -57,6 +57,13 @@
<th field="salesPersonFk"> <th field="salesPersonFk">
<span translate>Comercial</span> <span translate>Comercial</span>
</th> </th>
<th field="country">
<span translate>Country</span>
</th>
<th field="payMethod"
vn-tooltip="Pay Method">
<span translate>P.Method</span>
</th>
<th <th
field="amount" field="amount"
vn-tooltip="Balance due"> vn-tooltip="Balance due">
@ -111,6 +118,12 @@
{{::defaulter.salesPersonName | dashIfEmpty}} {{::defaulter.salesPersonName | dashIfEmpty}}
</span> </span>
</td> </td>
<td>
{{::defaulter.country}}
</td>
<td>
{{::defaulter.payMethod}}
</td>
<td>{{::defaulter.amount | currency: 'EUR': 2}}</td> <td>{{::defaulter.amount | currency: 'EUR': 2}}</td>
<td> <td>
<span <span

View File

@ -20,8 +20,7 @@ export default class Controller extends Section {
showField: 'name', showField: 'name',
valueField: 'id' valueField: 'id'
} }
}, }, {
{
field: 'salesPersonFk', field: 'salesPersonFk',
autocomplete: { autocomplete: {
url: 'Workers/activeWithInheritedRole', url: 'Workers/activeWithInheritedRole',
@ -30,6 +29,18 @@ export default class Controller extends Section {
showField: 'name', showField: 'name',
valueField: 'id', valueField: 'id',
} }
}, {
field: 'country',
autocomplete: {
showField: 'country',
valueField: 'country'
}
}, {
field: 'payMethodFk',
autocomplete: {
showField: 'name',
valueField: 'id'
}
}, },
{ {
field: 'workerFk', field: 'workerFk',
@ -132,7 +143,7 @@ export default class Controller extends Section {
sendMail() { sendMail() {
const params = { const params = {
defaulters: this.checked, defaulters: this.checked,
observation: this.defaulter.observation observation: this.defaulter.observation,
}; };
this.$http.post(`Defaulters/observationEmail`, params); this.$http.post(`Defaulters/observationEmail`, params);
} }
@ -143,6 +154,8 @@ export default class Controller extends Section {
case 'amount': case 'amount':
case 'clientFk': case 'clientFk':
case 'workerFk': case 'workerFk':
case 'country':
case 'payMethod':
case 'salesPersonFk': case 'salesPersonFk':
return {[`d.${param}`]: value}; return {[`d.${param}`]: value};
case 'created': case 'created':

View File

@ -9,3 +9,6 @@ Search client: Buscar clientes
Worker who made the last observation: Trabajador que ha realizado la última observación Worker who made the last observation: Trabajador que ha realizado la última observación
Email sended!: Email enviado! Email sended!: Email enviado!
Observation saved!: Observación añadida! Observation saved!: Observación añadida!
P.Method: F.Pago
Pay Method: Forma de Pago
Country: Pais

View File

@ -2,6 +2,11 @@
vn-id="watcher" vn-id="watcher"
data="$ctrl.dms"> data="$ctrl.dms">
</vn-watcher> </vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form <form
name="form" name="form"
ng-submit="$ctrl.onSubmit()" ng-submit="$ctrl.onSubmit()"
@ -29,7 +34,7 @@
<vn-autocomplete vn-one required="true" <vn-autocomplete vn-one required="true"
label="Warehouse" label="Warehouse"
ng-model="$ctrl.dms.warehouseId" ng-model="$ctrl.dms.warehouseId"
url="Warehouses" data="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -5,6 +5,11 @@
form="form" form="form"
save="patch"> save="patch">
</vn-watcher> </vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form name="form" ng-submit="watcher.submit()" class="vn-w-md"> <form name="form" ng-submit="watcher.submit()" class="vn-w-md">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal> <vn-horizontal>
@ -152,14 +157,14 @@
<vn-autocomplete <vn-autocomplete
label="Warehouse Out" label="Warehouse Out"
ng-model="$ctrl.travelFilterParams.warehouseOutFk" ng-model="$ctrl.travelFilterParams.warehouseOutFk"
url="Warehouses" data="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
label="Warehouse In" label="Warehouse In"
ng-model="$ctrl.travelFilterParams.warehouseInFk" ng-model="$ctrl.travelFilterParams.warehouseInFk"
url="Warehouses" data="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -25,7 +25,6 @@ module.exports = Self => {
Self.createPdf = async function(ctx, id, options) { Self.createPdf = async function(ctx, id, options) {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`); throw new UserError(`Action not allowed on the test environment`);
@ -43,9 +42,9 @@ module.exports = Self => {
try { try {
const invoiceOut = await Self.findById(id, null, myOptions); const invoiceOut = await Self.findById(id, null, myOptions);
const hasInvoicing = await models.VnUser.hasRole(userId, 'invoicing', myOptions); const canCreatePdf = await models.ACL.checkAccessAcl(ctx, 'InvoiceOut', 'canCreatePdf', 'WRITE');
if (invoiceOut.hasPdf && !hasInvoicing) if (invoiceOut.hasPdf && !canCreatePdf)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
await invoiceOut.updateAttributes({ await invoiceOut.updateAttributes({

View File

@ -26,8 +26,8 @@ module.exports = Self => {
Object.assign(myOptions, options); Object.assign(myOptions, options);
const where = filter.where; const where = filter.where;
const query = 'CALL vn.item_getBalance(?, ?)'; const query = 'CALL vn.item_getBalance(?, ?, ?)';
const [diary] = await Self.rawSql(query, [where.itemFk, where.warehouseFk], myOptions); const [diary] = await Self.rawSql(query, [where.itemFk, where.warehouseFk, where.date], myOptions);
for (const entry of diary) for (const entry of diary)
if (entry.clientType === 'loses') entry.highlighted = true; if (entry.clientType === 'loses') entry.highlighted = true;

View File

@ -21,7 +21,8 @@ describe('item getBalance()', () => {
const filter = { const filter = {
where: { where: {
itemFk: 1, itemFk: 1,
warehouseFk: 1 warehouseFk: 1,
date: null
} }
}; };
const results = await models.Item.getBalance(filter, options); const results = await models.Item.getBalance(filter, options);
@ -45,14 +46,16 @@ describe('item getBalance()', () => {
const firstFilter = { const firstFilter = {
where: { where: {
itemFk: 1, itemFk: 1,
warehouseFk: 1 warehouseFk: 1,
date: null
} }
}; };
const secondFilter = { const secondFilter = {
where: { where: {
itemFk: 2, itemFk: 2,
warehouseFk: 1 warehouseFk: 1,
date: null
} }
}; };

View File

@ -1,3 +1,8 @@
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<vn-descriptor-content <vn-descriptor-content
module="item" module="item"
description="$ctrl.item.name" description="$ctrl.item.name"
@ -108,7 +113,9 @@
<vn-autocomplete <vn-autocomplete
label="Warehouse" label="Warehouse"
ng-model="$ctrl.warehouseFk" ng-model="$ctrl.warehouseFk"
url="Warehouses"> data="warehouses"
show-field="name"
value="id">>
</vn-autocomplete> </vn-autocomplete>
</tpl-body> </tpl-body>
<tpl-buttons> <tpl-buttons>

View File

@ -25,6 +25,16 @@
ng-model="$ctrl.warehouseFk" ng-model="$ctrl.warehouseFk"
label="Select warehouse"> label="Select warehouse">
</vn-autocomplete> </vn-autocomplete>
<vn-check
ng-class="{'table-check':$ctrl.showOld}"
label="Show what's before the inventory"
ng-model="$ctrl.showOld">
</vn-check>
<vn-date-picker
label="Since"
ng-model="$ctrl.date"
ng-show="$ctrl.showOld">
</vn-date-picker>
</vn-horizontal> </vn-horizontal>
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>

View File

@ -38,12 +38,8 @@ class Controller extends Section {
if (value && value != this._warehouseFk) { if (value && value != this._warehouseFk) {
this._warehouseFk = value; this._warehouseFk = value;
this.card.warehouseFk = value; this.card.warehouseFk = value;
this.filter.where.warehouseFk = this.warehouseFk;
this.$state.go(this.$state.current.name, {
warehouseFk: value
});
this.filter.where.warehouseFk = value;
this.$.model.refresh(); this.$.model.refresh();
} }
} }
@ -52,6 +48,28 @@ class Controller extends Section {
return this._warehouseFk; return this._warehouseFk;
} }
set date(value) {
this._date = value;
this.filter.where.date = value;
this.filter.where.warehouseFk = this.warehouseFk;
this.$.model.refresh();
}
get date() {
return this._date;
}
set showOld(value) {
this._showOld = value;
if (!this._showOld) this.date = null;
else this.date = new Date();
}
get showOld() {
return this._showOld;
}
scrollToLine(lineFk) { scrollToLine(lineFk) {
this.$.$applyAsync(() => { this.$.$applyAsync(() => {
const hashFk = this.lineFk || lineFk; const hashFk = this.lineFk || lineFk;

View File

@ -2,3 +2,4 @@ In: Entrada
Out: Salida Out: Salida
Visible quantity: Cantidad visible Visible quantity: Cantidad visible
Ticket/Entry: Ticket/Entrada Ticket/Entry: Ticket/Entrada
Show what's before the inventory: Mostrar lo anterior al inventario

View File

@ -27,4 +27,7 @@ vn-item-diary {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.table-check{
justify-content: center;
}
} }

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -35,7 +40,9 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses"> data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal class="vn-px-lg"> <vn-horizontal class="vn-px-lg">

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -83,7 +88,7 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses"> data="Warehouses">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Agencies"
data="agencies">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -79,7 +84,7 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses" data="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -99,18 +99,20 @@ module.exports = Self => {
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) return;
const loopbackContext = LoopBackContext.getCurrentContext();
const changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance; const orgData = ctx.currentInstance;
const userId = loopbackContext.active.accessToken.userId; const loopBackContext = LoopBackContext.getCurrentContext();
const accessToken = {req: loopBackContext.active.accessToken};
const editPayMethodCheck =
await Self.app.models.ACL.checkAccessAcl(accessToken, 'Supplier', 'editPayMethodCheck', 'WRITE');
const isNotFinancial = !await Self.app.models.VnUser.hasRole(userId, 'financial');
const isPayMethodChecked = changes.isPayMethodChecked || orgData.isPayMethodChecked; const isPayMethodChecked = changes.isPayMethodChecked || orgData.isPayMethodChecked;
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
const isPayMethodCheckedChanged = hasChanges const isPayMethodCheckedChanged = hasChanges
&& orgData.isPayMethodChecked != isPayMethodChecked; && orgData.isPayMethodChecked != isPayMethodChecked;
if (isNotFinancial && isPayMethodCheckedChanged) if (!editPayMethodCheck && isPayMethodCheckedChanged)
throw new UserError('You can not modify is pay method checked'); throw new UserError('You can not modify is pay method checked');
}); });

View File

@ -38,6 +38,9 @@ module.exports = Self => {
} }
}, myOptions); }, myOptions);
if (!salesData.length)
throw new UserError(`The sales do not exists`);
const ticketId = salesData[0].ticketFk; const ticketId = salesData[0].ticketFk;
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions); const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId, myOptions);
@ -62,7 +65,5 @@ module.exports = Self => {
throw new UserError('It is not possible to modify cloned sales'); throw new UserError('It is not possible to modify cloned sales');
if (!shouldEditFloramondo) if (!shouldEditFloramondo)
throw new UserError('It is not possible to modify sales that their articles are from Floramondo'); throw new UserError('It is not possible to modify sales that their articles are from Floramondo');
return true;
}; };
}; };

View File

@ -17,6 +17,32 @@ describe('sale canEdit()', () => {
}); });
}); });
describe('sale not exists', () => {
it('should return error if sale not exists', async() => {
const tx = await models.Sale.beginTransaction({});
try {
const options = {transaction: tx};
const developerId = 9;
const ctx = {req: {accessToken: {userId: developerId}}};
let max = await models.Sale.findOne({fields: ['id'], order: 'id DESC'}, options);
max.id = max.id + 1;
const sales = [max.id];
await models.Sale.canEdit(ctx, sales, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e.message;
}
expect(error).toEqual('The sales do not exists');
});
});
describe('sale editTracked', () => { describe('sale editTracked', () => {
it('should return true if the role is production regardless of the saleTrackings', async() => { it('should return true if the role is production regardless of the saleTrackings', async() => {
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
@ -29,9 +55,7 @@ describe('sale canEdit()', () => {
const sales = [25]; const sales = [25];
const result = await models.Sale.canEdit(ctx, sales, options); await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -51,9 +75,7 @@ describe('sale canEdit()', () => {
const sales = [10]; const sales = [10];
const result = await models.Sale.canEdit(ctx, sales, options); await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -87,9 +109,7 @@ describe('sale canEdit()', () => {
}); });
const ctx = {req: {accessToken: {userId: role.id}}}; const ctx = {req: {accessToken: {userId: role.id}}};
const result = await models.Sale.canEdit(ctx, saleCloned, options); await models.Sale.canEdit(ctx, saleCloned, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -150,9 +170,7 @@ describe('sale canEdit()', () => {
const saleToEdit = await models.Sale.findById(sales[0], null, options); const saleToEdit = await models.Sale.findById(sales[0], null, options);
await saleToEdit.updateAttribute('itemFk', 9, options); await saleToEdit.updateAttribute('itemFk', 9, options);
const result = await models.Sale.canEdit(ctx, sales, options); await models.Sale.canEdit(ctx, sales, options);
expect(result).toEqual(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -64,7 +64,7 @@ describe('sale updateQuantity()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const isRoleAdvanced = await models.Ticket.isRoleAdvanced(ctx, options); const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
expect(isRoleAdvanced).toEqual(true); expect(isRoleAdvanced).toEqual(true);

View File

@ -64,7 +64,7 @@ module.exports = Self => {
const sale = await models.Sale.findById(id, filter, myOptions); const sale = await models.Sale.findById(id, filter, myOptions);
const isRoleAdvanced = await models.Ticket.isRoleAdvanced(ctx, myOptions); const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
if (newQuantity > sale.quantity && !isRoleAdvanced) if (newQuantity > sale.quantity && !isRoleAdvanced)
throw new UserError('The new quantity should be smaller than the old one'); throw new UserError('The new quantity should be smaller than the old one');

View File

@ -19,13 +19,11 @@ module.exports = Self => {
Self.editableStates = async(ctx, filter, options) => { Self.editableStates = async(ctx, filter, options) => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {...(options || {})}; const myOptions = {...(options || {})};
const isProduction = await models.VnUser.hasRole(userId, 'production', myOptions); const seeEditableStates = await models.ACL.checkAccessAcl(ctx, 'State', 'seeEditableStates', 'READ');
const isAdministrative = await models.VnUser.hasRole(userId, 'administrative', myOptions);
if (!isProduction && !isAdministrative) if (!seeEditableStates)
filter = mergeFilters(filter, {where: {alertLevel: 0}}); filter = mergeFilters(filter, {where: {alertLevel: 0}});
const states = await models.State.find(filter, myOptions); const states = await models.State.find(filter, myOptions);

View File

@ -19,22 +19,23 @@ module.exports = Self => {
}); });
Self.isEditable = async(ctx, stateId, options) => { Self.isEditable = async(ctx, stateId, options) => {
const accessToken = ctx.req.accessToken;
const models = Self.app.models; const models = Self.app.models;
const userId = accessToken.userId;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const isProduction = await models.VnUser.hasRole(userId, 'production', myOptions); const isAllEditable = await models.ACL.checkAccessAcl(ctx, 'State', 'isAllEditable', 'READ');
const isSalesPerson = await models.VnUser.hasRole(userId, 'salesPerson', myOptions);
const isAdministrative = await models.VnUser.hasRole(userId, 'administrative', myOptions);
const state = await models.State.findById(stateId, null, myOptions); const state = await models.State.findById(stateId, null, myOptions);
const isSomeEditable = (
await models.ACL.checkAccessAcl(ctx, 'State', 'isSomeEditable', 'READ')
&& (
state.code == 'PICKER_DESIGNED' || state.code == 'PRINTED'
)
);
const salesPersonAllowed = (isSalesPerson && (state.code == 'PICKER_DESIGNED' || state.code == 'PRINTED')); const isAllowed = isAllEditable || isSomeEditable || state.alertLevel == 0;
const isAllowed = isProduction || isAdministrative || salesPersonAllowed || state.alertLevel == 0;
return isAllowed; return isAllowed;
}; };
}; };

View File

@ -52,7 +52,7 @@ module.exports = Self => {
JOIN province p ON p.id = c.provinceFk JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED' OR (am.code = 'refund' AND al.code != 'delivered') WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code != 'delivered'))
AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
AND util.dayEnd(?) AND util.dayEnd(?)
AND t.refFk IS NULL AND t.refFk IS NULL

View File

@ -121,8 +121,8 @@ module.exports = Self => {
if (!isEditable) if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
const isDeliveryBoss = await models.VnUser.hasRole(userId, 'deliveryBoss', myOptions); const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE');
if (!isDeliveryBoss) { if (!editZone) {
const zoneShipped = await models.Agency.getShipped( const zoneShipped = await models.Agency.getShipped(
ctx, ctx,
args.landed, args.landed,

View File

@ -30,7 +30,7 @@ module.exports = Self => {
where: {ticketFk: id} where: {ticketFk: id}
}, myOptions); }, myOptions);
const isRoleAdvanced = await models.Ticket.isRoleAdvanced(ctx, myOptions); const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const alertLevel = state ? state.alertLevel : null; const alertLevel = state ? state.alertLevel : null;
const ticket = await models.Ticket.findById(id, { const ticket = await models.Ticket.findById(id, {

View File

@ -1,32 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('isRoleAdvanced', {
description: 'Check if a ticket is editable',
accessType: 'READ',
returns: {
type: 'boolean',
root: true
},
http: {
path: `/isRoleAdvanced`,
verb: 'GET'
}
});
Self.isRoleAdvanced = async(ctx, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', myOptions);
const isDeliveryBoss = await models.VnUser.hasRole(userId, 'deliveryBoss', myOptions);
const isBuyer = await models.VnUser.hasRole(userId, 'buyer', myOptions);
const isClaimManager = await models.VnUser.hasRole(userId, 'claimManager', myOptions);
const isRoleAdvanced = isSalesAssistant || isDeliveryBoss || isBuyer || isClaimManager;
return isRoleAdvanced;
};
};

View File

@ -63,10 +63,11 @@ module.exports = Self => {
newInstance: {mergedTicket: ticket.originId} newInstance: {mergedTicket: ticket.originId}
}; };
await models.TicketLog.create(ticketDestinationLogRecord, myOptions);
await models.Sale.updateAll({ticketFk: ticket.originId}, {ticketFk: ticket.destinationId}, myOptions); await models.Sale.updateAll({ticketFk: ticket.originId}, {ticketFk: ticket.destinationId}, myOptions);
await models.Ticket.setDeleted(ctx, ticket.originId, myOptions); if (await models.Ticket.setDeleted(ctx, ticket.originId, myOptions)) {
await models.Chat.sendCheckingPresence(ctx, ticket.workerFk, message); await models.TicketLog.create(ticketDestinationLogRecord, myOptions);
await models.Chat.sendCheckingPresence(ctx, ticket.workerFk, message);
}
} }
if (tx) if (tx)
await tx.commit(); await tx.commit();

View File

@ -60,7 +60,6 @@ module.exports = Self => {
Self.priceDifference = async(ctx, options) => { Self.priceDifference = async(ctx, options) => {
const args = ctx.args; const args = ctx.args;
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -78,8 +77,8 @@ module.exports = Self => {
if (!isEditable) if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
const isDeliveryBoss = await models.VnUser.hasRole(userId, 'deliveryBoss', myOptions); const editZone = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'editZone', 'WRITE');
if (!isDeliveryBoss) { if (!editZone) {
const zoneShipped = await models.Agency.getShipped( const zoneShipped = await models.Agency.getShipped(
ctx, ctx,
args.landed, args.landed,

View File

@ -29,8 +29,7 @@ module.exports = Self => {
} }
}); });
Self.saveSign = async(ctx, options) => { Self.saveSign = async(ctx, tickets, location, signedTime, options) => {
const args = Object.assign({}, ctx.args);
const models = Self.app.models; const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -48,9 +47,9 @@ module.exports = Self => {
async function setLocation(ticketId) { async function setLocation(ticketId) {
await models.Delivery.create({ await models.Delivery.create({
ticketFk: ticketId, ticketFk: ticketId,
longitude: args.location.Longitude, longitude: location.Longitude,
latitude: args.location.Latitude, latitude: location.Latitude,
dated: args.signedTime || new Date() dated: signedTime || new Date()
}, myOptions); }, myOptions);
} }
@ -107,9 +106,9 @@ module.exports = Self => {
} }
try { try {
for (let i = 0; i < args.tickets.length; i++) { for (const ticketId of tickets) {
const ticketState = await models.TicketState.findOne( const ticketState = await models.TicketState.findOne(
{where: {ticketFk: args.tickets[i]}, {where: {ticketFk: ticketId},
fields: ['alertLevel'] fields: ['alertLevel']
}, myOptions); }, myOptions);
@ -117,16 +116,19 @@ module.exports = Self => {
fields: ['id'] fields: ['id']
}, myOptions); }, myOptions);
if (!ticketState)
throw new UserError('Ticket does not exist');
if (ticketState.alertLevel < packedAlertLevel.id) if (ticketState.alertLevel < packedAlertLevel.id)
throw new UserError('This ticket cannot be signed because it has not been boxed'); throw new UserError('This ticket cannot be signed because it has not been boxed');
else if (!await gestDocExists(args.tickets[i])) { if (await gestDocExists(ticketId))
if (args.location) setLocation(args.tickets[i]); throw new UserError('Ticket is already signed');
if (!gestDocCreated) await createGestDoc(args.tickets[i]);
await models.TicketDms.create({ticketFk: args.tickets[i], dmsFk: dms[0].id}, myOptions); if (location) setLocation(ticketId);
const ticket = await models.Ticket.findById(args.tickets[i], null, myOptions); if (!gestDocCreated) await createGestDoc(ticketId);
await ticket.updateAttribute('isSigned', true, myOptions); await models.TicketDms.create({ticketFk: ticketId, dmsFk: dms[0].id}, myOptions);
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [args.tickets[i], 'DELIVERED'], myOptions); const ticket = await models.Ticket.findById(ticketId, null, myOptions);
} await ticket.updateAttribute('isSigned', true, myOptions);
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [ticketId, 'DELIVERED'], myOptions);
} }
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -36,7 +36,9 @@ module.exports = Self => {
} }
try { try {
const userId = ctx.req.accessToken.userId; const ticketToDelete = await models.Ticket.findById(id, {fields: ['isDeleted']}, myOptions);
if (ticketToDelete.isDeleted) return false;
const isEditable = await Self.isEditable(ctx, id, myOptions); const isEditable = await Self.isEditable(ctx, id, myOptions);
if (!isEditable) if (!isEditable)
@ -51,7 +53,8 @@ module.exports = Self => {
throw new UserError($t('Tickets with associated refunds', {id: ticketRefunds[0].id})); throw new UserError($t('Tickets with associated refunds', {id: ticketRefunds[0].id}));
// Check if has sales with shelving // Check if has sales with shelving
const isSalesAssistant = await models.VnUser.hasRole(userId, 'salesAssistant', myOptions); const canDeleteTicketWithPartPrepared =
await models.ACL.checkAccessAcl(ctx, 'Ticket', 'deleteTicketWithPartPrepared', 'WRITE');
const sales = await models.Sale.find({ const sales = await models.Sale.find({
include: {relation: 'itemShelvingSale'}, include: {relation: 'itemShelvingSale'},
where: {ticketFk: id} where: {ticketFk: id}
@ -60,7 +63,7 @@ module.exports = Self => {
return sale.itemShelvingSale(); return sale.itemShelvingSale();
}); });
if (hasItemShelvingSales && !isSalesAssistant) if (hasItemShelvingSales && !canDeleteTicketWithPartPrepared)
throw new UserError(`You cannot delete a ticket that part of it is being prepared`); throw new UserError(`You cannot delete a ticket that part of it is being prepared`);
// Check for existing claim // Check for existing claim

View File

@ -3,6 +3,7 @@ let UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('transferSales', { Self.remoteMethodCtx('transferSales', {
description: 'Transfer sales to a new or a given ticket', description: 'Transfer sales to a new or a given ticket',
accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'id',
type: 'number', type: 'number',
@ -78,7 +79,7 @@ module.exports = Self => {
const saleIds = sales.map(sale => sale.id); const saleIds = sales.map(sale => sale.id);
const hasClaimedSales = await models.ClaimBeginning.findOne({where: {saleFk: {inq: saleIds}}}); const hasClaimedSales = await models.ClaimBeginning.findOne({where: {saleFk: {inq: saleIds}}});
if (hasClaimedSales) if (ticketId != id && hasClaimedSales)
throw new UserError(`Can't transfer claimed sales`); throw new UserError(`Can't transfer claimed sales`);
for (const sale of sales) { for (const sale of sales) {

View File

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

View File

@ -36,7 +36,6 @@ module.exports = function(Self) {
require('../methods/ticket/getTicketsFuture')(Self); require('../methods/ticket/getTicketsFuture')(Self);
require('../methods/ticket/merge')(Self); require('../methods/ticket/merge')(Self);
require('../methods/ticket/getTicketsAdvance')(Self); require('../methods/ticket/getTicketsAdvance')(Self);
require('../methods/ticket/isRoleAdvanced')(Self);
require('../methods/ticket/collectionLabel')(Self); require('../methods/ticket/collectionLabel')(Self);
require('../methods/ticket/expeditionPalletLabel')(Self); require('../methods/ticket/expeditionPalletLabel')(Self);
require('../methods/ticket/saveSign')(Self); require('../methods/ticket/saveSign')(Self);

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-date-picker <vn-date-picker
@ -49,7 +54,9 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses" data="warehouses"
show-field="name"
value-field="id"
required="true"> required="true">
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>

View File

@ -4,6 +4,11 @@
order="name" order="name"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form name="form"> <form name="form">
<vn-card class="vn-w-md vn-pa-lg"> <vn-card class="vn-w-md vn-pa-lg">
<vn-horizontal> <vn-horizontal>
@ -25,7 +30,7 @@
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
required="true" required="true"
url="Warehouses" data="Warehouses"
label="Warehouse" label="Warehouse"
show-field="name" show-field="name"
value-field="id" value-field="id"

View File

@ -2,6 +2,11 @@
vn-id="watcher" vn-id="watcher"
data="$ctrl.dms"> data="$ctrl.dms">
</vn-watcher> </vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form <form
name="form" name="form"
ng-submit="$ctrl.onSubmit()" ng-submit="$ctrl.onSubmit()"
@ -29,7 +34,7 @@
<vn-autocomplete vn-one required="true" <vn-autocomplete vn-one required="true"
label="Warehouse" label="Warehouse"
ng-model="$ctrl.dms.warehouseId" ng-model="$ctrl.dms.warehouseId"
url="Warehouses" data="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-date-picker <vn-date-picker
@ -83,7 +88,9 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses" data="warehouses"
show-field="name"
value-field="id"
required="true"> required="true">
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>

View File

@ -1,5 +1,6 @@
import ngModule from '../module'; import ngModule from '../module';
import ModuleMain from 'salix/components/module-main'; import ModuleMain from 'salix/components/module-main';
const UserError = require('vn-loopback/util/user-error');
export default class Ticket extends ModuleMain { export default class Ticket extends ModuleMain {
fetchParams($params) { fetchParams($params) {
@ -14,10 +15,19 @@ export default class Ticket extends ModuleMain {
'scopeDays' 'scopeDays'
]; ];
const seachPanelParams = Object.entries($params);
const hasFromParam = seachPanelParams.some(subarray => subarray.length > 0 && subarray[0] === 'from');
const hasToParam = seachPanelParams.some(subarray => subarray.length > 0 && subarray[0] === 'to');
if ((hasFromParam && !hasToParam) || (!hasFromParam && hasToParam))
throw new UserError(`Date range must have 'from' and 'to'`);
const hasExcludedParams = excludedParams.some(param => { const hasExcludedParams = excludedParams.some(param => {
return $params && $params[param] != undefined; return $params && $params[param] != undefined;
}); });
const hasParams = Object.entries($params).length; const hasParams = Object.entries($params).length;
if (!hasParams || !hasExcludedParams) if (!hasParams || !hasExcludedParams)
$params.scopeDays = 1; $params.scopeDays = 1;
@ -28,7 +38,6 @@ export default class Ticket extends ModuleMain {
const to = new Date(from.getTime()); const to = new Date(from.getTime());
to.setDate(to.getDate() + $params.scopeDays); to.setDate(to.getDate() + $params.scopeDays);
to.setHours(23, 59, 59, 999); to.setHours(23, 59, 59, 999);
Object.assign($params, {from, to}); Object.assign($params, {from, to});
} }

View File

@ -0,0 +1 @@
Date range must have 'from' and 'to': El rango de fechas debe tener 'desde' y 'hasta'

View File

@ -244,7 +244,12 @@ class Controller extends Section {
const query = `tickets/${this.ticket.id}/transferSales`; const query = `tickets/${this.ticket.id}/transferSales`;
this.$http.post(query, params) this.$http.post(query, params)
.then(res => this.$state.go('ticket.card.sale', {id: res.data.id})); .then(res => {
if (res.data && res.data.id === this.ticket.id) {
this.$.transfer.hide();
this.$.model.refresh();
} else this.$state.go('ticket.card.sale', {id: res.data.id});
});
} }
showEditPricePopover(event, sale) { showEditPricePopover(event, sale) {

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()"> <form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg"> <vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-textfield <vn-textfield
@ -102,7 +107,7 @@
vn-one vn-one
label="Warehouse" label="Warehouse"
ng-model="filter.warehouseFk" ng-model="filter.warehouseFk"
url="Warehouses"> data="Warehouses">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete
vn-one vn-one

View File

@ -1,4 +1,9 @@
<div class="search-panel"> <div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form ng-submit="$ctrl.onSearch()"> <form ng-submit="$ctrl.onSearch()">
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
@ -52,14 +57,14 @@
<vn-autocomplete vn-one <vn-autocomplete vn-one
label="Warehouse Out" label="Warehouse Out"
ng-model="filter.warehouseOutFk" ng-model="filter.warehouseOutFk"
url="Warehouses" data="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
label="Warehouse In" label="Warehouse In"
ng-model="filter.warehouseInFk" ng-model="filter.warehouseInFk"
url="Warehouses" data="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('worker-dms downloadFile()', () => { describe('worker-dms downloadFile()', () => {
let dmsId = 4; let dmsId = 4;
@ -6,7 +6,7 @@ describe('worker-dms downloadFile()', () => {
it('should return a response for an employee with text content-type', async() => { it('should return a response for an employee with text content-type', async() => {
let workerId = 1106; let workerId = 1106;
let ctx = {req: {accessToken: {userId: workerId}}}; let ctx = {req: {accessToken: {userId: workerId}}};
const result = await app.models.WorkerDms.downloadFile(ctx, dmsId); const result = await models.WorkerDms.downloadFile(ctx, dmsId);
expect(result[1]).toEqual('text/plain'); expect(result[1]).toEqual('text/plain');
}); });
@ -17,7 +17,7 @@ describe('worker-dms downloadFile()', () => {
let error; let error;
try { try {
await app.models.WorkerDms.downloadFile(ctx, dmsId); await models.WorkerDms.downloadFile(ctx, dmsId);
} catch (e) { } catch (e) {
error = e; error = e;
} }

View File

@ -40,10 +40,10 @@ module.exports = Self => {
Object.assign(myOptions, options); Object.assign(myOptions, options);
const isSubordinate = await models.Worker.isSubordinate(ctx, workerId, myOptions); const isSubordinate = await models.Worker.isSubordinate(ctx, workerId, myOptions);
const isTeamBoss = await models.VnUser.hasRole(currentUserId, 'teamBoss', myOptions); const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
const isHimself = currentUserId == workerId; const isHimself = currentUserId == workerId;
if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss)) if (!isSubordinate || (isSubordinate && isHimself && !isTeamBoss))
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
query = `CALL vn.workerTimeControl_clockIn(?,?,?)`; query = `CALL vn.workerTimeControl_clockIn(?,?,?)`;

View File

@ -32,7 +32,7 @@ module.exports = Self => {
const targetTimeEntry = await Self.findById(id, null, myOptions); const targetTimeEntry = await Self.findById(id, null, myOptions);
const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions); const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions);
const isTeamBoss = await models.VnUser.hasRole(currentUserId, 'teamBoss', myOptions); const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
const isHimself = currentUserId == targetTimeEntry.userFk; const isHimself = currentUserId == targetTimeEntry.userFk;
if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss)) if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss))

View File

@ -38,7 +38,7 @@ module.exports = Self => {
const targetTimeEntry = await Self.findById(id, null, myOptions); const targetTimeEntry = await Self.findById(id, null, myOptions);
const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions); const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions);
const isTeamBoss = await models.VnUser.hasRole(currentUserId, 'teamBoss', myOptions); const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
const isHimself = currentUserId == targetTimeEntry.userFk; const isHimself = currentUserId == targetTimeEntry.userFk;
const notAllowed = isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss); const notAllowed = isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss);

Some files were not shown because too many files have changed in this diff Show More