Merge branch 'dev' into 2510-client-address-create-test
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Javi Gallego 2020-10-30 08:40:47 +01:00
commit 3fdc9c315d
79 changed files with 1186 additions and 175 deletions

View File

@ -23,21 +23,23 @@ module.exports = Self => {
}
});
Self.sendCheckingPresence = async(ctx, workerId, message) => {
if (!workerId) return false;
Self.sendCheckingPresence = async(ctx, recipientId, message) => {
if (!recipientId) return false;
const models = Self.app.models;
const account = await models.Account.findById(workerId);
const account = await models.Account.findById(recipientId);
const userId = ctx.req.accessToken.userId;
if (recipientId == userId) return false;
if (!account)
throw new Error(`Could not send message "${message}" to worker id ${workerId} from user ${userId}`);
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
const query = `SELECT worker_isWorking(?) isWorking`;
const [result] = await Self.rawSql(query, [workerId]);
const [result] = await Self.rawSql(query, [recipientId]);
if (!result.isWorking) {
const workerDepartment = await models.WorkerDepartment.findById(workerId, {
const workerDepartment = await models.WorkerDepartment.findById(recipientId, {
include: {
relation: 'department'
}
@ -46,7 +48,7 @@ module.exports = Self => {
const channelName = department && department.chatName;
if (channelName)
return Self.send(ctx, `#${channelName}`, `@${account.name} => ${message}`);
return Self.send(ctx, `#${channelName}`, `@${account.name} ${message}`);
}
return Self.send(ctx, `@${account.name}`, message);

View File

@ -38,7 +38,7 @@ describe('chat sendCheckingPresence()', () => {
expect(response.statusCode).toEqual(200);
expect(response.message).toEqual('Fake notification sent');
expect(chatModel.send).toHaveBeenCalledWith(ctx, '#cooler', '@HankPym => I changed something');
expect(chatModel.send).toHaveBeenCalledWith(ctx, '#cooler', '@HankPym I changed something');
// restores
await department.updateAttribute('chatName', null);

View File

@ -1,3 +1,8 @@
UPDATE `salix`.`ACL` SET `principalId` = 'deliveryBoss' WHERE (`id` = '194');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '97');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '100');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '103');
UPDATE `salix`.`ACL` SET `principalId` = 'claimManager' WHERE (`id` = '202');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Town', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Province', '*', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss');

View File

@ -0,0 +1,3 @@
UPDATE `vn`.`claimState` SET `roleFk` = '72' WHERE (`id` = '3');
UPDATE `vn`.`claimState` SET `roleFk` = '72' WHERE (`id` = '4');
UPDATE `vn`.`claimState` SET `roleFk` = '72' WHERE (`id` = '5');

File diff suppressed because one or more lines are too long

View File

@ -1209,11 +1209,11 @@ INSERT INTO `vn`.`annualAverageInvoiced`(`clientFk`, `invoiced`)
(104, 500),
(105, 5000);
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `phone`, `payDay`)
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`)
VALUES
(1, 'Plants SL', 'Plants nick', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 123456789, 15),
(2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2, 987654321, 10),
(442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 987123654, 15);
(1, 'Plants SL', 'Plants nick', 4000000001, 1, '06089160W', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15),
(2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2, 10),
(442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15);
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
VALUES
@ -1535,9 +1535,9 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`)
VALUES
( 1, 'pending', 'Pendiente', 1, 1),
( 2, 'managed', 'Gestionado', 1, 5),
( 3, 'resolved', 'Resuelto', 21, 7),
( 4, 'canceled', 'Anulado', 1, 6),
( 5, 'disputed', 'Cuestionado', 21, 3),
( 3, 'resolved', 'Resuelto', 72, 7),
( 4, 'canceled', 'Anulado', 72, 6),
( 5, 'disputed', 'Cuestionado', 72, 3),
( 6, 'mana', 'Mana', 1, 4),
( 7, 'inProgress', 'En Curso', 1, 2);

View File

@ -399,16 +399,16 @@ let actions = {
pickDate: async function(selector, date) {
date = date || new Date();
const tzoffset = date.getTimezoneOffset() * 60000;
const localIso = (new Date(date.getTime() - tzoffset))
.toISOString();
const timeZoneOffset = date.getTimezoneOffset() * 60000;
const localDate = (new Date(date.getTime() - timeZoneOffset))
.toISOString().substr(0, 10);
await this.wait(selector);
await this.evaluate((selector, localIso) => {
await this.evaluate((selector, localDate) => {
let input = document.querySelector(selector).$ctrl.input;
input.value = localIso.substr(0, 10);
input.value = localDate;
input.dispatchEvent(new Event('change'));
}, selector, localIso);
}, selector, localDate);
},
pickTime: async function(selector, time) {

View File

@ -202,6 +202,21 @@ export default {
firstDocWorker: 'vn-client-dms-index vn-td:nth-child(8) > span',
firstDocWorkerDescriptor: '.vn-popover.shown vn-worker-descriptor'
},
clientCreditInsurance: {
addNewContract: 'vn-client-credit-insurance-index vn-float-button[ui-sref="client.card.creditInsurance.create"]',
newCreditClassification: 'vn-client-credit-insurance-create vn-input-number[ng-model="$ctrl.creditClassification.credit"]',
newInsuranceCredit: 'vn-client-credit-insurance-insurance-create vn-input-number[ng-model="$ctrl.insurance.credit"]',
newClassificationGrade: 'vn-client-credit-insurance-create vn-input-number[ng-model="$ctrl.creditClassification.grade"]',
newInsuranceGrade: 'vn-client-credit-insurance-insurance-create vn-input-number[ng-model="$ctrl.insurance.grade"]',
newClassificationStartingDate: 'vn-client-credit-insurance-create vn-date-picker[ng-model="$ctrl.creditClassification.started"]',
newInsuranceStartingDate: 'vn-client-credit-insurance-insurance-create vn-date-picker[ng-model="$ctrl.insurance.created"]',
endCurrentContract: 'vn-client-credit-insurance-index vn-icon-button[icon="lock"]',
firstContratViewCreditButton: 'vn-client-credit-insurance-index vn-card > vn-horizontal:nth-child(1) vn-icon-button[icon="desktop_windows"]',
addNewCredit: 'vn-client-credit-insurance-insurance-index vn-float-button vn-icon[icon="add"]',
saveNewContract: 'vn-client-credit-insurance-create vn-submit',
saveNewInsuranceCredit: 'vn-client-credit-insurance-insurance-create button[type="submit"]',
anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr',
},
clientContacts: {
addContactButton: 'vn-client-contact vn-icon[icon="add_circle"]',
name: 'vn-client-contact vn-textfield[ng-model="contact.name"]',
@ -886,5 +901,16 @@ export default {
newEntryTravel: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.travelFk"]',
newEntryCompany: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
saveNewEntry: 'vn-entry-create button[type="submit"]'
},
supplierSummary: {
header: 'vn-supplier-summary > vn-card > h5',
basicDataId: 'vn-supplier-summary vn-label-value[label="Id"]',
fiscalAddressTaxNumber: 'vn-supplier-summary vn-label-value[label="Tax number"]',
billingDataPayMethod: 'vn-supplier-summary vn-label-value[label="Pay method"]'
},
supplierDescriptor: {
alias: 'vn-supplier-descriptor vn-label-value[label="Alias"]',
clientButton: 'vn-supplier-descriptor vn-icon[icon="person"]',
entriesButton: 'vn-supplier-descriptor vn-icon[icon="icon-entry"]',
}
};

View File

@ -0,0 +1,93 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Client credit insurance path', () => {
let browser;
let page;
let previousMonth = new Date();
previousMonth.setMonth(previousMonth.getMonth() - 1);
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('insurance', 'client');
await page.accessToSearchResult('Tony Stark');
await page.accessToSection('client.card.creditInsurance.index');
});
afterAll(async() => {
await browser.close();
});
it('should open the create a new credit contract form', async() => {
await page.waitToClick(selectors.clientCreditInsurance.addNewContract);
await page.waitForState('client.card.creditInsurance.create');
});
it('should create a new credit contract', async() => {
await page.write(selectors.clientCreditInsurance.newCreditClassification, '1000');
await page.write(selectors.clientCreditInsurance.newClassificationGrade, '1');
await page.pickDate(selectors.clientCreditInsurance.newClassificationStartingDate, previousMonth);
await page.waitToClick(selectors.clientCreditInsurance.saveNewContract);
await page.waitForState('client.card.creditInsurance.index');
});
it(`should verify the addNewContract button is not present since there's an active contract`, async() => {
await page.waitForSelector(selectors.clientCreditInsurance.addNewContract, {hidden: true});
});
it(`should click the view credits button`, async() => {
await page.waitToClick(selectors.clientCreditInsurance.firstContratViewCreditButton);
await page.waitForState('client.card.creditInsurance.insurance.index');
});
it('should click the add new credit button which opens the new credit form', async() => {
await page.waitToClick(selectors.clientCreditInsurance.addNewCredit);
await page.waitForState('client.card.creditInsurance.insurance.create');
});
it('should fill the form but provide no grade to the new credit hence fail', async() => {
await page.write(selectors.clientCreditInsurance.newInsuranceCredit, '2000');
await page.pickDate(selectors.clientCreditInsurance.newInsuranceStartingDate, previousMonth);
await page.waitToClick(selectors.clientCreditInsurance.saveNewInsuranceCredit);
const message = await page.waitForSnackbar();
expect(message.text).toEqual('The grade must be similar to the last one');
});
it('should provide a correct grade and succesfully save a new credit', async() => {
await page.write(selectors.clientCreditInsurance.newInsuranceGrade, '999');
await page.waitToClick(selectors.clientCreditInsurance.saveNewInsuranceCredit);
const message = await page.waitForSnackbar();
expect(message.text).toEqual('Data saved!');
});
it('should be redirected to the credit index', async() => {
await page.waitForState('client.card.creditInsurance.insurance.index');
});
it('should check the amount of credits is the expected', async() => {
const result = await page.countElement(selectors.clientCreditInsurance.anyCreditInsuranceLine);
expect(result).toEqual(2);
});
it('should navigate to the credit insurance section', async() => {
await page.waitToClick(`vn-left-menu li > a[ui-sref="client.card.creditInsurance.index"]`);
await page.waitForState('client.card.creditInsurance.index');
});
it('should bring the current contract to an end', async() => {
await page.waitToClick(selectors.clientCreditInsurance.endCurrentContract);
await page.waitToClick(selectors.globalItems.acceptButton);
});
it(`should verify the addNewContract button is now present since there's no active contract`, async() => {
await page.waitForSelector(selectors.clientCreditInsurance.addNewContract, {visible: true});
});
it(`should verify the endCurrentContract button is not present since there's no active contract`, async() => {
await page.waitForSelector(selectors.clientCreditInsurance.endCurrentContract, {hidden: true});
});
});

View File

@ -14,8 +14,8 @@ describe('Claim edit basic data path', () => {
await browser.close();
});
it(`should log in as salesAssistant then reach basic data of the target claim`, async() => {
await page.loginAndModule('salesAssistant', 'claim');
it(`should log in as claimManager then reach basic data of the target claim`, async() => {
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('1');
await page.accessToSection('claim.card.basicData');
});
@ -30,7 +30,7 @@ describe('Claim edit basic data path', () => {
expect(message.type).toBe('success');
});
it(`should have been redirected to the next section of claims as the role is salesAssistant`, async() => {
it(`should have been redirected to the next section of claims as the role is claimManager`, async() => {
await page.waitForState('claim.card.detail');
});

View File

@ -8,7 +8,7 @@ describe('Claim development', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesAssistant', 'claim');
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('1');
await page.accessToSection('claim.card.development');
});
@ -31,7 +31,7 @@ describe('Claim development', () => {
expect(message.type).toBe('success');
});
it(`should redirect to the next section of claims as the role is salesAssistant`, async() => {
it(`should redirect to the next section of claims as the role is claimManager`, async() => {
await page.waitForState('claim.card.action');
});

View File

@ -8,7 +8,7 @@ describe('Claim action path', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'claim');
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('2');
await page.accessToSection('claim.card.action');
});

View File

@ -26,8 +26,8 @@ describe('claim Descriptor path', () => {
await page.waitForSelector(selectors.claimDescriptor.moreMenuDeleteClaim, {hidden: true});
});
it(`should log in as salesAssistant and navigate to the target claim`, async() => {
await page.loginAndModule('salesAssistant', 'claim');
it(`should log in as claimManager and navigate to the target claim`, async() => {
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary');
});

View File

@ -0,0 +1,84 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Supplier descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('administrative', 'supplier');
await page.accessToSearchResult('1');
});
afterAll(async() => {
await browser.close();
});
// summary
it('should reach the second entry summary section', async() => {
await page.waitForState('supplier.card.summary');
});
it(`should confirm there's data on the summary header`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.header, 'innerText');
expect(result).toContain('Plants SL - 1');
});
it(`should confirm there's data on the summary basic data`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.basicDataId, 'innerText');
expect(result).toContain('Id 1');
});
it(`should confirm there's data on the summary fiscal address`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.fiscalAddressTaxNumber, 'innerText');
expect(result).toContain('Tax number 06089160W');
});
it(`should confirm there's data on the summary fiscal pay method`, async() => {
const result = await page.waitToGetProperty(selectors.supplierSummary.billingDataPayMethod, 'innerText');
expect(result).toContain('Pay method PayMethod one');
});
// descriptor
it(`should confirm there's data on the descriptor`, async() => {
const result = await page.waitToGetProperty(selectors.supplierDescriptor.alias, 'innerText');
expect(result).toContain('Plants nick');
});
it(`should navigate to the supplier's client summary using the icon client button`, async() => {
await page.waitToClick(selectors.supplierDescriptor.clientButton);
await page.waitForState('client.card.summary');
});
it(`should navigate back to the supplier`, async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.waitForState('home');
await page.selectModule('supplier');
await page.accessToSearchResult('1');
await page.waitForState('supplier.card.summary');
});
it(`should navigate to the supplier's entries`, async() => {
await page.waitToClick(selectors.supplierDescriptor.entriesButton);
await page.waitForState('entry.index');
});
it(`should navigate back to suppliers but a different one this time`, async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.waitForState('home');
await page.selectModule('supplier');
await page.accessToSearchResult('2');
await page.waitForState('supplier.card.summary');
});
it(`should check the client button isn't present since this supplier should not be a client`, async() => {
await page.waitForSelector(selectors.supplierDescriptor.clientButton, {hidden: true});
});
});

