From 8082e3071ad2e35d163cff01838372a4682dbde7 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 27 Sep 2022 15:17:54 +0200 Subject: [PATCH 01/23] 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 89b5d7df69..fc194bc597 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 f26a6b4a2d..3168d7ee73 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 9e6e7009c9..7880f257fd 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 02/23] 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 e24fa6a9f1..373ceb95a5 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 fc194bc597..50f9d00505 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 7880f257fd..c78cbbca0d 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 03/23] 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 0ad9ad7f40..c8e0ae1684 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 0000000000..4fc2802098 --- /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 373ceb95a5..e24fa6a9f1 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 4994e15475..1095985dc1 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 50f9d00505..6e74a37795 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 e4f58d294c..efa98818d8 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 04/23] 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 6e74a37795..57a77d9801 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 efa98818d8..ed8fd9d074 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 3168d7ee73..752019313c 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 c78cbbca0d..31541143cc 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 720e24c7e3..5fd4c33b7d 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 248572feb438836e32d3b971ea83be1f1b19e03a Mon Sep 17 00:00:00 2001 From: alexm Date: Fri, 14 Oct 2022 07:40:11 +0200 Subject: [PATCH 05/23] 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 57a77d9801..dff4836dbd 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 eb58839839dc7e9a560ce4bf21af876f998b57aa Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 17 Oct 2022 15:13:27 +0200 Subject: [PATCH 06/23] 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 0000000000..71ba5710e6 --- /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 3b5aa65d9f..e2c1326714 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 33f9ee8c65..c23a14ffc5 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 cde3242963..0198e868f2 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 0000000000..37a98dfddb --- /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 0000000000..9fda2a6138 --- /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 0000000000..3fed1b867e --- /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 0000000000..4e4688b554 --- /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 0000000000..d37e77943f --- /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 0000000000..e6b1eb9956 --- /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 c9073641aa89a0b299ccc11de59b5cef8c573748 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 18 Oct 2022 14:58:38 +0200 Subject: [PATCH 07/23] 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 71ba5710e6..1b7ca9c686 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 1d646a0db9..a1d488560c 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 3fed1b867e..103f36d1cc 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 d37e77943f..6386c611aa 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 0000000000..28a80ff9cf --- /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 0000000000..0b29cd81cd --- /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 e6b1eb9956..8913a833b8 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 0000000000..18ca6db093 --- /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 08/23] 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 7e59c1a54a..8e11137814 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 a1d488560c..4bc6d8ee0e 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 103f36d1cc..cfad062ed9 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 4e4688b554..7a8767ad32 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 6386c611aa..f2fb28e644 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 8913a833b8..fe9ef1e9e0 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 18ca6db093..20df33f83a 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 066cf6f69bd3f036edd86caf47f414b1cd9990cf Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 20 Oct 2022 15:11:56 +0200 Subject: [PATCH 09/23] 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 c23a14ffc5..faff5b9765 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 4f36b33fa8..8cdea3323a 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 9e0eab5a77dab1f8aa66ccb940cd6b69be041d95 Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 24 Oct 2022 15:15:23 +0200 Subject: [PATCH 10/23] 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 0000000000..d7dc038aa2 --- /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 e69de29bb2..0000000000 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 0000000000..b04108bd7e --- /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 1b7ca9c686..f1d17dce76 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 e2c1326714..95ccc7b205 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 c6a736b06a..fa8a1d8f84 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 582c2abb8c..c7ac08cc7e 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 faff5b9765..095bbd8e71 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 1aab4b6d8a..9a70fc4cda 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 0000000000..4b4bb70869 --- /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 0000000000..cd23d3f924 --- /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 0000000000..65453ccd6d --- /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 0000000000..43e95120c1 --- /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 0000000000..47ebc3966e --- /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 0000000000..2698763cf6 --- /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 0000000000..1c38f3c25f --- /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 0000000000..a43e3a79da --- /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 4bc6d8ee0e..5f15e6a9a7 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 7a8767ad32..92d3b0c2dd 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 fe9ef1e9e0..2f6929b2a9 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 e8500f926c6af4cbc1c14a528555adf56f0fcab7 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 25 Oct 2022 08:19:56 +0200 Subject: [PATCH 11/23] 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 b04108bd7e..0768541a85 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 6c844a2337..5cd00d7434 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 43e95120c1..f7a472eb28 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 5f15e6a9a7..8919403b9e 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 2f6929b2a9..fe9ef1e9e0 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 12/23] =?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 f99c385fab..8405ae78d9 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 d166f3307f..e34620591d 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 1037e51296..78f0f7662b 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 c74b875204..5902da8b39 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 5c5a6af4d3..d87198f45a 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 df7ca053b7..603623a475 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 1f418b31f5..fb49d230b4 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 f78c725444..3d16b53d85 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 13/23] 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 e34620591d..a07c851aa7 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 78f0f7662b..2e356f62e6 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 3d16b53d85..9eac2d322b 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 14/23] 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 8e11137814..2e00389e5b 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 d243f9a87d8864901219309b6554c9907e3d5bf5 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 26 Oct 2022 10:03:13 +0200 Subject: [PATCH 15/23] =?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 b27073ca5c..3cd6386a87 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 df9fe2155c..a26d9c5106 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 4ae07c81a5..a3ede2058d 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 4cb537198a..5232dd95d5 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 405bc8765a275552e2d83cd9c221ed93bd899f97 Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 26 Oct 2022 12:48:48 +0200 Subject: [PATCH 16/23] 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 cd6d39795b..bf918c74a0 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 c1aa140192..3b5f40c357 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 1ba4166dcf..3a08bffffd 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 fb2f5f4529..fdc6b058e5 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 9cf6f43008..4f9204c6a3 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 c456859a58..d3c802ad21 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 423e9d70d2..68a65e8903 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 519f8e39aa..4f12fc7bb3 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 38acdf77d6..adcb9d6d4a 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 88f1641451..05b71da99a 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 ffd8aafab5..04844ab99d 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 a141ce56ca..1761561edd 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 bc6e6397462333abec2d5149fcde37e67bcf69de Mon Sep 17 00:00:00 2001 From: alexandre Date: Wed, 26 Oct 2022 13:23:54 +0200 Subject: [PATCH 17/23] 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 8926fa4d1a..4d16c51833 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 736de00e77da5cf492a7e1e9a433233d97053c71 Mon Sep 17 00:00:00 2001 From: Pau Navarro Date: Thu, 27 Oct 2022 09:18:00 +0200 Subject: [PATCH 18/23] 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 f2a3d0c4e3..0000000000 --- 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 19/23] =?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 1d646a0db9..aed4b38f3f 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 db94a7a12e..31df7f7f59 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 4e4688b554..336592f0c8 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 d37e77943f..32f6fc7080 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 e2ee476678..5cc3ebd7f1 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 9891f7060d6d924015e18cf0034e0936daa577c6 Mon Sep 17 00:00:00 2001 From: alexm Date: Wed, 2 Nov 2022 09:40:09 +0100 Subject: [PATCH 20/23] 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 dff4836dbd..10ec1f6085 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 31541143cc..8d2c3c1531 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 b84c2cc2da..89ce0b1728 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 eafc2256ee..381c0e1ae5 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 f5e7d9ae85..75e1fdc576 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 21/23] =?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 819659066d..a2a2684a9a 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 5fd62a68b1..168b5ffb47 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 22/23] 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 f1d17dce76..e7962c93f9 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 cfad062ed9..526c40fc66 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 23/23] 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 095bbd8e71..819615c20b 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 8cdea3323a..1deff32d32 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