refs #6553 changes models #2522

Open
carlossa wants to merge 22 commits from 6553-workerBusiness into dev
115 changed files with 399 additions and 4418 deletions
Showing only changes of commit 0486c40454 - Show all commits

View File

@ -12,6 +12,12 @@
"required": true,
"id": true
},
"sectorFromCode": {
"type": "string"
},
"sectorToCode": {
"type": "string"
},
"backupPrinterNotificationDelay": {
"type": "string"
}

View File

@ -1,5 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `util`.`debugAdd`(vVariable VARCHAR(255), vValue VARCHAR(255))
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `util`.`debugAdd`(
vVariable VARCHAR(255),
vValue TEXT
)
MODIFIES SQL DATA
BEGIN
/**

View File

@ -28,6 +28,8 @@ BEGIN
DECLARE vLockName VARCHAR(215);
DECLARE vLockTime INT DEFAULT 30;
DECLARE vFreeWagonFk INT;
DECLARE vErrorNumber INT;
DECLARE vErrorMsg TEXT;
DECLARE c1 CURSOR FOR
SELECT ticketFk, `lines`, m3
@ -47,12 +49,18 @@ BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
vErrorNumber = MYSQL_ERRNO,
vErrorMsg = MESSAGE_TEXT;
IF vLockName IS NOT NULL THEN
DO RELEASE_LOCK(vLockName);
CALL util.debugAdd(JSON_OBJECT(
'type', 'releaseLock',
CALL util.debugAdd('collection_new', JSON_OBJECT(
'errorNumber', vErrorNumber,
'errorMsg', vErrorMsg,
'lockName', vLockName,
'userFk', vUserFk
), vLockName); -- Tmp
)); -- Tmp
END IF;
RESIGNAL;

View File

@ -30,6 +30,7 @@ BEGIN
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
WHERE t.shipped BETWEEN vDatedFrom AND vDatedTo
AND t.warehouseFk
AND s.quantity != 0
AND (vItemFk IS NULL OR s.itemFk = vItemFk)
AND (vWarehouseFk IS NULL OR t.warehouseFk = vWarehouseFk)
@ -40,7 +41,7 @@ BEGIN
t.warehouseInFk
FROM buy b
JOIN entry e ON e.id = b.entryFk
LEFT JOIN travel t ON t.id = e.travelFk
JOIN travel t ON t.id = e.travelFk
WHERE t.landed BETWEEN vDatedFrom AND vDatedTo
AND (vWarehouseFk IS NULL OR t.warehouseInFk = vWarehouseFk)
AND !e.isExcludedFromAvailable
@ -53,7 +54,7 @@ BEGIN
t.warehouseOutFk
FROM buy b
JOIN entry e ON e.id = b.entryFk
LEFT JOIN travel t ON t.id = e.travelFk
JOIN travel t ON t.id = e.travelFk
WHERE t.shipped BETWEEN vDatedFrom AND vDatedTo
AND (vWarehouseFk IS NULL OR t.warehouseOutFk = vWarehouseFk)
AND !e.isExcludedFromAvailable

View File

@ -5,10 +5,10 @@ CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`supplierPackaging_R
)
BEGIN
/**
* Selecciona los embalajes de un proveedor a partir de una fecha
* Create a report with packaging balance
*
* @param vFromDated Fecha de la que partir
* @param vSupplierFk Id del proveedor
* @param vFromDated Starting date
* @param vSupplierFk Supplier ID
*/
SET @vBalance = 0;
SET @vItemFk = NULL;
@ -99,6 +99,7 @@ BEGIN
WHERE su.id = vSupplierFk
AND t.shipped < vFromDated
AND p.isPackageReturnable
AND NOT t.isDeleted
GROUP BY s.itemFk
UNION ALL
SELECT vSupplierFk,
@ -138,8 +139,9 @@ BEGIN
JOIN client c ON c.id = t.clientFk
JOIN supplier su ON su.nif = c.fi
WHERE su.id = vSupplierFk
AND t.shipped >= vFromDated
AND t.shipped < vFromDated
AND p.isPackageReturnable
AND NOT t.isDeleted
GROUP BY p.itemFk
ORDER BY itemFk, landed, entryFk
) sub

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`clientUnpaid_beforeInsert`
BEFORE INSERT ON `clientUnpaid`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`clientUnpaid_beforeUpdate`
BEFORE UPDATE ON `clientUnpaid`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,5 @@
-- Place your SQL code here
USE vn;
ALTER TABLE vn.productionConfig ADD itemOlderReviewHours int(11) DEFAULT 0 NOT NULL COMMENT 'Horas que se tienen en cuenta para comprobar si un ítem es más viejo.';

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.client
CHANGE hasIncoterms hasIncoterms__ tinyint(1) DEFAULT 0 NOT NULL
COMMENT '@deprecated 2024-06-12 refs #7545 Received incoterms authorization from client';

View File

@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS vn.travelKgPercentage (
value INT(3) PRIMARY KEY,
className VARCHAR(50)
);
INSERT INTO vn.travelKgPercentage (value, className)
VALUES
(80, 'primary'),
(100, 'alert');
INSERT INTO salix.ACL
SET model = 'TravelKgPercentage',
property = '*',
accessType = 'READ',
permission = 'ALLOW',
principalType = 'ROLE',
principalId = 'employee';

View File

@ -0,0 +1,2 @@
ALTER TABLE vn.ticketPackaging DROP FOREIGN KEY ticketPackaging_fk3;
ALTER TABLE vn.ticketPackaging DROP COLUMN workerFk;

View File

@ -0,0 +1,9 @@
-- Place your SQL code here
USE vn;
ALTER TABLE vn.productionConfig ADD sectorFromCode varchar(15) NULL COMMENT 'Sector origen que se revisa ítems más nuevos al parkinear';
ALTER TABLE vn.productionConfig ADD sectorToCode varchar(15) NULL COMMENT 'Sector destino que se revisa ítems más nuevos al parkinear';
ALTER TABLE vn.productionConfig ADD CONSTRAINT productionConfig_sector_FK FOREIGN KEY (sectorFromCode) REFERENCES vn.sector(code) ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE vn.productionConfig ADD CONSTRAINT productionConfig_sector_FK_1 FOREIGN KEY (sectorToCode) REFERENCES vn.sector(code) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,14 @@
-- Place your SQL code here
USE vn;
ALTER TABLE vn.itemShelving DROP FOREIGN KEY itemShelvingBuy_FK;
ALTER TABLE vn.itemShelving DROP FOREIGN KEY itemShelving_fk2;
ALTER TABLE vn.itemShelving DROP INDEX itemShelving_UN;
ALTER TABLE vn.itemShelving ADD CONSTRAINT itemShelving_fk2 FOREIGN KEY (shelvingFk) REFERENCES vn.shelving(code) ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE vn.itemShelving ADD CONSTRAINT itemShelvingBuy_FK FOREIGN KEY (buyFk) REFERENCES vn.buy(id) ON DELETE RESTRICT ON UPDATE RESTRICT;

View File

@ -0,0 +1,4 @@
ALTER TABLE vn.clientUnpaid
ADD editorFk INT UNSIGNED NULL,
ADD CONSTRAINT ClientUnpaid_editorFk FOREIGN KEY (editorFk)
REFERENCES account.`user`(id);

View File

@ -33,106 +33,4 @@ describe('Travel descriptor path', () => {
expect(state).toBe('travel.card.summary');
});
it('should be redirected to the create entry view', async() => {
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuAddEntry);
await page.waitForState('entry.create');
const state = await page.getState();
expect(state).toBe('entry.create');
});
it('should check some data was imported from the travel', async() => {
const travel = await page.waitToGetProperty(selectors.entryCreate.travel, 'value');
const campany = await page.waitToGetProperty(selectors.entryCreate.company, 'value');
expect(travel).toContain('Warehouse');
expect(campany).toContain('VNL');
});
it('should navigate back to the travel index', async() => {
await page.waitToClick('.cancel');
await page.waitToClick(selectors.globalItems.homeButton);
await page.selectModule('travel');
await page.waitForState('travel.index');
const state = await page.getState();
expect(state).toBe('travel.index');
});
it('should click on the add entry button of the third result to be redirected to create entry', async() => {
await page.keyboard.press('Enter');
await page.waitToClick(selectors.travelIndex.firstTravelAddEntryButton);
await page.waitForState('entry.create');
const state = await page.getState();
expect(state).toBe('entry.create');
});
it('should check again some data was imported from the travel', async() => {
const travel = await page.waitToGetProperty(selectors.entryCreate.travel, 'value');
const campany = await page.waitToGetProperty(selectors.entryCreate.company, 'value');
expect(travel).toContain('Warehouse');
expect(campany).toContain('VNL');
});
it('should navigate to the travel summary of a given travel', async() => {
await page.waitToClick('.cancel');
await page.waitToClick(selectors.globalItems.homeButton);
await page.selectModule('travel');
await page.write(selectors.travelIndex.generalSearchFilter, '3');
await page.keyboard.press('Enter');
await page.waitForState('travel.card.summary');
const state = await page.getState();
expect(state).toBe('travel.card.summary');
});
it('should be redirected to the create travel when using the clone option of the dot menu', async() => {
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuClone);
await page.respondToDialog('accept');
await page.waitForState('travel.create');
const state = await page.getState();
expect(state).toBe('travel.create');
});
it('should edit the data to clone and then get redirected to the cloned travel basic data', async() => {
await page.clearInput(selectors.travelCreate.reference);
await page.write(selectors.travelCreate.reference, 'reference');
await page.autocompleteSearch(selectors.travelCreate.agency, 'entanglement');
await page.pickDate(selectors.travelCreate.shipped);
await page.pickDate(selectors.travelCreate.landed);
await page.autocompleteSearch(selectors.travelCreate.warehouseOut, 'warehouse one');
await page.autocompleteSearch(selectors.travelCreate.warehouseIn, 'warehouse two');
await page.waitToClick(selectors.travelCreate.saveButton);
await page.waitForState('travel.card.basicData');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should update the landed date to a future date to enable cloneWithEntries', async() => {
const nextMonth = Date.vnNew();
nextMonth.setMonth(nextMonth.getMonth() + 1);
await page.pickDate(selectors.travelBasicData.deliveryDate, nextMonth);
await page.waitToClick(selectors.travelBasicData.save);
await page.waitForState('travel.card.basicData');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should navigate to the summary and then clone the travel and its entries using the descriptor menu to get redirected to the cloned travel basic data', async() => {
await page.waitToClick('vn-icon[icon="launch"]');
await page.waitForState('travel.card.summary');
await page.waitForTimeout(1000);
await page.waitToClick(selectors.travelDescriptor.dotMenu);
await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries);
await page.waitToClick(selectors.travelDescriptor.acceptClonation);
await page.waitForState('travel.card.basicData');
});
});

View File

@ -1,47 +0,0 @@
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.accessToSearchResult('4');
});
afterAll(async() => {
await browser.close();
});
it('should reach the second entry summary section', async() => {
await page.waitForState('entry.card.summary');
});
it(`should display details from the entry on the header`, async() => {
await page.waitForTextInElement(selectors.entrySummary.header, 'The farmer');
const result = await page.waitToGetProperty(selectors.entrySummary.header, 'innerText');
expect(result).toContain('The farmer');
});
it('should display some entry details like the reference', async() => {
const result = await page.waitToGetProperty(selectors.entrySummary.reference, 'innerText');
expect(result).toContain('Movement 4');
});
it('should display other entry details like the confirmed', async() => {
const result = await page.checkboxState(selectors.entrySummary.confirmed, 'innerText');
expect(result).toContain('unchecked');
});
it('should display all buys for the entry', async() => {
const result = await page.countElement(selectors.entrySummary.anyBuyLine);
expect(result).toEqual(4);
});
});

View File

@ -1,49 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry descriptor path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('2');
});
afterAll(async() => {
await browser.close();
});
it('should reach the second entry summary section', async() => {
await page.waitForState('entry.card.summary');
});
it('should show some entry information', async() => {
const result = await page.waitToGetProperty(selectors.entryDescriptor.agency, 'innerText');
expect(result).toContain('inhouse pickup');
});
it('should click the travels button to be redirected to the travels index filtered by the current agency', async() => {
await page.waitToClick(selectors.entryDescriptor.travelsQuicklink);
await page.expectURL('/travel/index');
await page.expectURL('agencyModeFk');
});
it('should go back to the entry summary', async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.selectModule('entry');
await page.accessToSearchResult('2');
await page.waitForState('entry.card.summary');
});
it('should click the entries button to be redirected to the entries index filtered by the current supplier', async() => {
await page.waitToClick(selectors.entryDescriptor.entriesQuicklink);
await page.expectURL('/entry/index');
await page.expectURL('supplierFk');
await page.expectURL('to');
await page.expectURL('from');
});
});

View File