View File

@ -162,6 +162,9 @@ function e2eSingleRun() {
`${__dirname}/e2e/paths/08*/*[sS]pec.js`,
`${__dirname}/e2e/paths/09*/*[sS]pec.js`,
`${__dirname}/e2e/paths/10*/*[sS]pec.js`,
`${__dirname}/e2e/paths/11*/*[sS]pec.js`,
`${__dirname}/e2e/paths/12*/*[sS]pec.js`,
`${__dirname}/e2e/paths/13*/*[sS]pec.js`,
`${__dirname}/e2e/paths/**/*[sS]pec.js`
];

View File

@ -72,5 +72,14 @@
"Deleted absence": "The worker <strong>{{author}}</strong> has deleted an absence of type '{{absenceType}}' to <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> for day {{dated}}.",
"I have deleted the ticket id": "I have deleted the ticket id [{{id}}]({{{url}}})",
"I have restored the ticket id": "I have restored the ticket id [{{id}}]({{{url}}})",
"Changed this data from the ticket": "I have changed the data from the ticket [{{ticketId}}]({{{ticketUrl}}}): ```{{{changes}}}```"
"Changed this data from the ticket": "I have changed the data from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"The grade must be similar to the last one": "The grade must be similar to the last one",
"agencyModeFk": "Agency",
"clientFk": "Client",
"zoneFk": "Zone",
"warehouseFk": "Warehouse",
"shipped": "Shipped",
"landed": "Landed",
"addressFk": "Address",
"companyFk": "Company"
}

