diff --git a/db/changes/10470-family/00-defaultViewConfig.sql b/db/changes/10470-family/00-defaultViewConfig.sql new file mode 100644 index 000000000..5290f5a98 --- /dev/null +++ b/db/changes/10470-family/00-defaultViewConfig.sql @@ -0,0 +1,3 @@ +INSERT INTO salix.defaultViewConfig (tableCode, columns) +VALUES ('clientsDetail', '{"id":true,"phone":true,"city":true,"socialName":true,"salesPersonFk":true,"email":true}'); + diff --git a/modules/client/back/methods/client/detailFilter.js b/modules/client/back/methods/client/detailFilter.js new file mode 100644 index 000000000..91cb7e411 --- /dev/null +++ b/modules/client/back/methods/client/detailFilter.js @@ -0,0 +1,202 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('detailFilter', { + description: 'Find all instances of the model matched by filter from the data source.', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + }, + { + arg: 'tags', + type: ['object'], + description: 'List of tags to filter with', + }, + { + 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: 'categoryFk', + type: 'integer', + description: 'Category id', + }, + { + arg: 'typeFk', + type: 'integer', + description: 'Type id', + }, + { + arg: 'isActive', + type: 'boolean', + description: 'Whether the item is or not active', + }, + { + arg: 'buyerFk', + type: 'integer', + description: 'The buyer of the item', + }, + { + arg: 'supplierFk', + type: 'integer', + description: 'The supplier of the item', + }, + { + arg: 'description', + type: 'string', + description: 'The item description', + }, + { + arg: 'stemMultiplier', + type: 'integer', + description: 'The item multiplier', + }, + { + arg: 'landed', + type: 'date', + description: 'The item last buy landed date', + }, + { + arg: 'isFloramondo', + type: 'boolean', + description: 'Whether the the item is or not floramondo', + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/detailFilter`, + verb: 'GET' + } + }); + + Self.detailFilter = async(ctx, filter, options) => { + const conn = Self.dataSource.connector; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + let codeWhere; + + if (ctx.args.search) { + const items = await Self.app.models.ItemBarcode.find({ + where: {code: ctx.args.search}, + fields: ['itemFk'] + }, myOptions); + + const itemIds = []; + + for (const item of items) + itemIds.push(item.itemFk); + + codeWhere = {'i.id': {inq: itemIds}}; + } + + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {or: [{'i.id': value}, codeWhere]} + : {or: [ + {'i.name': {like: `%${value}%`}}, + {'i.longName': {like: `%${value}%`}}, + codeWhere]}; + case 'id': + case 'isActive': + case 'typeFk': + case 'isFloramondo': + return {[`i.${param}`]: value}; + case 'multiplier': + return {'i.stemMultiplier': value}; + case 'categoryFk': + return {'ic.id': value}; + case 'buyerFk': + return {'it.workerFk': value}; + case 'supplierFk': + return {'s.id': value}; + case 'origin': + return {'ori.code': value}; + case 'intrastat': + return {'intr.description': value}; + case 'landed': + return {'lb.landed': value}; + } + }); + + filter = mergeFilters(filter, {where}); + + const stmts = []; + const stmt = new ParameterizedSQL( + `SELECT + c.id, + c.name, + c.socialName, + c.fi, + c.credit, + c.creditInsurance, + c.phone, + c.mobile, + c.street, + c.city, + c.postcode, + c.email, + c.created, + c.isActive, + c.isVies, + c.isTaxDataChecked, + c.isEqualizated, + c.isFreezed, + c.hasToInvoice, + c.hasToInvoiceByAddress, + c.isToBeMailed, + c.hasSepaVnl, + c.hasLcr, + c.hasCoreVnl, + ct.id AS countryFk, + ct.country, + p.id AS provinceFk, + p.name AS province, + u.id AS salesPersonFk, + u.name AS salesPerson, + bt.code AS businessTypeFk, + bt.description AS businessType, + sti.CodigoIva AS sageTaxTypeFk, + sti.Iva AS sageTaxType, + stt.CodigoTransaccion AS sageTransactionTypeFk, + stt.Transaccion AS sageTransactionType + FROM client c + LEFT JOIN account.user u ON u.id = c.salesPersonFk + LEFT JOIN country ct ON ct.id = c.countryFk + LEFT JOIN province p ON p.id = c.provinceFk + LEFT JOIN businessType bt ON bt.code = c.businessTypeFk + LEFT JOIN sage.TiposIva sti ON sti.CodigoIva = c.taxTypeSageFk + LEFT JOIN sage.TiposTransacciones stt ON stt.CodigoTransaccion = c.transactionTypeSageFk + + ` + ); + + stmt.merge(conn.makeWhere(filter.where)); + stmt.merge(conn.makePagination(filter)); + + const clientsIndex = stmts.push(stmt) - 1; + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + + return clientsIndex === 0 ? result : result[clientsIndex]; + }; +}; diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 5ed777ab5..d71279bd0 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -31,6 +31,7 @@ module.exports = Self => { require('../methods/client/createReceipt')(Self); require('../methods/client/updatePortfolio')(Self); require('../methods/client/checkDuplicated')(Self); + require('../methods/client/detailFilter')(Self); // Validations diff --git a/modules/client/front/detail/index.html b/modules/client/front/detail/index.html new file mode 100644 index 000000000..66028efc0 --- /dev/null +++ b/modules/client/front/detail/index.html @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Identifier + + Name + + Social name + + Fiscal ID + + Salesperson + + Credit + + Credit insurance + + Phone + + Mobile + + Street + + Country + + Province + + City + + Postcode + + Email + + Created + + Business type + + Sage tax type + + Sage tr. type + + Active + + Vies + + Tax data checked + + Tax equalized + + Freezed + + Invoice + + Invoice by address + + Mailing + + Received LCR + + Received core VNL + + Received B2B VNL +
+ + + + + + + {{::client.id}} + + {{::client.name}}{{::client.socialName}}{{::client.fi}} + + {{::client.salesPerson | dashIfEmpty}} + + {{::client.credit}}{{::client.creditInsurance | dashIfEmpty}}{{::client.phone | dashIfEmpty}}{{::client.mobile | dashIfEmpty}}{{::client.street | dashIfEmpty}}{{::client.country | dashIfEmpty}}{{::client.province | dashIfEmpty}}{{::client.city | dashIfEmpty}}{{::client.postcode | dashIfEmpty}}{{::client.email | dashIfEmpty}}{{::client.created | date:'dd/MM/yyyy'}}{{::client.businessType | dashIfEmpty}}{{::client.sageTaxType | dashIfEmpty}}{{::client.sageTransactionType | dashIfEmpty}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + Filter by selection + + + Exclude selection + + + Remove filter + + + Remove all filters + + + \ No newline at end of file diff --git a/modules/client/front/detail/index.js b/modules/client/front/detail/index.js new file mode 100644 index 000000000..cca19622a --- /dev/null +++ b/modules/client/front/detail/index.js @@ -0,0 +1,113 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + + this.smartTableOptions = { + activeButtons: { + search: true, + shownColumns: true, + }, + columns: [ + { + field: 'category', + autocomplete: { + url: 'ItemCategories', + valueField: 'name', + } + }, + { + field: 'origin', + autocomplete: { + url: 'Origins', + showField: 'code', + valueField: 'code' + } + }, + { + field: 'typeFk', + autocomplete: { + url: 'ItemTypes', + } + }, + { + field: 'intrastat', + autocomplete: { + url: 'Intrastats', + showField: 'description', + valueField: 'description' + } + }, + { + field: 'buyerFk', + autocomplete: { + url: 'Workers/activeWithRole', + where: `{role: {inq: ['logistic', 'buyer']}}`, + searchFunction: '{firstName: $search}', + showField: 'nickname', + valueField: 'id', + } + }, + { + field: 'active', + searchable: false + }, + { + field: 'landed', + searchable: false + }, + ] + }; + } + + exprBuilder(param, value) { + switch (param) { + case 'category': + return {'ic.name': value}; + case 'buyerFk': + return {'it.workerFk': value}; + case 'grouping': + return {'b.grouping': value}; + case 'packing': + return {'b.packing': value}; + case 'origin': + return {'ori.code': value}; + case 'typeFk': + return {'i.typeFk': value}; + case 'intrastat': + return {'intr.description': value}; + case 'name': + return {'i.name': {like: `%${value}%`}}; + case 'producer': + return {'pr.name': {like: `%${value}%`}}; + case 'id': + case 'size': + case 'subname': + case 'isActive': + case 'density': + case 'stemMultiplier': + case 'stems': + return {[`i.${param}`]: value}; + } + } + + onCloneAccept(itemFk) { + return this.$http.post(`Items/${itemFk}/clone`) + .then(res => { + this.$state.go('item.card.tags', {id: res.data.id}); + }); + } + + preview(client) { + this.clientSelected = client; + this.$.preview.show(); + } +} + +ngModule.vnComponent('vnClientDetail', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/client/front/detail/index.spec.js b/modules/client/front/detail/index.spec.js new file mode 100644 index 000000000..18abde581 --- /dev/null +++ b/modules/client/front/detail/index.spec.js @@ -0,0 +1,30 @@ +import './index.js'; + +describe('Item', () => { + describe('Component vnItemIndex', () => { + let controller; + let $httpBackend; + let $scope; + + beforeEach(ngModule('item')); + + beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => { + $httpBackend = _$httpBackend_; + $scope = $rootScope.$new(); + const $element = angular.element(''); + controller = $componentController('vnItemIndex', {$element, $scope}); + })); + + describe('onCloneAccept()', () => { + it('should perform a post query and then call go() then update itemSelected in the controller', () => { + jest.spyOn(controller.$state, 'go'); + + $httpBackend.expectRoute('POST', `Items/:id/clone`).respond({id: 99}); + controller.onCloneAccept(1); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('item.card.tags', {id: 99}); + }); + }); + }); +}); diff --git a/modules/client/front/detail/locale/es.yml b/modules/client/front/detail/locale/es.yml new file mode 100644 index 000000000..0d72edd28 --- /dev/null +++ b/modules/client/front/detail/locale/es.yml @@ -0,0 +1,2 @@ +picture: Foto +Buy requests: Peticiones de compra \ No newline at end of file diff --git a/modules/client/front/detail/style.scss b/modules/client/front/detail/style.scss new file mode 100644 index 000000000..eaa1a16ed --- /dev/null +++ b/modules/client/front/detail/style.scss @@ -0,0 +1,32 @@ +@import "variables"; + +vn-item-product { + display: block; + + .id { + background-color: $color-main; + color: $color-font-dark; + margin-bottom: 0; + } + .image { + height: 112px; + width: 112px; + + & > img { + max-height: 100%; + max-width: 100%; + border-radius: 3px; + } + } + vn-label-value:first-of-type section{ + margin-top: 9px; + } +} + +table { + img { + border-radius: 50%; + width: 50px; + height: 50px; + } +} \ No newline at end of file diff --git a/modules/client/front/index.js b/modules/client/front/index.js index ea732beea..5985966d1 100644 --- a/modules/client/front/index.js +++ b/modules/client/front/index.js @@ -47,3 +47,4 @@ import './consumption-search-panel'; import './defaulter'; import './notification'; import './unpaid'; +import './detail'; diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json index 293243470..2073cb200 100644 --- a/modules/client/front/routes.json +++ b/modules/client/front/routes.json @@ -7,6 +7,7 @@ "menus": { "main": [ {"state": "client.index", "icon": "person"}, + {"state": "client.detail", "icon": "person"}, {"state": "client.notification", "icon": "campaign"}, {"state": "client.defaulter", "icon": "icon-defaulter"} ], @@ -381,6 +382,12 @@ "component": "vn-client-unpaid", "acl": ["administrative"], "description": "Unpaid" + }, + { + "url": "/detail", + "state": "client.detail", + "component": "vn-client-detail", + "description": "Detail" } ] }