@ -1,116 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry lastest buys path', () => {
let browser;
let page;
const httpRequests = [];
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
page.on('request', req => {
if (req.url().includes(`Buys/latestBuysFilter`))
httpRequests.push(req.url());
});
await page.loginAndModule('buyer', 'entry');
});
afterAll(async() => {
await browser.close();
});
it('should access the latest buys seccion and search not seeing the edit buys button yet', async() => {
await page.waitToClick(selectors.entryLatestBuys.latestBuysSectionButton);
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: false});
});
it('should filter by name', async() => {
await page.write(selectors.entryLatestBuys.generalSearchInput, 'Melee');
await page.keyboard.press('Enter');
await page.waitToClick(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('search=Melee')))).toBeDefined();
});
it('should filter by reign and type', async() => {
await page.click(selectors.entryLatestBuys.firstReignIcon);
await page.autocompleteSearch(selectors.entryLatestBuys.typeInput, 'Alstroemeria');
await page.click(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('categoryFk')))).toBeDefined();
expect(httpRequests.find(req => req.includes(('typeFk')))).toBeDefined();
});
it('should filter by sales person', async() => {
await page.autocompleteSearch(selectors.entryLatestBuys.salesPersonInput, 'buyerNick');
await page.waitToClick(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('salesPersonFk')))).toBeDefined();
});
it('should filter by supplier', async() => {
await page.autocompleteSearch(selectors.entryLatestBuys.supplierInput, 'Farmer King');
await page.waitToClick(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('supplierFk')))).toBeDefined();
});
it('should filter by active', async() => {
await page.waitToClick(selectors.entryLatestBuys.activeCheck);
await page.waitToClick(selectors.entryLatestBuys.activeCheck);
await page.waitToClick(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('active=true')))).toBeDefined();
expect(httpRequests.find(req => req.includes(('active=false')))).toBeDefined();
});
it('should filter by visible', async() => {
await page.waitToClick(selectors.entryLatestBuys.visibleCheck);
await page.waitToClick(selectors.entryLatestBuys.visibleCheck);
await page.waitToClick(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('visible=true')))).toBeDefined();
expect(httpRequests.find(req => req.includes(('visible=false')))).toBeDefined();
});
it('should filter by floramondo', async() => {
await page.waitToClick(selectors.entryLatestBuys.floramondoCheck);
await page.waitToClick(selectors.entryLatestBuys.floramondoCheck);
await page.waitToClick(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('floramondo=true')))).toBeDefined();
expect(httpRequests.find(req => req.includes(('floramondo=false')))).toBeDefined();
});
it('should filter by tag Color', async() => {
await page.waitToClick(selectors.entryLatestBuys.addTagButton);
await page.autocompleteSearch(selectors.entryLatestBuys.itemTagInput, 'Color');
await page.autocompleteSearch(selectors.entryLatestBuys.itemTagValueInput, 'Brown');
await page.waitToClick(selectors.entryLatestBuys.chip);
expect(httpRequests.find(req => req.includes(('tags')))).toBeDefined();
});
it('should select all lines but one and then check the edit buys button appears', async() => {
await page.waitToClick(selectors.entryLatestBuys.allBuysCheckBox);
await page.waitToClick(selectors.entryLatestBuys.secondBuyCheckBox);
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: true});
});
it('should open the edit dialog', async() => {
await page.waitToClick(selectors.entryLatestBuys.editBuysButton);
await page.waitForSelector(selectors.entryLatestBuys.fieldAutocomplete, {visible: true});
});
it('should search for the "Description" and type a new one for the items in each selected buy', async() => {
await page.autocompleteSearch(selectors.entryLatestBuys.fieldAutocomplete, 'Description');
await page.write(selectors.entryLatestBuys.newValueInput, 'Crafted item');
await page.waitToClick(selectors.entryLatestBuys.acceptEditBuysDialog);
});
it('should navigate to the entry.buy section by clicking one of the buys', async() => {
await page.waitToClick(selectors.entryLatestBuys.firstBuy);
await page.waitForState('entry.card.buy.index');
});
});

View File

@ -1,34 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry create path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
});
afterAll(async() => {
await browser.close();
});
it('should click the create entry button to open the form', async() => {
await page.waitToClick(selectors.entryIndex.createEntryButton);
await page.waitForState('entry.create');
});
it('should fill the form to create a valid entry then redirect to basic Data', async() => {
await page.autocompleteSearch(selectors.entryIndex.newEntrySupplier, 'The farmer');
await page.autocompleteSearch(selectors.entryIndex.newEntryTravel, 'Warehouse');
await page.autocompleteSearch(selectors.entryIndex.newEntryCompany, 'ORN');
await page.waitToClick(selectors.entryIndex.saveNewEntry);
await page.waitForNavigation({
waitUntil: 'load',
});
await page.waitForState('entry.card.basicData');
});
});

View File

@ -1,81 +0,0 @@
import getBrowser from '../../helpers/puppeteer';
const $ = {
reference: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.reference"]',
invoiceNumber: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.invoiceNumber"]',
notes: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.notes"]',
observations: 'vn-entry-basic-data vn-textarea[ng-model="$ctrl.entry.observation"]',
supplier: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]',
currency: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.currencyFk"]',
commission: 'vn-entry-basic-data vn-input-number[ng-model="$ctrl.entry.commission"]',
company: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
ordered: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isOrdered"]',
confirmed: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isConfirmed"]',
inventory: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isExcludedFromAvailable"]',
raid: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isRaid"]',
booked: 'vn-entry-basic-data vn-check[ng-model="$ctrl.entry.isBooked"]',
save: 'vn-entry-basic-data button[type=submit]',
};
describe('Entry basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('2');
await page.accessToSection('entry.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should edit the basic data and confirm the reference was edited', async() => {
await page.write($.reference, 'new movement 8');
await page.write($.invoiceNumber, 'new movement 8');
await page.write($.observations, ' edited');
await page.autocompleteSearch($.supplier, 'Plants nick');
await page.autocompleteSearch($.currency, 'eur');
await page.clearInput($.commission);
await page.write($.commission, '100');
await page.autocompleteSearch($.company, 'CCs');
await page.waitToClick($.ordered);
await page.waitToClick($.confirmed);
await page.waitToClick($.inventory);
await page.waitToClick($.raid);
await page.waitToClick($.booked);
await page.waitToClick($.save);
const message = await page.waitForSnackbar();
await page.reloadSection('entry.card.basicData');
const reference = await page.waitToGetProperty($.reference, 'value');
const supplier = await page.waitToGetProperty($.supplier, 'value');
const invoiceNumber = await page.waitToGetProperty($.invoiceNumber, 'value');
const observations = await page.waitToGetProperty($.observations, 'value');
const currency = await page.waitToGetProperty($.currency, 'value');
const commission = await page.waitToGetProperty($.commission, 'value');
const company = await page.waitToGetProperty($.company, 'value');
const ordered = await page.checkboxState($.ordered);
const confirmed = await page.checkboxState($.confirmed);
const inventory = await page.checkboxState($.inventory);
const raid = await page.checkboxState($.raid);
const booked = await page.checkboxState($.booked);
expect(message.text).toContain('Data saved!');
expect(reference).toEqual('new movement 8');
expect(supplier).toEqual('Plants nick');
expect(invoiceNumber).toEqual('new movement 8');
expect(observations).toEqual('observation two edited');
expect(currency).toEqual('EUR');
expect(commission).toEqual('100');
expect(company).toEqual('CCs');
expect(ordered).toBe('checked');
expect(confirmed).toBe('checked');
expect(inventory).toBe('checked');
expect(raid).toBe('checked');
expect(booked).toBe('unchecked');
});
});

View File

@ -1,65 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry observations path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('2');
await page.accessToSection('entry.card.observation');
});
afterAll(async() => {
await browser.close();
});
it(`should add two new observations of the same type then fail to save as they can't be repeated`, async() => {
await page.waitToClick(selectors.entryObservations.addNewObservation);
await page.waitToClick(selectors.entryObservations.addNewObservation);
await page.autocompleteSearch(selectors.entryObservations.firstObservationType, 'SalesPerson');
await page.autocompleteSearch(selectors.entryObservations.secondObservationType, 'SalesPerson');
await page.write(selectors.entryObservations.firstObservationDescription, 'first observation');
await page.write(selectors.entryObservations.secondObservationDescription, 'second observation');
await page.waitToClick(selectors.entryObservations.saveObservationsButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`The observation type can't be repeated`);
});
it('should set the 2nd observation of a different one and successfully save both', async() => {
await page.autocompleteSearch(selectors.entryObservations.secondObservationType, 'Delivery');
await page.waitToClick(selectors.entryObservations.saveObservationsButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the section and make sure the first observation type was saved correctly', async() => {
await page.reloadSection('entry.card.observation');
const result = await page.waitToGetProperty(selectors.entryObservations.firstObservationType, 'value');
expect(result).toEqual('SalesPerson');
});
it('should make sure the first observation description was saved correctly', async() => {
const result = await page.waitToGetProperty(selectors.entryObservations.firstObservationDescription, 'value');
expect(result).toEqual('first observation');
});
it('should make sure the second observation type was saved correctly', async() => {
const result = await page.waitToGetProperty(selectors.entryObservations.secondObservationType, 'value');
expect(result).toEqual('Delivery');
});
it('should make sure the second observation description was saved correctly', async() => {
const result = await page.waitToGetProperty(selectors.entryObservations.secondObservationDescription, 'value');
expect(result).toEqual('second observation');
});
});

View File

@ -1,69 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Entry import, create and edit buys path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('3');
});
afterAll(async() => {
await browser.close();
});
it('should count the summary buys and find there only one at this point', async() => {
const buysCount = await page.countElement(selectors.entrySummary.anyBuyLine);
expect(buysCount).toEqual(2);
});
it('should navigate to the buy section and then click the import button opening the import form', async() => {
await page.accessToSection('entry.card.buy.index');
await page.waitToClick(selectors.entryBuys.importButton);
await page.waitForState('entry.card.buy.import');
});
it('should fill the form, import the a JSON file and select items for each import and confirm import', async() => {
let currentDir = process.cwd();
let filePath = `${currentDir}/e2e/assets/07_import_buys.json`;
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
page.waitToClick(selectors.entryBuys.file)
]);
await fileChooser.accept([filePath]);
await page.waitForTextInField(selectors.entryBuys.ref, '200573095, 200573106, 200573117, 200573506');
await page.waitForTextInField(selectors.entryBuys.observation, '729-6340 2846');
await page.autocompleteSearch(selectors.entryBuys.firstImportedItem, 'Ranged weapon longbow 200cm');
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, 'Ranged weapon longbow 200cm');
await page.autocompleteSearch(selectors.entryBuys.thirdImportedItem, 'Ranged weapon sniper rifle 113cm');
await page.autocompleteSearch(selectors.entryBuys.fourthImportedItem, 'Melee weapon heavy shield 100cm');
await page.waitToClick(selectors.entryBuys.importBuysButton);
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.text).toContain('Data saved!');
expect(state).toBe('entry.card.buy.index');
});
it('should count the buys to find 4 buys have been added', async() => {
await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 6);
});
it('should delete the four buys that were just added', async() => {
await page.waitToClick(selectors.entryBuys.allBuyCheckbox);
await page.waitToClick(selectors.entryBuys.firstBuyCheckbox);
await page.waitToClick(selectors.entryBuys.deleteBuysButton);
await page.waitToClick(selectors.globalItems.acceptButton);
await page.waitForNumberOfElements(selectors.entryBuys.anyBuyLine, 1);
});
});

View File

@ -65,11 +65,6 @@ describe('Supplier summary & descriptor path', () => {
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');

View File

@ -1,27 +1,27 @@
export default function moduleImport(moduleName) {
// TODO: Webpack watches module backend files when using dynamic import
//return import(
// return import(
// /* webpackInclude: /modules\/[a-z0-9-]+\/front\/index.js$/ */
// '../modules/'+ moduleName +'/front/index.js'
//);
// );
switch(moduleName) {
case 'client' : return import('client/front');
case 'item' : return import('item/front');
case 'ticket' : return import('ticket/front');
case 'order' : return import('order/front');
case 'claim' : return import('claim/front');
case 'zone' : return import('zone/front');
case 'travel' : return import('travel/front');
case 'worker' : return import('worker/front');
case 'invoiceOut' : return import('invoiceOut/front');
case 'invoiceIn' : return import('invoiceIn/front');
case 'route' : return import('route/front');
case 'entry' : return import('entry/front');
case 'account' : return import('account/front');
case 'supplier' : return import('supplier/front');
case 'shelving' : return import('shelving/front');
case 'monitor' : return import('monitor/front');
switch (moduleName) {
case 'client': return import('client/front');
case 'item': return import('item/front');
case 'ticket': return import('ticket/front');
case 'order': return import('order/front');
case 'claim': return import('claim/front');
case 'zone': return import('zone/front');
case 'travel': return import('travel/front');
case 'worker': return import('worker/front');
case 'invoiceOut': return import('invoiceOut/front');
case 'invoiceIn': return import('invoiceIn/front');
case 'route': return import('route/front');
case 'account': return import('account/front');
case 'supplier': return import('supplier/front');
case 'shelving': return import('shelving/front');
case 'monitor': return import('monitor/front');
case 'entry': return import('entry/front');
}
}

View File

@ -232,5 +232,6 @@
"Incoterms and Customs agent are required for a non UEE member": "Incoterms and Customs agent are required for a non UEE member",
"It has been invoiced but the PDF could not be generated": "It has been invoiced but the PDF could not be generated",
"It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated",
"Cannot add holidays on this day": "Cannot add holidays on this day"
"Cannot add holidays on this day": "Cannot add holidays on this day",
"Cannot send mail": "Cannot send mail"
}

View File

@ -351,7 +351,7 @@
"You are not allowed to modify the alias": "No estás autorizado a modificar el alias",
"The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas",
"The line could not be marked": "La linea no puede ser marcada",
"This password can only be changed by the user themselves": "Esta contraseña solo puede ser modificada por el propio usuario",
"Through this procedure, it is not possible to modify the password of users with verified email": "Mediante este procedimiento, no es posible modificar la contraseña de usuarios con correo verificado",
"They're not your subordinate": "No es tu subordinado/a.",
"No results found": "No se han encontrado resultados",
"InvoiceIn is already booked": "La factura recibida está contabilizada",
@ -365,5 +365,6 @@
"You can only have one PDA": "Solo puedes tener un PDA",
"It has been invoiced but the PDF could not be generated": "Se ha facturado pero no se ha podido generar el PDF",
"It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono",
"Payment method is required": "El método de pago es obligatorio"
"Payment method is required": "El método de pago es obligatorio",
"Cannot send mail": "Não é possível enviar o email"
}

View File

@ -359,5 +359,6 @@
"Select ticket or client": "Choisissez un ticket ou un client",
"It was not able to create the invoice": "Il n'a pas été possible de créer la facture",
"It has been invoiced but the PDF could not be generated": "La facture a été émise mais le PDF n'a pas pu être généré",
"It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré"
"It has been invoiced but the PDF of refund not be generated": "Il a été facturé mais le PDF de remboursement n'a pas été généré",
"Cannot send mail": "Impossible d'envoyer le mail"
}

View File

