From 2a64fde57c74836804c639e7cd21cc2b2f43064f Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 22 Sep 2022 14:58:38 +0200 Subject: [PATCH 01/77] feat: add option "move expedition" --- modules/ticket/front/expedition/index.html | 119 ++++++++++++--------- modules/ticket/front/expedition/index.js | 11 ++ 2 files changed, 82 insertions(+), 48 deletions(-) diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index a41d368f6..37e64fad5 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -8,54 +8,77 @@ auto-load="true"> - - - - - - Expedition - Item - Name - Package type - Counter - externalId - Created - State - - - - - - - - - - {{expedition.id | zeroFill:6}} - - - {{expedition.packagingFk}} - - - {{::expedition.packageItemName}} - {{::expedition.freightItemName}} - {{::expedition.counter}} - {{::expedition.externalId}} - {{::expedition.created | date:'dd/MM/yyyy HH:mm'}} - {{::expedition.state}} - - - - - - - + + + + + + + + + +

Subtotal {{$ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}

+

VAT {{$ctrl.ticket.totalWithVat - $ctrl.ticket.totalWithoutVat | currency: 'EUR':2}}

+

Total {{$ctrl.ticket.totalWithVat | currency: 'EUR':2}}

+
+
+ + + + + + + + Expedition + Item + Name + Package type + Counter + externalId + Created + State + + + + + + + + + + {{expedition.id | zeroFill:6}} + + + {{expedition.packagingFk}} + + + {{::expedition.packageItemName}} + {{::expedition.freightItemName}} + {{::expedition.counter}} + {{::expedition.externalId}} + {{::expedition.created | date:'dd/MM/yyyy HH:mm'}} + {{::expedition.state}} + + + + + + +
Date: Fri, 23 Sep 2022 08:50:11 +0200 Subject: [PATCH 02/77] feat: add totalChecked --- modules/ticket/front/expedition/index.html | 20 ++++++++++++++++---- modules/ticket/front/expedition/index.js | 4 ++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index 37e64fad5..3a5d5f8a6 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -12,12 +12,11 @@ + ng-show="$ctrl.totalChecked"> @@ -134,4 +133,17 @@ - \ No newline at end of file + + + + + New ticket without route + + + New ticket with route + + \ No newline at end of file diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index adbbc53d4..38d02f548 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -22,6 +22,10 @@ class Controller extends Section { return checkedRows; } + + get totalChecked() { + return this.checked.length; + } } ngModule.vnComponent('vnTicketExpedition', { From 8082e3071ad2e35d163cff01838372a4682dbde7 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 27 Sep 2022 15:17:54 +0200 Subject: [PATCH 03/77] try --- front/core/components/searchbar/searchbar.js | 82 +++++++++++++++++++- front/core/components/smart-table/index.html | 4 + front/core/components/smart-table/index.js | 82 +++++++++++++++++--- 3 files changed, 152 insertions(+), 16 deletions(-) diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index 89b5d7df6..fc194bc59 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -139,8 +139,12 @@ export default class Searchbar extends Component { } removeParam(index) { + const field = this.params[index].key; + this.filterSanitizer(field); + this.params.splice(index, 1); - this.doSearch(this.fromBar(), 'bar'); + this.toRemove = field; + this.doSearch(this.fromBar(), 'removeBar'); } fromBar() { @@ -163,7 +167,7 @@ export default class Searchbar extends Component { let keys = Object.keys(filter); keys.forEach(key => { - if (key == 'search') return; + if (key == 'search' || key == 'tableQ') return; let value = filter[key]; let chip; @@ -195,9 +199,10 @@ export default class Searchbar extends Component { doSearch(filter, source) { if (filter === this.filter && source != 'state') return; - let promise = this.onSearch({$params: filter}); + let promise = this.onSearch({$params: filter}, source); promise = promise || this.$q.resolve(); promise.then(data => this.onFilter(filter, source, data)); + this.toBar(filter); } onFilter(filter, source, data) { @@ -247,8 +252,15 @@ export default class Searchbar extends Component { this.filter = filter; + if (source == 'removeBar') { + delete params[this.toRemove]; + delete this.model.userParams[this.toRemove]; + this.model.refresh(); + } + if (!filter && this.model) this.model.clear(); + if (source != 'state') this.transition = this.$state.go(state, params, opts).transition; if (source != 'bar' && (source != 'state' || this.$state.is(this.baseState))) @@ -270,6 +282,12 @@ export default class Searchbar extends Component { return; } + if (Object.keys(filter).length === 0) { + this.filterSanitizer('search'); + if (this.model.userParams) + delete this.model.userParams['search']; + } + let where = null; let params = null; @@ -283,9 +301,65 @@ export default class Searchbar extends Component { params = this.fetchParams({$params: params}); } - return this.model.applyFilter(where ? {where} : null, params) + if (this.$params.q) + Object.assign(params, JSON.parse(this.$params.q)); + + return this.model.addFilter(where ? {where} : null, params) .then(() => this.model.data); } + + filterSanitizer(field) { + const userFilter = this.model.userFilter; + const userParams = this.model.userParams; + const where = userFilter && userFilter.where; + delete this.$params.q[field]; + + if (this.exprBuilder) { + const param = this.exprBuilder({ + param: field, + value: null + }); + if (param) [field] = Object.keys(param); + } + + if (!where) return; + + const whereKeys = Object.keys(where); + for (let key of whereKeys) { + removeProp(where, field, key); + + if (Object.keys(where).length == 0) + delete userFilter.where; + } + + function removeProp(obj, targetProp, prop) { + if (prop == targetProp) + delete obj[prop]; + + if (prop === 'and' || prop === 'or' && obj[prop]) { + const arrayCopy = obj[prop].slice(); + for (let param of arrayCopy) { + const [key] = Object.keys(param); + const index = obj[prop].findIndex(param => { + return Object.keys(param)[0] == key; + }); + if (key == targetProp) + obj[prop].splice(index, 1); + + if (param[key] instanceof Array) + removeProp(param, field, key); + + if (Object.keys(param).length == 0) + obj[prop].splice(index, 1); + } + + if (obj[prop].length == 0) + delete obj[prop]; + } + } + + return {userFilter, userParams}; + } } ngModule.vnComponent('vnSearchbar', { diff --git a/front/core/components/smart-table/index.html b/front/core/components/smart-table/index.html index f26a6b4a2..3168d7ee7 100644 --- a/front/core/components/smart-table/index.html +++ b/front/core/components/smart-table/index.html @@ -103,3 +103,7 @@ + + diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index 9e6e7009c..7880f257f 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -15,9 +15,15 @@ export default class SmartTable extends Component { this.$inputsScope; this.columns = []; this.autoSave = false; + this.autoState = true; this.transclude(); } + $onInit() { + if (this.model) + this.defaultFilter(); + } + $onDestroy() { const styleElement = document.querySelector('style[id="smart-table"]'); if (this.$.css && styleElement) @@ -49,6 +55,7 @@ export default class SmartTable extends Component { this._model = value; if (value) { this.$.model = value; + this.defaultFilter(); this.defaultOrder(); } } @@ -160,6 +167,26 @@ export default class SmartTable extends Component { } } + defaultFilter() { + if (!this.$params.q) return; + const stateFilter = JSON.parse(this.$params.q).tableQ; + if (!stateFilter || !this.exprBuilder) return; + + const columns = this.columns.map(column => column.field); + + this.displaySearch(); + if (!this.$inputsScope.searchProps) + this.$inputsScope.searchProps = {}; + + for (let param in stateFilter) { + if (columns.includes(param)) { + const whereParams = {[param]: stateFilter[param]}; + Object.assign(this.$inputsScope.searchProps, whereParams); + this.addFilter(param, stateFilter[param]); + } + } + } + defaultOrder() { const order = this.model.order; if (!order) return; @@ -394,29 +421,56 @@ export default class SmartTable extends Component { this.searchByColumn(field); } - searchByColumn(field) { - const searchCriteria = this.$inputsScope.searchProps[field]; - const emptySearch = searchCriteria === '' || searchCriteria == null; + searchPropsSanitizer() { + if (!this.$inputsScope || !this.$inputsScope.searchProps) return null; + let searchProps = this.$inputsScope.searchProps; + const searchPropsArray = Object.entries(searchProps); + searchProps = searchPropsArray.filter( + ([key, value]) => value && value != '' + ); + return Object.fromEntries(searchProps); + } + + searchByColumn(field) { const filters = this.filterSanitizer(field); if (filters && filters.userFilter) this.model.userFilter = filters.userFilter; - if (!emptySearch) - this.addFilter(field, this.$inputsScope.searchProps[field]); - else this.model.refresh(); + this.addFilter(field, this.$inputsScope.searchProps[field]); } addFilter(field, value) { - let where = {[field]: value}; + if (value == '') value = null; - if (this.exprBuilder) { - where = buildFilter(where, (param, value) => - this.exprBuilder({param, value}) - ); + let filterState; + if (this.$params.q) { + filterState = JSON.parse(this.$params.q); + delete filterState.tableQ[field]; } - this.model.addFilter({where}); + const whereParams = {[field]: value}; + if (value) { + let where = {[field]: value}; + if (this.exprBuilder) { + where = buildFilter(whereParams, (param, value) => + this.exprBuilder({param, value}) + ); + } + this.model.addFilter({where}); + } + + const searchProps = this.searchPropsSanitizer(); + + if (filterState.tableQ) + Object.assign(searchProps, filterState.tableQ); + + Object.assign(filterState.tableQ, searchProps); + + const params = {q: JSON.stringify(filterState)}; + + this.$state.go(this.$state.current.name, params, {location: 'replace'}); + this.model.refresh(); } applySort() { @@ -517,6 +571,10 @@ export default class SmartTable extends Component { this.model.refresh() .then(() => this.isRefreshing = false); } + + test() { + console.log('USER_FILTER', this.model.userFilter, 'USER_PARAMS', this.model.userParams); + } } SmartTable.$inject = ['$element', '$scope', '$transclude']; From 4df99a82e1a9f7cce8602fd07bbef9cdb99c6d4f Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 28 Sep 2022 11:22:29 +0200 Subject: [PATCH 04/77] feat(tableOrder): save order in url --- e2e/paths/04-item/01_summary.spec.js | 2 +- front/core/components/searchbar/searchbar.js | 15 ++++---- front/core/components/smart-table/index.js | 36 +++++++++++++++----- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/e2e/paths/04-item/01_summary.spec.js b/e2e/paths/04-item/01_summary.spec.js index e24fa6a9f..373ceb95a 100644 --- a/e2e/paths/04-item/01_summary.spec.js +++ b/e2e/paths/04-item/01_summary.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -describe('Item summary path', () => { +fdescribe('Item summary path', () => { let browser; let page; beforeAll(async() => { diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index fc194bc59..50f9d0050 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -167,7 +167,7 @@ export default class Searchbar extends Component { let keys = Object.keys(filter); keys.forEach(key => { - if (key == 'search' || key == 'tableQ') return; + if (key == 'search' || key == 'tableQ' || key == 'tableOrder') return; let value = filter[key]; let chip; @@ -281,7 +281,6 @@ export default class Searchbar extends Component { this.model.clear(); return; } - if (Object.keys(filter).length === 0) { this.filterSanitizer('search'); if (this.model.userParams) @@ -301,10 +300,12 @@ export default class Searchbar extends Component { params = this.fetchParams({$params: params}); } - if (this.$params.q) + if (this.$params.q) { Object.assign(params, JSON.parse(this.$params.q)); - - return this.model.addFilter(where ? {where} : null, params) + return this.model.addFilter(where ? {where} : null, params) + .then(() => this.model.data); + } + return this.model.applyFilter(where ? {where} : null, params) .then(() => this.model.data); } @@ -312,7 +313,9 @@ export default class Searchbar extends Component { const userFilter = this.model.userFilter; const userParams = this.model.userParams; const where = userFilter && userFilter.where; - delete this.$params.q[field]; + + if (this.$params.q) + delete this.$params.q[field]; if (this.exprBuilder) { const param = this.exprBuilder({ diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index 7880f257f..c78cbbca0 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -188,7 +188,12 @@ export default class SmartTable extends Component { } defaultOrder() { - const order = this.model.order; + let stateOrder; + if (this.$params.q) + stateOrder = JSON.parse(this.$params.q).tableOrder; + + const order = stateOrder ? stateOrder : this.model.order; + if (!order) return; const orderFields = order.split(', '); @@ -222,6 +227,9 @@ export default class SmartTable extends Component { this.setPriority(column.element, priority); } } + + this.model.order = order; + this.model.refresh(); } registerColumns() { @@ -443,10 +451,12 @@ export default class SmartTable extends Component { addFilter(field, value) { if (value == '') value = null; - let filterState; + let stateFilter = {tableQ: {}}; if (this.$params.q) { - filterState = JSON.parse(this.$params.q); - delete filterState.tableQ[field]; + stateFilter = JSON.parse(this.$params.q); + if (!stateFilter.tableQ) + stateFilter.tableQ = {}; + delete stateFilter.tableQ[field]; } const whereParams = {[field]: value}; @@ -462,12 +472,9 @@ export default class SmartTable extends Component { const searchProps = this.searchPropsSanitizer(); - if (filterState.tableQ) - Object.assign(searchProps, filterState.tableQ); + Object.assign(stateFilter.tableQ, searchProps); - Object.assign(filterState.tableQ, searchProps); - - const params = {q: JSON.stringify(filterState)}; + const params = {q: JSON.stringify(stateFilter)}; this.$state.go(this.$state.current.name, params, {location: 'replace'}); this.model.refresh(); @@ -480,6 +487,17 @@ export default class SmartTable extends Component { if (order) this.model.order = order; + let stateFilter = {tableOrder: {}}; + if (this.$params.q) { + stateFilter = JSON.parse(this.$params.q); + if (!stateFilter.tableOrder) + stateFilter.tableOrder = {}; + } + + stateFilter.tableOrder = order; + + const params = {q: JSON.stringify(stateFilter)}; + this.$state.go(this.$state.current.name, params, {location: 'replace'}); this.model.refresh(); } From 84003ab09cff510def09ab0100ada39433a9af28 Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 29 Sep 2022 12:36:16 +0200 Subject: [PATCH 05/77] fix(searchbar): smart-table.tableQ integration --- e2e/helpers/selectors.js | 11 ++- ..._smartTable_searchBar_integrations.spec.js | 77 +++++++++++++++++++ e2e/paths/04-item/01_summary.spec.js | 2 +- .../core/components/crud-model/crud-model.js | 12 +++ front/core/components/searchbar/searchbar.js | 59 ++++++++++++-- .../components/searchbar/searchbar.spec.js | 7 +- 6 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 0ad9ad7f4..c8e0ae168 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -392,10 +392,16 @@ export default { originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]', buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]', densityCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Density"]', - saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button' + saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button', + openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]', + advancedSearchItemType: 'vn-item-search-panel vn-autocomplete[ng-model="filter.typeFk"]', + advancedSearchButton: 'vn-item-search-panel button[type=submit]', + advancedSmartTableButton: 'vn-item-index vn-button[icon="search"]', + advancedSmartTableGrouping: 'vn-item-index vn-textfield[name=grouping]', }, itemFixedPrice: { add: 'vn-fixed-price vn-icon-button[icon="add_circle"]', + firstItemID: 'vn-fixed-price tr:nth-child(2) vn-autocomplete[ng-model="price.itemFk"]', fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)', fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]', fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]', @@ -405,7 +411,8 @@ export default { fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]', fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]', fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]', - fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]' + fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]', + orderColumnId: 'vn-fixed-price th[field="itemFk"]' }, itemCreateView: { temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]', diff --git a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js new file mode 100644 index 000000000..4fc280209 --- /dev/null +++ b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js @@ -0,0 +1,77 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('SmartTable SearchBar integration', () => { + let browser; + let page; + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('salesPerson', 'item'); + await page.waitToClick(selectors.globalItems.searchButton); + }); + + afterAll(async() => { + await browser.close(); + }); + + describe('as filters', () => { + it('should search by type in searchBar', async() => { + await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton); + await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium'); + await page.waitToClick(selectors.itemsIndex.advancedSearchButton); + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); + }); + + it('should reload page and have same results', async() => { + await page.reload({ + waitUntil: 'networkidle2' + }); + + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); + }); + + it('should search by grouping in smartTable', async() => { + await page.waitToClick(selectors.itemsIndex.advancedSmartTableButton); + await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1'); + await page.keyboard.press('Enter'); + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); + }); + + it('should now reload page and have same results', async() => { + await page.reload({ + waitUntil: 'networkidle2' + }); + + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); + }); + }); + + describe('as orders', () => { + it('should order by first id', async() => { + await page.loginAndModule('developer', 'item'); + await page.accessToSection('item.fixedPrice'); + await page.doSearch(); + + const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); + + expect(result).toEqual('1'); + }); + + it('should order by last id', async() => { + await page.waitToClick(selectors.itemFixedPrice.orderColumnId); + const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); + + expect(result).toEqual('13'); + }); + + it('should reload page and have same order', async() => { + await page.reload({ + waitUntil: 'networkidle2' + }); + const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); + + expect(result).toEqual('13'); + }); + }); +}); diff --git a/e2e/paths/04-item/01_summary.spec.js b/e2e/paths/04-item/01_summary.spec.js index 373ceb95a..e24fa6a9f 100644 --- a/e2e/paths/04-item/01_summary.spec.js +++ b/e2e/paths/04-item/01_summary.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -fdescribe('Item summary path', () => { +describe('Item summary path', () => { let browser; let page; beforeAll(async() => { diff --git a/front/core/components/crud-model/crud-model.js b/front/core/components/crud-model/crud-model.js index 4994e1547..1095985dc 100644 --- a/front/core/components/crud-model/crud-model.js +++ b/front/core/components/crud-model/crud-model.js @@ -99,6 +99,18 @@ export default class CrudModel extends ModelProxy { return this.refresh(); } + /** + * Applies a new filter to the model. + * + * @param {Object} params Custom parameters + * @return {Promise} The request promise + */ + + applyParams(params) { + this.userParams = params; + return this.refresh(); + } + removeFilter() { return this.applyFilter(null, null); } diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index 50f9d0050..6e74a3779 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -199,7 +199,7 @@ export default class Searchbar extends Component { doSearch(filter, source) { if (filter === this.filter && source != 'state') return; - let promise = this.onSearch({$params: filter}, source); + let promise = this.onSearch({$params: filter}); promise = promise || this.$q.resolve(); promise.then(data => this.onFilter(filter, source, data)); this.toBar(filter); @@ -243,8 +243,11 @@ export default class Searchbar extends Component { } else { state = this.searchState; - if (filter) + if (filter) { + if (this.tableQ) + filter.tableQ = this.tableQ; params = {q: JSON.stringify(filter)}; + } if (this.$state.is(state)) opts = {location: 'replace'}; } @@ -253,6 +256,7 @@ export default class Searchbar extends Component { this.filter = filter; if (source == 'removeBar') { + console.log(params); delete params[this.toRemove]; delete this.model.userParams[this.toRemove]; this.model.refresh(); @@ -260,7 +264,6 @@ export default class Searchbar extends Component { if (!filter && this.model) this.model.clear(); - if (source != 'state') this.transition = this.$state.go(state, params, opts).transition; if (source != 'bar' && (source != 'state' || this.$state.is(this.baseState))) @@ -296,26 +299,66 @@ export default class Searchbar extends Component { } else { params = Object.assign({}, filter); + console.log('pre', params); if (this.fetchParams) params = this.fetchParams({$params: params}); + console.log('post', params); } - if (this.$params.q) { - Object.assign(params, JSON.parse(this.$params.q)); - return this.model.addFilter(where ? {where} : null, params) + /* console.log(params); + const paramsKeys = Object.keys(params); + const suggestedKeys = Object.keys(this.suggestedFilter); + if (params != this.suggestedFilter) { + for (let suggested in this.suggestedFilter) + delete this.model.userParams[suggested]; + }*/ + console.log('this.fromBar()', this.fromBar()); + console.log('this.$params.q', this.$params.q); + console.log('param', params); + console.log('userParams', this.model.userParams); + console.log('userFilter', this.model.userFilter); + console.log('suggestedFilter', this.suggestedFilter); + console.log('fetch-params', this.fetchParams); + /* if (this.fromBar()) { + for (let param in params) + delete this.model.userParams[param]; + }*/ + this.tableQ = null; + if (this.$params.q && Object.keys(JSON.parse(this.$params.q)).length) { + const stateFilter = JSON.parse(this.$params.q); + for (let param in stateFilter) { + if (param != 'tableQ' && param != 'orderQ') + this.filterSanitizer(param); + } + + for (let param in this.suggestedFilter) { + this.filterSanitizer(param); + delete stateFilter[param]; + } + + this.tableQ = stateFilter.tableQ; + for (let param in stateFilter.tableQ) + params[param] = stateFilter.tableQ[param]; + + Object.assign(stateFilter, params); + console.log('PRE FINAL PARAMS: ', params); + return this.model.applyParams(params) .then(() => this.model.data); } + console.log('FINAL PARAMS: ', params); + return this.model.applyFilter(where ? {where} : null, params) .then(() => this.model.data); } filterSanitizer(field) { + if (!field) return; const userFilter = this.model.userFilter; const userParams = this.model.userParams; const where = userFilter && userFilter.where; - if (this.$params.q) - delete this.$params.q[field]; + if (this.model.userParams) + delete this.model.userParams[field]; if (this.exprBuilder) { const param = this.exprBuilder({ diff --git a/front/core/components/searchbar/searchbar.spec.js b/front/core/components/searchbar/searchbar.spec.js index e4f58d294..efa98818d 100644 --- a/front/core/components/searchbar/searchbar.spec.js +++ b/front/core/components/searchbar/searchbar.spec.js @@ -172,13 +172,18 @@ describe('Component vnSearchbar', () => { describe('removeParam()', () => { it(`should remove the parameter from the filter`, () => { jest.spyOn(controller, 'doSearch'); + controller.model = { + applyParams: () => { + return new Promise(resolve => resolve()); + } + }; controller.filter = filter; controller.removeParam(0); expect(controller.doSearch).toHaveBeenCalledWith({ search: 'needle' - }, 'bar'); + }, 'removeBar'); }); }); From 94c143bc4ceca4b82f46284971eac172f1f6966d Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 29 Sep 2022 14:28:50 +0200 Subject: [PATCH 06/77] test(searchbar_smart-table): fix for integration --- front/core/components/searchbar/searchbar.js | 23 --- .../components/searchbar/searchbar.spec.js | 26 +-- front/core/components/smart-table/index.html | 5 +- front/core/components/smart-table/index.js | 26 ++- .../core/components/smart-table/index.spec.js | 159 ++++++++++++------ 5 files changed, 131 insertions(+), 108 deletions(-) diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index 6e74a3779..57a77d980 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -256,7 +256,6 @@ export default class Searchbar extends Component { this.filter = filter; if (source == 'removeBar') { - console.log(params); delete params[this.toRemove]; delete this.model.userParams[this.toRemove]; this.model.refresh(); @@ -299,30 +298,10 @@ export default class Searchbar extends Component { } else { params = Object.assign({}, filter); - console.log('pre', params); if (this.fetchParams) params = this.fetchParams({$params: params}); - console.log('post', params); } - /* console.log(params); - const paramsKeys = Object.keys(params); - const suggestedKeys = Object.keys(this.suggestedFilter); - if (params != this.suggestedFilter) { - for (let suggested in this.suggestedFilter) - delete this.model.userParams[suggested]; - }*/ - console.log('this.fromBar()', this.fromBar()); - console.log('this.$params.q', this.$params.q); - console.log('param', params); - console.log('userParams', this.model.userParams); - console.log('userFilter', this.model.userFilter); - console.log('suggestedFilter', this.suggestedFilter); - console.log('fetch-params', this.fetchParams); - /* if (this.fromBar()) { - for (let param in params) - delete this.model.userParams[param]; - }*/ this.tableQ = null; if (this.$params.q && Object.keys(JSON.parse(this.$params.q)).length) { const stateFilter = JSON.parse(this.$params.q); @@ -341,11 +320,9 @@ export default class Searchbar extends Component { params[param] = stateFilter.tableQ[param]; Object.assign(stateFilter, params); - console.log('PRE FINAL PARAMS: ', params); return this.model.applyParams(params) .then(() => this.model.data); } - console.log('FINAL PARAMS: ', params); return this.model.applyFilter(where ? {where} : null, params) .then(() => this.model.data); diff --git a/front/core/components/searchbar/searchbar.spec.js b/front/core/components/searchbar/searchbar.spec.js index efa98818d..ed8fd9d07 100644 --- a/front/core/components/searchbar/searchbar.spec.js +++ b/front/core/components/searchbar/searchbar.spec.js @@ -6,7 +6,7 @@ describe('Component vnSearchbar', () => { let $state; let $params; let $scope; - let filter = {id: 1, search: 'needle'}; + const filter = {id: 1, search: 'needle'}; beforeEach(ngModule('vnCore', $stateProvider => { $stateProvider @@ -70,8 +70,8 @@ describe('Component vnSearchbar', () => { describe('filter() setter', () => { it(`should update the bar params and search`, () => { - let withoutHours = new Date(2000, 1, 1); - let withHours = new Date(withoutHours.getTime()); + const withoutHours = new Date(2000, 1, 1); + const withHours = new Date(withoutHours.getTime()); withHours.setHours(12, 30, 15, 10); controller.filter = { @@ -83,8 +83,8 @@ describe('Component vnSearchbar', () => { myObjectProp: {myProp: 1} }; - let chips = {}; - for (let param of controller.params || []) + const chips = {}; + for (const param of controller.params || []) chips[param.key] = param.chip; expect(controller.searchString).toBe('needle'); @@ -173,11 +173,15 @@ describe('Component vnSearchbar', () => { it(`should remove the parameter from the filter`, () => { jest.spyOn(controller, 'doSearch'); controller.model = { - applyParams: () => { - return new Promise(resolve => resolve()); + refresh: jest.fn(), + userParams: { + id: 1 } }; + controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve()); + jest.spyOn(controller.model, 'applyParams'); + controller.filter = filter; controller.removeParam(0); @@ -204,7 +208,7 @@ describe('Component vnSearchbar', () => { it(`should go to the summary state when one result`, () => { jest.spyOn($state, 'go'); - let data = [{id: 1}]; + const data = [{id: 1}]; controller.baseState = 'foo'; controller.onFilter(filter, 'any', data); @@ -219,7 +223,7 @@ describe('Component vnSearchbar', () => { $scope.$apply(); jest.spyOn($state, 'go'); - let data = [{id: 1}]; + const data = [{id: 1}]; controller.baseState = 'foo'; controller.onFilter(filter, 'any', data); @@ -234,7 +238,7 @@ describe('Component vnSearchbar', () => { $scope.$apply(); jest.spyOn($state, 'go'); - let data = [{id: 1}]; + const data = [{id: 1}]; controller.baseState = 'foo'; controller.onFilter(filter, 'any', data); @@ -252,7 +256,7 @@ describe('Component vnSearchbar', () => { controller.onFilter(filter, 'any'); $scope.$apply(); - let queryParams = {q: JSON.stringify(filter)}; + const queryParams = {q: JSON.stringify(filter)}; expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined); expect(controller.filter).toEqual(filter); diff --git a/front/core/components/smart-table/index.html b/front/core/components/smart-table/index.html index 3168d7ee7..752019313 100644 --- a/front/core/components/smart-table/index.html +++ b/front/core/components/smart-table/index.html @@ -103,7 +103,4 @@ - - + diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index c78cbbca0..31541143c 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -229,7 +229,7 @@ export default class SmartTable extends Component { } this.model.order = order; - this.model.refresh(); + this.refresh(); } registerColumns() { @@ -429,6 +429,14 @@ export default class SmartTable extends Component { this.searchByColumn(field); } + searchByColumn(field) { + const filters = this.filterSanitizer(field); + + if (filters && filters.userFilter) + this.model.userFilter = filters.userFilter; + this.addFilter(field, this.$inputsScope.searchProps[field]); + } + searchPropsSanitizer() { if (!this.$inputsScope || !this.$inputsScope.searchProps) return null; let searchProps = this.$inputsScope.searchProps; @@ -440,14 +448,6 @@ export default class SmartTable extends Component { return Object.fromEntries(searchProps); } - searchByColumn(field) { - const filters = this.filterSanitizer(field); - - if (filters && filters.userFilter) - this.model.userFilter = filters.userFilter; - this.addFilter(field, this.$inputsScope.searchProps[field]); - } - addFilter(field, value) { if (value == '') value = null; @@ -477,7 +477,7 @@ export default class SmartTable extends Component { const params = {q: JSON.stringify(stateFilter)}; this.$state.go(this.$state.current.name, params, {location: 'replace'}); - this.model.refresh(); + this.refresh(); } applySort() { @@ -498,7 +498,7 @@ export default class SmartTable extends Component { const params = {q: JSON.stringify(stateFilter)}; this.$state.go(this.$state.current.name, params, {location: 'replace'}); - this.model.refresh(); + this.refresh(); } filterSanitizer(field) { @@ -589,10 +589,6 @@ export default class SmartTable extends Component { this.model.refresh() .then(() => this.isRefreshing = false); } - - test() { - console.log('USER_FILTER', this.model.userFilter, 'USER_PARAMS', this.model.userParams); - } } SmartTable.$inject = ['$element', '$scope', '$transclude']; diff --git a/front/core/components/smart-table/index.spec.js b/front/core/components/smart-table/index.spec.js index 720e24c7e..5fd4c33b7 100644 --- a/front/core/components/smart-table/index.spec.js +++ b/front/core/components/smart-table/index.spec.js @@ -9,6 +9,11 @@ describe('Component smartTable', () => { $httpBackend = _$httpBackend_; $element = $compile(``)($rootScope); controller = $element.controller('smartTable'); + controller.model = { + refresh: jest.fn().mockReturnValue(new Promise(resolve => resolve())), + addFilter: jest.fn(), + userParams: {} + }; })); afterEach(() => { @@ -83,7 +88,7 @@ describe('Component smartTable', () => { describe('defaultOrder', () => { it('should insert a new object to the controller sortCriteria with a sortType value of "ASC"', () => { const element = document.createElement('div'); - controller.model = {order: 'id'}; + controller.model.order = 'id'; controller.columns = [ {field: 'id', element: element}, {field: 'test1', element: element}, @@ -101,7 +106,8 @@ describe('Component smartTable', () => { it('should add new entries to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => { const element = document.createElement('div'); - controller.model = {order: 'test1, id DESC'}; + controller.model.order = 'test1, id DESC'; + controller.columns = [ {field: 'id', element: element}, {field: 'test1', element: element}, @@ -125,8 +131,6 @@ describe('Component smartTable', () => { describe('addFilter()', () => { it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => { - controller.model = {addFilter: jest.fn()}; - controller.addFilter('myField', 'myValue'); const expectedFilter = { @@ -140,7 +144,6 @@ describe('Component smartTable', () => { it('should call the model addFilter() with a built where filter resultant of exprBuilder()', () => { controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'}); - controller.model = {addFilter: jest.fn()}; controller.addFilter('myField', 'myValue'); @@ -155,35 +158,48 @@ describe('Component smartTable', () => { }); describe('applySort()', () => { - it('should call the model refresh() without making changes on the model order', () => { - controller.model = {refresh: jest.fn()}; + it('should call the $state go and model refresh without making changes on the model order', () => { + controller.$state = { + go: jest.fn(), + current: { + name: 'section' + } + }; + jest.spyOn(controller, 'refresh'); controller.applySort(); expect(controller.model.order).toBeUndefined(); - expect(controller.model.refresh).toHaveBeenCalled(); + expect(controller.$state.go).toHaveBeenCalled(); + expect(controller.refresh).toHaveBeenCalled(); }); - it('should call the model.refresh() after setting model order according to the controller sortCriteria', () => { - controller.model = {refresh: jest.fn()}; + it('should call the $state go and model refresh after setting model order according to the controller sortCriteria', () => { const orderBy = {field: 'myField', sortType: 'ASC'}; + controller.$state = { + go: jest.fn(), + current: { + name: 'section' + } + }; + jest.spyOn(controller, 'refresh'); + controller.sortCriteria = [orderBy]; controller.applySort(); expect(controller.model.order).toEqual(`${orderBy.field} ${orderBy.sortType}`); - expect(controller.model.refresh).toHaveBeenCalled(); + expect(controller.$state.go).toHaveBeenCalled(); + expect(controller.refresh).toHaveBeenCalled(); }); }); describe('filterSanitizer()', () => { it('should remove the where filter after leaving no fields in it', () => { - controller.model = { - userFilter: { - where: {fieldToRemove: 'valueToRemove'} - }, - userParams: {} + controller.model.userFilter = { + where: {fieldToRemove: 'valueToRemove'} }; + controller.model.userParams = {}; const result = controller.filterSanitizer('fieldToRemove'); @@ -193,23 +209,21 @@ describe('Component smartTable', () => { }); it('should remove the where filter after leaving no fields and "empty ands/ors" in it', () => { - controller.model = { - userFilter: { - where: { - and: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - { - or: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - ] - } - ] - } - }, - userParams: {} - }; + controller.model.userFilter = { + where: { + and: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + { + or: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + ] + } + ] + } + }, + controller.model.userParams = {}; const result = controller.filterSanitizer('aFieldToRemove'); @@ -219,24 +233,22 @@ describe('Component smartTable', () => { }); it('should not remove the where filter after leaving no empty "ands/ors" in it', () => { - controller.model = { - userFilter: { - where: { - and: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - { - or: [ - {aFieldToRemove: 'aValueToRemove'}, - {aFieldToRemove: 'aValueToRemove'}, - ] - } - ], - or: [{dontKillMe: 'thanks'}] - } - }, - userParams: {} + controller.model.userFilter = { + where: { + and: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + { + or: [ + {aFieldToRemove: 'aValueToRemove'}, + {aFieldToRemove: 'aValueToRemove'}, + ] + } + ], + or: [{dontKillMe: 'thanks'}] + } }; + controller.model.userParams = {}; const result = controller.filterSanitizer('aFieldToRemove'); @@ -249,7 +261,7 @@ describe('Component smartTable', () => { describe('saveAll()', () => { it('should throw an error if there are no changes to save in the model', () => { jest.spyOn(controller.vnApp, 'showError'); - controller.model = {isChanged: false}; + controller.model.isChanged = false; controller.saveAll(); expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save'); @@ -258,10 +270,8 @@ describe('Component smartTable', () => { it('should call the showSuccess() if there are changes to save in the model', done => { jest.spyOn(controller.vnApp, 'showSuccess'); - controller.model = { - save: jest.fn().mockReturnValue(Promise.resolve()), - isChanged: true - }; + controller.model.save = jest.fn().mockReturnValue(Promise.resolve()); + controller.model.isChanged = true; controller.saveAll().then(() => { expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!'); @@ -269,4 +279,43 @@ describe('Component smartTable', () => { }).catch(done.fail); }); }); + + describe('defaultFilter()', () => { + it('should call model refresh and model addFilter with filter', () => { + controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'}); + + controller.$params = { + q: '{"tableQ": {"fieldName":"value"}}' + }; + controller.columns = [ + {field: 'fieldName'} + ]; + controller.$inputsScope = { + searchProps: {} + }; + jest.spyOn(controller, 'refresh'); + + controller.defaultFilter(); + + expect(controller.model.addFilter).toHaveBeenCalled(); + expect(controller.refresh).toHaveBeenCalled(); + }); + }); + + describe('searchPropsSanitizer()', () => { + it('should searchProps sanitize', () => { + controller.$inputsScope = { + searchProps: { + filterOne: '1', + filterTwo: '' + } + }; + const searchPropsExpected = { + filterOne: '1' + }; + const newSearchProps = controller.searchPropsSanitizer(); + + expect(newSearchProps).toEqual(searchPropsExpected); + }); + }); }); From 11484edd105c0c57210b523c89f246a55bb1f17d Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 3 Oct 2022 15:18:07 +0200 Subject: [PATCH 07/77] feat: create new ticket --- modules/ticket/front/expedition/index.html | 22 +++++++++++++++++++--- modules/ticket/front/expedition/index.js | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index 3a5d5f8a6..694b3eb33 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -138,12 +138,28 @@ + ng-click="$ctrl.createTicket()"> New ticket without route + ng-click="selectRoute.show()"> New ticket with route - \ No newline at end of file + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index 38d02f548..d80c8e812 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -26,6 +26,23 @@ class Controller extends Section { get totalChecked() { return this.checked.length; } + + createTicket(routeFk) { + const tomorrow = new Date(); + const params = { + clientId: this.ticket.clientFk, + landed: tomorrow.getDay() + 1, + addressId: this.ticket.addressFk, + agencyModeId: this.ticket.agencyModeFk, + warehouseId: this.ticket.warehouseFk + }; + const query = `Tickets/new`; + this.$http.post(query, params).then(res => { + if (routeFk) this.$http.patch(`Tickets/${res.data.id}`, {routeFk: routeFk}); + this.vnApp.showSuccess(this.$t('Data saved!')); + this.$state.go('ticket.card.summary', {id: res.data.id}); + }); + } } ngModule.vnComponent('vnTicketExpedition', { From 2f17257e46096cdb9049d276df9b1c29c7d3071b Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 5 Oct 2022 15:13:48 +0200 Subject: [PATCH 08/77] feat: add new sub section itemShelving --- front/salix/locale/es.yml | 1 + modules/item/back/model-config.json | 3 + .../item-shelving-placement-supply.json | 29 +++ modules/item/front/index.js | 2 + modules/item/front/item-shelving/index.html | 172 ++++++++++++++++++ modules/item/front/item-shelving/index.js | 132 ++++++++++++++ .../item/front/item-shelving/index.spec.js | 121 ++++++++++++ .../item/front/item-shelving/locale/es.yml | 9 + modules/item/front/locale/es.yml | 1 + modules/item/front/routes.json | 13 +- modules/shelving/front/routes.json | 2 +- 11 files changed, 483 insertions(+), 2 deletions(-) create mode 100644 modules/item/back/models/item-shelving-placement-supply.json create mode 100644 modules/item/front/item-shelving/index.html create mode 100644 modules/item/front/item-shelving/index.js create mode 100644 modules/item/front/item-shelving/index.spec.js create mode 100644 modules/item/front/item-shelving/locale/es.yml diff --git a/front/salix/locale/es.yml b/front/salix/locale/es.yml index e5dc82b16..d92c32b33 100644 --- a/front/salix/locale/es.yml +++ b/front/salix/locale/es.yml @@ -51,6 +51,7 @@ Entries: Entradas Users: Usuarios Suppliers: Proveedores Monitors: Monitores +Shelvings: Carros # Common diff --git a/modules/item/back/model-config.json b/modules/item/back/model-config.json index 9737d26fc..40d73f1a6 100644 --- a/modules/item/back/model-config.json +++ b/modules/item/back/model-config.json @@ -53,6 +53,9 @@ "ItemShelvingSale": { "dataSource": "vn" }, + "ItemShelvingPlacementSupplyStock": { + "dataSource": "vn" + }, "ItemImageQueue": { "dataSource": "vn" }, diff --git a/modules/item/back/models/item-shelving-placement-supply.json b/modules/item/back/models/item-shelving-placement-supply.json new file mode 100644 index 000000000..11497b4fc --- /dev/null +++ b/modules/item/back/models/item-shelving-placement-supply.json @@ -0,0 +1,29 @@ +{ + "name": "ItemShelvingPlacementSupplyStock", + "base": "VnModel", + "options": { + "mysql": { + "table": "itemShelvingPlacementSupplyStock" + } + }, + "properties": { + "created": { + "type": "date" + }, + "itemFk": { + "type": "number" + }, + "concept": { + "type": "string" + }, + "parking": { + "type": "string" + }, + "shelving": { + "type": "string" + }, + "packing": { + "type": "number" + } + } +} \ No newline at end of file diff --git a/modules/item/front/index.js b/modules/item/front/index.js index 6a8d1b3b7..d2ffcc8fb 100644 --- a/modules/item/front/index.js +++ b/modules/item/front/index.js @@ -24,3 +24,5 @@ import './waste/detail'; import './fixed-price'; import './fixed-price-search-panel'; import './item-type'; +import './item-shelving'; + diff --git a/modules/item/front/item-shelving/index.html b/modules/item/front/item-shelving/index.html new file mode 100644 index 000000000..7949e73a3 --- /dev/null +++ b/modules/item/front/item-shelving/index.html @@ -0,0 +1,172 @@ + + + + + + + + + +
+
+
Total
+ + +
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Created + + Item + + Concept + + Parking + + Shelving + + Etiqueta + + Packing +
+ + + + + {{::defaulter.clientName}} + + + + {{::defaulter.salesPersonName | dashIfEmpty}} + + {{::defaulter.amount | currency: 'EUR': 2}} + + {{::defaulter.workerName | dashIfEmpty}} + + + + + + + {{::defaulter.created | date: 'dd/MM/yyyy'}} + + {{::defaulter.creditInsurance | currency: 'EUR': 2}}{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}
+
+
+
+ + + + + + + + + + + + +
+
{{$ctrl.$t('Add observation to all selected clients', {total: $ctrl.checked.length})}}
+ + + + +
+
+ + + + +
diff --git a/modules/item/front/item-shelving/index.js b/modules/item/front/item-shelving/index.js new file mode 100644 index 000000000..f51a1a65b --- /dev/null +++ b/modules/item/front/item-shelving/index.js @@ -0,0 +1,132 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; +import UserError from 'core/lib/user-error'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.defaulter = {}; + + this.smartTableOptions = { + activeButtons: { + search: true + }, + columns: [ + { + field: 'clientFk', + autocomplete: { + url: 'Clients', + showField: 'name', + valueField: 'id' + } + }, + { + field: 'salesPersonFk', + autocomplete: { + url: 'Workers/activeWithInheritedRole', + where: `{role: 'salesPerson'}`, + searchFunction: '{firstName: $search}', + showField: 'name', + valueField: 'id', + } + }, + { + field: 'workerFk', + autocomplete: { + url: 'Workers/activeWithInheritedRole', + searchFunction: '{firstName: $search}', + showField: 'name', + valueField: 'id', + } + }, + { + field: 'observation', + searchable: false + }, + { + field: 'created', + searchable: false + }, + { + field: 'defaulterSinced', + searchable: false + } + ] + }; + + this.getBalanceDueTotal(); + } + + get checked() { + const clients = this.$.model.data || []; + const checkedLines = []; + for (let defaulter of clients) { + if (defaulter.checked) + checkedLines.push(defaulter); + } + + return checkedLines; + } + + getBalanceDueTotal() { + this.$http.get('Defaulters/filter') + .then(res => { + if (!res.data) return 0; + + this.balanceDueTotal = res.data.reduce( + (accumulator, currentValue) => { + return accumulator + (currentValue['amount'] || 0); + }, 0); + }); + } + + chipColor(date) { + const day = 24 * 60 * 60 * 1000; + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const observationShipped = new Date(date); + observationShipped.setHours(0, 0, 0, 0); + + const difference = today - observationShipped; + + if (difference > (day * 20)) + return 'alert'; + if (difference > (day * 10)) + return 'warning'; + } + + onResponse() { + if (!this.defaulter.observation) + throw new UserError(`The message can't be empty`); + + const params = []; + for (let defaulter of this.checked) { + params.push({ + text: this.defaulter.observation, + clientFk: defaulter.clientFk + }); + } + + this.$http.post(`ClientObservations`, params) .then(() => { + this.vnApp.showMessage(this.$t('Observation saved!')); + this.$state.reload(); + }); + } + + exprBuilder(param, value) { + switch (param) { + case 'creditInsurance': + case 'amount': + case 'clientFk': + case 'workerFk': + case 'salesPersonFk': + return {[`d.${param}`]: value}; + } + } +} + +ngModule.vnComponent('vnItemShelving', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/item/front/item-shelving/index.spec.js b/modules/item/front/item-shelving/index.spec.js new file mode 100644 index 000000000..0732c68a1 --- /dev/null +++ b/modules/item/front/item-shelving/index.spec.js @@ -0,0 +1,121 @@ +import './index'; +import crudModel from 'core/mocks/crud-model'; + +describe('client defaulter', () => { + describe('Component vnClientDefaulter', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('client')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + const $element = angular.element(''); + controller = $componentController('vnClientDefaulter', {$element}); + controller.$.model = crudModel; + controller.$.model.data = [ + {clientFk: 1101, amount: 125}, + {clientFk: 1102, amount: 500}, + {clientFk: 1103, amount: 250} + ]; + })); + + describe('checked() getter', () => { + it('should return the checked lines', () => { + const data = controller.$.model.data; + data[1].checked = true; + data[2].checked = true; + + const checkedRows = controller.checked; + + const firstCheckedRow = checkedRows[0]; + const secondCheckedRow = checkedRows[1]; + + expect(firstCheckedRow.clientFk).toEqual(1102); + expect(secondCheckedRow.clientFk).toEqual(1103); + }); + }); + + describe('chipColor()', () => { + it('should return undefined when the date is the present', () => { + let today = new Date(); + let result = controller.chipColor(today); + + expect(result).toEqual(undefined); + }); + + it('should return warning when the date is 10 days in the past', () => { + let pastDate = new Date(); + pastDate = pastDate.setDate(pastDate.getDate() - 11); + let result = controller.chipColor(pastDate); + + expect(result).toEqual('warning'); + }); + + it('should return alert when the date is 20 days in the past', () => { + let pastDate = new Date(); + pastDate = pastDate.setDate(pastDate.getDate() - 21); + let result = controller.chipColor(pastDate); + + expect(result).toEqual('alert'); + }); + }); + + describe('onResponse()', () => { + it('should return error for empty message', () => { + let error; + try { + controller.onResponse(); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.message).toBe(`The message can't be empty`); + }); + + it('should return saved message', () => { + const data = controller.$.model.data; + data[1].checked = true; + controller.defaulter = {observation: 'My new observation'}; + + const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}]; + + jest.spyOn(controller.vnApp, 'showMessage'); + $httpBackend.expect('GET', `Defaulters/filter`).respond(200); + $httpBackend.expect('POST', `ClientObservations`, params).respond(200, params); + + controller.onResponse(); + $httpBackend.flush(); + + expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Observation saved!'); + }); + }); + + describe('exprBuilder()', () => { + it('should search by sales person', () => { + const expr = controller.exprBuilder('salesPersonFk', '5'); + + expect(expr).toEqual({'d.salesPersonFk': '5'}); + }); + + it('should search by client', () => { + const expr = controller.exprBuilder('clientFk', '5'); + + expect(expr).toEqual({'d.clientFk': '5'}); + }); + }); + + describe('getBalanceDueTotal()', () => { + it('should return balance due total', () => { + const defaulters = controller.$.model.data; + $httpBackend.when('GET', `Defaulters/filter`).respond(defaulters); + + controller.getBalanceDueTotal(); + $httpBackend.flush(); + + expect(controller.balanceDueTotal).toEqual(875); + }); + }); + }); +}); diff --git a/modules/item/front/item-shelving/locale/es.yml b/modules/item/front/item-shelving/locale/es.yml new file mode 100644 index 000000000..c3e1d4e19 --- /dev/null +++ b/modules/item/front/item-shelving/locale/es.yml @@ -0,0 +1,9 @@ +Add observation: Añadir observación +Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s) +Balance D.: Saldo V. +Credit I.: Crédito A. +Last observation: Última observación +L. O. Date: Fecha Ú. O. +Last observation date: Fecha última observación +Search client: Buscar clientes +Worker who made the last observation: Trabajador que ha realizado la última observación \ No newline at end of file diff --git a/modules/item/front/locale/es.yml b/modules/item/front/locale/es.yml index 1b75e3802..88ab031e1 100644 --- a/modules/item/front/locale/es.yml +++ b/modules/item/front/locale/es.yml @@ -54,6 +54,7 @@ Basic data: Datos básicos Tax: IVA History: Historial Botanical: Botánico +Shelvings: Carros Barcodes: Códigos de barras Diary: Histórico Item diary: Registro de compra-venta diff --git a/modules/item/front/routes.json b/modules/item/front/routes.json index 5743d0ce7..52cf5d2be 100644 --- a/modules/item/front/routes.json +++ b/modules/item/front/routes.json @@ -16,7 +16,8 @@ {"state": "item.card.basicData", "icon": "settings"}, {"state": "item.card.tags", "icon": "icon-tags"}, {"state": "item.card.tax", "icon": "icon-tax"}, - {"state": "item.card.botanical", "icon": "local_florist"}, + {"state": "item.card.botanical", "icon": "local_florist"}, + {"state": "item.card.shelving", "icon": "icon-inventory"}, {"state": "item.card.itemBarcode", "icon": "icon-barcode"}, {"state": "item.card.diary", "icon": "icon-transaction"}, {"state": "item.card.last-entries", "icon": "icon-regentry"}, @@ -92,6 +93,16 @@ }, "acl": ["buyer"] }, + { + "url" : "/shelving", + "state": "item.card.shelving", + "component": "vn-item-shelving", + "description": "Shelvings", + "params": { + "item": "$ctrl.item" + }, + "acl": ["employee"] + }, { "url" : "/barcode", "state": "item.card.itemBarcode", diff --git a/modules/shelving/front/routes.json b/modules/shelving/front/routes.json index b99ca4cac..0a05db9e3 100644 --- a/modules/shelving/front/routes.json +++ b/modules/shelving/front/routes.json @@ -14,7 +14,7 @@ ] }, "keybindings": [ - {"key": "s", "state": "shelving.index"} + {"key": "c", "state": "shelving.index"} ], "routes": [ { From ddbe1057ad9996e42100e156c188faafd40f1a3f Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 6 Oct 2022 09:47:53 +0200 Subject: [PATCH 09/77] feat: add new section --- .../10490-august/00-aclItemShelving.sql | 3 + ...00-aclItemShelvingPlacementSupply copy.sql | 3 + .../item-shelving-placement-supply.json | 9 +- modules/item/front/item-shelving/index.html | 132 +++++------------- modules/item/front/item-shelving/index.js | 106 ++++++-------- .../item/front/item-shelving/locale/es.yml | 12 +- 6 files changed, 95 insertions(+), 170 deletions(-) create mode 100644 db/changes/10490-august/00-aclItemShelving.sql create mode 100644 db/changes/10490-august/00-aclItemShelvingPlacementSupply copy.sql diff --git a/db/changes/10490-august/00-aclItemShelving.sql b/db/changes/10490-august/00-aclItemShelving.sql new file mode 100644 index 000000000..3995bbe49 --- /dev/null +++ b/db/changes/10490-august/00-aclItemShelving.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('ItemShelving', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10490-august/00-aclItemShelvingPlacementSupply copy.sql b/db/changes/10490-august/00-aclItemShelvingPlacementSupply copy.sql new file mode 100644 index 000000000..cc589a58f --- /dev/null +++ b/db/changes/10490-august/00-aclItemShelvingPlacementSupply copy.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('ItemShelvingPlacementSupplyStock', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/modules/item/back/models/item-shelving-placement-supply.json b/modules/item/back/models/item-shelving-placement-supply.json index 11497b4fc..a54013e05 100644 --- a/modules/item/back/models/item-shelving-placement-supply.json +++ b/modules/item/back/models/item-shelving-placement-supply.json @@ -7,13 +7,17 @@ } }, "properties": { + "itemShelvingFk": { + "type": "number", + "id": true + }, "created": { "type": "date" }, "itemFk": { "type": "number" }, - "concept": { + "longName": { "type": "string" }, "parking": { @@ -24,6 +28,9 @@ }, "packing": { "type": "number" + }, + "stock": { + "type": "number" } } } \ No newline at end of file diff --git a/modules/item/front/item-shelving/index.html b/modules/item/front/item-shelving/index.html index 7949e73a3..115829c8d 100644 --- a/modules/item/front/item-shelving/index.html +++ b/modules/item/front/item-shelving/index.html @@ -1,21 +1,10 @@ - - - - + ng-click="removeConfirm.show()" + vn-tooltip="Remove selected lines" + vn-acl="replenisherBos" + icon="delete"> @@ -50,123 +39,78 @@ model="model"> - + Created - + Item + field="longName"> Concept + field="parking"> Parking - + Shelving + field="label"> Etiqueta Packing - + - + {{::itemShelvingPlacementSupplyStock.created | date: 'dd/MM/yyyy'}} + + {{::itemShelvingPlacementSupplyStock.itemFk}} + + - {{::defaulter.clientName}} + {{itemShelvingPlacementSupplyStock.longName}} - - {{::defaulter.salesPersonName | dashIfEmpty}} - + {{::itemShelvingPlacementSupplyStock.parking}} - {{::defaulter.amount | currency: 'EUR': 2}} - - {{::defaulter.workerName | dashIfEmpty}} - + {{::itemShelvingPlacementSupplyStock.shelving}} - - - + + {{::itemShelvingPlacementSupplyStock.label}} - - - {{::defaulter.created | date: 'dd/MM/yyyy'}} - + + {{::itemShelvingPlacementSupplyStock.packing}} - {{::defaulter.creditInsurance | currency: 'EUR': 2}} - {{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}} - - - - - - - - + + - - - -
-
{{$ctrl.$t('Add observation to all selected clients', {total: $ctrl.checked.length})}}
- - - - -
-
- - - - -
+ + \ No newline at end of file diff --git a/modules/item/front/item-shelving/index.js b/modules/item/front/item-shelving/index.js index f51a1a65b..6fd8825aa 100644 --- a/modules/item/front/item-shelving/index.js +++ b/modules/item/front/item-shelving/index.js @@ -1,11 +1,9 @@ import ngModule from '../module'; import Section from 'salix/components/section'; -import UserError from 'core/lib/user-error'; export default class Controller extends Section { constructor($element, $) { super($element, $); - this.defaulter = {}; this.smartTableOptions = { activeButtons: { @@ -13,42 +11,31 @@ export default class Controller extends Section { }, columns: [ { - field: 'clientFk', + field: 'parking', autocomplete: { - url: 'Clients', - showField: 'name', - valueField: 'id' + url: 'Parkings', + showField: 'code', + valueField: 'code' } }, { - field: 'salesPersonFk', + field: 'shelving', autocomplete: { - url: 'Workers/activeWithInheritedRole', - where: `{role: 'salesPerson'}`, - searchFunction: '{firstName: $search}', - showField: 'name', - valueField: 'id', + url: 'Shelvings', + showField: 'code', + valueField: 'code' } }, - { - field: 'workerFk', - autocomplete: { - url: 'Workers/activeWithInheritedRole', - searchFunction: '{firstName: $search}', - showField: 'name', - valueField: 'id', - } - }, - { - field: 'observation', - searchable: false - }, { field: 'created', searchable: false }, { - field: 'defaulterSinced', + field: 'itemFk', + searchable: false + }, + { + field: 'longName', searchable: false } ] @@ -58,16 +45,24 @@ export default class Controller extends Section { } get checked() { - const clients = this.$.model.data || []; + const itemShelvings = this.$.model.data || []; const checkedLines = []; - for (let defaulter of clients) { - if (defaulter.checked) - checkedLines.push(defaulter); + for (let itemShelving of itemShelvings) { + if (itemShelving.checked) + checkedLines.push(itemShelving); } return checkedLines; } + get label() { + const itemShelvings = this.$.model.data || []; + for (let itemShelving of itemShelvings) + itemShelving.label = itemShelving.stock / itemShelving.packing; + + return true; + } + getBalanceDueTotal() { this.$http.get('Defaulters/filter') .then(res => { @@ -80,48 +75,27 @@ export default class Controller extends Section { }); } - chipColor(date) { - const day = 24 * 60 * 60 * 1000; - const today = new Date(); - today.setHours(0, 0, 0, 0); - - const observationShipped = new Date(date); - observationShipped.setHours(0, 0, 0, 0); - - const difference = today - observationShipped; - - if (difference > (day * 20)) - return 'alert'; - if (difference > (day * 10)) - return 'warning'; - } - - onResponse() { - if (!this.defaulter.observation) - throw new UserError(`The message can't be empty`); - + async onRemove() { const params = []; - for (let defaulter of this.checked) { - params.push({ - text: this.defaulter.observation, - clientFk: defaulter.clientFk - }); - } + for (let itemShelving of this.checked) + params.push(itemShelving.itemShelvingFk); - this.$http.post(`ClientObservations`, params) .then(() => { - this.vnApp.showMessage(this.$t('Observation saved!')); - this.$state.reload(); - }); + for (let id of params) { + await this.$http.delete(`ItemShelvings/${id}`) + .then(() => { + this.vnApp.showSuccess(this.$t('ItemShelving removed')); + this.$state.reload(); + }); + } } exprBuilder(param, value) { switch (param) { - case 'creditInsurance': - case 'amount': - case 'clientFk': - case 'workerFk': - case 'salesPersonFk': - return {[`d.${param}`]: value}; + case 'parking': + case 'shelving': + case 'label': + case 'packing': + return {[param]: value}; } } } diff --git a/modules/item/front/item-shelving/locale/es.yml b/modules/item/front/item-shelving/locale/es.yml index c3e1d4e19..00b6ffe58 100644 --- a/modules/item/front/item-shelving/locale/es.yml +++ b/modules/item/front/item-shelving/locale/es.yml @@ -1,9 +1,3 @@ -Add observation: Añadir observación -Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s) -Balance D.: Saldo V. -Credit I.: Crédito A. -Last observation: Última observación -L. O. Date: Fecha Ú. O. -Last observation date: Fecha última observación -Search client: Buscar clientes -Worker who made the last observation: Trabajador que ha realizado la última observación \ No newline at end of file +Shelving: Matrícula +Remove selected lines: Eliminar líneas seleccionadas +Selected lines will be deleted: Las líneas seleccionadas serán eliminadas \ No newline at end of file From a3a432c753c19d263d642c379aa1d7c4977b3a6e Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 6 Oct 2022 10:18:19 +0200 Subject: [PATCH 10/77] fix: delete one or more expeditions --- modules/ticket/front/expedition/index.html | 11 ++++++----- modules/ticket/front/expedition/index.js | 14 ++++++++++++++ modules/ticket/front/expedition/locale/es.yml | 3 ++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index 694b3eb33..caef67f59 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -17,8 +17,9 @@ ng-show="$ctrl.totalChecked">
@@ -88,11 +89,11 @@ - + on-accept="$ctrl.onRemove()"> diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index d80c8e812..1525f97b5 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -27,6 +27,20 @@ class Controller extends Section { return this.checked.length; } + async onRemove() { + const params = []; + for (let expedition of this.checked) + params.push(expedition.id); + + for (let id of params) { + await this.$http.delete(`Expeditions/${id}`) + .then(() => { + this.vnApp.showSuccess(this.$t('Expedition removed')); + this.$state.reload(); + }); + } + } + createTicket(routeFk) { const tomorrow = new Date(); const params = { diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml index d23cf25af..9c7872fd7 100644 --- a/modules/ticket/front/expedition/locale/es.yml +++ b/modules/ticket/front/expedition/locale/es.yml @@ -1 +1,2 @@ -Status log: Hitorial de estados \ No newline at end of file +Status log: Hitorial de estados +Expedition removed: Expedición eliminada \ No newline at end of file From 37601934f579e55668e5b2eae07e9b68e95a3e81 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 6 Oct 2022 11:40:46 +0200 Subject: [PATCH 11/77] feat: first option to delete and update --- modules/ticket/front/expedition/index.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index 1525f97b5..4f7b34e7c 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -42,17 +42,25 @@ class Controller extends Section { } createTicket(routeFk) { - const tomorrow = new Date(); - const params = { + const date = new Date(); // esta fecha hay que preguntarla a Fran Monsalvez + const ticketParams = { clientId: this.ticket.clientFk, - landed: tomorrow.getDay() + 1, + landed: date, addressId: this.ticket.addressFk, agencyModeId: this.ticket.agencyModeFk, warehouseId: this.ticket.warehouseFk }; const query = `Tickets/new`; - this.$http.post(query, params).then(res => { + this.$http.post(query, ticketParams).then(res => { if (routeFk) this.$http.patch(`Tickets/${res.data.id}`, {routeFk: routeFk}); + + const params = []; + for (let expedition of this.checked) + params.push(expedition.id); + const expeditionParams = {ticketFk: res.data.id}; + for (let id of params) + this.$http.patch(`Expeditions/${id}`, expeditionParams); + this.vnApp.showSuccess(this.$t('Data saved!')); this.$state.go('ticket.card.summary', {id: res.data.id}); }); From deb8a00dfb2edbd5d3a2a2e84caec89679cce5ad Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Oct 2022 10:47:54 +0200 Subject: [PATCH 12/77] feat: add backRoutes --- .../methods/expedition/deleteExpeditions.js | 48 +++++++++++++++ .../methods/expedition/moveExpeditions.js | 59 +++++++++++++++++++ modules/ticket/back/models/expedition.js | 2 + modules/ticket/front/expedition/index.html | 4 +- modules/ticket/front/expedition/index.js | 35 +++++------ modules/ticket/front/expedition/locale/es.yml | 5 +- 6 files changed, 130 insertions(+), 23 deletions(-) create mode 100644 modules/ticket/back/methods/expedition/deleteExpeditions.js create mode 100644 modules/ticket/back/methods/expedition/moveExpeditions.js diff --git a/modules/ticket/back/methods/expedition/deleteExpeditions.js b/modules/ticket/back/methods/expedition/deleteExpeditions.js new file mode 100644 index 000000000..b902b9f0c --- /dev/null +++ b/modules/ticket/back/methods/expedition/deleteExpeditions.js @@ -0,0 +1,48 @@ + +module.exports = Self => { + Self.remoteMethod('deleteExpeditions', { + description: 'Delete the selected expeditions', + accessType: 'WRITE', + accepts: [{ + arg: 'expeditionsIds', + type: ['number'], + required: true, + description: 'The expeditions ids to delete' + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/deleteExpeditions`, + verb: 'POST' + } + }); + + Self.deleteExpeditions = async(expeditionsIds, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const deletedExpeditions = await models.Expedition.destroyAll({ + id: {inq: expeditionsIds} + }, myOptions); + + if (tx) await tx.commit(); + + return deletedExpeditions; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/expedition/moveExpeditions.js b/modules/ticket/back/methods/expedition/moveExpeditions.js new file mode 100644 index 000000000..2c521dda7 --- /dev/null +++ b/modules/ticket/back/methods/expedition/moveExpeditions.js @@ -0,0 +1,59 @@ + +module.exports = Self => { + Self.remoteMethod('moveExpeditions', { + description: 'Move the selected expeditions to another ticket', + accessType: 'WRITE', + accepts: [{ + arg: 'expeditionsIds', + type: ['number'], + required: true, + description: 'The expeditions ids to nove' + }, + { + arg: 'ticketId', + type: 'number', + required: true, + description: 'the ticket id to which the expeditions are added' + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/moveExpeditions`, + verb: 'POST' + } + }); + + Self.moveExpeditions = async(expeditionsIds, ticketId, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const promises = []; + for (let expeditionsId of expeditionsIds) { + const expeditionToUpdate = await models.Expedition.findById(expeditionsId); + const expeditionUpdated = expeditionToUpdate.updateAttribute('ticketFk', ticketId, myOptions); + promises.push(expeditionUpdated); + } + + const updated = await Promise.all(promises); + + if (tx) await tx.commit(); + + return updated; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/models/expedition.js b/modules/ticket/back/models/expedition.js index 9d6564373..46cde6890 100644 --- a/modules/ticket/back/models/expedition.js +++ b/modules/ticket/back/models/expedition.js @@ -1,3 +1,5 @@ module.exports = function(Self) { require('../methods/expedition/filter')(Self); + require('../methods/expedition/deleteExpeditions')(Self); + require('../methods/expedition/moveExpeditions')(Self); }; diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index caef67f59..35f56872c 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -14,10 +14,10 @@ + disabled="!$ctrl.totalChecked"> diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index 4f7b34e7c..a81439d64 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -17,7 +17,7 @@ class Controller extends Section { const checkedRows = []; for (let row of rows) { if (row.checked) - checkedRows.push(row); + checkedRows.push(row.id); } return checkedRows; @@ -27,18 +27,14 @@ class Controller extends Section { return this.checked.length; } - async onRemove() { - const params = []; - for (let expedition of this.checked) - params.push(expedition.id); - - for (let id of params) { - await this.$http.delete(`Expeditions/${id}`) - .then(() => { - this.vnApp.showSuccess(this.$t('Expedition removed')); - this.$state.reload(); - }); - } + onRemove() { + const params = {expeditionsIds: this.checked}; + const query = `Expeditions/deleteExpeditions`; + this.$http.post(query, params) + .then(() => { + this.vnApp.showSuccess(this.$t('Expedition removed')); + this.$state.reload(); + }); } createTicket(routeFk) { @@ -54,13 +50,12 @@ class Controller extends Section { this.$http.post(query, ticketParams).then(res => { if (routeFk) this.$http.patch(`Tickets/${res.data.id}`, {routeFk: routeFk}); - const params = []; - for (let expedition of this.checked) - params.push(expedition.id); - const expeditionParams = {ticketFk: res.data.id}; - for (let id of params) - this.$http.patch(`Expeditions/${id}`, expeditionParams); - + const params = { + expeditionsIds: this.checked, + ticketId: res.data.id + }; + const query = `Expeditions/moveExpeditions`; + this.$http.post(query, params); this.vnApp.showSuccess(this.$t('Data saved!')); this.$state.go('ticket.card.summary', {id: res.data.id}); }); diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml index 9c7872fd7..bc3ec3345 100644 --- a/modules/ticket/front/expedition/locale/es.yml +++ b/modules/ticket/front/expedition/locale/es.yml @@ -1,2 +1,5 @@ Status log: Hitorial de estados -Expedition removed: Expedición eliminada \ No newline at end of file +Expedition removed: Expedición eliminada +Move: Mover +New ticket without route: Nuevo ticket sin ruta +New ticket with route: Nuevo ticket con ruta \ No newline at end of file From bb26fa6502232335c784f9fb101a381bf4f1be39 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 7 Oct 2022 13:49:42 +0200 Subject: [PATCH 13/77] feat: add tFront --- modules/ticket/front/expedition/index.js | 23 ++++---- modules/ticket/front/expedition/index.spec.js | 58 +++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index a81439d64..f7674716b 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -2,16 +2,6 @@ import ngModule from '../module'; import Section from 'salix/components/section'; class Controller extends Section { - onDialogAccept(id) { - return this.$http.delete(`Expeditions/${id}`) - .then(() => this.$.model.refresh()); - } - - showLog(expedition) { - this.expedition = expedition; - this.$.statusLog.show(); - } - get checked() { const rows = this.$.model.data || []; const checkedRows = []; @@ -27,13 +17,23 @@ class Controller extends Section { return this.checked.length; } + onDialogAccept(id) { + return this.$http.delete(`Expeditions/${id}`) + .then(() => this.$.model.refresh()); + } + + showLog(expedition) { + this.expedition = expedition; + this.$.statusLog.show(); + } + onRemove() { const params = {expeditionsIds: this.checked}; const query = `Expeditions/deleteExpeditions`; this.$http.post(query, params) .then(() => { this.vnApp.showSuccess(this.$t('Expedition removed')); - this.$state.reload(); + this.$.model.refresh(); }); } @@ -49,7 +49,6 @@ class Controller extends Section { const query = `Tickets/new`; this.$http.post(query, ticketParams).then(res => { if (routeFk) this.$http.patch(`Tickets/${res.data.id}`, {routeFk: routeFk}); - const params = { expeditionsIds: this.checked, ticketId: res.data.id diff --git a/modules/ticket/front/expedition/index.spec.js b/modules/ticket/front/expedition/index.spec.js index 586ef2109..fe23046ce 100644 --- a/modules/ticket/front/expedition/index.spec.js +++ b/modules/ticket/front/expedition/index.spec.js @@ -17,6 +17,14 @@ describe('Ticket', () => { refresh: () => {} }; controller = $componentController('vnTicketExpedition', {$element: null, $scope}); + controller.$.model.data = [ + {id: 1}, + {id: 2}, + {id: 3} + ]; + const modelData = controller.$.model.data; + modelData[0].checked = true; + modelData[1].checked = true; })); describe('onDialogAccept()', () => { @@ -50,5 +58,55 @@ describe('Ticket', () => { expect(controller.$.statusLog.show).toHaveBeenCalledWith(); }); }); + + describe('onRemove()', () => { + it('should make a query and then call to the model refresh() method', () => { + jest.spyOn($scope.model, 'refresh'); + + const expectedParams = {expeditionsIds: [1, 2]}; + $httpBackend.expect('POST', 'Expeditions/deleteExpeditions', expectedParams).respond(200); + controller.onRemove(); + $httpBackend.flush(); + + expect($scope.model.refresh).toHaveBeenCalledWith(); + }); + }); + + describe('createTicket()', () => { + it('should make a query and then call to the $state go() method', () => { + jest.spyOn(controller.$state, 'go').mockReturnThis(); + + const ticket = { + clientFk: 1101, + landed: new Date(), + addressFk: 121, + agencyModeFk: 1, + warehouseFk: 1 + }; + controller.ticket = ticket; + + const expectedTicket = { + clientId: 1101, + landed: new Date(), + addressId: 121, + agencyModeId: 1, + warehouseId: 1 + }; + + const ticketIdToTransfer = 28; + const expectedResponse = {id: ticketIdToTransfer}; + + const expectedParams = { + expeditionsIds: [1, 2], + ticketId: 28 + }; + $httpBackend.expect('POST', 'Tickets/new', expectedTicket).respond(expectedResponse); + $httpBackend.expect('POST', 'Expeditions/moveExpeditions', expectedParams).respond(200); + controller.createTicket(); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.summary', {id: ticketIdToTransfer}); + }); + }); }); }); From 843f67919c9992e7e622f01b8dcfca7697fd81b3 Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 10 Oct 2022 12:56:03 +0200 Subject: [PATCH 14/77] feat: add backTest --- .../specs/deleteExpeditions.spec.js | 22 ++++++++++++++++++ .../expedition/specs/moveExpeditions.spec.js | 23 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js create mode 100644 modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js diff --git a/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js new file mode 100644 index 000000000..0a47c78da --- /dev/null +++ b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js @@ -0,0 +1,22 @@ +const models = require('vn-loopback/server/server').models; + +describe('ticket deleteExpeditions()', () => { + it('should delete the selected expeditions', async() => { + const tx = await models.Expedition.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const expeditionIds = [12, 13]; + const result = await models.Expedition.deleteExpeditions(expeditionIds, options); + + expect(result.count).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); + diff --git a/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js new file mode 100644 index 000000000..0bb402a55 --- /dev/null +++ b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js @@ -0,0 +1,23 @@ +const models = require('vn-loopback/server/server').models; + +describe('ticket moveExpeditions()', () => { + it('should delete the selected expeditions', async() => { + const tx = await models.Expedition.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const expeditionIds = [12, 13]; + const ticketId = 1; + const result = await models.Expedition.moveExpeditions(expeditionIds, ticketId, options); + + expect(result.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); + From ea1714240e63f97bd91a9de75d3ca8463c88de17 Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 10 Oct 2022 13:45:34 +0200 Subject: [PATCH 15/77] fix: test e2e --- e2e/helpers/selectors.js | 3 ++- e2e/paths/05-ticket/02_expeditions_and_log.spec.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index fedcaf5f9..97a58ba9c 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -596,7 +596,8 @@ export default { submitNotesButton: 'button[type=submit]' }, ticketExpedition: { - thirdExpeditionRemoveButton: 'vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(3) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]', + thirdSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(3) vn-check[ng-model="expedition.checked"]', + deleteExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="delete"]', expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr' }, ticketPackages: { diff --git a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js index dd2525f43..f970247e5 100644 --- a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js +++ b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js @@ -18,7 +18,8 @@ describe('Ticket expeditions and log path', () => { }); it(`should delete a former expedition and confirm the remaining expedition are the expected ones`, async() => { - await page.waitToClick(selectors.ticketExpedition.thirdExpeditionRemoveButton); + await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox); + await page.waitToClick(selectors.ticketExpedition.deleteExpeditionButton); await page.waitToClick(selectors.globalItems.acceptButton); await page.reloadSection('ticket.card.expedition'); From 9c52508a88a20d1d5d827b3c8c3e4d08494b32d0 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Oct 2022 08:40:22 +0200 Subject: [PATCH 16/77] feat: add total labels --- .../00-aclItemShelving.sql | 0 .../00-aclItemShelvingPlacementSupply.sql} | 0 .../00-itemType.sql | 0 .../item-shelving/deleteItemShelvings.js | 47 +++++++++++++++++++ .../specs/deleteItemShelvings.spec.js | 21 +++++++++ modules/item/back/models/item-shelving.js | 3 ++ modules/item/front/item-shelving/index.html | 18 +++---- modules/item/front/item-shelving/index.js | 45 ++++++------------ .../item/front/item-shelving/locale/es.yml | 4 +- 9 files changed, 98 insertions(+), 40 deletions(-) rename db/changes/{10490-august => 10491-august}/00-aclItemShelving.sql (100%) rename db/changes/{10490-august/00-aclItemShelvingPlacementSupply copy.sql => 10491-august/00-aclItemShelvingPlacementSupply.sql} (100%) rename db/changes/{10480-june => 10491-august}/00-itemType.sql (100%) create mode 100644 modules/item/back/methods/item-shelving/deleteItemShelvings.js create mode 100644 modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js create mode 100644 modules/item/back/models/item-shelving.js diff --git a/db/changes/10490-august/00-aclItemShelving.sql b/db/changes/10491-august/00-aclItemShelving.sql similarity index 100% rename from db/changes/10490-august/00-aclItemShelving.sql rename to db/changes/10491-august/00-aclItemShelving.sql diff --git a/db/changes/10490-august/00-aclItemShelvingPlacementSupply copy.sql b/db/changes/10491-august/00-aclItemShelvingPlacementSupply.sql similarity index 100% rename from db/changes/10490-august/00-aclItemShelvingPlacementSupply copy.sql rename to db/changes/10491-august/00-aclItemShelvingPlacementSupply.sql diff --git a/db/changes/10480-june/00-itemType.sql b/db/changes/10491-august/00-itemType.sql similarity index 100% rename from db/changes/10480-june/00-itemType.sql rename to db/changes/10491-august/00-itemType.sql diff --git a/modules/item/back/methods/item-shelving/deleteItemShelvings.js b/modules/item/back/methods/item-shelving/deleteItemShelvings.js new file mode 100644 index 000000000..0b58970d8 --- /dev/null +++ b/modules/item/back/methods/item-shelving/deleteItemShelvings.js @@ -0,0 +1,47 @@ +module.exports = Self => { + Self.remoteMethod('deleteItemShelvings', { + description: 'Deletes the selected orders', + accessType: 'WRITE', + accepts: [{ + arg: 'itemShelvingIds', + type: ['number'], + required: true, + description: 'The itemShelving ids to delete' + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/deleteItemShelvings`, + verb: 'POST' + } + }); + + Self.deleteItemShelvings = async(itemShelvingIds, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const deletedItemShelvings = await models.ItemShelving.destroyAll({ + id: {inq: itemShelvingIds} + }, myOptions); + + if (tx) await tx.commit(); + + return deletedItemShelvings; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js b/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js new file mode 100644 index 000000000..a152b5981 --- /dev/null +++ b/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js @@ -0,0 +1,21 @@ +const models = require('vn-loopback/server/server').models; + +describe('ItemShelving deleteItemShelvings()', () => { + it('should return the deleted itemShelvings', async() => { + const tx = await models.Order.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const itemShelvingIds = [1, 2]; + const result = await models.ItemShelving.deleteItemShelvings(itemShelvingIds, options); + + expect(result.count).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/item/back/models/item-shelving.js b/modules/item/back/models/item-shelving.js new file mode 100644 index 000000000..5f372a3be --- /dev/null +++ b/modules/item/back/models/item-shelving.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/item-shelving/deleteItemShelvings')(Self); +}; diff --git a/modules/item/front/item-shelving/index.html b/modules/item/front/item-shelving/index.html index 115829c8d..fa7a70544 100644 --- a/modules/item/front/item-shelving/index.html +++ b/modules/item/front/item-shelving/index.html @@ -2,7 +2,7 @@ vn-id="model" url="ItemShelvingPlacementSupplyStocks" link="{itemFk: $ctrl.$params.id}" - data="itemShelvingPlacementSupplyStocks" + data="$ctrl.itemShelvingPlacementSupplyStocks" auto-load="true"> @@ -15,18 +15,18 @@
Total
+ label="Total labels" + value="{{$ctrl.labelTotal.toFixed(2)}}">
+ vn-acl="replenisherBos">
@@ -68,7 +68,9 @@ - + - {{::itemShelvingPlacementSupplyStock.label}} + {{(itemShelvingPlacementSupplyStock.stock / itemShelvingPlacementSupplyStock.packing).toFixed(2)}} {{::itemShelvingPlacementSupplyStock.packing}} diff --git a/modules/item/front/item-shelving/index.js b/modules/item/front/item-shelving/index.js index 6fd8825aa..b8584039b 100644 --- a/modules/item/front/item-shelving/index.js +++ b/modules/item/front/item-shelving/index.js @@ -40,8 +40,6 @@ export default class Controller extends Section { } ] }; - - this.getBalanceDueTotal(); } get checked() { @@ -49,46 +47,31 @@ export default class Controller extends Section { const checkedLines = []; for (let itemShelving of itemShelvings) { if (itemShelving.checked) - checkedLines.push(itemShelving); + checkedLines.push(itemShelving.itemShelvingFk); } return checkedLines; } - get label() { + calculateTotals() { + this.labelTotal = 0; const itemShelvings = this.$.model.data || []; - for (let itemShelving of itemShelvings) - itemShelving.label = itemShelving.stock / itemShelving.packing; - - return true; + itemShelvings.forEach(itemShelving => { + const label = itemShelving.stock / itemShelving.packing; + this.labelTotal += label; + }); } - getBalanceDueTotal() { - this.$http.get('Defaulters/filter') - .then(res => { - if (!res.data) return 0; - - this.balanceDueTotal = res.data.reduce( - (accumulator, currentValue) => { - return accumulator + (currentValue['amount'] || 0); - }, 0); + onRemove() { + const params = {itemShelvingIds: this.checked}; + const query = `ItemShelvings/deleteItemShelvings`; + this.$http.post(query, params) + .then(() => { + this.vnApp.showSuccess(this.$t('ItemShelvings removed')); + this.$.model.refresh(); }); } - async onRemove() { - const params = []; - for (let itemShelving of this.checked) - params.push(itemShelving.itemShelvingFk); - - for (let id of params) { - await this.$http.delete(`ItemShelvings/${id}`) - .then(() => { - this.vnApp.showSuccess(this.$t('ItemShelving removed')); - this.$state.reload(); - }); - } - } - exprBuilder(param, value) { switch (param) { case 'parking': diff --git a/modules/item/front/item-shelving/locale/es.yml b/modules/item/front/item-shelving/locale/es.yml index 00b6ffe58..006363cfa 100644 --- a/modules/item/front/item-shelving/locale/es.yml +++ b/modules/item/front/item-shelving/locale/es.yml @@ -1,3 +1,5 @@ Shelving: Matrícula Remove selected lines: Eliminar líneas seleccionadas -Selected lines will be deleted: Las líneas seleccionadas serán eliminadas \ No newline at end of file +Selected lines will be deleted: Las líneas seleccionadas serán eliminadas +ItemShelvings removed: Carros eliminados +Total labels: Total etiquetas \ No newline at end of file From 1a38fc307f1364676adadf5feb02dc231daf10c4 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Oct 2022 08:42:59 +0200 Subject: [PATCH 17/77] delete: duplicated keybinding --- modules/shelving/front/routes.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/shelving/front/routes.json b/modules/shelving/front/routes.json index 0a05db9e3..09a8e389b 100644 --- a/modules/shelving/front/routes.json +++ b/modules/shelving/front/routes.json @@ -13,9 +13,6 @@ {"state": "shelving.card.log", "icon": "history"} ] }, - "keybindings": [ - {"key": "c", "state": "shelving.index"} - ], "routes": [ { "url": "/shelving", From bfefec397e68e86c2879bd1f89d3aacc8763a276 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 11 Oct 2022 09:13:29 +0200 Subject: [PATCH 18/77] feat: add testFront --- .../item/front/item-shelving/index.spec.js | 118 ++++++------------ 1 file changed, 39 insertions(+), 79 deletions(-) diff --git a/modules/item/front/item-shelving/index.spec.js b/modules/item/front/item-shelving/index.spec.js index 0732c68a1..55df1c27d 100644 --- a/modules/item/front/item-shelving/index.spec.js +++ b/modules/item/front/item-shelving/index.spec.js @@ -1,120 +1,80 @@ import './index'; import crudModel from 'core/mocks/crud-model'; -describe('client defaulter', () => { - describe('Component vnClientDefaulter', () => { +describe('item shelving', () => { + describe('Component vnItemShelving', () => { let controller; let $httpBackend; - beforeEach(ngModule('client')); + beforeEach(ngModule('item')); beforeEach(inject(($componentController, _$httpBackend_) => { $httpBackend = _$httpBackend_; - const $element = angular.element(''); - controller = $componentController('vnClientDefaulter', {$element}); + const $element = angular.element(''); + controller = $componentController('vnItemShelving', {$element}); controller.$.model = crudModel; controller.$.model.data = [ - {clientFk: 1101, amount: 125}, - {clientFk: 1102, amount: 500}, - {clientFk: 1103, amount: 250} + {itemShelvingFk: 1, packing: 10, stock: 1}, + {itemShelvingFk: 2, packing: 12, stock: 5}, + {itemShelvingFk: 4, packing: 20, stock: 10} ]; + const modelData = controller.$.model.data; + modelData[0].checked = true; + modelData[1].checked = true; })); describe('checked() getter', () => { - it('should return the checked lines', () => { - const data = controller.$.model.data; - data[1].checked = true; - data[2].checked = true; + it('should return a the selected rows', () => { + const result = controller.checked; - const checkedRows = controller.checked; - - const firstCheckedRow = checkedRows[0]; - const secondCheckedRow = checkedRows[1]; - - expect(firstCheckedRow.clientFk).toEqual(1102); - expect(secondCheckedRow.clientFk).toEqual(1103); + expect(result).toEqual(expect.arrayContaining([1, 2])); }); }); - describe('chipColor()', () => { - it('should return undefined when the date is the present', () => { - let today = new Date(); - let result = controller.chipColor(today); + describe('calculateTotals()', () => { + it('should calculate the total of labels', () => { + controller.calculateTotals(); - expect(result).toEqual(undefined); - }); - - it('should return warning when the date is 10 days in the past', () => { - let pastDate = new Date(); - pastDate = pastDate.setDate(pastDate.getDate() - 11); - let result = controller.chipColor(pastDate); - - expect(result).toEqual('warning'); - }); - - it('should return alert when the date is 20 days in the past', () => { - let pastDate = new Date(); - pastDate = pastDate.setDate(pastDate.getDate() - 21); - let result = controller.chipColor(pastDate); - - expect(result).toEqual('alert'); + expect(controller.labelTotal).toEqual(1.0166666666666666); }); }); - describe('onResponse()', () => { - it('should return error for empty message', () => { - let error; - try { - controller.onResponse(); - } catch (e) { - error = e; - } + describe('onRemove()', () => { + it('shoud remove the selected lines', () => { + jest.spyOn(controller.$.model, 'refresh'); + const expectedParams = {itemShelvingIds: [1, 2]}; - expect(error).toBeDefined(); - expect(error.message).toBe(`The message can't be empty`); - }); - - it('should return saved message', () => { - const data = controller.$.model.data; - data[1].checked = true; - controller.defaulter = {observation: 'My new observation'}; - - const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}]; - - jest.spyOn(controller.vnApp, 'showMessage'); - $httpBackend.expect('GET', `Defaulters/filter`).respond(200); - $httpBackend.expect('POST', `ClientObservations`, params).respond(200, params); - - controller.onResponse(); + $httpBackend.expectPOST('ItemShelvings/deleteItemShelvings', expectedParams).respond(200); + controller.onRemove(); $httpBackend.flush(); - expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Observation saved!'); + expect(controller.$.model.refresh).toHaveBeenCalled(); }); }); describe('exprBuilder()', () => { - it('should search by sales person', () => { - const expr = controller.exprBuilder('salesPersonFk', '5'); + it('should search by parking', () => { + const expr = controller.exprBuilder('parking', '700-01'); - expect(expr).toEqual({'d.salesPersonFk': '5'}); + expect(expr).toEqual({'parking': '700-01'}); }); - it('should search by client', () => { - const expr = controller.exprBuilder('clientFk', '5'); + it('should search by shelving', () => { + const expr = controller.exprBuilder('shelving', 'AAA'); - expect(expr).toEqual({'d.clientFk': '5'}); + expect(expr).toEqual({'shelving': 'AAA'}); }); - }); - describe('getBalanceDueTotal()', () => { - it('should return balance due total', () => { - const defaulters = controller.$.model.data; - $httpBackend.when('GET', `Defaulters/filter`).respond(defaulters); + it('should search by label', () => { + const expr = controller.exprBuilder('label', 0.17); - controller.getBalanceDueTotal(); - $httpBackend.flush(); + expect(expr).toEqual({'label': 0.17}); + }); - expect(controller.balanceDueTotal).toEqual(875); + it('should search by packing', () => { + const expr = controller.exprBuilder('packing', 10); + + expect(expr).toEqual({'packing': 10}); }); }); }); From 248572feb438836e32d3b971ea83be1f1b19e03a Mon Sep 17 00:00:00 2001 From: alexm Date: Fri, 14 Oct 2022 07:40:11 +0200 Subject: [PATCH 19/77] use variable --- front/core/components/searchbar/searchbar.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index 57a77d980..dff4836db 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -303,7 +303,9 @@ export default class Searchbar extends Component { } this.tableQ = null; - if (this.$params.q && Object.keys(JSON.parse(this.$params.q)).length) { + + const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length; + if (hasParams) { const stateFilter = JSON.parse(this.$params.q); for (let param in stateFilter) { if (param != 'tableQ' && param != 'orderQ') From 2455a11c26744ac45f81b869b618af686d868157 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 14 Oct 2022 14:29:21 +0200 Subject: [PATCH 20/77] feat: changeMultipleDiscount uses the same popover than changeDiscount --- modules/ticket/front/sale/index.html | 30 ++---------------------- modules/ticket/front/sale/index.js | 34 ++++++++++++---------------- 2 files changed, 17 insertions(+), 47 deletions(-) diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 42eb10cb0..112476c30 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -300,7 +300,7 @@ ng-model="$ctrl.manaCode"> -
+

New price

{{$ctrl.getNewPrice() | currency: 'EUR': 2}} @@ -321,32 +321,6 @@

- - - - - -
-
- - -
-
- Mana: {{::$ctrl.edit.mana | currency: 'EUR': 0}} -
-
-
-
-
@@ -490,7 +464,7 @@ + ng-click="$ctrl.showEditDiscountPopover($event, sale)"> Update discount Date: Fri, 14 Oct 2022 14:30:37 +0200 Subject: [PATCH 21/77] fix: only can exists 'mana' or 'manaClaim' discount. Never both --- .../back/methods/ticket/updateDiscount.js | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index b1291a45b..99daac512 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -115,10 +115,43 @@ module.exports = Self => { for (let sale of sales) { const oldDiscount = sale.discount; const value = ((-sale.price * newDiscount) / 100); - const newComponent = models.SaleComponent.upsert({ - saleFk: sale.id, - value: value, - componentFk: componentId}, myOptions); + + const manaComponent = await models.Component.findOne({ + where: {code: 'mana'} + }, myOptions); + + const manaClaimComponent = await models.Component.findOne({ + where: {code: 'manaClaim'} + }, myOptions); + + const oldComponent = await models.SaleComponent.find({ + where: { + and: [ + {saleFk: sale.id}, + {componentFk: {inq: [manaComponent.id, manaClaimComponent.id]}} + ] + } + }, myOptions); + + let newComponent; + if (oldComponent) { + const filter = { + saleFk: sale.id, + componentFk: oldComponent.componentFk + }; + await models.SaleComponent.destroyAll(filter, myOptions); + + newComponent = models.SaleComponent.create({ + saleFk: sale.id, + value: value, + componentFk: componentId + }, myOptions); + } else { + newComponent = models.SaleComponent.create({ + saleFk: sale.id, + value: value, + componentFk: componentId}, myOptions); + } const updatedSale = sale.updateAttribute('discount', newDiscount, myOptions); From 40075e7e21633569a5c7eb29d5f60fbedbb4a9c1 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 14 Oct 2022 14:38:47 +0200 Subject: [PATCH 22/77] fix: testFront --- modules/ticket/front/sale/index.js | 1 - modules/ticket/front/sale/index.spec.js | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index 714baa126..0d1d28b0e 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -273,7 +273,6 @@ class Controller extends Section { const hasChanges = sales.some(sale => { return sale.discount != newDiscount; }); - if (newDiscount != null && hasChanges) this.updateDiscount(sales); diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index 28d874932..a59eb3865 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -393,7 +393,7 @@ describe('Ticket', () => { secondSelectedSale.checked = true; const expectedSales = [firstSelectedSale, secondSelectedSale]; - controller.$.editDiscountDialog = {hide: jest.fn()}; + controller.$.editDiscount = {hide: jest.fn()}; controller.edit = { discount: 10, sales: expectedSales @@ -402,7 +402,7 @@ describe('Ticket', () => { controller.changeMultipleDiscount(); expect(controller.updateDiscount).toHaveBeenCalledWith(expectedSales); - expect(controller.$.editDiscountDialog.hide).toHaveBeenCalledWith(); + expect(controller.$.editDiscount.hide).toHaveBeenCalledWith(); }); it('should not call to the updateDiscount() method and then to the editDiscountDialog hide() method', () => { @@ -417,7 +417,7 @@ describe('Ticket', () => { secondSelectedSale.discount = 10; const expectedSales = [firstSelectedSale, secondSelectedSale]; - controller.$.editDiscountDialog = {hide: jest.fn()}; + controller.$.editDiscount = {hide: jest.fn()}; controller.edit = { discount: 10, sales: expectedSales @@ -426,7 +426,7 @@ describe('Ticket', () => { controller.changeMultipleDiscount(); expect(controller.updateDiscount).not.toHaveBeenCalledWith(expectedSales); - expect(controller.$.editDiscountDialog.hide).toHaveBeenCalledWith(); + expect(controller.$.editDiscount.hide).toHaveBeenCalledWith(); }); }); From a11ef73dfbb70a780dc503767cab76c3e7476112 Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 17 Oct 2022 08:01:55 +0200 Subject: [PATCH 23/77] refactor: code repetead --- .../back/methods/ticket/updateDiscount.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index 99daac512..d452b03fe 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -141,17 +141,9 @@ module.exports = Self => { }; await models.SaleComponent.destroyAll(filter, myOptions); - newComponent = models.SaleComponent.create({ - saleFk: sale.id, - value: value, - componentFk: componentId - }, myOptions); - } else { - newComponent = models.SaleComponent.create({ - saleFk: sale.id, - value: value, - componentFk: componentId}, myOptions); - } + await createSaleComponent(sale.id, value, componentId, myOptions); + } else + await createSaleComponent(sale.id, value, componentId, myOptions); const updatedSale = sale.updateAttribute('discount', newDiscount, myOptions); @@ -198,4 +190,14 @@ module.exports = Self => { throw e; } }; + + async function createSaleComponent(saleId, value, componentId, myOptions) { + const models = Self.app.models; + + newComponent = models.SaleComponent.create({ + saleFk: saleId, + value: value, + componentFk: componentId + }, myOptions); + } }; From 06ecceb478373820cfd0d0c9968ae200462c034c Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 17 Oct 2022 08:02:19 +0200 Subject: [PATCH 24/77] refactor: lines to long --- .../back/methods/ticket/specs/componentUpdate.spec.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js index 2aa2a07c4..a8a4d5048 100644 --- a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js +++ b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js @@ -18,13 +18,17 @@ describe('ticket componentUpdate()', () => { beforeAll(async() => { const deliveryComponenet = await models.Component.findOne({where: {code: 'delivery'}}); deliveryComponentId = deliveryComponenet.id; - componentOfSaleSeven = `SELECT value FROM vn.saleComponent WHERE saleFk = 7 AND componentFk = ${deliveryComponentId}`; - componentOfSaleEight = `SELECT value FROM vn.saleComponent WHERE saleFk = 8 AND componentFk = ${deliveryComponentId}`; - + componentOfSaleSeven = `SELECT value + FROM vn.saleComponent + WHERE saleFk = 7 AND componentFk = ${deliveryComponentId}`; + componentOfSaleEight = `SELECT value + FROM vn.saleComponent + WHERE saleFk = 8 AND componentFk = ${deliveryComponentId}`; [componentValue] = await models.SaleComponent.rawSql(componentOfSaleSeven); firstvalueBeforeChange = componentValue.value; [componentValue] = await models.SaleComponent.rawSql(componentOfSaleEight); + secondvalueBeforeChange = componentValue.value; }); From cdce628038a2bea5aefd5589a4e68d13a900d69c Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 17 Oct 2022 08:11:50 +0200 Subject: [PATCH 25/77] refactor: delete line break --- modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js index a8a4d5048..b3291c25a 100644 --- a/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js +++ b/modules/ticket/back/methods/ticket/specs/componentUpdate.spec.js @@ -28,7 +28,6 @@ describe('ticket componentUpdate()', () => { firstvalueBeforeChange = componentValue.value; [componentValue] = await models.SaleComponent.rawSql(componentOfSaleEight); - secondvalueBeforeChange = componentValue.value; }); From 787d10aac6c08fb7e69643b622505d9d1d3a47d4 Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 17 Oct 2022 08:29:09 +0200 Subject: [PATCH 26/77] fix: select one result only --- modules/ticket/back/methods/ticket/updateDiscount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index d452b03fe..bfa3cdbf6 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -124,7 +124,7 @@ module.exports = Self => { where: {code: 'manaClaim'} }, myOptions); - const oldComponent = await models.SaleComponent.find({ + const [oldComponent] = await models.SaleComponent.find({ where: { and: [ {saleFk: sale.id}, From f85c2e9700dcada8120b286a54a99fd97c333d8e Mon Sep 17 00:00:00 2001 From: vicent Date: Mon, 17 Oct 2022 14:01:21 +0200 Subject: [PATCH 27/77] refactor: delete code repeated --- modules/ticket/back/methods/ticket/updateDiscount.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index bfa3cdbf6..7e7c4e06d 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -140,10 +140,9 @@ module.exports = Self => { componentFk: oldComponent.componentFk }; await models.SaleComponent.destroyAll(filter, myOptions); + } - await createSaleComponent(sale.id, value, componentId, myOptions); - } else - await createSaleComponent(sale.id, value, componentId, myOptions); + await createSaleComponent(sale.id, value, componentId, myOptions); const updatedSale = sale.updateAttribute('discount', newDiscount, myOptions); From eb58839839dc7e9a560ce4bf21af876f998b57aa Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 17 Oct 2022 15:13:27 +0200 Subject: [PATCH 28/77] feat(invoiceIn): invoiceInPdf --- .../back/methods/invoice-in/invoiceInPdf.js | 50 +++ modules/invoiceIn/back/models/invoice-in.js | 1 + modules/invoiceIn/front/descriptor/index.html | 27 +- modules/invoiceIn/front/descriptor/index.js | 3 + .../reports/invoiceIn/assets/css/import.js | 12 + .../reports/invoiceIn/assets/css/style.css | 42 +++ .../invoiceIn/assets/images/europe.png | Bin 0 -> 55634 bytes .../reports/invoiceIn/invoiceIn.html | 319 ++++++++++++++++++ .../templates/reports/invoiceIn/invoiceIn.js | 33 ++ .../templates/reports/invoiceIn/locale/en.yml | 36 ++ .../templates/reports/invoiceIn/locale/es.yml | 36 ++ .../reports/invoiceIn/sql/invoice.sql | 5 + 12 files changed, 556 insertions(+), 8 deletions(-) create mode 100644 modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js create mode 100644 print/templates/reports/invoiceIn/assets/css/import.js create mode 100644 print/templates/reports/invoiceIn/assets/css/style.css create mode 100644 print/templates/reports/invoiceIn/assets/images/europe.png create mode 100644 print/templates/reports/invoiceIn/invoiceIn.html create mode 100755 print/templates/reports/invoiceIn/invoiceIn.js create mode 100644 print/templates/reports/invoiceIn/locale/en.yml create mode 100644 print/templates/reports/invoiceIn/locale/es.yml create mode 100644 print/templates/reports/invoiceIn/sql/invoice.sql diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js new file mode 100644 index 000000000..71ba5710e --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js @@ -0,0 +1,50 @@ +const {Report} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('invoiceInPdf', { + description: 'Returns the delivery note pdf', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The ticket 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/invoiceInPdf', + verb: 'GET' + } + }); + + Self.invoiceInPdf = async(ctx, id) => { + const args = Object.assign({}, ctx.args); + const params = {lang: ctx.req.getLocale()}; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const report = new Report('invoiceIn', params); + const stream = await report.toPdfStream(); + + return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; + }; +}; diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js index 3b5aa65d9..e2c132671 100644 --- a/modules/invoiceIn/back/models/invoice-in.js +++ b/modules/invoiceIn/back/models/invoice-in.js @@ -4,4 +4,5 @@ module.exports = Self => { require('../methods/invoice-in/clone')(Self); require('../methods/invoice-in/toBook')(Self); require('../methods/invoice-in/getTotals')(Self); + require('../methods/invoice-in/invoiceInPdf')(Self); }; diff --git a/modules/invoiceIn/front/descriptor/index.html b/modules/invoiceIn/front/descriptor/index.html index 33f9ee8c6..c23a14ffc 100644 --- a/modules/invoiceIn/front/descriptor/index.html +++ b/modules/invoiceIn/front/descriptor/index.html @@ -1,5 +1,5 @@ - @@ -10,7 +10,6 @@ translate> To book - Clone Invoice + + Show Invoice as PDF + + + Send Invoice as PDF +
@@ -37,7 +48,7 @@ - {{$ctrl.invoiceIn.supplier.nickname}} + {{$ctrl.invoiceIn.supplier.nickname}}
@@ -57,9 +68,9 @@ icon="icon-invoice-in">
- +
- + - - \ No newline at end of file +
diff --git a/modules/invoiceIn/front/descriptor/index.js b/modules/invoiceIn/front/descriptor/index.js index cde324296..0198e868f 100644 --- a/modules/invoiceIn/front/descriptor/index.js +++ b/modules/invoiceIn/front/descriptor/index.js @@ -96,6 +96,9 @@ class Controller extends Descriptor { .then(() => this.$state.reload()) .then(() => this.vnApp.showSuccess(this.$t('InvoiceIn booked'))); } + showPdfInvoice() { + this.vnReport.show(`InvoiceIns/${this.id}/invoiceInPdf`); + } } ngModule.vnComponent('vnInvoiceInDescriptor', { diff --git a/print/templates/reports/invoiceIn/assets/css/import.js b/print/templates/reports/invoiceIn/assets/css/import.js new file mode 100644 index 000000000..37a98dfdd --- /dev/null +++ b/print/templates/reports/invoiceIn/assets/css/import.js @@ -0,0 +1,12 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/report.css`, + `${__dirname}/style.css`]) + .mergeStyles(); diff --git a/print/templates/reports/invoiceIn/assets/css/style.css b/print/templates/reports/invoiceIn/assets/css/style.css new file mode 100644 index 000000000..9fda2a613 --- /dev/null +++ b/print/templates/reports/invoiceIn/assets/css/style.css @@ -0,0 +1,42 @@ +h2 { + font-weight: 100; + color: #555 +} + +.table-title { + margin-bottom: 15px; + font-size: .8rem +} + +.table-title h2 { + margin: 0 15px 0 0 +} + +.ticket-info { + font-size: 22px +} + + +#nickname h2 { + max-width: 400px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +#phytosanitary { + padding-right: 10px +} + +#phytosanitary .flag img { + width: 100% +} + +#phytosanitary .flag .flag-text { + padding-left: 10px; + box-sizing: border-box; +} + +.phytosanitary-info { + margin-top: 10px +} \ No newline at end of file diff --git a/print/templates/reports/invoiceIn/assets/images/europe.png b/print/templates/reports/invoiceIn/assets/images/europe.png new file mode 100644 index 0000000000000000000000000000000000000000..673be92ae2f0647fd1748e12a36bf073aa145d64 GIT binary patch literal 55634 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 + + + + + + + + +
+ + + + + + + + + +
+
+
+
+
+

{{$t('title')}}

+ + + + + + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t('invoice')}}{{invoice.ref}}
{{$t('date')}}{{invoice.issued | date('%d-%m-%Y')}}
+
+
+
+
+
{{$t('invoiceData')}}
+
+

{{client.socialName}}

+
+ {{client.postalAddress}} +
+
+ {{client.postcodeCity}} +
+
+ {{$t('fiscalId')}}: {{client.fi}} +
+
+
+
+
+ + +
+

{{$t('rectifiedInvoices')}}

+ + + + + + + + + + + + + + + + + +
{{$t('invoice')}}{{$t('issued')}}{{$t('amount')}}{{$t('description')}}
{{row.ref}}{{row.issued | date}}{{row.amount | currency('EUR', $i18n.locale)}}{{row.description}}
+
+ + + +
+
+
+

{{$t('deliveryNote')}} +

+
+
+ {{ticket.id}} +
+
+
+

{{$t('shipped')}}

+
+
+
+ {{ticket.shipped | date}} +
+
+ +

{{ticket.nickname}}

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}}{{saleImport(sale) | currency('EUR', $i18n.locale)}}
+ + {{sale.tag5}} {{sale.value5}} + + + {{sale.tag6}} {{sale.value6}} + + + {{sale.tag7}} {{sale.value7}} + +
+ {{$t('subtotal')}} + {{ticketSubtotal(ticket) | currency('EUR', $i18n.locale)}}
+
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('taxBreakdown')}}
{{$t('type')}} + {{$t('taxBase')}} + {{$t('tax')}}{{$t('fee')}}
{{tax.name}} + {{tax.base | currency('EUR', $i18n.locale)}} + {{tax.vatPercent | percentage}}{{tax.vat | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} + {{sumTotal(taxes, 'base') | currency('EUR', $i18n.locale)}} + {{sumTotal(taxes, 'vat') | currency('EUR', $i18n.locale)}}
{{$t('total')}}{{taxTotal | currency('EUR', $i18n.locale)}}
+ +
+
{{$t('notes')}}
+
+ {{invoice.footNotes}} +
+
+
+ + + +
+
+
+
+
+
+ +
+
+ {{$t('plantPassport')}}
+
+
+
+
+
+ A + {{botanical}} +
+
+ B + ES17462130 +
+
+ C + {{ticketsId}} +
+
+ D + ES +
+
+
+
+
+ +
+ + + +
+

{{$t('intrastat')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('code')}}{{$t('description')}}{{$t('stems')}}{{$t('netKg')}}{{$t('amount')}}
{{row.code}}{{row.description}}{{row.stems | number($i18n.locale)}}{{row.netKg | number($i18n.locale)}}{{row.subtotal | currency('EUR', $i18n.locale)}}
+ {{sumTotal(intrastat, 'stems') | number($i18n.locale)}} + + {{sumTotal(intrastat, 'netKg') | number($i18n.locale)}} + + {{sumTotal(intrastat, 'subtotal') | currency('EUR', $i18n.locale)}} +
+
+ + + +
+
+
+
{{$t('observations')}}
+
+
{{$t('wireTransfer')}}
+
{{$t('accountNumber', [invoice.iban])}}
+
+
+
+
+ + +
+
+ + + +
+ + \ No newline at end of file diff --git a/print/templates/reports/invoiceIn/invoiceIn.js b/print/templates/reports/invoiceIn/invoiceIn.js new file mode 100755 index 000000000..3fed1b867 --- /dev/null +++ b/print/templates/reports/invoiceIn/invoiceIn.js @@ -0,0 +1,33 @@ +const Component = require(`vn-print/core/component`); +const Report = require(`vn-print/core/report`); +const reportHeader = new Component('report-header'); +const reportFooter = new Component('report-footer'); +const invoiceIncoterms = new Report('invoice-incoterms'); + +module.exports = { + name: 'invoiceIn', + async serverPrefetch() { + this.invoice = await this.fetchInvoice(this.id); + + if (!this.invoice) + throw new Error('Something went wrong'); + }, + computed: { + }, + methods: { + fetchInvoice(id) { + return this.findOneFromDef('invoice', [id]); + } + }, + components: { + 'report-header': reportHeader.build(), + 'report-footer': reportFooter.build(), + 'invoice-incoterms': invoiceIncoterms.build() + }, + props: { + id: { + type: [Number, String], + description: 'The invoice id' + } + } +}; diff --git a/print/templates/reports/invoiceIn/locale/en.yml b/print/templates/reports/invoiceIn/locale/en.yml new file mode 100644 index 000000000..4e4688b55 --- /dev/null +++ b/print/templates/reports/invoiceIn/locale/en.yml @@ -0,0 +1,36 @@ +reportName: invoice +title: Invoice +invoice: Invoice +clientId: Client +invoiceData: Invoice data +fiscalId: FI / NIF +invoiceRef: Invoice {0} +deliveryNote: Delivery note +shipped: Shipped +date: Date +reference: Ref. +quantity: Qty. +concept: Concept +price: PSP/u +discount: Disc. +vat: VAT +amount: Amount +type: Type +taxBase: Tax base +tax: Tax +fee: Fee +total: Total +subtotal: Subtotal +taxBreakdown: Tax breakdown +notes: Notes +intrastat: Intrastat +code: Code +description: Description +stems: Stems +netKg: Net kg +rectifiedInvoices: Rectified invoices +issued: Issued +plantPassport: Plant passport +observations: Observations +wireTransfer: "Pay method: Transferencia" +accountNumber: "Account number: {0}" \ No newline at end of file diff --git a/print/templates/reports/invoiceIn/locale/es.yml b/print/templates/reports/invoiceIn/locale/es.yml new file mode 100644 index 000000000..d37e77943 --- /dev/null +++ b/print/templates/reports/invoiceIn/locale/es.yml @@ -0,0 +1,36 @@ +reportName: factura +title: Factura +invoice: Factura +clientId: Cliente +invoiceData: Datos de facturación +fiscalId: CIF / NIF +invoiceRef: Factura {0} +deliveryNote: Albarán +shipped: F. envío +date: Fecha +reference: Ref. +quantity: Cant. +concept: Concepto +price: PVP/u +discount: Dto. +vat: IVA +amount: Importe +type: Tipo +taxBase: Base imp. +tax: Tasa +fee: Cuota +total: Total +subtotal: Subtotal +taxBreakdown: Desglose impositivo +notes: Notas +intrastat: Intrastat +code: Código +description: Descripción +stems: Tallos +netKg: KG Neto +rectifiedInvoices: Facturas rectificadas +issued: F. emisión +plantPassport: Pasaporte fitosanitario +observations: Observaciones +wireTransfer: "Forma de pago: Transferencia" +accountNumber: "Número de cuenta: {0}" \ No newline at end of file diff --git a/print/templates/reports/invoiceIn/sql/invoice.sql b/print/templates/reports/invoiceIn/sql/invoice.sql new file mode 100644 index 000000000..e6b1eb995 --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/invoice.sql @@ -0,0 +1,5 @@ +SELECT * + FROM invoiceIn i + JOIN supplier s ON s.id = i.supplierFk + JOIN company c ON c.id = i.companyFk + WHERE i.id = ? From 423454d19ea755b1ebc1667c1ebbb6830a7bfb80 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 18 Oct 2022 11:47:22 +0200 Subject: [PATCH 29/77] feat: add date-picker --- modules/ticket/front/expedition/index.html | 20 ++++++++++++-------- modules/ticket/front/expedition/index.js | 15 ++++++++++----- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index 35f56872c..f4b261533 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -138,25 +138,29 @@ + name="withoutRoute" + ng-click="selectLanded.show('withoutRoute')"> New ticket without route + name="withRoute" + ng-click="selectLanded.show('withRoute')"> New ticket with route + vn-id="selectLanded" + on-accept="$ctrl.createTicket($ctrl.landed, $ctrl.newRoute)"> + + + ng-model="$ctrl.newRoute"> diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index f7674716b..49ba3280d 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -2,6 +2,12 @@ import ngModule from '../module'; import Section from 'salix/components/section'; class Controller extends Section { + constructor($element, $scope) { + super($element, $scope); + this.landed = new Date(); + this.newRoute = null; + } + get checked() { const rows = this.$.model.data || []; const checkedRows = []; @@ -28,7 +34,7 @@ class Controller extends Section { } onRemove() { - const params = {expeditionsIds: this.checked}; + const params = {expeditionIds: this.checked}; const query = `Expeditions/deleteExpeditions`; this.$http.post(query, params) .then(() => { @@ -37,11 +43,10 @@ class Controller extends Section { }); } - createTicket(routeFk) { - const date = new Date(); // esta fecha hay que preguntarla a Fran Monsalvez + createTicket(landed, routeFk) { const ticketParams = { clientId: this.ticket.clientFk, - landed: date, + landed: landed, addressId: this.ticket.addressFk, agencyModeId: this.ticket.agencyModeFk, warehouseId: this.ticket.warehouseFk @@ -50,7 +55,7 @@ class Controller extends Section { this.$http.post(query, ticketParams).then(res => { if (routeFk) this.$http.patch(`Tickets/${res.data.id}`, {routeFk: routeFk}); const params = { - expeditionsIds: this.checked, + expeditionIds: this.checked, ticketId: res.data.id }; const query = `Expeditions/moveExpeditions`; From 5fce93abd5bd36720eec2ffa6d7df0f0ec3cbcd4 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 18 Oct 2022 11:47:37 +0200 Subject: [PATCH 30/77] refactor: change variables name --- modules/ticket/back/methods/expedition/deleteExpeditions.js | 6 +++--- modules/ticket/back/methods/expedition/moveExpeditions.js | 6 +++--- modules/ticket/front/expedition/index.spec.js | 4 ++-- modules/ticket/front/expedition/locale/es.yml | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/ticket/back/methods/expedition/deleteExpeditions.js b/modules/ticket/back/methods/expedition/deleteExpeditions.js index b902b9f0c..5b9d0daaa 100644 --- a/modules/ticket/back/methods/expedition/deleteExpeditions.js +++ b/modules/ticket/back/methods/expedition/deleteExpeditions.js @@ -4,7 +4,7 @@ module.exports = Self => { description: 'Delete the selected expeditions', accessType: 'WRITE', accepts: [{ - arg: 'expeditionsIds', + arg: 'expeditionIds', type: ['number'], required: true, description: 'The expeditions ids to delete' @@ -19,7 +19,7 @@ module.exports = Self => { } }); - Self.deleteExpeditions = async(expeditionsIds, options) => { + Self.deleteExpeditions = async(expeditionIds, options) => { const models = Self.app.models; const myOptions = {}; let tx; @@ -34,7 +34,7 @@ module.exports = Self => { try { const deletedExpeditions = await models.Expedition.destroyAll({ - id: {inq: expeditionsIds} + id: {inq: expeditionIds} }, myOptions); if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/expedition/moveExpeditions.js b/modules/ticket/back/methods/expedition/moveExpeditions.js index 2c521dda7..338568fdb 100644 --- a/modules/ticket/back/methods/expedition/moveExpeditions.js +++ b/modules/ticket/back/methods/expedition/moveExpeditions.js @@ -4,7 +4,7 @@ module.exports = Self => { description: 'Move the selected expeditions to another ticket', accessType: 'WRITE', accepts: [{ - arg: 'expeditionsIds', + arg: 'expeditionIds', type: ['number'], required: true, description: 'The expeditions ids to nove' @@ -25,7 +25,7 @@ module.exports = Self => { } }); - Self.moveExpeditions = async(expeditionsIds, ticketId, options) => { + Self.moveExpeditions = async(expeditionIds, ticketId, options) => { const models = Self.app.models; const myOptions = {}; let tx; @@ -40,7 +40,7 @@ module.exports = Self => { try { const promises = []; - for (let expeditionsId of expeditionsIds) { + for (let expeditionsId of expeditionIds) { const expeditionToUpdate = await models.Expedition.findById(expeditionsId); const expeditionUpdated = expeditionToUpdate.updateAttribute('ticketFk', ticketId, myOptions); promises.push(expeditionUpdated); diff --git a/modules/ticket/front/expedition/index.spec.js b/modules/ticket/front/expedition/index.spec.js index fe23046ce..ad5dafbd7 100644 --- a/modules/ticket/front/expedition/index.spec.js +++ b/modules/ticket/front/expedition/index.spec.js @@ -63,7 +63,7 @@ describe('Ticket', () => { it('should make a query and then call to the model refresh() method', () => { jest.spyOn($scope.model, 'refresh'); - const expectedParams = {expeditionsIds: [1, 2]}; + const expectedParams = {expeditionIds: [1, 2]}; $httpBackend.expect('POST', 'Expeditions/deleteExpeditions', expectedParams).respond(200); controller.onRemove(); $httpBackend.flush(); @@ -97,7 +97,7 @@ describe('Ticket', () => { const expectedResponse = {id: ticketIdToTransfer}; const expectedParams = { - expeditionsIds: [1, 2], + expeditionIds: [1, 2], ticketId: 28 }; $httpBackend.expect('POST', 'Tickets/new', expectedTicket).respond(expectedResponse); diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml index bc3ec3345..68812f985 100644 --- a/modules/ticket/front/expedition/locale/es.yml +++ b/modules/ticket/front/expedition/locale/es.yml @@ -2,4 +2,5 @@ Status log: Hitorial de estados Expedition removed: Expedición eliminada Move: Mover New ticket without route: Nuevo ticket sin ruta -New ticket with route: Nuevo ticket con ruta \ No newline at end of file +New ticket with route: Nuevo ticket con ruta +Id route: Id ruta \ No newline at end of file From ae3e3c3c49c4dc474d96fbd510febaa03fb28f7c Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 18 Oct 2022 12:37:06 +0200 Subject: [PATCH 31/77] fix: id model duplicated --- modules/ticket/front/expedition/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index f4b261533..0ebe388c1 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -98,16 +98,16 @@ - + - + State From 9ea427dbfece8d68a6350e1f9861f2222c31f631 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 18 Oct 2022 13:45:46 +0200 Subject: [PATCH 32/77] refactor: move front to back funcionality --- loopback/locale/es.json | 3 +- .../methods/expedition/moveExpeditions.js | 60 +++++++++++++++---- modules/ticket/front/expedition/index.js | 17 ++---- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 67370b343..bd9de41c2 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -235,5 +235,6 @@ "Dirección incorrecta": "Dirección incorrecta", "Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador", "Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador", - "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente" + "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente", + "This route not exists": "Esta ruta no existe" } \ No newline at end of file diff --git a/modules/ticket/back/methods/expedition/moveExpeditions.js b/modules/ticket/back/methods/expedition/moveExpeditions.js index 338568fdb..d0f8aa893 100644 --- a/modules/ticket/back/methods/expedition/moveExpeditions.js +++ b/modules/ticket/back/methods/expedition/moveExpeditions.js @@ -1,19 +1,47 @@ +const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethod('moveExpeditions', { + Self.remoteMethodCtx('moveExpeditions', { description: 'Move the selected expeditions to another ticket', accessType: 'WRITE', accepts: [{ + arg: 'clientId', + type: 'number', + description: `The client id`, + required: true + }, + { + arg: 'landed', + type: 'date', + description: `The landing date` + }, + { + arg: 'warehouseId', + type: 'number', + description: `The warehouse id`, + required: true + }, + { + arg: 'addressId', + type: 'number', + description: `The address id`, + required: true + }, + { + arg: 'agencyModeId', + type: 'any', + description: `The agencyMode id` + }, + { + arg: 'routeId', + type: 'any', + description: `The route id` + }, + { arg: 'expeditionIds', type: ['number'], required: true, - description: 'The expeditions ids to nove' - }, - { - arg: 'ticketId', - type: 'number', - required: true, - description: 'the ticket id to which the expeditions are added' + description: 'The expeditions ids to move' }], returns: { type: 'object', @@ -25,8 +53,9 @@ module.exports = Self => { } }); - Self.moveExpeditions = async(expeditionIds, ticketId, options) => { + Self.moveExpeditions = async(ctx, options) => { const models = Self.app.models; + const args = ctx.args; const myOptions = {}; let tx; @@ -39,18 +68,23 @@ module.exports = Self => { } try { + if (args.routeId) { + const route = await models.Route.findById(args.routeId, null, myOptions); + if (!route) throw new UserError('This route not exists'); + } + const ticket = await models.Ticket.new(ctx, myOptions); const promises = []; - for (let expeditionsId of expeditionIds) { + for (let expeditionsId of args.expeditionIds) { const expeditionToUpdate = await models.Expedition.findById(expeditionsId); - const expeditionUpdated = expeditionToUpdate.updateAttribute('ticketFk', ticketId, myOptions); + const expeditionUpdated = expeditionToUpdate.updateAttribute('ticketFk', ticket.id, myOptions); promises.push(expeditionUpdated); } - const updated = await Promise.all(promises); + await Promise.all(promises); if (tx) await tx.commit(); - return updated; + return ticket; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/ticket/front/expedition/index.js b/modules/ticket/front/expedition/index.js index 49ba3280d..7ffe2fe5e 100644 --- a/modules/ticket/front/expedition/index.js +++ b/modules/ticket/front/expedition/index.js @@ -44,22 +44,17 @@ class Controller extends Section { } createTicket(landed, routeFk) { - const ticketParams = { + const params = { clientId: this.ticket.clientFk, landed: landed, + warehouseId: this.ticket.warehouseFk, addressId: this.ticket.addressFk, agencyModeId: this.ticket.agencyModeFk, - warehouseId: this.ticket.warehouseFk + routeId: routeFk, + expeditionIds: this.checked }; - const query = `Tickets/new`; - this.$http.post(query, ticketParams).then(res => { - if (routeFk) this.$http.patch(`Tickets/${res.data.id}`, {routeFk: routeFk}); - const params = { - expeditionIds: this.checked, - ticketId: res.data.id - }; - const query = `Expeditions/moveExpeditions`; - this.$http.post(query, params); + const query = `Expeditions/moveExpeditions`; + this.$http.post(query, params).then(res => { this.vnApp.showSuccess(this.$t('Data saved!')); this.$state.go('ticket.card.summary', {id: res.data.id}); }); From c9073641aa89a0b299ccc11de59b5cef8c573748 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 18 Oct 2022 14:58:38 +0200 Subject: [PATCH 33/77] feat(invoiceInPdf): content --- .../back/methods/invoice-in/invoiceInPdf.js | 3 +- .../reports/invoiceIn/invoiceIn.html | 135 +++++++++--------- .../templates/reports/invoiceIn/invoiceIn.js | 49 ++++++- .../templates/reports/invoiceIn/locale/es.yml | 8 +- print/templates/reports/invoiceIn/sql/buy.sql | 18 +++ .../templates/reports/invoiceIn/sql/entry.sql | 8 ++ .../reports/invoiceIn/sql/invoice.sql | 9 +- .../templates/reports/invoiceIn/sql/taxes.sql | 13 ++ 8 files changed, 165 insertions(+), 78 deletions(-) create mode 100644 print/templates/reports/invoiceIn/sql/buy.sql create mode 100644 print/templates/reports/invoiceIn/sql/entry.sql create mode 100644 print/templates/reports/invoiceIn/sql/taxes.sql diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js index 71ba5710e..1b7ca9c68 100644 --- a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js @@ -35,10 +35,11 @@ module.exports = Self => { }); Self.invoiceInPdf = async(ctx, id) => { + console.log(id); const args = Object.assign({}, ctx.args); const params = {lang: ctx.req.getLocale()}; - delete args.ctx; + console.log(args); for (const param in args) params[param] = args[param]; diff --git a/print/templates/reports/invoiceIn/invoiceIn.html b/print/templates/reports/invoiceIn/invoiceIn.html index 1d646a0db..a1d488560 100644 --- a/print/templates/reports/invoiceIn/invoiceIn.html +++ b/print/templates/reports/invoiceIn/invoiceIn.html @@ -6,12 +6,6 @@ - - - - @@ -26,16 +20,16 @@ - - + + - - + + - +
{{$t('clientId')}}{{client.id}}{{$t('supplierId')}}{{invoice.supplierId}}
{{$t('invoice')}}{{invoice.ref}}{{$t('invoiceId')}}{{invoice.id}}
{{$t('date')}}{{invoice.issued | date('%d-%m-%Y')}}{{invoice.created | date('%d-%m-%Y')}}
@@ -45,22 +39,25 @@
{{$t('invoiceData')}}
-

{{client.socialName}}

+

{{invoice.name}}

- {{client.postalAddress}} + {{invoice.postalAddress}}
- {{client.postcodeCity}} + {{invoice.postcodeCity}}
- {{$t('fiscalId')}}: {{client.fi}} + {{$t('fiscalId')}}: {{invoice.nif}} +
+
+ {{$t('phone')}}: {{invoice.phone}}
- - + - - -
+ End of rectified invoices block--> + +
-

{{$t('deliveryNote')}} +

{{$t('invoiceIn')}}

- {{ticket.id}} + {{entry.id}}
-

{{$t('shipped')}}

+

{{$t('date')}}

- {{ticket.shipped | date}} + {{entry.landed | date}}
-

{{ticket.nickname}}

+
+

{{$t('reference')}}

+
+
+
+ {{entry.ref}} +
+
- + - - - - + - + - - - - - - - + + + + - - - +
{{$t('reference')}}{{$t('item')}} {{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('buyingValue')}} {{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}}{{saleImport(sale) | currency('EUR', $i18n.locale)}}{{buy.name}}{{buy.quantity}}{{buy.buyingValue}}{{buyImport(buy) | currency('EUR', $i18n.locale)}}
- - {{sale.tag5}} {{sale.value5}} - - - {{sale.tag6}} {{sale.value6}} - - - {{sale.tag7}} {{sale.value7}} - + + + {{buy.tag5}} {{buy.value5}} + + + {{buy.tag6}} {{buy.value6}} + + + {{buy.tag7}} {{buy.value7}} +
+ {{$t('subtotal')}} {{ticketSubtotal(ticket) | currency('EUR', $i18n.locale)}}{{entrySubtotal(entry) | currency('EUR', $i18n.locale)}}
- +
- + Taxes block
@@ -208,8 +205,8 @@ - - + + End of phytosanitary block - - + + Intrastat block

{{$t('intrastat')}}

@@ -286,9 +283,9 @@
- + End of intrastat block - + Observations block
@@ -300,20 +297,20 @@
- + End of observations block
- + Footer block - + --> - \ No newline at end of file + diff --git a/print/templates/reports/invoiceIn/invoiceIn.js b/print/templates/reports/invoiceIn/invoiceIn.js index 3fed1b867..103f36d1c 100755 --- a/print/templates/reports/invoiceIn/invoiceIn.js +++ b/print/templates/reports/invoiceIn/invoiceIn.js @@ -1,28 +1,71 @@ const Component = require(`vn-print/core/component`); -const Report = require(`vn-print/core/report`); const reportHeader = new Component('report-header'); const reportFooter = new Component('report-footer'); -const invoiceIncoterms = new Report('invoice-incoterms'); module.exports = { name: 'invoiceIn', async serverPrefetch() { this.invoice = await this.fetchInvoice(this.id); + this.taxes = await this.fetchTaxes(this.id); if (!this.invoice) throw new Error('Something went wrong'); + + const entries = await this.fetchEntry(this.id); + const buys = await this.fetchBuy(this.id); + + const map = new Map(); + + for (let entry of entries) { + entry.buys = []; + + map.set(entry.id, entry); + } + + for (let buy of buys) { + const entry = map.get(buy.entryFk); + + if (entry) entry.buys.push(buy); + } + + this.entries = entries; }, computed: { }, methods: { fetchInvoice(id) { return this.findOneFromDef('invoice', [id]); + }, + fetchEntry(id) { + return this.rawSqlFromDef('entry', [id]); + }, + fetchBuy(id) { + return this.rawSqlFromDef('buy', [id]); + }, + fetchTaxes(id) { + return this.rawSqlFromDef(`taxes`, [id]); + }, + buyImport(buy) { + return buy.quantity * buy.buyingValue; + }, + entrySubtotal(entry) { + let subTotal = 0.00; + for (let buy of entry.buys) + subTotal += this.buyImport(buy); + + return subTotal; + }, + sumTotal(rows, prop) { + let total = 0.00; + for (let row of rows) + total += parseFloat(row[prop]); + + return total; } }, components: { 'report-header': reportHeader.build(), 'report-footer': reportFooter.build(), - 'invoice-incoterms': invoiceIncoterms.build() }, props: { id: { diff --git a/print/templates/reports/invoiceIn/locale/es.yml b/print/templates/reports/invoiceIn/locale/es.yml index d37e77943..6386c611a 100644 --- a/print/templates/reports/invoiceIn/locale/es.yml +++ b/print/templates/reports/invoiceIn/locale/es.yml @@ -1,7 +1,7 @@ reportName: factura -title: Factura -invoice: Factura -clientId: Cliente +title: Factura Agrícola +invoiceId: Factura Agrícola +supplierId: Proveedor invoiceData: Datos de facturación fiscalId: CIF / NIF invoiceRef: Factura {0} @@ -33,4 +33,4 @@ issued: F. emisión plantPassport: Pasaporte fitosanitario observations: Observaciones wireTransfer: "Forma de pago: Transferencia" -accountNumber: "Número de cuenta: {0}" \ No newline at end of file +accountNumber: "Número de cuenta: {0}" diff --git a/print/templates/reports/invoiceIn/sql/buy.sql b/print/templates/reports/invoiceIn/sql/buy.sql new file mode 100644 index 000000000..28a80ff9c --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/buy.sql @@ -0,0 +1,18 @@ +SELECT + b.id, + e.id entryFk, + it.name, + b.quantity, + b.buyingValue, + (b.quantity * b.buyingValue) total, + it.tag5, + it.value5, + it.tag6, + it.value6, + it.tag7, + it.value7 + FROM entry e + JOIN invoiceIn i ON i.id = e.invoiceInFk + JOIN buy b ON b.entryFk = e.id + JOIN item it ON it.id = b.itemFk + WHERE i.id = ? diff --git a/print/templates/reports/invoiceIn/sql/entry.sql b/print/templates/reports/invoiceIn/sql/entry.sql new file mode 100644 index 000000000..0b29cd81c --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/entry.sql @@ -0,0 +1,8 @@ +SELECT + e.id, + t.landed, + e.ref + FROM entry e + JOIN invoiceIn i ON i.id = e.invoiceInFk + JOIN travel t ON t.id = e.travelFk + WHERE i.id = ? diff --git a/print/templates/reports/invoiceIn/sql/invoice.sql b/print/templates/reports/invoiceIn/sql/invoice.sql index e6b1eb995..8913a833b 100644 --- a/print/templates/reports/invoiceIn/sql/invoice.sql +++ b/print/templates/reports/invoiceIn/sql/invoice.sql @@ -1,4 +1,11 @@ -SELECT * +SELECT + i.id, + s.id supplierId, + i.created, + s.name, + s.street AS postalAddress, + s.nif, + s.phone FROM invoiceIn i JOIN supplier s ON s.id = i.supplierFk JOIN company c ON c.id = i.companyFk diff --git a/print/templates/reports/invoiceIn/sql/taxes.sql b/print/templates/reports/invoiceIn/sql/taxes.sql new file mode 100644 index 000000000..18ca6db09 --- /dev/null +++ b/print/templates/reports/invoiceIn/sql/taxes.sql @@ -0,0 +1,13 @@ +SELECT + id, + invoiceInFk, + taxableBase, + expenseFk, + taxTypeSageFk, + transactionTypeSageFk, + foreignValue + FROM invoiceInTax iit + JOIN transactionTypeSage ts ON iit.transactionTypeSageFk = ts.id + JOIN taxTypeSage tts ON tts.id = + WHERE io.ref = ? + ORDER BY iot.id From 4b124055869e955f3eb29285674585006c2187de Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 19 Oct 2022 15:15:54 +0200 Subject: [PATCH 34/77] feat: report invoiceInPdf --- db/dump/fixtures.sql | 6 +- .../reports/invoiceIn/invoiceIn.html | 143 +++--------------- .../templates/reports/invoiceIn/invoiceIn.js | 22 ++- .../templates/reports/invoiceIn/locale/en.yml | 27 +--- .../templates/reports/invoiceIn/locale/es.yml | 21 +-- .../reports/invoiceIn/sql/invoice.sql | 4 +- .../templates/reports/invoiceIn/sql/taxes.sql | 19 +-- 7 files changed, 66 insertions(+), 176 deletions(-) diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 7e59c1a54..8e1113781 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -14,10 +14,10 @@ INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`) ('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66); INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`) - VALUES + VALUES (1, 'it@gotamcity.com', 'incidences@gotamcity.com'); -INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) +INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) VALUES ('1', '6'); @@ -349,7 +349,7 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`) INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`) VALUES - (200, 10); + (200, null); INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`) VALUES diff --git a/print/templates/reports/invoiceIn/invoiceIn.html b/print/templates/reports/invoiceIn/invoiceIn.html index a1d488560..4bc6d8ee0 100644 --- a/print/templates/reports/invoiceIn/invoiceIn.html +++ b/print/templates/reports/invoiceIn/invoiceIn.html @@ -46,45 +46,21 @@
{{invoice.postcodeCity}}
-
+
{{$t('fiscalId')}}: {{invoice.nif}}
-
+
{{$t('phone')}}: {{invoice.phone}}
-
-

{{$t('invoiceIn')}} +

{{$t('invoiceId')}}

@@ -153,7 +129,7 @@
- Taxes block +
@@ -175,9 +151,9 @@ - + @@ -185,14 +161,14 @@ - +
{{tax.name}} - {{tax.base | currency('EUR', $i18n.locale)}} + {{tax.taxableBase | currency('EUR', $i18n.locale)}} {{tax.vatPercent | percentage}}{{tax.rate | percentage}} {{tax.vat | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} - {{sumTotal(taxes, 'base') | currency('EUR', $i18n.locale)}} + {{sumTotal(taxes, 'taxableBase') | currency('EUR', $i18n.locale)}} {{sumTotal(taxes, 'vat') | currency('EUR', $i18n.locale)}}
{{$t('total')}}{{taxTotal | currency('EUR', $i18n.locale)}}{{taxTotal() | currency('EUR', $i18n.locale)}}
@@ -205,109 +181,30 @@
- +
{{$t('observations')}}
-
{{$t('wireTransfer')}}
-
{{$t('accountNumber', [invoice.iban])}}
+
{{$t('payMethod')}}
+
{{invoice.payMethod}}
- End of observations block +
- Footer block - - --> +
+ + + diff --git a/print/templates/reports/invoiceIn/invoiceIn.js b/print/templates/reports/invoiceIn/invoiceIn.js index 103f36d1c..cfad062ed 100755 --- a/print/templates/reports/invoiceIn/invoiceIn.js +++ b/print/templates/reports/invoiceIn/invoiceIn.js @@ -42,8 +42,9 @@ module.exports = { fetchBuy(id) { return this.rawSqlFromDef('buy', [id]); }, - fetchTaxes(id) { - return this.rawSqlFromDef(`taxes`, [id]); + async fetchTaxes(id) { + const taxes = await this.rawSqlFromDef(`taxes`, [id]); + return this.taxVat(taxes); }, buyImport(buy) { return buy.quantity * buy.buyingValue; @@ -61,6 +62,23 @@ module.exports = { total += parseFloat(row[prop]); return total; + }, + taxVat(taxes) { + for (tax of taxes) { + let vat = 0; + if (tax.rate && tax.taxableBase) + vat = (tax.rate / 100) * tax.taxableBase; + + tax.vat = vat; + } + + return taxes; + }, + taxTotal() { + const base = this.sumTotal(this.taxes, 'taxableBase'); + const vat = this.sumTotal(this.taxes, 'vat'); + + return base + vat; } }, components: { diff --git a/print/templates/reports/invoiceIn/locale/en.yml b/print/templates/reports/invoiceIn/locale/en.yml index 4e4688b55..7a8767ad3 100644 --- a/print/templates/reports/invoiceIn/locale/en.yml +++ b/print/templates/reports/invoiceIn/locale/en.yml @@ -1,17 +1,16 @@ reportName: invoice -title: Invoice -invoice: Invoice -clientId: Client +title: Agrobusiness invoice +invoiceId: Agrobusiness invoice +supplierId: Proveedor invoiceData: Invoice data +reference: Reference fiscalId: FI / NIF -invoiceRef: Invoice {0} -deliveryNote: Delivery note -shipped: Shipped +phone: Phone date: Date -reference: Ref. +item: Item quantity: Qty. concept: Concept -price: PSP/u +buyingValue: PSP/u discount: Disc. vat: VAT amount: Amount @@ -22,15 +21,5 @@ fee: Fee total: Total subtotal: Subtotal taxBreakdown: Tax breakdown -notes: Notes -intrastat: Intrastat -code: Code -description: Description -stems: Stems -netKg: Net kg -rectifiedInvoices: Rectified invoices -issued: Issued -plantPassport: Plant passport observations: Observations -wireTransfer: "Pay method: Transferencia" -accountNumber: "Account number: {0}" \ No newline at end of file +payMethod: Pay method diff --git a/print/templates/reports/invoiceIn/locale/es.yml b/print/templates/reports/invoiceIn/locale/es.yml index 6386c611a..f2fb28e64 100644 --- a/print/templates/reports/invoiceIn/locale/es.yml +++ b/print/templates/reports/invoiceIn/locale/es.yml @@ -3,15 +3,14 @@ title: Factura Agrícola invoiceId: Factura Agrícola supplierId: Proveedor invoiceData: Datos de facturación +reference: Referencia fiscalId: CIF / NIF -invoiceRef: Factura {0} -deliveryNote: Albarán -shipped: F. envío +phone: Tlf date: Fecha -reference: Ref. +item: Artículo quantity: Cant. concept: Concepto -price: PVP/u +buyingValue: PVP/u discount: Dto. vat: IVA amount: Importe @@ -22,15 +21,5 @@ fee: Cuota total: Total subtotal: Subtotal taxBreakdown: Desglose impositivo -notes: Notas -intrastat: Intrastat -code: Código -description: Descripción -stems: Tallos -netKg: KG Neto -rectifiedInvoices: Facturas rectificadas -issued: F. emisión -plantPassport: Pasaporte fitosanitario observations: Observaciones -wireTransfer: "Forma de pago: Transferencia" -accountNumber: "Número de cuenta: {0}" +payMethod: Método de pago diff --git a/print/templates/reports/invoiceIn/sql/invoice.sql b/print/templates/reports/invoiceIn/sql/invoice.sql index 8913a833b..fe9ef1e9e 100644 --- a/print/templates/reports/invoiceIn/sql/invoice.sql +++ b/print/templates/reports/invoiceIn/sql/invoice.sql @@ -5,8 +5,10 @@ SELECT s.name, s.street AS postalAddress, s.nif, - s.phone + s.phone, + p.name payMethod FROM invoiceIn i JOIN supplier s ON s.id = i.supplierFk JOIN company c ON c.id = i.companyFk + JOIN payMethod p ON p.id = s.payMethodFk WHERE i.id = ? diff --git a/print/templates/reports/invoiceIn/sql/taxes.sql b/print/templates/reports/invoiceIn/sql/taxes.sql index 18ca6db09..20df33f83 100644 --- a/print/templates/reports/invoiceIn/sql/taxes.sql +++ b/print/templates/reports/invoiceIn/sql/taxes.sql @@ -1,13 +1,8 @@ SELECT - id, - invoiceInFk, - taxableBase, - expenseFk, - taxTypeSageFk, - transactionTypeSageFk, - foreignValue - FROM invoiceInTax iit - JOIN transactionTypeSage ts ON iit.transactionTypeSageFk = ts.id - JOIN taxTypeSage tts ON tts.id = - WHERE io.ref = ? - ORDER BY iot.id + ti.iva as name, + ti.PorcentajeIva as rate, + iit.taxableBase + FROM invoiceIn ii + JOIN invoiceInTax iit ON ii.id = iit.invoiceInFk + JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk + WHERE ii.id = ?; From 51cad752984fb2d13f804772aa8bec233430e4f7 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 20 Oct 2022 08:17:15 +0200 Subject: [PATCH 35/77] fix: testFront --- modules/ticket/front/expedition/index.spec.js | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/modules/ticket/front/expedition/index.spec.js b/modules/ticket/front/expedition/index.spec.js index ad5dafbd7..b95d64fa3 100644 --- a/modules/ticket/front/expedition/index.spec.js +++ b/modules/ticket/front/expedition/index.spec.js @@ -83,29 +83,25 @@ describe('Ticket', () => { agencyModeFk: 1, warehouseFk: 1 }; + const routeId = null; controller.ticket = ticket; - const expectedTicket = { - clientId: 1101, - landed: new Date(), - addressId: 121, - agencyModeId: 1, - warehouseId: 1 - }; - - const ticketIdToTransfer = 28; - const expectedResponse = {id: ticketIdToTransfer}; + const ticketToTransfer = {id: 28}; const expectedParams = { - expeditionIds: [1, 2], - ticketId: 28 + clientId: 1101, + landed: new Date(), + warehouseId: 1, + addressId: 121, + agencyModeId: 1, + routeId: null, + expeditionIds: [1, 2] }; - $httpBackend.expect('POST', 'Tickets/new', expectedTicket).respond(expectedResponse); - $httpBackend.expect('POST', 'Expeditions/moveExpeditions', expectedParams).respond(200); - controller.createTicket(); + $httpBackend.expect('POST', 'Expeditions/moveExpeditions', expectedParams).respond(ticketToTransfer); + controller.createTicket(ticket.landed, routeId); $httpBackend.flush(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.summary', {id: ticketIdToTransfer}); + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.summary', {id: ticketToTransfer.id}); }); }); }); From bcc9acac0ac997d9c54df4e12efc830dc50c1331 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 20 Oct 2022 08:41:00 +0200 Subject: [PATCH 36/77] fix: tBack --- .../expedition/specs/moveExpeditions.spec.js | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js index 0bb402a55..67919e76c 100644 --- a/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js +++ b/modules/ticket/back/methods/expedition/specs/moveExpeditions.spec.js @@ -1,17 +1,33 @@ const models = require('vn-loopback/server/server').models; describe('ticket moveExpeditions()', () => { - it('should delete the selected expeditions', async() => { + it('should move the selected expeditions to new ticket', async() => { const tx = await models.Expedition.beginTransaction({}); + const ctx = { + req: {accessToken: {userId: 9}}, + args: {}, + params: {} + }; + const myCtx = Object.assign({}, ctx); try { const options = {transaction: tx}; + myCtx.args = { + clientId: 1101, + landed: new Date(), + warehouseId: 1, + addressId: 121, + agencyModeId: 1, + routeId: null, + expeditionIds: [1, 2] - const expeditionIds = [12, 13]; - const ticketId = 1; - const result = await models.Expedition.moveExpeditions(expeditionIds, ticketId, options); + }; - expect(result.length).toEqual(2); + const ticket = await models.Expedition.moveExpeditions(myCtx, options); + + const newestTicketIdInFixtures = 27; + + expect(ticket.id).toBeGreaterThan(newestTicketIdInFixtures); await tx.rollback(); } catch (e) { From 2da34d0850de92ee8684fb9d339b59a4ff2cc852 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 20 Oct 2022 09:40:11 +0200 Subject: [PATCH 37/77] feat: add e2e --- e2e/helpers/selectors.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 74dabc31a..dabb4c0cd 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -596,8 +596,14 @@ export default { submitNotesButton: 'button[type=submit]' }, ticketExpedition: { + firstSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(1) vn-check[ng-model="expedition.checked"]', thirdSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(3) vn-check[ng-model="expedition.checked"]', deleteExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="delete"]', + moveExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="keyboard_arrow_down"]', + moreMenuWithoutRoute: 'vn-item[name="withoutRoute"]', + moreMenuWithRoute: 'vn-item[name="withRoute"]', + newRouteId: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.newRoute"]', + saveButton: '.vn-dialog.shown [response="accept"]', expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr' }, ticketPackages: { From 5824fee213055de7401a1ebd3e63f7879199315a Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 20 Oct 2022 09:40:23 +0200 Subject: [PATCH 38/77] feat: add e2e --- e2e/paths/05-ticket/20_moveExpedition.spec.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 e2e/paths/05-ticket/20_moveExpedition.spec.js diff --git a/e2e/paths/05-ticket/20_moveExpedition.spec.js b/e2e/paths/05-ticket/20_moveExpedition.spec.js new file mode 100644 index 000000000..9673c7071 --- /dev/null +++ b/e2e/paths/05-ticket/20_moveExpedition.spec.js @@ -0,0 +1,50 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +fdescribe('Ticket expeditions', () => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('production', 'ticket'); + await page.accessToSearchResult('1'); + await page.accessToSection('ticket.card.expedition'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it(`should move one expedition to new ticket withoute route`, async() => { + await page.waitToClick(selectors.ticketExpedition.thirdSaleCheckbox); + await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton); + await page.waitToClick(selectors.ticketExpedition.moreMenuWithoutRoute); + await page.waitToClick(selectors.ticketExpedition.saveButton); + await page.waitForState('ticket.card.summary'); + await page.accessToSection('ticket.card.expedition'); + + await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {}); + const result = await page + .countElement(selectors.ticketExpedition.expeditionRow); + + expect(result).toEqual(1); + }); + + it(`should move one expedition to new ticket with route`, async() => { + await page.waitToClick(selectors.ticketExpedition.firstSaleCheckbox); + await page.waitToClick(selectors.ticketExpedition.moveExpeditionButton); + await page.waitToClick(selectors.ticketExpedition.moreMenuWithRoute); + await page.write(selectors.ticketExpedition.newRouteId, '1'); + await page.waitToClick(selectors.ticketExpedition.saveButton); + await page.waitForState('ticket.card.summary'); + await page.accessToSection('ticket.card.expedition'); + + await page.waitForSelector(selectors.ticketExpedition.expeditionRow, {}); + const result = await page + .countElement(selectors.ticketExpedition.expeditionRow); + + expect(result).toEqual(1); + }); +}); From dd15d06f5edfb7c439482764551175e7fe225b70 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 20 Oct 2022 10:21:44 +0200 Subject: [PATCH 39/77] delete focus test --- e2e/paths/05-ticket/20_moveExpedition.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/paths/05-ticket/20_moveExpedition.spec.js b/e2e/paths/05-ticket/20_moveExpedition.spec.js index 9673c7071..cf1c5ded3 100644 --- a/e2e/paths/05-ticket/20_moveExpedition.spec.js +++ b/e2e/paths/05-ticket/20_moveExpedition.spec.js @@ -1,7 +1,7 @@ import selectors from '../../helpers/selectors.js'; import getBrowser from '../../helpers/puppeteer'; -fdescribe('Ticket expeditions', () => { +describe('Ticket expeditions', () => { let browser; let page; From 29093d7d30e98083c36ee81bebb5c5ed95453020 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 20 Oct 2022 14:25:55 +0200 Subject: [PATCH 40/77] =?UTF-8?q?feat:=20en=20vez=20de=20consultar=20si=20?= =?UTF-8?q?el=20usuario=20est=C3=A1=20en=20la=20tabla=20workerMana,=20cons?= =?UTF-8?q?ultar=20es=20si=20el=20usuario=20est=C3=A1=20en=20un=20departam?= =?UTF-8?q?ento=20que=20cuelga=20de=20ventas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../back/methods/sale/specs/updatePrice.spec.js | 11 ++++++++++- modules/ticket/back/methods/sale/updatePrice.js | 8 ++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js index e76511421..75be665fb 100644 --- a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js @@ -79,10 +79,19 @@ describe('sale updatePrice()', () => { const price = 5.4; const originalSalesPersonMana = await models.WorkerMana.findById(18, null, options); const manaComponent = await models.Component.findOne({where: {code: 'mana'}}, options); + const teamCLopez = 96; + const userId = 18; + await models.Sale.rawSql(`UPDATE vn.business b + JOIN vn.department d ON d.id = b.departmentFk + SET b.departmentFk = ? + WHERE b.id = ?`, [teamCLopez, userId]); await models.Sale.updatePrice(ctx, saleId, price, options); const updatedSale = await models.Sale.findById(saleId, null, options); - createdSaleComponent = await models.SaleComponent.findOne({where: {saleFk: saleId, componentFk: manaComponent.id}}, options); + const createdSaleComponent = await models.SaleComponent.findOne({ + where: { + saleFk: saleId, componentFk: manaComponent.id + }}, options); expect(updatedSale.price).toBe(price); expect(createdSaleComponent.value).toEqual(-2.04); diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index bbd9d154d..dbd4ed091 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -77,7 +77,12 @@ module.exports = Self => { const oldPrice = sale.price; const userId = ctx.req.accessToken.userId; - const usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, myOptions); + + const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); + const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); + const workerDepartment = await models.WorkerDepartment.findById(userId); + const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); + const componentCode = usesMana ? 'mana' : 'buyerDiscount'; const discount = await models.Component.findOne({where: {code: componentCode}}, myOptions); const componentId = discount.id; @@ -88,7 +93,6 @@ module.exports = Self => { saleFk: id }; const saleComponent = await models.SaleComponent.findOne({where}, myOptions); - if (saleComponent) { await models.SaleComponent.updateAll(where, { value: saleComponent.value + componentValue From 6891190c72e019cb8c2c2232a028e27a47a1ea56 Mon Sep 17 00:00:00 2001 From: vicent Date: Thu, 20 Oct 2022 15:05:46 +0200 Subject: [PATCH 41/77] refactor: create endpoint to unify code --- db/changes/10491-august/00-aclUsesMana.sql | 3 ++ .../ticket/back/methods/sale/updatePrice.js | 10 +++--- modules/ticket/back/methods/sale/usesMana.js | 31 +++++++++++++++++++ modules/ticket/back/models/sale.js | 1 + 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 db/changes/10491-august/00-aclUsesMana.sql create mode 100644 modules/ticket/back/methods/sale/usesMana.js diff --git a/db/changes/10491-august/00-aclUsesMana.sql b/db/changes/10491-august/00-aclUsesMana.sql new file mode 100644 index 000000000..5bb7178dd --- /dev/null +++ b/db/changes/10491-august/00-aclUsesMana.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Sale', 'usesMana', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index dbd4ed091..d325d6eaa 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -78,11 +78,13 @@ module.exports = Self => { const oldPrice = sale.price; const userId = ctx.req.accessToken.userId; - const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); - const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); - const workerDepartment = await models.WorkerDepartment.findById(userId); - const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); + // const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); + // const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); + // const workerDepartment = await models.WorkerDepartment.findById(userId); + // const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); + const usesMana = await models.Sale.usesMana(); + console.log(usesMana ? 'mana' : 'buyerDiscount'); const componentCode = usesMana ? 'mana' : 'buyerDiscount'; const discount = await models.Component.findOne({where: {code: componentCode}}, myOptions); const componentId = discount.id; diff --git a/modules/ticket/back/methods/sale/usesMana.js b/modules/ticket/back/methods/sale/usesMana.js new file mode 100644 index 000000000..7ff2a5e2a --- /dev/null +++ b/modules/ticket/back/methods/sale/usesMana.js @@ -0,0 +1,31 @@ +module.exports = Self => { + Self.remoteMethodCtx('usesMana', { + description: 'Returns if the worker uses mana', + accessType: 'WRITE', + accepts: [], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/usesMana`, + verb: 'POST' + } + }); + + Self.usesMana = async(ctx, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); + const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); + const workerDepartment = await models.WorkerDepartment.findById(userId); + const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); + + return usesMana ? true : false; + }; +}; diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index 2a4457263..ae247fc24 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -8,6 +8,7 @@ module.exports = Self => { require('../methods/sale/recalculatePrice')(Self); require('../methods/sale/refund')(Self); require('../methods/sale/canEdit')(Self); + require('../methods/sale/usesMana')(Self); Self.validatesPresenceOf('concept', { message: `Concept cannot be blank` From 066cf6f69bd3f036edd86caf47f414b1cd9990cf Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 20 Oct 2022 15:11:56 +0200 Subject: [PATCH 42/77] add show dialog --- modules/invoiceIn/front/descriptor/index.html | 23 ++++++++++++++++--- modules/invoiceIn/front/locale/es.yml | 2 ++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/modules/invoiceIn/front/descriptor/index.html b/modules/invoiceIn/front/descriptor/index.html index c23a14ffc..faff5b976 100644 --- a/modules/invoiceIn/front/descriptor/index.html +++ b/modules/invoiceIn/front/descriptor/index.html @@ -26,14 +26,12 @@ Clone Invoice Show Invoice as PDF Send Invoice as PDF @@ -94,3 +92,22 @@ + + + + + {{sendPdfConfirmation.data.email}} + Are you sure you want to send it? + + + + + + + + diff --git a/modules/invoiceIn/front/locale/es.yml b/modules/invoiceIn/front/locale/es.yml index 4f36b33fa..8cdea3323 100644 --- a/modules/invoiceIn/front/locale/es.yml +++ b/modules/invoiceIn/front/locale/es.yml @@ -19,3 +19,5 @@ To book: Contabilizar Total amount: Total importe Total net: Total neto Total stems: Total tallos +Show Invoice as PDF: Ver Factura Agrícola como PDF +Send Invoice as PDF: Enviar Factura Agrícola como PDF From 5da29fe38158e71d3d8fab36fd2fb9deab0bf137 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 21 Oct 2022 08:00:24 +0200 Subject: [PATCH 43/77] refactor: unify duplicated code --- modules/ticket/back/methods/sale/updatePrice.js | 8 +------- modules/ticket/back/methods/sale/usesMana.js | 6 +++--- modules/ticket/back/methods/ticket/updateDiscount.js | 7 +------ 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index d325d6eaa..302522cfb 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -78,13 +78,7 @@ module.exports = Self => { const oldPrice = sale.price; const userId = ctx.req.accessToken.userId; - // const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); - // const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); - // const workerDepartment = await models.WorkerDepartment.findById(userId); - // const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); - - const usesMana = await models.Sale.usesMana(); - console.log(usesMana ? 'mana' : 'buyerDiscount'); + const usesMana = await models.Sale.usesMana(ctx, myOptions); const componentCode = usesMana ? 'mana' : 'buyerDiscount'; const discount = await models.Component.findOne({where: {code: componentCode}}, myOptions); const componentId = discount.id; diff --git a/modules/ticket/back/methods/sale/usesMana.js b/modules/ticket/back/methods/sale/usesMana.js index 7ff2a5e2a..093057dca 100644 --- a/modules/ticket/back/methods/sale/usesMana.js +++ b/modules/ticket/back/methods/sale/usesMana.js @@ -1,7 +1,7 @@ module.exports = Self => { Self.remoteMethodCtx('usesMana', { description: 'Returns if the worker uses mana', - accessType: 'WRITE', + accessType: 'READ', accepts: [], returns: { type: 'boolean', @@ -9,7 +9,7 @@ module.exports = Self => { }, http: { path: `/usesMana`, - verb: 'POST' + verb: 'GET' } }); @@ -23,7 +23,7 @@ module.exports = Self => { const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); - const workerDepartment = await models.WorkerDepartment.findById(userId); + const workerDepartment = await models.WorkerDepartment.findById(userId, null, myOptions); const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); return usesMana ? true : false; diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index b1291a45b..9419b9a40 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -98,12 +98,7 @@ module.exports = Self => { if (isLocked || (!hasAllowedRoles && alertLevel > 0)) throw new UserError(`The sales of this ticket can't be modified`); - const usesMana = await models.WorkerMana.findOne({ - where: { - workerFk: userId - }, - fields: 'amount'}, myOptions); - + const usesMana = await models.Sale.usesMana(ctx); const componentCode = usesMana ? manaCode : 'buyerDiscount'; const discountComponent = await models.Component.findOne({ where: {code: componentCode}}, myOptions); From 6f31bea21f50306ef47133afeacb447b2fb709a0 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 21 Oct 2022 08:00:48 +0200 Subject: [PATCH 44/77] feat: update testBack --- db/changes/10491-august/00-aclBusiness.sql | 3 ++ modules/client/back/model-config.json | 3 ++ modules/client/back/models/business.json | 27 +++++++++++ .../methods/sale/specs/updatePrice.spec.js | 11 ++--- .../back/methods/sale/specs/usesMana.spec.js | 48 +++++++++++++++++++ 5 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 db/changes/10491-august/00-aclBusiness.sql create mode 100644 modules/client/back/models/business.json create mode 100644 modules/ticket/back/methods/sale/specs/usesMana.spec.js diff --git a/db/changes/10491-august/00-aclBusiness.sql b/db/changes/10491-august/00-aclBusiness.sql new file mode 100644 index 000000000..f1f7a3632 --- /dev/null +++ b/db/changes/10491-august/00-aclBusiness.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Business', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/modules/client/back/model-config.json b/modules/client/back/model-config.json index b2e600610..4ef34ca3a 100644 --- a/modules/client/back/model-config.json +++ b/modules/client/back/model-config.json @@ -8,6 +8,9 @@ "BankEntity": { "dataSource": "vn" }, + "Business": { + "dataSource": "vn" + }, "BusinessType": { "dataSource": "vn" }, diff --git a/modules/client/back/models/business.json b/modules/client/back/models/business.json new file mode 100644 index 000000000..7ad2d307f --- /dev/null +++ b/modules/client/back/models/business.json @@ -0,0 +1,27 @@ +{ + "name": "Business", + "base": "VnModel", + "options": { + "mysql": { + "table": "business" + } + }, + "properties": { + "id": { + "type": "number", + "id": true + } + }, + "relations": { + "worker": { + "type": "belongsTo", + "model": "Worker", + "foreignKey": "workerFk" + }, + "department": { + "type": "belongsTo", + "model": "Department", + "foreignKey": "departmentFk" + } + } +} \ No newline at end of file diff --git a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js index 75be665fb..f8cb5a4ec 100644 --- a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -describe('sale updatePrice()', () => { +fdescribe('sale updatePrice()', () => { const ctx = { req: { accessToken: {userId: 18}, @@ -80,11 +80,10 @@ describe('sale updatePrice()', () => { const originalSalesPersonMana = await models.WorkerMana.findById(18, null, options); const manaComponent = await models.Component.findOne({where: {code: 'mana'}}, options); const teamCLopez = 96; - const userId = 18; - await models.Sale.rawSql(`UPDATE vn.business b - JOIN vn.department d ON d.id = b.departmentFk - SET b.departmentFk = ? - WHERE b.id = ?`, [teamCLopez, userId]); + const userId = ctx.req.accessToken.userId; + + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamCLopez, options); await models.Sale.updatePrice(ctx, saleId, price, options); const updatedSale = await models.Sale.findById(saleId, null, options); diff --git a/modules/ticket/back/methods/sale/specs/usesMana.spec.js b/modules/ticket/back/methods/sale/specs/usesMana.spec.js new file mode 100644 index 000000000..4e14ed2c9 --- /dev/null +++ b/modules/ticket/back/methods/sale/specs/usesMana.spec.js @@ -0,0 +1,48 @@ +const models = require('vn-loopback/server/server').models; + +describe('sale usesMana()', () => { + const ctx = { + req: { + accessToken: {userId: 18} + } + }; + + it('should return that the worker uses mana', async() => { + const tx = await models.Sale.beginTransaction({}); + + try { + const options = {transaction: tx}; + const teamCLopez = 96; + const userId = ctx.req.accessToken.userId; + + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamCLopez, options); + + const usesMana = await models.Sale.usesMana(ctx, options); + + expect(usesMana).toBe(true); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return that the worker not uses mana', async() => { + const tx = await models.Sale.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const usesMana = await models.Sale.usesMana(ctx, options); + + expect(usesMana).toBe(false); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); From e7261573d7954cd9ce129efc0dd945e1114febfe Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 21 Oct 2022 08:01:22 +0200 Subject: [PATCH 45/77] delete focus --- modules/ticket/back/methods/sale/specs/updatePrice.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js index f8cb5a4ec..15b00a732 100644 --- a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -fdescribe('sale updatePrice()', () => { +describe('sale updatePrice()', () => { const ctx = { req: { accessToken: {userId: 18}, From 2d0cb60b50825144113edde29f97f084240002ac Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 21 Oct 2022 08:55:05 +0200 Subject: [PATCH 46/77] feat: check if uses mana --- modules/ticket/front/sale/index.html | 14 ++++++++++++-- modules/ticket/front/sale/index.js | 8 ++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 42eb10cb0..35e45a812 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -264,6 +264,16 @@
+ + + + + + @@ -278,7 +288,7 @@
-
Mana: {{::$ctrl.edit.mana | currency: 'EUR':0}}
+
MANÁ: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
- + { + this.useMana = res.data; + }); + } + /** * Returns checked instances * From c33cab7b10b94be095f0b96e4c2860e68e3fcf36 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 21 Oct 2022 09:01:08 +0200 Subject: [PATCH 47/77] fix: test back --- .../back/methods/ticket/specs/updateDiscount.spec.js | 10 ++++++++++ modules/ticket/back/methods/ticket/updateDiscount.js | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js index 1873207aa..5249fe5d8 100644 --- a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js +++ b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js @@ -110,6 +110,11 @@ describe('sale updateDiscount()', () => { const componentId = manaDiscount.id; const manaCode = 'mana'; + const teamCLopez = 96; + const userId = ctx.req.accessToken.userId; + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamCLopez, options); + await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, manaCode, options); const updatedSale = await models.Sale.findById(originalSaleId, null, options); @@ -150,6 +155,11 @@ describe('sale updateDiscount()', () => { const componentId = manaDiscount.id; const manaCode = 'manaClaim'; + const teamCLopez = 96; + const userId = ctx.req.accessToken.userId; + const business = await models.Business.findOne({where: {workerFk: userId}}, options); + await business.updateAttribute('departmentFk', teamCLopez, options); + await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, manaCode, options); const updatedSale = await models.Sale.findById(originalSaleId, null, options); diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index 9419b9a40..8bc79ca9c 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -98,7 +98,7 @@ module.exports = Self => { if (isLocked || (!hasAllowedRoles && alertLevel > 0)) throw new UserError(`The sales of this ticket can't be modified`); - const usesMana = await models.Sale.usesMana(ctx); + const usesMana = await models.Sale.usesMana(ctx, myOptions); const componentCode = usesMana ? manaCode : 'buyerDiscount'; const discountComponent = await models.Component.findOne({ where: {code: componentCode}}, myOptions); From f968b19d1e2603036050fbc2b5b17680f5b05d13 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 21 Oct 2022 09:08:17 +0200 Subject: [PATCH 48/77] fix: testFront --- modules/ticket/front/sale/index.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index 28d874932..a7260a3ec 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -115,6 +115,7 @@ describe('Ticket', () => { const expectedAmount = 250; $httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount); + $httpBackend.expect('GET', 'Sales/usesMana').respond(200); $httpBackend.expect('GET', 'WorkerManas/getCurrentWorkerMana').respond(200, expectedAmount); controller.getMana(); $httpBackend.flush(); From 18c119c62a88a0e1959d1af2e00d22f9a91e37b1 Mon Sep 17 00:00:00 2001 From: vicent Date: Fri, 21 Oct 2022 09:20:27 +0200 Subject: [PATCH 49/77] change acl --- db/changes/10491-august/00-aclBusiness.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/changes/10491-august/00-aclBusiness.sql b/db/changes/10491-august/00-aclBusiness.sql index f1f7a3632..8ea2c6d83 100644 --- a/db/changes/10491-august/00-aclBusiness.sql +++ b/db/changes/10491-august/00-aclBusiness.sql @@ -1,3 +1,3 @@ INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES - ('Business', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file + ('Business', '*', '*', 'ALLOW', 'ROLE', 'hr'); \ No newline at end of file From 9e0eab5a77dab1f8aa66ccb940cd6b69be041d95 Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 24 Oct 2022 15:15:23 +0200 Subject: [PATCH 50/77] feat(invoiceIn): email --- db/changes/10491-august/00-invoiceInPdf.sql | 4 ++ db/changes/10491-august/delete.keep | 0 .../back/methods/invoice-in/invoiceInEmail.js | 56 +++++++++++++++++++ .../back/methods/invoice-in/invoiceInPdf.js | 5 +- modules/invoiceIn/back/models/invoice-in.js | 1 + modules/invoiceIn/back/models/invoice-in.json | 5 ++ modules/invoiceIn/front/card/index.js | 8 +++ modules/invoiceIn/front/descriptor/index.html | 3 +- modules/invoiceIn/front/descriptor/index.js | 14 ++++- .../email/delivery-note-link/locale/pt.yml | 2 +- .../email/invoiceIn/assets/css/import.js | 11 ++++ .../email/invoiceIn/attachments.json | 6 ++ .../templates/email/invoiceIn/invoiceIn.html | 47 ++++++++++++++++ print/templates/email/invoiceIn/invoiceIn.js | 19 +++++++ print/templates/email/invoiceIn/locale/en.yml | 5 ++ print/templates/email/invoiceIn/locale/es.yml | 5 ++ print/templates/email/invoiceIn/locale/fr.yml | 5 ++ print/templates/email/invoiceIn/locale/pt.yml | 5 ++ .../reports/invoiceIn/invoiceIn.html | 1 + .../templates/reports/invoiceIn/locale/en.yml | 4 +- .../reports/invoiceIn/sql/invoice.sql | 3 +- 21 files changed, 199 insertions(+), 10 deletions(-) create mode 100644 db/changes/10491-august/00-invoiceInPdf.sql delete mode 100644 db/changes/10491-august/delete.keep create mode 100644 modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js create mode 100644 print/templates/email/invoiceIn/assets/css/import.js create mode 100644 print/templates/email/invoiceIn/attachments.json create mode 100644 print/templates/email/invoiceIn/invoiceIn.html create mode 100755 print/templates/email/invoiceIn/invoiceIn.js create mode 100644 print/templates/email/invoiceIn/locale/en.yml create mode 100644 print/templates/email/invoiceIn/locale/es.yml create mode 100644 print/templates/email/invoiceIn/locale/fr.yml create mode 100644 print/templates/email/invoiceIn/locale/pt.yml diff --git a/db/changes/10491-august/00-invoiceInPdf.sql b/db/changes/10491-august/00-invoiceInPdf.sql new file mode 100644 index 000000000..d7dc038aa --- /dev/null +++ b/db/changes/10491-august/00-invoiceInPdf.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('InvoiceIn', 'invoiceInPdf', 'READ', 'ALLOW', 'ROLE', 'administrative'), + ('InvoiceIn', 'invoiceInEmail', 'WRITE', 'ALLOW', 'ROLE', 'administrative'), diff --git a/db/changes/10491-august/delete.keep b/db/changes/10491-august/delete.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js new file mode 100644 index 000000000..b04108bd7 --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js @@ -0,0 +1,56 @@ +const {Email} = require('vn-print'); + +module.exports = Self => { + Self.remoteMethodCtx('invoiceInEmail', { + description: 'Sends the invoice in email with an attached PDF', + accessType: 'WRITE', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The invoice id', + http: {source: 'path'} + }, + { + arg: 'recipient', + type: 'string', + description: 'The recipient email', + required: true, + }, + { + arg: 'recipientId', + type: 'number', + description: 'The recipient id to send to the recipient preferred language', + required: false + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: '/:id/invoice-in-email', + verb: 'POST' + } + }); + + Self.invoiceInEmail = async(ctx, id) => { + const args = Object.assign({}, ctx.args); + const params = { + recipient: 'alexm@verdnatura.es', // args.recipient, + lang: ctx.req.getLocale() + }; + + console.log(id); + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + console.log(params); + + const email = new Email('invoiceIn', params); + + return email.send(); + }; +}; diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js index 1b7ca9c68..f1d17dce7 100644 --- a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js @@ -29,17 +29,16 @@ module.exports = Self => { } ], http: { - path: '/:id/invoiceInPdf', + path: '/:id/invoice-in-pdf', verb: 'GET' } }); Self.invoiceInPdf = async(ctx, id) => { - console.log(id); const args = Object.assign({}, ctx.args); const params = {lang: ctx.req.getLocale()}; delete args.ctx; - console.log(args); + for (const param in args) params[param] = args[param]; diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js index e2c132671..95ccc7b20 100644 --- a/modules/invoiceIn/back/models/invoice-in.js +++ b/modules/invoiceIn/back/models/invoice-in.js @@ -5,4 +5,5 @@ module.exports = Self => { require('../methods/invoice-in/toBook')(Self); require('../methods/invoice-in/getTotals')(Self); require('../methods/invoice-in/invoiceInPdf')(Self); + require('../methods/invoice-in/invoiceInEmail')(Self); }; diff --git a/modules/invoiceIn/back/models/invoice-in.json b/modules/invoiceIn/back/models/invoice-in.json index c6a736b06..fa8a1d8f8 100644 --- a/modules/invoiceIn/back/models/invoice-in.json +++ b/modules/invoiceIn/back/models/invoice-in.json @@ -94,6 +94,11 @@ "model": "Supplier", "foreignKey": "supplierFk" }, + "supplierContact": { + "type": "hasMany", + "model": "SupplierContact", + "foreignKey": "supplierFk" + }, "currency": { "type": "belongsTo", "model": "Currency", diff --git a/modules/invoiceIn/front/card/index.js b/modules/invoiceIn/front/card/index.js index 582c2abb8..c7ac08cc7 100644 --- a/modules/invoiceIn/front/card/index.js +++ b/modules/invoiceIn/front/card/index.js @@ -8,6 +8,14 @@ class Controller extends ModuleCard { { relation: 'supplier' }, + { + relation: 'supplierContact', + scope: { + where: { + email: {neq: null} + } + } + }, { relation: 'invoiceInDueDay' }, diff --git a/modules/invoiceIn/front/descriptor/index.html b/modules/invoiceIn/front/descriptor/index.html index faff5b976..095bbd8e7 100644 --- a/modules/invoiceIn/front/descriptor/index.html +++ b/modules/invoiceIn/front/descriptor/index.html @@ -31,7 +31,7 @@ Show Invoice as PDF Send Invoice as PDF @@ -99,7 +99,6 @@ on-accept="$ctrl.sendPdfInvoice($data)" message="Send PDF invoice"> - {{sendPdfConfirmation.data.email}} Are you sure you want to send it? this.$state.reload()) .then(() => this.vnApp.showSuccess(this.$t('InvoiceIn booked'))); } + showPdfInvoice() { - this.vnReport.show(`InvoiceIns/${this.id}/invoiceInPdf`); + this.vnReport.show(`InvoiceIns/${this.id}/invoice-in-pdf`); + } + + sendPdfInvoice($data) { + if (!$data.email) + return this.vnApp.showError(this.$t(`The email can't be empty`)); + + return this.vnEmail.send(`InvoiceIns/${this.entity.id}/invoice-in-email`, { + recipient: $data.email, + recipientId: this.entity.supplier.id + }); } } diff --git a/print/templates/email/delivery-note-link/locale/pt.yml b/print/templates/email/delivery-note-link/locale/pt.yml index 1aab4b6d8..9a70fc4cd 100644 --- a/print/templates/email/delivery-note-link/locale/pt.yml +++ b/print/templates/email/delivery-note-link/locale/pt.yml @@ -7,4 +7,4 @@ copyLink: 'Como alternativa, podes copiar o siguinte link no teu navegador:' poll: Si o deseja, podes responder nosso questionário de satiscação para ajudar-nos a prestar-vos um melhor serviço. Tua opinião é muito importante para nós! help: Cualquer dúvida que surja, no hesites em consultar-la, Estamos aqui para atender-te! -conclusion: Obrigado por tua atenção! \ No newline at end of file +conclusion: Obrigado por tua atenção! diff --git a/print/templates/email/invoiceIn/assets/css/import.js b/print/templates/email/invoiceIn/assets/css/import.js new file mode 100644 index 000000000..4b4bb7086 --- /dev/null +++ b/print/templates/email/invoiceIn/assets/css/import.js @@ -0,0 +1,11 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/email.css`]) + .mergeStyles(); diff --git a/print/templates/email/invoiceIn/attachments.json b/print/templates/email/invoiceIn/attachments.json new file mode 100644 index 000000000..cd23d3f92 --- /dev/null +++ b/print/templates/email/invoiceIn/attachments.json @@ -0,0 +1,6 @@ +[ + { + "filename": "invoiceIn.pdf", + "component": "invoiceIn" + } +] diff --git a/print/templates/email/invoiceIn/invoiceIn.html b/print/templates/email/invoiceIn/invoiceIn.html new file mode 100644 index 000000000..65453ccd6 --- /dev/null +++ b/print/templates/email/invoiceIn/invoiceIn.html @@ -0,0 +1,47 @@ + + + + + + {{ $t('subject') }} + + + + + + + + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+

{{ $t('title') }}

+

{{$t('dear')}},

+

+

+
+
+ +
+
+ +
+
+ +
+
+
+
+ + diff --git a/print/templates/email/invoiceIn/invoiceIn.js b/print/templates/email/invoiceIn/invoiceIn.js new file mode 100755 index 000000000..43e95120c --- /dev/null +++ b/print/templates/email/invoiceIn/invoiceIn.js @@ -0,0 +1,19 @@ +const Component = require(`vn-print/core/component`); +const emailHeader = new Component('email-header'); +const emailFooter = new Component('email-footer'); + +module.exports = { + name: 'invoiceIn', + async serverPrefetch() { + this.invoice = await this.fetchInvoice(this.id); + }, + methods: { + fetchInvoice(reference) { + return this.findOneFromDef('invoice', [reference]); + }, + }, + components: { + 'email-header': emailHeader.build(), + 'email-footer': emailFooter.build() + } +}; diff --git a/print/templates/email/invoiceIn/locale/en.yml b/print/templates/email/invoiceIn/locale/en.yml new file mode 100644 index 000000000..47ebc3966 --- /dev/null +++ b/print/templates/email/invoiceIn/locale/en.yml @@ -0,0 +1,5 @@ +subject: Your agricultural invoice +title: Your agricultural invoice +dear: Dear supplier +description: Attached you can find agricultural receipt generated from your last deliveries. Please return a signed and stamped copy to our administration department. +conclusion: Thanks for your attention! diff --git a/print/templates/email/invoiceIn/locale/es.yml b/print/templates/email/invoiceIn/locale/es.yml new file mode 100644 index 000000000..2698763cf --- /dev/null +++ b/print/templates/email/invoiceIn/locale/es.yml @@ -0,0 +1,5 @@ +subject: Tu factura agrícola +title: Tu factura agrícola +dear: Estimado proveedor +description: Adjunto puede encontrar recibo agrícola generado de sus últimas entregas. Por favor, devuelva una copia firmada y sellada a nuestro de departamento de administración. +conclusion: ¡Gracias por tu atención! diff --git a/print/templates/email/invoiceIn/locale/fr.yml b/print/templates/email/invoiceIn/locale/fr.yml new file mode 100644 index 000000000..1c38f3c25 --- /dev/null +++ b/print/templates/email/invoiceIn/locale/fr.yml @@ -0,0 +1,5 @@ +subject: Votre facture agricole +title: Votre facture agricole +dear: Cher Fournisseur +description: Vous trouverez en pièce jointe le reçu agricole généré à partir de vos dernières livraisons. Veuillez retourner une copie signée et tamponnée à notre service administratif. +conclusion: Merci pour votre attention! diff --git a/print/templates/email/invoiceIn/locale/pt.yml b/print/templates/email/invoiceIn/locale/pt.yml new file mode 100644 index 000000000..a43e3a79d --- /dev/null +++ b/print/templates/email/invoiceIn/locale/pt.yml @@ -0,0 +1,5 @@ +subject: A sua fatura agrícola +title: A sua fatura agrícola +dear: Caro Fornecedor +description: Em anexo encontra-se o recibo agrícola gerado a partir das suas últimas entregas. Por favor, devolva uma cópia assinada e carimbada ao nosso departamento de administração. +conclusion: Obrigado pela atenção. diff --git a/print/templates/reports/invoiceIn/invoiceIn.html b/print/templates/reports/invoiceIn/invoiceIn.html index 4bc6d8ee0..5f15e6a9a 100644 --- a/print/templates/reports/invoiceIn/invoiceIn.html +++ b/print/templates/reports/invoiceIn/invoiceIn.html @@ -201,6 +201,7 @@
diff --git a/print/templates/reports/invoiceIn/locale/en.yml b/print/templates/reports/invoiceIn/locale/en.yml index 7a8767ad3..92d3b0c2d 100644 --- a/print/templates/reports/invoiceIn/locale/en.yml +++ b/print/templates/reports/invoiceIn/locale/en.yml @@ -1,6 +1,6 @@ reportName: invoice -title: Agrobusiness invoice -invoiceId: Agrobusiness invoice +title: Agricultural invoice +invoiceId: Agricultural invoice supplierId: Proveedor invoiceData: Invoice data reference: Reference diff --git a/print/templates/reports/invoiceIn/sql/invoice.sql b/print/templates/reports/invoiceIn/sql/invoice.sql index fe9ef1e9e..2f6929b2a 100644 --- a/print/templates/reports/invoiceIn/sql/invoice.sql +++ b/print/templates/reports/invoiceIn/sql/invoice.sql @@ -6,7 +6,8 @@ SELECT s.street AS postalAddress, s.nif, s.phone, - p.name payMethod + p.name payMethod, + c.companyCode FROM invoiceIn i JOIN supplier s ON s.id = i.supplierFk JOIN company c ON c.id = i.companyFk From 8a3573474a99de0a60a330ab00c0fd106273c406 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 25 Oct 2022 07:40:32 +0200 Subject: [PATCH 51/77] refactor: move 'lastEntries' to thrid position in descriptor --- modules/item/front/routes.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/item/front/routes.json b/modules/item/front/routes.json index 52cf5d2be..3dea69ba1 100644 --- a/modules/item/front/routes.json +++ b/modules/item/front/routes.json @@ -15,12 +15,12 @@ "card": [ {"state": "item.card.basicData", "icon": "settings"}, {"state": "item.card.tags", "icon": "icon-tags"}, + {"state": "item.card.last-entries", "icon": "icon-regentry"}, {"state": "item.card.tax", "icon": "icon-tax"}, {"state": "item.card.botanical", "icon": "local_florist"}, {"state": "item.card.shelving", "icon": "icon-inventory"}, {"state": "item.card.itemBarcode", "icon": "icon-barcode"}, {"state": "item.card.diary", "icon": "icon-transaction"}, - {"state": "item.card.last-entries", "icon": "icon-regentry"}, {"state": "item.card.log", "icon": "history"} ], "itemType": [ From 7f1dd801a55aff414fbaf06eacbbdb8bf25039ad Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 25 Oct 2022 07:56:17 +0200 Subject: [PATCH 52/77] refactor: change ACLs --- db/changes/10491-august/00-aclItemShelving.sql | 3 --- db/changes/10491-august/00-aclItemShelvingPlacementSupply.sql | 3 --- db/changes/10500-november/00-itemShelvingACL.sql | 4 ++++ .../10500-november/00-itemShelvingPlacementSupplyStockACL.sql | 4 ++++ 4 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 db/changes/10491-august/00-aclItemShelving.sql delete mode 100644 db/changes/10491-august/00-aclItemShelvingPlacementSupply.sql create mode 100644 db/changes/10500-november/00-itemShelvingACL.sql create mode 100644 db/changes/10500-november/00-itemShelvingPlacementSupplyStockACL.sql diff --git a/db/changes/10491-august/00-aclItemShelving.sql b/db/changes/10491-august/00-aclItemShelving.sql deleted file mode 100644 index 3995bbe49..000000000 --- a/db/changes/10491-august/00-aclItemShelving.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES - ('ItemShelving', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10491-august/00-aclItemShelvingPlacementSupply.sql b/db/changes/10491-august/00-aclItemShelvingPlacementSupply.sql deleted file mode 100644 index cc589a58f..000000000 --- a/db/changes/10491-august/00-aclItemShelvingPlacementSupply.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) - VALUES - ('ItemShelvingPlacementSupplyStock', '*', '*', 'ALLOW', 'ROLE', 'employee'); \ No newline at end of file diff --git a/db/changes/10500-november/00-itemShelvingACL.sql b/db/changes/10500-november/00-itemShelvingACL.sql new file mode 100644 index 000000000..fc32fe1ed --- /dev/null +++ b/db/changes/10500-november/00-itemShelvingACL.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) +VALUES + ('ItemShelving', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('ItemShelving', '*', 'WRITE', 'ALLOW', 'ROLE', 'production'); \ No newline at end of file diff --git a/db/changes/10500-november/00-itemShelvingPlacementSupplyStockACL.sql b/db/changes/10500-november/00-itemShelvingPlacementSupplyStockACL.sql new file mode 100644 index 000000000..25eac8d51 --- /dev/null +++ b/db/changes/10500-november/00-itemShelvingPlacementSupplyStockACL.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) +VALUES + ('ItemShelvingPlacementSupplyStock', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); + From e8500f926c6af4cbc1c14a528555adf56f0fcab7 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 25 Oct 2022 08:19:56 +0200 Subject: [PATCH 53/77] feat(invoiceIn): add email --- .../invoiceIn/back/methods/invoice-in/invoiceInEmail.js | 7 ++----- modules/invoiceIn/front/descriptor/index.js | 1 - print/templates/email/invoiceIn/invoiceIn.js | 8 -------- print/templates/reports/invoiceIn/invoiceIn.html | 2 +- print/templates/reports/invoiceIn/sql/invoice.sql | 3 +-- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js index b04108bd7..0768541a8 100644 --- a/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js @@ -35,19 +35,16 @@ module.exports = Self => { } }); - Self.invoiceInEmail = async(ctx, id) => { + Self.invoiceInEmail = async ctx => { const args = Object.assign({}, ctx.args); const params = { - recipient: 'alexm@verdnatura.es', // args.recipient, + recipient: args.recipient, lang: ctx.req.getLocale() }; - console.log(id); - delete args.ctx; for (const param in args) params[param] = args[param]; - console.log(params); const email = new Email('invoiceIn', params); diff --git a/modules/invoiceIn/front/descriptor/index.js b/modules/invoiceIn/front/descriptor/index.js index 6c844a233..5cd00d743 100644 --- a/modules/invoiceIn/front/descriptor/index.js +++ b/modules/invoiceIn/front/descriptor/index.js @@ -8,7 +8,6 @@ class Controller extends Descriptor { set invoiceIn(value) { this.entity = value; - console.log(value); } get entryFilter() { diff --git a/print/templates/email/invoiceIn/invoiceIn.js b/print/templates/email/invoiceIn/invoiceIn.js index 43e95120c..f7a472eb2 100755 --- a/print/templates/email/invoiceIn/invoiceIn.js +++ b/print/templates/email/invoiceIn/invoiceIn.js @@ -4,14 +4,6 @@ const emailFooter = new Component('email-footer'); module.exports = { name: 'invoiceIn', - async serverPrefetch() { - this.invoice = await this.fetchInvoice(this.id); - }, - methods: { - fetchInvoice(reference) { - return this.findOneFromDef('invoice', [reference]); - }, - }, components: { 'email-header': emailHeader.build(), 'email-footer': emailFooter.build() diff --git a/print/templates/reports/invoiceIn/invoiceIn.html b/print/templates/reports/invoiceIn/invoiceIn.html index 5f15e6a9a..8919403b9 100644 --- a/print/templates/reports/invoiceIn/invoiceIn.html +++ b/print/templates/reports/invoiceIn/invoiceIn.html @@ -202,7 +202,7 @@ diff --git a/print/templates/reports/invoiceIn/sql/invoice.sql b/print/templates/reports/invoiceIn/sql/invoice.sql index 2f6929b2a..fe9ef1e9e 100644 --- a/print/templates/reports/invoiceIn/sql/invoice.sql +++ b/print/templates/reports/invoiceIn/sql/invoice.sql @@ -6,8 +6,7 @@ SELECT s.street AS postalAddress, s.nif, s.phone, - p.name payMethod, - c.companyCode + p.name payMethod FROM invoiceIn i JOIN supplier s ON s.id = i.supplierFk JOIN company c ON c.id = i.companyFk From 67402dfe5ee435e5f07af5f16ab485e1a40de601 Mon Sep 17 00:00:00 2001 From: alexandre Date: Tue, 25 Oct 2022 13:27:04 +0200 Subject: [PATCH 54/77] =?UTF-8?q?refs=20#4650=20observaciones=20a=C3=B1adi?= =?UTF-8?q?das?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/reports/delivery-note/assets/css/style.css | 5 +++++ print/templates/reports/delivery-note/delivery-note.html | 6 ++++++ print/templates/reports/delivery-note/delivery-note.js | 4 ++++ print/templates/reports/delivery-note/locale/en.yml | 3 ++- print/templates/reports/delivery-note/locale/es.yml | 3 ++- print/templates/reports/delivery-note/locale/fr.yml | 3 ++- print/templates/reports/delivery-note/locale/pt.yml | 3 ++- print/templates/reports/delivery-note/sql/ticket.sql | 8 ++++++-- 8 files changed, 29 insertions(+), 6 deletions(-) diff --git a/print/templates/reports/delivery-note/assets/css/style.css b/print/templates/reports/delivery-note/assets/css/style.css index f99c385fa..8405ae78d 100644 --- a/print/templates/reports/delivery-note/assets/css/style.css +++ b/print/templates/reports/delivery-note/assets/css/style.css @@ -37,4 +37,9 @@ h2 { .phytosanitary-info { margin-top: 10px +} + +.observations{ + text-align: justify; + text-justify: inter-word; } \ No newline at end of file diff --git a/print/templates/reports/delivery-note/delivery-note.html b/print/templates/reports/delivery-note/delivery-note.html index d166f3307..e34620591 100644 --- a/print/templates/reports/delivery-note/delivery-note.html +++ b/print/templates/reports/delivery-note/delivery-note.html @@ -263,6 +263,12 @@
+
+ +
+

{{$t('observations')}}

+

{{ticket.description}}

+
diff --git a/print/templates/reports/delivery-note/delivery-note.js b/print/templates/reports/delivery-note/delivery-note.js index 1037e5129..78f0f7662 100755 --- a/print/templates/reports/delivery-note/delivery-note.js +++ b/print/templates/reports/delivery-note/delivery-note.js @@ -55,6 +55,7 @@ module.exports = { const translatedType = this.$t(this.deliverNoteType); return `${translatedType} ${this.id}`; } + }, methods: { fetchClient(id) { @@ -119,6 +120,9 @@ module.exports = { return phytosanitary.filter((item, index) => phytosanitary.indexOf(item) == index ).join(', '); + }, + hasObservations() { + return this.ticket.code == 'deliveryNote' && this.ticket.description != null; } }, components: { diff --git a/print/templates/reports/delivery-note/locale/en.yml b/print/templates/reports/delivery-note/locale/en.yml index c74b87520..5902da8b3 100644 --- a/print/templates/reports/delivery-note/locale/en.yml +++ b/print/templates/reports/delivery-note/locale/en.yml @@ -46,4 +46,5 @@ taxes: fee: Fee tfoot: subtotal: Subtotal - total: Total \ No newline at end of file + total: Total +observations: Observations \ No newline at end of file diff --git a/print/templates/reports/delivery-note/locale/es.yml b/print/templates/reports/delivery-note/locale/es.yml index 5c5a6af4d..d87198f45 100644 --- a/print/templates/reports/delivery-note/locale/es.yml +++ b/print/templates/reports/delivery-note/locale/es.yml @@ -47,4 +47,5 @@ taxes: fee: Cuota tfoot: subtotal: Subtotal - total: Total \ No newline at end of file + total: Total +observations: Observaciones \ No newline at end of file diff --git a/print/templates/reports/delivery-note/locale/fr.yml b/print/templates/reports/delivery-note/locale/fr.yml index df7ca053b..603623a47 100644 --- a/print/templates/reports/delivery-note/locale/fr.yml +++ b/print/templates/reports/delivery-note/locale/fr.yml @@ -47,4 +47,5 @@ taxes: fee: Quote tfoot: subtotal: Total partiel - total: Total \ No newline at end of file + total: Total +observations: Observations \ No newline at end of file diff --git a/print/templates/reports/delivery-note/locale/pt.yml b/print/templates/reports/delivery-note/locale/pt.yml index 1f418b31f..fb49d230b 100644 --- a/print/templates/reports/delivery-note/locale/pt.yml +++ b/print/templates/reports/delivery-note/locale/pt.yml @@ -47,4 +47,5 @@ taxes: fee: Compartilhado tfoot: subtotal: Subtotal - total: Total \ No newline at end of file + total: Total +observations: Observações \ No newline at end of file diff --git a/print/templates/reports/delivery-note/sql/ticket.sql b/print/templates/reports/delivery-note/sql/ticket.sql index f78c72544..3d16b53d8 100644 --- a/print/templates/reports/delivery-note/sql/ticket.sql +++ b/print/templates/reports/delivery-note/sql/ticket.sql @@ -2,7 +2,11 @@ SELECT t.id, t.shipped, c.code companyCode, - t.packages + t.packages, + tto.description, + ot.code FROM ticket t JOIN company c ON c.id = t.companyFk -WHERE t.id = ? \ No newline at end of file + LEFT JOIN ticketObservation tto ON tto.ticketFk = t.id + LEFT JOIN observationType ot ON tto.observationTypeFk = ot.id +WHERE t.id = 1; \ No newline at end of file From 79485236853b1f22220a94fd487da4f2a5a84bd4 Mon Sep 17 00:00:00 2001 From: alexandre Date: Tue, 25 Oct 2022 15:03:09 +0200 Subject: [PATCH 55/77] refs #4650 cambios en la query --- .../reports/delivery-note/delivery-note.html | 579 +++++++++--------- .../reports/delivery-note/delivery-note.js | 6 +- .../reports/delivery-note/sql/ticket.sql | 10 +- 3 files changed, 300 insertions(+), 295 deletions(-) diff --git a/print/templates/reports/delivery-note/delivery-note.html b/print/templates/reports/delivery-note/delivery-note.html index e34620591..a07c851aa 100644 --- a/print/templates/reports/delivery-note/delivery-note.html +++ b/print/templates/reports/delivery-note/delivery-note.html @@ -1,299 +1,304 @@ - - - - - + + +
- - - - -
-
-
-
-
-

{{$t(deliverNoteType)}}

- - - - - - - - - - - - - - - - - - - -
{{$t('clientId')}}{{client.id}}
{{$t(deliverNoteType)}}{{ticket.id}}
{{$t('date')}}{{ticket.shipped | date('%d-%m-%Y')}}
{{$t('packages')}}{{ticket.packages}}
-
-
-
-
-
{{$t('deliveryAddress')}}
-
-

{{address.nickname}}

-
- {{address.street}} -
-
- {{address.postalCode}}, {{address.city}} ({{address.province}}) -
-
-
- -
-
{{$t('fiscalData')}}
-
-
- {{client.socialName}} -
-
- {{client.street}} -
-
- {{client.fi}} -
-
-
-
-
- - -

{{$t('saleLines')}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', $i18n.locale)}}{{(sale.discount / 100) | percentage}}{{sale.vatType}}{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', $i18n.locale)}}
- - {{sale.tag5}} {{sale.value5}} - - - {{sale.tag6}} {{sale.value6}} - - - {{sale.tag7}} {{sale.value7}} - -
- {{$t('subtotal')}} - {{getSubTotal() | currency('EUR', $i18n.locale)}}
- - -
- -
-

{{$t('services.title')}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('services.theader.quantity')}}{{$t('services.theader.concept')}}{{$t('services.theader.price')}}{{$t('services.theader.vat')}}{{$t('services.theader.amount')}}
{{service.quantity}}{{service.description}}{{service.price | currency('EUR', $i18n.locale)}}{{service.taxDescription}}{{service.price | currency('EUR', $i18n.locale)}}
- {{$t('services.tfoot.subtotal')}} - {{serviceTotal | currency('EUR', $i18n.locale)}}
- * {{ $t('services.warning') }} -
- -
-
- -
-

{{$t('packagings.title')}}

- - - - - - - - - - - - - - - -
{{$t('packagings.theader.reference')}}{{$t('packagings.theader.quantity')}}{{$t('packagings.theader.concept')}}
{{packaging.itemFk | zerofill('000000')}}{{packaging.quantity}}{{packaging.name}}
-
- -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{$t('taxes.title')}}
{{$t('taxes.theader.type')}}{{$t('taxes.theader.taxBase')}}{{$t('taxes.theader.tax')}}{{$t('taxes.theader.fee')}}
{{tax.name}} - {{tax.Base | currency('EUR', $i18n.locale)}} - {{tax.vatPercent | percentage}}{{tax.tax | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} - {{getTotalBase() | currency('EUR', $i18n.locale)}} - {{getTotalTax()| currency('EUR', $i18n.locale)}}
{{$t('total')}}{{getTotal() | currency('EUR', $i18n.locale)}}
-
- - -
-
-
-
-
-
- -
-
- {{$t('plantPassport')}}
-
-
-
-
-
- A - {{getBotanical()}} -
-
- B - ES17462130 -
-
- C - {{ticket.id}} -
-
- D - ES -
-
-
-
+ + + + + - - -
+ + + + +
+
+
+
+
+

{{$t(deliverNoteType)}}

+ + + + + + + + + + + + + + + + + + + +
{{$t('clientId')}}{{client.id}}
{{$t(deliverNoteType)}}{{ticket.id}}
{{$t('date')}}{{ticket.shipped | date('%d-%m-%Y')}}
{{$t('packages')}}{{ticket.packages}}
-
-
- -
-

{{$t('observations')}}

-

{{ticket.description}}

-
-
- -
-
-
{{$t('digitalSignature')}}
-
- -
{{signature.created | date('%d-%m-%Y')}}
+
+
+
{{$t('deliveryAddress')}}
+
+

{{address.nickname}}

+
+ {{address.street}} +
+
+ {{address.postalCode}}, {{address.city}} ({{address.province}})
- + +
+
{{$t('fiscalData')}}
+
+
+ {{client.socialName}} +
+
+ {{client.street}} +
+
+ {{client.fi}} +
+
+
+
+
+ + +

{{$t('saleLines')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('reference')}}{{$t('quantity')}}{{$t('concept')}}{{$t('price')}}{{$t('discount')}}{{$t('vat')}}{{$t('amount')}}
{{sale.itemFk | zerofill('000000')}}{{sale.quantity}}{{sale.concept}}{{sale.price | currency('EUR', + $i18n.locale)}}{{(sale.discount / 100) | + percentage}}{{sale.vatType}}{{sale.price * sale.quantity * (1 - + sale.discount / 100) | currency('EUR', $i18n.locale)}}
+ + {{sale.tag5}} {{sale.value5}} + + + {{sale.tag6}} {{sale.value6}} + + + {{sale.tag7}} {{sale.value7}} + +
+ {{$t('subtotal')}} + {{getSubTotal() | currency('EUR', $i18n.locale)}}
+ + +
+ +
+

{{$t('services.title')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('services.theader.quantity')}}{{$t('services.theader.concept')}}{{$t('services.theader.price')}}{{$t('services.theader.vat')}}{{$t('services.theader.amount')}}
{{service.quantity}}{{service.description}}{{service.price | currency('EUR', $i18n.locale)}} + {{service.taxDescription}}{{service.price | currency('EUR', $i18n.locale)}} +
+ {{$t('services.tfoot.subtotal')}} + {{serviceTotal | currency('EUR', $i18n.locale)}}
+ * {{ $t('services.warning') }} +
+ +
+
+ +
+

{{$t('packagings.title')}}

+ + + + + + + + + + + + + + + +
{{$t('packagings.theader.reference')}}{{$t('packagings.theader.quantity')}}{{$t('packagings.theader.concept')}}
{{packaging.itemFk | zerofill('000000')}}{{packaging.quantity}}{{packaging.name}}
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{$t('taxes.title')}}
{{$t('taxes.theader.type')}}{{$t('taxes.theader.taxBase')}}{{$t('taxes.theader.tax')}}{{$t('taxes.theader.fee')}}
{{tax.name}} + {{tax.Base | currency('EUR', $i18n.locale)}} + {{tax.vatPercent | percentage}}{{tax.tax | currency('EUR', $i18n.locale)}}
{{$t('subtotal')}} + {{getTotalBase() | currency('EUR', $i18n.locale)}} + {{getTotalTax()| currency('EUR', $i18n.locale)}}
{{$t('total')}}{{getTotal() | currency('EUR', + $i18n.locale)}}
+
+ + + +
+
+
+
+
+
+ +
+
+ {{$t('plantPassport')}}
+
+
+
+
+
+ A + {{getBotanical()}} +
+
+ B + ES17462130 +
+
+ C + {{ticket.id}} +
+
+ D + ES +
+
+
+
+
+ +
+
+ +
+
+
{{$t('digitalSignature')}}
+
+ +
{{signature.created | date('%d-%m-%Y')}}
+
+
+
+ +
+
+ +
+

{{$t('observations')}}

+

{{ticket.description}}

- - - -
- +
+ + + +
+ + \ No newline at end of file diff --git a/print/templates/reports/delivery-note/delivery-note.js b/print/templates/reports/delivery-note/delivery-note.js index 78f0f7662..2e356f62e 100755 --- a/print/templates/reports/delivery-note/delivery-note.js +++ b/print/templates/reports/delivery-note/delivery-note.js @@ -54,6 +54,9 @@ module.exports = { footerType() { const translatedType = this.$t(this.deliverNoteType); return `${translatedType} ${this.id}`; + }, + hasObservations() { + return this.ticket.description !== null; } }, @@ -120,9 +123,6 @@ module.exports = { return phytosanitary.filter((item, index) => phytosanitary.indexOf(item) == index ).join(', '); - }, - hasObservations() { - return this.ticket.code == 'deliveryNote' && this.ticket.description != null; } }, components: { diff --git a/print/templates/reports/delivery-note/sql/ticket.sql b/print/templates/reports/delivery-note/sql/ticket.sql index 3d16b53d8..9eac2d322 100644 --- a/print/templates/reports/delivery-note/sql/ticket.sql +++ b/print/templates/reports/delivery-note/sql/ticket.sql @@ -3,10 +3,10 @@ SELECT t.shipped, c.code companyCode, t.packages, - tto.description, - ot.code + tto.description FROM ticket t JOIN company c ON c.id = t.companyFk - LEFT JOIN ticketObservation tto ON tto.ticketFk = t.id - LEFT JOIN observationType ot ON tto.observationTypeFk = ot.id -WHERE t.id = 1; \ No newline at end of file + LEFT JOIN ticketObservation tto + ON tto.ticketFk = t.id + AND tto.observationTypeFk = (SELECT id FROM observationType WHERE code = 'deliveryNote') +WHERE t.id = ? \ No newline at end of file From f07f0679d8d58656aabd60d2cb1875a20994a939 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 25 Oct 2022 15:03:20 +0200 Subject: [PATCH 56/77] fix fixture --- db/dump/fixtures.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 8e1113781..2e00389e5 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -349,7 +349,7 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`) INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`) VALUES - (200, null); + (200, 10); INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`) VALUES From 07a18dc736048fd3f6304daa80a9ef0fcf985e0c Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 08:05:11 +0200 Subject: [PATCH 57/77] delete sql --- db/changes/10491-august/00-itemType.sql | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 db/changes/10491-august/00-itemType.sql diff --git a/db/changes/10491-august/00-itemType.sql b/db/changes/10491-august/00-itemType.sql deleted file mode 100644 index d201acf37..000000000 --- a/db/changes/10491-august/00-itemType.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE `vn`.`itemType` CHANGE `transaction` transaction__ tinyint(4) DEFAULT 0 NOT NULL; -ALTER TABLE `vn`.`itemType` CHANGE location location__ varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL; -ALTER TABLE `vn`.`itemType` CHANGE hasComponents hasComponents__ tinyint(1) DEFAULT 1 NOT NULL; -ALTER TABLE `vn`.`itemType` CHANGE warehouseFk warehouseFk__ smallint(6) unsigned DEFAULT 60 NOT NULL; -ALTER TABLE `vn`.`itemType` CHANGE compression compression__ decimal(5,2) DEFAULT 1.00 NULL; \ No newline at end of file From 925565d3d24037bb53d209823db0b5cef12bc83c Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 08:23:36 +0200 Subject: [PATCH 58/77] =?UTF-8?q?refator:=20borrada=20variable=20global=20?= =?UTF-8?q?y=20a=C3=B1adida=20Promesa=20al=20array?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ticket/back/methods/ticket/updateDiscount.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index 7e7c4e06d..a60d732b1 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -133,20 +133,20 @@ module.exports = Self => { } }, myOptions); - let newComponent; + let deletedComponent; if (oldComponent) { const filter = { saleFk: sale.id, componentFk: oldComponent.componentFk }; - await models.SaleComponent.destroyAll(filter, myOptions); + deletedComponent = await models.SaleComponent.destroyAll(filter, myOptions); } - await createSaleComponent(sale.id, value, componentId, myOptions); + const newComponent = await createSaleComponent(sale.id, value, componentId, myOptions); const updatedSale = sale.updateAttribute('discount', newDiscount, myOptions); - promises.push(newComponent, updatedSale); + promises.push(newComponent, updatedSale, deletedComponent); const change = `${oldDiscount}% ➔ *${newDiscount}%*`; changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${change}`; @@ -193,7 +193,7 @@ module.exports = Self => { async function createSaleComponent(saleId, value, componentId, myOptions) { const models = Self.app.models; - newComponent = models.SaleComponent.create({ + return models.SaleComponent.create({ saleFk: saleId, value: value, componentFk: componentId From d4e278363009abaa45090212dc3e50a2f0430086 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 08:39:07 +0200 Subject: [PATCH 59/77] =?UTF-8?q?refactor:=20corregidas=20traducciones,=20?= =?UTF-8?q?eliminado=20destroyAll=20y=20a=C3=B1adida=20transacci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- loopback/locale/es.json | 2 +- .../back/methods/expedition/deleteExpeditions.js | 10 +++++++--- .../ticket/back/methods/expedition/moveExpeditions.js | 4 ++-- .../methods/expedition/specs/deleteExpeditions.spec.js | 2 +- modules/ticket/front/expedition/index.html | 2 +- modules/ticket/front/expedition/locale/es.yml | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/loopback/locale/es.json b/loopback/locale/es.json index bd9de41c2..2d9bae1d8 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -236,5 +236,5 @@ "Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador", "Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador", "Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente", - "This route not exists": "Esta ruta no existe" + "This route does not exists": "Esta ruta no existe" } \ No newline at end of file diff --git a/modules/ticket/back/methods/expedition/deleteExpeditions.js b/modules/ticket/back/methods/expedition/deleteExpeditions.js index 5b9d0daaa..2419d3a5e 100644 --- a/modules/ticket/back/methods/expedition/deleteExpeditions.js +++ b/modules/ticket/back/methods/expedition/deleteExpeditions.js @@ -33,9 +33,13 @@ module.exports = Self => { } try { - const deletedExpeditions = await models.Expedition.destroyAll({ - id: {inq: expeditionIds} - }, myOptions); + const promises = []; + for (let expeditionId of expeditionIds) { + const deletedExpedition = models.Expedition.destroyById(expeditionId, myOptions); + promises.push(deletedExpedition); + } + + const deletedExpeditions = await Promise.all(promises); if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/expedition/moveExpeditions.js b/modules/ticket/back/methods/expedition/moveExpeditions.js index d0f8aa893..cef35ab86 100644 --- a/modules/ticket/back/methods/expedition/moveExpeditions.js +++ b/modules/ticket/back/methods/expedition/moveExpeditions.js @@ -70,12 +70,12 @@ module.exports = Self => { try { if (args.routeId) { const route = await models.Route.findById(args.routeId, null, myOptions); - if (!route) throw new UserError('This route not exists'); + if (!route) throw new UserError('This route does not exists'); } const ticket = await models.Ticket.new(ctx, myOptions); const promises = []; for (let expeditionsId of args.expeditionIds) { - const expeditionToUpdate = await models.Expedition.findById(expeditionsId); + const expeditionToUpdate = await models.Expedition.findById(expeditionsId, null, myOptions); const expeditionUpdated = expeditionToUpdate.updateAttribute('ticketFk', ticket.id, myOptions); promises.push(expeditionUpdated); } diff --git a/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js index 0a47c78da..14bdf7aea 100644 --- a/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js +++ b/modules/ticket/back/methods/expedition/specs/deleteExpeditions.spec.js @@ -10,7 +10,7 @@ describe('ticket deleteExpeditions()', () => { const expeditionIds = [12, 13]; const result = await models.Expedition.deleteExpeditions(expeditionIds, options); - expect(result.count).toEqual(2); + expect(result.length).toEqual(2); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/front/expedition/index.html b/modules/ticket/front/expedition/index.html index 0ebe388c1..ec6dc7ee2 100644 --- a/modules/ticket/front/expedition/index.html +++ b/modules/ticket/front/expedition/index.html @@ -159,7 +159,7 @@ diff --git a/modules/ticket/front/expedition/locale/es.yml b/modules/ticket/front/expedition/locale/es.yml index 68812f985..278dcc8f2 100644 --- a/modules/ticket/front/expedition/locale/es.yml +++ b/modules/ticket/front/expedition/locale/es.yml @@ -3,4 +3,4 @@ Expedition removed: Expedición eliminada Move: Mover New ticket without route: Nuevo ticket sin ruta New ticket with route: Nuevo ticket con ruta -Id route: Id ruta \ No newline at end of file +Route id: Id ruta \ No newline at end of file From d1e2b1f7d5ff9b98b8d62554e879c539937337dc Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 09:02:54 +0200 Subject: [PATCH 60/77] refactor: tranducciones --- modules/ticket/front/sale/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index 35e45a812..c68138141 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -244,7 +244,7 @@
-
MANÁ: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
+
Mana: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
-
MANÁ: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
+
Mana: {{::$ctrl.edit.mana | currency: 'EUR': 0}}
Date: Wed, 26 Oct 2022 09:04:53 +0200 Subject: [PATCH 61/77] refactor: renombrada variable --- .../ticket/back/methods/sale/specs/updatePrice.spec.js | 4 ++-- modules/ticket/back/methods/sale/specs/usesMana.spec.js | 4 ++-- .../back/methods/ticket/specs/updateDiscount.spec.js | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js index 15b00a732..51cd2403f 100644 --- a/modules/ticket/back/methods/sale/specs/updatePrice.spec.js +++ b/modules/ticket/back/methods/sale/specs/updatePrice.spec.js @@ -79,11 +79,11 @@ describe('sale updatePrice()', () => { const price = 5.4; const originalSalesPersonMana = await models.WorkerMana.findById(18, null, options); const manaComponent = await models.Component.findOne({where: {code: 'mana'}}, options); - const teamCLopez = 96; + const teamOne = 96; const userId = ctx.req.accessToken.userId; const business = await models.Business.findOne({where: {workerFk: userId}}, options); - await business.updateAttribute('departmentFk', teamCLopez, options); + await business.updateAttribute('departmentFk', teamOne, options); await models.Sale.updatePrice(ctx, saleId, price, options); const updatedSale = await models.Sale.findById(saleId, null, options); diff --git a/modules/ticket/back/methods/sale/specs/usesMana.spec.js b/modules/ticket/back/methods/sale/specs/usesMana.spec.js index 4e14ed2c9..777bdc8f0 100644 --- a/modules/ticket/back/methods/sale/specs/usesMana.spec.js +++ b/modules/ticket/back/methods/sale/specs/usesMana.spec.js @@ -12,11 +12,11 @@ describe('sale usesMana()', () => { try { const options = {transaction: tx}; - const teamCLopez = 96; + const teamOne = 96; const userId = ctx.req.accessToken.userId; const business = await models.Business.findOne({where: {workerFk: userId}}, options); - await business.updateAttribute('departmentFk', teamCLopez, options); + await business.updateAttribute('departmentFk', teamOne, options); const usesMana = await models.Sale.usesMana(ctx, options); diff --git a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js index 5249fe5d8..1f6712087 100644 --- a/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js +++ b/modules/ticket/back/methods/ticket/specs/updateDiscount.spec.js @@ -110,10 +110,10 @@ describe('sale updateDiscount()', () => { const componentId = manaDiscount.id; const manaCode = 'mana'; - const teamCLopez = 96; + const teamOne = 96; const userId = ctx.req.accessToken.userId; const business = await models.Business.findOne({where: {workerFk: userId}}, options); - await business.updateAttribute('departmentFk', teamCLopez, options); + await business.updateAttribute('departmentFk', teamOne, options); await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, manaCode, options); @@ -155,10 +155,10 @@ describe('sale updateDiscount()', () => { const componentId = manaDiscount.id; const manaCode = 'manaClaim'; - const teamCLopez = 96; + const teamOne = 96; const userId = ctx.req.accessToken.userId; const business = await models.Business.findOne({where: {workerFk: userId}}, options); - await business.updateAttribute('departmentFk', teamCLopez, options); + await business.updateAttribute('departmentFk', teamOne, options); await models.Ticket.updateDiscount(ctx, ticketId, sales, newDiscount, manaCode, options); From 21e2945d4f6b59df9b239fd197b8845dbda7f8ef Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 09:30:17 +0200 Subject: [PATCH 62/77] refactor: delete destroyAll --- .../methods/item-shelving/deleteItemShelvings.js | 12 ++++++++---- .../item-shelving/specs/deleteItemShelvings.spec.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/item/back/methods/item-shelving/deleteItemShelvings.js b/modules/item/back/methods/item-shelving/deleteItemShelvings.js index 0b58970d8..f534b4e9a 100644 --- a/modules/item/back/methods/item-shelving/deleteItemShelvings.js +++ b/modules/item/back/methods/item-shelving/deleteItemShelvings.js @@ -1,6 +1,6 @@ module.exports = Self => { Self.remoteMethod('deleteItemShelvings', { - description: 'Deletes the selected orders', + description: 'Deletes the selected item shelvings', accessType: 'WRITE', accepts: [{ arg: 'itemShelvingIds', @@ -32,9 +32,13 @@ module.exports = Self => { } try { - const deletedItemShelvings = await models.ItemShelving.destroyAll({ - id: {inq: itemShelvingIds} - }, myOptions); + const promises = []; + for (let itemShelvingId of itemShelvingIds) { + const itemShelvingToDelete = models.ItemShelving.destroyById(itemShelvingId, myOptions); + promises.push(itemShelvingToDelete); + } + + const deletedItemShelvings = await Promise.all(promises); if (tx) await tx.commit(); diff --git a/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js b/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js index a152b5981..b4113d7cf 100644 --- a/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js +++ b/modules/item/back/methods/item-shelving/specs/deleteItemShelvings.spec.js @@ -10,7 +10,7 @@ describe('ItemShelving deleteItemShelvings()', () => { const itemShelvingIds = [1, 2]; const result = await models.ItemShelving.deleteItemShelvings(itemShelvingIds, options); - expect(result.count).toEqual(2); + expect(result.length).toEqual(2); await tx.rollback(); } catch (e) { From d243f9a87d8864901219309b6554c9907e3d5bf5 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 26 Oct 2022 10:03:13 +0200 Subject: [PATCH 63/77] =?UTF-8?q?refs=20#4644=20checkbox=20a=C3=B1adido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/supplier/back/models/supplier.json | 3 +++ modules/supplier/front/descriptor/index.js | 1 + modules/supplier/front/fiscal-data/index.html | 12 ++++++++---- modules/supplier/front/fiscal-data/locale/es.yml | 1 + 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/supplier/back/models/supplier.json b/modules/supplier/back/models/supplier.json index b27073ca5..3cd6386a8 100644 --- a/modules/supplier/back/models/supplier.json +++ b/modules/supplier/back/models/supplier.json @@ -51,6 +51,9 @@ "isSerious": { "type": "boolean" }, + "isTrucker": { + "type": "boolean" + }, "note": { "type": "string" }, diff --git a/modules/supplier/front/descriptor/index.js b/modules/supplier/front/descriptor/index.js index df9fe2155..a26d9c510 100644 --- a/modules/supplier/front/descriptor/index.js +++ b/modules/supplier/front/descriptor/index.js @@ -41,6 +41,7 @@ class Controller extends Descriptor { 'payDay', 'isActive', 'isSerious', + 'isTrucker', 'account' ], include: [ diff --git a/modules/supplier/front/fiscal-data/index.html b/modules/supplier/front/fiscal-data/index.html index 4ae07c81a..a3ede2058 100644 --- a/modules/supplier/front/fiscal-data/index.html +++ b/modules/supplier/front/fiscal-data/index.html @@ -118,8 +118,6 @@ rule vn-focus> - - + + - - {{name}} ({{country.country}}) + + + + diff --git a/modules/supplier/front/fiscal-data/locale/es.yml b/modules/supplier/front/fiscal-data/locale/es.yml index 4cb537198..5232dd95d 100644 --- a/modules/supplier/front/fiscal-data/locale/es.yml +++ b/modules/supplier/front/fiscal-data/locale/es.yml @@ -3,3 +3,4 @@ Sage transaction type: Tipo de transacción Sage Sage withholding: Retención Sage Supplier activity: Actividad proveedor Healt register: Pasaporte sanitario +Trucker: Transportista \ No newline at end of file From 65e32f5e7fc1b0c3b1ff0d9a7fa5564d12f0e84b Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 12:40:13 +0200 Subject: [PATCH 64/77] feat: client search panel add find by zone --- .../10500-november/00-zone_getPostalCode.sql | 56 ++++++ modules/client/back/methods/client/filter.js | 174 ++++++++++++++++++ modules/client/back/models/client-methods.js | 1 + modules/client/front/main/index.html | 5 +- modules/client/front/main/index.js | 26 --- modules/client/front/search-panel/index.html | 7 + 6 files changed, 240 insertions(+), 29 deletions(-) create mode 100644 db/changes/10500-november/00-zone_getPostalCode.sql create mode 100644 modules/client/back/methods/client/filter.js diff --git a/db/changes/10500-november/00-zone_getPostalCode.sql b/db/changes/10500-november/00-zone_getPostalCode.sql new file mode 100644 index 000000000..b71306a58 --- /dev/null +++ b/db/changes/10500-november/00-zone_getPostalCode.sql @@ -0,0 +1,56 @@ +DROP PROCEDURE IF EXISTS `vn`.`zone_getPostalCode`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getPostalCode`(vSelf INT) +BEGIN +/** + * Devuelve los códigos postales incluidos en una zona + * + * @return vNewTicket + */ + DECLARE vGeoFk INT DEFAULT NULL; + + DROP TEMPORARY TABLE IF EXISTS tmp.zoneNodes; + CREATE TEMPORARY TABLE tmp.zoneNodes ( + geoFk INT, + name VARCHAR(100), + parentFk INT, + sons INT, + isChecked BOOL DEFAULT 0, + zoneFk INT, + PRIMARY KEY zoneNodesPk (zoneFk, geoFk), + INDEX(geoFk)) + ENGINE = MEMORY; + + CALL zone_getLeaves2(vSelf, NULL , NULL); + + UPDATE tmp.zoneNodes zn + SET isChecked = 0 + WHERE parentFk IS NULL; + + myLoop: LOOP + SET vGeoFk = NULL; + SELECT geoFk INTO vGeoFk + FROM tmp.zoneNodes zn + WHERE NOT isChecked + LIMIT 1; + + CALL zone_getLeaves2(vSelf, vGeoFk, NULL); + UPDATE tmp.zoneNodes + SET isChecked = TRUE + WHERE geoFk = vGeoFk; + + IF vGeoFk IS NULL THEN + LEAVE myLoop; + END IF; + END LOOP; + + DELETE FROM tmp.zoneNodes + WHERE sons > 0; + + SELECT zn.geoFk, zn.name + FROM tmp.zoneNodes zn + JOIN zone z ON z.id = zn.zoneFk; +END$$ +DELIMITER ; diff --git a/modules/client/back/methods/client/filter.js b/modules/client/back/methods/client/filter.js new file mode 100644 index 000000000..0e9eaf160 --- /dev/null +++ b/modules/client/back/methods/client/filter.js @@ -0,0 +1,174 @@ + +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('filter', { + description: 'Find all clients matched by the filter', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + }, + { + arg: 'search', + type: 'string', + description: `If it's and integer searchs by id, otherwise it searchs by name`, + }, + { + arg: 'name', + type: 'string', + description: 'The client name', + }, + { + arg: 'salesPersonFk', + type: 'number', + }, + { + arg: 'fi', + type: 'string', + description: 'The client fiscal id', + }, + { + arg: 'socialName', + type: 'string', + }, + { + arg: 'city', + type: 'string', + }, + { + arg: 'postcode', + type: 'string', + }, + { + arg: 'provinceFk', + type: 'number', + }, + { + arg: 'email', + type: 'string', + }, + { + arg: 'phone', + type: 'string', + }, + { + arg: 'zoneFk', + type: 'number', + }, + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/filter`, + verb: 'GET' + } + }); + + Self.filter = async(ctx, filter, options) => { + const conn = Self.dataSource.connector; + const myOptions = {}; + const postalCode = []; + const args = ctx.args; + if (typeof options == 'object') + Object.assign(myOptions, options); + if (args.zoneFk) { + query = `CALL vn.zone_getPostalCode(?)`; + const [geos] = await Self.rawSql(query, [args.zoneFk]); + for (let geo of geos) + postalCode.push(geo.name); + } + + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {'c.id': {inq: value}} + : {'c.name': {like: `%${value}%`}}; + case 'name': + case 'salesPersonFk': + case 'fi': + case 'socialName': + case 'city': + case 'postcode': + case 'provinceFk': + case 'email': + case 'phone': + param = `c.${param}`; + return {[param]: value}; + case 'zoneFk': + param = 'a.postalCode'; + return {[param]: {inq: postalCode}}; + } + }); + + 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, + pm.id AS payMethodFk, + pm.name AS payMethod, + 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 payMethod pm ON pm.id = c.payMethodFk + LEFT JOIN sage.TiposIva sti ON sti.CodigoIva = c.taxTypeSageFk + LEFT JOIN sage.TiposTransacciones stt ON stt.CodigoTransaccion = c.transactionTypeSageFk + LEFT JOIN vn.address a ON a.id = c.defaultAddressFk + ` + ); + + 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-methods.js b/modules/client/back/models/client-methods.js index 04d10413a..5134e3942 100644 --- a/modules/client/back/models/client-methods.js +++ b/modules/client/back/models/client-methods.js @@ -47,4 +47,5 @@ module.exports = Self => { require('../methods/client/incotermsAuthorizationHtml')(Self); require('../methods/client/incotermsAuthorizationEmail')(Self); require('../methods/client/consumptionSendQueued')(Self); + require('../methods/client/filter')(Self); }; diff --git a/modules/client/front/main/index.html b/modules/client/front/main/index.html index e8bc4b988..0787858ae 100644 --- a/modules/client/front/main/index.html +++ b/modules/client/front/main/index.html @@ -1,6 +1,6 @@ @@ -10,8 +10,7 @@ vn-focus panel="vn-client-search-panel" info="Search client by id or name" - model="model" - expr-builder="$ctrl.exprBuilder(param, value)"> + model="model"> diff --git a/modules/client/front/main/index.js b/modules/client/front/main/index.js index 1069d3487..346880f4c 100644 --- a/modules/client/front/main/index.js +++ b/modules/client/front/main/index.js @@ -2,32 +2,6 @@ import ngModule from '../module'; import ModuleMain from 'salix/components/module-main'; export default class Client extends ModuleMain { - exprBuilder(param, value) { - switch (param) { - case 'search': - return /^\d+$/.test(value) - ? {id: value} - : {or: [{name: {like: `%${value}%`}}, {socialName: {like: `%${value}%`}}]}; - case 'phone': - return { - or: [ - {phone: value}, - {mobile: value} - ] - }; - case 'name': - case 'socialName': - case 'city': - case 'email': - return {[param]: {like: `%${value}%`}}; - case 'id': - case 'fi': - case 'postcode': - case 'provinceFk': - case 'salesPersonFk': - return {[param]: value}; - } - } } ngModule.vnComponent('vnClient', { diff --git a/modules/client/front/search-panel/index.html b/modules/client/front/search-panel/index.html index 234cb6f53..a02f93882 100644 --- a/modules/client/front/search-panel/index.html +++ b/modules/client/front/search-panel/index.html @@ -58,6 +58,13 @@ value-field="id" label="Province"> + + Date: Wed, 26 Oct 2022 12:48:48 +0200 Subject: [PATCH 65/77] refs #4645 valores ref cambiados --- e2e/helpers/selectors.js | 3 +- e2e/paths/12-entry/05_basicData.spec.js | 8 ++++ modules/entry/back/methods/entry/filter.js | 3 +- .../entry/back/methods/entry/importBuys.js | 10 ++++- .../methods/entry/specs/importBuys.spec.js | 9 +++-- modules/entry/back/models/entry.json | 13 ++++++- modules/entry/front/basic-data/index.html | 39 ++++++++++++------- modules/entry/front/index/index.html | 4 +- modules/entry/front/index/locale/es.yml | 3 +- modules/entry/front/search-panel/index.html | 9 ++++- .../entry/front/search-panel/locale/es.yml | 3 +- modules/entry/front/summary/index.html | 5 ++- modules/entry/front/summary/locale/es.yml | 2 +- 13 files changed, 81 insertions(+), 30 deletions(-) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index cd6d39795..bf918c74a 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -1100,7 +1100,8 @@ export default { anyBuyLine: 'vn-entry-summary tr.dark-row' }, entryBasicData: { - reference: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.ref"]', + reference: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.reference"]', + invoiceNumber: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.invoiceNumber"]', notes: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.notes"]', observations: 'vn-entry-basic-data vn-textarea[ng-model="$ctrl.entry.observation"]', supplier: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]', diff --git a/e2e/paths/12-entry/05_basicData.spec.js b/e2e/paths/12-entry/05_basicData.spec.js index c1aa14019..3b5f40c35 100644 --- a/e2e/paths/12-entry/05_basicData.spec.js +++ b/e2e/paths/12-entry/05_basicData.spec.js @@ -19,6 +19,7 @@ describe('Entry basic data path', () => { it('should edit the basic data', async() => { await page.write(selectors.entryBasicData.reference, 'new movement 8'); + await page.write(selectors.entryBasicData.invoiceNumber, 'new movement 8'); await page.write(selectors.entryBasicData.notes, 'new notes'); await page.write(selectors.entryBasicData.observations, ' edited'); await page.autocompleteSearch(selectors.entryBasicData.supplier, 'Plants nick'); @@ -45,6 +46,13 @@ describe('Entry basic data path', () => { expect(result).toEqual('new movement 8'); }); + it('should confirm the invoiceNumber was edited', async() => { + await page.reloadSection('entry.card.basicData'); + const result = await page.waitToGetProperty(selectors.entryBasicData.invoiceNumber, 'value'); + + expect(result).toEqual('new movement 8'); + }); + it('should confirm the note was edited', async() => { const result = await page.waitToGetProperty(selectors.entryBasicData.notes, 'value'); diff --git a/modules/entry/back/methods/entry/filter.js b/modules/entry/back/methods/entry/filter.js index 1ba4166dc..3a08bffff 100644 --- a/modules/entry/back/methods/entry/filter.js +++ b/modules/entry/back/methods/entry/filter.js @@ -154,7 +154,8 @@ module.exports = Self => { e.id, e.supplierFk, e.dated, - e.ref, + e.ref reference, + e.ref invoiceNumber, e.isBooked, e.isExcludedFromAvailable, e.notes, diff --git a/modules/entry/back/methods/entry/importBuys.js b/modules/entry/back/methods/entry/importBuys.js index fb2f5f452..fdc6b058e 100644 --- a/modules/entry/back/methods/entry/importBuys.js +++ b/modules/entry/back/methods/entry/importBuys.js @@ -12,10 +12,15 @@ module.exports = Self => { http: {source: 'path'} }, { - arg: 'ref', + arg: 'reference', type: 'string', description: 'The buyed boxes ids', }, + { + arg: 'invoiceNumber', + type: 'string', + description: 'The registered invoice number', + }, { arg: 'observation', type: 'string', @@ -63,7 +68,8 @@ module.exports = Self => { await entry.updateAttributes({ observation: args.observation, - ref: args.ref + reference: args.reference, + invoiceNumber: args.invoiceNumber }, myOptions); const travel = entry.travel(); diff --git a/modules/entry/back/methods/entry/specs/importBuys.spec.js b/modules/entry/back/methods/entry/specs/importBuys.spec.js index 9cf6f4300..4f9204c6a 100644 --- a/modules/entry/back/methods/entry/specs/importBuys.spec.js +++ b/modules/entry/back/methods/entry/specs/importBuys.spec.js @@ -15,13 +15,15 @@ describe('entry import()', () => { }); it('should import the buy rows', async() => { - const expectedRef = '1, 2'; + const expectedReference = '1, 2'; + const expectedInvoiceNumber = '1, 2'; const expectedObservation = '123456'; const ctx = { req: activeCtx, args: { observation: expectedObservation, - ref: expectedRef, + reference: expectedReference, + invoiceNumber: expectedInvoiceNumber, buys: [ { itemFk: 1, @@ -58,7 +60,8 @@ describe('entry import()', () => { }, options); expect(updatedEntry.observation).toEqual(expectedObservation); - expect(updatedEntry.ref).toEqual(expectedRef); + expect(updatedEntry.reference).toEqual(expectedReference); + expect(updatedEntry.invoiceNumber).toEqual(expectedInvoiceNumber); expect(entryBuys.length).toEqual(4); await tx.rollback(); diff --git a/modules/entry/back/models/entry.json b/modules/entry/back/models/entry.json index c456859a5..d3c802ad2 100644 --- a/modules/entry/back/models/entry.json +++ b/modules/entry/back/models/entry.json @@ -18,8 +18,17 @@ "dated": { "type": "date" }, - "ref": { - "type": "string" + "reference": { + "type": "string", + "mysql": { + "columnName": "ref" + } + }, + "invoiceNumber": { + "type": "string", + "mysql": { + "columnName": "ref" + } }, "isBooked": { "type": "boolean" diff --git a/modules/entry/front/basic-data/index.html b/modules/entry/front/basic-data/index.html index 423e9d70d..68a65e890 100644 --- a/modules/entry/front/basic-data/index.html +++ b/modules/entry/front/basic-data/index.html @@ -48,7 +48,7 @@ @@ -61,17 +61,25 @@ - - + label="Invoice number" + ng-model="$ctrl.entry.invoiceNumber" + rule + vn-focus> + + + - - + + + + Id Landed Reference + Invoice number Supplier Booked Confirmed @@ -45,7 +46,8 @@ {{::entry.landed | date:'dd/MM/yyyy'}} - {{::entry.ref}} + {{::entry.reference}} + {{::entry.invoiceNumber}} {{::entry.supplierName}} diff --git a/modules/entry/front/index/locale/es.yml b/modules/entry/front/index/locale/es.yml index 519f8e39a..4f12fc7bb 100644 --- a/modules/entry/front/index/locale/es.yml +++ b/modules/entry/front/index/locale/es.yml @@ -14,4 +14,5 @@ Booked: Contabilizada Is inventory: Inventario Notes: Notas Status: Estado -Selection: Selección \ No newline at end of file +Selection: Selección +Invoice number: Núm. factura \ No newline at end of file diff --git a/modules/entry/front/search-panel/index.html b/modules/entry/front/search-panel/index.html index 38acdf77d..adcb9d6d4 100644 --- a/modules/entry/front/search-panel/index.html +++ b/modules/entry/front/search-panel/index.html @@ -13,9 +13,16 @@ + ng-model="filter.reference"> + + + + diff --git a/modules/entry/front/search-panel/locale/es.yml b/modules/entry/front/search-panel/locale/es.yml index 88f164145..05b71da99 100644 --- a/modules/entry/front/search-panel/locale/es.yml +++ b/modules/entry/front/search-panel/locale/es.yml @@ -5,4 +5,5 @@ From: Desde To: Hasta Agency: Agencia Warehouse: Almacén -Search entry by id or a suppliers by name or alias: Buscar entrada por id o proveedores por nombre y alias \ No newline at end of file +Search entry by id or a suppliers by name or alias: Buscar entrada por id o proveedores por nombre y alias +Invoice number: Núm. factura \ No newline at end of file diff --git a/modules/entry/front/summary/index.html b/modules/entry/front/summary/index.html index ffd8aafab..04844ab99 100644 --- a/modules/entry/front/summary/index.html +++ b/modules/entry/front/summary/index.html @@ -27,7 +27,10 @@ value="{{$ctrl.entryData.company.code}}"> + value="{{$ctrl.entryData.reference}}"> + + diff --git a/modules/entry/front/summary/locale/es.yml b/modules/entry/front/summary/locale/es.yml index a141ce56c..1761561ed 100644 --- a/modules/entry/front/summary/locale/es.yml +++ b/modules/entry/front/summary/locale/es.yml @@ -8,4 +8,4 @@ Minimum price: Precio mínimo Buys: Compras Travel: Envio Go to the entry: Ir a la entrada - +Invoice number: Núm. factura From b39b28e43d011689acc911adedfe8b1e9609a5d9 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 13:10:17 +0200 Subject: [PATCH 66/77] =?UTF-8?q?refactor:=20a=C3=B1adido=20salto=20de=20l?= =?UTF-8?q?ineas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/client/back/methods/client/filter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/client/back/methods/client/filter.js b/modules/client/back/methods/client/filter.js index 0e9eaf160..7cf4ba2ea 100644 --- a/modules/client/back/methods/client/filter.js +++ b/modules/client/back/methods/client/filter.js @@ -75,8 +75,10 @@ module.exports = Self => { const myOptions = {}; const postalCode = []; const args = ctx.args; + if (typeof options == 'object') Object.assign(myOptions, options); + if (args.zoneFk) { query = `CALL vn.zone_getPostalCode(?)`; const [geos] = await Self.rawSql(query, [args.zoneFk]); From fa89d1f944d724e11b8b93d4cdf389a8405a5531 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 13:10:50 +0200 Subject: [PATCH 67/77] fix: comentario corregido --- db/changes/10500-november/00-zone_getPostalCode.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/db/changes/10500-november/00-zone_getPostalCode.sql b/db/changes/10500-november/00-zone_getPostalCode.sql index b71306a58..58a281cb2 100644 --- a/db/changes/10500-november/00-zone_getPostalCode.sql +++ b/db/changes/10500-november/00-zone_getPostalCode.sql @@ -6,8 +6,6 @@ CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getPostalCode`(vSelf INT) BEGIN /** * Devuelve los códigos postales incluidos en una zona - * - * @return vNewTicket */ DECLARE vGeoFk INT DEFAULT NULL; From bc6e6397462333abec2d5149fcde37e67bcf69de Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 26 Oct 2022 13:23:54 +0200 Subject: [PATCH 68/77] refs #4644 cambio en un test --- modules/supplier/front/descriptor/index.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/supplier/front/descriptor/index.spec.js b/modules/supplier/front/descriptor/index.spec.js index 8926fa4d1..4d16c5183 100644 --- a/modules/supplier/front/descriptor/index.spec.js +++ b/modules/supplier/front/descriptor/index.spec.js @@ -27,6 +27,7 @@ describe('Supplier Component vnSupplierDescriptor', () => { 'payDay', 'isActive', 'isSerious', + 'isTrucker', 'account' ], include: [ From c0b95564c3fc5bd764603524fffa1e99d8125541 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 14:25:17 +0200 Subject: [PATCH 69/77] refactor: borrado codigo sql innecesario --- modules/client/back/methods/client/filter.js | 37 +++----------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/modules/client/back/methods/client/filter.js b/modules/client/back/methods/client/filter.js index 7cf4ba2ea..3e1ea43bb 100644 --- a/modules/client/back/methods/client/filter.js +++ b/modules/client/back/methods/client/filter.js @@ -114,53 +114,24 @@ module.exports = Self => { const stmts = []; const stmt = new ParameterizedSQL( `SELECT - c.id, + DISTINCT c.id, c.name, - c.socialName, c.fi, - c.credit, - c.creditInsurance, + c.socialName, 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, - pm.id AS payMethodFk, - pm.name AS payMethod, - sti.CodigoIva AS sageTaxTypeFk, - sti.Iva AS sageTaxType, - stt.CodigoTransaccion AS sageTransactionTypeFk, - stt.Transaccion AS sageTransactionType + u.name AS salesPerson 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 payMethod pm ON pm.id = c.payMethodFk - LEFT JOIN sage.TiposIva sti ON sti.CodigoIva = c.taxTypeSageFk - LEFT JOIN sage.TiposTransacciones stt ON stt.CodigoTransaccion = c.transactionTypeSageFk - LEFT JOIN vn.address a ON a.id = c.defaultAddressFk + JOIN vn.address a ON a.clientFk = c.id ` ); From 1d908002e22481c39bd5e72f8c0add5e45161514 Mon Sep 17 00:00:00 2001 From: vicent Date: Wed, 26 Oct 2022 14:25:27 +0200 Subject: [PATCH 70/77] feat: add backTest --- db/dump/fixtures.sql | 85 ++++---- .../back/methods/client/specs/filter.spec.js | 199 ++++++++++++++++++ 2 files changed, 242 insertions(+), 42 deletions(-) create mode 100644 modules/client/back/methods/client/specs/filter.spec.js diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 5b769e285..beed24739 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -353,48 +353,48 @@ INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`) INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`) VALUES - (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 1), - (2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 1), - (3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 1), - (4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 1), - (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1), - (6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1), - (7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1), - (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1), - (9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1), - (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1), - (11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1), - (12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1), - (101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), - (103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), - (104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), - (105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), - (109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), - (121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0), - (122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0), - (123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 0), - (124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0), - (125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0), - (126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0), - (127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0), - (128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0), - (129, 'Luke Cages Bar', 'address 29', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0), - (130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0); + (1, 'Bruce Wayne', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 1), + (2, 'Petter Parker', '20 Ingram Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 1), + (3, 'Clark Kent', '344 Clinton Street', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 1), + (4, 'Tony Stark', '10880 Malibu Point', 'Gotham', 46460, 1, 1111111111, 222222222, 1 , 1104, 2, NULL, NULL, 0, 1), + (5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1), + (6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1), + (7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1), + (8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1), + (9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1), + (10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1), + (11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1), + (12, 'Trash', 'New York city', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1112, 10, NULL, NULL, 0, 1), + (101, 'Somewhere in Thailand', 'address 01', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (102, 'Somewhere in Poland', 'address 02', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), + (103, 'Somewhere in Japan', 'address 03', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), + (104, 'Somewhere in Spain', 'address 04', 'Gotham', 46460, 1, 3333333333, 444444444, 1, 1109, 2, NULL, NULL, 0, 0), + (105, 'Somewhere in Potugal', 'address 05', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (106, 'Somewhere in UK', 'address 06', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (107, 'Somewhere in Valencia', 'address 07', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (108, 'Somewhere in Gotham', 'address 08', 'Gotham', 46460, 1, 5555555555, 666666666, 1, 1109, 2, NULL, NULL, 0, 0), + (109, 'Somewhere in London', 'address 09', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (110, 'Somewhere in Algemesi', 'address 10', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (111, 'Somewhere in Carlet', 'address 11', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (112, 'Somewhere in Campanar', 'address 12', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (113, 'Somewhere in Malilla', 'address 13', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (114, 'Somewhere in France', 'address 14', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (115, 'Somewhere in Birmingham', 'address 15', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (116, 'Somewhere in Scotland', 'address 16', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (117, 'Somewhere in nowhere', 'address 17', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (118, 'Somewhere over the rainbow', 'address 18', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (119, 'Somewhere in Alberic', 'address 19', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (120, 'Somewhere in Montortal', 'address 20', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 0), + (121, 'the bat cave', 'address 21', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1101, 2, NULL, NULL, 0, 0), + (122, 'NY roofs', 'address 22', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1102, 2, NULL, NULL, 0, 0), + (123, 'The phone box', 'address 23', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1103, 2, NULL, NULL, 0, 0), + (124, 'Stark tower Gotham', 'address 24', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1104, 2, NULL, NULL, 0, 0), + (125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0), + (126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0), + (127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0), + (128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0), + (129, 'Luke Cages Bar', 'address 29', 'Gotham', 'EC170150', 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0), + (130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0); INSERT INTO `vn`.`address`( `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `isActive`, `clientFk`, `agencyModeFk`, `isDefaultAddress`) SELECT name, CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, 1, id, 2, 1 @@ -2047,6 +2047,7 @@ INSERT INTO `vn`.`zoneIncluded` (`zoneFk`, `geoFk`, `isIncluded`) (8, 4, 0), (8, 5, 0), (8, 1, 1), + (9, 7, 1), (10, 14, 1); INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `dated`) diff --git a/modules/client/back/methods/client/specs/filter.spec.js b/modules/client/back/methods/client/specs/filter.spec.js new file mode 100644 index 000000000..679585050 --- /dev/null +++ b/modules/client/back/methods/client/specs/filter.spec.js @@ -0,0 +1,199 @@ +const {models} = require('vn-loopback/server/server'); + +describe('client filter()', () => { + it('should return the clients matching the filter with a limit of 20 rows', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {}}; + const filter = {limit: '8'}; + const result = await models.Client.filter(ctx, filter, options); + + expect(result.length).toEqual(8); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the client "Bruce Wayne" matching the search argument with his name', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {search: 'Bruce Wayne'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstResult = result[0]; + + expect(result.length).toEqual(1); + expect(firstResult.name).toEqual('Bruce Wayne'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the client "Bruce Wayne" matching the search argument with his id', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {search: '1101'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstResult = result[0]; + + expect(result.length).toEqual(1); + expect(firstResult.name).toEqual('Bruce Wayne'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the client "Bruce Wayne" matching the name argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {name: 'Bruce Wayne'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstResult = result[0]; + + expect(result.length).toEqual(1); + expect(firstResult.name).toEqual('Bruce Wayne'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "salesPersonFk" argument', async() => { + const tx = await models.Client.beginTransaction({}); + const salesPersonId = 18; + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {salesPersonFk: salesPersonId}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const randomIndex = Math.floor(Math.random() * result.length); + const randomResultClient = result[randomIndex]; + + expect(result.length).toBeGreaterThanOrEqual(5); + expect(randomResultClient.salesPersonFk).toEqual(salesPersonId); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "fi" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {fi: '251628698'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const firstClient = result[0]; + + expect(result.length).toEqual(1); + expect(firstClient.name).toEqual('Max Eisenhardt'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "city" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {city: 'Gotham'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const randomIndex = Math.floor(Math.random() * result.length); + const randomResultClient = result[randomIndex]; + + expect(result.length).toBeGreaterThanOrEqual(20); + expect(randomResultClient.city.toLowerCase()).toEqual('gotham'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "postcode" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {postcode: '46460'}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + const randomIndex = Math.floor(Math.random() * result.length); + const randomResultClient = result[randomIndex]; + + expect(result.length).toBeGreaterThanOrEqual(20); + expect(randomResultClient.postcode).toEqual('46460'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should return the clients matching the "zoneFk" argument', async() => { + const tx = await models.Client.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const ctx = {req: {accessToken: {userId: 1}}, args: {zoneFk: 9}}; + const filter = {}; + const result = await models.Client.filter(ctx, filter, options); + + expect(result.length).toBe(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); From 736de00e77da5cf492a7e1e9a433233d97053c71 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 27 Oct 2022 09:18:00 +0200 Subject: [PATCH 71/77] deleted the test as requested on #1885 --- db/tests/vn/orderConfirmWithUser.spec.js | 37 ------------------------ 1 file changed, 37 deletions(-) delete mode 100644 db/tests/vn/orderConfirmWithUser.spec.js diff --git a/db/tests/vn/orderConfirmWithUser.spec.js b/db/tests/vn/orderConfirmWithUser.spec.js deleted file mode 100644 index f2a3d0c4e..000000000 --- a/db/tests/vn/orderConfirmWithUser.spec.js +++ /dev/null @@ -1,37 +0,0 @@ -const app = require('vn-loopback/server/server'); -const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; - -// #1885 -xdescribe('order_confirmWithUser()', () => { - it('should confirm an order', async() => { - let stmts = []; - let stmt; - - stmts.push('START TRANSACTION'); - - let params = { - orderFk: 10, - userId: 9 - }; - // problema: la funcion order_confirmWithUser tiene una transacción, por tanto esta nunca hace rollback - stmt = new ParameterizedSQL('CALL hedera.order_confirmWithUser(?, ?)', [ - params.orderFk, - params.userId - ]); - stmts.push(stmt); - - stmt = new ParameterizedSQL('SELECT confirmed FROM hedera.order WHERE id = ?', [ - params.orderFk - ]); - let orderIndex = stmts.push(stmt) - 1; - - stmts.push('ROLLBACK'); - - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await app.models.Ticket.rawStmt(sql); - - savedDescription = result[orderIndex][0].confirmed; - - expect(savedDescription).toBeTruthy(); - }); -}); From b5889d2f404bbca837e666e1a0e6098d56c48ff2 Mon Sep 17 00:00:00 2001 From: alexandre Date: Mon, 31 Oct 2022 10:35:43 +0100 Subject: [PATCH 72/77] =?UTF-8?q?refs=20#4497=20servicios=20a=C3=B1adidos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- print/templates/reports/invoice/invoice.html | 2 +- print/templates/reports/invoice/invoice.js | 2 +- print/templates/reports/invoice/locale/en.yml | 3 ++- print/templates/reports/invoice/locale/es.yml | 3 ++- print/templates/reports/invoice/sql/intrastat.sql | 14 ++++++++++++-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/print/templates/reports/invoice/invoice.html b/print/templates/reports/invoice/invoice.html index 1d646a0db..aed4b38f3 100644 --- a/print/templates/reports/invoice/invoice.html +++ b/print/templates/reports/invoice/invoice.html @@ -264,7 +264,7 @@ {{row.code}} - {{row.description}} + {{row.description || $t('services') }} {{row.stems | number($i18n.locale)}} {{row.netKg | number($i18n.locale)}} {{row.subtotal | currency('EUR', $i18n.locale)}} diff --git a/print/templates/reports/invoice/invoice.js b/print/templates/reports/invoice/invoice.js index db94a7a12..31df7f7f5 100755 --- a/print/templates/reports/invoice/invoice.js +++ b/print/templates/reports/invoice/invoice.js @@ -81,7 +81,7 @@ module.exports = { return this.rawSqlFromDef(`taxes`, [reference]); }, fetchIntrastat(reference) { - return this.rawSqlFromDef(`intrastat`, [reference, reference, reference]); + return this.rawSqlFromDef(`intrastat`, [reference, reference, reference, reference]); }, fetchRectified(reference) { return this.rawSqlFromDef(`rectified`, [reference]); diff --git a/print/templates/reports/invoice/locale/en.yml b/print/templates/reports/invoice/locale/en.yml index 4e4688b55..336592f0c 100644 --- a/print/templates/reports/invoice/locale/en.yml +++ b/print/templates/reports/invoice/locale/en.yml @@ -33,4 +33,5 @@ issued: Issued plantPassport: Plant passport observations: Observations wireTransfer: "Pay method: Transferencia" -accountNumber: "Account number: {0}" \ No newline at end of file +accountNumber: "Account number: {0}" +services: Services \ No newline at end of file diff --git a/print/templates/reports/invoice/locale/es.yml b/print/templates/reports/invoice/locale/es.yml index d37e77943..32f6fc708 100644 --- a/print/templates/reports/invoice/locale/es.yml +++ b/print/templates/reports/invoice/locale/es.yml @@ -33,4 +33,5 @@ issued: F. emisión plantPassport: Pasaporte fitosanitario observations: Observaciones wireTransfer: "Forma de pago: Transferencia" -accountNumber: "Número de cuenta: {0}" \ No newline at end of file +accountNumber: "Número de cuenta: {0}" +services: Servicios \ No newline at end of file diff --git a/print/templates/reports/invoice/sql/intrastat.sql b/print/templates/reports/invoice/sql/intrastat.sql index e2ee47667..5cc3ebd7f 100644 --- a/print/templates/reports/invoice/sql/intrastat.sql +++ b/print/templates/reports/invoice/sql/intrastat.sql @@ -1,4 +1,4 @@ -SELECT +(SELECT ir.id code, ir.description description, CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, @@ -19,4 +19,14 @@ SELECT WHERE t.refFk = ? AND i.intrastatFk GROUP BY i.intrastatFk - ORDER BY i.intrastatFk; \ No newline at end of file + ORDER BY i.intrastatFk) +UNION ALL +(SELECT + NULL AS code, + NULL AS description, + 0 AS stems, + 0 AS netKg, + CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)) AS subtotal + FROM vn.ticketService ts + JOIN vn.ticket t ON ts.ticketFk = t.id + WHERE t.refFk = ?); \ No newline at end of file From a7b1ee85fec2bcd7eab29fed84c642de265224f7 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 2 Nov 2022 07:27:57 +0100 Subject: [PATCH 73/77] fix changes folder --- .../{10490-goldenSummer => 10500-november}/00-aclNotification.sql | 0 .../{10490-august => 10500-november}/00-packingSiteConfig.sql | 0 .../{10490-august => 10500-november}/00-packingSiteUpdate.sql | 0 db/changes/{10490-august => 10500-november}/00-salix_url.sql | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename db/changes/{10490-goldenSummer => 10500-november}/00-aclNotification.sql (100%) rename db/changes/{10490-august => 10500-november}/00-packingSiteConfig.sql (100%) rename db/changes/{10490-august => 10500-november}/00-packingSiteUpdate.sql (100%) rename db/changes/{10490-august => 10500-november}/00-salix_url.sql (100%) diff --git a/db/changes/10490-goldenSummer/00-aclNotification.sql b/db/changes/10500-november/00-aclNotification.sql similarity index 100% rename from db/changes/10490-goldenSummer/00-aclNotification.sql rename to db/changes/10500-november/00-aclNotification.sql diff --git a/db/changes/10490-august/00-packingSiteConfig.sql b/db/changes/10500-november/00-packingSiteConfig.sql similarity index 100% rename from db/changes/10490-august/00-packingSiteConfig.sql rename to db/changes/10500-november/00-packingSiteConfig.sql diff --git a/db/changes/10490-august/00-packingSiteUpdate.sql b/db/changes/10500-november/00-packingSiteUpdate.sql similarity index 100% rename from db/changes/10490-august/00-packingSiteUpdate.sql rename to db/changes/10500-november/00-packingSiteUpdate.sql diff --git a/db/changes/10490-august/00-salix_url.sql b/db/changes/10500-november/00-salix_url.sql similarity index 100% rename from db/changes/10490-august/00-salix_url.sql rename to db/changes/10500-november/00-salix_url.sql From 9891f7060d6d924015e18cf0034e0936daa577c6 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 2 Nov 2022 09:40:09 +0100 Subject: [PATCH 74/77] fix(smart-table): monitor only ticketFilter use url and add ?q in routes --- front/core/components/searchbar/searchbar.js | 2 +- front/core/components/smart-table/index.js | 20 +++++++++++-------- modules/claim/front/routes.json | 6 +++--- modules/client/front/routes.json | 14 ++++++------- modules/item/front/fixed-price/index.html | 14 ++++++------- modules/item/front/fixed-price/index.js | 12 ++--------- .../monitor/front/index/clients/index.html | 16 ++++++++------- modules/route/front/routes.json | 8 ++++---- 8 files changed, 45 insertions(+), 47 deletions(-) diff --git a/front/core/components/searchbar/searchbar.js b/front/core/components/searchbar/searchbar.js index dff4836db..10ec1f608 100644 --- a/front/core/components/searchbar/searchbar.js +++ b/front/core/components/searchbar/searchbar.js @@ -290,7 +290,7 @@ export default class Searchbar extends Component { } let where = null; - let params = null; + let params = {}; if (this.exprBuilder) { where = buildFilter(filter, diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index 31541143c..8d2c3c153 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -19,9 +19,11 @@ export default class SmartTable extends Component { this.transclude(); } - $onInit() { - if (this.model) + $onChanges() { + if (this.model) { this.defaultFilter(); + this.defaultOrder(); + } } $onDestroy() { @@ -53,11 +55,8 @@ export default class SmartTable extends Component { set model(value) { this._model = value; - if (value) { + if (value) this.$.model = value; - this.defaultFilter(); - this.defaultOrder(); - } } getDefaultViewConfig() { @@ -168,7 +167,8 @@ export default class SmartTable extends Component { } defaultFilter() { - if (!this.$params.q) return; + if (this.disabledTableFilter || !this.$params.q) return; + const stateFilter = JSON.parse(this.$params.q).tableQ; if (!stateFilter || !this.exprBuilder) return; @@ -188,6 +188,8 @@ export default class SmartTable extends Component { } defaultOrder() { + if (this.disabledTableOrder) return; + let stateOrder; if (this.$params.q) stateOrder = JSON.parse(this.$params.q).tableOrder; @@ -607,6 +609,8 @@ ngModule.vnComponent('smartTable', { autoSave: '
- @@ -34,18 +34,18 @@ - - -
Item ID + Description Warehouse P.P.U. P.P.P. @@ -170,7 +170,7 @@ - + - \ No newline at end of file + diff --git a/modules/item/front/fixed-price/index.js b/modules/item/front/fixed-price/index.js index b84c2cc2d..89ce0b172 100644 --- a/modules/item/front/fixed-price/index.js +++ b/modules/item/front/fixed-price/index.js @@ -12,14 +12,6 @@ export default class Controller extends Section { }, defaultSearch: true, columns: [ - { - field: 'itemName', - autocomplete: { - url: 'Items', - showField: 'name', - valueField: 'id' - } - }, { field: 'warehouseFk', autocomplete: { @@ -105,8 +97,8 @@ export default class Controller extends Section { exprBuilder(param, value) { switch (param) { - case 'itemName': - return {'i.id': value}; + case 'name': + return {'i.name': {like: `%${value}%`}}; case 'itemFk': case 'warehouseFk': case 'rate2': diff --git a/modules/monitor/front/index/clients/index.html b/modules/monitor/front/index/clients/index.html index eafc2256e..381c0e1ae 100644 --- a/modules/monitor/front/index/clients/index.html +++ b/modules/monitor/front/index/clients/index.html @@ -19,22 +19,24 @@ - @@ -100,9 +102,9 @@ - - \ No newline at end of file + diff --git a/modules/route/front/routes.json b/modules/route/front/routes.json index f5e7d9ae8..75e1fdc57 100644 --- a/modules/route/front/routes.json +++ b/modules/route/front/routes.json @@ -39,7 +39,7 @@ "abstract": true, "component": "vn-route-card" }, { - "url": "/agency-term", + "url": "/agency-term?q", "abstract": true, "state": "route.agencyTerm", "component": "ui-view" @@ -49,12 +49,12 @@ "component": "vn-agency-term-index", "description": "Autonomous", "acl": ["administrative"] - },{ + },{ "url": "/createInvoiceIn?q", "state": "route.agencyTerm.createInvoiceIn", "component": "vn-agency-term-create-invoice-in", "description": "File management", - "params": { + "params": { "route": "$ctrl.route" }, "acl": ["administrative"] @@ -92,4 +92,4 @@ "acl": ["delivery"] } ] -} \ No newline at end of file +} From dc3ee2c30d244caee1df896d893150caeffc3124 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 2 Nov 2022 10:18:38 +0100 Subject: [PATCH 75/77] =?UTF-8?q?refs=20#2818=20sincronizaci=C3=B3n=20quit?= =?UTF-8?q?ada=20en=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/account/back/models/ldap-config.js | 31 +++++++++++++-------- modules/account/back/models/samba-config.js | 9 ++++-- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/modules/account/back/models/ldap-config.js b/modules/account/back/models/ldap-config.js index 819659066..a2a2684a9 100644 --- a/modules/account/back/models/ldap-config.js +++ b/modules/account/back/models/ldap-config.js @@ -5,6 +5,8 @@ const crypto = require('crypto'); const nthash = require('smbhash').nthash; module.exports = Self => { + const shouldSync = process.env.NODE_ENV !== 'test'; + Self.getSynchronizer = async function() { return await Self.findOne({ fields: [ @@ -30,6 +32,7 @@ module.exports = Self => { }, async syncUser(userName, info, password) { + let { client, accountConfig @@ -130,13 +133,14 @@ module.exports = Self => { })); } - if (changes.length) + if (shouldSync && changes.length) await client.modify(dn, changes); - } else + } else if (shouldSync) await client.add(dn, newEntry); } else { try { - await client.del(dn); + if (shouldSync) + await client.del(dn); console.log(` -> User '${userName}' removed from LDAP`); } catch (e) { if (e.name !== 'NoSuchObjectError') throw e; @@ -196,17 +200,19 @@ module.exports = Self => { for (let group of groups) { try { let dn = `cn=${group},${groupDn}`; - await client.modify(dn, new ldap.Change({ - operation, - modification: {memberUid: userName} - })); + if (shouldSync) { + await client.modify(dn, new ldap.Change({ + operation, + modification: {memberUid: userName} + })); + } } catch (err) { if (err.name !== 'NoSuchObjectError') throw err; } } } - + await applyOperations(deleteGroups, 'delete'); await applyOperations(addGroups, 'add'); }, @@ -266,8 +272,10 @@ module.exports = Self => { filter: 'objectClass=posixGroup' }; let reqs = []; - await client.searchForeach(this.groupDn, opts, - o => reqs.push(client.del(o.dn))); + await client.searchForeach(this.groupDn, opts, object => { + if (shouldSync) + reqs.push(client.del(object.dn)); + }); await Promise.all(reqs); // Recreate roles @@ -291,7 +299,8 @@ module.exports = Self => { } let dn = `cn=${role.name},${this.groupDn}`; - reqs.push(client.add(dn, newEntry)); + if (shouldSync) + reqs.push(client.add(dn, newEntry)); } await Promise.all(reqs); } diff --git a/modules/account/back/models/samba-config.js b/modules/account/back/models/samba-config.js index 5fd62a68b..168b5ffb4 100644 --- a/modules/account/back/models/samba-config.js +++ b/modules/account/back/models/samba-config.js @@ -60,16 +60,19 @@ module.exports = Self => { return `cn=Users,${dnBase}`; }, - async syncUser(userName, info, password) { + async syncUser(userName, info, password) { let {sshClient} = this; - + let sambaUser = await this.adClient.searchOne(this.usersDn(), { scope: 'sub', attributes: ['userAccountControl'], filter: `(&(objectClass=user)(sAMAccountName=${userName}))` }); let isEnabled = sambaUser - && !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE); + && !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE); + + if (process.env.NODE_ENV === 'test') + return; if (info.hasAccount) { if (!sambaUser) { From 59e66e1f49f5445097bbcd343b3a86be95c52b55 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 2 Nov 2022 13:55:06 +0100 Subject: [PATCH 76/77] typo --- modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js | 4 ++-- print/templates/reports/invoiceIn/invoiceIn.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js index f1d17dce7..e7962c93f 100644 --- a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js @@ -2,14 +2,14 @@ const {Report} = require('vn-print'); module.exports = Self => { Self.remoteMethodCtx('invoiceInPdf', { - description: 'Returns the delivery note pdf', + description: 'Returns the invoiceIn pdf', accessType: 'READ', accepts: [ { arg: 'id', type: 'number', required: true, - description: 'The ticket id', + description: 'The invoiceIn id', http: {source: 'path'} } ], diff --git a/print/templates/reports/invoiceIn/invoiceIn.js b/print/templates/reports/invoiceIn/invoiceIn.js index cfad062ed..526c40fc6 100755 --- a/print/templates/reports/invoiceIn/invoiceIn.js +++ b/print/templates/reports/invoiceIn/invoiceIn.js @@ -87,7 +87,7 @@ module.exports = { }, props: { id: { - type: [Number, String], + type: Number, description: 'The invoice id' } } From 6d85cf7cf5bc57cd9e0e0240c7d8e921eb570dcb Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 3 Nov 2022 08:21:03 +0100 Subject: [PATCH 77/77] translations --- modules/invoiceIn/front/descriptor/index.html | 4 ++-- modules/invoiceIn/front/locale/es.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/invoiceIn/front/descriptor/index.html b/modules/invoiceIn/front/descriptor/index.html index 095bbd8e7..819615c20 100644 --- a/modules/invoiceIn/front/descriptor/index.html +++ b/modules/invoiceIn/front/descriptor/index.html @@ -28,12 +28,12 @@ - Show Invoice as PDF + Show agricultural invoice as PDF - Send Invoice as PDF + Send agricultural invoice as PDF diff --git a/modules/invoiceIn/front/locale/es.yml b/modules/invoiceIn/front/locale/es.yml index 8cdea3323..1deff32d3 100644 --- a/modules/invoiceIn/front/locale/es.yml +++ b/modules/invoiceIn/front/locale/es.yml @@ -19,5 +19,5 @@ To book: Contabilizar Total amount: Total importe Total net: Total neto Total stems: Total tallos -Show Invoice as PDF: Ver Factura Agrícola como PDF -Send Invoice as PDF: Enviar Factura Agrícola como PDF +Show agricultural invoice as PDF: Ver factura agrícola como PDF +Send agricultural invoice as PDF: Enviar factura agrícola como PDF