View File

@ -121,11 +121,11 @@
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
"MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} (#{{itemId}}) para el ticket id [{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} (#{{clientId}})]({{{url}}}) a *{{credit}} €*",
"MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} (#{{clientId}})]({{{url}}})",
"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}}*",
"MESSAGE_BOUGHT_UNITS": "Se ha comprado {{quantity}} unidades de {{concept}} ({{itemId}}) para el ticket id [{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
"MESSAGE_CHANGED_PAYMETHOD": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"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}}*",
"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",
@ -140,7 +140,6 @@
"Role already assigned": "Role already assigned",
"Invalid role name": "Invalid role name",
"Role name must be written in camelCase": "Role name must be written in camelCase",
"can't be set": "can't be set",
"Email already exists": "Email already exists",
"User already exists": "User already exists",
"Absence change notification on the labour calendar": "Notificacion de cambio de ausencia en el calendario laboral",
@ -149,5 +148,13 @@
"I have deleted the ticket id": "He eliminado el ticket id [{{id}}]({{{url}}})",
"I have restored the ticket id": "He restaurado el ticket id [{{id}}]({{{url}}})",
"You can only restore a ticket within the first hour after deletion": "Únicamente puedes restaurar el ticket dentro de la primera hora después de su eliminación",
"Changed this data from the ticket": "He cambiado estos datos del ticket [{{ticketId}}]({{{ticketUrl}}}): ```{{{changes}}}```"
"Changed this data from the ticket": "He cambiado estos datos del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"agencyModeFk": "Agencia",
"clientFk": "Cliente",
"zoneFk": "Zona",
"warehouseFk": "Almacén",
"shipped": "F. envío",
"landed": "F. entrega",
"addressFk": "Consignatario",
"companyFk": "Empresa"
}

83
loopback/util/log.js Normal file
View File

