Merge branch 'dev' into 3791-travel_extra-community
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Carlos Jimenez Ruiz 2022-03-29 08:10:30 +00:00
commit 7209bc52de
41 changed files with 667 additions and 161 deletions

View File

@ -1,2 +0,0 @@
INSERT INTO salix.ACL (id, model, property, accessType, permission, principalType, principalId)
VALUES(301, 'Agency', '*', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES(304, 'Agency', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('SupplierAgencyTerm', '*', '*', 'ALLOW', 'ROLE', 'administrative');

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`claimState` ADD `hasToNotify` TINYINT DEFAULT 0 NULL;
UPDATE `vn`.`claimState` SET `hasToNotify` = 1 WHERE `code` IN ('canceled', 'incomplete');

View File

@ -0,0 +1,48 @@
ALTER TABLE `vn`.`agencyTerm` ADD `supplierFk` INT NULL;
ALTER TABLE `vn`.`agencyTerm` CHANGE `supplierFk` `supplierFk` INT NULL AFTER `agencyFk`;
UPDATE `vn`.`agencyTerm` `at`
JOIN `vn`.`agency` `a` ON `a`.`id` = `at`.`agencyFk`
SET `at`.`supplierFk` = `a`.`supplierFk`;
ALTER TABLE `vn`.`agencyTerm` ADD CONSTRAINT `agencyTerm_FK` FOREIGN KEY (`agencyFk`) REFERENCES `vn`.`agency`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE `vn`.`agencyTerm` ADD CONSTRAINT `agencyTerm_FK_1` FOREIGN KEY (`supplierFk`) REFERENCES `vn`.`supplier`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
RENAME TABLE `vn`.`agencyTerm` TO `vn`.`supplierAgencyTerm`;
CREATE OR REPLACE
ALGORITHM = UNDEFINED
DEFINER=`root`@`localhost`
VIEW `vn`.`agencyTerm` AS
SELECT
`sat`.`agencyFk` AS `agencyFk`,
`sat`.`minimumPackages` AS `minimumPackages`,
`sat`.`kmPrice` AS `kmPrice`,
`sat`.`packagePrice` AS `packagePrice`,
`sat`.`routePrice` AS `routePrice`,
`sat`.`minimumKm` AS `minimumKm`,
`sat`.`minimumM3` AS `minimumM3`,
`sat`.`m3Price` AS `m3Price`
FROM
`vn`.`supplierAgencyTerm` `sat`;
ALTER TABLE `vn`.`agency` DROP FOREIGN KEY `agency_ibfk_4`;
ALTER TABLE `vn`.`agency` CHANGE `supplierFk` `supplierFk__` int(11) DEFAULT NULL NULL;
CREATE OR REPLACE
ALGORITHM = UNDEFINED
DEFINER=`root`@`localhost`
VIEW `vn2008`.`agency` AS
SELECT
`a`.`id` AS `agency_id`,
`a`.`name` AS `name`,
`a`.`warehouseFk` AS `warehouse_id`,
`a`.`isVolumetric` AS `por_volumen`,
`a`.`bankFk` AS `Id_Banco`,
`a`.`warehouseAliasFk` AS `warehouse_alias_id`,
`a`.`isOwn` AS `propios`,
`a`.`labelZone` AS `zone_label`,
`a`.`workCenterFk` AS `workCenterFk`,
`a`.`supplierFk__` AS `supplierFk__`
FROM
`vn`.`agency` `a`;

View File

@ -0,0 +1,3 @@
ALTER TABLE `postgresql`.`business_labour_payroll` DROP FOREIGN KEY `business_labour_payroll_cod_contrato`;
ALTER TABLE `vn`.`workerBusinessType` MODIFY COLUMN `id` int(11) NOT NULL;
ALTER TABLE `postgresql`.`business_labour_payroll` ADD CONSTRAINT `business_labour_payroll_FK` FOREIGN KEY (cod_contrato) REFERENCES `vn`.`workerBusinessType`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -1700,15 +1700,15 @@ INSERT INTO `vn`.`clientSample`(`id`, `clientFk`, `typeFk`, `created`, `workerFk
(4, 1102, 2, CURDATE(), 18, 18, 567),
(5, 1102, 3, CURDATE(), 19, 19, 567);
INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`)
INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`, `hasToNotify`)
VALUES
( 1, 'pending', 'Pendiente', 1, 1),
( 2, 'managed', 'Gestionado', 1, 5),
( 3, 'resolved', 'Resuelto', 72, 7),
( 4, 'canceled', 'Anulado', 72, 6),
( 5, 'incomplete', 'Incompleta', 72, 3),
( 6, 'mana', 'Mana', 1, 4),
( 7, 'lack', 'Faltas', 1, 2);
( 1, 'pending', 'Pendiente', 1, 1, 0),
( 2, 'managed', 'Gestionado', 1, 5, 0),
( 3, 'resolved', 'Resuelto', 72, 7, 0),
( 4, 'canceled', 'Anulado', 72, 6, 1),
( 5, 'incomplete', 'Incompleta', 72, 3, 1),
( 6, 'mana', 'Mana', 1, 4, 0),
( 7, 'lack', 'Faltas', 1, 2, 0);
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `observation`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`)
VALUES
@ -1902,14 +1902,29 @@ INSERT INTO `vn`.`workCenterHoliday` (`workCenterFk`, `days`, `year`)
('1', '24.5', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR))),
('5', '23', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 YEAR)));
INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `code`, `holidayEntitlementRate`)
INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `code`, `holidayEntitlementRate`, `discountRate`)
VALUES
(1, 'Holidays', '#FF4444', 'holiday', 0),
(2, 'Leave of absence', '#C71585', 'absence', 0),
(6, 'Half holiday', '#E65F00', 'halfHoliday', 0),
(15, 'Half Paid Leave', '#5151c0', 'halfPaidLeave', 0),
(20, 'Furlough', '#97B92F', 'furlough', 1),
(21, 'Furlough half day', '#778899', 'halfFurlough', 0.5);
(1, 'Holidays', '#FF4444', 'holiday', 0, 0),
(2, 'Leave of absence', '#C71585', 'absence', 0, 1),
(6, 'Half holiday', '#E65F00', 'halfHoliday', 0, 0.5),
(15, 'Half Paid Leave', '#5151c0', 'halfPaidLeave', 0, 1),
(20, 'Furlough', '#97B92F', 'furlough', 1, 1),
(21, 'Furlough half day', '#778899', 'halfFurlough', 0.5, 1);
ALTER TABLE `postgresql`.`business_labour_payroll` DROP FOREIGN KEY `business_labour_payroll_cod_categoria`;
INSERT INTO `vn`.`workerBusinessType` (`id`, `name`, `isFullTime`, `isPermanent`, `hasHolidayEntitlement`)
VALUES
(1, 'CONTRATO HOLANDA', 1, 0, 1),
(100, 'INDEFINIDO A TIEMPO COMPLETO', 1, 1, 1),
(109, 'CONVERSION DE TEMPORAL EN INDEFINIDO T.COMPLETO', 1, 1, 1);
INSERT INTO `postgresql`.`business_labour_payroll` (`business_id`, `cod_tarifa`, `cod_categoria`, `cod_contrato`, `importepactado`)
VALUES
(1, 7, 12, 100, 900.50),
(1106, 7, 12, 100, 1263.03),
(1107, 7, 12, 100, 2000),
(1108, 7, 12, 100, 1500);
INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`, `date`)
VALUES
@ -2462,12 +2477,6 @@ INSERT INTO `bs`.`defaulter` (`clientFk`, `amount`, `created`, `defaulterSinced`
(1107, 500, CURDATE(), CURDATE()),
(1109, 500, CURDATE(), CURDATE());
INSERT INTO `vn`.`agencyTerm` (`agencyFk`, `minimumPackages`, `kmPrice`, `packagePrice`, `routePrice`, `minimumKm`, `minimumM3`, `m3Price`)
VALUES
(1, 0, 0.00, 0.00, NULL, 0, 0.00, 0),
(3, 0, 0.00, 3.05, NULL, 0, 0.00, 0),
(2, 60, 0.00, 0.00, NULL, 0, 5.00, 33);
UPDATE `vn`.`agency`
SET `supplierFk`=1
WHERE `id`=1;
@ -2503,3 +2512,11 @@ INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName` , `find`)
INSERT INTO `vn`.`docuwareConfig` (`url`)
VALUES
('https://verdnatura.docuware.cloud/docuware/platform');
INSERT INTO `vn`.`supplierAgencyTerm` (`agencyFk`, `supplierFk`, `minimumPackages`, `kmPrice`, `packagePrice`, `routePrice`, `minimumKm`, `minimumM3`, `m3Price`)
VALUES
(1, 1, 0, 0.00, 0.00, NULL, 0, 0.00, 23),
(2, 1, 60, 0.00, 0.00, NULL, 0, 5.00, 33),
(3, 2, 0, 15.00, 0.00, NULL, 0, 0.00, 0),
(4, 2, 0, 20.00, 0.00, NULL, 0, 0.00, 0),
(5, 442, 0, 0.00, 3.05, NULL, 0, 0.00, 0);