@ -13,7 +13,11 @@ module.exports = Self => {
Self.setUnverifiedPassword = async(id, pass, options) => {
const {emailVerified} = await models.VnUser.findById(id, {fields: ['emailVerified']}, options);
if (emailVerified) throw new ForbiddenError('This password can only be changed by the user themselves');
if (emailVerified) {
throw new ForbiddenError(
'Through this procedure, it is not possible to modify the password of users with verified email'
);
}
await models.VnUser.setPassword(id, pass, options);
};

View File

@ -0,0 +1,5 @@
name: unpaid
columns:
clientFk: client
dated: date
amount: amount

View File

@ -0,0 +1,5 @@
name: impagado
columns:
clientFk: cliente
dated: fecha
amount: cantidad

View File

@ -96,10 +96,6 @@ module.exports = Self => {
arg: 'despiteOfClient',
type: 'any'
},
{
arg: 'hasIncoterms',
type: 'boolean'
},
{
arg: 'hasElectronicInvoice',
type: 'boolean'

View File

@ -1,6 +1,9 @@
{
"name": "ClientUnpaid",
"base": "VnModel",
"mixins": {
"Loggable": true
},
"options": {
"mysql": {
"table": "clientUnpaid"

View File

@ -97,9 +97,6 @@
"hasCoreVnh": {
"type": "boolean"
},
"hasIncoterms": {
"type": "boolean"
},
"isTaxDataChecked":{
"type": "boolean"
},

View File

@ -184,11 +184,6 @@
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-check
label="Incoterms authorization"
ng-model="$ctrl.client.hasIncoterms"
vn-acl="administrative">
</vn-check>
<vn-check
label="Electronic invoice"
ng-model="$ctrl.client.hasElectronicInvoice"

View File

@ -1,234 +0,0 @@
<mg-ajax path="Entries/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.entry"
form="form"
save="patch">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form name="form" ng-submit="watcher.submit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.entry.supplierFk"
url="Suppliers"
show-field="nickname"
search-function="{or: [{id: $search}, {nickname: {like: '%'+ $search +'%'}}]}"
value-field="id"
order="nickname"
label="Supplier"
required="true">
<tpl-item>
<div>#{{::nickname}}</div>
<div class="text-secondary text-caption">#{{::id}}</div>
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
vn-one
ng-model="$ctrl.entry.travelFk"
url="Travels/filter"
search-function="$ctrl.searchFunction($search)"
value-field="id"
show-field="warehouseInName"
order="id"
label="Travel"
required="true">
<tpl-item>
<div>
{{::agencyModeName}} - {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}}) &#x2192;
{{::warehouseOutName}} ({{::landed | date: 'dd/MM/yyyy'}})
</div>
<div class="text-secondary text-caption">#{{::id}}</div>
</tpl-item>
<append>
<vn-icon-button
icon="filter_alt"
vn-click-stop="$ctrl.showFilterDialog($ctrl.entry.travelFk)"
vn-tooltip="Filter...">
</vn-icon-button>
</append>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Reference"
ng-model="$ctrl.entry.reference"
rule
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Invoice number"
ng-model="$ctrl.entry.invoiceNumber"
rule
vn-focus>
</vn-textfield>
<vn-autocomplete
url="Companies"
label="Company"
show-field="code"
value-field="id"
ng-model="$ctrl.entry.companyFk">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Currency"
ng-model="$ctrl.entry.currencyFk"
url="Currencies"
show-field="code"
value-field="id">
</vn-autocomplete>
<vn-input-number
vn-one
step="0.1"
label="Commission"
ng-model="$ctrl.entry.commission"
rule>
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
label="Observation"
ng-model="$ctrl.entry.observation"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-check
label="Ordered"
ng-model="$ctrl.entry.isOrdered">
</vn-check>
<vn-check
label="Confirmed"
ng-model="$ctrl.entry.isConfirmed">
</vn-check>
<vn-check
label="Inventory"
ng-model="$ctrl.entry.isExcludedFromAvailable">
</vn-check>
<vn-check
label="Raid"
ng-model="$ctrl.entry.isRaid">
</vn-check>
<vn-check
label="Booked"
ng-model="$ctrl.entry.isBooked"
vn-acl="administrative"
>
</vn-check>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>
<!-- Filter travel dialog -->
<vn-dialog
vn-id="filterDialog"
message="Filter travel">
<tpl-body class="travelFilter">
<vn-horizontal>
<vn-autocomplete
label="Agency"
ng-model="$ctrl.travelFilterParams.agencyModeFk"
url="AgencyModes"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Warehouse Out"
ng-model="$ctrl.travelFilterParams.warehouseOutFk"
data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Warehouse In"
ng-model="$ctrl.travelFilterParams.warehouseInFk"
data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-date-picker
label="Shipped"
ng-model="$ctrl.travelFilterParams.shipped">
</vn-date-picker>
<vn-date-picker
label="Landed"
ng-model="$ctrl.travelFilterParams.landed">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal class="vn-mb-md">
<vn-button vn-none
label="Search"
ng-click="$ctrl.filter()">
</vn-button>
</vn-horizontal>
<vn-crud-model
vn-id="travelsModel"
url="Travels"
filter="$ctrl.travelFilter"
data="travels"
limit="10">
</vn-crud-model>
<vn-data-viewer
model="travelsModel"
class="vn-w-lg">
<vn-table class="scrollable">
<vn-thead>
<vn-tr>
<vn-th shrink>ID</vn-th>
<vn-th expand>Agency</vn-th>
<vn-th expand>Warehouse Out</vn-th>
<vn-th expand>Warehouse In</vn-th>
<vn-th expand>Shipped</vn-th>
<vn-th expand>Landed</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="travel in travels"
class="clickable vn-tr search-result"
ng-click="$ctrl.selectTravel(travel.id)">
<vn-td shrink>
<span
vn-click-stop="travelDescriptor.show($event, travel.id)"
class="link">
{{::travel.id}}
</span>
</vn-td>
<vn-td expand>{{::travel.agency.name}}</vn-td>
<vn-td expand>{{::travel.warehouseOut.name}}</vn-td>
<vn-td expand>{{::travel.warehouseIn.name}}</vn-td>
<vn-td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-data-viewer>
<vn-travel-descriptor-popover
vn-id="travel-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-travel-descriptor-popover>
</tpl-body>
</vn-dialog>

View File

@ -1,68 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
showFilterDialog(travel) {
this.activeTravel = travel;
this.travelFilterParams = {};
this.travelFilter = {
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
}
]
};
this.$.filterDialog.show();
}
selectTravel(id) {
this.entry.travelFk = id;
this.$.filterDialog.hide();
}
filter() {
const filter = this.travelFilter;
const params = this.travelFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'agencyModeFk':
case 'warehouseInFk':
case 'warehouseOutFk':
case 'shipped':
case 'landed':
where[key] = value;
}
}
filter.where = where;
this.$.travelsModel.applyFilter(filter);
}
}
ngModule.vnComponent('vnEntryBasicData', {
template: require('./index.html'),
bindings: {
entry: '<'
},
controller: Controller
});

View File

@ -1,3 +0,0 @@
.travelFilter{
width: 950px;
}

View File

@ -1,205 +0,0 @@
<mg-ajax path="dms/upload" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-ma-md">
<div class="vn-w-lg">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-input-file
vn-one
label="File"
ng-model="$ctrl.import.file"
on-change="$ctrl.onFileChange($event)"
accept="application/json"
required="true">
<append>
<vn-icon vn-none
color-marginal
title="{{'JSON files only' | translate}}"
icon="info">
</vn-icon>
</append>
</vn-input-file>
</vn-horizontal>
<vn-horizontal ng-show="$ctrl.import.ref">
<vn-textfield vn-focus
vn-one
label="Reference"
ng-model="$ctrl.import.ref">
</vn-textfield>
</vn-horizontal>
<vn-horizontal ng-show="$ctrl.import.observation">
<vn-textarea
vn-one
label="Observation"
ng-model="$ctrl.import.observation">
</vn-textarea>
</vn-horizontal>
<vn-horizontal ng-show="$ctrl.import.buys.length > 0">
<table class="vn-table">
<thead>
<tr>
<th translate>Item</th>
<th translate expand>Description</th>
<th translate center>Size</th>
<th translate center>Packing</th>
<th translate center>Grouping</th>
<th translate center>Buying value</th>
<th translate center>Box</th>
</tr>
</thead>
<tbody ng-repeat="buy in $ctrl.import.buys">
<tr>
<td title="{{::buy.itemFk}}">
<vn-autocomplete
class="dense"
vn-focus
url="Entries/{{$ctrl.$params.id}}/lastItemBuys"
ng-model="buy.itemFk"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
order="id DESC"
tabindex="1">
<tpl-item>
{{::id}} - {{::name}}
</tpl-item>
<append>
<vn-icon-button
icon="filter_alt"
vn-click-stop="$ctrl.showFilterDialog(buy)"
vn-tooltip="Filter...">
</vn-icon-button>
</append>
</vn-autocomplete>
</td>
<td title="{{::buy.description}}" expand>{{::buy.description | dashIfEmpty}}</td>
<td center title="{{::buy.size}}">{{::buy.size | dashIfEmpty}}</td>
<td center>{{::buy.packing | dashIfEmpty}}</td>
<td center>{{::buy.grouping | dashIfEmpty}}</td>
<td>{{::buy.buyingValue | currency: 'EUR':2}}</td>
<td center title="{{::buy.packagingFk | dashIfEmpty}}">
<vn-autocomplete
vn-one
url="Packagings"
show-field="id"
value-field="id"
where="{isBox: true}"
ng-model="buy.packagingFk">
</vn-autocomplete>
</td>
</tr>
</tbody>
</table>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
label="Import buys">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="entry.card.buy.index">
</vn-button>
</vn-button-bar>
</div>
</form>
<vn-dialog
vn-id="filterDialog"
message="Filter item">
<tpl-body class="itemFilter">
<vn-horizontal>
<vn-textfield
label="Name"
ng-model="$ctrl.itemFilterParams.name"
ng-keydown="$ctrl.onKeyPress($event)"
vn-focus>
</vn-textfield>
<vn-textfield
label="Size"
ng-model="$ctrl.itemFilterParams.size"
ng-keydown="$ctrl.onKeyPress($event)">
</vn-textfield>
<vn-autocomplete
label="Producer"
ng-model="$ctrl.itemFilterParams.producerFk"
ng-keydown="$ctrl.onKeyPress($event)"
url="Producers"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Type"
ng-model="$ctrl.itemFilterParams.typeFk"
ng-keydown="$ctrl.onKeyPress($event)"
url="ItemTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Color"
ng-model="$ctrl.itemFilterParams.inkFk"
ng-keydown="$ctrl.onKeyPress($event)"
url="Inks"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-mb-md">
<vn-button vn-none
label="Search"
ng-click="$ctrl.filter()">
</vn-button>
</vn-horizontal>
<vn-crud-model
vn-id="itemsModel"
url="Entries/{{$ctrl.$params.id}}/lastItemBuys"
filter="$ctrl.itemFilter"
data="items"
limit="10">
</vn-crud-model>
<vn-data-viewer
model="itemsModel"
class="vn-w-lg">
<vn-table class="scrollable">
<vn-thead>
<vn-tr>
<vn-th shrink>ID</vn-th>
<vn-th expand>Item</vn-th>
<vn-th number>Size</vn-th>
<vn-th expand>Producer</vn-th>
<vn-th>Color</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="item in items"
class="clickable vn-tr search-result"
ng-click="$ctrl.selectItem(item.id)">
<vn-td shrink>
<span
vn-click-stop="itemDescriptor.show($event, item.id)"
class="link">
{{::item.id}}
</span>
</vn-td>
<vn-td expand>{{::item.name}}</vn-td>
<vn-td number>{{::item.size}}</vn-td>
<vn-td expand>{{::item.producerName}}</vn-td>
<vn-td>{{::item.inkName}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
</tpl-body>
</vn-dialog>

View File

@ -1,159 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.import = {
file: '',
invoice: null,
buys: []
};
}
onFileChange($event) {
const input = $event.target;
const file = input.files[0];
const reader = new FileReader();
reader.onload = event =>
this.fillData(event.target.result);
reader.readAsText(file, 'UTF-8');
}
fillData(raw) {
const data = JSON.parse(raw);
const [invoice] = data.invoices;
this.$.$applyAsync(() => {
this.import.observation = invoice.tx_awb;
const companyName = invoice.tx_company;
const boxes = invoice.boxes;
const buys = [];
for (let box of boxes) {
const boxVolume = box.nu_length * box.nu_width * box.nu_height;
for (let product of box.products) {
const packing = product.nu_stems_bunch * product.nu_bunches;
buys.push({
description: product.nm_product,
companyName: companyName,
size: product.nu_length,
packing: packing,
grouping: product.nu_stems_bunch,
buyingValue: parseFloat(product.mny_rate_stem),
volume: boxVolume,
});
}
}
const boxesId = boxes.map(box => box.id_box);
this.import.ref = boxesId.join(', ');
this.fetchBuys(buys);
});
}
fetchBuys(buys) {
const params = {buys};
const query = `Entries/${this.$params.id}/importBuysPreview`;
this.$http.post(query, params).then(res => {
this.import.buys = res.data;
});
}
onSubmit() {
try {
const params = this.import;
const hasAnyEmptyRow = params.buys.some(buy => {
return buy.itemFk == null;
});
if (hasAnyEmptyRow)
throw new Error(`Some of the imported buys doesn't have an item`);
const query = `Entries/${this.$params.id}/importBuys`;
return this.$http.post(query, params)
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => this.$state.go('entry.card.buy.index'));
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
}
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
showFilterDialog(buy) {
this.activeBuy = buy;
this.itemFilterParams = {};
this.itemFilter = {
include: [
{
relation: 'producer',
scope: {
fields: ['name']
}
},
{
relation: 'ink',
scope: {
fields: ['name']
}
}
]
};
this.$.filterDialog.show();
}
selectItem(id) {
this.activeBuy['itemFk'] = id;
this.$.filterDialog.hide();
}
filter() {
const filter = this.itemFilter;
const params = this.itemFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'name':
where[key] = {like: `%${value}%`};
break;
case 'producerFk':
case 'typeFk':
case 'size':
case 'inkFk':
where[key] = value;
}
}
filter.where = where;
this.$.itemsModel.applyFilter(filter);
}
onKeyPress($event) {
if ($event.key === 'Enter')
this.filter();
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnEntryBuyImport', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
}
});

View File

