Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2138-zone_event_m3

This commit is contained in:
Joan Sanchez 2020-02-26 11:00:40 +01:00
commit 4add07898f
53 changed files with 861 additions and 117 deletions

View File

@ -1103,11 +1103,11 @@ INSERT INTO `vn`.`annualAverageInvoiced`(`clientFk`, `invoiced`)
(104, 500),
(105, 5000);
INSERT INTO `vn`.`supplier`(`id`, `name`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`)
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`retAccount`,`commission`, `created`, `postcodeFk`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`)
VALUES
(1, 'Plants SL', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1),
(2, 'Flower King', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2),
(442, 'Verdnatura Levante SL', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2);
(1, 'Plants SL', 'Plants nick', 4000000001, 1, 'A11111111', 0, NULL, 0, CURDATE(), 1111, 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1),
(2, 'Flower King', 'The king', 4000000002, 1, 'B22222222', 0, NULL, 0, CURDATE(), 2222, 1, 'supplier address 2', 'LONDON', 2, 45671, 1, 2),
(442, 'Verdnatura Levante SL', 'Verdnatura', 4000000442, 1, 'C33333333', 0, NULL, 0, CURDATE(), 3333, 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2);
INSERT INTO `cache`.`cache_calc`(`id`, `cache_id`, `cacheName`, `params`, `last_refresh`, `expires`, `created`, `connection_id`)
VALUES

View File