@ -0,0 +1,83 @@
/**
* Translates to a readable values
* @param {Object} instance - The model or context instance
* @param {Object} changes - Object containing changes
*/
exports.translateValues = async(instance, changes) => {
const models = instance.app.models;
function getRelation(instance, property) {
const relations = instance.definition.settings.relations;
for (let relationName in relations) {
const relation = relations[relationName];
if (relation.foreignKey == property)
return relation;
}
return;
}
function getValue(rawData) {
const row = JSON.parse(JSON.stringify(rawData));
for (column in row)
return row[column];
}
function formatDate(date) {
return new Intl.DateTimeFormat('es', {
year: '2-digit',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).format(date);
}
const properties = Object.assign({}, changes);
for (let property in properties) {
const relation = getRelation(instance, property);
const value = properties[property];
let finalValue = value;
if (relation) {
const model = relation.model;
const row = await models[model].findById(value, {
fields: ['alias', 'name', 'code', 'description']
});
const newValue = getValue(row);
if (newValue) finalValue = newValue;
}
if (finalValue instanceof Date)
finalValue = formatDate(finalValue);
properties[property] = finalValue;
}
return properties;
};
/**
* Returns the changes between two objects
* @param {Object} original - Original object
* @param {Object} changes - New changes
* @return {Object} Old and new values
*/
exports.getChanges = (original, changes) => {
const oldChanges = {};
const newChanges = {};
for (let property in changes) {
if (changes[property] != original[property]) {
newChanges[property] = changes[property];
if (original[property] != undefined)
oldChanges[property] = original[property];
}
}
return {
old: oldChanges,
new: newChanges
};
};

View File

@ -2,13 +2,13 @@ const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('claimBeginning', () => {
const salesAssistantId = 21;
const claimManagerId = 72;
let ticket;
let refundTicketSales;
let salesInsertedInClaimEnd;
const activeCtx = {
accessToken: {userId: salesAssistantId},
accessToken: {userId: claimManagerId},
};
const ctx = {req: activeCtx};

View File

@ -1,12 +1,12 @@
module.exports = Self => {
Self.remoteMethod('getSummary', {
description: 'Updates the item taxes',
description: 'Return the claim summary',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
description: 'The claim id',
http: {source: 'path'}
}],
returns: {

View File

@ -21,10 +21,8 @@ module.exports = Self => {
Self.isEditable = async(ctx, id) => {
const userId = ctx.req.accessToken.userId;
const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant');
let claim = await Self.app.models.Claim.findById(id, {
const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager');
const claim = await Self.app.models.Claim.findById(id, {
fields: ['claimStateFk'],
include: [{
relation: 'claimState'
@ -33,7 +31,7 @@ module.exports = Self => {
const isClaimResolved = claim && claim.claimState().code == 'resolved';
if (!claim || (isClaimResolved && !isSalesAssistant))
if (!claim || (isClaimResolved && !isClaimManager))
return false;
return true;

View File

@ -2,9 +2,9 @@ const app = require('vn-loopback/server/server');
describe('claim isEditable()', () => {
const salesPerdonId = 18;
const salesAssistantId = 21;
const claimManagerId = 72;
it('should return false if the given claim does not exist', async() => {
let ctx = {req: {accessToken: {userId: salesAssistantId}}};
let ctx = {req: {accessToken: {userId: claimManagerId}}};
let result = await app.models.Claim.isEditable(ctx, 99999);
expect(result).toEqual(false);
@ -17,14 +17,14 @@ describe('claim isEditable()', () => {
expect(result).toEqual(false);
});
it('should be able to edit a resolved claim for a salesAssistant', async() => {
let ctx = {req: {accessToken: {userId: salesAssistantId}}};
it('should be able to edit a resolved claim for a claimManager', async() => {
let ctx = {req: {accessToken: {userId: claimManagerId}}};
let result = await app.models.Claim.isEditable(ctx, 4);
expect(result).toEqual(true);
});
it('should be able to edit a claim for a salesAssistant', async() => {
it('should be able to edit a claim for a claimManager', async() => {
let ctx = {req: {accessToken: {userId: salesPerdonId}}};
let result = await app.models.Claim.isEditable(ctx, 1);

View File

@ -42,21 +42,21 @@ describe('Update Claim', () => {
it(`should success to update the claim within privileges `, async() => {
let newClaim = await app.models.Claim.create(originalData);
const correctState = 4;
const salesPersonId = 18;
const canceledState = 4;
const claimManagerId = 72;
const ctx = {
req: {
accessToken: {
userId: salesPersonId
userId: claimManagerId
}
},
args: {
observation: 'valid observation',
claimStateFk: correctState,
claimStateFk: canceledState,
hasToPickUp: false
}
};
await app.models.Claim.updateClaim(ctx, newClaim.id,);
await app.models.Claim.updateClaim(ctx, newClaim.id);
let updatedClaim = await app.models.Claim.findById(newClaim.id);
@ -66,15 +66,15 @@ describe('Update Claim', () => {
await app.models.Claim.destroyById(newClaim.id);
});
it('should change some sensible fields as salesAssistant', async() => {
it('should change some sensible fields as claimManager', async() => {
let newClaim = await app.models.Claim.create(originalData);
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
const salesAssistantId = 21;
const claimManagerId = 72;
const ctx = {
req: {
accessToken: {userId: salesAssistantId},
accessToken: {userId: claimManagerId},
headers: {origin: 'http://localhost'}
},
args: {

View File

@ -60,9 +60,9 @@ module.exports = Self => {
if (args.claimStateFk) {
const canUpdate = await canChangeState(ctx, claim.claimStateFk);
const hasRights = await canChangeState(ctx, args.claimStateFk);
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant');
const isClaimManager = await models.Account.hasRole(userId, 'claimManager');
if (!canUpdate || !hasRights || changedHasToPickUp && !isSalesAssistant)
if (!canUpdate || !hasRights || changedHasToPickUp && !isClaimManager)
throw new UserError(`You don't have enough privileges to change that field`);
}
delete args.ctx;

View File

@ -56,7 +56,8 @@
class="vn-mr-md"
label="Pick up"
ng-model="$ctrl.claim.hasToPickUp"
vn-acl="salesAssistant">
vn-acl="claimManager"
info="When checked will notify to the salesPerson">
</vn-check>
</vn-horizontal>
</vn-card>

View File

@ -5,7 +5,7 @@ import './style.scss';
class Controller extends Section {
onSubmit() {
this.$.watcher.submit().then(() => {
if (this.aclService.hasAny(['salesAssistant']))
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.detail');
});
}

View File

@ -4,4 +4,5 @@ Is paid with mana: Cargado al maná
Responsability: Responsabilidad
Company: Empresa
Sales/Client: Comercial/Cliente
Pick up: Recoger
Pick up: Recoger
When checked will notify a pickup to the salesPerson: Cuando se marque enviará una notificación de recogida al comercial

View File

@ -13,7 +13,7 @@
Send Pickup order
</vn-item>
<vn-item
vn-acl="salesAssistant"
vn-acl="claimManager"
vn-acl-action="remove"
ng-click="confirmDeleteClaim.show()"
name="deleteClaim"

View File

@ -77,7 +77,7 @@ class Controller extends Section {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
if (this.aclService.hasAny(['salesAssistant']))
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.development');
});
}
@ -133,7 +133,7 @@ class Controller extends Section {
showEditPopover(event, saleClaimed) {
if (this.isEditable) {
if (!this.aclService.hasAny(['salesAssistant']))
if (!this.aclService.hasAny(['claimManager']))
return this.vnApp.showError(this.$t('Insuficient permisos'));
this.saleClaimed = saleClaimed;

View File

@ -9,7 +9,7 @@ class Controller extends Section {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
if (this.aclService.hasAny(['salesAssistant']))
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.action');
});
}

View File

@ -70,7 +70,7 @@
"params": {
"claim": "$ctrl.claim"
},
"acl": ["salesAssistant"]
"acl": ["claimManager"]
}, {
"url": "/action",
"state": "claim.card.action",
@ -79,7 +79,7 @@
"params": {
"claim": "$ctrl.claim"
},
"acl": ["salesAssistant"]
"acl": ["claimManager"]
}, {
"url": "/photos",
"state": "claim.card.photos",

View File

@ -42,7 +42,7 @@
max="5"
min="1"
step="1"
vn-acl="salesAssistant">
vn-acl="claimManager">
</vn-range>
</vn-one>
<vn-auto>

View File

@ -1,29 +1,29 @@
{
"name": "PayMethod",
"base": "VnModel",
"options": {
"mysql": {
"table": "payMethod"
"name": "PayMethod",
"base": "VnModel",
"options": {
"mysql": {
"table": "payMethod"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "string",
"required": true
},
"graceDays": {
"type": "string"
},
"outstandingDebt": {
"type": "Number"
},
"ibanRequired": {
"type": "boolean"
}
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "string",
"required": true
},
"graceDays": {
"type": "string"
},
"outstandingDebt": {
"type": "Number"
},
"ibanRequired": {
"type": "boolean"
}
}
}

View File

@ -51,7 +51,7 @@ describe('Client', () => {
}
};
const serializedParams = $httpParamSerializer({filter});
$httpBackend.expect('GET', `ClientRisks?${serializedParams}`,).respond([{amount: 20}]);
$httpBackend.expect('GET', `ClientRisks?${serializedParams}`).respond([{amount: 20}]);
controller.getAmountPaid();
$httpBackend.flush();
@ -65,7 +65,7 @@ describe('Client', () => {
controller.$params = {id: 101};
$httpBackend.expect('POST', `Receipts`,).respond({id: 1});
$httpBackend.expect('POST', `Receipts`).respond({id: 1});
controller.responseHandler('accept');
$httpBackend.flush();

View File

@ -7,7 +7,7 @@ export default class Client extends ModuleMain {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {name: {like: `%${value}%`}};
: {or: [{name: {like: `%${value}%`}}, {socialName: {like: `%${value}%`}}]};
case 'phone':
return {
or: [

View File

@ -55,8 +55,8 @@
</span>
</vn-td>
<vn-td number>
<span ng-class="::{link: sale.isTicket}"
ng-click="$ctrl.showTicketDescriptor($event, sale)"
<span class="link"
ng-click="$ctrl.showDescriptor($event, sale)"
name="origin">
{{::sale.origin | dashIfEmpty}}
</span>
@ -94,3 +94,7 @@
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>

View File

@ -58,10 +58,12 @@ class Controller extends Section {
this.$anchorScroll();
}
showTicketDescriptor(event, sale) {
if (!sale.isTicket) return;
showDescriptor(event, sale) {
let descriptor = 'entryDescriptor';
if (sale.isTicket)
descriptor = 'ticketDescriptor';
this.$.ticketDescriptor.show(event.target, sale.origin);
this.$[descriptor].show(event.target, sale.origin);
}
}

View File

@ -60,6 +60,34 @@ describe('Item', () => {
expect(controller.$anchorScroll).toHaveBeenCalledWith();
});
});
describe('showDescriptor ()', () => {
it('should call to the entryDescriptor show() method', () => {
controller.$.entryDescriptor = {};
controller.$.entryDescriptor.show = jest.fn();
const $event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent($event);
const data = {id: 1, origin: 1};
controller.showDescriptor($event, data);
expect(controller.$.entryDescriptor.show).toHaveBeenCalledWith($event.target, data.origin);
});
it('should call to the ticketDescriptor show() method', () => {
controller.$.ticketDescriptor = {};
controller.$.ticketDescriptor.show = jest.fn();
const $event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent($event);
const data = {id: 1, origin: 1, isTicket: true};
controller.showDescriptor($event, data);
expect(controller.$.ticketDescriptor.show).toHaveBeenCalledWith($event.target, data.origin);
});
});
});
});

View File

@ -3,7 +3,7 @@
"name": "Items",
"icon": "icon-item",
"validations" : true,
"dependencies": ["worker", "client", "ticket"],
"dependencies": ["worker", "client", "ticket", "entry"],
"menus": {
"main": [
{"state": "item.index", "icon": "icon-item"},

View File

@ -41,7 +41,7 @@
<vn-autocomplete vn-three
ng-show="tag.selection.isFree === false"
url="{{$ctrl.sourceTables[itemTag.id].url}}"
search-function="{name: {like: '%'+ $search +'%'}}"
search-function="{name: {like: $search +'%'}}"
label="Value"
ng-model="itemTag.value"
show-field="{{$ctrl.sourceTables[itemTag.id].field}}"

View File

@ -18,7 +18,7 @@ describe('Component vnOrderIndex', () => {
beforeEach(ngModule('order'));
beforeEach(inject(($componentController, _$window_,) => {
beforeEach(inject(($componentController, _$window_) => {
$window = _$window_;
const $element = angular.element('<vn-order-index></vn-order-index>');
controller = $componentController('vnOrderIndex', {$element});

View File

@ -60,7 +60,11 @@ module.exports = Self => {
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return {'s.id': value};
return {or: [
{'s.id': value},
{'s.name': {like: `%${value}%`}},
{'s.nickname': {like: `%${value}%`}}
]};
case 'nickname':
param = `s.${param}`;
return {[param]: {like: `%${value}%`}};

View File

@ -0,0 +1,74 @@
module.exports = Self => {
Self.remoteMethod('getSummary', {
description: 'Returns the supplier summary',
accessType: 'READ',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The supplier id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getSummary`,
verb: 'GET'
}
});
Self.getSummary = async id => {
let filter = {
where: {id: id},
fields: [
'id',
'name',
'nickname',
'isOfficial',
'isActive',
'note',
'nif',
'street',
'city',
'postCode',
'provinceFk',
'countryFk',
'payMethodFk',
'payDemFk',
'payDay',
'account',
'isFarmer',
],
include: [
{
relation: 'province',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'country',
scope: {
fields: ['id', 'country', 'code']
}
},
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
}
]
};
let supplier = await Self.app.models.Supplier.findOne(filter);
return supplier;
};
};

View File

@ -0,0 +1,28 @@
const app = require('vn-loopback/server/server');
describe('Supplier getSummary()', () => {
it('should return a summary object containing data from one supplier', async() => {
const supplier = await app.models.Supplier.getSummary(1);
expect(supplier.id).toEqual(1);
expect(supplier.name).toEqual('Plants SL');
expect(supplier.nif).toEqual('06089160W');
expect(supplier.account).toEqual(4000000001);
expect(supplier.payDay).toEqual(15);
});
it(`should return a summary object containing it's supplier country relation`, async() => {
const supplier = await app.models.Supplier.getSummary(1);
const country = supplier.country();
expect(country.id).toEqual(1);
expect(country.code).toEqual('ES');
});
it(`should return a summary object containing it's billing data relation`, async() => {
const supplier = await app.models.Supplier.getSummary(1);
const payMethod = supplier.payMethod();
expect(payMethod.name).toEqual('PayMethod one');
});
});

View File

@ -1,5 +1,8 @@
{
"Supplier": {
"dataSource": "vn"
},
"PayDem": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,19 @@
{
"name": "PayDem",
"base": "VnModel",
"options": {
"mysql": {
"table": "payDem"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"payDem": {
"type": "Number"
}
}
}

View File

@ -1,3 +1,4 @@
module.exports = Self => {
require('../methods/supplier/filter')(Self);
require('../methods/supplier/getSummary')(Self);
};

View File

@ -45,6 +45,12 @@
"isActive": {
"type": "Boolean"
},
"isOfficial": {
"type": "Boolean"
},
"note": {
"type": "String"
},
"street": {
"type": "String"
},
@ -63,10 +69,41 @@
"payDemFk": {
"type": "Number"
},
"payDay": {
"type": "Number"
},
"nickname": {
"type": "String"
}
},
"relations": {
"payMethod": {
"type": "belongsTo",
"model": "PayMethod",
"foreignKey": "payMethodFk"
},
"payDem": {
"type": "belongsTo",
"model": "PayDem",
"foreignKey": "payDemFk"
},
"province": {
"type": "belongsTo",
"model": "Province",
"foreignKey": "provinceFk"
},
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "countryFk"
},
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "nif",
"primaryKey": "fi"
}
},
"acls": [
{
"accessType": "READ",

View File

@ -0,0 +1,5 @@
<vn-portal slot="menu">
<vn-supplier-descriptor supplier="$ctrl.supplier"></vn-entry-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,48 @@
import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
let filter = {
include: [
{
relation: 'province',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'country',
scope: {
fields: ['id', 'name', 'code']
}
},
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
},
{
relation: 'client',
scope: {
fields: ['id', 'fi']
}
}
]
};
this.$http.get(`Suppliers/${this.$params.id}`, {filter})
.then(response => this.supplier = response.data);
}
}
ngModule.vnComponent('vnSupplierCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,57 @@
<vn-descriptor-content
module="supplier"
description="$ctrl.supplier.name">
<slot-body>
<div class="attributes">
<vn-label-value label="Tax number"
value="{{$ctrl.supplier.nif}}">
</vn-label-value>
<vn-label-value label="Alias"
value="{{$ctrl.supplier.nickname}}">
</vn-label-value>
<vn-label-value label="Pay method"
value="{{$ctrl.supplier.payMethod.name}}">
</vn-label-value>
<vn-label-value label="Payment deadline"
value="{{$ctrl.supplier.payDem.payDem}}">
</vn-label-value>
<vn-label-value label="Pay day"
value="{{$ctrl.supplier.payDay}}">
</vn-label-value>
<vn-label-value label="Account"
value="{{$ctrl.supplier.account}}">
</vn-label-value>
</div>
<div class="icons">
<vn-icon
vn-tooltip="Inactive supplier"
icon="icon-disabled"
ng-class="{bright: $ctrl.supplier.isActive == false}">
</vn-icon>
<vn-icon
vn-tooltip="Official supplier"
icon="icon-net"
ng-class="{bright: $ctrl.supplier.isOfficial == false}">
</vn-icon>
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="All entries with current supplier"
state="['entry.index', {q: $ctrl.entryFilter}]"
icon="icon-entry">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link
ng-if="$ctrl.supplier.client.fi"
tooltip="Go to client"
state="['client.card.summary', {id: $ctrl.supplier.client.id}]"
icon="person">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
</div>
</div>
</slot-body>
</vn-descriptor-content>

View File

@ -0,0 +1,79 @@
import ngModule from '../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get supplier() {
return this.entity;
}
set supplier(value) {
this.entity = value;
}
get entryFilter() {
if (!this.supplier) return null;
const date = new Date();
date.setHours(0, 0, 0, 0);
const from = new Date(date.getTime());
from.setDate(from.getDate() - 10);
const to = new Date(date.getTime());
to.setDate(to.getDate() + 10);
return JSON.stringify({
supplierFk: this.supplier.id,
from,
to
});
}
loadData() {
const filter = {
fields: [
'id',
'name',
'nickname',
'nif',
'payMethodFk',
'payDemFk',
'payDay',
'isActive',
'isOfficial',
'account'
],
include: [
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
},
{
relation: 'client',
scope: {
fields: ['id', 'fi']
}
}
]
};
return this.getData(`Suppliers/${this.supplier.id}`, {filter})
.then(res => this.supplier = res.data);
}
}
ngModule.vnComponent('vnSupplierDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
supplier: '<'
}
});

View File

@ -0,0 +1,64 @@
import './index.js';
describe('Supplier Component vnSupplierDescriptor', () => {
let $httpBackend;
let controller;
let $httpParamSerializer;
const supplier = {id: 1};
beforeEach(ngModule('supplier'));
beforeEach(inject(($componentController, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnSupplierDescriptor', {$element: null}, {supplier});
}));
describe('loadData()', () => {
it('should perform ask for the supplier', () => {
const filter = {
fields: [
'id',
'name',
'nickname',
'nif',
'payMethodFk',
'payDemFk',
'payDay',
'isActive',
'isOfficial',
'account'
],
include: [
{
relation: 'payMethod',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'payDem',
scope: {
fields: ['id', 'payDem']
}
},
{
relation: 'client',
scope: {
fields: ['id', 'fi']
}
}
]
};
const serializedParams = $httpParamSerializer({filter});
let query = `Suppliers/${controller.supplier.id}?${serializedParams}`;
jest.spyOn(controller, 'getData');
$httpBackend.expect('GET', query).respond({id: 1});
controller.loadData();
$httpBackend.flush();
expect(controller.getData).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -0,0 +1,5 @@
Tax number: NIF / CIF
All entries with current supplier: Todas las entradas con el proveedor actual
Go to client: Ir al cliente
Official supplier: Proveedor oficial
Inactive supplier: Proveedor inactivo

View File

@ -3,3 +3,6 @@ export * from './module';
import './main';
import './index/';
import './search-panel';
import './summary';
import './card';
import './descriptor';

View File

@ -8,7 +8,7 @@
<vn-searchbar
vn-focus
panel="vn-supplier-search-panel"
info="Search suppliers by id"
info="Search suppliers by id, name or alias"
model="model">
</vn-searchbar>
</vn-portal>

View File

@ -2,6 +2,7 @@
"module": "supplier",
"name": "Suppliers",
"icon" : "icon-supplier",
"dependencies": ["client", "item"],
"validations" : true,
"menus": {
"main": [
@ -22,6 +23,19 @@
"state": "supplier.index",
"component": "vn-supplier-index",
"description": "Suppliers"
}, {
"url": "/:id",
"state": "supplier.card",
"abstract": true,
"component": "vn-supplier-card"
}, {
"url": "/summary",
"state": "supplier.card.summary",
"component": "vn-supplier-summary",
"description": "Summary",
"params": {
"supplier": "$ctrl.supplier"
}
}
]
}

View File

@ -5,7 +5,7 @@
vn-one
label="General search"
ng-model="filter.search"
info="Search suppliers by id"
info="Search suppliers by id, name or alias"
vn-focus>
</vn-textfield>
</vn-horizontal>

View File

@ -1,3 +1,4 @@
Province: Provincia
Country: País
Tax number: Nif
Tax number: NIF / CIF
Search suppliers by id, name or alias: Busca proveedores por id, nombre o alias

View File

@ -0,0 +1,74 @@
<vn-card class="summary">
<h5>{{$ctrl.summary.name}} - {{$ctrl.summary.id}}</h5>
<vn-horizontal>
<vn-one>
<h4 translate>Basic data</h4>
<vn-label-value label="Id"
value="{{$ctrl.summary.id}}">
</vn-label-value>
<vn-label-value label="Alias"
value="{{$ctrl.summary.nickname}}">
</vn-label-value>
<vn-check
label="Is official"
ng-model="$ctrl.summary.isOfficial"
disabled="true">
</vn-check>
<vn-check
label="Is active"
ng-model="$ctrl.summary.isActive"
disabled="true">
</vn-check>
<vn-label-value label="Notes"
value="{{$ctrl.summary.note}}">
</vn-label-value>
</vn-one>
<vn-one>
<h4 translate>Fiscal address</h4>
<vn-label-value label="Social name"
value="{{$ctrl.summary.name}}">
</vn-label-value>
<vn-label-value label="Tax number"
value="{{$ctrl.summary.nif}}">
</vn-label-value>
<vn-label-value label="Street" ellipsize="false"
value="{{$ctrl.summary.street}}">
</vn-label-value>
<vn-label-value label="City"
value="{{$ctrl.summary.city}}">
</vn-label-value>
<vn-label-value label="Postcode"
value="{{$ctrl.summary.postCode}}">
</vn-label-value>
<vn-label-value label="Province"
value="{{$ctrl.summary.province.name}}">
</vn-label-value>
<vn-label-value label="Country"
value="{{$ctrl.summary.country.country}}">
</vn-label-value>
</vn-one>
<vn-one>
<h4 translate>Billing data</h4>
<vn-label-value label="Pay method"
value="{{$ctrl.summary.payMethod.name}}">
</vn-label-value>
<vn-label-value label="Payment deadline"
value="{{$ctrl.summary.payDem.payDem}}">
</vn-label-value>
<vn-label-value label="Pay day"
value="{{$ctrl.summary.payDay}}">
</vn-label-value>
<vn-label-value label="Account"
value="{{$ctrl.summary.account}}">
</vn-label-value>
<vn-check
label="Is Farmer"
ng-model="$ctrl.summary.isFarmer"
disabled="true">
</vn-check>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -0,0 +1,26 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
$onChanges() {
if (!this.supplier)
return;
this.getSummary();
}
getSummary() {
return this.$http.get(`Suppliers/${this.supplier.id}/getSummary`).then(response => {
this.summary = response.data;
});
}
}
ngModule.vnComponent('vnSupplierSummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
supplier: '<'
}
});

View File

@ -0,0 +1,32 @@
import './index';
describe('Supplier', () => {
describe('Component vnSupplierSummary', () => {
let controller;
let $httpBackend;
let $scope;
beforeEach(ngModule('supplier'));
beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $element = angular.element('<vn-supplier-summary></vn-supplier-summary>');
controller = $componentController('vnSupplierSummary', {$element, $scope});
}));
describe('getSummary()', () => {
it('should perform a get asking for the supplier data', () => {
controller.supplier = {id: 1};
const query = `Suppliers/${controller.supplier.id}/getSummary`;
$httpBackend.expectGET(query).respond({id: 1});
controller.getSummary();
$httpBackend.flush();
expect(controller.summary).toEqual({id: 1});
});
});
});
});

View File

@ -0,0 +1,5 @@
Is official: Es oficial
Country: País
Tax number: NIF / CIF
Search suppliers by id, name or alias: Busca proveedores por id, nombre o alias
Is Farmer: Es agrícola

View File

@ -0,0 +1,7 @@
@import "variables";
vn-client-summary {
.alert span {
color: $color-alert
}
}

View File

@ -1,5 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
const diff = require('object-diff');
const loggable = require('vn-loopback/util/log');
module.exports = Self => {
Self.remoteMethodCtx('componentUpdate', {
@ -108,10 +108,8 @@ module.exports = Self => {
delete updatedTicket.ctx;
delete updatedTicket.option;
// Force unroute
// Force to unroute ticket
const hasToBeUnrouted = true;
const changedProperties = diff(originalTicket, updatedTicket);
const query = 'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
const res = await Self.rawSql(query, [
id,
@ -161,14 +159,18 @@ module.exports = Self => {
}
}
const changes = loggable.getChanges(originalTicket, updatedTicket);
const oldProperties = await loggable.translateValues(Self, changes.old);
const newProperties = await loggable.translateValues(Self, changes.new);
await models.TicketLog.create({
originFk: id,
userFk: userId,
action: 'update',
changedModel: 'Ticket',
changedModelId: id,
oldInstance: originalTicket,
newInstance: changedProperties
oldInstance: oldProperties,
newInstance: newProperties
});
const salesPersonId = originalTicket.client().salesPersonFk;
@ -176,20 +178,11 @@ module.exports = Self => {
const origin = ctx.req.headers.origin;
let changesMade = '';
for (let change in changedProperties) {
let value = changedProperties[change];
if (value instanceof Date) {
value = new Intl.DateTimeFormat('es', {
year: '2-digit',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
}).format(value);
}
for (let change in newProperties) {
let value = newProperties[change];
let oldValue = oldProperties[change];
changesMade += `${change}: ${value}\r\n`;
changesMade += `\r\n~${$t(change)}: ${oldValue}~ ➔ *${$t(change)}: ${value}*`;
}
const message = $t('Changed this data from the ticket', {

View File

@ -50,8 +50,8 @@ describe('ticket componentUpdate()', () => {
req: {
accessToken: {userId: userID},
headers: {origin: 'http://localhost'},
__: (value, params) => {
return params.nickname;
__: value => {
return value;
}
}
};
@ -98,3 +98,19 @@ describe('ticket componentUpdate()', () => {
expect(secondvalueBeforeChange).toEqual(secondvalueAfterChange);
});
});
it('should change the addressFk to modify the observations and then undo the changes', async() => {
const clientID = 102;
const addressID = 122;
const agencyModeID = 8;
const warehouseID = 1;
const zoneID = 5;
const shipped = today;
const companyID = 442;
const isDeleted = false;
const landed = tomorrow;
const option = 1;
await app.models.Ticket.componentUpdate(ctx, ticketID, clientID, agencyModeID, addressID,
zoneID, warehouseID, companyID, shipped, landed, isDeleted, option);
});

View File

@ -48,6 +48,12 @@
},
"zoneFk": {
"type": "Number"
},
"zonePrice": {
"type": "Number"
},
"zoneBonus": {
"type": "Number"
}
},
"relations": {

View File

@ -15,6 +15,9 @@ class Controller extends ModuleCard {
relation: 'warehouse',
scope: {fields: ['name']}
},
{
relation: 'zone',
},
{
relation: 'invoiceOut',
scope: {fields: ['id']}

View File

@ -76,11 +76,35 @@
</section>
</div>
<div class="totalBox align-left">
<h6 class="align-center" translate>Theorical cost</h6>
<div> <vn-label translate>Price</vn-label> {{$ctrl.theoricalCost | currency: 'EUR': 2}} </div>
<h6 class="align-center" translate>Zone breakdown</h6>
<div> <vn-label translate>Price</vn-label> {{$ctrl.ticket.zonePrice | currency: 'EUR': 2}} </div>
<div> <vn-label translate>Bonus</vn-label> {{$ctrl.ticket.zoneBonus | currency: 'EUR': 2}} </div>
<div> <vn-label translate>Zone</vn-label>
<span
title="{{$ctrl.ticket.zone.name}}"
vn-click-stop="zoneDescriptor.show($event, $ctrl.ticket.zone.id)"
class="link">
{{$ctrl.ticket.zone.name | dashIfEmpty}}
</span>
</div>
<div ng-show="$ctrl.ticket.zone.isVolumetric">
<vn-label translate>Volume</vn-label> {{$ctrl.ticketVolume}}
</div>
<div ng-show="!$ctrl.ticket.zone.isVolumetric">
<vn-label translate>Packages</vn-label> {{$ctrl.ticket.packages}}
</div>
</div>
<div class="totalBox align-left">
<h6 class="align-center" translate>Theorical cost</h6>
<div class="total"> <vn-label translate>Price total</vn-label> {{$ctrl.theoricalCost | currency: 'EUR': 2}} </div>
</div>
</vn-side-menu>
<vn-item-descriptor-popover
vn-id="descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk">
</vn-item-descriptor-popover>
</vn-item-descriptor-popover>
<vn-zone-descriptor-popover
vn-id="zoneDescriptor">
</vn-zone-descriptor-popover>

View File

@ -37,9 +37,10 @@ class Controller extends Section {
this._ticket = value;
if (!value) return;
this.getTheoricalCost();
this.getComponentsSum();
if (this.ticket.zone.isVolumetric)
this.getTicketVolume();
}
base() {
@ -76,6 +77,13 @@ class Controller extends Section {
this.$http.get(`Tickets/${this.ticket.id}/getComponentsSum`)
.then(res => this.componentsList = res.data);
}
getTicketVolume() {
if (!this.ticket) return;
this.$http.get(`Tickets/${this.ticket.id}/getVolume`)
.then(res => this.ticketVolume = res.data[0].volume);
}
}
ngModule.vnComponent('vnTicketComponents', {

View File

@ -89,7 +89,10 @@ describe('ticket', () => {
jest.spyOn(controller, 'getComponentsSum');
controller._ticket = undefined;
controller.ticket = {
id: 7
id: 7,
zone: {
isVolumetric: false
}
};
expect(controller.ticket).toBeDefined();

View File

@ -1,2 +1,6 @@
Theorical cost: Porte teorico
Total without VAT: Total sin IVA
Total without VAT: Total sin IVA
Bonus: Bonificación
Price: Precio
Price total: Precio total
Zone breakdown: Desglose zona

View File

@ -14,6 +14,7 @@
</vn-multi-check>
</vn-th>
<vn-th></vn-th>
<vn-th></vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="salesPersonFk" class="expendable">Salesperson</vn-th>
<vn-th field="shipped">Date</vn-th>
@ -40,27 +41,36 @@
</vn-td>
<vn-td shrink>
<vn-icon
ng-show="ticket.hasTicketRequest"
ng-show="ticket.isTaxDataChecked"
translate-attr="{title: 'Verified data'}"
class="bright"
icon="check">
</vn-icon>
</vn-td>
<vn-td shrink>
<vn-icon
ng-show="ticket.hasTicketRequest"
translate-attr="{title: 'Purchase request'}"
class="bright"
vn-tooltip="Purchase request"
icon="icon-100">
</vn-icon>
<vn-icon
ng-show="ticket.isAvailable === 0"
translate-attr="{title: 'Not available'}"
class="bright"
vn-tooltip="Not available"
icon="icon-unavailable">
</vn-icon>
<vn-icon
ng-show="ticket.isFreezed"
translate-attr="{title: 'Client frozen'}"
class="bright"
vn-tooltip="Client frozen"
icon="icon-frozen">
</vn-icon>
<vn-icon
ng-show="ticket.risk"
title="{{::$ctrl.$t('Risk')}}: {{ticket.risk}}"
class="bright"
vn-tooltip="{{::$ctrl.$t('Risk')}}: {{ticket.risk}}"
icon="icon-risk">
</vn-icon>
</vn-td>

View File

@ -28,8 +28,12 @@
<vn-label-value label="Agency"
value="{{$ctrl.summary.agencyMode.name}}">
</vn-label-value>
<vn-label-value label="Zone"
value="{{$ctrl.summary.zone.name}}">
<vn-label-value label="Zone">
<span
ng-click="zoneDescriptor.show($event, $ctrl.summary.zoneFk)"
class="link">
{{$ctrl.summary.zone.name}}
</span>
</vn-label-value>
<vn-label-value label="Warehouse"
value="{{$ctrl.summary.warehouse.name}}">
@ -247,3 +251,6 @@
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-zone-descriptor-popover
vn-id="zoneDescriptor">
</vn-zone-descriptor-popover>

View File

@ -3,7 +3,7 @@
"name": "Travels",
"icon": "local_airport",
"validations": true,
"dependencies": ["worker"],
"dependencies": ["worker", "entry"],
"menus": {
"main": [
{"state": "travel.index", "icon": "local_airport"}

View File

@ -41,7 +41,7 @@
value="{{$ctrl.travelData.ref}}">
</vn-label-value>
<vn-label-value
label="m3"
label="m³"
value="{{$ctrl.travelData.m3}}">
</vn-label-value>
<vn-label-value
@ -63,7 +63,7 @@
<vn-th shrink>Package</vn-th>
<vn-th shrink>CC</vn-th>
<vn-th shrink>Pallet</vn-th>
<vn-th shrink>m3</vn-th>
<vn-th shrink>m³</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
@ -75,7 +75,12 @@
disabled="true">
</vn-check>
</vn-td>
<vn-td shrink>{{entry.id}} </vn-td>
<vn-td shrink>
<span class="link"
vn-click-stop="entryDescriptor.show($event, entry.id)">
{{entry.id}}
</span>
</vn-td>
<vn-td expand>{{entry.supplierName}}</vn-td>
<vn-td shrink>{{entry.ref}}</vn-td>
<vn-td shrink>{{entry.hb}}</vn-td>
@ -141,4 +146,7 @@
</vn-table>
</vn-auto>
</vn-horizontal>
</vn-card>
</vn-card>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>

View File

@ -9,7 +9,7 @@ Received: Recibida
Agency: Agencia
Entries: Entradas
Confirmed: Confirmada
Entry Id: Entrada Id
Entry Id: Id entrada
Supplier: Proveedor
Pallet: Pallet
Freight: Porte

View File

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server');
// 2066
xdescribe('Worker absences()', () => {
describe('Worker absences()', () => {
it('should get the absence calendar for a full year contract', async() => {
let ctx = {req: {accessToken: {userId: 106}}};
let workerFk = 106;
@ -84,14 +83,16 @@ xdescribe('Worker absences()', () => {
yearStart.setDate(1);
const yearEnd = new Date();
yearEnd.setHours(23, 59, 59, 59);
yearEnd.setMonth(11);
yearEnd.setDate(31);
const currentYear = yearEnd.getFullYear();
yearEnd.setFullYear(currentYear + 1);
yearEnd.setHours(0, 0, 0, 0);
yearEnd.setMonth(0);
yearEnd.setDate(1);
const startedTime = yearStart.getTime();
const endedTime = yearEnd.getTime();
const dayTimestamp = 1000 * 60 * 60 * 24;
const daysInYear = Math.floor((endedTime - startedTime) / dayTimestamp);
const daysInYear = Math.round((endedTime - startedTime) / dayTimestamp);
// sets the holidays per year to the amount of days in the current year
let holidaysConfig = await app.models.WorkCenterHoliday.findOne({
@ -102,17 +103,11 @@ xdescribe('Worker absences()', () => {
let originalHolidaysValue = holidaysConfig.days;
await app.models.WorkCenterHoliday.updateAll(
{
workCenterFk: 1,
year: today.getFullYear()
},
{
days: daysInYear
}
);
await holidaysConfig.updateAttribute('days', daysInYear);
// normal test begins
const contract = await app.models.WorkerLabour.findById(106);
const userId = 106;
const contract = await app.models.WorkerLabour.findById(userId);
const contractStartDate = contract.started;
const startingContract = new Date();
@ -121,14 +116,13 @@ xdescribe('Worker absences()', () => {
startingContract.setDate(1);
await app.models.WorkerLabour.rawSql(
`UPDATE postgresql.business SET date_start = ? WHERE business_id = ?`,
[startingContract, contract.businessFk]
`UPDATE postgresql.business SET date_start = ?, date_end = ? WHERE business_id = ?`,
[startingContract, yearEnd, contract.businessFk]
);
let ctx = {req: {accessToken: {userId: 106}}};
let workerFk = 106;
let ctx = {req: {accessToken: {userId: userId}}};
let result = await app.models.Calendar.absences(ctx, workerFk, yearStart, yearEnd);
let result = await app.models.Calendar.absences(ctx, userId, yearStart, yearEnd);
let calendar = result[0];
let absences = result[1];

View File

@ -73,7 +73,7 @@
value="{{::row.bonus | currency:'EUR':2}}">
</vn-label-value>
<vn-label-value
label="Max m3"
label="Max m³"
value="{{::row.m3Max}}">
</vn-label-value>
</vn-item-section>
@ -166,7 +166,7 @@
</vn-input-number>
</vn-horizontal>
<vn-input-number
label="Max m3"
label="Max m³"
ng-model="$ctrl.selected.m3Max"
min="0"
step="0.01">