View File

@ -70,6 +70,7 @@
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked",
"Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*",
"Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*",
"This ticket is not an stowaway anymore": "The ticket id [{{ticketId}}]({{{ticketUrl}}}) is not an stowaway anymore",
"Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member",
"Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member",

View File

@ -137,6 +137,7 @@
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*",
"Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*",
"Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*",
"This ticket is not an stowaway anymore": "El ticket id [{{ticketId}}]({{{ticketUrl}}}) ha dejado de ser un polizón",
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}",
"ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
@ -220,6 +221,7 @@
"Can't transfer claimed sales": "No puedes transferir lineas reclamadas",
"You don't have privileges to create pay back": "No tienes permisos para crear un abono",
"The item is required": "El artículo es requerido",
"The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo",
"date in the future": "Fecha en el futuro",
"reference duplicated": "Referencia duplicada"
}

View File

@ -47,7 +47,7 @@ describe('Update Claim', () => {
expect(error.message).toEqual(`You don't have enough privileges to change that field`);
});
it(`should success to update the claim within privileges `, async() => {
it(`should success to update the claimState to 'canceled' and send a rocket message`, async() => {
const tx = await app.models.Claim.beginTransaction({});
try {
@ -55,13 +55,15 @@ describe('Update Claim', () => {
const newClaim = await app.models.Claim.create(originalData, options);
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
const canceledState = 4;
const claimManagerId = 72;
const ctx = {
req: {
accessToken: {
userId: claimManagerId
}
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
},
args: {
observation: 'valid observation',
@ -69,11 +71,56 @@ describe('Update Claim', () => {
hasToPickUp: false
}
};
ctx.req.__ = (value, params) => {
return params.nickname;
};
await app.models.Claim.updateClaim(ctx, newClaim.id, options);
let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
expect(updatedClaim.observation).toEqual(ctx.args.observation);
expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it(`should success to update the claimState to 'incomplete' and send a rocket message`, async() => {
const tx = await app.models.Claim.beginTransaction({});
try {
const options = {transaction: tx};
const newClaim = await app.models.Claim.create(originalData, options);
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
const incompleteState = 5;
const claimManagerId = 72;
const ctx = {
req: {
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
},
args: {
observation: 'valid observation',
claimStateFk: incompleteState,
hasToPickUp: false
}
};
ctx.req.__ = (value, params) => {
return params.nickname;
};
await app.models.Claim.updateClaim(ctx, newClaim.id, options);
let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
expect(updatedClaim.observation).toEqual(ctx.args.observation);
expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
await tx.rollback();
} catch (e) {

View File

@ -96,9 +96,12 @@ module.exports = Self => {
// When claimState has been changed
if (args.claimStateFk) {
const newState = await models.ClaimState.findById(args.claimStateFk, null, options);
if (newState.code == 'incomplete')
notifyStateChange(ctx, salesPerson.id, claim);
if (newState.hasToNotify) {
if (newState.code == 'incomplete')
notifyStateChange(ctx, salesPerson.id, claim, newState.code);
if (newState.code == 'canceled')
notifyStateChange(ctx, claim.workerFk, claim, newState.code);
}
}
if (tx) await tx.commit();
@ -125,11 +128,12 @@ module.exports = Self => {
return canUpdate;
}
async function notifyStateChange(ctx, workerId, claim) {
const origin = ctx.req.headers.origin;
async function notifyStateChange(ctx, workerId, claim, state) {
const models = Self.app.models;
const origin = ctx.req.headers.origin;
const $t = ctx.req.__; // $translate
const message = $t('Claim state has changed to incomplete', {
const message = $t(`Claim state has changed to ${state}`, {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary`

View File

@ -13,20 +13,24 @@
},
"properties": {
"id": {
"type": "Number",
"type": "number",
"id": true,
"description": "Identifier"
},
"code": {
"type": "String",
"type": "string",
"required": true
},
"description": {
"type": "String",
"type": "string",
"required": true
},
"priority": {
"type": "Number",
"type": "number",
"required": true
},
"hasToNotify": {
"type": "boolean",
"required": true
}
},

View File

@ -87,21 +87,21 @@ module.exports = Self => {
SUM(t.packages) packages,
r.m3,
r.kmEnd - r.kmStart kmTotal,
CAST(IFNULL(ate.routePrice,
(ate.kmPrice * (GREATEST(r.kmEnd - r.kmStart , ate.minimumKm))
+ GREATEST(r.m3 , ate.minimumM3) * ate.m3Price)
+ ate.packagePrice * SUM(t.packages) )
CAST(IFNULL(sat.routePrice,
(sat.kmPrice * (GREATEST(r.kmEnd - r.kmStart , sat.minimumKm))
+ GREATEST(r.m3 , sat.minimumM3) * sat.m3Price)
+ sat.packagePrice * SUM(t.packages) )
AS DECIMAL(10,2)) price,
r.invoiceInFk,
a.supplierFk,
sat.supplierFk,
s.name supplierName
FROM vn.route r
LEFT JOIN vn.agencyMode am ON r.agencyModeFk = am.id
LEFT JOIN vn.agency a ON am.agencyFk = a.id
LEFT JOIN vn.ticket t ON t.routeFk = r.id
LEFT JOIN vn.agencyTerm ate ON ate.agencyFk = a.id
LEFT JOIN vn.supplier s ON s.id = a.supplierFk
WHERE r.created > DATE_ADD(CURDATE(), INTERVAL -2 MONTH) AND a.supplierFk IS NOT NULL
LEFT JOIN vn.supplierAgencyTerm sat ON sat.agencyFk = a.id
LEFT JOIN vn.supplier s ON s.id = sat.supplierFk
WHERE r.created > DATE_ADD(CURDATE(), INTERVAL -2 MONTH) AND sat.supplierFk IS NOT NULL
GROUP BY r.id
) a`
);

View File

@ -18,7 +18,7 @@ describe('AgencyTerm filter()', () => {
const firstAgencyTerm = agencyTerms[0];
expect(firstAgencyTerm.routeFk).toEqual(1);
expect(agencyTerms.length).toEqual(3);
expect(agencyTerms.length).toEqual(5);
await tx.rollback();
} catch (e) {
@ -72,7 +72,7 @@ describe('AgencyTerm filter()', () => {
const results = await models.AgencyTerm.filter(ctx, options);
expect(results.length).toBe(3);
expect(results.length).toBe(5);
await tx.rollback();
} catch (e) {

View File

@ -0,0 +1,42 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('freeAgencies', {
description: 'Returns a list of agencies without a supplier assigned',
accepts: [{
arg: 'filter',
type: 'object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/freeAgencies`,
verb: 'GET'
}
});
Self.freeAgencies = async(filter, options) => {
const conn = Self.dataSource.connector;
const where = {'sat.supplierFk': null};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
filter = mergeFilters(filter, {where});
let stmt = new ParameterizedSQL(
`SELECT a.name, a.id
FROM agency a
LEFT JOIN supplierAgencyTerm sat ON sat.agencyFk = a.id`,
null, myOptions);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -11,6 +11,9 @@
"SupplierAccount": {
"dataSource": "vn"
},
"SupplierAgencyTerm": {
"dataSource": "vn"
},
"SupplierLog": {
"dataSource": "vn"
},

View File

@ -0,0 +1,9 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`The agency is already assigned to another autonomous`);
return err;
});
};

View File

@ -0,0 +1,46 @@
{
"name": "SupplierAgencyTerm",
"base": "VnModel",
"options": {
"mysql": {
"table": "supplierAgencyTerm"
}
},
"properties": {
"agencyFk": {
"type": "number",
"id": true
},
"supplierFk": {
"type": "number"
},
"minimumPackages": {
"type": "number"
},
"kmPrice": {
"type": "number"
},
"packagePrice": {
"type": "number"
},
"routePrice": {
"type": "number"
},
"minimumKm": {
"type": "number"
},
"minimumM3": {
"type": "number"
},
"m3Price": {
"type": "number"
}
},
"relations": {
"agency": {
"type": "belongsTo",
"model": "Agency",
"foreignKey": "agencyFk"
}
}
}

View File

@ -7,6 +7,7 @@ module.exports = Self => {
require('../methods/supplier/getSummary')(Self);
require('../methods/supplier/updateFiscalData')(Self);
require('../methods/supplier/consumption')(Self);
require('../methods/supplier/freeAgencies')(Self);
Self.validatesPresenceOf('name', {
message: 'The social name cannot be empty'

View File

@ -0,0 +1,74 @@
<vn-watcher
vn-id="watcher"
url="SupplierAgencyTerms"
primary-key="agencyFk"
data="$ctrl.supplierAgencyTerm"
insert-mode="true"
form="form">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Suppliers/freeAgencies"
data="$ctrl.agencies">
</vn-crud-model>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-autocomplete vn-one
label="Agency"
ng-model="$ctrl.supplierAgencyTerm.agencyFk"
data="$ctrl.agencies"
show-field="name"
value-field="id"
rule>
</vn-autocomplete>
<vn-input-number
type="number"
label="Minimum M3"
ng-model="$ctrl.supplierAgencyTerm.minimumM3"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
type="number"
label="Package Price"
ng-model="$ctrl.supplierAgencyTerm.packagePrice"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="Km Price"
ng-model="$ctrl.supplierAgencyTerm.kmPrice"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="M3 Price"
ng-model="$ctrl.supplierAgencyTerm.m3Price"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
type="number"
label="Route Price"
ng-model="$ctrl.supplierAgencyTerm.routePrice"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="Minimum Km"
ng-model="$ctrl.supplierAgencyTerm.minimumKm"
rule>
</vn-input-number>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button
label="Cancel"
ui-sref="supplier.card.agencyTerm.index">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,26 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.supplierAgencyTerm = {
supplierFk: this.$params.id
};
}
onSubmit() {
this.$.watcher.submit().then(res => {
this.$state.go('supplier.card.agencyTerm.index');
});
}
}
ngModule.vnComponent('vnSupplierAgencyTermCreate', {
template: require('./index.html'),
controller: Controller,
bindings: {
supplier: '<'
}
});

View File

@ -0,0 +1,28 @@
import './index';
import watcher from 'core/mocks/watcher';
describe('Supplier', () => {
describe('Component vnSupplierAddressCreate', () => {
let $scope;
let controller;
let $element;
beforeEach(ngModule('supplier'));
beforeEach(inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$scope.watcher = watcher;
$element = angular.element('<vn-supplier-agency-term-create></vn-supplier-agency-term-create>');
controller = $componentController('vnSupplierAgencyTermCreate', {$element, $scope});
}));
describe('onSubmit()', () => {
it(`should redirect to 'supplier.card.agencyTerm.index' state`, () => {
jest.spyOn(controller.$state, 'go');
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('supplier.card.agencyTerm.index');
});
});
});
});

View File

@ -0,0 +1,85 @@
<vn-crud-model
vn-id="model"
url="SupplierAgencyTerms"
link="{supplierFk: $ctrl.$params.id}"
primary-key="agencyFk"
filter="$ctrl.filter"
data="$ctrl.supplierAgencyTerms"
auto-load="true">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="$ctrl.supplierAgencyTerms"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-lg">
<vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="supplierAgencyTerm in $ctrl.supplierAgencyTerms">
<vn-textfield
disabled="true"
vn-id="agency"
label="Agency"
ng-model="supplierAgencyTerm.agency.name"
rule>
</vn-textfield>
<vn-input-number
type="number"
label="Minimum M3"
ng-model="supplierAgencyTerm.minimumM3"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="Package Price"
ng-model="supplierAgencyTerm.packagePrice"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="Km Price"
ng-model="supplierAgencyTerm.kmPrice"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="M3 Price"
ng-model="supplierAgencyTerm.m3Price"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="Route Price"
ng-model="supplierAgencyTerm.routePrice"
rule>
</vn-input-number>
<vn-input-number
type="number"
label="Minimum Km"
ng-model="supplierAgencyTerm.minimumKm"
rule>
</vn-input-number>
<vn-none>
<vn-icon-button
vn-tooltip="Remove row"
icon="delete"
ng-click="model.remove($index)"
tabindex="-1">
</vn-icon-button>
</vn-none>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
</vn-button-bar>
</form>
<vn-float-button
vn-bind="+"
fixed-bottom-right
vn-tooltip="New row"
ui-sref="supplier.card.agencyTerm.create"
icon="add"
label="Add">
</vn-float-button>

View File

@ -0,0 +1,36 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
include:
{relation: 'agency',
scope: {
fields: ['id', 'name']
}
}
};
}
add() {
this.$.model.insert({});
}
onSubmit() {
this.$.watcher.check();
this.$.model.save().then(() => {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
});
}
}
ngModule.vnComponent('vnSupplierAgencyTermIndex', {
template: require('./index.html'),
controller: Controller,
bindings: {
supplier: '<'
}
});

View File

@ -0,0 +1,37 @@
import './index';
import watcher from 'core/mocks/watcher';
import crudModel from 'core/mocks/crud-model';
describe('Supplier', () => {
describe('Component vnSupplierAddressCreate', () => {
let $scope;
let controller;
let $element;
beforeEach(ngModule('supplier'));
beforeEach(inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$scope.model = crudModel;
$scope.watcher = watcher;
$element = angular.element('<vn-supplier-agency-term-index></vn-supplier-agency-term-index>');
controller = $componentController('vnSupplierAgencyTermIndex', {$element, $scope});
}));
describe('onSubmit()', () => {
it('should make HTTP POST request to save values', () => {
jest.spyOn($scope.watcher, 'check');
jest.spyOn($scope.watcher, 'notifySaved');
jest.spyOn($scope.watcher, 'updateOriginalData');
jest.spyOn($scope.model, 'save');
controller.onSubmit();
expect($scope.model.save).toHaveBeenCalledWith();
expect($scope.watcher.updateOriginalData).toHaveBeenCalledWith();
expect($scope.watcher.check).toHaveBeenCalledWith();
expect($scope.watcher.notifySaved).toHaveBeenCalledWith();
});
});
});
});

View File

@ -0,0 +1,8 @@
Minimum M3: M3 minimos
Package Price: Precio bulto
Km Price: Precio Km
M3 Price: Precio M3
Route Price: Precio ruta
Minimum Km: Km minimos
Remove row: Eliminar fila
Add row: Añadir fila

View File

@ -18,3 +18,5 @@ import './billing-data';
import './address/index';
import './address/create';
import './address/edit';
import './agency-term/index';
import './agency-term/create';

View File

@ -15,6 +15,7 @@
{"state": "supplier.card.address.index", "icon": "icon-delivery"},
{"state": "supplier.card.account", "icon": "icon-account"},
{"state": "supplier.card.contact", "icon": "contact_phone"},
{"state": "supplier.card.agencyTerm.index", "icon": "contact_support"},
{"state": "supplier.card.log", "icon": "history"},
{"state": "supplier.card.consumption", "icon": "show_chart"}
]
@ -86,6 +87,30 @@
"supplier": "$ctrl.supplier"
}
},
{
"url": "/agency-term",
"state": "supplier.card.agencyTerm",
"component": "ui-view",
"abstract": true
},
{
"url": "/index",
"state": "supplier.card.agencyTerm.index",
"component": "vn-supplier-agency-term-index",
"description": "Autonomous",
"params": {
"supplier": "$ctrl.supplier"
}
},
{
"url": "/create",
"state": "supplier.card.agencyTerm.create",
"component": "vn-supplier-agency-term-create",
"description": "New autonomous",
"params": {
"supplier": "$ctrl.supplier"
}
},
{
"url": "/consumption?q",
"state": "supplier.card.consumption",

View File

@ -32,6 +32,12 @@
translate>
as PDF
</a>
<vn-item
ng-if="!$ctrl.hasDocuwareFile"
ng-click="$ctrl.showPdfDeliveryNote('withoutPrices')"
translate>
as PDF without prices
</vn-item>
<vn-item
ng-click="$ctrl.showCsvDeliveryNote()"
translate>

View File

@ -2,6 +2,7 @@ Show Delivery Note...: Ver albarán...
Send Delivery Note...: Enviar albarán...
as PDF: como PDF
as CSV: como CSV
as PDF without prices: como PDF sin precios
Send PDF: Enviar PDF
Send CSV: Enviar CSV
Send CSV Delivery Note: Enviar albarán en CSV

View File

@ -1,4 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethodCtx('holidays', {
@ -33,7 +34,8 @@ module.exports = Self => {
Self.holidays = async(ctx, id, options) => {
const models = Self.app.models;
const args = ctx.args;
const conn = Self.dataSource.connector;
const stmts = [];
const myOptions = {};
if (typeof options == 'object')
@ -56,42 +58,6 @@ module.exports = Self => {
ended.setHours(23, 59, 59, 59);
const filter = {
include: [{
relation: 'holidays',
scope: {
where: {year: args.year}
}
},
{
relation: 'absences',
scope: {
include: {
relation: 'absenceType',
},
where: {
dated: {between: [started, ended]}
}
}
},
{
relation: 'workCenter',
scope: {
include: {
relation: 'holidays',
scope: {
include: [{
relation: 'detail'
},
{
relation: 'type'
}],
where: {
dated: {between: [started, ended]}
}
}
}
}
}],
where: {
and: [
{workerFk: id},
@ -107,76 +73,32 @@ module.exports = Self => {
}
};
if (args.businessFk)
filter.where.and.push({businessFk: args.businessFk});
const contracts = await models.WorkerLabour.find(filter, myOptions);
let [firstContract] = contracts;
let payedHolidays;
const payedHolidays = firstContract.payedHolidays;
if (firstContract.payedHolidays)
payedHolidays = firstContract.payedHolidays;
else payedHolidays = 0;
let queryIndex;
const year = started.getFullYear();
let totalHolidays = 0;
let holidaysEnjoyed = 0;
for (let contract of contracts) {
const contractStarted = contract.started;
contractStarted.setHours(0, 0, 0, 0);
const contractEnded = contract.ended;
if (contractEnded)
contractEnded.setHours(23, 59, 59, 59);
let startedTime;
if (contractStarted < started)
startedTime = started.getTime();
else startedTime = contractStarted.getTime();
let endedTime;
if (!contractEnded || (contractEnded && contractEnded > ended))
endedTime = ended.getTime();
else endedTime = contractEnded.getTime();
const dayTimestamp = 1000 * 60 * 60 * 24;
// Get number of worked days between dates
let workedDays = Math.floor((endedTime - startedTime) / dayTimestamp);
workedDays += 1; // 1 day inclusion
// Calculates absences
let entitlementRate = 0;
for (let absence of contract.absences()) {
const absenceType = absence.absenceType();
const isHoliday = absenceType.code === 'holiday';
const isHalfHoliday = absenceType.code === 'halfHoliday';
if (isHoliday) holidaysEnjoyed += 1;
if (isHalfHoliday) holidaysEnjoyed += 0.5;
entitlementRate += absenceType.holidayEntitlementRate;
}
workedDays -= entitlementRate;
// Max holidays for the selected year
const maxHolidays = contract.holidays() && contract.holidays().days;
if (workedDays < daysInYear())
totalHolidays += Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2;
else totalHolidays = maxHolidays;
if (args.businessFk) {
stmts.push(new ParameterizedSQL('CALL vn.workerCalendar_calculateBusiness(?,?)', [year, args.businessFk]));
queryIndex = stmts.push('SELECT * FROM tmp.workerCalendarCalculateBusiness') - 1;
stmts.push('DROP TEMPORARY TABLE tmp.workerCalendarCalculateBusiness');
} else {
stmts.push(new ParameterizedSQL('CALL vn.workerCalendar_calculateYear(?,?)', [year, id]));
queryIndex = stmts.push('SELECT * FROM tmp.workerCalendarCalculateYear') - 1;
stmts.push('DROP TEMPORARY TABLE tmp.workerCalendarCalculateYear');
}
function daysInYear() {
const year = started.getFullYear();
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
const [holidays] = result[queryIndex];
return isLeapYear(year) ? 366 : 365;
}
return {totalHolidays, holidaysEnjoyed, payedHolidays};
const totalHolidays = holidays.days;
const holidaysEnjoyed = holidays.daysEnjoyed;
const totalHours = holidays.hours;
const hoursEnjoyed = holidays.hoursEnjoyed;
return {totalHolidays, holidaysEnjoyed, totalHours, hoursEnjoyed, payedHolidays};
};
function isLeapYear(year) {
return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
}
};

View File

@ -25,20 +25,28 @@
<div class="totalBox vn-mb-sm" style="text-align: center;">
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed}}
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
</div>
<div>
{{'Paid holidays' | translate}} {{$ctrl.contractHolidays.payedHolidays}} {{'days' | translate}}
{{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.contractHolidays.totalHours || 0}} {{'hours' | translate}}
</div>
<div>
{{'Paid holidays' | translate}} {{$ctrl.contractHolidays.payedHolidays || 0}} {{'days' | translate}}
</div>
</div>
<div class="totalBox" style="text-align: center;">
<h6>{{'Year' | translate}} {{$ctrl.year}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed}}
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
</div>
<div>
{{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.yearHolidays.totalHours || 0}} {{'hours' | translate}}
</div>
</div>
<div class="vn-pt-md">

View File

@ -2,9 +2,11 @@ Calendar: Calendario
Contract: Contrato
Festive: Festivo
Used: Utilizados
Spent: Utilizadas
Year: Año
of: de
days: días
hours: horas
Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha
To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia
You can just add absences within the current year: Solo puedes añadir ausencias dentro del año actual

View File

@ -16,5 +16,12 @@
"type": "string",
"required": false
}
},
"relations": {
"supplierAgencyTerm": {
"type": "hasOne",
"model": "SupplierAgencyTerm",
"foreignKey": "agencyFk"
}
}
}

View File

@ -77,10 +77,10 @@
<th width="5%">{{$t('reference')}}</th>
<th class="number">{{$t('quantity')}}</th>
<th width="50%">{{$t('concept')}}</th>
<th class="number">{{$t('price')}}</th>
<th class="centered" width="5%">{{$t('discount')}}</th>
<th class="centered">{{$t('vat')}}</th>
<th class="number">{{$t('amount')}}</th>
<th class="number" v-if="showPrices">{{$t('price')}}</th>
<th class="centered" width="5%" v-if="showPrices">{{$t('discount')}}</th>
<th class="centered" v-if="showPrices">{{$t('vat')}}</th>
<th class="number" v-if="showPrices">{{$t('amount')}}</th>
</tr>
</thead>
<tbody v-for="sale in sales" class="no-page-break">
@ -88,10 +88,10 @@
<td width="5%">{{sale.itemFk | zerofill('000000')}}</td>
<td class="number">{{sale.quantity}}</td>
<td width="50%">{{sale.concept}}</td>
<td class="number">{{sale.price | currency('EUR', $i18n.locale)}}</td>
<td class="centered" width="5%">{{(sale.discount / 100) | percentage}}</td>
<td class="centered">{{sale.vatType}}</td>
<td class="number">{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', $i18n.locale)}}</td>
<td class="number" v-if="showPrices">{{sale.price | currency('EUR', $i18n.locale)}}</td>
<td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) | percentage}}</td>
<td class="centered" v-if="showPrices">{{sale.vatType}}</td>
<td class="number" v-if="showPrices">{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', $i18n.locale)}}</td>
</tr>
<tr class="description font light-gray">
<td colspan="7">
@ -107,7 +107,7 @@
</td>
</tr>
</tbody>
<tfoot>
<tfoot v-if="showPrices">
<tr>
<td colspan="6" class="font bold">
<span class="pull-right">{{$t('subtotal')}}</span>
@ -181,7 +181,7 @@
</div>
<!-- End of packages block -->
</div>
<div class="columns vn-mt-xl">
<div class="columns vn-mt-xl" v-if="showPrices">
<!-- Taxes block -->
<div id="taxes" class="size50 pull-right no-page-break" v-if="taxes">
<table class="column-oriented">

View File

@ -45,6 +45,9 @@ module.exports = {
return total;
},
showPrices() {
return this.type != 'withoutPrices';
},
footerType() {
const translatedType = this.$t(this.type);
return `${translatedType} ${this.ticketId}`;

View File

@ -1,6 +1,7 @@
reportName: delivery-note
deliveryNote: Delivery note
proforma: Proforma
withoutPrices: Delivery note
clientId: Client
deliveryAddress: Delivery address
fiscalData: Fiscal data

View File

@ -1,6 +1,7 @@
reportName: albaran
deliveryNote: Albarán
proforma: Proforma
withoutPrices: Albarán
clientId: Cliente
deliveryAddress: Dirección de entrega
fiscalData: Datos fiscales

View File

@ -1,6 +1,7 @@
reportName: bon-de-livraison
deliveryNote: Bon de livraison
proforma: Proforma
withoutPrices: Bon de livraison
clientId: Client
deliveryAddress: Adresse de livraison
fiscalData: Coordonnées

View File

@ -1,6 +1,7 @@
reportName: nota-de-entrega
deliveryNote: Nota de Entrega
proforma: Proforma
withoutPrices: Nota de Entrega
clientId: Cliente
deliveryAddress: Morada de Entrega
fiscalData: Dados Fiscais