@ -1,199 +0,0 @@
import './index.js';
describe('Entry', () => {
describe('Component vnEntryBuyImport', () => {
let controller;
let $httpParamSerializer;
let $httpBackend;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, $compile, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
let $element = $compile('<vn-entry-buy-import-buys></vn-entry-latest-buys')($rootScope);
controller = $componentController('vnEntryBuyImport', {$element});
controller.entry = {
id: 1
};
controller.$params = {id: 1};
}));
describe('fillData()', () => {
it(`should call to the fillData() method`, () => {
controller.fetchBuys = jest.fn();
const rawData = `{
"invoices": [
{
"tx_awb": "123456",
"boxes": [
{
"id_box": 1,
"nu_length": 1,
"nu_width": 15,
"nu_height": 80,
"products": [
{
"nm_product": "Bow",
"nu_length": 1,
"nu_stems_bunch": 1,
"nu_bunches": 1,
"mny_rate_stem": 5.77
}
]
},
{
"id_box": 2,
"nu_length": 25,
"nu_width": 1,
"nu_height": 45,
"products": [
{
"nm_product": "Arrow",
"nu_length": 25,
"nu_stems_bunch": 1,
"nu_bunches": 1,
"mny_rate_stem": 2.16
}
]
}
]
}
]}`;
const expectedBuys = [
{
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
];
controller.fillData(rawData);
controller.$.$apply();
const importData = controller.import;
expect(importData.observation).toEqual('123456');
expect(importData.ref).toEqual('1, 2');
expect(controller.fetchBuys).toHaveBeenCalledWith(expectedBuys);
});
});
describe('fetchBuys()', () => {
it(`should perform a query to fetch the buys data`, () => {
const buys = [
{
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
];
const query = `Entries/1/importBuysPreview`;
$httpBackend.expectPOST(query).respond(200, buys);
controller.fetchBuys(buys);
$httpBackend.flush();
const importData = controller.import;
expect(importData.buys.length).toEqual(2);
});
});
describe('onSubmit()', () => {
it(`should throw an error when some of the rows doesn't have an item`, () => {
jest.spyOn(controller.vnApp, 'showError');
controller.import = {
observation: '123456',
ref: '1, 2',
buys: [
{
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
]
};
controller.onSubmit();
const message = `Some of the imported buys doesn't have an item`;
expect(controller.vnApp.showError).toHaveBeenCalledWith(message);
});
it(`should now perform a query to update columns`, () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$state.go = jest.fn();
controller.import = {
observation: '123456',
ref: '1, 2',
buys: [
{
'itemFk': 10,
'buyingValue': 5.77,
'description': 'Bow',
'grouping': 1,
'packing': 1,
'size': 1,
'volume': 1200},
{
'itemFk': 11,
'buyingValue': 2.16,
'description': 'Arrow',
'grouping': 1,
'packing': 1,
'size': 25,
'volume': 1125}
]
};
const params = controller.import;
const query = `Entries/1/importBuys`;
$httpBackend.expectPOST(query, params).respond(200, params.buys);
controller.onSubmit();
$httpBackend.flush();
const importData = controller.import;
expect(importData.buys.length).toEqual(2);
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect(controller.$state.go).toHaveBeenCalledWith('entry.card.buy.index');
});
});
});
});

View File

@ -1,5 +0,0 @@
.itemFilter {
vn-table.scrollable {
height: 500px
}
}

View File

@ -1,243 +0,0 @@
<vn-crud-model
auto-load="true"
vn-id="model"
url="Entries/{{$ctrl.$params.id}}/getBuys"
data="$ctrl.buys"
limit="20">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="$ctrl.buys">
</vn-watcher>
<vn-data-viewer
model="model"
class="vn-w-xl">
<vn-card class="vn-pa-lg">
<div class="tableWrapper">
<vn-horizontal class="header">
<vn-tool-bar class="vn-mb-md">
<vn-button
disabled="$ctrl.selectedBuys() == 0"
ng-click="deleteBuys.show()"
vn-tooltip="Delete buy(s)"
icon="delete">
</vn-button>
</vn-tool-bar>
<vn-one class="taxes" ng-if="$ctrl.sales.length > 0">
<p><vn-label translate>Subtotal</vn-label> {{$ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label translate>VAT</vn-label> {{$ctrl.ticket.totalWithVat - $ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}</p>
<p><vn-label><strong>Total</strong></vn-label> <strong>{{$ctrl.ticket.totalWithVat | currency: 'EUR':2}}</strong></p>
</vn-one>
</vn-horizontal>
<table class="vn-table">
<thead>
<tr>
<th shrink>
<vn-multi-check model="model" on-change="$ctrl.resetChanges()">
</vn-multi-check>
</th>
<th translate center>Item</th>
<th translate center>Quantity</th>
<th translate center>Package</th>
<th translate>Stickers</th>
<th translate>Weight</th>
<th translate>Packing</th>
<th translate>Grouping</th>
<th translate>Buying value</th>
<th translate expand>Grouping price</th>
<th translate expand>Packing price</th>
<th translate>Import</th>
</tr>
</thead>
<tbody ng-repeat="buy in $ctrl.buys">
<tr>
<td shrink>
<vn-check tabindex="-1" ng-model="buy.checked">
</vn-check>
</td>
<td shrink>
<span
ng-if="buy.id"
ng-click="itemDescriptor.show($event, buy.item.id)"
class="link">
{{::buy.item.id}}
</span>
<vn-autocomplete ng-if="!buy.id" class="dense"
vn-focus
url="Items/withName"
ng-model="buy.itemFk"
show-field="id"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.saveBuy(buy)"
order="id DESC"
tabindex="1">
<tpl-item>
<div>{{::name}}</div>
<div class="text-secondary text-caption">#{{::id}}</div>
</tpl-item>
</vn-autocomplete>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.quantity | dashIfEmpty}}"
ng-model="buy.quantity"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td center>
<vn-autocomplete
vn-one
title="{{::buy.packagingFk | dashIfEmpty}}"
url="Packagings"
show-field="id"
value-field="id"
where="{freightItemFk: true}"
ng-model="buy.packagingFk"
on-change="$ctrl.saveBuy(buy)">
</vn-autocomplete>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.stickers | dashIfEmpty}}"
ng-model="buy.stickers"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.weight | dashIfEmpty}}"
ng-model="buy.weight"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number
title="{{::buy.packing | dashIfEmpty}}"
ng-model="buy.packing"
on-change="$ctrl.saveBuy(buy)">
<append>
<vn-icon
pointer
ng-show="buy.groupingMode == 'packing'"
ng-click="$ctrl.toggleGroupingMode(buy, 'packing')"
icon="lock">
</vn-icon>
<vn-icon
pointer
ng-show="buy.groupingMode != 'packing'"
ng-click="$ctrl.toggleGroupingMode(buy, 'packing')"
icon="lock_open">
</vn-icon>
</append>
</vn-input-number>
</td>
<td>
<vn-input-number
title="{{::buy.grouping | dashIfEmpty}}"
ng-model="buy.grouping"
on-change="$ctrl.saveBuy(buy)">
<append>
<vn-icon
pointer
ng-show="buy.groupingMode == 'grouping'"
ng-click="$ctrl.toggleGroupingMode(buy, 'grouping')"
icon="lock">
</vn-icon>
<vn-icon
pointer
ng-show="buy.groupingMode != 'grouping'"
ng-click="$ctrl.toggleGroupingMode(buy, 'grouping')"
icon="lock_open">
</vn-icon>
</append>
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.buyingValue | dashIfEmpty}}"
ng-model="buy.buyingValue"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.price2 | dashIfEmpty}}"
ng-model="buy.price2"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<vn-input-number class="dense"
title="{{::buy.price3 | dashIfEmpty}}"
ng-model="buy.price3"
on-change="$ctrl.saveBuy(buy)">
</vn-input-number>
</td>
<td>
<span
ng-if="buy.quantity != null && buy.buyingValue != null"
title="{{buy.quantity * buy.buyingValue | currency: 'EUR':2}}">
{{buy.quantity * buy.buyingValue | currency: 'EUR':2}}
</span>
</td>
</tr>
<tr class="dark-row">
<td shrink>
</td>
<td shrink>
<span translate-attr="{title: 'Item type'}">
{{::buy.item.itemType.code}}
</span>
</td>
<td number shrink>
<span translate-attr="{title: 'Item size'}">
{{::buy.item.size}}
</span>
</td>
<td center>
<span translate-attr="{title: 'Minimum price'}">
{{::buy.item.minPrice | currency: 'EUR':2}}
</span>
</td>
<td vn-fetched-tags colspan="8">
<div>
<vn-one title="{{::buy.item.concept}}">{{::buy.item.name}}</vn-one>
<vn-one ng-if="::buy.item.subName">
<h3 title="{{::buy.item.subName}}">{{::buy.item.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="::buy.item"
tabindex="-1">
</vn-fetched-tags>
</td>
</tr>
<tr><td></td></tr>
</tbody>
</table>
</div>
</vn-card>
</vn-data-viewer>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<a ui-sref="entry.card.buy.import" >
<vn-button class="round md vn-mb-sm"
icon="publish"
vn-tooltip="Import buys"
tooltip-position="left">
</vn-button>
</a>
</vn-vertical>
</div>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-confirm
vn-id="delete-buys"
question="You are going to delete buy(s) from this entry"
message="Continue anyway?"
on-accept="$ctrl.deleteBuys()">
</vn-confirm>

View File

@ -1,81 +0,0 @@
import ngModule from '../../module';
import './style.scss';
import Section from 'salix/components/section';
export default class Controller extends Section {
saveBuy(buy) {
const missingData = !buy.itemFk || !buy.quantity || !buy.packagingFk;
if (missingData) return;
let options;
if (buy.id) {
options = {
query: `Buys/${buy.id}`,
method: 'patch'
};
}
this.$http[options.method](options.query, buy).then(res => {
if (!res.data) return;
buy = Object.assign(buy, res.data);
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
/**
* Returns checked instances
*
* @return {Array} Checked instances
*/
selectedBuys() {
if (!this.buys) return;
return this.buys.filter(buy => {
return buy.checked;
});
}
deleteBuys() {
const buys = this.selectedBuys();
const actualInstances = buys.filter(buy => buy.id);
const params = {buys: actualInstances};
if (actualInstances.length) {
this.$http.post(`Buys/deleteBuys`, params).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
buys.forEach(buy => {
const index = this.buys.indexOf(buy);
this.buys.splice(index, 1);
});
}
toggleGroupingMode(buy, mode) {
const groupingMode = mode === 'grouping' ? mode : 'packing';
const newGroupingMode = buy.groupingMode === groupingMode ? null : groupingMode;
const params = {
groupingMode: newGroupingMode
};
this.$http.patch(`Buys/${buy.id}`, params).then(() => {
buy.groupingMode = newGroupingMode;
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnEntryBuyIndex', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -1,92 +0,0 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import './index.js';
describe('Entry buy', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, $compile, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
let $element = $compile('<vn-entry-buy-index></vn-entry-buy-index')($rootScope);
controller = $componentController('vnEntryBuyIndex', {$element});
const params = _$httpParamSerializer_({filter: {limit: 20}});
$httpBackend.whenGET(`Entries//getBuys?${params}`).respond([{id: 1}]);
}));
describe('saveBuy()', () => {
it(`should call the buys patch route if the received buy has an ID`, () => {
const buy = {id: 1, itemFk: 1, quantity: 1, packagingFk: 1};
const query = `Buys/${buy.id}`;
$httpBackend.expectPATCH(query).respond(200);
controller.saveBuy(buy);
$httpBackend.flush();
});
});
describe('deleteBuys()', () => {
it(`should perform no queries if all buys to delete were not actual instances`, () => {
controller.buys = [
{checked: true},
{checked: true},
{checked: false}];
controller.deleteBuys();
expect(controller.buys.length).toEqual(1);
});
it(`should perform a query to delete as there's an actual instance at least`, () => {
controller.buys = [
{checked: true, id: 1},
{checked: true},
{checked: false}];
const query = 'Buys/deleteBuys';
$httpBackend.expectPOST(query).respond(200);
controller.deleteBuys();
$httpBackend.flush();
expect(controller.buys.length).toEqual(1);
});
});
describe('toggleGroupingMode()', () => {
it(`should toggle grouping mode from grouping to packing`, () => {
const buy = {id: 999, groupingMode: 'grouping'};
const query = `Buys/${buy.id}`;
$httpBackend.expectPATCH(query, {groupingMode: 'packing'}).respond(200);
controller.toggleGroupingMode(buy, 'packing');
$httpBackend.flush();
});
it(`should toggle grouping mode from packing to grouping`, () => {
const buy = {id: 999, groupingMode: 'packing'};
const query = `Buys/${buy.id}`;
$httpBackend.expectPATCH(query, {groupingMode: 'grouping'}).respond(200);
controller.toggleGroupingMode(buy, 'grouping');
$httpBackend.flush();
});
it(`should toggle off the grouping mode if it was packing to packing`, () => {
const buy = {id: 999, groupingMode: 'packing'};
const query = `Buys/${buy.id}`;
$httpBackend.expectPATCH(query, {groupingMode: null}).respond(200);
controller.toggleGroupingMode(buy, 'packing');
$httpBackend.flush();
});
it(`should toggle off the grouping mode if it was grouping to grouping`, () => {
const buy = {id: 999, groupingMode: 'grouping'};
const query = `Buys/${buy.id}`;
$httpBackend.expectPATCH(query, {groupingMode: null}).respond(200);
controller.toggleGroupingMode(buy, 'grouping');
$httpBackend.flush();
});
});
});

View File

@ -1,3 +0,0 @@
Buys: Compras
Delete buy(s): Eliminar compra(s)
Add buy: Añadir compra

View File

@ -1,42 +0,0 @@
@import "variables";
vn-entry-buy-index vn-card {
max-width: $width-xl;
.dark-row {
background-color: lighten($color-marginal, 10%);
}
thead tr {
border: 1px solid white;;
}
tbody tr:nth-child(1),
tbody tr:nth-child(2) {
border-left: 1px solid $color-spacer;
border-right: 1px solid $color-spacer;
}
tbody tr:nth-child(2) {
border-bottom: 1px solid $color-spacer;
}
tbody{
border-bottom: 1px solid $color-spacer;
}
tbody:last-child {
border-bottom: 0;
}
tbody tr:nth-child(3) {
height: inherit
}
tr {
margin-bottom: 10px;
}
}
$color-font-link-medium: lighten($color-font-link, 20%)

View File

@ -1,8 +0,0 @@
reference: Referencia
Observation: Observación
Box: Embalaje
Import buys: Importar compras
Some of the imported buys doesn't have an item: Algunas de las compras importadas no tienen un artículo
JSON files only: Solo ficheros JSON
Filter item: Filtrar artículo
Filter...: Filtrar...

View File

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

View File

@ -1,59 +0,0 @@
import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
let filter = {
include: [
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
},
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
},
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
},
{
relation: 'currency'
}
]
};
this.$http.get(`Entries/${this.$params.id}`, {filter})
.then(response => this.entry = response.data);
}
}
ngModule.vnComponent('vnEntryCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,69 +0,0 @@
<vn-watcher
vn-id="watcher"
url="Entries"
data="$ctrl.entry"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-icon color-marginal
icon="info"
vn-tooltip="Required fields (*)">
</vn-icon>
<vn-horizontal>
<vn-autocomplete
label="Supplier"
ng-model="$ctrl.entry.supplierFk"
url="Suppliers"
show-field="nickname"
search-function="{or: [{id: $search}, {nickname: {like: '%'+ $search +'%'}}]}"
order="nickname"
required="true">
<tpl-item>
<div>{{::nickname}}</div>
<div class="text-secondary text-caption">#{{::id}}</div>
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
label="Travel"
ng-model="$ctrl.entry.travelFk"
url="Travels/filter"
search-function="$ctrl.searchFunction($search)"
show-field="warehouseInName"
order="id"
required="true">
<tpl-item>
<div>
{{::agencyModeName}}
- {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}})
&#x2192; {{::warehouseOutName}} ({{::landed | date: 'dd/MM/yyyy'}})
</div>
<div class="text-secondary text-caption">#{{::id}}</div>
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
label="Company"
ng-model="$ctrl.entry.companyFk"
url="Companies"
show-field="code"
required="true">
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="entry.index"></vn-button>
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,43 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.entry = {
companyFk: this.vnConfig.companyFk
};
if (this.$params && this.$params.supplierFk)
this.entry.supplierFk = parseInt(this.$params.supplierFk);
if (this.$params && this.$params.travelFk)
this.entry.travelFk = parseInt(this.$params.travelFk);
if (this.$params && this.$params.companyFk)
this.entry.companyFk = parseInt(this.$params.companyFk);
}
onSubmit() {
this.$.watcher.submit().then(
res => this.$state.go('entry.card.basicData', {id: res.data.id})
);
}
searchFunction($search) {
return {or: [
{'agencyModeName': {like: `%${$search}%`}},
{'warehouseInName': {like: `%${$search}%`}},
{'warehouseOutName': {like: `%${$search}%`}},
{'shipped': new Date($search)},
{'landed': new Date($search)}
]};
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnEntryCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,2 +0,0 @@
New entry: Nueva entrada
Required fields (*): Campos requeridos (*)

View File

@ -1,10 +0,0 @@
vn-entry-create {
vn-card {
position: relative
}
vn-icon[icon="info"] {
position: absolute;
top: 16px;
right: 16px
}
}

View File

@ -1,3 +0,0 @@
<slot-descriptor>
<vn-entry-descriptor></vn-entry-descriptor>
</slot-descriptor>

View File

@ -1,9 +0,0 @@
import ngModule from '../module';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover {}
ngModule.vnComponent('vnEntryDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: Controller
});

