diff --git a/back/methods/account/acl.js b/back/methods/account/acl.js index c1dcdc11b..bc1990e1d 100644 --- a/back/methods/account/acl.js +++ b/back/methods/account/acl.js @@ -23,7 +23,13 @@ module.exports = Self => { let models = Self.app.models; let user = await models.Account.findById(userId, { - fields: ['id', 'name', 'nickname', 'email'] + fields: ['id', 'name', 'nickname', 'email', 'lang'], + include: { + relation: 'userConfig', + scope: { + fields: ['darkMode'] + } + } }); let roles = await models.RoleMapping.find({ diff --git a/back/methods/chat/sendCheckingPresence.js b/back/methods/chat/sendCheckingPresence.js index 5c27d72fd..6560240c6 100644 --- a/back/methods/chat/sendCheckingPresence.js +++ b/back/methods/chat/sendCheckingPresence.js @@ -46,7 +46,7 @@ module.exports = Self => { const {data} = await Self.getUserStatus(recipient.name); if (data) { - if (data.status === 'offline') { + if (data.status === 'offline' || data.status === 'busy') { // Send message to department room const workerDepartment = await models.WorkerDepartment.findById(recipientId, { include: { @@ -58,6 +58,8 @@ module.exports = Self => { if (channelName) return Self.send(ctx, `#${channelName}`, `@${recipient.name} ➔ ${message}`); + else + return Self.send(ctx, `@${recipient.name}`, message); } else return Self.send(ctx, `@${recipient.name}`, message); } diff --git a/back/models/account.json b/back/models/account.json index b59cf39c2..9150bc1a7 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -71,6 +71,11 @@ "type": "hasOne", "model": "Worker", "foreignKey": "userFk" + }, + "userConfig": { + "type": "hasOne", + "model": "UserConfig", + "foreignKey": "userFk" } }, "acls": [ diff --git a/back/models/user-config.json b/back/models/user-config.json index 336f26f63..8e4684713 100644 --- a/back/models/user-config.json +++ b/back/models/user-config.json @@ -9,20 +9,23 @@ "properties": { "userFk": { "id": true, - "type": "Number", + "type": "number", "required": true }, "warehouseFk": { - "type": "Number" + "type": "number" }, "companyFk": { - "type": "Number" + "type": "number" }, "created": { - "type": "Date" + "type": "date" }, "updated": { - "type": "Date" + "type": "date" + }, + "darkMode": { + "type": "boolean" } }, "relations": { diff --git a/db/changes/10451-april/00-account_user.sql b/db/changes/10451-april/00-account_user.sql new file mode 100644 index 000000000..2988c310e --- /dev/null +++ b/db/changes/10451-april/00-account_user.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`userConfig` ADD darkMode tinyint(1) DEFAULT 1 NOT NULL COMMENT 'Salix interface dark mode'; \ No newline at end of file diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 0849e6708..e03027b36 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2266,13 +2266,19 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`) VALUES - (1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()), - (2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', CURDATE()), - (3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE()), - (4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE()), - (5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', CURDATE()), - (6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', CURDATE()); + (1, 14, '1.txt', 'text/plain', 5, 1, 442, NULL, FALSE, 'Ticket:11', 'Ticket:11 dms for the ticket', CURDATE()), + (2, 5, '2.txt', 'text/plain', 5, 1, 442, 1, TRUE, 'Client:104', 'Client:104 dms for the client', CURDATE()), + (3, 5, '3.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Client: 104', 'Client:104 readme', CURDATE()), + (4, 3, '4.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'Worker: 106', 'Worker:106 readme', CURDATE()), + (5, 5, '5.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'travel: 1', 'dmsForThermograph', CURDATE()), + (6, 5, '6.txt', 'text/plain', 5, 1, 442, NULL, TRUE, 'NotExists', 'DoesNotExists', CURDATE()), + (7, 20, '7.jpg', 'image/jpeg', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', CURDATE()), + (8, 20, '8.mp4', 'video/mp4', 9, 1, 442, NULL, FALSE, '1', 'TICKET ID DEL CLIENTE BRUCE WAYNE ID 1101', CURDATE()); +INSERT INTO `vn`.`claimDms`(`claimFk`, `dmsFk`) + VALUES + (1, 7), + (1, 8); INSERT INTO `vn`.`ticketDms`(`ticketFk`, `dmsFk`) VALUES diff --git a/front/core/styles/icons/salixfont.css b/front/core/styles/icons/salixfont.css index 530772246..6b2482bee 100644 --- a/front/core/styles/icons/salixfont.css +++ b/front/core/styles/icons/salixfont.css @@ -26,7 +26,7 @@ .icon-agency-term:before { content: "\e950"; } -.icon-deaulter:before { +.icon-defaulter:before { content: "\e94b"; } .icon-100:before { diff --git a/loopback/server/datasources.json b/loopback/server/datasources.json index 8b741ec97..f51beeb19 100644 --- a/loopback/server/datasources.json +++ b/loopback/server/datasources.json @@ -39,7 +39,8 @@ "multipart/x-zip", "image/png", "image/jpeg", - "image/jpg" + "image/jpg", + "video/mp4" ] }, "dmsStorage": { @@ -84,5 +85,18 @@ "application/octet-stream", "application/pdf" ] + }, + "claimStorage": { + "name": "claimStorage", + "connector": "loopback-component-storage", + "provider": "filesystem", + "root": "./storage/dms", + "maxFileSize": "31457280", + "allowedContentTypes": [ + "image/png", + "image/jpeg", + "image/jpg", + "video/mp4" + ] } } \ No newline at end of file diff --git a/modules/claim/back/methods/claim/downloadFile.js b/modules/claim/back/methods/claim/downloadFile.js new file mode 100644 index 000000000..750356b0b --- /dev/null +++ b/modules/claim/back/methods/claim/downloadFile.js @@ -0,0 +1,59 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('downloadFile', { + description: 'Get the claim file', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'Number', + description: 'The document id', + http: {source: 'path'} + } + ], + returns: [ + { + arg: 'body', + type: 'file', + root: true + }, + { + arg: 'Content-Type', + type: 'String', + http: {target: 'header'} + }, + { + arg: 'Content-Disposition', + type: 'String', + http: {target: 'header'} + } + ], + http: { + path: `/:id/downloadFile`, + verb: 'GET' + } + }); + + Self.downloadFile = async function(ctx, id) { + const models = Self.app.models; + const ClaimContainer = models.ClaimContainer; + const dms = await models.Dms.findById(id); + const pathHash = ClaimContainer.getHash(dms.id); + try { + await ClaimContainer.getFile(pathHash, dms.file); + } catch (e) { + if (e.code != 'ENOENT') + throw e; + + const error = new UserError(`File doesn't exists`); + error.statusCode = 404; + + throw error; + } + + const stream = ClaimContainer.downloadStream(pathHash, dms.file); + + return [stream, dms.contentType, `filename="${dms.file}"`]; + }; +}; diff --git a/modules/claim/back/methods/claim/specs/downloadFile.spec.js b/modules/claim/back/methods/claim/specs/downloadFile.spec.js new file mode 100644 index 000000000..3e46a9bc3 --- /dev/null +++ b/modules/claim/back/methods/claim/specs/downloadFile.spec.js @@ -0,0 +1,13 @@ +const app = require('vn-loopback/server/server'); + +describe('claim downloadFile()', () => { + const dmsId = 7; + + it('should return a response for an employee with image content-type', async() => { + const workerId = 1107; + const ctx = {req: {accessToken: {userId: workerId}}}; + const result = await app.models.Claim.downloadFile(ctx, dmsId); + + expect(result[1]).toEqual('image/jpeg'); + }); +}); diff --git a/modules/claim/back/methods/claim/specs/uploadFile.spec.js b/modules/claim/back/methods/claim/specs/uploadFile.spec.js new file mode 100644 index 000000000..952d80d27 --- /dev/null +++ b/modules/claim/back/methods/claim/specs/uploadFile.spec.js @@ -0,0 +1,18 @@ +const app = require('vn-loopback/server/server'); + +describe('claim uploadFile()', () => { + it(`should return an error for a user without enough privileges`, async() => { + const clientId = 1101; + const ticketDmsTypeId = 14; + const ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}}; + + let error; + await app.models.Claim.uploadFile(ctx).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/claim/back/methods/claim/uploadFile.js b/modules/claim/back/methods/claim/uploadFile.js index 81ad40219..3d0737cf8 100644 --- a/modules/claim/back/methods/claim/uploadFile.js +++ b/modules/claim/back/methods/claim/uploadFile.js @@ -1,6 +1,10 @@ +const UserError = require('vn-loopback/util/user-error'); +const fs = require('fs-extra'); +const path = require('path'); + module.exports = Self => { Self.remoteMethodCtx('uploadFile', { - description: 'Upload and attach a document', + description: 'Upload and attach a file', accessType: 'WRITE', accepts: [{ arg: 'id', @@ -53,22 +57,54 @@ module.exports = Self => { }); Self.uploadFile = async(ctx, id, options) => { - let tx; + const tx = await Self.beginTransaction({}); const myOptions = {}; if (typeof options == 'object') Object.assign(myOptions, options); - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); + if (!myOptions.transaction) myOptions.transaction = tx; - } + const models = Self.app.models; const promises = []; + const TempContainer = models.TempContainer; + const ClaimContainer = models.ClaimContainer; + const fileOptions = {}; + const args = ctx.args; + let srcFile; try { - const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions); - uploadedFiles.forEach(dms => { + const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions); + if (!hasWriteRole) + throw new UserError(`You don't have enough privileges`); + + // Upload file to temporary path + const tempContainer = await TempContainer.container('dms'); + const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); + const files = Object.values(uploaded.files).map(file => { + return file[0]; + }); + + const addedDms = []; + for (const uploadedFile of files) { + const newDms = await createDms(ctx, uploadedFile, myOptions); + const pathHash = ClaimContainer.getHash(newDms.id); + + const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name); + srcFile = path.join(file.client.root, file.container, file.name); + + const claimContainer = await ClaimContainer.container(pathHash); + const dstFile = path.join(claimContainer.client.root, pathHash, newDms.file); + + await fs.move(srcFile, dstFile, { + overwrite: true + }); + + addedDms.push(newDms); + } + + addedDms.forEach(dms => { const newClaimDms = models.ClaimDms.create({ claimFk: id, dmsFk: dms.id @@ -83,7 +119,34 @@ module.exports = Self => { return resolvedPromises; } catch (e) { if (tx) await tx.rollback(); + + if (fs.existsSync(srcFile)) + await fs.unlink(srcFile); + throw e; } }; + + async function createDms(ctx, file, myOptions) { + const models = Self.app.models; + const myUserId = ctx.req.accessToken.userId; + const args = ctx.args; + + const newDms = await models.Dms.create({ + workerFk: myUserId, + dmsTypeFk: args.dmsTypeId, + companyFk: args.companyId, + warehouseFk: args.warehouseId, + reference: args.reference, + description: args.description, + contentType: file.type, + hasFile: args.hasFile + }, myOptions); + + let fileName = file.name; + const extension = models.DmsContainer.getFileExtension(fileName); + fileName = `${newDms.id}.${extension}`; + + return newDms.updateAttribute('file', fileName, myOptions); + } }; diff --git a/modules/claim/back/model-config.json b/modules/claim/back/model-config.json index 16d34543c..d4d772b58 100644 --- a/modules/claim/back/model-config.json +++ b/modules/claim/back/model-config.json @@ -37,5 +37,8 @@ }, "ClaimLog": { "dataSource": "vn" + }, + "ClaimContainer": { + "dataSource": "claimStorage" } } diff --git a/modules/claim/back/models/claim-container.json b/modules/claim/back/models/claim-container.json new file mode 100644 index 000000000..446be77b9 --- /dev/null +++ b/modules/claim/back/models/claim-container.json @@ -0,0 +1,10 @@ +{ + "name": "ClaimContainer", + "base": "Container", + "acls": [{ + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }] +} \ No newline at end of file diff --git a/modules/claim/back/models/claim.js b/modules/claim/back/models/claim.js index fba11cfb6..18b9b3c87 100644 --- a/modules/claim/back/models/claim.js +++ b/modules/claim/back/models/claim.js @@ -7,4 +7,5 @@ module.exports = Self => { require('../methods/claim/uploadFile')(Self); require('../methods/claim/updateClaimAction')(Self); require('../methods/claim/isEditable')(Self); + require('../methods/claim/downloadFile')(Self); }; diff --git a/modules/claim/front/photos/index.html b/modules/claim/front/photos/index.html index 9cc6c649c..9e00ee02f 100644 --- a/modules/claim/front/photos/index.html +++ b/modules/claim/front/photos/index.html @@ -1,6 +1,7 @@
+ zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}" + ng-if="photo.dms.contentType != 'video/mp4'">
+
diff --git a/modules/claim/front/photos/index.js b/modules/claim/front/photos/index.js index 2b77c6abc..62e439a91 100644 --- a/modules/claim/front/photos/index.js +++ b/modules/claim/front/photos/index.js @@ -6,6 +6,13 @@ class Controller extends Section { constructor($element, $, vnFile) { super($element, $); this.vnFile = vnFile; + this.filter = { + include: [ + { + relation: 'dms' + } + ] + }; } deleteDms(index) { @@ -13,7 +20,7 @@ class Controller extends Section { return this.$http.post(`ClaimDms/${dmsFk}/removeFile`) .then(() => { this.$.model.remove(index); - this.vnApp.showSuccess(this.$t('Photo deleted')); + this.vnApp.showSuccess(this.$t('File deleted')); }); } @@ -81,13 +88,13 @@ class Controller extends Section { data: this.dms.files }; this.$http(options).then(() => { - this.vnApp.showSuccess(this.$t('Photo uploaded!')); + this.vnApp.showSuccess(this.$t('File uploaded!')); this.$.model.refresh(); }); } getImagePath(dmsId) { - return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`); + return this.vnFile.getPath(`/api/Claims/${dmsId}/downloadFile`); } } diff --git a/modules/claim/front/photos/locale/es.yml b/modules/claim/front/photos/locale/es.yml index 8ccc1dba4..d2ee9ffbd 100644 --- a/modules/claim/front/photos/locale/es.yml +++ b/modules/claim/front/photos/locale/es.yml @@ -1,5 +1,5 @@ Are you sure you want to continue?: ¿Seguro que quieres continuar? Drag & Drop photos here...: Arrastra y suelta fotos aquí... -Photo deleted: Foto eliminada -Photo uploaded!: Foto subida! -Select photo: Seleccionar foto \ No newline at end of file +File deleted: Archivo eliminado +File uploaded!: Archivo subido! +Select file: Seleccionar fichero \ No newline at end of file diff --git a/modules/claim/front/photos/style.scss b/modules/claim/front/photos/style.scss index d747dd9b8..101cb0da2 100644 --- a/modules/claim/front/photos/style.scss +++ b/modules/claim/front/photos/style.scss @@ -29,4 +29,19 @@ vn-claim-photos { height: 288px; } } -} \ No newline at end of file + + .video { + width: 100%; + height: 100%; + object-fit: cover; + cursor: pointer; + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), + 0 3px 1px -2px rgba(0,0,0,.2), + 0 1px 5px 0 rgba(0,0,0,.12); + border: 2px solid transparent; + + } + .video:hover { + border: 2px solid $color-primary + } +} diff --git a/modules/client/back/methods/client/specs/updateAddress.spec.js b/modules/client/back/methods/client/specs/updateAddress.spec.js index 5597c6e5a..2ad564cc5 100644 --- a/modules/client/back/methods/client/specs/updateAddress.spec.js +++ b/modules/client/back/methods/client/specs/updateAddress.spec.js @@ -33,7 +33,6 @@ describe('Address updateAddress', () => { error = e; } - expect(error).toBeDefined(); expect(error.message).toEqual('Incoterms is required for a non UEE member'); }); @@ -57,7 +56,6 @@ describe('Address updateAddress', () => { error = e; } - expect(error).toBeDefined(); expect(error.message).toEqual('Customs agent is required for a non UEE member'); }); @@ -91,6 +89,8 @@ describe('Address updateAddress', () => { it('should return an error for a user without enough privileges', async() => { const tx = await models.Client.beginTransaction({}); + let error; + try { const options = {transaction: tx}; ctx.args = { @@ -124,8 +124,10 @@ describe('Address updateAddress', () => { expect(address.isLogifloraAllowed).toEqual(true); await tx.rollback(); + ctx.req.accessToken.userId = employeeId; } catch (e) { await tx.rollback(); + ctx.req.accessToken.userId = employeeId; throw e; } }); diff --git a/modules/client/back/methods/client/updateAddress.js b/modules/client/back/methods/client/updateAddress.js index d7e20b876..cae797f6b 100644 --- a/modules/client/back/methods/client/updateAddress.js +++ b/modules/client/back/methods/client/updateAddress.js @@ -89,11 +89,12 @@ module.exports = function(Self) { const args = ctx.args; const userId = ctx.req.accessToken.userId; const myOptions = {}; - const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions); if (typeof options == 'object') Object.assign(myOptions, options); + const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions); + if (args.isLogifloraAllowed && !isSalesAssistant) throw new UserError(`You don't have enough privileges`); diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 5ccc1ec64..9b4d411c8 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -46,10 +46,6 @@ module.exports = Self => { message: 'TIN must be unique' }); - Self.validatesUniquenessOf('socialName', { - message: 'The company name must be unique' - }); - Self.validatesFormatOf('email', { message: 'Invalid email', allowNull: true, @@ -63,17 +59,37 @@ module.exports = Self => { min: 3, max: 10 }); + Self.validateAsync('socialName', socialNameIsUnique, { + message: 'The company name must be unique' + }); + + async function socialNameIsUnique(err, done) { + const filter = { + where: { + and: [ + {socialName: this.socialName}, + {isActive: true}, + {id: {neq: this.id}} + ] + } + }; + const client = await Self.app.models.Client.findOne(filter); + if (client) + err(); + done(); + } + Self.validateAsync('iban', ibanNeedsValidation, { message: 'The IBAN does not have the correct format' }); async function ibanNeedsValidation(err, done) { - let filter = { + const filter = { fields: ['code'], where: {id: this.countryFk} }; - let country = await Self.app.models.Country.findOne(filter); - let code = country ? country.code.toLowerCase() : null; + const country = await Self.app.models.Country.findOne(filter); + const code = country ? country.code.toLowerCase() : null; if (code != 'es') return done(); @@ -90,12 +106,12 @@ module.exports = Self => { if (!this.isTaxDataChecked) return done(); - let filter = { + const filter = { fields: ['code'], where: {id: this.countryFk} }; - let country = await Self.app.models.Country.findOne(filter); - let code = country ? country.code.toLowerCase() : null; + const country = await Self.app.models.Country.findOne(filter); + const code = country ? country.code.toLowerCase() : null; if (!this.fi || !validateTin(this.fi, code)) err(); @@ -118,8 +134,8 @@ module.exports = Self => { function cannotHaveET(err) { if (!this.fi) return; - let tin = this.fi.toUpperCase(); - let cannotHaveET = /^[A-B]/.test(tin); + const tin = this.fi.toUpperCase(); + const cannotHaveET = /^[A-B]/.test(tin); if (cannotHaveET && this.isEqualizated) err(); diff --git a/modules/invoiceIn/back/methods/invoice-in/getTotals.js b/modules/invoiceIn/back/methods/invoice-in/getTotals.js index 918467010..f35c10617 100644 --- a/modules/invoiceIn/back/methods/invoice-in/getTotals.js +++ b/modules/invoiceIn/back/methods/invoice-in/getTotals.js @@ -30,7 +30,7 @@ module.exports = Self => { SUM(iidd.amount) totalDueDay FROM vn.invoiceIn ii LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase, - SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) totalVat + CAST(SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) AS DECIMAL(10,2)) totalVat FROM vn.invoiceInTax iit LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk WHERE iit.invoiceInFk = ?) iit ON TRUE diff --git a/modules/invoiceIn/front/create/index.html b/modules/invoiceIn/front/create/index.html index 4ec724854..16ecf26cf 100644 --- a/modules/invoiceIn/front/create/index.html +++ b/modules/invoiceIn/front/create/index.html @@ -13,13 +13,14 @@ vn-id="supplier" url="Suppliers" label="Supplier" - search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}" + search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}, {nif: {like: '%'+ $search +'%'}}]}" + fields="['nif']" show-field="name" value-field="id" ng-model="$ctrl.invoiceIn.supplierFk" order="id" vn-focus> - {{id}}: {{name}} + {{id}}: {{nif}}: {{name}} - - { - const minDate = new Date(value); - minHour.setHours(0, 0, 0, 0); - const maxDate = new Date(value); - maxHour.setHours(23, 59, 59, 59); + it('should return one entry for the given item', async() => { + const minDate = new Date(); + minDate.setHours(0, 0, 0, 0); + const maxDate = new Date(); + maxDate.setHours(23, 59, 59, 59); - it('should return one entry for a given item', async() => { const tx = await models.Item.beginTransaction({}); const options = {transaction: tx}; @@ -23,13 +22,18 @@ describe('item lastEntriesFilter()', () => { } }); - it('should return five entries for a given item', async() => { + it('should return five entries for the given item', async() => { + const minDate = new Date(); + minDate.setHours(0, 0, 0, 0); + minDate.setMonth(minDate.getMonth() - 2, 1); + + const maxDate = new Date(); + maxDate.setHours(23, 59, 59, 59); + const tx = await models.Item.beginTransaction({}); const options = {transaction: tx}; try { - minDate.setMonth(minDate.getMonth() - 2, 1); - const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}}; const result = await models.Item.lastEntriesFilter(filter, options); diff --git a/modules/monitor/back/methods/sales-monitor/salesFilter.js b/modules/monitor/back/methods/sales-monitor/salesFilter.js index d67c5c3fc..4521b2351 100644 --- a/modules/monitor/back/methods/sales-monitor/salesFilter.js +++ b/modules/monitor/back/methods/sales-monitor/salesFilter.js @@ -304,7 +304,7 @@ module.exports = Self => { {'tp.hasTicketRequest': true}, {'tp.hasComponentLack': true}, {'tp.isTaxDataChecked': false}, - {'tp.isAvailable': false} + {'tp.itemShortage': {neq: null}} ]}; } else if (hasProblems === false) { whereProblems = {and: [ @@ -313,7 +313,7 @@ module.exports = Self => { {'tp.hasTicketRequest': false}, {'tp.hasComponentLack': false}, {'tp.isTaxDataChecked': true}, - {'tp.isAvailable': true} + {'tp.itemShortage': null} ]}; } diff --git a/modules/monitor/front/index/tickets/index.html b/modules/monitor/front/index/tickets/index.html index 76b9219ee..a504301a5 100644 --- a/modules/monitor/front/index/tickets/index.html +++ b/modules/monitor/front/index/tickets/index.html @@ -50,7 +50,7 @@ Salesperson - + Date @@ -98,8 +98,8 @@ icon="icon-buyrequest"> @@ -153,8 +153,8 @@ - - {{::ticket.shipped | date: 'dd/MM/yyyy'}} + + {{::ticket.shippedDate | date: 'dd/MM/yyyy'}} {{::ticket.zoneLanding | date: 'HH:mm'}} diff --git a/modules/monitor/front/index/tickets/index.js b/modules/monitor/front/index/tickets/index.js index 58b56bbc5..0770d8634 100644 --- a/modules/monitor/front/index/tickets/index.js +++ b/modules/monitor/front/index/tickets/index.js @@ -52,7 +52,7 @@ export default class Controller extends Section { } }, { - field: 'shipped', + field: 'shippedDate', searchable: false }, { @@ -136,7 +136,7 @@ export default class Controller extends Section { return {'z.hour': value}; case 'practicalHour': return {'zed.etc': value}; - case 'shipped': + case 'shippedDate': return {'t.shipped': { between: this.dateRange(value)} }; diff --git a/modules/order/front/index/index.html b/modules/order/front/index/index.html index a2a4a5226..c4bed7307 100644 --- a/modules/order/front/index/index.html +++ b/modules/order/front/index/index.html @@ -33,7 +33,7 @@ {{::order.name | dashIfEmpty}} - + @@ -46,7 +46,7 @@ disabled="true"> - {{::order.created | date: 'dd/MM/yyyy HH:mm'}} + {{::order.created | date: 'dd/MM/yyyy HH:mm'}} {{::order.landed | date:'dd/MM/yyyy'}} diff --git a/modules/route/back/methods/route/getSuggestedTickets.js b/modules/route/back/methods/route/getSuggestedTickets.js index f0333e66b..c1a9c7cae 100644 --- a/modules/route/back/methods/route/getSuggestedTickets.js +++ b/modules/route/back/methods/route/getSuggestedTickets.js @@ -59,6 +59,12 @@ module.exports = Self => { fields: ['id', 'name'] } }, + { + relation: 'agencyMode', + scope: { + fields: ['id', 'name'] + } + }, { relation: 'address', scope: { diff --git a/modules/route/front/locale/en.yml b/modules/route/front/locale/en.yml new file mode 100644 index 000000000..0e5302b14 --- /dev/null +++ b/modules/route/front/locale/en.yml @@ -0,0 +1 @@ +Unlink zone: Unlink zone {{zoneName}} from agency {{agencyName}} \ No newline at end of file diff --git a/modules/route/front/locale/es.yml b/modules/route/front/locale/es.yml index 268bc4812..74ab2cd61 100644 --- a/modules/route/front/locale/es.yml +++ b/modules/route/front/locale/es.yml @@ -1,4 +1,5 @@ Routes: Rutas Search routes by id: Buscar rutas por identificador New route: Nueva ruta -route: ruta \ No newline at end of file +route: ruta +Unlink zone: Desvincular zona {{zoneName}} de agencia {{agencyName}} \ No newline at end of file diff --git a/modules/route/front/ticket-popup/index.html b/modules/route/front/ticket-popup/index.html index 33684a433..99e3364b8 100644 --- a/modules/route/front/ticket-popup/index.html +++ b/modules/route/front/ticket-popup/index.html @@ -33,7 +33,7 @@ PC Address - Warehouse + Zone @@ -62,7 +62,7 @@ @@ -76,4 +76,9 @@ question="{{$ctrl.confirmationMessage}}" message="Unlink selected zone?"> + + + + + diff --git a/modules/supplier/front/agency-term/index/index.html b/modules/supplier/front/agency-term/index/index.html index ac3b9b0d5..9d53226c5 100644 --- a/modules/supplier/front/agency-term/index/index.html +++ b/modules/supplier/front/agency-term/index/index.html @@ -78,7 +78,7 @@ diff --git a/modules/ticket/back/methods/ticket/filter.js b/modules/ticket/back/methods/ticket/filter.js index 58c440e95..902831d99 100644 --- a/modules/ticket/back/methods/ticket/filter.js +++ b/modules/ticket/back/methods/ticket/filter.js @@ -324,7 +324,7 @@ module.exports = Self => { case true: condition = `or`; hasProblem = true; - range = 0; + range = {neq: null}; hasWhere = true; break; @@ -340,7 +340,7 @@ module.exports = Self => { {'tp.isFreezed': hasProblem}, {'tp.risk': hasProblem}, {'tp.hasTicketRequest': hasProblem}, - {'tp.isAvailable': range} + {'tp.itemShortage': range} ]}; if (hasWhere) diff --git a/modules/ticket/back/methods/ticket/getSales.js b/modules/ticket/back/methods/ticket/getSales.js index 6dfee5dfa..3e45b3fb5 100644 --- a/modules/ticket/back/methods/ticket/getSales.js +++ b/modules/ticket/back/methods/ticket/getSales.js @@ -94,7 +94,7 @@ module.exports = Self => { sale.visible = itemStock.visible; sale.claim = claimedSales.get(sale.id); if (problems) { - sale.isAvailable = problems.isAvailable; + sale.itemShortage = problems.itemShortage; sale.hasTicketRequest = problems.hasTicketRequest; sale.hasComponentLack = problems.hasComponentLack; } diff --git a/modules/ticket/back/methods/ticket/specs/filter.spec.js b/modules/ticket/back/methods/ticket/specs/filter.spec.js index b251d5335..4b583fc87 100644 --- a/modules/ticket/back/methods/ticket/specs/filter.spec.js +++ b/modules/ticket/back/methods/ticket/specs/filter.spec.js @@ -39,7 +39,7 @@ describe('ticket filter()', () => { const filter = {}; const result = await models.Ticket.filter(ctx, filter, options); - expect(result.length).toEqual(4); + expect(result.length).toEqual(6); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/front/index/index.html b/modules/ticket/front/index/index.html index 36af86001..1e18ce284 100644 --- a/modules/ticket/front/index/index.html +++ b/modules/ticket/front/index/index.html @@ -52,8 +52,8 @@ icon="icon-buyrequest"> diff --git a/modules/ticket/front/index/locale/es.yml b/modules/ticket/front/index/locale/es.yml index eac0084f6..74b17b0f3 100644 --- a/modules/ticket/front/index/locale/es.yml +++ b/modules/ticket/front/index/locale/es.yml @@ -1,6 +1,7 @@ Weekly tickets: Tickets programados Go to lines: Ir a lineas Not available: No disponible +Not visible: No visible Payment on account...: Pago a cuenta... Set as delivered and open delivery note(s): Marcar como servido/s y abrir albarán/es Closure: Cierre diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 836fadb9b..f4e5840f3 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -95,8 +95,8 @@ translate-attr="{title: 'Reserved'}"> diff --git a/modules/ticket/front/summary/index.html b/modules/ticket/front/summary/index.html index 99fb949b6..fe49a301f 100644 --- a/modules/ticket/front/summary/index.html +++ b/modules/ticket/front/summary/index.html @@ -157,8 +157,8 @@ translate-attr="{title: 'Reserved'}"> diff --git a/modules/ticket/front/volume/index.js b/modules/ticket/front/volume/index.js index 81491bca0..a3d531898 100644 --- a/modules/ticket/front/volume/index.js +++ b/modules/ticket/front/volume/index.js @@ -20,7 +20,7 @@ class Controller extends Section { set sales(value) { this._sales = value; - if (value) this.applyVolumes(); + if (value && value.length) this.applyVolumes(); } applyVolumes() { diff --git a/modules/zone/front/delivery-days/index.js b/modules/zone/front/delivery-days/index.js index e4d5e72e9..71e8c8ab7 100644 --- a/modules/zone/front/delivery-days/index.js +++ b/modules/zone/front/delivery-days/index.js @@ -73,15 +73,14 @@ class Controller extends Section { for (let event of $events) zoneIds.push(event.zoneFk); - this.$.zoneEvents.show($event.target); - const params = { zoneIds: zoneIds, date: day }; this.$http.post(`Zones/getZoneClosing`, params) - .then(res => this.zoneClosing = res.data); + .then(res => this.zoneClosing = res.data) + .then(() => this.$.zoneEvents.show($event.target)); } preview(zone) { diff --git a/print/templates/reports/delivery-note/delivery-note.js b/print/templates/reports/delivery-note/delivery-note.js index adda6e756..f9dba0578 100755 --- a/print/templates/reports/delivery-note/delivery-note.js +++ b/print/templates/reports/delivery-note/delivery-note.js @@ -7,6 +7,10 @@ const fs = require('fs-extra'); module.exports = { name: 'delivery-note', + created() { + if (!this.type) + this.type = 'deliveryNote'; + }, async serverPrefetch() { this.client = await this.fetchClient(this.ticketId); this.ticket = await this.fetchTicket(this.ticketId); @@ -129,7 +133,7 @@ module.exports = { }, type: { type: String, - required: true + required: false } } }; diff --git a/storage/dms/8f1/7.jpg b/storage/dms/8f1/7.jpg new file mode 100644 index 000000000..83052dea6 Binary files /dev/null and b/storage/dms/8f1/7.jpg differ diff --git a/storage/dms/c9f/8.mp4 b/storage/dms/c9f/8.mp4 new file mode 100644 index 000000000..b11552f9c Binary files /dev/null and b/storage/dms/c9f/8.mp4 differ