@ -40,16 +40,12 @@ let actions = {
},
login: async function(userName) {
try {
await this.expectURL('#!/login');
} catch (e) {
await this.goto(`${defaultURL}/#!/login`);
let dialog = await this.evaluate(() => {
return document.querySelector('button[response="accept"]');
});
if (dialog)
await this.waitToClick('button[response="accept"]');
}
await this.goto(`${defaultURL}/#!/login`);
let dialog = await this.evaluate(() => {
return document.querySelector('button[response="accept"]');
});
if (dialog)
await this.waitToClick('button[response="accept"]');
await this.doLogin(userName);
await this.waitForFunction(() => {
@ -175,7 +171,7 @@ let actions = {
},
waitToClick: async function(selector) {
await this.waitForSelector(selector, {});
await this.waitForSelector(selector);
await this.click(selector);
},
@ -327,11 +323,18 @@ let actions = {
},
hideSnackbar: async function() {
await this.waitToClick('#shapes .shown button');
await this.waitFor(750); // holds up for the snackbar to be visible for a small period of time.
await this.evaluate(() => {
let hideButton = document.querySelector('#shapes .shown button');
if (hideButton)
return document.querySelector('#shapes .shown button').click();
});
},
waitForLastShape: async function(selector) {
await this.wait(selector);
waitForLastSnackbar: async function() {
const selector = 'vn-snackbar .shown .text';
await this.waitForSelector(selector);
let snackBarText = await this.evaluate(selector => {
const shape = document.querySelector(selector);
@ -341,12 +344,6 @@ let actions = {
return snackBarText;
},
waitForLastSnackbar: async function() {
await this.waitFor(1000); // this needs a refactor to be somehow dynamic ie: page.waitForResponse(urlOrPredicate[, options]) or something to fire waitForLastShape once the request is completed
await this.waitForSpinnerLoad();
return await this.waitForLastShape('vn-snackbar .shown .text');
},
accessToSearchResult: async function(searchValue) {
await this.clearInput('vn-searchbar');
await this.write('vn-searchbar', searchValue);
@ -412,7 +409,7 @@ let actions = {
.includes(searchValue.toLowerCase());
}, {}, builtSelector, searchValue);
await this.waitForMutation(`.vn-drop-down`, 'childList');
await this.waitForMutation('.vn-drop-down', 'childList');
await this.waitForContentLoaded();
},

View File

@ -28,7 +28,7 @@ export async function getBrowser() {
});
});
page = extendPage(page);
page.setDefaultTimeout(5000);
page.setDefaultTimeout(10000);
await page.goto(defaultURL, {waitUntil: 'networkidle0'});
return {page, close: browser.close.bind(browser)};
}

View File

@ -799,5 +799,10 @@ export default {
inflation: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.inflation"]',
volumetric: 'vn-zone-basic-data vn-check[ng-model="$ctrl.zone.isVolumetric"]',
saveButton: 'vn-zone-basic-data vn-submit > button',
},
entrySummary: {
header: 'vn-entry-summary > vn-card > h5',
reference: 'vn-entry-summary vn-label-value[label="Reference"]',
confirmed: 'vn-entry-summary vn-check[label="Confirmed"]',
}
};

View File

@ -101,7 +101,6 @@ describe('Client create path', async() => {
});
it('should click on the Clients button of the top bar menu', async() => {
await page.waitFor(500);
await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.wait(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.clientsButton);

View File

@ -160,6 +160,7 @@ describe('Client Edit fiscalData path', () => {
});
it('should propagate the Equalization tax changes', async() => {
await page.waitFor(1000);
await page.waitToClick(selectors.clientFiscalData.acceptPropagationButton);
const result = await page.waitForLastSnackbar();

View File

@ -47,7 +47,7 @@ describe('Client Add address path', () => {
expect(result).toEqual('Incoterms is required for a non UEE member');
});
it(`should receive an error after clicking save button as consignee, incoterms and customsAgent are empty`, async() => {
it(`should receive an error after clicking save button as customsAgent is empty`, async() => {
await page.autocompleteSearch(selectors.clientAddresses.incoterms, 'Free Alongside Ship');
await page.waitToClick(selectors.clientAddresses.saveButton);
const result = await page.waitForLastSnackbar();
@ -63,13 +63,20 @@ describe('Client Add address path', () => {
expect(result).toEqual('Data saved!');
});
it(`should click on the first address button to confirm the new address exists and it's the default one`, async() => {
it(`should navigate back to the addresses index`, async() => {
let url = await page.expectURL('/address/index');
expect(url).toBe(true);
});
it(`should confirm the new address exists and it's the default one`, async() => {
const result = await page.waitToGetProperty(selectors.clientAddresses.defaultAddress, 'innerText');
expect(result).toContain('320 Park Avenue New York');
});
it(`should click on the make default icon of the second address`, async() => {
await page.waitForContentLoaded();
await page.waitToClick(selectors.clientAddresses.secondMakeDefaultStar);
const result = await page.waitForLastSnackbar();
@ -101,6 +108,7 @@ describe('Client Add address path', () => {
});
it(`should go back to the addreses section by clicking the cancel button`, async() => {
await page.waitForSelector('#shapes .shown', {hidden: true});
await page.waitToClick(selectors.clientAddresses.cancelEditAddressButton);
await page.waitToClick('.vn-confirm.shown button[response="accept"]');
let url = await page.expectURL('address/index');

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Client lock verified data path', () => {
xdescribe('Client lock verified data path', () => {
let browser;
let page;
beforeAll(async() => {
@ -113,7 +113,7 @@ describe('Client lock verified data path', () => {
});
});
describe('as salesAssistant', () => {
xdescribe('as salesAssistant', () => {
it('should log in as salesAssistant then get to the client fiscal data', async() => {
await page.forceReloadSection('client.card.fiscalData');
await page.loginAndModule('salesAssistant', 'client');

View File

@ -85,7 +85,7 @@ describe('User config', () => {
await page.autocompleteSearch(selectors.globalItems.userLocalCompany, 'VNL');
let result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
expect(result).toContain('Data saved!');
});
});
@ -125,7 +125,7 @@ describe('User config', () => {
await page.clearInput(selectors.globalItems.userConfigThirdAutocomplete);
let result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
expect(result).toContain('Data saved!');
});
});
});

View File

@ -32,6 +32,6 @@ describe('Client contacts', () => {
await page.waitToClick(selectors.clientContacts.saveButton);
let result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
expect(result).toContain('Data saved!');
});
});

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker time control path', () => {
xdescribe('Worker time control path', () => {
let browser;
let page;
beforeAll(async() => {

View File

@ -37,22 +37,21 @@ describe('Item Edit basic data path', () => {
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
}, 20000);
});
it(`should create a new intrastat`, async() => {
await page.waitToClick(selectors.itemBasicData.newIntrastatButton);
await page.write(selectors.itemBasicData.newIntrastatId, '588420239');
await page.write(selectors.itemBasicData.newIntrastatDescription, 'Tropical Flowers');
await page.waitToClick(selectors.itemBasicData.acceptIntrastatButton);
await page.waitToClick(selectors.itemBasicData.acceptIntrastatButton); // this popover obscures the rest of the form for aprox 2 seconds
await page.waitFor(2000);
await page.waitForTextInField(selectors.itemBasicData.intrastat, 'Tropical Flowers');
let newcode = await page.waitToGetProperty(selectors.itemBasicData.intrastat, 'value');
expect(newcode).toEqual('588420239 Tropical Flowers');
});
it(`should save with the new intrastat`, async() => {
await page.waitFor(250);
await page.waitForTextInField(selectors.itemBasicData.intrastat, 'Tropical Flowers');
it('should save with the new intrastat', async() => {
await page.waitToClick(selectors.itemBasicData.submitBasicDataButton);
const result = await page.waitForLastSnackbar();

View File

@ -33,7 +33,6 @@ describe('Item regularize path', () => {
});
it('should search for an specific item', async() => {
await page.clearInput(selectors.itemsIndex.topbarSearch);
await page.write(selectors.itemsIndex.topbarSearch, 'Ranged weapon pistol 9mm');
await page.waitToClick(selectors.itemsIndex.searchButton);
await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1);
@ -42,7 +41,7 @@ describe('Item regularize path', () => {
expect(resultCount).toEqual(1);
});
it(`should click on the search result to access to the item tax`, async() => {
it(`should click on the search result to access to the summary`, async() => {
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon pistol 9mm');
await page.waitToClick(selectors.itemsIndex.searchResult);
let url = await page.expectURL('/summary');
@ -80,6 +79,7 @@ describe('Item regularize path', () => {
});
it('should clear the user local settings now', async() => {
await page.waitForContentLoaded();
await page.waitToClick(selectors.globalItems.userMenuButton);
await page.clearInput(selectors.globalItems.userConfigFirstAutocomplete);
const result = await page.waitForLastSnackbar();

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Item index path', () => {
xdescribe('Item index path', () => {
let browser;
let page;
beforeAll(async() => {
@ -54,7 +54,8 @@ describe('Item index path', () => {
await page.waitForSelector(selectors.itemsIndex.firstItemId, {hidden: true});
});
it('should mark all unchecked boxes to leave the index as it was', async() => {
xit('should mark all unchecked boxes to leave the index as it was', async() => {
await page.waitForContentLoaded();
await page.waitToClick(selectors.itemsIndex.fieldsToShowButton);
await page.waitToClick(selectors.itemsIndex.idCheckbox);
await page.waitToClick(selectors.itemsIndex.stemsCheckbox);

View File

@ -97,6 +97,7 @@ describe('Ticket List sale path', () => {
});
it('should select the 2nd and 3th item and delete both', async() => {
await page.waitFor(2000);
await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.deleteSaleButton);

View File

@ -129,6 +129,7 @@ describe('Ticket descriptor path', () => {
});
it('should delete the stowaway', async() => {
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteStowawayButton);
await page.waitToClick(selectors.ticketDescriptor.acceptDeleteStowawayButton);

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Claim action path', () => {
xdescribe('Claim action path', () => {
let browser;
let page;
@ -24,7 +24,8 @@ describe('Claim action path', () => {
expect(result).toEqual('Data saved!');
});
it('should import the second importable ticket', async() => {
xit('should import the second importable ticket', async() => {
await page.waitFor(2000); // the animation adding the header element for the claimed total obscures somehow other elements for 2 seconds
await page.waitToClick(selectors.claimAction.importTicketButton);
await page.waitToClick(selectors.claimAction.secondImportableTicket);
const result = await page.waitForLastSnackbar();
@ -33,6 +34,7 @@ describe('Claim action path', () => {
});
it('should edit the second line destination field', async() => {
await page.waitForContentLoaded();
await page.autocompleteSearch(selectors.claimAction.secondLineDestination, 'Bueno');
const result = await page.waitForLastSnackbar();
@ -43,7 +45,7 @@ describe('Claim action path', () => {
await page.waitToClick(selectors.claimAction.firstDeleteLine);
const result = await page.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
expect(result).toContain('Data saved!');
});
it('should refresh the view to check the remaining line is the expected one', async() => {
@ -61,6 +63,7 @@ describe('Claim action path', () => {
});
it('should check the "is paid with mana" checkbox', async() => {
page.waitFor(2500); // can't use waitForNavigation here and needs more time than a single second to get the section ready...
await page.waitToClick(selectors.claimAction.isPaidWithManaCheckbox);
const result = await page.waitForLastSnackbar();

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('InvoiceOut descriptor path', () => {
xdescribe('InvoiceOut descriptor path', () => {
let browser;
let page;
@ -26,7 +26,7 @@ describe('InvoiceOut descriptor path', () => {
expect(result).toEqual(1);
});
it('should navigate to the invoiceOut index', async() => {
xit('should navigate to the invoiceOut index', async() => {
await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.wait(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.invoiceOutButton);

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Zone basic data path', () => {
xdescribe('Zone basic data path', () => {
let browser;
let page;
@ -42,7 +42,7 @@ describe('Zone basic data path', () => {
await page.waitToClick(selectors.zoneBasicData.saveButton);
});
it('should reload the section', async() => {
xit('should reload the section', async() => {
await page.reloadSection('zone.card.basicData');
let url = await page.expectURL('#!/zone/10/basic-data');
@ -73,7 +73,7 @@ describe('Zone basic data path', () => {
expect(result).toEqual('1');
});
it('should confirm the closing hour was updated', async() => {
xit('should confirm the closing hour was updated', async() => {
const result = await page.waitToGetProperty(selectors.zoneBasicData.closing, 'value');
expect(result).toEqual('21:00');

View File

@ -0,0 +1,43 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.waitToClick('vn-entry-index vn-tbody > a:nth-child(2)');
});
afterAll(async() => {
await browser.close();
});
it('should reach the second entry summary section', async() => {
let url = await page.expectURL('#!/entry/2/summary');
expect(url).toBe(true);
});
it(`should display details from the entry on the header`, async() => {
await page.waitForTextInElement(selectors.entrySummary.header, 'The king');
const result = await page.waitToGetProperty(selectors.entrySummary.header, 'innerText');
expect(result).toContain('The king');
});
it('should display some entry details like the reference', async() => {
const result = await page.waitToGetProperty(selectors.entrySummary.reference, 'innerText');
expect(result).toContain('Movement 2');
});
it('should display other entry details like the confirmed', async() => {
const result = await page.checkboxState(selectors.entrySummary.confirmed, 'innerText');
expect(result).toContain('unchecked');
});
});

View File

@ -12,6 +12,15 @@ vn-chip {
max-width: 100%;
box-sizing: border-box;
&.small {
height: 1.5em;
& > div {
padding: 0.6em;
font-size: 0.8rem;
}
}
&.colored {
background-color: $color-main;
color: $color-font-bg;

View File

@ -100,7 +100,7 @@ export default class CrudModel extends ModelProxy {
}
removeFilter() {
return applyFilter(null, null);
return this.applyFilter(null, null);
}
/**
@ -240,14 +240,12 @@ export default class CrudModel extends ModelProxy {
onRemoteDone(json, filter, append) {
let data = json.data;
if (append)
this.orgData = this.orgData.concat(data);
else {
this.orgData = data;
this.currentFilter = filter;
}
this.data = this.proxiedData.slice();
this.moreRows = filter.limit && data.length == filter.limit;
this.onRequestEnd();

View File

@ -25,7 +25,7 @@ describe('Component vnCrudModel', () => {
});
describe('save()', () => {
it(`should make an HTTP post query and then update the original rows with the returned values`, () => {
it('should make an HTTP post query and then update the original rows with the returned values', () => {
spyOn(controller, 'applyChanges');
controller.insert({value: 'My new item 1'});
@ -47,4 +47,186 @@ describe('Component vnCrudModel', () => {
expect(controller.applyChanges).toHaveBeenCalledWith();
});
});
describe('setter url()', () => {
it('should set the url', () => {
spyOn(controller, 'autoRefresh');
spyOn(controller, 'clear');
controller.url = '/TestUrl';
expect(controller.url).toEqual('/TestUrl');
});
});
describe('isLoading()', () => {
it('should return false if canceler is null', () => {
controller.canceler = null;
expect(controller.isLoading).toBe(false);
});
it('should return true if canceler is not null', () => {
controller.canceler = 'validValue';
expect(controller.isLoading).toBe(true);
});
});
describe('buildFilter()', () => {
it('should build a filter and return it', () => {
controller.order = 'id ASC';
controller.fields = ['id'];
controller.limit = 1;
controller.filter = 'filterTest';
const result = controller.buildFilter();
expect(Object.keys(result).length).toEqual(13);
});
});
describe('sendRequest()', () => {
it('should call refresh() and check that the sendRequest is called', () => {
spyOn(controller, 'sendRequest').and.callThrough();
spyOn(controller, 'onRemoteDone');
spyOn(controller, 'onRemoteError');
const filter = {id: 1};
const serializedParams = encodeURIComponent(JSON.stringify(filter));
$httpBackend.whenRoute('GET', `model?filter=${serializedParams}`).respond();
controller.sendRequest(filter, true);
$httpBackend.flush();
expect(controller.isPaging).toBe(false);
});
});
describe('addFilter()', () => {
it('should call addFilter and check that the new filter has been added', () => {
spyOn(controller, 'refresh');
const filter = {where: {id: 1}};
controller.userFilter = {where: {name: 'test'}};
const filterMerged = {'where': {'and': [{'name': 'test'}, {'id': 1}]}};
controller.addFilter(filter);
expect(controller.userFilter).toEqual(filterMerged);
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('applyFilter()', () => {
it('should call applyFilter and check that the refresh() is called', () => {
spyOn(controller, 'refresh');
const filter = {where: {id: 1}};
const params = {where: {id: 2}};
controller.applyFilter(filter, params);
expect(controller.userFilter).toEqual(filter);
expect(controller.userParams).toEqual(params);
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('removeFilter()', () => {
it('should check the userFilter and userParams are removed', () => {
controller.removeFilter();
expect(controller.userFilter).toBe(null);
expect(controller.userParams).toBe(null);
});
});
describe('loadMore()', () => {
it('should call sendRequest with the new filter', () => {
spyOn(controller, 'sendRequest');
controller.moreRows = true;
controller.loadMore();
expect(controller.sendRequest).toHaveBeenCalledWith({'skip': 2}, true);
});
});
describe('clear()', () => {
it('should check that orgData and moreRows are set to null', () => {
controller.moreRows = true;
controller.clear();
expect(controller.moreRows).toBe(null);
expect(controller.orgData).toBe(null);
});
});
describe('refresh()', () => {
it('shold resolve a fake promise if this._url is undefined', () => {
spyOn(controller.$q, 'resolve');
controller._url = undefined;
controller.refresh();
expect(controller.$q.resolve).toHaveBeenCalledWith();
});
});
describe('onRemoteDone()', () => {
it('should check onRequestEnd is called, moreRows is true and currentFilter is undefined when append is true', () => {
spyOn(controller, 'onRequestEnd');
const append = true;
const json = {data: [
{
id: 1,
name: 'test'
}]};
const filter = {limit: 1};
controller.onRemoteDone(json, filter, append);
expect(controller.moreRows).toBe(true);
expect(controller.currentFilter).toBeUndefined();
expect(controller.onRequestEnd).toHaveBeenCalledWith();
});
it('should check onRequestEnd is called, moreRows is true and currentFilter is defined when append is false', () => {
spyOn(controller, 'onRequestEnd');
const append = false;
const json = {data: [
{
id: 1,
name: 'test'
}]};
const filter = {limit: 1};
controller.onRemoteDone(json, filter, append);
expect(controller.moreRows).toBe(true);
expect(controller.currentFilter).toBe(filter);
expect(controller.onRequestEnd).toHaveBeenCalledWith();
});
});
describe('onRemoteError()', () => {
it('should check the error', () => {
spyOn(controller, 'onRequestEnd');
let error;
try {
const newError = new Error('TestError');
controller.onRemoteError(newError);
} catch (e) {
error = e.message;
}
expect(controller.onRequestEnd).toHaveBeenCalledWith();
expect(error).toEqual('TestError');
});
});
});

View File

@ -63,13 +63,56 @@ export default class Controller extends Component {
show(data) {
this.actionHandler = data.actionHandler;
let shape = this.createShape(data);
let shape;
setTimeout(() =>
this.hide(shape), data.timeout || 3000);
const lastShape = this.lastShape;
const lastShapeData = lastShape && lastShape.data;
const isEqual = lastShape && (lastShapeData.shapeType == data.shapeType && lastShapeData.message == data.message);
setTimeout(() =>
shape.classList.add('shown'), 30);
if (lastShape && isEqual) {
shape = lastShape.element;
const shapeText = shape.querySelector('.text');
let chip = shapeText.querySelector('vn-chip');
if (chip) {
const text = chip.querySelector('span');
const number = parseInt(text.innerHTML);
text.innerHTML = number + 1;
} else {
chip = document.createElement('vn-chip');
chip.setAttribute('class', 'warning small');
let parent = document.createElement('div');
let span = document.createElement('span');
let text = document.createTextNode(1);
span.append(text);
parent.append(span);
chip.append(parent);
shapeText.appendChild(chip);
}
lastShape.element.classList.add('shown');
if (this.hideTimeout)
clearTimeout(this.hideTimeout);
} else {
shape = this.createShape(data);
setTimeout(() =>
shape.classList.add('shown'), 30);
}
this.hideTimeout = setTimeout(() => {
this.hide(shape);
this.lastShape = null;
}, data.timeout || 3000);
this.lastShape = {
data: data,
element: shape
};
}
/**

View File

@ -19,10 +19,16 @@ vn-snackbar .shape {
border-radius: .2em;
margin-bottom: 15px;
color: white;
padding: 1em;
padding: 0.8em;
&.text {
text-align: center
& > .text {
text-align: center;
vn-chip {
position: absolute;
left: -1em;
top: -1em;
}
}
&.shown {

View File

@ -23,6 +23,12 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-net:before {
content: "\e95b";
}
.icon-anonymous:before {
content: "\e95c";
}
.icon-buyrequest:before {
content: "\e914";
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@ -44,12 +44,6 @@ describe('Client', () => {
});
describe('company setter/getter', () => {
it('should return the company', () => {
controller.companyId = null;
expect(controller._companyId).toEqual(jasmine.any(Object));
});
it('should return the company and then call getData()', () => {
spyOn(controller, 'getData');
controller.companyId = 442;

View File

@ -77,18 +77,15 @@
<tpl-body>
<div>
<h5 style="text-align: center">
<span translate>From date</span>
<span translate>Send consumer report</span>
</h5>
<vn-date-picker
vn-id="from"
vn-one
ng-model="$ctrl.from"
label="From hour"
label="From date"
vn-focus>
</vn-date-picker>
<h5 style="text-align: center">
<span translate>To date</span>
</h5>
<vn-date-picker
vn-id="to"
vn-one

View File

@ -1,2 +1,4 @@
Simple ticket: Ticket simple
Send consumer report: Enviar informe de consumo
From date: Fecha desde
To date: Fecha hasta

View File

@ -68,6 +68,14 @@ module.exports = Self => {
type: 'Number',
description: 'The currency id to filter',
http: {source: 'query'}
}, {
arg: 'from',
type: 'Date',
description: `The from date filter`
}, {
arg: 'to',
type: 'Date',
description: `The to date filter`
}
],
returns: {
@ -91,6 +99,10 @@ module.exports = Self => {
return {[param]: {like: `%${value}%`}};
case 'created':
return {'e.created': {gte: value}};
case 'from':
return {'t.landed': {gte: value}};
case 'to':
return {'t.landed': {lte: value}};
case 'id':
case 'isBooked':
case 'isConfirmed':

View File

@ -0,0 +1,76 @@
module.exports = Self => {
Self.remoteMethod('getEntry', {
description: 'Returns an entry',
accessType: 'READ',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The entry id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getEntry`,
verb: 'GET'
}
});
Self.getEntry = async id => {
let filter = {
where: {id: id},
include: [
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
},
{
relation: 'travel',
scope: {
fields: ['id', 'name', 'shipped', 'landed', 'agencyFk', 'warehouseOutFk', 'warehouseInFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
},
{
relation: 'currency',
scope: {
fields: ['id', 'name']
}
},
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
}
],
};
let entry = await Self.app.models.Entry.findOne(filter);
return entry;
};
};

View File

@ -0,0 +1,31 @@
const app = require('vn-loopback/server/server');
describe('travel getEntry()', () => {
const entryId = 1;
it('should check the entry contains the id', async() => {
const entry = await app.models.Entry.getEntry(entryId);
expect(entry.id).toEqual(entryId);
});
it('should check the entry contains the supplier name', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const supplierName = entry.supplier().nickname;
expect(supplierName).toEqual('Plants nick');
});
it('should check the entry contains the receiver warehouse name', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const receiverWarehouseName = entry.travel().warehouseIn().name;
expect(receiverWarehouseName).toEqual('Warehouse One');
});
it('should check the entry contains the company code', async() => {
const entry = await app.models.Entry.getEntry(entryId);
const companyCode = entry.company().code;
expect(companyCode).toEqual('VNL');
});
});

View File

@ -1,4 +1,5 @@
module.exports = Self => {
require('../methods/entry/filter')(Self);
require('../methods/entry/getEntry')(Self);
};

View File

@ -39,6 +39,9 @@
"commission": {
"type": "Number"
},
"isOrdered": {
"type": "Boolean"
},
"created": {
"type": "date"
},

View File

@ -11,11 +11,34 @@ class Controller extends ModuleCard {
fields: ['id', 'code']
}
}, {
relation: 'travel'
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
}, {
relation: 'supplier',
scope: {
fields: ['id', 'name']
fields: ['id', 'nickname']
}
}, {
relation: 'currency'
@ -27,7 +50,7 @@ class Controller extends ModuleCard {
}
}
ngModule.component('vnEntry Card', {
ngModule.component('vnEntryCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -6,16 +6,37 @@
<a translate-attr="{title: 'Preview'}" ui-sref="entry.card.summary({id: $ctrl.entry.id})">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
<span></span>
<vn-icon-menu
vn-id="more-button"
icon="more_vert"
show-filter="false"
value-field="callback"
translate-fields="['name']"
data="$ctrl.moreOptions"
on-change="$ctrl.onMoreChange(value)"
on-open="$ctrl.onMoreOpen()">
</vn-icon-menu>
</div>
<div class="body">
<div class="attributes">
<vn-label-value label="Id"
value="{{$ctrl.entry.id}}">
</vn-label-value>
<vn-label-value label="Reference"
value="{{$ctrl.entry.ref}}">
<vn-label-value label="Supplier"
value="{{$ctrl.entry.supplier.nickname}}">
</vn-label-value>
<vn-label-value label="Agency "
value="{{$ctrl.entry.travel.agency.name}}">
</vn-label-value>
<vn-label-value label="Landed"
value="{{$ctrl.entry.travel.landed | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse Out"
value="{{$ctrl.entry.travel.warehouseOut.name}}">
</vn-label-value>
</div>
<vn-quick-links
links="$ctrl.quicklinks">
</vn-quick-links>
</div>
</div>

View File

@ -1,17 +1,80 @@
import ngModule from '../module';
import Component from 'core/lib/component';
class Controller {
constructor($scope) {
this.$ = $scope;
class Controller extends Component {
constructor($element, $, $httpParamSerializer, vnConfig) {
super($element, $);
this.vnConfig = vnConfig;
this.$httpParamSerializer = $httpParamSerializer;
this.moreOptions = [
{name: 'Show entry report', callback: this.showEntryReport}
];
}
onMoreChange(callback) {
callback.call(this);
}
get entry() {
return this._entry;
}
set entry(value) {
this._entry = value;
if (!value) return;
const date = value.travel.landed;
let to = new Date(date);
let from = new Date(date);
to.setDate(to.getDate() + 10);
to.setHours(0, 0, 0, 0);
from.setDate(from.getDate() - 10);
from.setHours(0, 0, 0, 0);
let links = {
btnOne: {
icon: 'local_airport',
state: `travel.index({q: '{"agencyFk": ${value.travel.agencyFk}}'})`,
tooltip: 'All travels with current agency'
}};
links.btnTwo = {
icon: 'icon-entry',
state: `entry.index({q: '{"supplierFk": ${value.supplierFk}, "to": "${to}", "from": "${from}"}'})`,
tooltip: 'All entries with current supplier'
};
this._quicklinks = links;
}
get quicklinks() {
return this._quicklinks;
}
set quicklinks(value = {}) {
this._quicklinks = Object.assign(value, this._quicklinks);
}
showEntryReport() {
const params = {
clientId: this.vnConfig.storage.currentUserWorkerId,
entryId: this.entry.id
};
const serializedParams = this.$httpParamSerializer(params);
let url = `api/report/entry-order?${serializedParams}`;
window.open(url);
}
}
Controller.$inject = ['$scope'];
Controller.$inject = ['$element', '$scope', '$httpParamSerializer', 'vnConfig'];
ngModule.component('vnEntryDescriptor', {
template: require('./index.html'),
bindings: {
entry: '<'
entry: '<',
quicklinks: '<'
},
require: {
card: '^?vnEntryCard'

View File

@ -0,0 +1,35 @@
import './index.js';
describe('Entry Component vnEntryDescriptor', () => {
let $httpParamSerializer;
let controller;
let $element;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, _$httpBackend_, $rootScope, _$httpParamSerializer_) => {
$httpParamSerializer = _$httpParamSerializer_;
$element = angular.element(`<vn-entry-descriptor></vn-entry-descriptor>`);
controller = $componentController('vnEntryDescriptor', {$element});
controller._entry = {id: 2};
controller.vnConfig.storage = {currentUserWorkerId: 9};
controller.cardReload = ()=> {
return true;
};
}));
describe('showEntryReport()', () => {
it('should open a new window showing a delivery note PDF document', () => {
const params = {
clientId: controller.vnConfig.storage.currentUserWorkerId,
entryId: controller.entry.id
};
const serializedParams = $httpParamSerializer(params);
let expectedPath = `api/report/entry-order?${serializedParams}`;
spyOn(window, 'open');
controller.showEntryReport();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
});

View File

@ -1 +1,4 @@
Reference: Referencia
All travels with current agency: Todos los envios con la agencia actual
All entries with current supplier: Todas las entradas con el proveedor actual
Show entry report: Ver informe del pedido

View File

@ -5,7 +5,4 @@ import './index/';
import './search-panel';
import './descriptor';
import './card';
// import './summary';
// import './basic-data';
// import './log';
// import './create';
import './summary';

View File

@ -16,7 +16,7 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th></vn-th>
<vn-th shrink></vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="landed" center>Landed</vn-th>
<vn-th>Reference</vn-th>
@ -33,18 +33,18 @@
<a ng-repeat="entry in entries"
class="clickable vn-tr search-result"
ui-sref="entry.card.summary({id: {{::entry.id}}})">
<vn-td>
<vn-td shrink>
<vn-icon
ng-show="entry.isInventory"
class="bright"
vn-tooltip="Inventory entry"
icon="icon-unavailable">
icon="icon-anonymous">
</vn-icon>
<vn-icon
ng-show="entry.isRaid"
class="bright"
vn-tooltip="Virtual entry"
icon="icon-100">
icon="icon-net">
</vn-icon>
</vn-td>
<vn-td number>{{::entry.id}}</vn-td>

View File

@ -28,6 +28,14 @@
"state": "entry.card",
"abstract": true,
"component": "vn-entry-card"
}, {
"url": "/summary",
"state": "entry.card.summary",
"component": "vn-entry-summary",
"description": "Summary",
"params": {
"entry": "$ctrl.entry"
}
}
]
}

View File

@ -54,6 +54,18 @@
ng-model="filter.created">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="From"
ng-model="filter.from">
</vn-date-picker>
<vn-date-picker
vn-one
label="To"
ng-model="filter.to">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-check
vn-one

View File

@ -0,0 +1,68 @@
<vn-card class="summary">
<h5><span translate>Entry</span> #{{$ctrl.entryData.id}} - {{$ctrl.entryData.supplier.nickname}}</h5>
<vn-horizontal>
<vn-one>
<vn-label-value label="Commission"
value="{{$ctrl.entryData.commission}}">
</vn-label-value>
<vn-label-value label="Currency"
value="{{$ctrl.entryData.currency.name}}">
</vn-label-value>
<vn-label-value label="Company"
value="{{$ctrl.entryData.company.code}}">
</vn-label-value>
<vn-label-value label="Reference"
value="{{$ctrl.entryData.ref}}">
</vn-label-value>
<vn-label-value label="Notes"
value="{{$ctrl.entryData.notes}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-label-value label="Agency"
value="{{$ctrl.entryData.travel.agency.name}}">
</vn-label-value>
<vn-label-value label="Shipped"
value="{{$ctrl.entryData.travel.shipped | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse Out"
value="{{$ctrl.entryData.travel.warehouseOut.name}}">
</vn-label-value>
<vn-label-value label="Landed"
value="{{$ctrl.entryData.travel.landed | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value label="Warehouse In"
value="{{$ctrl.entryData.travel.warehouseIn.name}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-vertical>
<vn-check
label="Ordered"
value="{{$ctrl.entryData.isOrdered}}"
disabled="true">
</vn-check>
<vn-check
label="Confirmed"
value="{{$ctrl.entryData.isConfirmed}}"
disabled="true">
</vn-check>
<vn-check
label="Booked"
value="{{$ctrl.entryData.isBooked}}"
disabled="true">
</vn-check>
<vn-check
label="Virtual"
value="{{$ctrl.entryData.isVirtual}}"
disabled="true">
</vn-check>
<vn-check
label="Inventory"
value="{{$ctrl.entryData.isInventory}}"
disabled="true">
</vn-check>
</vn-vertical>
</vn-one>
</vn-horizontal>
</vn-card>

View File

@ -0,0 +1,37 @@
import ngModule from '../module';
import './style.scss';
import Component from 'core/lib/component';
class Controller extends Component {
constructor($element, $, $httpParamSerializer) {
super($element, $);
this.$httpParamSerializer = $httpParamSerializer;
}
get entry() {
return this._entry;
}
set entry(value) {
this._entry = value;
if (value && value.id)
this.getEntryData();
}
getEntryData() {
return this.$http.get(`/api/Entries/${this.entry.id}/getEntry`).then(response => {
this.entryData = response.data;
});
}
}
Controller.$inject = ['$element', '$scope', '$httpParamSerializer'];
ngModule.component('vnEntrySummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -0,0 +1,50 @@
import './index';
describe('component vnEntrySummary', () => {
let controller;
let $httpBackend;
let $scope;
let $element;
beforeEach(angular.mock.module('entry', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$element = angular.element(`<vn-entry-summary></vn-entry-summary>`);
controller = $componentController('vnEntrySummary', {$element, $scope});
}));
describe('entry setter/getter', () => {
it('should check if value.id is defined', () => {
spyOn(controller, 'getEntryData');
controller.entry = {id: 1};
expect(controller.getEntryData).toHaveBeenCalledWith();
});
it('should return the entry and then call getEntryData()', () => {
spyOn(controller, 'getEntryData');
controller.entry = {id: 99};
expect(controller._entry.id).toEqual(99);
expect(controller.getEntryData).toHaveBeenCalledWith();
});
});
describe('getEntryData()', () => {
it('should perform a get and then store data on the controller', () => {
controller._entry = {id: 999};
const query = `/api/Entries/${controller._entry.id}/getEntry`;
$httpBackend.expectGET(query).respond('I am the entryData');
controller.getEntryData();
$httpBackend.flush();
expect(controller.entryData).toEqual('I am the entryData');
});
});
});

View File

@ -0,0 +1,3 @@
Inventory: Inventario
Virtual: Redada
Entry: Entrada

View File

@ -0,0 +1,10 @@
@import "variables";
vn-entry-summary .summary {
max-width: $width-lg;
vn-icon[icon=insert_drive_file]{
color: $color-font-secondary;
}
}

View File

@ -8,22 +8,22 @@
}
},
"properties": {
"code": {
"type": "String",
"id": true,
"description": "Identifier"
},
"alertLevel": {
"type": "Number",
"id": true
}
"code": {
"type": "String",
"id": true,
"description": "Identifier"
},
"alertLevel": {
"type": "Number",
"id": true
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}
}

View File

@ -20,12 +20,6 @@ describe('component vnTravelSummary', () => {
}));
describe('travel setter/getter', () => {
it('should return the travel', () => {
controller.travel = {id: null};
expect(controller.travel).toEqual(jasmine.any(Object));
});
it('should return the travel and then call both getTravel() and getEntries()', () => {
spyOn(controller, 'getTravel');
spyOn(controller, 'getEntries');