View File

@ -1,65 +0,0 @@
<vn-descriptor-content
module="entry"
description="$ctrl.entry.supplier.nickname"
summary="$ctrl.$.summary">
<slot-menu>
<vn-item
ng-click="$ctrl.showEntryReport()"
translate>
Show entry report
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<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>
<div class="icons">
<vn-icon
vn-tooltip="Is inventory entry"
icon="icon-inventory"
ng-if="$ctrl.entry.isExcludedFromAvailable">
</vn-icon>
<vn-icon
vn-tooltip="Is virtual entry"
icon="icon-net"
ng-if="$ctrl.entry.isRaid">
</vn-icon>
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link
tooltip="Supplier card"
state="['supplier.card.summary', {id: $ctrl.entry.supplier.id}]"
icon="icon-supplier">
</vn-quick-link>
</div>
<div ng-transclude="btnTwo">
<vn-quick-link
tooltip="All travels with current agency"
state="['travel.index', {q: $ctrl.travelFilter}]"
icon="local_airport">
</vn-quick-link>
</div>
<div ng-transclude="btnThree">
<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="btnThree">
</div>
</div>
</slot-body>
</vn-descriptor-content>
<vn-popup vn-id="summary">
<vn-entry-summary entry="$ctrl.entry"></vn-entry-summary>
</vn-popup>

View File

