diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index d0d215900..36fcc00d4 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2348,4 +2348,25 @@ INSERT INTO `vn`.`zoneAgencyMode`(`id`, `agencyModeFk`, `zoneFk`) (1, 1, 1), (2, 1, 2), (3, 6, 5), - (4, 7, 1); \ No newline at end of file + (4, 7, 1); + +INSERT INTO `vn`.`expeditionTruck` (`id`, `ETD`, `description`) + VALUES + (1, CONCAT(YEAR(DATE_ADD(CURDATE(), INTERVAL +3 YEAR))), 'Best truck in fleet'); + +INSERT INTO `vn`.`expeditionPallet` (`id`, `truckFk`, `built`, `position`, `isPrint`) + VALUES + (1, 1, CURDATE(), 1, 1); + +INSERT INTO `vn`.`expeditionScan` (`id`, `expeditionFk`, `scanned`, `palletFk`) + VALUES + (1, 1, CURDATE(), 1), + (2, 2, CURDATE(), 1), + (3, 3, CURDATE(), 1), + (4, 4, CURDATE(), 1), + (5, 5, CURDATE(), 1), + (6, 6, CURDATE(), 1), + (7, 7, CURDATE(), 1), + (8, 8, CURDATE(), 1), + (9, 9, CURDATE(), 1), + (10, 10, CURDATE(), 1); \ No newline at end of file diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 5060a8921..850a8c3fb 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -474,7 +474,7 @@ export default { advancedSearchDaysOnward: 'vn-ticket-search-panel vn-input-number[ng-model="filter.scopeDays"]', advancedSearchClient: 'vn-ticket-search-panel vn-textfield[ng-model="filter.clientFk"]', advancedSearchButton: 'vn-ticket-search-panel button[type=submit]', - newTicketButton: 'vn-ticket-index a[ui-sref="ticket.create"]', + newTicketButton: 'vn-ticket-index vn-button[icon="add"]', searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr', firstTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > vn-check', secondTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(2) > vn-td:nth-child(1) > vn-check', @@ -888,7 +888,7 @@ export default { }, workerCalendar: { year: 'vn-worker-calendar vn-autocomplete[ng-model="$ctrl.year"]', - totalHolidaysUsed: 'vn-worker-calendar div.totalBox > div', + totalHolidaysUsed: 'vn-worker-calendar div.totalBox:first-child > div', penultimateMondayOfJanuary: 'vn-worker-calendar vn-calendar:nth-child(2) section:nth-child(22) > div', lastMondayOfMarch: 'vn-worker-calendar vn-calendar:nth-child(4) section:nth-child(29) > div', fistMondayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(8) > div', @@ -896,11 +896,11 @@ export default { secondTuesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(16) > div', secondWednesdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(17) > div', secondThursdayOfMay: 'vn-worker-calendar vn-calendar:nth-child(6) section:nth-child(18) > div', - holidays: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(1)', - absence: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(2)', - halfHoliday: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(3)', - furlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(4)', - halfFurlough: 'vn-worker-calendar > vn-side-menu div:nth-child(3) > vn-chip:nth-child(5)', + holidays: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(1)', + absence: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(2)', + halfHoliday: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(3)', + furlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(4)', + halfFurlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(5)', }, invoiceOutIndex: { topbarSearch: 'vn-searchbar', diff --git a/e2e/paths/10-travel/01_create.spec.js b/e2e/paths/10-travel/01_create.spec.js index 6f5956377..e5d812ebd 100644 --- a/e2e/paths/10-travel/01_create.spec.js +++ b/e2e/paths/10-travel/01_create.spec.js @@ -26,7 +26,8 @@ describe('Travel create path', () => { it('should fill the reference, agency and ship date then save the form', async() => { await page.write(selectors.travelIndex.reference, 'Testing reference'); await page.autocompleteSearch(selectors.travelIndex.agency, 'inhouse pickup'); - await page.pickDate(selectors.travelIndex.shipDate, date); + await page.pickDate(selectors.travelIndex.shipDate, date); // this line autocompletes another 3 fields + await page.waitForTimeout(1000); await page.waitToClick(selectors.travelIndex.save); const message = await page.waitForSnackbar(); @@ -35,8 +36,6 @@ describe('Travel create path', () => { }); it('should check the user was redirected to the travel basic data upon creation', async() => { - // backup code for further intermitences still on track. - // await page.screenshot({path: 'e2e/paths/10-travel/error.jpeg', type: 'jpeg'}); await page.waitForState('travel.card.basicData'); }); diff --git a/front/core/components/autocomplete/index.js b/front/core/components/autocomplete/index.js index 56b30667e..52491f7e0 100755 --- a/front/core/components/autocomplete/index.js +++ b/front/core/components/autocomplete/index.js @@ -11,7 +11,7 @@ import './style.scss'; * @property {String} valueField The data field name that should be used as value * @property {Array} data Static data for the autocomplete * @property {Object} intialData An initial data to avoid the server request used to get the selection - * @property {Boolean} multiple Wether to allow multiple selection + * @property {Boolean} multiple Whether to allow multiple selection * @property {Object} selection Current object selected * * @event change Thrown when value is changed diff --git a/front/core/components/popup/index.js b/front/core/components/popup/index.js index 2a2433770..3743b9f41 100644 --- a/front/core/components/popup/index.js +++ b/front/core/components/popup/index.js @@ -17,7 +17,7 @@ export default class Popup extends Component { } /** - * @type {Boolean} Wether to show or hide the popup. + * @type {Boolean} Whether to show or hide the popup. */ get shown() { return this._shown; diff --git a/front/core/components/table/style.scss b/front/core/components/table/style.scss index 7da20fe82..dea26a462 100644 --- a/front/core/components/table/style.scss +++ b/front/core/components/table/style.scss @@ -102,7 +102,7 @@ vn-table { & > vn-one { overflow: hidden; text-overflow: ellipsis; - font-size: 0.75rem; + font-size: 1rem; } & > vn-one:nth-child(2) h3 { diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 89e301fca..e2194b8ad 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -180,5 +180,6 @@ "This genus already exist": "Este genus ya existe", "This specie already exist": "Esta especie ya existe", "Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})", - "None": "Ninguno" + "None": "Ninguno", + "The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada" } \ No newline at end of file diff --git a/modules/entry/back/methods/entry/addBuy.js b/modules/entry/back/methods/entry/addBuy.js index a7d2f4646..f21c1650c 100644 --- a/modules/entry/back/methods/entry/addBuy.js +++ b/modules/entry/back/methods/entry/addBuy.js @@ -89,7 +89,7 @@ module.exports = Self => { const newBuy = await models.Buy.create(ctx.args, myOptions); - let filter = { + const filter = { fields: [ 'id', 'itemFk', @@ -136,7 +136,7 @@ module.exports = Self => { } }; - let stmts = []; + const stmts = []; let stmt; stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc'); diff --git a/modules/entry/back/methods/entry/deleteBuys.js b/modules/entry/back/methods/entry/deleteBuys.js index de038d66e..951abfd4c 100644 --- a/modules/entry/back/methods/entry/deleteBuys.js +++ b/modules/entry/back/methods/entry/deleteBuys.js @@ -32,7 +32,7 @@ module.exports = Self => { } try { - let promises = []; + const promises = []; for (let buy of ctx.args.buys) { const buysToDelete = models.Buy.destroyById(buy.id, myOptions); promises.push(buysToDelete); diff --git a/modules/entry/back/methods/entry/editLatestBuys.js b/modules/entry/back/methods/entry/editLatestBuys.js index bd5358e31..be379b0a3 100644 --- a/modules/entry/back/methods/entry/editLatestBuys.js +++ b/modules/entry/back/methods/entry/editLatestBuys.js @@ -30,7 +30,18 @@ module.exports = Self => { } }); - Self.editLatestBuys = async(field, newValue, lines) => { + Self.editLatestBuys = async(field, newValue, lines, options) => { + let tx; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + let modelName; let identifier; @@ -60,28 +71,27 @@ module.exports = Self => { const models = Self.app.models; const model = models[modelName]; - let tx = await model.beginTransaction({}); - try { let promises = []; - let options = {transaction: tx}; - let targets = lines.map(line => { + const targets = lines.map(line => { return line[identifier]; }); - let value = {}; + const value = {}; value[field] = newValue; - // intentarlo con updateAll for (let target of targets) - promises.push(model.upsertWithWhere({id: target}, value, options)); + promises.push(model.upsertWithWhere({id: target}, value, myOptions)); - await Promise.all(promises); - await tx.commit(); - } catch (error) { - await tx.rollback(); - throw error; + const result = await Promise.all(promises, myOptions); + + if (tx) await tx.commit(); + + return result; + } catch (e) { + if (tx) await tx.rollback(); + throw e; } }; }; diff --git a/modules/entry/back/methods/entry/filter.js b/modules/entry/back/methods/entry/filter.js index 0148d866d..24c518de8 100644 --- a/modules/entry/back/methods/entry/filter.js +++ b/modules/entry/back/methods/entry/filter.js @@ -13,71 +13,85 @@ module.exports = Self => { type: 'object', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', http: {source: 'query'} - }, { + }, + { arg: 'search', type: 'string', description: 'Searchs the entry by id', http: {source: 'query'} - }, { + }, + { arg: 'id', type: 'integer', description: 'The entry id', http: {source: 'query'} - }, { + }, + { arg: 'created', type: 'date', description: 'The created date to filter', http: {source: 'query'} - }, { + }, + { arg: 'travelFk', type: 'number', description: 'The travel id to filter', http: {source: 'query'} - }, { + }, + { arg: 'companyFk', type: 'number', description: 'The company to filter', http: {source: 'query'} - }, { + }, + { arg: 'isBooked', type: 'boolean', description: 'The isBokked filter', http: {source: 'query'} - }, { + }, + { arg: 'isConfirmed', type: 'boolean', description: 'The isConfirmed filter', http: {source: 'query'} - }, { + }, + { arg: 'isOrdered', type: 'boolean', description: 'The isOrdered filter', http: {source: 'query'} - }, { + }, + { arg: 'ref', type: 'string', description: 'The ref filter', http: {source: 'query'} - }, { + }, + { arg: 'supplierFk', type: 'number', description: 'The supplier id to filter', http: {source: 'query'} - }, { + }, + { arg: 'invoiceInFk', type: 'number', description: 'The invoiceIn id to filter', http: {source: 'query'} - }, { + }, + { arg: 'currencyFk', type: 'number', description: 'The currency id to filter', http: {source: 'query'} - }, { + }, + { arg: 'from', type: 'date', description: `The from date filter` - }, { + }, + { arg: 'to', type: 'date', description: `The to date filter` @@ -93,9 +107,14 @@ module.exports = Self => { } }); - Self.filter = async(ctx, filter) => { - let conn = Self.dataSource.connector; - let where = buildFilter(ctx.args, (param, value) => { + Self.filter = async(ctx, filter, options) => { + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const conn = Self.dataSource.connector; + const where = buildFilter(ctx.args, (param, value) => { switch (param) { case 'search': return /^\d+$/.test(value) @@ -128,7 +147,7 @@ module.exports = Self => { }); filter = mergeFilters(ctx.args.filter, {where}); - let stmts = []; + const stmts = []; let stmt; stmt = new ParameterizedSQL( `SELECT @@ -163,10 +182,10 @@ module.exports = Self => { ); stmt.merge(conn.makeSuffix(filter)); - let itemsIndex = stmts.push(stmt) - 1; + const itemsIndex = stmts.push(stmt) - 1; - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await conn.executeStmt(sql); + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); return itemsIndex === 0 ? result : result[itemsIndex]; }; }; diff --git a/modules/entry/back/methods/entry/getBuys.js b/modules/entry/back/methods/entry/getBuys.js index 1b05ec483..0f09f06d5 100644 --- a/modules/entry/back/methods/entry/getBuys.js +++ b/modules/entry/back/methods/entry/getBuys.js @@ -1,14 +1,22 @@ +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + module.exports = Self => { Self.remoteMethod('getBuys', { description: 'Returns buys for one entry', accessType: 'READ', - accepts: { + accepts: [{ arg: 'id', type: 'number', required: true, description: 'The entry id', http: {source: 'path'} }, + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + } + ], returns: { type: ['Object'], root: true @@ -19,8 +27,14 @@ module.exports = Self => { } }); - Self.getBuys = async id => { - let filter = { + Self.getBuys = async(id, filter, options) => { + const models = Self.app.models; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + let defaultFilter = { where: {entryFk: id}, fields: [ 'id', @@ -68,7 +82,8 @@ module.exports = Self => { } }; - let buys = await Self.app.models.Buy.find(filter); - return buys; + defaultFilter = mergeFilters(defaultFilter, filter); + + return models.Buy.find(defaultFilter, myOptions); }; }; diff --git a/modules/entry/back/methods/entry/getEntry.js b/modules/entry/back/methods/entry/getEntry.js index 008ee8148..c14ca800a 100644 --- a/modules/entry/back/methods/entry/getEntry.js +++ b/modules/entry/back/methods/entry/getEntry.js @@ -19,8 +19,14 @@ module.exports = Self => { } }); - Self.getEntry = async id => { - let filter = { + Self.getEntry = async(id, options) => { + const models = Self.app.models; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const filter = { where: {id: id}, include: [ { @@ -70,7 +76,6 @@ module.exports = Self => { ], }; - let entry = await Self.app.models.Entry.findOne(filter); - return entry; + return models.Entry.findOne(filter, myOptions); }; }; diff --git a/modules/entry/back/methods/entry/importBuys.js b/modules/entry/back/methods/entry/importBuys.js index 10871f4ad..325fe4d22 100644 --- a/modules/entry/back/methods/entry/importBuys.js +++ b/modules/entry/back/methods/entry/importBuys.js @@ -45,8 +45,8 @@ module.exports = Self => { const conn = Self.dataSource.connector; const args = ctx.args; const models = Self.app.models; - let tx; + if (!options.transaction) { tx = await Self.beginTransaction({}); options.transaction = tx; @@ -76,7 +76,7 @@ module.exports = Self => { const createdBuys = await models.Buy.create(buys, options); const buyIds = createdBuys.map(buy => buy.id); - let stmts = []; + const stmts = []; let stmt; stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc'); diff --git a/modules/entry/back/methods/entry/importBuysPreview.js b/modules/entry/back/methods/entry/importBuysPreview.js index 9d6662327..9ba2b58ed 100644 --- a/modules/entry/back/methods/entry/importBuysPreview.js +++ b/modules/entry/back/methods/entry/importBuysPreview.js @@ -24,14 +24,19 @@ module.exports = Self => { } }); - Self.importBuysPreview = async(id, buys) => { + Self.importBuysPreview = async(id, buys, options) => { const models = Self.app.models; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + for (let buy of buys) { const packaging = await models.Packaging.findOne({ fields: ['id'], where: {volume: {gte: buy.volume}}, order: 'volume ASC' - }); + }, myOptions); buy.packageFk = packaging.id; } diff --git a/modules/entry/back/methods/entry/latestBuysFilter.js b/modules/entry/back/methods/entry/latestBuysFilter.js index 4c3536b8d..68abc9c03 100644 --- a/modules/entry/back/methods/entry/latestBuysFilter.js +++ b/modules/entry/back/methods/entry/latestBuysFilter.js @@ -17,36 +17,44 @@ module.exports = Self => { arg: 'search', type: 'String', description: `If it's and integer searchs by id, otherwise it searchs by name`, - }, { + }, + { arg: 'id', type: 'Integer', description: 'Item id', - }, { + }, + { arg: 'tags', type: ['Object'], description: 'List of tags to filter with', http: {source: 'query'} - }, { + }, + { arg: 'description', type: 'String', description: 'The item description', - }, { + }, + { arg: 'salesPersonFk', type: 'Integer', description: 'The buyer of the item', - }, { + }, + { arg: 'active', type: 'Boolean', description: 'Whether the item is or not active', - }, { + }, + { arg: 'visible', type: 'Boolean', description: 'Whether the item is or not visible', - }, { + }, + { arg: 'typeFk', type: 'Integer', description: 'Type id', - }, { + }, + { arg: 'categoryFk', type: 'Integer', description: 'Category id', @@ -62,9 +70,14 @@ module.exports = Self => { } }); - Self.latestBuysFilter = async(ctx, filter) => { - let conn = Self.dataSource.connector; - let where = buildFilter(ctx.args, (param, value) => { + Self.latestBuysFilter = async(ctx, filter, options) => { + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const conn = Self.dataSource.connector; + const where = buildFilter(ctx.args, (param, value) => { switch (param) { case 'search': return /^\d+$/.test(value) @@ -93,7 +106,7 @@ module.exports = Self => { }); filter = mergeFilters(ctx.args.filter, {where}); - let stmts = []; + const stmts = []; let stmt; stmts.push('CALL cache.last_buy_refresh(FALSE)'); @@ -179,10 +192,10 @@ module.exports = Self => { } stmt.merge(conn.makeSuffix(filter)); - let buysIndex = stmts.push(stmt) - 1; + const buysIndex = stmts.push(stmt) - 1; - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await conn.executeStmt(sql); + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); return buysIndex === 0 ? result : result[buysIndex]; }; }; diff --git a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js index 5d1bd5a0d..40be5f70e 100644 --- a/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js +++ b/modules/entry/back/methods/entry/specs/editLatestBuys.spec.js @@ -3,29 +3,32 @@ const model = app.models.Buy; describe('Buy editLatestsBuys()', () => { it('should change the value of a given column for the selected buys', async() => { - let ctx = { - args: { - search: 'Ranged weapon longbow 2m' - } - }; + const tx = await app.models.Entry.beginTransaction({}); + const options = {transaction: tx}; - let [original] = await model.latestBuysFilter(ctx); + try { + let ctx = { + args: { + search: 'Ranged weapon longbow 2m' + } + }; - const field = 'size'; - let newValue = 99; - const lines = [{itemFk: original.itemFk, id: original.id}]; + const [original] = await model.latestBuysFilter(ctx, null, options); - await model.editLatestBuys(field, newValue, lines); + const field = 'size'; + const newValue = 99; + const lines = [{itemFk: original.itemFk, id: original.id}]; - let [result] = await model.latestBuysFilter(ctx); + await model.editLatestBuys(field, newValue, lines, options); - expect(result.size).toEqual(99); + const [result] = await model.latestBuysFilter(ctx, null, options); - newValue = original.size; - await model.editLatestBuys(field, newValue, lines); + expect(result[field]).toEqual(newValue); - let [restoredFixture] = await model.latestBuysFilter(ctx); - - expect(restoredFixture.size).toEqual(original.size); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/entry/front/index/locale/es.yml b/modules/entry/front/index/locale/es.yml index 2770ccfe9..519f8e39a 100644 --- a/modules/entry/front/index/locale/es.yml +++ b/modules/entry/front/index/locale/es.yml @@ -10,7 +10,7 @@ Commission: Comisión Landed: F. entrega Reference: Referencia Created: Creado -Booked: Facturado +Booked: Contabilizada Is inventory: Inventario Notes: Notas Status: Estado diff --git a/modules/entry/front/summary/index.html b/modules/entry/front/summary/index.html index e4ad6ba31..3f91ddc26 100644 --- a/modules/entry/front/summary/index.html +++ b/modules/entry/front/summary/index.html @@ -1,3 +1,10 @@ + +
Packing price - + {{::line.quantity}} {{::line.stickers | dashIfEmpty}} @@ -156,6 +163,10 @@ + + diff --git a/modules/entry/front/summary/index.js b/modules/entry/front/summary/index.js index 93c985a67..c949dba64 100644 --- a/modules/entry/front/summary/index.js +++ b/modules/entry/front/summary/index.js @@ -10,10 +10,8 @@ class Controller extends Summary { set entry(value) { this._entry = value; - if (value && value.id) { + if (value && value.id) this.getEntryData(); - this.getBuys(); - } } getEntryData() { @@ -21,12 +19,6 @@ class Controller extends Summary { this.entryData = response.data; }); } - - getBuys() { - return this.$http.get(`Entries/${this.entry.id}/getBuys`).then(response => { - this.buys = response.data; - }); - } } ngModule.vnComponent('vnEntrySummary', { diff --git a/modules/entry/front/summary/index.spec.js b/modules/entry/front/summary/index.spec.js index 396e92b01..baeb43ac8 100644 --- a/modules/entry/front/summary/index.spec.js +++ b/modules/entry/front/summary/index.spec.js @@ -46,20 +46,4 @@ describe('component vnEntrySummary', () => { expect(controller.entryData).toEqual('I am the entryData'); }); }); - - describe('getBuys()', () => { - it('should perform a get asking for the buys of an entry', () => { - controller._entry = {id: 999}; - - const thatQuery = `Entries/${controller._entry.id}/getEntry`; - const query = `Entries/${controller._entry.id}/getBuys`; - - $httpBackend.whenGET(thatQuery).respond('My Entries'); - $httpBackend.expectGET(query).respond('Some buys'); - controller.getBuys(); - $httpBackend.flush(); - - expect(controller.buys).toEqual('Some buys'); - }); - }); }); diff --git a/modules/invoiceIn/back/methods/invoice-in/filter.js b/modules/invoiceIn/back/methods/invoice-in/filter.js index 0b9825752..b480df64a 100644 --- a/modules/invoiceIn/back/methods/invoice-in/filter.js +++ b/modules/invoiceIn/back/methods/invoice-in/filter.js @@ -139,6 +139,7 @@ module.exports = Self => { ii.supplierRef, ii.docFk AS dmsFk, ii.supplierFk, + ii.expenceFkDeductible deductibleExpenseFk, s.name AS supplierName, s.account, SUM(iid.amount) AS amount, diff --git a/modules/invoiceIn/back/methods/invoice-in/summary.js b/modules/invoiceIn/back/methods/invoice-in/summary.js index 1a22ea043..b99102ba1 100644 --- a/modules/invoiceIn/back/methods/invoice-in/summary.js +++ b/modules/invoiceIn/back/methods/invoice-in/summary.js @@ -57,6 +57,19 @@ module.exports = Self => { } }] } + }, + { + relation: 'expenseDeductible', + scope: { + fields: ['id', 'name', 'taxTypeFk'] + } + }, + { + relation: 'currency', + scope: { + fields: ['id', 'name'] + + } } ] }; diff --git a/modules/invoiceIn/back/models/invoice-in.json b/modules/invoiceIn/back/models/invoice-in.json index 897e156d8..6e6cea4e8 100644 --- a/modules/invoiceIn/back/models/invoice-in.json +++ b/modules/invoiceIn/back/models/invoice-in.json @@ -33,9 +33,6 @@ "isBooked": { "type": "boolean" }, - "isVatDeductible": { - "type": "boolean" - }, "booked": { "type": "date" }, @@ -50,6 +47,12 @@ "mysql": { "columnName": "docFk" } + }, + "deductibleExpenseFk": { + "type": "number", + "mysql": { + "columnName": "expenceFkDeductible" + } } }, "relations": { @@ -68,6 +71,11 @@ "model": "SageWithholding", "foreignKey": "withholdingSageFk" }, + "expenseDeductible": { + "type": "belongsTo", + "model": "Expense", + "foreignKey": "deductibleExpenseFk" + }, "company": { "type": "belongsTo", "model": "Company", diff --git a/modules/invoiceIn/front/basic-data/index.html b/modules/invoiceIn/front/basic-data/index.html index 15052c5fe..037b0657a 100644 --- a/modules/invoiceIn/front/basic-data/index.html +++ b/modules/invoiceIn/front/basic-data/index.html @@ -7,21 +7,6 @@
- - - - - - + + + + + + + + + + {{id}} - {{name}} + + + + + @@ -34,11 +36,11 @@ + + - - diff --git a/modules/invoiceIn/front/summary/locale/es.yml b/modules/invoiceIn/front/summary/locale/es.yml index 29bed3f9c..6f7cefddb 100644 --- a/modules/invoiceIn/front/summary/locale/es.yml +++ b/modules/invoiceIn/front/summary/locale/es.yml @@ -7,4 +7,4 @@ Booked date: Fecha contable Accounted date: Fecha contable Doc number: Numero documento Sage withholding: Retención sage -Deductible: Deducible \ No newline at end of file +Undeductible VAT: Iva no deducible \ No newline at end of file diff --git a/modules/invoiceOut/back/methods/invoiceOut/getTickets.js b/modules/invoiceOut/back/methods/invoiceOut/getTickets.js new file mode 100644 index 000000000..dc3296aba --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/getTickets.js @@ -0,0 +1,55 @@ +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethod('getTickets', { + description: 'Returns tickets for one invoiceOut', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The invoiceOut id', + http: {source: 'path'} + }, + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:id/getTickets`, + verb: 'GET' + } + }); + + Self.getTickets = async(id, filter, options) => { + const models = Self.app.models; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const invoiceOut = await models.InvoiceOut.findById(id, {fields: 'ref'}, myOptions); + + let defaultFilter = { + where: {refFk: invoiceOut.ref}, + fields: [ + 'id', + 'nickname', + 'shipped', + 'totalWithVat', + 'clientFk' + ] + + }; + + defaultFilter = mergeFilters(defaultFilter, filter); + + return models.Ticket.find(defaultFilter, myOptions); + }; +}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/getTickets.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/getTickets.spec.js new file mode 100644 index 000000000..0872ff85f --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/getTickets.spec.js @@ -0,0 +1,10 @@ +const app = require('vn-loopback/server/server'); + +describe('entry getTickets()', () => { + const invoiceOutId = 4; + it('should get the ticket of an invoiceOut', async() => { + const result = await app.models.InvoiceOut.getTickets(invoiceOutId); + + expect(result.length).toEqual(1); + }); +}); diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/summary.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/summary.spec.js index 1b1b84d15..115597717 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/summary.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/summary.spec.js @@ -7,14 +7,6 @@ describe('invoiceOut summary()', () => { expect(result.invoiceOut.id).toEqual(1); }); - it(`should return a summary object containing data from it's tickets`, async() => { - const summary = await app.models.InvoiceOut.summary(1); - const tickets = summary.invoiceOut.tickets(); - - expect(summary.invoiceOut.ref).toEqual('T1111111'); - expect(tickets.length).toEqual(2); - }); - it(`should return a summary object containing it's supplier country`, async() => { const summary = await app.models.InvoiceOut.summary(1); const supplier = summary.invoiceOut.supplier(); diff --git a/modules/invoiceOut/back/methods/invoiceOut/summary.js b/modules/invoiceOut/back/methods/invoiceOut/summary.js index caa2a1c06..0dd82fd0d 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/summary.js +++ b/modules/invoiceOut/back/methods/invoiceOut/summary.js @@ -1,5 +1,3 @@ -const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; - module.exports = Self => { Self.remoteMethod('summary', { description: 'The invoiceOut summary', @@ -22,7 +20,6 @@ module.exports = Self => { }); Self.summary = async id => { - const conn = Self.dataSource.connector; let summary = {}; const filter = { @@ -57,54 +54,20 @@ module.exports = Self => { scope: { fields: ['id', 'socialName'] } - }, - { - relation: 'tickets' } ] }; summary.invoiceOut = await Self.app.models.InvoiceOut.findOne(filter); - let stmts = []; - - stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticket'); - - stmt = new ParameterizedSQL(` - CREATE TEMPORARY TABLE tmp.ticket - (INDEX (ticketFk)) ENGINE = MEMORY - SELECT id ticketFk FROM vn.ticket WHERE refFk=?`, [summary.invoiceOut.ref]); - stmts.push(stmt); - - stmts.push('CALL ticketGetTotal()'); - - let ticketTotalsIndex = stmts.push('SELECT * FROM tmp.ticketTotal') - 1; - - stmt = new ParameterizedSQL(` + const invoiceOutTaxes = await Self.rawSql(` SELECT iot.* , pgc.*, IF(pe.equFk IS NULL, taxableBase, 0) AS Base, pgc.rate / 100 as vatPercent FROM vn.invoiceOutTax iot JOIN vn.pgc ON pgc.code = iot.pgcFk LEFT JOIN vn.pgcEqu pe ON pe.equFk = pgc.code WHERE invoiceOutFk = ?`, [summary.invoiceOut.id]); - let invoiceOutTaxesIndex = stmts.push(stmt) - 1; - stmts.push( - `DROP TEMPORARY TABLE - tmp.ticket, - tmp.ticketTotal`); - - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await conn.executeStmt(sql); - - totalMap = {}; - for (ticketTotal of result[ticketTotalsIndex]) - totalMap[ticketTotal.ticketFk] = ticketTotal.total; - - summary.invoiceOut.tickets().forEach(ticket => { - ticket.total = totalMap[ticket.id]; - }); - - summary.invoiceOut.taxesBreakdown = result[invoiceOutTaxesIndex]; + summary.invoiceOut.taxesBreakdown = invoiceOutTaxes; return summary; }; diff --git a/modules/invoiceOut/back/models/invoiceOut.js b/modules/invoiceOut/back/models/invoiceOut.js index e84a0495e..7c6503d8e 100644 --- a/modules/invoiceOut/back/models/invoiceOut.js +++ b/modules/invoiceOut/back/models/invoiceOut.js @@ -1,6 +1,7 @@ module.exports = Self => { require('../methods/invoiceOut/filter')(Self); require('../methods/invoiceOut/summary')(Self); + require('../methods/invoiceOut/getTickets')(Self); require('../methods/invoiceOut/download')(Self); require('../methods/invoiceOut/delete')(Self); require('../methods/invoiceOut/book')(Self); diff --git a/modules/invoiceOut/front/summary/index.html b/modules/invoiceOut/front/summary/index.html index 452ec23db..0dd9bfe0b 100644 --- a/modules/invoiceOut/front/summary/index.html +++ b/modules/invoiceOut/front/summary/index.html @@ -1,3 +1,10 @@ + +
- + {{ticket.shipped | date: 'dd/MM/yyyy' | dashIfEmpty}} - {{ticket.total | currency: 'EUR': 2}} + {{ticket.totalWithVat | currency: 'EUR': 2}} + + diff --git a/modules/item/back/models/expense.json b/modules/item/back/models/expense.json index c0ef2c487..65af02013 100644 --- a/modules/item/back/models/expense.json +++ b/modules/item/back/models/expense.json @@ -9,14 +9,17 @@ "properties": { "id": { "id": true, - "type": "Number", + "type": "number", "description": "Identifier" }, "name": { "type": "String" }, "isWithheld": { - "type": "Number" + "type": "number" + }, + "taxTypeFk": { + "type": "number" } }, "relations": { diff --git a/modules/route/back/methods/route/specs/updateVolume.spec.js b/modules/route/back/methods/route/specs/updateVolume.spec.js index e50a2ba00..fdcc1a6ec 100644 --- a/modules/route/back/methods/route/specs/updateVolume.spec.js +++ b/modules/route/back/methods/route/specs/updateVolume.spec.js @@ -3,6 +3,7 @@ const LoopBackContext = require('loopback-context'); describe('route updateVolume()', () => { const routeId = 1; + const ticketId = 14; const userId = 50; const activeCtx = { accessToken: {userId: userId}, @@ -13,31 +14,39 @@ describe('route updateVolume()', () => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); - const route = await app.models.Route.findById(routeId); - expect(route.m3).toEqual(1.8); + const tx = await app.models.Ticket.beginTransaction({}); - const ticket = await app.models.Ticket.findById(14); - await ticket.updateAttributes({routeFk: routeId}); - await app.models.Route.updateVolume(ctx, routeId); + try { + const options = {transaction: tx}; - const updatedRoute = await app.models.Route.findById(routeId); + const route = await app.models.Route.findById(routeId, null, options); - expect(updatedRoute.m3).not.toEqual(route.m3); + expect(route.m3).toEqual(1.8); - const logs = await app.models.RouteLog.find({fields: ['id', 'newInstance']}); + const ticket = await app.models.Ticket.findById(ticketId, null, options); + await ticket.updateAttributes({routeFk: routeId}, options); + await app.models.Route.updateVolume(ctx, routeId, options); - const m3Log = logs.filter(log => { - if (log.newInstance) - return log.newInstance.m3 === updatedRoute.m3; - }); - const logIdToDestroy = m3Log[0].id; + const updatedRoute = await app.models.Route.findById(routeId, null, options); - expect(m3Log.length).toEqual(1); + expect(updatedRoute.m3).not.toEqual(route.m3); - // restores - await ticket.updateAttributes({routeFk: null}); - await route.updateAttributes({m3: 1.8}); - await app.models.RouteLog.destroyById(logIdToDestroy); + const logs = await app.models.RouteLog.find({ + fields: ['id', 'newInstance'] + }, options); + + const m3Log = logs.filter(log => { + if (log.newInstance) + return log.newInstance.m3 === updatedRoute.m3; + }); + + expect(m3Log.length).toEqual(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/route/back/methods/route/updateVolume.js b/modules/route/back/methods/route/updateVolume.js index 27e96ebab..ce6e16b5c 100644 --- a/modules/route/back/methods/route/updateVolume.js +++ b/modules/route/back/methods/route/updateVolume.js @@ -19,24 +19,44 @@ module.exports = Self => { } }); - Self.updateVolume = async(ctx, id) => { - let query = `CALL vn.routeUpdateM3(?)`; - let userId = ctx.req.accessToken.userId; - let originalRoute = await Self.app.models.Route.findById(id); + Self.updateVolume = async(ctx, id, options) => { + const userId = ctx.req.accessToken.userId; + const models = Self.app.models; - await Self.rawSql(query, [id]); - let updatedRoute = await Self.app.models.Route.findById(id); + let tx; + let myOptions = {}; - let logRecord = { - originFk: id, - userFk: userId, - action: 'update', - changedModel: 'Route', - changedModelId: id, - oldInstance: {m3: originalRoute.m3}, - newInstance: {m3: updatedRoute.m3} - }; + if (typeof options == 'object') + Object.assign(myOptions, options); - return await Self.app.models.RouteLog.create(logRecord); + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const originalRoute = await models.Route.findById(id, null, myOptions); + + await Self.rawSql(`CALL vn.routeUpdateM3(?)`, [id], myOptions); + + const updatedRoute = await models.Route.findById(id, null, myOptions); + + await models.RouteLog.create({ + originFk: id, + userFk: userId, + action: 'update', + changedModel: 'Route', + changedModelId: id, + oldInstance: {m3: originalRoute.m3}, + newInstance: {m3: updatedRoute.m3} + }, myOptions); + + if (tx) await tx.commit(); + + return updatedRoute; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; }; diff --git a/modules/route/front/tickets/index.html b/modules/route/front/tickets/index.html index 012c45fe2..19450b4ac 100644 --- a/modules/route/front/tickets/index.html +++ b/modules/route/front/tickets/index.html @@ -149,11 +149,13 @@ ng-model="ticket.checked"> - {{::ticket.id}} - + + {{::ticket.id}} + + + + {{::ticket.nickname}} @@ -180,3 +182,9 @@ vn-bind="+" fixed-bottom-right> + + + + diff --git a/modules/ticket/back/methods/expedition/filter.js b/modules/ticket/back/methods/expedition/filter.js index 79c7131ea..96884d3c3 100644 --- a/modules/ticket/back/methods/expedition/filter.js +++ b/modules/ticket/back/methods/expedition/filter.js @@ -39,14 +39,19 @@ module.exports = Self => { e.externalId, i3.name packagingName, i3.id packagingItemFk, - e.packagingFk + e.packagingFk, + es.workerFk expeditionScanWorkerFk, + su.nickname scannerUserNickname, + es.scanned FROM vn.expedition e - LEFT JOIN vn.item i2 ON i2.id = e.itemFk + LEFT JOIN vn.item i2 ON i2.id = e.itemFk INNER JOIN vn.item i1 ON i1.id = e.isBox LEFT JOIN vn.packaging p ON p.id = e.packagingFk LEFT JOIN vn.item i3 ON i3.id = p.itemFk LEFT JOIN account.user u ON u.id = e.workerFk + LEFT JOIN vn.expeditionScan es ON es.expeditionFk = e.id + LEFT JOIN account.user su ON su.id = es.workerFk `); stmt.merge(Self.buildSuffix(filter, 'e')); diff --git a/modules/ticket/back/methods/ticket/filter.js b/modules/ticket/back/methods/ticket/filter.js index 5d7c014f7..31834112e 100644 --- a/modules/ticket/back/methods/ticket/filter.js +++ b/modules/ticket/back/methods/ticket/filter.js @@ -12,79 +12,98 @@ module.exports = Self => { arg: 'ctx', type: 'object', http: {source: 'context'} - }, { + }, + { arg: 'filter', type: 'object', description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string` - }, { + }, + { arg: 'search', type: 'string', description: `If it's and number searchs by id, otherwise it searchs by nickname` - }, { + }, + { arg: 'from', type: 'date', description: `The from date filter` - }, { + }, + { arg: 'to', type: 'date', description: `The to date filter` - }, { + }, + { arg: 'nickname', type: 'string', description: `The nickname filter` - }, { + }, + { arg: 'id', type: 'number', description: `The ticket id filter` - }, { + }, + { arg: 'clientFk', type: 'number', description: `The client id filter` - }, { + }, + { arg: 'agencyModeFk', type: 'number', description: `The agency mode id filter` - }, { + }, + { arg: 'warehouseFk', type: 'number', description: `The warehouse id filter` - }, { + }, + { arg: 'salesPersonFk', type: 'number', description: `The salesperson id filter` - }, { + }, + { arg: 'provinceFk', type: 'number', description: `The province id filter` - }, { + }, + { arg: 'stateFk', type: 'number', description: `The state id filter` - }, { + }, + { arg: 'myTeam', type: 'boolean', description: `Whether to show only tickets for the current logged user team (For now it shows only the current user tickets)` - }, { + }, + { arg: 'problems', type: 'boolean', description: `Whether to show only tickets with problems` - }, { + }, + { arg: 'pending', type: 'boolean', description: `Whether to show only tickets with state 'Pending'` - }, { + }, + { arg: 'mine', type: 'boolean', description: `Whether to show only tickets for the current logged user` - }, { + }, + { arg: 'orderFk', type: 'number', description: `The order id filter` - }, { + }, + { arg: 'refFk', type: 'string', description: `The invoice reference filter` - }, { + }, + { arg: 'alertLevel', type: 'number', description: `The alert level of the tickets` diff --git a/modules/ticket/back/methods/ticket/summary.js b/modules/ticket/back/methods/ticket/summary.js index 760a71e2b..037aee8b1 100644 --- a/modules/ticket/back/methods/ticket/summary.js +++ b/modules/ticket/back/methods/ticket/summary.js @@ -63,7 +63,7 @@ module.exports = Self => { }, { relation: 'address', scope: { - fields: ['street', 'city', 'provinceFk', 'phone', 'mobile'], + fields: ['street', 'city', 'provinceFk', 'phone', 'mobile', 'postalCode'], include: { relation: 'province', scope: { diff --git a/modules/ticket/front/basic-data/step-one/locale/es.yml b/modules/ticket/front/basic-data/step-one/locale/es.yml index ab804aea5..116392b89 100644 --- a/modules/ticket/front/basic-data/step-one/locale/es.yml +++ b/modules/ticket/front/basic-data/step-one/locale/es.yml @@ -2,4 +2,5 @@ No delivery zone available for this landing date: No hay una zona de reparto dis No delivery zone available for this shipping date: No hay una zona de reparto disponible para la fecha de preparación seleccionada No delivery zone available for this parameters: No hay una zona de reparto disponible con estos parámetros Deleted: Eliminado -Zone: Zona \ No newline at end of file +Zone: Zona +Edit address: Editar dirección \ No newline at end of file diff --git a/modules/ticket/front/create/card.js b/modules/ticket/front/create/card.js index 188d12d4d..94efe5f12 100644 --- a/modules/ticket/front/create/card.js +++ b/modules/ticket/front/create/card.js @@ -101,7 +101,7 @@ class Controller extends Component { this.$http.get(`Agencies/getAgenciesWithWarehouse`, {params}).then(res => { this.agencies = res.data; - const defaultAgency = this.agencies.find(agency=> { + const defaultAgency = this.agencies.find(agency => { return agency.agencyModeFk == this.defaultAddress.agencyModeFk; }); if (defaultAgency) diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index e4d42d62e..64f8b0958 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -19,8 +19,10 @@ Package type Counter externalId - Worker + Packager Created + Scanned + Palletizer @@ -51,6 +53,12 @@ {{::expedition.created | date:'dd/MM/yyyy HH:mm'}} + {{::expedition.scanned | date:'dd/MM/yyyy HH:mm'}} + + + {{::expedition.scannerUserNickname | dashIfEmpty}} + + diff --git a/modules/ticket/front/index/index.html b/modules/ticket/front/index/index.html index 4865c8c62..acef6cd23 100644 --- a/modules/ticket/front/index/index.html +++ b/modules/ticket/front/index/index.html @@ -166,7 +166,7 @@ vn-tooltip="Payment on account..." tooltip-position="left"> - + { }); describe('formattedAddress()', () => { - it('should return a full fromatted address with city and province', () => { + it('should return the full fromatted address with city and province', () => { controller.summary = { address: { province: { name: 'Gotham' }, street: '1007 Mountain Drive', + postalCode: 46060, city: 'Gotham' } }; - expect(controller.formattedAddress).toEqual('1007 Mountain Drive - Gotham (Gotham)'); + expect(controller.formattedAddress).toEqual('1007 Mountain Drive - 46060 - Gotham (Gotham)'); }); }); }); diff --git a/modules/travel/back/models/currency.json b/modules/travel/back/models/currency.json index 9ee7640b2..f3241fad1 100644 --- a/modules/travel/back/models/currency.json +++ b/modules/travel/back/models/currency.json @@ -8,18 +8,18 @@ }, "properties": { "id": { - "type": "Number", + "type": "number", "id": true, "description": "Identifier" }, "code": { - "type": "String" + "type": "string" }, "name": { - "type": "String" + "type": "string" }, "ratio": { - "type": "Number" + "type": "number" } }, "acls": [ diff --git a/modules/worker/back/methods/calendar/absences.js b/modules/worker/back/methods/calendar/absences.js index b84b55843..96293c931 100644 --- a/modules/worker/back/methods/calendar/absences.js +++ b/modules/worker/back/methods/calendar/absences.js @@ -2,32 +2,24 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('absences', { - description: 'Returns an array of absences from an specified worker', + description: 'Returns an array of absences from an specified contract', accepts: [{ - arg: 'workerFk', - type: 'Number', + arg: 'businessFk', + type: 'number', required: true, }, { - arg: 'started', - type: 'Date', - required: true, - }, - { - arg: 'ended', - type: 'Date', + arg: 'year', + type: 'date', required: true, }], returns: [{ - arg: 'calendar' - }, - { arg: 'absences', - type: 'Number' + type: 'number' }, { arg: 'holidays', - type: 'Number' + type: 'number' }], http: { path: `/absences`, @@ -35,25 +27,42 @@ module.exports = Self => { } }); - Self.absences = async(ctx, workerFk, yearStarted, yearEnded) => { + Self.absences = async(ctx, businessFk, year, options) => { const models = Self.app.models; - const isSubordinate = await models.Worker.isSubordinate(ctx, workerFk); - if (!isSubordinate) - throw new UserError(`You don't have enough privileges`); + const started = new Date(); + started.setFullYear(year); + started.setMonth(0); + started.setDate(1); - const calendar = {totalHolidays: 0, holidaysEnjoyed: 0}; - const holidays = []; + const ended = new Date(); + ended.setFullYear(year); + ended.setMonth(12); + ended.setDate(0); - // Get active contracts on current year - const year = yearStarted.getFullYear(); - const contracts = await models.WorkerLabour.find({ + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const contract = await models.WorkerLabour.findOne({ include: [{ relation: 'holidays', scope: { where: {year} } }, + { + relation: 'absences', + scope: { + include: { + relation: 'absenceType', + }, + where: { + dated: {between: [started, ended]} + } + } + }, { relation: 'workCenter', scope: { @@ -67,104 +76,39 @@ module.exports = Self => { relation: 'type' }], where: { - dated: {between: [yearStarted, yearEnded]} + dated: {between: [started, ended]} } } } } }], - where: { - and: [ - {workerFk: workerFk}, - {or: [{ - ended: {gte: [yearStarted]} - }, {ended: null}]} - ], + where: {businessFk} + }, myOptions); - } - }); + if (!contract) return; - // Contracts ids - const contractsId = contracts.map(contract => { - return contract.businessFk; - }); - - // Get absences of year - let absences = await Self.find({ - include: { - relation: 'absenceType' - }, - where: { - businessFk: {inq: contractsId}, - dated: {between: [yearStarted, yearEnded]} - } - }); - - let entitlementRate = 0; - absences.forEach(absence => { - const absenceType = absence.absenceType(); - const isHoliday = absenceType.code === 'holiday'; - const isHalfHoliday = absenceType.code === 'halfHoliday'; - - if (isHoliday) - calendar.holidaysEnjoyed += 1; - if (isHalfHoliday) - calendar.holidaysEnjoyed += 0.5; - - entitlementRate += absenceType.holidayEntitlementRate; + const isSubordinate = await models.Worker.isSubordinate(ctx, contract.workerFk, myOptions); + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + const absences = []; + for (let absence of contract.absences()) { absence.dated = new Date(absence.dated); absence.dated.setHours(0, 0, 0, 0); - }); - // Get number of worked days - let workedDays = 0; - contracts.forEach(contract => { - const started = contract.started; - const ended = contract.ended; - const startedTime = started.getTime(); - const endedTime = ended && ended.getTime() || yearEnded; - const dayTimestamp = 1000 * 60 * 60 * 24; - - workedDays += Math.floor((endedTime - startedTime) / dayTimestamp); - - if (workedDays > daysInYear()) - workedDays = daysInYear(); - - // Workcenter holidays - let holidayList = contract.workCenter().holidays(); - for (let day of holidayList) { - day.dated = new Date(day.dated); - day.dated.setHours(0, 0, 0, 0); - - holidays.push(day); - } - }); - const currentContract = contracts.find(contract => { - return contract.started <= new Date() - && (contract.ended >= new Date() || contract.ended == null); - }); - - if (currentContract) { - const maxHolidays = currentContract.holidays() && currentContract.holidays().days; - calendar.totalHolidays = maxHolidays; - - workedDays -= entitlementRate; - - if (workedDays < daysInYear()) - calendar.totalHolidays = Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2; + absences.push(absence); } - function daysInYear() { - const year = yearStarted.getFullYear(); + // Workcenter holidays + const holidays = []; + const holidayList = contract.workCenter().holidays(); + for (let day of holidayList) { + day.dated = new Date(day.dated); + day.dated.setHours(0, 0, 0, 0); - return isLeapYear(year) ? 366 : 365; + holidays.push(day); } - return [calendar, absences, holidays]; + return [absences, holidays]; }; - - function isLeapYear(year) { - return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0); - } }; diff --git a/modules/worker/back/methods/calendar/specs/absences.spec.js b/modules/worker/back/methods/calendar/specs/absences.spec.js index 42b97e2fc..1b95bfdb6 100644 --- a/modules/worker/back/methods/calendar/specs/absences.spec.js +++ b/modules/worker/back/methods/calendar/specs/absences.spec.js @@ -2,78 +2,56 @@ const app = require('vn-loopback/server/server'); describe('Worker absences()', () => { it('should get the absence calendar for a full year contract', async() => { - let ctx = {req: {accessToken: {userId: 106}}}; - let workerFk = 106; + const ctx = {req: {accessToken: {userId: 106}}}; + const businessId = 106; - const started = new Date(); - started.setHours(0, 0, 0, 0); - started.setMonth(0); - started.setDate(1); + const now = new Date(); + const year = now.getFullYear(); - const monthIndex = 11; - const ended = new Date(); - ended.setHours(0, 0, 0, 0); - ended.setMonth(monthIndex + 1); - ended.setDate(0); + const [absences] = await app.models.Calendar.absences(ctx, businessId, year); - let result = await app.models.Calendar.absences(ctx, workerFk, started, ended); - let calendar = result[0]; - let absences = result[1]; - - expect(calendar.totalHolidays).toEqual(27.5); - expect(calendar.holidaysEnjoyed).toEqual(5); - - let firstType = absences[0].absenceType().name; - let sixthType = absences[5].absenceType().name; + const firstType = absences[0].absenceType().name; + const sixthType = absences[5].absenceType().name; expect(firstType).toMatch(/(Holidays|Leave of absence)/); expect(sixthType).toMatch(/(Holidays|Leave of absence)/); }); it('should get the absence calendar for a permanent contract', async() => { - let workerFk = 106; - let worker = await app.models.WorkerLabour.findById(workerFk); - let endedDate = worker.ended; + const businessId = 106; + const ctx = {req: {accessToken: {userId: 9}}}; - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`, - [null, worker.businessFk] - ); + const now = new Date(); + const year = now.getFullYear(); - let ctx = {req: {accessToken: {userId: 9}}}; + const tx = await app.models.Calendar.beginTransaction({}); - const started = new Date(); - started.setHours(0, 0, 0, 0); - started.setMonth(0); - started.setDate(1); + try { + const options = {transaction: tx}; - const monthIndex = 11; - const ended = new Date(); - ended.setHours(0, 0, 0, 0); - ended.setMonth(monthIndex + 1); - ended.setDate(0); + const worker = await app.models.WorkerLabour.findById(businessId, null, options); - let result = await app.models.Calendar.absences(ctx, workerFk, started, ended); - let calendar = result[0]; - let absences = result[1]; + await app.models.WorkerLabour.rawSql( + `UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`, + [null, worker.businessFk], options); - expect(calendar.totalHolidays).toEqual(27.5); - expect(calendar.holidaysEnjoyed).toEqual(5); + const [absences] = await app.models.Calendar.absences(ctx, businessId, year, options); - let firstType = absences[0].absenceType().name; - let sixthType = absences[5].absenceType().name; + let firstType = absences[0].absenceType().name; + let sixthType = absences[5].absenceType().name; - expect(firstType).toMatch(/(Holidays|Leave of absence)/); - expect(sixthType).toMatch(/(Holidays|Leave of absence)/); - - // restores the contract end date - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_end = ? WHERE business_id = ?`, - [endedDate, worker.businessFk] - ); + expect(firstType).toMatch(/(Holidays|Leave of absence)/); + expect(sixthType).toMatch(/(Holidays|Leave of absence)/); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); it('should give the same holidays as worked days since the holidays amount matches the amount of days in a year', async() => { + const businessId = 106; + const userId = 106; const today = new Date(); // getting how many days in a year @@ -94,70 +72,47 @@ describe('Worker absences()', () => { const daysInYear = Math.round((endedTime - startedTime) / dayTimestamp); - // sets the holidays per year to the amount of days in the current year - let holidaysConfig = await app.models.WorkCenterHoliday.findOne({ - where: { - workCenterFk: 1, - year: today.getFullYear() - }}); + const tx = await app.models.Calendar.beginTransaction({}); + try { + const options = {transaction: tx}; - let originalHolidaysValue = holidaysConfig.days; + // sets the holidays per year to the amount of days in the current year + const holidaysConfig = await app.models.WorkCenterHoliday.findOne({ + where: { + workCenterFk: 1, + year: today.getFullYear() + } + }, options); - await holidaysConfig.updateAttribute('days', daysInYear); + await holidaysConfig.updateAttribute('days', daysInYear, options); - // normal test begins - const userId = 106; - const contract = await app.models.WorkerLabour.findById(userId); - const contractStartDate = contract.started; + // normal test begins + const contract = await app.models.WorkerLabour.findById(businessId, null, options); - const startingContract = new Date(); - startingContract.setHours(0, 0, 0, 0); - startingContract.setMonth(today.getMonth()); - startingContract.setDate(1); + const startingContract = new Date(); + startingContract.setHours(0, 0, 0, 0); + startingContract.setMonth(today.getMonth()); + startingContract.setDate(1); - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_start = ?, date_end = ? WHERE business_id = ?`, - [startingContract, yearEnd, contract.businessFk] - ); + await app.models.WorkerLabour.rawSql( + `UPDATE postgresql.business SET date_start = ?, date_end = ? WHERE business_id = ?`, + [startingContract, yearEnd, contract.businessFk], options + ); - let ctx = {req: {accessToken: {userId: userId}}}; + const ctx = {req: {accessToken: {userId: userId}}}; - let result = await app.models.Calendar.absences(ctx, userId, yearStart, yearEnd); - let calendar = result[0]; - let absences = result[1]; + const [absences] = await app.models.Calendar.absences(ctx, businessId, currentYear); - let remainingDays = 0; - for (let i = today.getMonth(); i < 12; i++) { - today.setDate(1); - today.setMonth(i + 1); - today.setDate(0); + const firstType = absences[0].absenceType().name; + const sixthType = absences[5].absenceType().name; - remainingDays += today.getDate(); + expect(firstType).toMatch(/(Holidays|Leave of absence)/); + expect(sixthType).toMatch(/(Holidays|Leave of absence)/); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; } - - expect(calendar.totalHolidays).toEqual(remainingDays); - expect(calendar.holidaysEnjoyed).toEqual(5); - - let firstType = absences[0].absenceType().name; - let sixthType = absences[5].absenceType().name; - - expect(firstType).toMatch(/(Holidays|Leave of absence)/); - expect(sixthType).toMatch(/(Holidays|Leave of absence)/); - - // resets the holidays per year with originalHolidaysValue and the contract starting date - await app.models.WorkCenterHoliday.updateAll( - { - workCenterFk: 1, - year: today.getFullYear() - }, - { - days: originalHolidaysValue - } - ); - - await app.models.WorkerLabour.rawSql( - `UPDATE postgresql.business SET date_start = ? WHERE business_id = ?`, - [contractStartDate, contract.businessFk] - ); }); }); diff --git a/modules/worker/back/methods/worker/activeContract.js b/modules/worker/back/methods/worker/activeContract.js new file mode 100644 index 000000000..05dcee6b5 --- /dev/null +++ b/modules/worker/back/methods/worker/activeContract.js @@ -0,0 +1,47 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('activeContract', { + description: 'Returns an array of contracts from an specified worker', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:id/activeContract`, + verb: 'GET' + } + }); + + Self.activeContract = async(ctx, id) => { + const models = Self.app.models; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + const now = new Date(); + + return models.WorkerLabour.findOne({ + where: { + and: [ + {workerFk: id}, + {started: {lte: now}}, + { + or: [ + {ended: {gte: now}}, + {ended: null} + ] + } + ] + } + }); + }; +}; diff --git a/modules/worker/back/methods/worker/contracts.js b/modules/worker/back/methods/worker/contracts.js new file mode 100644 index 000000000..4a38cac8f --- /dev/null +++ b/modules/worker/back/methods/worker/contracts.js @@ -0,0 +1,43 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('contracts', { + description: 'Returns an array of contracts from an specified worker', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'filter', + type: 'Object', + description: 'Filter defining where and paginated data', + required: true + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:id/contracts`, + verb: 'GET' + } + }); + + Self.contracts = async(ctx, id, filter) => { + const models = Self.app.models; + const isSubordinate = await models.Worker.isSubordinate(ctx, id); + + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + if (!filter.where) filter.where = {}; + + const where = filter.where; + where['workerFk'] = id; + + return models.WorkerLabour.find(filter); + }; +}; diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js index cc0ee13a6..da64c413f 100644 --- a/modules/worker/back/methods/worker/createAbsence.js +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -5,19 +5,24 @@ module.exports = Self => { description: 'Creates a new worker absence', accepts: [{ arg: 'id', - type: 'Number', + type: 'number', description: 'The worker id', http: {source: 'path'} }, + { + arg: 'businessFk', + type: 'number', + required: true + }, { arg: 'absenceTypeId', - type: 'Number', + type: 'number', required: true }, { arg: 'dated', - type: 'Date', - required: false + type: 'date', + required: true }], returns: { type: 'Object', @@ -29,55 +34,70 @@ module.exports = Self => { } }); - Self.createAbsence = async(ctx, id, absenceTypeId, dated) => { + Self.createAbsence = async(ctx, id, options) => { const models = Self.app.models; const $t = ctx.req.__; // $translate + const args = ctx.args; const userId = ctx.req.accessToken.userId; - const isSubordinate = await models.Worker.isSubordinate(ctx, id); - const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); - if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) - throw new UserError(`You don't have enough privileges`); + let tx; + let myOptions = {}; - const labour = await models.WorkerLabour.findOne({ - include: {relation: 'department'}, - where: { - and: [ - {workerFk: id}, - {or: [{ - ended: {gte: [dated]} - }, {ended: null}]} - ] - } - }); + if (typeof options == 'object') + Object.assign(myOptions, options); - const absence = await models.Calendar.create({ - businessFk: labour.businessFk, - dayOffTypeFk: absenceTypeId, - dated: dated - }); - - const department = labour.department(); - if (department && department.notificationEmail) { - const absenceType = await models.AbsenceType.findById(absenceTypeId); - const account = await models.Account.findById(userId); - const subordinated = await models.Account.findById(id); - const origin = ctx.req.headers.origin; - const body = $t('Created absence', { - author: account.nickname, - employee: subordinated.nickname, - absenceType: absenceType.name, - dated: formatDate(dated), - workerUrl: `${origin}/#!/worker/${id}/calendar` - }); - await models.Mail.create({ - subject: $t('Absence change notification on the labour calendar'), - body: body, - sender: department.notificationEmail - }); + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; } - return absence; + try { + const isSubordinate = await models.Worker.isSubordinate(ctx, id, myOptions); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss', myOptions); + + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) + throw new UserError(`You don't have enough privileges`); + + const labour = await models.WorkerLabour.findById(args.businessFk, { + include: {relation: 'department'} + }, myOptions); + + if (args.dated < labour.started || (labour.ended != null && args.dated > labour.ended)) + throw new UserError(`The contract was not active during the selected date`); + + const absence = await models.Calendar.create({ + businessFk: labour.businessFk, + dayOffTypeFk: args.absenceTypeId, + dated: args.dated + }, myOptions); + + const department = labour.department(); + if (department && department.notificationEmail) { + const absenceType = await models.AbsenceType.findById(args.absenceTypeId, null, myOptions); + const account = await models.Account.findById(userId, null, myOptions); + const subordinated = await models.Account.findById(id, null, myOptions); + const origin = ctx.req.headers.origin; + const body = $t('Created absence', { + author: account.nickname, + employee: subordinated.nickname, + absenceType: absenceType.name, + dated: formatDate(args.dated), + workerUrl: `${origin}/#!/worker/${id}/calendar` + }); + await models.Mail.create({ + subject: $t('Absence change notification on the labour calendar'), + body: body, + sender: department.notificationEmail + }, myOptions); + } + + if (tx) await tx.commit(); + + return absence; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; function formatDate(date) { diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js index c7e24fa58..e322288d2 100644 --- a/modules/worker/back/methods/worker/deleteAbsence.js +++ b/modules/worker/back/methods/worker/deleteAbsence.js @@ -5,13 +5,13 @@ module.exports = Self => { description: 'Deletes a worker absence', accepts: [{ arg: 'id', - type: 'Number', + type: 'number', description: 'The worker id', http: {source: 'path'} }, { arg: 'absenceId', - type: 'Number', + type: 'number', required: true }], returns: 'Object', @@ -21,47 +21,67 @@ module.exports = Self => { } }); - Self.deleteAbsence = async(ctx, id, absenceId) => { + Self.deleteAbsence = async(ctx, id, options) => { const models = Self.app.models; const $t = ctx.req.__; // $translate + const args = ctx.args; const userId = ctx.req.accessToken.userId; - const isSubordinate = await models.Worker.isSubordinate(ctx, id); - const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss'); - if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) - throw new UserError(`You don't have enough privileges`); + let tx; + let myOptions = {}; - const absence = await models.Calendar.findById(absenceId, { - include: { - relation: 'labour', - scope: { - include: {relation: 'department'} - } - } - }); - const result = await absence.destroy(); - const labour = absence.labour(); - const department = labour && labour.department(); - if (department && department.notificationEmail) { - const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk); - const account = await models.Account.findById(userId); - const subordinated = await models.Account.findById(labour.workerFk); - const origin = ctx.req.headers.origin; - const body = $t('Deleted absence', { - author: account.nickname, - employee: subordinated.nickname, - absenceType: absenceType.name, - dated: formatDate(absence.dated), - workerUrl: `${origin}/#!/worker/${id}/calendar` - }); - await models.Mail.create({ - subject: $t('Absence change notification on the labour calendar'), - body: body, - sender: department.notificationEmail - }); + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; } - return result; + try { + const isSubordinate = await models.Worker.isSubordinate(ctx, id, myOptions); + const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss', myOptions); + + if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss)) + throw new UserError(`You don't have enough privileges`); + + const absence = await models.Calendar.findById(args.absenceId, { + include: { + relation: 'labour', + scope: { + include: {relation: 'department'} + } + } + }, myOptions); + const result = await absence.destroy(myOptions); + const labour = absence.labour(); + const department = labour && labour.department(); + if (department && department.notificationEmail) { + const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk, null, myOptions); + const account = await models.Account.findById(userId, null, myOptions); + const subordinated = await models.Account.findById(labour.workerFk, null, myOptions); + const origin = ctx.req.headers.origin; + const body = $t('Deleted absence', { + author: account.nickname, + employee: subordinated.nickname, + absenceType: absenceType.name, + dated: formatDate(absence.dated), + workerUrl: `${origin}/#!/worker/${id}/calendar` + }); + await models.Mail.create({ + subject: $t('Absence change notification on the labour calendar'), + body: body, + sender: department.notificationEmail + }, myOptions); + } + + if (tx) await tx.commit(); + + return result; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } }; function formatDate(date) { diff --git a/modules/worker/back/methods/worker/getWorkedHours.js b/modules/worker/back/methods/worker/getWorkedHours.js index dfb219c72..9957fca25 100644 --- a/modules/worker/back/methods/worker/getWorkedHours.js +++ b/modules/worker/back/methods/worker/getWorkedHours.js @@ -34,35 +34,45 @@ module.exports = Self => { }); Self.getWorkedHours = async(id, started, ended) => { + const models = Self.app.models; const conn = Self.dataSource.connector; + + const worker = await models.Worker.findById(id); + const userId = worker.userFk; + const stmts = []; + const startedMinusOne = new Date(started); + startedMinusOne.setDate(started.getDate() - 1); + const endedPlusOne = new Date(ended); - let worker = await Self.app.models.Worker.findById(id); - let userId = worker.userFk; + endedPlusOne.setDate(ended.getDate() + 1); stmts.push(` DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate, tmp.timeBusinessCalculate `); - startedMinusOne.setDate(started.getDate() - 1); - endedPlusOne.setDate(ended.getDate() + 1); + stmts.push(new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [userId, startedMinusOne, endedPlusOne])); + stmts.push(new ParameterizedSQL('CALL vn.timeBusiness_calculateByUser(?, ?, ?)', [userId, startedMinusOne, endedPlusOne])); - let resultIndex = stmts.push(new ParameterizedSQL(` + + const resultIndex = stmts.push(new ParameterizedSQL(` SELECT tbc.dated, tbc.timeWorkSeconds expectedHours, tcc.timeWorkSeconds workedHours FROM tmp.timeBusinessCalculate tbc LEFT JOIN tmp.timeControlCalculate tcc ON tcc.dated = tbc.dated WHERE tbc.dated BETWEEN ? AND ? `, [started, ended])) - 1; + stmts.push(` DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate, tmp.timeBusinessCalculate `); - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await conn.executeStmt(sql); + + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql); return result[resultIndex]; }; diff --git a/modules/worker/back/methods/worker/holidays.js b/modules/worker/back/methods/worker/holidays.js new file mode 100644 index 000000000..f3ce0c661 --- /dev/null +++ b/modules/worker/back/methods/worker/holidays.js @@ -0,0 +1,176 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('holidays', { + description: 'Returns the holidays available whitin a contract or a year', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'year', + type: 'date', + required: true, + }, + { + arg: 'businessFk', + type: 'number', + required: false + }], + returns: [{ + type: 'object', + root: true + }], + http: { + path: `/:id/holidays`, + verb: 'GET' + } + }); + + Self.holidays = async(ctx, id, options) => { + const models = Self.app.models; + const args = ctx.args; + + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const isSubordinate = await models.Worker.isSubordinate(ctx, id, myOptions); + if (!isSubordinate) + throw new UserError(`You don't have enough privileges`); + + const started = new Date(); + started.setFullYear(args.year); + started.setMonth(0); + started.setDate(1); + started.setHours(0, 0, 0, 0); + + const ended = new Date(); + ended.setFullYear(args.year); + ended.setMonth(12); + ended.setDate(0); + ended.setHours(23, 59, 59, 59); + + const filter = { + include: [{ + relation: 'holidays', + scope: { + where: {year: args.year} + } + }, + { + relation: 'absences', + scope: { + include: { + relation: 'absenceType', + }, + where: { + dated: {between: [started, ended]} + } + } + }, + { + relation: 'workCenter', + scope: { + include: { + relation: 'holidays', + scope: { + include: [{ + relation: 'detail' + }, + { + relation: 'type' + }], + where: { + dated: {between: [started, ended]} + } + } + } + } + }], + where: { + and: [ + {workerFk: id}, + { + or: [ + {started: {between: [started, ended]}}, + {ended: {between: [started, ended]}}, + {and: [{started: {lt: started}}, {ended: {gt: ended}}]}, + {and: [{started: {lt: started}}, {ended: null}]} + ] + } + ], + + } + }; + if (args.businessFk) + filter.where.and.push({businessFk: args.businessFk}); + + const contracts = await models.WorkerLabour.find(filter, myOptions); + + let totalHolidays = 0; + let holidaysEnjoyed = 0; + + for (let contract of contracts) { + const contractStarted = contract.started; + contractStarted.setHours(0, 0, 0, 0); + const contractEnded = contract.ended; + if (contractEnded) + contractEnded.setHours(23, 59, 59, 59); + + let startedTime; + if (contractStarted < started) + startedTime = started.getTime(); + else startedTime = contractStarted.getTime(); + + let endedTime; + if (!contractEnded || (contractEnded && contractEnded > ended)) + endedTime = ended.getTime(); + else endedTime = contractEnded.getTime(); + + const dayTimestamp = 1000 * 60 * 60 * 24; + + // Get number of worked days between dates + let workedDays = Math.floor((endedTime - startedTime) / dayTimestamp); + workedDays += 1; // 1 day inclusion + + // Calculates absences + let entitlementRate = 0; + for (let absence of contract.absences()) { + const absenceType = absence.absenceType(); + const isHoliday = absenceType.code === 'holiday'; + const isHalfHoliday = absenceType.code === 'halfHoliday'; + + if (isHoliday) holidaysEnjoyed += 1; + if (isHalfHoliday) holidaysEnjoyed += 0.5; + + entitlementRate += absenceType.holidayEntitlementRate; + } + + workedDays -= entitlementRate; + + // Max holidays for the selected year + const maxHolidays = contract.holidays() && contract.holidays().days; + + if (workedDays < daysInYear()) + totalHolidays += Math.round(2 * maxHolidays * (workedDays) / daysInYear()) / 2; + else totalHolidays = maxHolidays; + } + + function daysInYear() { + const year = started.getFullYear(); + + return isLeapYear(year) ? 366 : 365; + } + + return {totalHolidays, holidaysEnjoyed}; + }; + + function isLeapYear(year) { + return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0); + } +}; diff --git a/modules/worker/back/methods/worker/isSubordinate.js b/modules/worker/back/methods/worker/isSubordinate.js index 18c7df17e..13cc365c6 100644 --- a/modules/worker/back/methods/worker/isSubordinate.js +++ b/modules/worker/back/methods/worker/isSubordinate.js @@ -23,16 +23,21 @@ module.exports = Self => { } }); - Self.isSubordinate = async(ctx, id) => { + Self.isSubordinate = async(ctx, id, options) => { const models = Self.app.models; const myUserId = ctx.req.accessToken.userId; - const mySubordinates = await Self.mySubordinates(ctx); + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const mySubordinates = await Self.mySubordinates(ctx, myOptions); const isSubordinate = mySubordinates.find(subordinate => { return subordinate.workerFk == id; }); - const isHr = await models.Account.hasRole(myUserId, 'hr'); + const isHr = await models.Account.hasRole(myUserId, 'hr', myOptions); if (isHr || isSubordinate) return true; diff --git a/modules/worker/back/methods/worker/mySubordinates.js b/modules/worker/back/methods/worker/mySubordinates.js index 956374964..07a22291d 100644 --- a/modules/worker/back/methods/worker/mySubordinates.js +++ b/modules/worker/back/methods/worker/mySubordinates.js @@ -20,17 +20,22 @@ module.exports = Self => { } }); - Self.mySubordinates = async ctx => { + Self.mySubordinates = async(ctx, options) => { const conn = Self.dataSource.connector; const userId = ctx.req.accessToken.userId; const stmts = []; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + stmts.push(new ParameterizedSQL('CALL vn.subordinateGetList(?)', [userId])); const queryIndex = stmts.push('SELECT * FROM tmp.subordinate') - 1; stmts.push('DROP TEMPORARY TABLE tmp.subordinate'); const sql = ParameterizedSQL.join(stmts, ';'); - const result = await conn.executeStmt(sql); + const result = await conn.executeStmt(sql, myOptions); return result[queryIndex]; }; diff --git a/modules/worker/back/methods/worker/specs/createAbsence.spec.js b/modules/worker/back/methods/worker/specs/createAbsence.spec.js index ace412890..f2c00e804 100644 --- a/modules/worker/back/methods/worker/specs/createAbsence.spec.js +++ b/modules/worker/back/methods/worker/specs/createAbsence.spec.js @@ -5,18 +5,34 @@ describe('Worker createAbsence()', () => { const workerId = 18; it('should return an error for a user without enough privileges', async() => { - const ctx = {req: {accessToken: {userId: 18}}}; - const absenceTypeId = 1; - const dated = new Date(); + const ctx = { + req: {accessToken: {userId: 18}}, + args: { + businessFk: 18, + absenceTypeId: 1, + dated: new Date() + } + }; - let error; - await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated).catch(e => { - error = e; - }).finally(() => { - expect(error.message).toEqual(`You don't have enough privileges`); - }); + const tx = await app.models.Calendar.beginTransaction({}); - expect(error).toBeDefined(); + try { + const options = {transaction: tx}; + + let error; + await app.models.Worker.createAbsence(ctx, workerId, options).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); it('should create a new absence', async() => { @@ -24,7 +40,14 @@ describe('Worker createAbsence()', () => { accessToken: {userId: 19}, headers: {origin: 'http://localhost'} }; - const ctx = {req: activeCtx}; + const ctx = { + req: activeCtx, + args: { + businessFk: 18, + absenceTypeId: 1, + dated: new Date() + } + }; ctx.req.__ = value => { return value; }; @@ -32,17 +55,23 @@ describe('Worker createAbsence()', () => { active: activeCtx }); - const absenceTypeId = 1; - const dated = new Date(); - const createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated); + const tx = await app.models.Calendar.beginTransaction({}); - const expectedBusinessId = 18; - const expectedAbsenceTypeId = 1; + try { + const options = {transaction: tx}; - expect(createdAbsence.businessFk).toEqual(expectedBusinessId); - expect(createdAbsence.dayOffTypeFk).toEqual(expectedAbsenceTypeId); + const createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, options); - // Restores - await app.models.Calendar.destroyById(createdAbsence.id); + const expectedBusinessId = 18; + const expectedAbsenceTypeId = 1; + + expect(createdAbsence.businessFk).toEqual(expectedBusinessId); + expect(createdAbsence.dayOffTypeFk).toEqual(expectedAbsenceTypeId); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js index 15bd854ed..16036eac6 100644 --- a/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js +++ b/modules/worker/back/methods/worker/specs/deleteAbsence.spec.js @@ -12,45 +12,68 @@ describe('Worker deleteAbsence()', () => { ctx.req.__ = value => { return value; }; - let createdAbsence; beforeEach(async() => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); - createdAbsence = await app.models.Calendar.create({ - businessFk: businessId, - dayOffTypeFk: 1, - dated: new Date() - }); - }); - - afterEach(async() => { - await app.models.Calendar.destroyById(createdAbsence.id); }); it('should return an error for a user without enough privileges', async() => { activeCtx.accessToken.userId = 106; + const tx = await app.models.Calendar.beginTransaction({}); - let error; - await app.models.Worker.deleteAbsence(ctx, 18, createdAbsence.id).catch(e => { - error = e; - }).finally(() => { - expect(error.message).toEqual(`You don't have enough privileges`); - }); + try { + const options = {transaction: tx}; + const createdAbsence = await app.models.Calendar.create({ + businessFk: businessId, + dayOffTypeFk: 1, + dated: new Date() + }, options); - expect(error).toBeDefined(); + ctx.args = {absenceId: createdAbsence.id}; + + let error; + await app.models.Worker.deleteAbsence(ctx, workerId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); - it('should create a new absence', async() => { + it('should successfully delete an absence', async() => { activeCtx.accessToken.userId = 19; - expect(createdAbsence.businessFk).toEqual(businessId); + const tx = await app.models.Calendar.beginTransaction({}); - await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id); + try { + const options = {transaction: tx}; + const createdAbsence = await app.models.Calendar.create({ + businessFk: businessId, + dayOffTypeFk: 1, + dated: new Date() + }, options); - const deletedAbsence = await app.models.Calendar.findById(createdAbsence.id); + ctx.args = {absenceId: createdAbsence.id}; - expect(deletedAbsence).toBeNull(); + await app.models.Worker.deleteAbsence(ctx, workerId, options); + + const deletedAbsence = await app.models.Calendar.findById(createdAbsence.id, null, options); + + expect(deletedAbsence).toBeNull(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } }); }); diff --git a/modules/worker/back/methods/worker/specs/holidays.spec.js b/modules/worker/back/methods/worker/specs/holidays.spec.js new file mode 100644 index 000000000..6f588147c --- /dev/null +++ b/modules/worker/back/methods/worker/specs/holidays.spec.js @@ -0,0 +1,30 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); + +describe('Worker holidays()', () => { + const businessId = 106; + const workerId = 106; + const activeCtx = { + accessToken: {userId: workerId}, + headers: {origin: 'http://localhost'} + }; + const ctx = {req: activeCtx}; + + beforeEach(async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should get the absence calendar for a full year contract', async() => { + const now = new Date(); + const year = now.getFullYear(); + + ctx.args = {businessFk: businessId, year: year}; + + const result = await app.models.Worker.holidays(ctx, workerId); + + expect(result.totalHolidays).toEqual(27.5); + expect(result.holidaysEnjoyed).toEqual(5); + }); +}); diff --git a/modules/worker/back/models/worker-labour.json b/modules/worker/back/models/worker-labour.json index eae28cf89..8ad7bf41e 100644 --- a/modules/worker/back/models/worker-labour.json +++ b/modules/worker/back/models/worker-labour.json @@ -12,10 +12,10 @@ "type": "Number" }, "started": { - "type": "Date" + "type": "date" }, "ended": { - "type": "Date" + "type": "date" } }, "relations": { @@ -38,6 +38,11 @@ "type": "belongsTo", "model": "WorkCenterHoliday", "foreignKey": "workCenterFk" + }, + "absences": { + "type": "hasMany", + "model": "Calendar", + "foreignKey": "businessFk" } } } diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 9bfef5235..ec6c4af28 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -10,4 +10,7 @@ module.exports = Self => { require('../methods/worker/active')(Self); require('../methods/worker/activeWithRole')(Self); require('../methods/worker/activeWithInheritedRole')(Self); + require('../methods/worker/contracts')(Self); + require('../methods/worker/holidays')(Self); + require('../methods/worker/activeContract')(Self); }; diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index f10201763..f012e8e55 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -22,13 +22,22 @@
-
-
Holidays
+
+
{{'Contract' | translate}} ID: {{$ctrl.businessId}}
- {{'Used' | translate}} {{$ctrl.calendar.holidaysEnjoyed}} - {{'of' | translate}} {{$ctrl.calendar.totalHolidays || 0}} {{'days' | translate}} + {{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed}} + {{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
+ +
+
{{'Year' | translate}} {{$ctrl.year}}
+
+ {{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed}} + {{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}} +
+
+
+ + +
ID: {{businessFk}}
+
+ {{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}} +
+
+
-
+
this.repaint()); + this.refresh() + .then(() => this.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()); + } + + get businessId() { + return this._businessId; + } + + set businessId(value) { + this._businessId = value; + if (value) { + this.refresh() + .then(() => this.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()); + } } get date() { @@ -31,16 +48,6 @@ class Controller extends Section { this._date = value; value.setHours(0, 0, 0, 0); - const started = new Date(value.getTime()); - started.setMonth(0); - started.setDate(1); - this.started = started; - - const ended = new Date(value.getTime()); - ended.setMonth(12); - ended.setDate(0); - this.ended = ended; - this.months = new Array(12); for (let i = 0; i < this.months.length; i++) { @@ -59,26 +66,51 @@ class Controller extends Section { this._worker = value; if (value) { - this.refresh().then(() => this.repaint()); this.getIsSubordinate(); + this.getActiveContract(); } } buildYearFilter() { - const currentYear = new Date().getFullYear(); - const minRange = currentYear - 5; + const now = new Date(); + now.setFullYear(now.getFullYear() + 1); + + const maxYear = now.getFullYear(); + const minRange = maxYear - 5; const years = []; - for (let i = currentYear; i > minRange; i--) + for (let i = maxYear; i > minRange; i--) years.push({year: i}); this.yearFilter = years; } getIsSubordinate() { - this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res => - this.isSubordinate = res.data - ); + this.$http.get(`Workers/${this.worker.id}/isSubordinate`) + .then(res => this.isSubordinate = res.data); + } + + getActiveContract() { + this.$http.get(`Workers/${this.worker.id}/activeContract`) + .then(res => this.businessId = res.data.businessFk); + } + + getContractHolidays() { + this.getHolidays({ + businessFk: this.businessId, + year: this.year + }, data => this.contractHolidays = data); + } + + getYearHolidays() { + this.getHolidays({ + year: this.year + }, data => this.yearHolidays = data); + } + + getHolidays(params, cb) { + this.$http.get(`Workers/${this.worker.id}/holidays`, {params}) + .then(res => cb(res.data)); } onData(data) { @@ -155,9 +187,6 @@ class Controller extends Section { if (!this.absenceType) return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu')); - if (this.year != new Date().getFullYear()) - return this.vnApp.showMessage(this.$t('You can just add absences within the current year')); - const day = $days[0]; const stamp = day.getTime(); const event = this.events[stamp]; @@ -176,7 +205,8 @@ class Controller extends Section { const absenceType = this.absenceType; const params = { dated: dated, - absenceTypeId: absenceType.id + absenceTypeId: absenceType.id, + businessFk: this.businessId }; const path = `Workers/${this.$params.id}/createAbsence`; @@ -190,7 +220,10 @@ class Controller extends Section { }; this.repaintCanceller(() => - this.refresh().then(calendar.repaint()) + this.refresh() + .then(calendar.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()) ); }); } @@ -208,7 +241,10 @@ class Controller extends Section { event.type = absenceType.code; this.repaintCanceller(() => - this.refresh().then(calendar.repaint()) + this.refresh() + .then(calendar.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()) ); }); } @@ -220,7 +256,10 @@ class Controller extends Section { delete this.events[day.getTime()]; this.repaintCanceller(() => - this.refresh().then(calendar.repaint()) + this.refresh() + .then(calendar.repaint()) + .then(() => this.getContractHolidays()) + .then(() => this.getYearHolidays()) ); }); } @@ -237,9 +276,8 @@ class Controller extends Section { refresh() { const params = { - workerFk: this.worker.id, - started: this.started, - ended: this.ended + businessFk: this.businessId, + year: this.year }; return this.$http.get(`Calendars/absences`, {params}) .then(res => this.onData(res.data)); diff --git a/modules/worker/front/calendar/index.spec.js b/modules/worker/front/calendar/index.spec.js index 14a5e2e0d..141bf6653 100644 --- a/modules/worker/front/calendar/index.spec.js +++ b/modules/worker/front/calendar/index.spec.js @@ -41,35 +41,31 @@ describe('Worker', () => { }); }); - describe('started property', () => { - it(`should return first day and month of current year`, () => { - let started = new Date(year, 0, 1); + describe('businessId() setter', () => { + it(`should set the contract id and then call to the refresh method`, () => { + jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve()); - expect(controller.started).toEqual(started); - }); - }); + controller.businessId = 106; - describe('ended property', () => { - it(`should return last day and month of current year`, () => { - let ended = new Date(year, 11, 31); - - expect(controller.ended).toEqual(ended); + expect(controller.refresh).toHaveBeenCalledWith(); }); }); describe('months property', () => { it(`should return an array of twelve months length`, () => { + const started = new Date(year, 0, 1); const ended = new Date(year, 11, 1); expect(controller.months.length).toEqual(12); - expect(controller.months[0]).toEqual(controller.started); + expect(controller.months[0]).toEqual(started); expect(controller.months[11]).toEqual(ended); }); }); describe('worker() setter', () => { it(`should perform a get query and set the reponse data on the model`, () => { - jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true); + controller.getIsSubordinate = jest.fn(); + controller.getActiveContract = jest.fn(); let today = new Date(); let tomorrow = new Date(today.getTime()); @@ -78,49 +74,60 @@ describe('Worker', () => { let yesterday = new Date(today.getTime()); yesterday.setDate(yesterday.getDate() - 1); - $httpBackend.whenRoute('GET', 'Calendars/absences') - .respond({ - holidays: [ - {dated: today, detail: {name: 'New year'}}, - {dated: tomorrow, detail: {name: 'Easter'}} - ], - absences: [ - {dated: today, absenceType: {name: 'Holiday', rgb: '#aaa'}}, - {dated: yesterday, absenceType: {name: 'Leave', rgb: '#bbb'}} - ] - }); - controller.worker = {id: 107}; + + expect(controller.getIsSubordinate).toHaveBeenCalledWith(); + expect(controller.getActiveContract).toHaveBeenCalledWith(); + }); + }); + + describe('getIsSubordinate()', () => { + it(`should return whether the worker is a subordinate`, () => { + $httpBackend.expect('GET', `Workers/106/isSubordinate`).respond(true); + controller.getIsSubordinate(); $httpBackend.flush(); - let events = controller.events; + expect(controller.isSubordinate).toBe(true); + }); + }); - expect(events[today.getTime()].name).toEqual('New year, Holiday'); - expect(events[tomorrow.getTime()].name).toEqual('Easter'); - expect(events[yesterday.getTime()].name).toEqual('Leave'); - expect(events[yesterday.getTime()].color).toEqual('#bbb'); - expect(controller.getIsSubordinate).toHaveBeenCalledWith(); + describe('getActiveContract()', () => { + it(`should return the current contract and then set the businessId property`, () => { + jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve()); + + $httpBackend.expect('GET', `Workers/106/activeContract`).respond({businessFk: 106}); + controller.getActiveContract(); + $httpBackend.flush(); + + expect(controller.businessId).toEqual(106); + }); + }); + + describe('getContractHolidays()', () => { + it(`should return the worker holidays amount and then set the contractHolidays property`, () => { + const today = new Date(); + const year = today.getFullYear(); + + const serializedParams = $httpParamSerializer({year}); + $httpBackend.expect('GET', `Workers/106/holidays?${serializedParams}`).respond({totalHolidays: 28}); + controller.getContractHolidays(); + $httpBackend.flush(); + + expect(controller.contractHolidays).toEqual({totalHolidays: 28}); }); }); describe('formatDay()', () => { it(`should set the day element style`, () => { - jest.spyOn(controller, 'getIsSubordinate').mockReturnThis(); + const today = new Date(); - let today = new Date(); + controller.events[today.getTime()] = { + name: 'Holiday', + color: '#000' + }; - $httpBackend.whenRoute('GET', 'Calendars/absences') - .respond({ - absences: [ - {dated: today, absenceType: {name: 'Holiday', rgb: '#000'}} - ] - }); - - controller.worker = {id: 1}; - $httpBackend.flush(); - - let dayElement = angular.element('
')[0]; - let dayNumber = dayElement.firstElementChild; + const dayElement = angular.element('
')[0]; + const dayNumber = dayElement.firstElementChild; controller.formatDay(today, dayElement); @@ -160,28 +167,6 @@ describe('Worker', () => { expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu'); }); - it(`should show an snackbar message if the selected day is not within the current year`, () => { - jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis(); - - const selectedDay = new Date(); - const $event = { - target: { - closest: () => { - return {$ctrl: {}}; - } - } - }; - const $days = [selectedDay]; - const pastYear = new Date(); - pastYear.setFullYear(pastYear.getFullYear() - 1); - - controller.date = pastYear; - controller.absenceType = {id: 1}; - controller.onSelection($event, $days); - - expect(controller.vnApp.showMessage).toHaveBeenCalledWith('You can just add absences within the current year'); - }); - it(`should call to the create() method`, () => { jest.spyOn(controller, 'create').mockReturnThis(); @@ -342,20 +327,8 @@ describe('Worker', () => { it(`should make a HTTP GET query and then call to the onData() method`, () => { jest.spyOn(controller, 'onData').mockReturnThis(); - const dated = controller.date; - const started = new Date(dated.getTime()); - started.setMonth(0); - started.setDate(1); - - const ended = new Date(dated.getTime()); - ended.setMonth(12); - ended.setDate(0); - - controller.started = started; - controller.ended = ended; - const expecteResponse = [{id: 1}]; - const expectedParams = {workerFk: 106, started: started, ended: ended}; + const expectedParams = {year: year}; const serializedParams = $httpParamSerializer(expectedParams); $httpBackend.expect('GET', `Calendars/absences?${serializedParams}`).respond(200, expecteResponse); controller.refresh(); diff --git a/modules/worker/front/calendar/locale/es.yml b/modules/worker/front/calendar/locale/es.yml index 0626b7550..1ff12358c 100644 --- a/modules/worker/front/calendar/locale/es.yml +++ b/modules/worker/front/calendar/locale/es.yml @@ -1,5 +1,5 @@ Calendar: Calendario -Holidays: Vacaciones +Contract: Contrato Festive: Festivo Used: Utilizados Year: Año diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index ab9bd0033..bed9e7af3 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -13,6 +13,19 @@ class Controller extends Section { this.date = new Date(); } + get worker() { + return this._worker; + } + + set worker(value) { + this._worker = value; + + if (value) { + this.getActiveContract() + .then(() => this.getAbsences()); + } + } + /** * The current selected date */ @@ -70,6 +83,11 @@ class Controller extends Section { } } + getActiveContract() { + return this.$http.get(`Workers/${this.worker.id}/activeContract`) + .then(res => this.businessId = res.data.businessFk); + } + fetchHours() { const params = {workerFk: this.$params.id}; const filter = { @@ -80,7 +98,6 @@ class Controller extends Section { }; this.$.model.applyFilter(filter, params).then(() => { this.getWorkedHours(this.started, this.ended); - this.getAbsences(); }); } @@ -89,10 +106,10 @@ class Controller extends Section { } getAbsences() { + const fullYear = this.started.getFullYear(); let params = { - workerFk: this.$params.id, - started: this.started, - ended: this.ended + businessFk: this.businessId, + year: fullYear }; return this.$http.get(`Calendars/absences`, {params}) @@ -257,5 +274,8 @@ Controller.$inject = ['$element', '$scope', 'vnWeekDays']; ngModule.vnComponent('vnWorkerTimeControl', { template: require('./index.html'), - controller: Controller + controller: Controller, + bindings: { + worker: '<' + } });