diff --git a/db/changes/10260-holidays/00-travel_cloneWithEntries.sql b/db/changes/10260-holidays/00-travel_cloneWithEntries.sql
new file mode 100644
index 000000000..1302a78ca
--- /dev/null
+++ b/db/changes/10260-holidays/00-travel_cloneWithEntries.sql
@@ -0,0 +1,135 @@
+-- DROP PROCEDURE `vn`.`clonTravelComplete`;
+
+DELIMITER $$
+USE `vn`$$
+CREATE
+ DEFINER = root@`%` PROCEDURE `vn`.`travel_cloneWithEntries`(IN vTravelFk INT, IN vDateStart DATE, IN vDateEnd DATE,
+ IN vRef VARCHAR(255), OUT vNewTravelFk INT)
+BEGIN
+ DECLARE vEntryNew INT;
+ DECLARE vDone BOOLEAN DEFAULT FALSE;
+ DECLARE vAuxEntryFk INT;
+ DECLARE vRsEntry CURSOR FOR
+ SELECT e.id
+ FROM entry e
+ JOIN travel t
+ ON t.id = e.travelFk
+ WHERE e.travelFk = vTravelFk;
+
+ DECLARE vRsBuy CURSOR FOR
+ SELECT b.*
+ FROM buy b
+ JOIN entry e
+ ON b.entryFk = e.id
+ WHERE e.travelFk = vNewTravelFk and b.entryFk=vNewTravelFk
+ ORDER BY e.id;
+
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
+
+ DECLARE EXIT HANDLER FOR SQLEXCEPTION
+ BEGIN
+ ROLLBACK;
+ RESIGNAL;
+ END;
+
+ START TRANSACTION;
+
+ INSERT INTO travel (shipped,landed, warehouseInFk, warehouseOutFk, agencyFk, ref, isDelivered, isReceived, m3, kg)
+ SELECT vDateStart, vDateEnd,warehouseInFk, warehouseOutFk, agencyFk, vRef, isDelivered, isReceived, m3, kg
+ FROM travel
+ WHERE id = vTravelFk;
+
+ SET vNewTravelFk = LAST_INSERT_ID();
+ SET vDone = FALSE;
+ OPEN vRsEntry ;
+ FETCH vRsEntry INTO vAuxEntryFk;
+
+ WHILE NOT vDone DO
+ INSERT INTO entry (supplierFk,
+ ref,
+ isInventory,
+ isConfirmed,
+ isOrdered,
+ isRaid,
+ commission,
+ created,
+ evaNotes,
+ travelFk,
+ currencyFk,
+ companyFk,
+ gestDocFk,
+ invoiceInFk)
+ SELECT supplierFk,
+ ref,
+ isInventory,
+ isConfirmed,
+ isOrdered,
+ isRaid,
+ commission,
+ created,
+ evaNotes,
+ vNewTravelFk,
+ currencyFk,
+ companyFk,
+ gestDocFk,
+ invoiceInFk
+ FROM entry
+ WHERE id = vAuxEntryFk;
+
+ SET vEntryNew = LAST_INSERT_ID();
+
+
+ INSERT INTO buy (entryFk,
+ itemFk,
+ quantity,
+ buyingValue,
+ packageFk,
+ stickers,
+ freightValue,
+ packageValue,
+ comissionValue,
+ packing,
+ `grouping`,
+ groupingMode,
+ location,
+ price1,
+ price2,
+ price3,
+ minPrice,
+ producer,
+ printedStickers,
+ isChecked,
+ weight)
+ SELECT vEntryNew,
+ itemFk,
+ quantity,
+ buyingValue,
+ packageFk,
+ stickers,
+ freightValue,
+ packageValue,
+ comissionValue,
+ packing,
+ `grouping`,
+ groupingMode,
+ location,
+ price1,
+ price2,
+ price3,
+ minPrice,
+ producer,
+ printedStickers,
+ isChecked,
+ weight
+ FROM buy
+ WHERE entryFk = vAuxEntryFk;
+
+
+ FETCH vRsEntry INTO vAuxEntryFk;
+ END WHILE;
+ CLOSE vRsEntry;
+ COMMIT;
+END;$$
+DELIMITER ;
+
+
diff --git a/db/changes/10260-holidays/00-zoneLog.sql b/db/changes/10260-holidays/00-zoneLog.sql
new file mode 100644
index 000000000..13d81bc92
--- /dev/null
+++ b/db/changes/10260-holidays/00-zoneLog.sql
@@ -0,0 +1,18 @@
+CREATE TABLE `vn`.`zoneLog` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `originFk` int(10) NOT NULL,
+ `userFk` int(10) unsigned DEFAULT NULL,
+ `action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL,
+ `creationDate` timestamp NULL DEFAULT current_timestamp(),
+ `description` text CHARACTER SET utf8 DEFAULT NULL,
+ `changedModel` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
+ `oldInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
+ `newInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
+ `changedModelId` int(11) DEFAULT NULL,
+ `changedModelValue` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `originFk` (`originFk`),
+ KEY `userFk` (`userFk`),
+ CONSTRAINT `zoneLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `vn`.`zone` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `zoneLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
\ No newline at end of file
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index 0ed3607ad..8d40542b8 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -831,7 +831,8 @@ export default {
firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)'
},
travelExtraCommunity: {
- firstTravelReference: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr > vn-td-editable',
+ anySearchResult: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr',
+ firstTravelReference: 'vn-travel-extra-community vn-card:nth-child(1) vn-td-editable',
removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i'
},
travelBasicData: {
@@ -863,7 +864,18 @@ export default {
travelDescriptor: {
filterByAgencyButton: 'vn-descriptor-content .quicklinks > div:nth-child(1) > vn-quick-link > a[vn-tooltip="All travels with current agency"]',
dotMenu: 'vn-travel-descriptor vn-icon-button[icon="more_vert"]',
- dotMenuClone: '#clone'
+ dotMenuClone: '#clone',
+ dotMenuCloneWithEntries: '#cloneWithEntries',
+ acceptClonation: 'tpl-buttons > button[response="accept"]'
+ },
+ travelCreate: {
+ reference: 'vn-travel-create vn-textfield[ng-model="$ctrl.travel.ref"]',
+ agency: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.agencyModeFk"]',
+ shipped: 'vn-travel-create vn-date-picker[ng-model="$ctrl.travel.shipped"]',
+ landed: 'vn-travel-create vn-date-picker[ng-model="$ctrl.travel.landed"]',
+ warehouseOut: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseOutFk"]',
+ warehouseIn: 'vn-travel-create vn-autocomplete[ng-model="$ctrl.travel.warehouseInFk"]',
+ saveButton: 'vn-travel-create vn-submit[label="Save"]'
},
zoneIndex: {
searchResult: 'vn-zone-index a.vn-tr',
@@ -927,6 +939,14 @@ export default {
newEntryCompany: 'vn-entry-create vn-autocomplete[ng-model="$ctrl.entry.companyFk"]',
saveNewEntry: 'vn-entry-create button[type="submit"]'
},
+ entryObservations: {
+ addNewObservation: 'vn-entry-observation vn-icon-button[icon="add_circle"]',
+ firstObservationType: 'vn-entry-observation vn-horizontal:nth-child(1) > vn-autocomplete[ng-model="observation.observationTypeFk"]',
+ secondObservationType: 'vn-entry-observation vn-horizontal:nth-child(2) > vn-autocomplete[ng-model="observation.observationTypeFk"]',
+ firstObservationDescription: 'vn-entry-observation vn-horizontal:nth-child(1) > vn-textfield[ng-model="observation.description"]',
+ secondObservationDescription: 'vn-entry-observation vn-horizontal:nth-child(2) > vn-textfield[ng-model="observation.description"]',
+ saveObservationsButton: 'vn-entry-observation vn-submit > button'
+ },
supplierSummary: {
header: 'vn-supplier-summary > vn-card > h5',
basicDataId: 'vn-supplier-summary vn-label-value[label="Id"]',
diff --git a/e2e/paths/02-client/03_edit_fiscal_data.spec.js b/e2e/paths/02-client/03_edit_fiscal_data.spec.js
index da04c813a..614ede0f0 100644
--- a/e2e/paths/02-client/03_edit_fiscal_data.spec.js
+++ b/e2e/paths/02-client/03_edit_fiscal_data.spec.js
@@ -200,7 +200,7 @@ describe('Client Edit fiscalData path', () => {
it('should confirm the sageTransaction have been edited', async() => {
const result = await page.waitToGetProperty(selectors.clientFiscalData.sageTransaction, 'value');
- expect(result).toEqual('Regularización de inversiones');
+ expect(result).toEqual('36: Regularización de inversiones');
});
it('should confirm the transferor have been edited', async() => {
diff --git a/e2e/paths/06-claim/05_summary.spec.js b/e2e/paths/06-claim/05_summary.spec.js
index c63e686cb..cea5edb55 100644
--- a/e2e/paths/06-claim/05_summary.spec.js
+++ b/e2e/paths/06-claim/05_summary.spec.js
@@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
-describe('claim Summary path', () => {
+describe('Claim summary path', () => {
let browser;
let page;
const claimId = '4';
diff --git a/e2e/paths/06-claim/06_descriptor.spec.js b/e2e/paths/06-claim/06_descriptor.spec.js
index c0affb3cc..0826bad63 100644
--- a/e2e/paths/06-claim/06_descriptor.spec.js
+++ b/e2e/paths/06-claim/06_descriptor.spec.js
@@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
-describe('claim Descriptor path', () => {
+describe('Claim descriptor path', () => {
let browser;
let page;
const claimId = '1';
diff --git a/e2e/paths/08-route/03_create_and_clone.spec.js b/e2e/paths/08-route/03_create_and_clone.spec.js
index be758f788..c0132362f 100644
--- a/e2e/paths/08-route/03_create_and_clone.spec.js
+++ b/e2e/paths/08-route/03_create_and_clone.spec.js
@@ -74,6 +74,7 @@ describe('Route create path', () => {
});
it(`should clone the first route`, async() => {
+ await page.waitForTimeout(1000); // needs time for the index to show all items
await page.waitToClick(selectors.routeIndex.firstRouteCheckbox);
await page.waitToClick(selectors.routeIndex.cloneButton);
await page.waitToClick(selectors.routeIndex.submitClonationButton);
diff --git a/e2e/paths/10-travel/03_descriptor.spec.js b/e2e/paths/10-travel/03_descriptor.spec.js
index 3f79bea06..cdca379ad 100644
--- a/e2e/paths/10-travel/03_descriptor.spec.js
+++ b/e2e/paths/10-travel/03_descriptor.spec.js
@@ -42,4 +42,48 @@ describe('Travel descriptor path', () => {
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 atempt to clone the travel and its entries using the descriptor menu but receive an error', async() => {
+ await page.waitToClick(selectors.travelDescriptor.dotMenu);
+ await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries);
+ await page.waitToClick(selectors.travelDescriptor.acceptClonation);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('A travel with this data already exists');
+ });
+
+ it('should update the landed date to a future date to enable cloneWithEntries', async() => {
+ const nextMonth = new Date();
+ 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="preview"]'); // summary icon
+ await page.waitForState('travel.card.summary');
+ await page.waitToClick(selectors.travelDescriptor.dotMenu);
+ await page.waitToClick(selectors.travelDescriptor.dotMenuCloneWithEntries);
+ await page.waitToClick(selectors.travelDescriptor.acceptClonation);
+ await page.waitForState('travel.card.basicData');
+ });
});
diff --git a/e2e/paths/10-travel/04_extra_community.spec.js b/e2e/paths/10-travel/04_extra_community.spec.js
index bc81c086c..7a37b89e4 100644
--- a/e2e/paths/10-travel/04_extra_community.spec.js
+++ b/e2e/paths/10-travel/04_extra_community.spec.js
@@ -18,6 +18,7 @@ describe('Travel extra community path', () => {
it('should edit the travel reference', async() => {
await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter);
+ await page.waitForSpinnerLoad();
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
});
diff --git a/e2e/paths/12-entry/06_observations.spec.js b/e2e/paths/12-entry/06_observations.spec.js
new file mode 100644
index 000000000..107c2e0b6
--- /dev/null
+++ b/e2e/paths/12-entry/06_observations.spec.js
@@ -0,0 +1,66 @@
+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'); // access denied, awaiting role confirmation
+ await page.loginAndModule('developer', '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, 'comercial');
+ await page.autocompleteSearch(selectors.entryObservations.secondObservationType, 'comercial');
+ 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('comercial');
+ });
+
+ 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');
+ });
+});
diff --git a/e2e/paths/13-supplier/03_fiscal_data.spec.js b/e2e/paths/13-supplier/03_fiscal_data.spec.js
index d929288d4..0238c8704 100644
--- a/e2e/paths/13-supplier/03_fiscal_data.spec.js
+++ b/e2e/paths/13-supplier/03_fiscal_data.spec.js
@@ -23,10 +23,13 @@ describe('Supplier fiscal data path', () => {
await page.clearInput(selectors.supplierFiscalData.country);
await page.clearInput(selectors.supplierFiscalData.postCode);
await page.write(selectors.supplierFiscalData.city, 'Valencia');
+ await page.waitForTimeout(1000); // must repeat this action twice or fails. also #2699 may be a cool solution to this.
+ await page.clearInput(selectors.supplierFiscalData.city);
+ await page.write(selectors.supplierFiscalData.city, 'Valencia');
await page.clearInput(selectors.supplierFiscalData.socialName);
await page.write(selectors.supplierFiscalData.socialName, 'Farmer King SL');
await page.clearInput(selectors.supplierFiscalData.taxNumber);
- await page.write(selectors.supplierFiscalData.taxNumber, 'invalid tax number');
+ await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number');
await page.clearInput(selectors.supplierFiscalData.account);
await page.write(selectors.supplierFiscalData.account, 'edited account number');
await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva');
diff --git a/loopback/common/models/vn-model.js b/loopback/common/models/vn-model.js
index f56183df2..cc3eede8e 100644
--- a/loopback/common/models/vn-model.js
+++ b/loopback/common/models/vn-model.js
@@ -145,9 +145,15 @@ module.exports = function(Self) {
rewriteDbError(replaceErrFunc) {
function replaceErr(err, replaceErrFunc) {
if (Array.isArray(err)) {
+ const errors = err.filter(error => {
+ return error != undefined && error != null;
+ });
let errs = [];
- for (let e of err)
- errs.push(replaceErrFunc(e));
+ for (let e of errors) {
+ if (!(e instanceof UserError))
+ errs.push(replaceErrFunc(e));
+ else errs.push(e);
+ }
return errs;
}
return replaceErrFunc(err);
diff --git a/loopback/locale/en.json b/loopback/locale/en.json
index 0081af429..1f85356dd 100644
--- a/loopback/locale/en.json
+++ b/loopback/locale/en.json
@@ -84,5 +84,7 @@
"companyFk": "Company",
"You need to fill sage information before you check verified data": "You need to fill sage information before you check verified data",
"The social name cannot be empty": "The social name cannot be empty",
- "The nif cannot be empty": "The nif cannot be empty"
+ "The nif cannot be empty": "The nif cannot be empty",
+ "A travel with this data already exists": "A travel with this data already exists",
+ "The observation type can't be repeated": "The observation type can't be repeated"
}
\ No newline at end of file
diff --git a/loopback/util/log.js b/loopback/util/log.js
index baba3e827..d81fc39a0 100644
--- a/loopback/util/log.js
+++ b/loopback/util/log.js
@@ -33,6 +33,9 @@ exports.translateValues = async(instance, changes) => {
}).format(date);
}
+ if (changes instanceof instance)
+ changes = changes.__data;
+
const properties = Object.assign({}, changes);
for (let property in properties) {
const relation = getRelation(instance, property);
@@ -41,13 +44,14 @@ exports.translateValues = async(instance, changes) => {
if (relation) {
let fieldsToShow = ['alias', 'name', 'code', 'description'];
- const log = instance.definition.settings.log;
+ const modelName = relation.model;
+ const model = models[modelName];
+ const log = model.definition.settings.log;
if (log && log.showField)
- fieldsToShow = log.showField;
+ fieldsToShow = [log.showField];
- const model = relation.model;
- const row = await models[model].findById(value, {
+ const row = await model.findById(value, {
fields: fieldsToShow
});
const newValue = getValue(row);
diff --git a/modules/claim/front/basic-data/index.html b/modules/claim/front/basic-data/index.html
index eb3c3b9e3..064a9d4f5 100644
--- a/modules/claim/front/basic-data/index.html
+++ b/modules/claim/front/basic-data/index.html
@@ -1,8 +1,9 @@
+