@ -1,99 +0,0 @@
import ngModule from '../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
get entry() {
return this.entity;
}
set entry(value) {
this.entity = value;
}
get travelFilter() {
let travelFilter;
const entryTravel = this.entry && this.entry.travel;
if (entryTravel && entryTravel.agencyModeFk) {
travelFilter = this.entry && JSON.stringify({
agencyModeFk: entryTravel.agencyModeFk
});
}
return travelFilter;
}
get entryFilter() {
let entryTravel = this.entry && this.entry.travel;
if (!entryTravel || !entryTravel.landed) return null;
const date = new Date(entryTravel.landed);
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.entry.supplierFk,
from,
to
});
}
loadData() {
const filter = {
include: [
{
relation: 'travel',
scope: {
fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'],
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
}
]
}
},
{
relation: 'supplier',
scope: {
fields: ['id', 'nickname']
}
}
]
};
return this.getData(`Entries/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
showEntryReport() {
this.vnReport.show(`Entries/${this.id}/entry-order-pdf`);
}
}
ngModule.vnComponent('vnEntryDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -1,40 +0,0 @@
import './index.js';
describe('Entry Component vnEntryDescriptor', () => {
let $httpBackend;
let controller;
const entry = {id: 2};
beforeEach(ngModule('entry'));
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
controller = $componentController('vnEntryDescriptor', {$element: null}, {entry});
}));
describe('showEntryReport()', () => {
it('should open a new window showing a delivery note PDF document', () => {
jest.spyOn(controller.vnReport, 'show');
window.open = jasmine.createSpy('open');
controller.showEntryReport();
const expectedPath = `Entries/${entry.id}/entry-order-pdf`;
expect(controller.vnReport.show).toHaveBeenCalledWith(expectedPath);
});
});
describe('loadData()', () => {
it('should perform ask for the entry', () => {
let query = `Entries/${entry.id}`;
jest.spyOn(controller, 'getData');
$httpBackend.expectGET(query).respond();
controller.loadData();
$httpBackend.flush();
expect(controller.getData).toHaveBeenCalledTimes(1);
expect(controller.getData).toHaveBeenCalledWith(query, jasmine.any(Object));
});
});
});

View File

@ -1,7 +0,0 @@
Reference: Referencia
Supplier card: Ficha del proveedor
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
Is inventory entry: Es una entrada de inventario
Is virtual entry: Es una redada

View File

@ -1,18 +1,3 @@
export * from './module';
import './main';
import './index/';
import './create';
import './basic-data';
import './latest-buys';
import './search-panel';
import './latest-buys-search-panel';
import './descriptor';
import './descriptor-popover';
import './card';
import './note';
import './summary';
import './log';
import './buy/index';
import './buy/import';

View File

@ -1,85 +0,0 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-mb-xl vn-w-xl">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th shrink></vn-th>
<vn-th field="id" number>Id</vn-th>
<vn-th field="landed" center expand>Landed</vn-th>
<vn-th>Reference</vn-th>
<vn-th>Invoice number</vn-th>
<vn-th field="supplierFk">Supplier</vn-th>
<vn-th field="isBooked" center>Booked</vn-th>
<vn-th field="isConfirmed" center>Confirmed</vn-th>
<vn-th field="isOrdered" center>Ordered</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="entry in model.data"
class="clickable vn-tr search-result"
ui-sref="entry.card.summary({id: {{::entry.id}}})">
<vn-td shrink>
<vn-icon
ng-show="entry.isExcludedFromAvailable"
class="bright"
vn-tooltip="Inventory entry"
icon="icon-inventory">
</vn-icon>
<vn-icon
ng-show="entry.isRaid"
class="bright"
vn-tooltip="Virtual entry"
icon="icon-net">
</vn-icon>
</vn-td>
<vn-td number>{{::entry.id}}</vn-td>
<vn-td center expand>
<span
class="link"
vn-click-stop="travelDescriptor.show($event, entry.travelFk)">
{{::entry.landed | date:'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td expand>{{::entry.reference}}</vn-td>
<vn-td expand>{{::entry.invoiceNumber}}</vn-td>
<vn-td expand>{{::entry.supplierName}}</vn-td>
<vn-td center><vn-check ng-model="entry.isBooked" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isConfirmed" disabled="true"></vn-check></vn-td>
<vn-td center><vn-check ng-model="entry.isOrdered" disabled="true"></vn-check></vn-td>
<vn-td shrink>
<vn-icon-button
vn-click-stop="$ctrl.preview(entry)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-travel-descriptor-popover
vn-id="travelDescriptor">
</vn-travel-descriptor-popover>
<vn-popup vn-id="summary">
<vn-entry-summary
entry="$ctrl.entrySelected">
</vn-entry-summary>
</vn-popup>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<a ui-sref="entry.create" vn-bind="+">
<vn-button class="round md vn-mb-sm"
icon="add"
vn-tooltip="New entry"
tooltip-position="left">
</vn-button>
</a>
</vn-vertical>
</div>

View File

@ -1,14 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
preview(entry) {
this.entrySelected = entry;
this.$.summary.show();
}
}
ngModule.vnComponent('vnEntryIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,17 +0,0 @@
Inventory entry: Es inventario
Virtual entry: Es una redada
Supplier: Proveedor
Currency: Moneda
Company: Empresa
Confirmed: Confirmada
Ordered: Pedida
Is raid: Redada
Commission: Comisión
Landed: F. entrega
Reference: Referencia
Created: Creado
Booked: Contabilizada
Is inventory: Inventario
Status: Estado
Selection: Selección
Invoice number: Núm. factura

View File

@ -1,242 +0,0 @@
<vn-crud-model url="Tags" fields="['id','name', 'isFree']" data="$ctrl.tags" auto-load="true">
<vn-crud-model url="ItemCategories" data="$ctrl.categories" auto-load="true"></vn-crud-model>
<vn-side-menu side="right">
<vn-horizontal class="input">
<vn-textfield
label="General search"
ng-model="$ctrl.filter.search"
info="Search items by id, name or barcode"
vn-focus
ng-keydown="$ctrl.onKeyPress($event)">
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="item-category">
<vn-autocomplete
vn-id="category"
data="$ctrl.categories"
ng-model="$ctrl.filter.categoryFk"
show-field="name"
value-field="id"
label="Category">
</vn-autocomplete>
<vn-one ng-repeat="category in $ctrl.categories">
<vn-icon
ng-class="{'active': $ctrl.filter.categoryFk == category.id}"
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.changeCategory(category.id)">
</vn-icon>
</vn-one>
</vn-horizontal>
<vn-vertical class="input">
<vn-autocomplete
vn-id="type"
disabled="!$ctrl.filter.categoryFk"
url="ItemTypes"
label="Type"
where="{categoryFk: $ctrl.filter.categoryFk}"
show-field="name"
value-field="id"
ng-model="$ctrl.filter.typeFk"
fields="['categoryFk']"
include="'category'"
on-change="$ctrl.addFilters()">
<tpl-item>
<div>{{name}}</div>
<div class="text-caption text-secondary">
{{category.name}}
</div> </tpl-item
>>
</vn-autocomplete>
</vn-vertical>
<vn-horizontal class="input horizontal">
<vn-autocomplete
vn-id="salesPerson"
disabled="false"
ng-model="$ctrl.filter.salesPersonFk"
url="TicketRequests/getItemTypeWorker"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
label="Buyer"
on-change="$ctrl.addFilters()">
</vn-autocomplete>
<vn-autocomplete
vn-id="supplier"
label="Supplier"
ng-model="$ctrl.filter.supplierFk"
url="Suppliers"
fields="['name','nickname']"
search-function="{or: [{nickname: {like: '%'+ $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
show-field="name"
value-field="id"
on-change="$ctrl.addFilters()">
<tpl-item>{{name}}: {{nickname}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-vertical class="input">
<vn-date-picker
label="From"
ng-model="$ctrl.filter.from"
on-change="$ctrl.addFilters()">
</vn-date-picker>
<vn-date-picker
label="To"
ng-model="$ctrl.filter.to"
on-change="$ctrl.addFilters()">
</vn-date-picker>
</vn-vertical>
<vn-horizontal class="checks">
<vn-check
label="Is active"
ng-model="$ctrl.filter.active"
triple-state="true"
ng-click="$ctrl.addFilters()">
</vn-check>
<vn-check
label="Is visible"
ng-model="$ctrl.filter.visible"
triple-state="true"
ng-click="$ctrl.addFilters()">
</vn-check>
<vn-check
label="Is floramondo"
ng-model="$ctrl.filter.floramondo"
triple-state="true"
ng-click="$ctrl.addFilters()">
</vn-check>
</vn-horizontal>
<vn-horizontal class="tags">
<vn-one class="text-subtitle1" translate> Tags </vn-one>
<vn-icon-button
vn-none
vn-tooltip="Add tag"
icon="add_circle"
ng-click="$ctrl.filter.tags.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="tags horizontal" ng-repeat="itemTag in $ctrl.filter.tags">
<vn-autocomplete
vn-id="tag"
data="$ctrl.tags"
ng-model="itemTag.tagFk"
show-field="name"
label="Tag"
on-change="itemTag.value = null">
</vn-autocomplete>
<vn-textfield
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
label="Value"
ng-model="itemTag.value"
ng-keydown="$ctrl.onKeyPress($event)">
</vn-textfield>
<vn-autocomplete
ng-show="tag.selection.isFree === false"
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
search-function="{value: $search}"
label="Value"
ng-model="itemTag.value"
show-field="value"
value-field="value"
on-change="$ctrl.addFilters()">
</vn-autocomplete>
<vn-icon-button
vn-none
vn-tooltip="Remove tag"
icon="delete"
ng-click="$ctrl.removeTag(itemTag)">
</vn-icon-button>
</vn-horizontal>
<div class="chips">
<vn-chip
ng-if="$ctrl.filter.search"
removable="true"
vn-tooltip="Item id/name"
on-remove="$ctrl.removeItemFilter('search')"
class="colored">
<span>Id/Name: {{$ctrl.filter.search}}</span>
</vn-chip>
<vn-chip
ng-if="category.selection"
removable="true"
vn-tooltip="Category"
on-remove="$ctrl.removeItemFilter('categoryFk')"
class="colored">
<span>{{category.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="type.selection"
removable="true"
vn-tooltip="Type"
on-remove="$ctrl.removeItemFilter('typeFk')"
class="colored">
<span>{{type.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="salesPerson.selection"
removable="true"
vn-tooltip="Sales person"
on-remove="$ctrl.removeItemFilter('salesPersonFk')"
class="colored">
<span>Sales person: {{salesPerson.selection.nickname}}</span>
</vn-chip>
<vn-chip
ng-if="supplier.selection"
removable="true"
vn-tooltip="Supplier"
on-remove="$ctrl.removeItemFilter('supplierFk')"
class="colored">
<span>Supplier: {{supplier.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.from"
removable="true"
vn-tooltip="From date"
on-remove="$ctrl.removeItemFilter('from')"
class="colored">
<span>From: {{$ctrl.filter.from | date:'dd/MM/yyyy'}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.to"
removable="true"
vn-tooltip="To date"
on-remove="$ctrl.removeItemFilter('to')"
class="colored">
<span>To: {{$ctrl.filter.to | date:'dd/MM/yyyy'}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.active != null"
removable="true"
vn-tooltip="Active"
on-remove="$ctrl.removeItemFilter('active')"
class="colored">
<span>Active: {{$ctrl.filter.active ? '✓' : '✗'}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.floramondo != null"
removable="true"
vn-tooltip="Floramondo"
on-remove="$ctrl.removeItemFilter('floramondo')"
class="colored">
<span>Floramondo: {{$ctrl.filter.floramondo ? '✓' : '✗'}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.visible != null"
removable="true"
vn-tooltip="Visible"
on-remove="$ctrl.removeItemFilter('visible')"
class="colored">
<span>Visible: {{$ctrl.filter.visible ? '✓' : '✗'}}</span>
</vn-chip>
<vn-chip
ng-repeat="chipTag in $ctrl.filter.tags"
removable="true"
vn-tooltip="Tag"
on-remove="$ctrl.removeTag(chipTag)"
class="colored"
ng-if="chipTag.value">
<span>{{$ctrl.showTagInfo(chipTag)}}</span>
</vn-chip>
</vn-chip>
</div>
</vn-side-menu>

View File

@ -1,61 +0,0 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
import './style.scss';
class Controller extends SearchPanel {
constructor($element, $) {
super($element, $);
}
$onInit() {
this.filter = {
isActive: true,
tags: []
};
}
changeCategory(id) {
if (this.filter.categoryFk != id) {
this.filter.categoryFk = id;
this.addFilters();
}
}
removeItemFilter(param) {
this.filter[param] = null;
if (param == 'categoryFk') this.filter['typeFk'] = null;
this.addFilters();
}
removeTag(tag) {
const index = this.filter.tags.indexOf(tag);
if (index > -1) this.filter.tags.splice(index, 1);
this.addFilters();
}
onKeyPress($event) {
if ($event.key === 'Enter')
this.addFilters();
}
addFilters() {
for (let i = 0; i < this.filter.tags.length; i++) {
if (!this.filter.tags[i].value)
this.filter.tags.splice(i, 1);
}
return this.model.addFilter({}, this.filter);
}
showTagInfo(itemTag) {
if (!itemTag.tagFk) return itemTag.value;
return `${this.tags.find(tag => tag.id == itemTag.tagFk).name}: ${itemTag.value}`;
}
}
ngModule.component('vnLatestBuysSearchPanel', {
template: require('./index.html'),
controller: Controller,
bindings: {
model: '<'
}
});

View File

@ -1,56 +0,0 @@
import './index.js';
describe('Entry', () => {
describe('Component vnLatestBuysSearchPanel', () => {
let $element;
let controller;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject($componentController => {
$element = angular.element(`<vn-latest-buys-search-panel></vn-latest-buys-search-panel>`);
controller = $componentController('vnLatestBuysSearchPanel', {$element});
controller.model = {addFilter: () => {}};
}));
describe('removeItemFilter()', () => {
it(`should remove param from filter`, () => {
controller.filter = {tags: [], categoryFk: 1, typeFk: 1};
const expectFilter = {tags: [], categoryFk: null, typeFk: null};
controller.removeItemFilter('categoryFk');
expect(controller.filter).toEqual(expectFilter);
});
});
describe('removeTag()', () => {
it(`should remove tag from filter`, () => {
const tag = {tagFk: 1, value: 'Value'};
controller.filter = {tags: [tag]};
const expectFilter = {tags: []};
controller.removeTag(tag);
expect(controller.filter).toEqual(expectFilter);
});
});
describe('showTagInfo()', () => {
it(`should show tag value`, () => {
const tag = {value: 'Value'};
const result = controller.showTagInfo(tag);
expect(result).toEqual('Value');
});
it(`should show tag name and value`, () => {
const tag = {tagFk: 1, value: 'Value'};
controller.tags = [{id: 1, name: 'tagName'}];
const result = controller.showTagInfo(tag);
expect(result).toEqual('tagName: Value');
});
});
});
});

View File

@ -1,70 +0,0 @@
@import "variables";
vn-latest-buys-search-panel vn-side-menu div {
& > .input {
padding-left: $spacing-md;
padding-right: $spacing-md;
border-color: $color-spacer;
border-bottom: $border-thin;
}
& > .horizontal {
grid-auto-flow: column;
grid-column-gap: $spacing-sm;
align-items: center;
}
& > .checks {
padding: $spacing-md;
flex-wrap: wrap;
border-color: $color-spacer;
border-bottom: $border-thin;
}
& > .tags {
padding: $spacing-md;
padding-bottom: 0%;
padding-top: 0%;
align-items: center;
}
& > .chips {
display: flex;
flex-wrap: wrap;
padding: $spacing-md;
overflow: hidden;
max-width: 100%;
border-color: $color-spacer;
border-top: $border-thin;
}
& > .item-category {
padding: $spacing-sm;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
vn-autocomplete[vn-id="category"] {
display: none;
}
& > vn-one {
padding: $spacing-sm;
min-width: 33.33%;
text-align: center;
box-sizing: border-box;
& > vn-icon {
padding: $spacing-sm;
background-color: $color-font-secondary;
border-radius: 50%;
cursor: pointer;
&.active {
background-color: $color-main;
color: #fff;
}
& > i:before {
font-size: 2.6rem;
width: 16px;
height: 16px;
}
}
}
}
}

View File

@ -1,267 +0,0 @@
<vn-crud-model
vn-id="model"
url="Buys/latestBuysFilter"
order="itemFk DESC"
limit="20"
data="$ctrl.buys"
on-data-change="$ctrl.reCheck()"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
</vn-portal>
<vn-latest-buys-search-panel
model="model">
</vn-latest-buys-search-panel>
<vn-card>
<smart-table
model="model"
view-config-id="latestBuys"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-table>
<table>
<thead>
<tr>
<th shrink>
<vn-multi-check
model="model"
checked="$ctrl.checkAll"
check-field="checked"
check-dummy-enabled="true"
checked-dummy-count="$ctrl.checkedDummyCount">
</vn-multi-check>
</th>
<th translate>Picture</th>
<th field="itemFk">
<span translate>Item ID</span>
</th>
<th field="packing" number>
<span translate>Packing</span>
</th>
<th field="grouping" number>
<span translate>Grouping</span>
</th>
<th field="quantity" number>
<span translate>Quantity</span>
</th>
<th field="description">
<span translate>Description</span>
</th>
<th field="size">
<span translate>Size</span>
</th>
<th field="name">
<span translate>Tags</span>
</th>
<th field="code">
<span translate>Type</span>
</th>
<th field="intrastat">
<span translate>Intrastat</span>
</th>
<th field="origin">
<span translate>Origin</span>
</th>
<th field="weightByPiece">
<span translate>Weight/Piece</span>
</th>
<th field="isActive">
<span translate>Active</span>
</th>
<th field="family">
<span translate>Family</span>
</th>
<th field="entryFk">
<span translate>Entry</span>
</th>
<th field="buyingValue" number>
<span translate>Buying value</span>
</th>
<th field="freightValue" number>
<span translate>Freight value</span>
</th>
<th field="comissionValue" number>
<span translate>Commission value</span>
</th>
<th field="packageValue" number>
<span translate>Package value</span>
</th>
<th field="isIgnored">
<span translate>Is ignored</span>
</th>
<th field="price2" number>
<span translate>Grouping</span>
</th>
<th field="price3" number>
<span translate>Packing</span>
</th>
<th field="minPrice" number>
<span translate>Min</span>
</th>
<th field="ektFk">
<span translate>Ekt</span>
</th>
<th field="weight">
<span translate>Weight</span>
</th>
<th field="packagingFk">
<span translate>Package</span>
</th>
<th field="packingOut">
<span translate>Package out</span>
</th>
<th field="landing">
<span translate>Landing</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="buy in $ctrl.buys"
vn-anchor="::{
state: 'entry.card.buy.index',
params: {id: {{::buy.entryFk}}}
}">
<td>
<vn-check
ng-model="buy.checked"
on-change="$ctrl.saveChecked(buy.id)"
vn-click-stop>
</vn-check>
</td>
<td >
<img
ng-src="{{::$root.imagePath('catalog', '50x50', buy.itemFk)}}"
zoom-image="{{::$root.imagePath('catalog', '1600x900', buy.itemFk)}}"
vn-click-stop
on-error-src/>
</td>
<td>
<span
vn-click-stop="itemDescriptor.show($event, buy.itemFk)"
class="link">
{{::buy.itemFk}}
</span>
</td>
<td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 'packing' ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': buy.groupingMode == 'packing'}">
<span>{{::buy.packing | dashIfEmpty}}</span>
</vn-chip>
</td>
<td number>
<vn-chip class="transparent" translate-attr="buy.groupingMode == 'grouping' ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': buy.groupingMode == 'grouping'}">
<span>{{::buy.grouping | dashIfEmpty}}</span>
</vn-chip>
</td>
<td number>{{::buy.quantity}}</td>
<td vn-two title="{{::buy.description}}">
{{::buy.description | dashIfEmpty}}
</td>
<td number>{{::buy.size}}</td>
<td vn-fetched-tags>
<div>
<vn-one title="{{::buy.name}}">{{::buy.name}}</vn-one>
<vn-one ng-if="::buy.subName">
<h3 title="{{::buy.subName}}">{{::buy.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="::buy"
tabindex="-1">
</vn-fetched-tags>
</td>
<td title="{{::buy.type}}">
{{::buy.code}}
</td>
<td title="{{::item.intrastat}}">
{{::buy.intrastat}}
</td>
<td>{{::buy.origin}}</td>
<td>{{::buy.weightByPiece}}</td>
<td>
<vn-check
disabled="true"
ng-model="::buy.isActive">
</vn-check>
</td>
<td>{{::buy.family}}</td>
<td>
<span
vn-click-stop="entryDescriptor.show($event, buy.entryFk)"
class="link">
{{::buy.entryFk}}
</span>
</td>
<td number>{{::buy.buyingValue | currency: 'EUR':3}}</td>
<td number>{{::buy.freightValue | currency: 'EUR':3}}</td>
<td number>{{::buy.comissionValue | currency: 'EUR':3}}</td>
<td number>{{::buy.packageValue | currency: 'EUR':3}}</td>
<td>
<vn-check
disabled="true"
ng-model="::buy.isIgnored">
</vn-check>
</td>
<td number>{{::buy.price2 | currency: 'EUR':3}}</td>
<td number>{{::buy.price3 | currency: 'EUR':3}}</td>
<td number>{{::buy.minPrice | currency: 'EUR':3}}</td>
<td>{{::buy.ektFk | dashIfEmpty}}</td>
<td>{{::buy.weight}}</td>
<td>{{::buy.packagingFk}}</td>
<td>{{::buy.packingOut}}</td>
<td>{{::buy.landing | date: 'dd/MM/yyyy'}}</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<vn-button class="round sm vn-mb-sm"
icon="edit"
ng-show="$ctrl.totalChecked > 0"
ng-click="edit.show($event)"
vn-tooltip="Edit buy(s)"
tooltip-position="left">
</vn-button>
</vn-vertical>
</div>
<vn-dialog class="edit"
vn-id="edit"
on-accept="$ctrl.onEditAccept()"
on-close="$ctrl.editedColumn = null">
<tpl-body>
<span translate>Edit</span>
<span class="countLines">
{{::$ctrl.totalChecked}}
</span>
<span translate>buy(s)</span>
<vn-horizontal>
<vn-autocomplete
vn-two
ng-model="$ctrl.editedColumn.field"
data="$ctrl.columns"
value-field="field"
show-field="displayName"
label="Field to edit">
</vn-autocomplete>
<vn-textfield
vn-one
label="Value"
ng-model="$ctrl.editedColumn.newValue">
</vn-textfield>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-entry-descriptor-popover
vn-id="entry-descriptor">
</vn-entry-descriptor-popover>

View File

@ -1,209 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.editedColumn;
this.checkAll = false;
this.checkedBuys = [];
this.smartTableOptions = {
activeButtons: {
search: true,
shownColumns: true,
},
columns: [
{
field: 'code',
autocomplete: {
url: 'ItemTypes',
showField: 'code',
valueField: 'code',
}
},
{
field: 'origin',
autocomplete: {
url: 'Origins',
showField: 'code',
valueField: 'code'
}
},
{
field: 'family',
autocomplete: {
url: 'ItemFamilies',
valueField: 'code',
showField: 'code'
}
},
{
field: 'intrastat',
autocomplete: {
url: 'Intrastats',
showField: 'description',
valueField: 'description'
}
},
{
field: 'packagingFk',
autocomplete: {
url: 'Packagings',
showField: 'id'
}
},
{
field: 'isActive',
searchable: false
},
{
field: 'isIgnored',
searchable: false
},
{
field: 'landing',
searchable: false
}
]
};
}
get columns() {
if (this._columns) return this._columns;
this._columns = [
{field: 'packing', displayName: this.$t('Packing')},
{field: 'grouping', displayName: this.$t('Grouping')},
{field: 'packageValue', displayName: this.$t('Package value')},
{field: 'weight', displayName: this.$t('Weight')},
{field: 'description', displayName: this.$t('Description')},
{field: 'size', displayName: this.$t('Size')},
{field: 'weightByPiece', displayName: this.$t('weight/Piece')},
{field: 'packingOut', displayName: this.$t('PackingOut')},
{field: 'landing', displayName: this.$t('Landing')}
];
return this._columns;
}
get checked() {
const buys = this.$.model.data || [];
const checkedBuys = [];
for (let buy of buys) {
if (buy.checked)
checkedBuys.push(buy);
}
return checkedBuys;
}
exprBuilder(param, value) {
switch (param) {
case 'id':
case 'size':
case 'weightByPiece':
case 'isActive':
case 'family':
case 'minPrice':
case 'packingOut':
return {[`i.${param}`]: value};
case 'name':
case 'description':
return {[`i.${param}`]: {like: `%${value}%`}};
case 'code':
return {'it.code': value};
case 'intrastat':
return {'intr.description': value};
case 'origin':
return {'ori.code': value};
case 'landing':
return {[`lb.${param}`]: value};
case 'packing':
case 'grouping':
case 'quantity':
case 'entryFk':
case 'buyingValue':
case 'freightValue':
case 'comissionValue':
case 'packageValue':
case 'isIgnored':
case 'price2':
case 'price3':
case 'ektFk':
case 'weight':
case 'packagingFk':
return {[`b.${param}`]: value};
}
}
uncheck() {
this.checkAll = false;
this.checkedBuys = [];
}
get totalChecked() {
if (this.checkedDummyCount)
return this.checkedDummyCount;
return this.checked.length;
}
saveChecked(buyId) {
const index = this.checkedBuys.indexOf(buyId);
if (index !== -1)
return this.checkedBuys.splice(index, 1);
return this.checkedBuys.push(buyId);
}
reCheck() {
if (!this.$.model.data) return;
if (!this.checkedBuys.length) return;
this.$.model.data.forEach(buy => {
if (this.checkedBuys.includes(buy.id))
buy.checked = true;
});
}
onEditAccept() {
const rowsToEdit = [];
for (let row of this.checked)
rowsToEdit.push({id: row.id, itemFk: row.itemFk});
const data = {
field: this.editedColumn.field,
newValue: this.editedColumn.newValue,
lines: rowsToEdit
};
if (this.checkedDummyCount && this.checkedDummyCount > 0) {
const params = {};
if (this.$.model.userParams) {
const userParams = this.$.model.userParams;
for (let param in userParams) {
let newParam = this.exprBuilder(param, userParams[param]);
if (!newParam)
newParam = {[param]: userParams[param]};
Object.assign(params, newParam);
}
}
if (this.$.model.userFilter)
Object.assign(params, this.$.model.userFilter.where);
data.filter = params;
}
return this.$http.post('Buys/editLatestBuys', data)
.then(() => {
this.uncheck();
this.$.model.refresh();
});
}
}
ngModule.component('vnEntryLatestBuys', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,100 +0,0 @@
import './index.js';
describe('Entry', () => {
describe('Component vnEntryLatestBuys', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('entry'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
const $element = angular.element('<vn-entry-latest-buys></vn-entry-latest-buys');
controller = $componentController('vnEntryLatestBuys', {$element});
controller.$ = {
model: {refresh: () => {}},
edit: {hide: () => {}}
};
}));
describe('get columns', () => {
it(`should return a set of columns`, () => {
let result = controller.columns;
let length = result.length;
let anyColumn = Object.keys(result[Math.floor(Math.random() * Math.floor(length))]);
expect(anyColumn).toContain('field', 'displayName');
});
});
describe('get checked', () => {
it(`should return a set of checked lines`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
];
let result = controller.checked;
expect(result.length).toEqual(3);
});
});
describe('onEditAccept()', () => {
it(`should perform a query to update columns`, () => {
controller.editedColumn = {field: 'my field', newValue: 'the new value'};
const query = 'Buys/editLatestBuys';
$httpBackend.expectPOST(query).respond();
controller.onEditAccept();
$httpBackend.flush();
const result = controller.checked;
expect(result.length).toEqual(0);
});
});
describe('reCheck()', () => {
it(`should recheck buys`, () => {
controller.$.model.data = [
{checked: false, id: 1},
{checked: false, id: 2},
{checked: false, id: 3},
{checked: false, id: 4},
];
controller.checkedBuys = [1, 2];
controller.reCheck();
expect(controller.$.model.data[0].checked).toEqual(true);
expect(controller.$.model.data[1].checked).toEqual(true);
expect(controller.$.model.data[2].checked).toEqual(false);
expect(controller.$.model.data[3].checked).toEqual(false);
});
});
describe('saveChecked()', () => {
it(`should check buy`, () => {
const buyCheck = 3;
controller.checkedBuys = [1, 2];
controller.saveChecked(buyCheck);
expect(controller.checkedBuys[2]).toEqual(buyCheck);
});
it(`should uncheck buy`, () => {
const buyUncheck = 3;
controller.checkedBuys = [1, 2, 3];
controller.saveChecked(buyUncheck);
expect(controller.checkedBuys[2]).toEqual(undefined);
});
});
});
});

View File

@ -1,2 +0,0 @@
Minimun amount: Minimun purchase quantity
PackageName: Package

View File

@ -1,19 +0,0 @@
Edit buy(s): Editar compra(s)
Buying value: Coste
Freight value: Porte
Commission value: Comisión
Package value: Embalaje
Is ignored: Ignorado
Is visible: Visible
Is floramondo: Floramondo
Grouping price: Precio grouping
Packing price: Precio packing
Min price: Precio min
Ekt: Ekt
Weight: Peso
Minimun amount: Cantidad mínima de compra
Field to edit: Campo a editar
PackageName: Cubo
Edit: Editar
buy(s): compra(s)
Package out: Embalaje envíos

View File

@ -1,7 +0,0 @@
.countLines {
flex: 0.15;
font-size: 24px;
color: orangered;
font-weight: bold;
max-width: 30px;
}

View File

@ -1,6 +0,0 @@
#Ordenar alfabeticamente
entry: entrada
Latest buys: Últimas compras
# Sections

View File

@ -1 +0,0 @@
<vn-log url="EntryLogs" origin-id="$ctrl.$params.id"></vn-log>

View File

@ -1,7 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnEntryLog', {
template: require('./index.html'),
controller: Section,
});

View File

@ -1 +0,0 @@
Date: Fecha

View File

@ -1,22 +0,0 @@
<vn-crud-model
vn-id="model"
url="Entries/filter"
limit="20"
auto-load="true"
order="landed DESC, id DESC">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-entry-search-panel"
info="Search entry by id or a suppliers by name or alias"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -1,7 +1,15 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Entry extends ModuleMain {}
export default class Entry extends ModuleMain {
constructor($element, $) {
super($element, $);
}
async $onInit() {
this.$state.go('home');
window.location.href = await this.vnApp.getUrl(`entry/`);
}
}
ngModule.vnComponent('vnEntry', {
controller: Entry,

View File

@ -0,0 +1 @@
entry: entry

View File

@ -1,72 +0,0 @@
<vn-crud-model
vn-id="model"
url="EntryObservations"
fields="['id', 'entryFk', 'observationTypeFk', 'description']"
link="{entryFk: $ctrl.$params.id}"
data="observations"
auto-load="true">
</vn-crud-model>
<vn-crud-model
url="ObservationTypes"
data="observationTypes"
auto-load="true">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="observations"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-one class="vn-mt-md">
<vn-horizontal ng-repeat="observation in observations track by $index">
<vn-autocomplete
ng-attr-disabled="observation.id ? true : false"
ng-model="observation.observationTypeFk"
data="observationTypes"
show-field="description"
label="Observation type"
vn-one
vn-focus>
</vn-autocomplete>
<vn-textfield
vn-two
class="vn-mr-lg"
label="Description"
ng-model="observation.description"
rule="EntryObservation">
</vn-textfield>
<vn-auto class="vn-pt-md">
<vn-icon-button
pointer
vn-tooltip="Remove note"
icon="delete"
ng-click="model.remove($index)">
</vn-icon-button>
</vn-auto>
</vn-horizontal>
</vn-one>
<vn-one>
<vn-icon-button
vn-tooltip="Add note"
icon="add_circle"
vn-bind="+"
ng-if="observationTypes.length > observations.length"
ng-click="model.insert()">
</vn-icon-button>
</vn-one>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<!-- # #2680 Undo changes button bugs -->
<!-- <vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button> -->
</vn-button-bar>
</form>

View File

@ -1,20 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
onSubmit() {
this.$.watcher.check();
this.$.model.save().then(() => {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
});
}
}
ngModule.vnComponent('vnEntryObservation', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -8,12 +8,6 @@
"main": [
{"state": "entry.index", "icon": "icon-entry"},
{"state": "entry.latestBuys", "icon": "contact_support"}
],
"card": [
{"state": "entry.card.basicData", "icon": "settings"},
{"state": "entry.card.buy.index", "icon": "icon-lines"},
{"state": "entry.card.observation", "icon": "insert_drive_file"},
{"state": "entry.card.log", "icon": "history"}
]
},
"keybindings": [
@ -33,90 +27,6 @@
"component": "vn-entry-index",
"description": "Entries",
"acl": ["buyer", "administrative"]
},
{
"url": "/latest-buys?q",
"state": "entry.latestBuys",
"component": "vn-entry-latest-buys",
"description": "Latest buys",
"acl": ["buyer", "administrative"]
},
{
"url": "/create?supplierFk&travelFk&companyFk",
"state": "entry.create",
"component": "vn-entry-create",
"description": "New entry",
"acl": ["buyer", "administrative"]
},
{
"url": "/:id",
"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"
},
"acl": ["buyer", "administrative"]
},
{
"url": "/basic-data",
"state": "entry.card.basicData",
"component": "vn-entry-basic-data",
"description": "Basic data",
"params": {
"entry": "$ctrl.entry"
},
"acl": ["buyer", "administrative"]
},
{
"url": "/observation",
"state": "entry.card.observation",
"component": "vn-entry-observation",
"description": "Notes",
"params": {
"entry": "$ctrl.entry"
},
"acl": ["buyer", "administrative"]
},
{
"url" : "/log",
"state": "entry.card.log",
"component": "vn-entry-log",
"description": "Log",
"acl": ["buyer", "administrative"]
},
{
"url": "/buy",
"state": "entry.card.buy",
"abstract": true,
"component": "ui-view",
"acl": ["buyer"]
},
{
"url" : "/index",
"state": "entry.card.buy.index",
"component": "vn-entry-buy-index",
"description": "Buys",
"params": {
"entry": "$ctrl.entry"
},
"acl": ["buyer", "administrative"]
},
{
"url" : "/import",
"state": "entry.card.buy.import",
"component": "vn-entry-buy-import",
"description": "Import buys",
"params": {
"entry": "$ctrl.entry"
},
"acl": ["buyer"]
}
]
}

View File

@ -1,100 +0,0 @@
<div class="search-panel">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="General search"
ng-model="filter.search"
info="Search entry by id or a suppliers by name or alias"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Reference"
ng-model="filter.reference">
</vn-textfield>
<vn-textfield
vn-one
label="Invoice number"
ng-model="filter.invoiceNumber">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Travel"
ng-model="filter.travelFk">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Company"
ng-model="filter.companyFk"
url="Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Currency"
ng-model="filter.currencyFk"
url="Currencies"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Supplier"
ng-model="filter.supplierFk"
url="Suppliers"
fields="['name','nickname']"
search-function="{or: [{nickname: {like: '%'+ $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
show-field="name"
value-field="id">
<tpl-item>{{name}}: {{nickname}}</tpl-item>
</vn-autocomplete>
<vn-date-picker
vn-one
label="Created"
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
label="Booked"
ng-model="filter.isBooked">
</vn-check>
<vn-check
vn-one
label="Confirmed"
ng-model="filter.isConfirmed">
</vn-check>
<vn-check
vn-one
label="Ordered"
ng-model="filter.isOrdered">
</vn-check>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,7 +0,0 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
ngModule.vnComponent('vnEntrySearchPanel', {
template: require('./index.html'),
controller: SearchPanel
});

View File

@ -1,9 +0,0 @@
Ticket id: Id ticket
Client id: Id cliente
Nickname: Alias
From: Desde
To: Hasta
Agency: Agencia
Warehouse: Almacén
Search entry by id or a suppliers by name or alias: Buscar entrada por id o proveedores por nombre y alias
Invoice number: Núm. factura

View File

@ -1,196 +0,0 @@
<vn-crud-model
vn-id="buysModel"
url="Entries/{{$ctrl.entry.id}}/getBuys"
limit="5"
data="buys"
auto-load="true">
</vn-crud-model>
<vn-card class="summary">
<h5>
<a ng-if="::$ctrl.entryData.id"
vn-tooltip="Go to the entry"
ui-sref="entry.card.summary({id: {{::$ctrl.entryData.id}}})"
name="goToSummary">
<vn-icon-button icon="launch"></vn-icon-button>
</a>
<span> #{{$ctrl.entryData.id}} - {{$ctrl.entryData.supplier.nickname}}</span>
</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.reference}}">
</vn-label-value>
<vn-label-value label="Invoice number"
value="{{$ctrl.entryData.invoiceNumber}}">
</vn-label-value>
</vn-one>
<vn-one>
<vn-label-value label="Reference">
<span
ng-click="travelDescriptor.show($event, $ctrl.entryData.travel.id)"
class="link">
{{$ctrl.entryData.travel.ref}}
</span>
</vn-label-value>
<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-check
label="Delivered"
ng-model="$ctrl.entryData.travel.isDelivered"
disabled="true">
</vn-check>
<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-check
label="Received"
ng-model="$ctrl.entryData.travel.isReceived"
disabled="true">
</vn-check>
</vn-one>
<vn-one>
<vn-vertical>
<vn-check
label="Ordered"
ng-model="$ctrl.entryData.isOrdered"
disabled="true">
</vn-check>
<vn-check
label="Confirmed"
ng-model="$ctrl.entryData.isConfirmed"
disabled="true">
</vn-check>
<vn-check
label="Booked"
ng-model="$ctrl.entryData.isBooked"
disabled="true">
</vn-check>
<vn-check
label="Raid"
ng-model="$ctrl.entryData.isRaid"
disabled="true">
</vn-check>
<vn-check
label="Inventory"
ng-model="$ctrl.entryData.isExcludedFromAvailable"
disabled="true">
</vn-check>
</vn-vertical>
</vn-one>
</vn-horizontal>
<vn-horizontal>
<vn-auto name="buys">
<h4 translate>Buys</h4>
<table class="vn-table">
<thead>
<tr>
<th translate center field="quantity">Quantity</th>
<th translate center field="sticker">Stickers</th>
<th translate center field="packagingFk">Package</th>
<th translate center field="weight">Weight</th>
<th translate center field="packing">Packing</th>
<th translate center field="grouping">Grouping</th>
<th translate center field="buyingValue">Buying value</th>
<th translate center field="price3">Import</th>
<th translate center expand field="price">PVP</th>
</tr>
</thead>
<tbody ng-repeat="line in buys">
<tr>
<td center title="{{::line.quantity}}">{{::line.quantity}}</td>
<td center title="{{::line.stickers | dashIfEmpty}}">{{::line.stickers | dashIfEmpty}}</td>
<td center title="{{::line.packagingFk | dashIfEmpty}}">{{::line.packagingFk | dashIfEmpty}}</td>
<td center title="{{::line.weight}}">{{::line.weight}}</td>
<td center>
<vn-chip class="transparent" translate-attr="line.groupingMode == 'packing' ? {title: 'Minimun amount'} : {title: 'Packing'}" ng-class="{'message': line.groupingMode == 'packing'}">
<span>{{::line.packing | dashIfEmpty}}</span>
</vn-chip>
</td>
<td center>
<vn-chip class="transparent" translate-attr="line.groupingMode == 'grouping' ? {title: 'Minimun amount'} : {title: 'Grouping'}" ng-class="{'message': line.groupingMode == 'grouping'}">
<span>{{::line.grouping | dashIfEmpty}}</span>
</vn-chip>
</vn-td>
<td center title="{{::line.buyingValue | currency: 'EUR':2}}">{{::line.buyingValue | currency: 'EUR':2}}</td>
<td center title="{{::line.quantity * line.buyingValue | currency: 'EUR':2}}">{{::line.quantity * line.buyingValue | currency: 'EUR':2}}</td>
<td center title="Grouping / Packing">{{::line.price2 | currency: 'EUR':2 | dashIfEmpty}} / {{::line.price3 | currency: 'EUR':2 | dashIfEmpty}}</td>
</tr>
<tr class="dark-row">
<td shrink>
<span
translate-attr="{title: 'Item type'}">
{{::line.item.itemType.code}}
</span>
</td>
<td shrink>
<span
ng-click="itemDescriptor.show($event, line.item.id)"
class="link">
{{::line.item.id}}
</span>
</td>
<td number shrink>
<span
translate-attr="{title: 'Item size'}">
{{::line.item.size}}
</span>
</td>
<td center>
<span
translate-attr="{title: 'Minimum price'}">
{{::line.item.minPrice | currency: 'EUR':2}}
</span>
</td>
<td vn-fetched-tags colspan="6">
<div>
<vn-one title="{{::line.item.concept}}">{{::line.item.name}}</vn-one>
<vn-one ng-if="::line.item.subName">
<h3 title="{{::line.item.subName}}">{{::line.item.subName}}</h3>
</vn-one>
</div>
<vn-fetched-tags
max-length="6"
item="::line.item"
tabindex="-1">
</vn-fetched-tags>
</td>
</tr>
<tr class="empty-row">
<td colspan="10"></td>
</tr>
</tbody>
</table>
<vn-pagination
model="buysModel"
class="vn-pt-xs">
</vn-pagination>
</vn-auto>
</vn-horizontal>
</vn-card>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-travel-descriptor-popover
vn-id="travelDescriptor">
</vn-travel-descriptor-popover>

View File

@ -1,33 +0,0 @@
import ngModule from '../module';
import './style.scss';
import Summary from 'salix/components/summary';
class Controller extends Summary {
get entry() {
if (!this._entry)
return this.$params;
return this._entry;
}
set entry(value) {
this._entry = value;
if (value && value.id)
this.getEntryData();
}
getEntryData() {
return this.$http.get(`Entries/${this.entry.id}/getEntry`).then(response => {
this.entryData = response.data;
});
}
}
ngModule.vnComponent('vnEntrySummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
entry: '<'
}
});

View File

@ -1,49 +0,0 @@
import './index';
describe('component vnEntrySummary', () => {
let controller;
let $httpBackend;
let $scope;
beforeEach(angular.mock.module('entry', $translateProvider => {
$translateProvider.translations('en', {});
}));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
const $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', () => {
jest.spyOn(controller, 'getEntryData');
controller.entry = {id: 1};
expect(controller.getEntryData).toHaveBeenCalledWith();
});
it('should return the entry and then call getEntryData()', () => {
jest.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 = `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

@ -1,11 +0,0 @@
Inventory: Inventario
Raid: Redada
Entry: Entrada
Stickers: Etiquetas
Item size: Tamaño
Item type: Tipo
Minimum price: Precio mínimo
Buys: Compras
Travel: Envio
Go to the entry: Ir a la entrada
Invoice number: Núm. factura

View File

@ -1,30 +0,0 @@
@import "variables";
vn-entry-summary .summary {
max-width: $width-lg;
.dark-row {
background-color: lighten($color-marginal, 10%);
}
tbody tr:nth-child(1) {
border-top: $border-thin;
}
tbody tr:nth-child(1),
tbody tr:nth-child(2) {
border-left: $border-thin;
border-right: $border-thin
}
tbody tr:nth-child(3) {
height: inherit
}
tr {
margin-bottom: 10px;
}
}
$color-font-link-medium: lighten($color-font-link, 20%)

View File

@ -0,0 +1,70 @@
module.exports = Self => {
Self.remoteMethod('getListItemNewer', {
description:
'Get boolean if any or specific item of the shelving has older created in another shelving or parking',
accessType: 'READ',
accepts: [{
arg: 'shelvingFk',
type: 'string',
required: true,
description: 'Shelving code'
},
{
arg: 'parking',
type: 'string',
required: true,
description: 'Parking code'
},
],
returns: {
type: 'Array',
root: true
},
http: {
path: `/getListItemNewer`,
verb: 'GET'
}
});
Self.getListItemNewer = async(shelvingFk, parking, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const [isParkingToReview] = await Self.rawSql(`
SELECT COUNT(p.id) parkingToReview
FROM vn.parking p
JOIN vn.sector s ON s.id = p.sectorFk
JOIN vn.productionConfig pc
WHERE p.code = ? AND s.code = pc.sectorToCode;`,
[parking], myOptions);
if (isParkingToReview['parkingToReview'] < 1) return [];
const result = await Self.rawSql(`
WITH tItemShelving AS(
SELECT is2.itemFk, is2.created, p.sectorFK, is2.id
FROM vn.itemShelving is2
JOIN vn.shelving sh ON sh.code = is2.shelvingFk
JOIN vn.parking p ON p.id = sh.parkingFk
JOIN vn.sector s ON s.id = p.sectorFk
JOIN vn.productionConfig pc
WHERE is2.shelvingFk = ? AND s.code = pc.sectorFromCode
), tItemInSector AS (
SELECT is2.itemFk, is2.created, is2.shelvingFk
FROM vn.itemShelving is2
JOIN vn.shelving sh ON sh.code = is2.shelvingFk
JOIN vn.parking p ON p.id = sh.parkingFk
JOIN vn.sector s ON s.id = p.sectorFk
JOIN vn.productionConfig pc
WHERE is2.shelvingFk <> ?
AND s.code = pc.sectorFromCode)
SELECT ti.itemFK, tis.shelvingFk
FROM tItemShelving ti
JOIN tItemInSector tis ON tis.itemFk = ti.itemFk
JOIN vn.productionConfig pc
WHERE ti.created > tis.created + INTERVAL pc.itemOlderReviewHours HOUR;`,
[shelvingFk, shelvingFk], myOptions);
return result;
};
};

View File

@ -1,63 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('hasItemOlder', {
description:
'Get boolean if any or specific item of the shelving has older created in another shelving or parking',
accessType: 'READ',
accepts: [{
arg: 'shelvingFkIn',
type: 'string',
required: true,
description: 'Shelving code'
},
{
arg: 'parking',
type: 'string',
description: 'Parking code'
},
{
arg: 'shelvingFkOut',
type: 'string',
description: 'Shelving code'
},
{
arg: 'itemFk',
type: 'integer',
description: 'Item id'
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/hasItemOlder`,
verb: 'GET'
}
});
Self.hasItemOlder = async(shelvingFkIn, parking, shelvingFkOut, itemFk, options) => {
if (!parking && !shelvingFkOut) throw new UserError('Missing data: parking or shelving');
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const result = await Self.rawSql(`
SELECT COUNT(ish.id) countItemOlder
FROM vn.itemShelving ish
JOIN (
SELECT ish.itemFk, created,shelvingFk
FROM vn.itemShelving ish
JOIN vn.shelving s ON ish.shelvingFk = s.code
WHERE ish.shelvingFk = ?
)sub ON sub.itemFK = ish.itemFk
JOIN vn.shelving s ON s.code = ish.shelvingFk
JOIN vn.parking p ON p.id = s.parkingFk
WHERE sub.created > ish.created
AND (p.code <> ? OR ? IS NULL)
AND (ish.shelvingFk <> ? OR ? IS NULL)
AND (ish.itemFk <> ? OR ? IS NULL)`,
[shelvingFkIn, parking, parking, shelvingFkOut, shelvingFkOut, itemFk, itemFk], myOptions);
return result[0]['countItemOlder'] > 0;
};
};

View File

@ -0,0 +1,45 @@
const {models} = require('vn-loopback/server/server');
describe('itemShelving getListItemNewer()', () => {
it('should return true because there is an older item', async() => {
const shelving = 'NCC';
const parking = 'A-47-1';
const sectorCamHighCode = 'CAMARA SECTOR D';
const sectorCamCode = 'NAVE ALGEMESI';
const sectorCamCodeHighId = 1;
const sectorCamCodeId = 9991;
const tx = await models.Sector.beginTransaction({});
const myOptions = {transaction: tx};
try {
const sectorHighCam = await models.Sector.findById(sectorCamCodeHighId, null, myOptions);
await sectorHighCam.updateAttributes({
code: sectorCamHighCode
});
const sectorCam = await models.Sector.findById(sectorCamCodeId, null, myOptions);
await sectorCam.updateAttributes({
code: sectorCamCode
});
const config = await models.ProductionConfig.findOne();
await config.updateAttributes({
sectorToCode: sectorCamHighCode,
sectorFromCode: sectorCamCode
});
const result = await models.ItemShelving.getListItemNewer(shelving, parking, myOptions);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,45 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('itemShelving hasOlder()', () => {
it('should return false because there are not older items', async() => {
const shelvingFkIn = 'GVC';
const shelvingFkOut = 'HEJ';
const result = await models.ItemShelving.hasItemOlder(shelvingFkIn, null, shelvingFkOut);
expect(result).toBe(false);
});
it('should return false because there are not older items in parking', async() => {
const shelvingFkIn = 'HEJ';
const parking = '700-01';
const result = await models.ItemShelving.hasItemOlder(shelvingFkIn, parking);
expect(result).toBe(false);
});
it('should return true because there is an older item', async() => {
const shelvingFkIn = 'UXN';
const shelvingFkOut = 'PCC';
const parking = 'A-01-1';
const itemFk = 1;
const tx = await models.ItemShelving.beginTransaction({});
const myOptions = {transaction: tx};
const filter = {where: {shelvingFk: shelvingFkOut}
};
try {
const itemShelvingBefore = await models.ItemShelving.findOne(filter, myOptions);
await itemShelvingBefore.updateAttributes({
itemFk: itemFk
}, myOptions);
const result = await models.ItemShelving.hasItemOlder(shelvingFkIn, parking, null, null, myOptions);
expect(result).toBe(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -4,5 +4,5 @@ module.exports = Self => {
require('../methods/item-shelving/getInventory')(Self);
require('../methods/item-shelving/getAlternative')(Self);
require('../methods/item-shelving/updateFromSale')(Self);
require('../methods/item-shelving/hasItemOlder')(Self);
require('../methods/item-shelving/getListItemNewer')(Self);
};

View File

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

View File

@ -41,14 +41,16 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
ids = ids.split(',');
for (const id of ids) {
const downloadAddZip = async id => {
ctx.args = ctx.args || {};
ctx.args.id = Number(id);
const [data] = await models.Route.cmr(ctx, myOptions);
zip.file(`${id}.pdf`, data, {binary: true});
}
};
ids = ids.split(',');
const promises = ids.map(id => downloadAddZip(id));
await Promise.all(promises);
const zipStream = zip.generateNodeStream({streamFiles: true});
return [zipStream, 'application/zip', `filename="cmrs.zip"`];
};

View File

@ -20,21 +20,4 @@ module.exports = Self => {
require('../methods/route/cmrEmail')(Self);
require('../methods/route/getExpeditionSummary')(Self);
require('../methods/route/getByWorker')(Self);
Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 4000'
});
Self.validate('kmEnd', validateDistance, {
message: 'Distance must be lesser than 4000'
});
function validateDistance(err) {
if (this.kmEnd) {
const routeTotalKm = this.kmEnd - this.kmStart;
const routeMaxKm = 4000;
if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
err();
}
}
};

View File

@ -48,6 +48,9 @@
"priority": {
"type": "number"
},
"roadmapStopFk": {
"type": "number"
},
"m3boxes": {
"type": "number"
},

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