Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3951-invoiceOut.index_downloadPdfs
gitea/salix/pipeline/head This commit is unstable
Details
gitea/salix/pipeline/head This commit is unstable
Details
This commit is contained in:
commit
b6d3a08871
|
@ -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'),
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -394,11 +394,18 @@ export default {
|
||||||
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]',
|
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]',
|
||||||
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]',
|
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"]',
|
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"]',
|
||||||
|
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]',
|
||||||
weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]',
|
weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]',
|
||||||
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
|
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
|
||||||
},
|
},
|
||||||
itemFixedPrice: {
|
itemFixedPrice: {
|
||||||
add: 'vn-fixed-price vn-icon-button[icon="add_circle"]',
|
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)',
|
fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)',
|
||||||
fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]',
|
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"]',
|
fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]',
|
||||||
|
@ -408,7 +415,8 @@ export default {
|
||||||
fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]',
|
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"]',
|
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"]',
|
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: {
|
itemCreateView: {
|
||||||
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
|
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
|
||||||
|
@ -1107,7 +1115,8 @@ export default {
|
||||||
anyBuyLine: 'vn-entry-summary tr.dark-row'
|
anyBuyLine: 'vn-entry-summary tr.dark-row'
|
||||||
},
|
},
|
||||||
entryBasicData: {
|
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"]',
|
notes: 'vn-entry-basic-data vn-textfield[ng-model="$ctrl.entry.notes"]',
|
||||||
observations: 'vn-entry-basic-data vn-textarea[ng-model="$ctrl.entry.observation"]',
|
observations: 'vn-entry-basic-data vn-textarea[ng-model="$ctrl.entry.observation"]',
|
||||||
supplier: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]',
|
supplier: 'vn-entry-basic-data vn-autocomplete[ng-model="$ctrl.entry.supplierFk"]',
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -19,6 +19,7 @@ describe('Entry basic data path', () => {
|
||||||
|
|
||||||
it('should edit the basic data', async() => {
|
it('should edit the basic data', async() => {
|
||||||
await page.write(selectors.entryBasicData.reference, 'new movement 8');
|
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.notes, 'new notes');
|
||||||
await page.write(selectors.entryBasicData.observations, ' edited');
|
await page.write(selectors.entryBasicData.observations, ' edited');
|
||||||
await page.autocompleteSearch(selectors.entryBasicData.supplier, 'Plants nick');
|
await page.autocompleteSearch(selectors.entryBasicData.supplier, 'Plants nick');
|
||||||
|
@ -45,6 +46,13 @@ describe('Entry basic data path', () => {
|
||||||
expect(result).toEqual('new movement 8');
|
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() => {
|
it('should confirm the note was edited', async() => {
|
||||||
const result = await page.waitToGetProperty(selectors.entryBasicData.notes, 'value');
|
const result = await page.waitToGetProperty(selectors.entryBasicData.notes, 'value');
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,18 @@ export default class CrudModel extends ModelProxy {
|
||||||
return this.refresh();
|
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() {
|
removeFilter() {
|
||||||
return this.applyFilter(null, null);
|
return this.applyFilter(null, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,8 +139,12 @@ export default class Searchbar extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeParam(index) {
|
removeParam(index) {
|
||||||
|
const field = this.params[index].key;
|
||||||
|
this.filterSanitizer(field);
|
||||||
|
|
||||||
this.params.splice(index, 1);
|
this.params.splice(index, 1);
|
||||||
this.doSearch(this.fromBar(), 'bar');
|
this.toRemove = field;
|
||||||
|
this.doSearch(this.fromBar(), 'removeBar');
|
||||||
}
|
}
|
||||||
|
|
||||||
fromBar() {
|
fromBar() {
|
||||||
|
@ -163,7 +167,7 @@ export default class Searchbar extends Component {
|
||||||
|
|
||||||
let keys = Object.keys(filter);
|
let keys = Object.keys(filter);
|
||||||
keys.forEach(key => {
|
keys.forEach(key => {
|
||||||
if (key == 'search') return;
|
if (key == 'search' || key == 'tableQ' || key == 'tableOrder') return;
|
||||||
let value = filter[key];
|
let value = filter[key];
|
||||||
let chip;
|
let chip;
|
||||||
|
|
||||||
|
@ -198,6 +202,7 @@ export default class Searchbar extends Component {
|
||||||
let promise = this.onSearch({$params: filter});
|
let promise = this.onSearch({$params: filter});
|
||||||
promise = promise || this.$q.resolve();
|
promise = promise || this.$q.resolve();
|
||||||
promise.then(data => this.onFilter(filter, source, data));
|
promise.then(data => this.onFilter(filter, source, data));
|
||||||
|
this.toBar(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilter(filter, source, data) {
|
onFilter(filter, source, data) {
|
||||||
|
@ -238,8 +243,11 @@ export default class Searchbar extends Component {
|
||||||
} else {
|
} else {
|
||||||
state = this.searchState;
|
state = this.searchState;
|
||||||
|
|
||||||
if (filter)
|
if (filter) {
|
||||||
|
if (this.tableQ)
|
||||||
|
filter.tableQ = this.tableQ;
|
||||||
params = {q: JSON.stringify(filter)};
|
params = {q: JSON.stringify(filter)};
|
||||||
|
}
|
||||||
if (this.$state.is(state))
|
if (this.$state.is(state))
|
||||||
opts = {location: 'replace'};
|
opts = {location: 'replace'};
|
||||||
}
|
}
|
||||||
|
@ -247,6 +255,12 @@ export default class Searchbar extends Component {
|
||||||
|
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
|
|
||||||
|
if (source == 'removeBar') {
|
||||||
|
delete params[this.toRemove];
|
||||||
|
delete this.model.userParams[this.toRemove];
|
||||||
|
this.model.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
if (!filter && this.model)
|
if (!filter && this.model)
|
||||||
this.model.clear();
|
this.model.clear();
|
||||||
if (source != 'state')
|
if (source != 'state')
|
||||||
|
@ -269,9 +283,14 @@ export default class Searchbar extends Component {
|
||||||
this.model.clear();
|
this.model.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (Object.keys(filter).length === 0) {
|
||||||
|
this.filterSanitizer('search');
|
||||||
|
if (this.model.userParams)
|
||||||
|
delete this.model.userParams['search'];
|
||||||
|
}
|
||||||
|
|
||||||
let where = null;
|
let where = null;
|
||||||
let params = null;
|
let params = {};
|
||||||
|
|
||||||
if (this.exprBuilder) {
|
if (this.exprBuilder) {
|
||||||
where = buildFilter(filter,
|
where = buildFilter(filter,
|
||||||
|
@ -283,9 +302,89 @@ export default class Searchbar extends Component {
|
||||||
params = this.fetchParams({$params: params});
|
params = this.fetchParams({$params: params});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.tableQ = null;
|
||||||
|
|
||||||
|
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')
|
||||||
|
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);
|
||||||
|
return this.model.applyParams(params)
|
||||||
|
.then(() => this.model.data);
|
||||||
|
}
|
||||||
|
|
||||||
return this.model.applyFilter(where ? {where} : null, params)
|
return this.model.applyFilter(where ? {where} : null, params)
|
||||||
.then(() => this.model.data);
|
.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.model.userParams)
|
||||||
|
delete this.model.userParams[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', {
|
ngModule.vnComponent('vnSearchbar', {
|
||||||
|
|
|
@ -6,7 +6,7 @@ describe('Component vnSearchbar', () => {
|
||||||
let $state;
|
let $state;
|
||||||
let $params;
|
let $params;
|
||||||
let $scope;
|
let $scope;
|
||||||
let filter = {id: 1, search: 'needle'};
|
const filter = {id: 1, search: 'needle'};
|
||||||
|
|
||||||
beforeEach(ngModule('vnCore', $stateProvider => {
|
beforeEach(ngModule('vnCore', $stateProvider => {
|
||||||
$stateProvider
|
$stateProvider
|
||||||
|
@ -70,8 +70,8 @@ describe('Component vnSearchbar', () => {
|
||||||
|
|
||||||
describe('filter() setter', () => {
|
describe('filter() setter', () => {
|
||||||
it(`should update the bar params and search`, () => {
|
it(`should update the bar params and search`, () => {
|
||||||
let withoutHours = new Date(2000, 1, 1);
|
const withoutHours = new Date(2000, 1, 1);
|
||||||
let withHours = new Date(withoutHours.getTime());
|
const withHours = new Date(withoutHours.getTime());
|
||||||
withHours.setHours(12, 30, 15, 10);
|
withHours.setHours(12, 30, 15, 10);
|
||||||
|
|
||||||
controller.filter = {
|
controller.filter = {
|
||||||
|
@ -83,8 +83,8 @@ describe('Component vnSearchbar', () => {
|
||||||
myObjectProp: {myProp: 1}
|
myObjectProp: {myProp: 1}
|
||||||
};
|
};
|
||||||
|
|
||||||
let chips = {};
|
const chips = {};
|
||||||
for (let param of controller.params || [])
|
for (const param of controller.params || [])
|
||||||
chips[param.key] = param.chip;
|
chips[param.key] = param.chip;
|
||||||
|
|
||||||
expect(controller.searchString).toBe('needle');
|
expect(controller.searchString).toBe('needle');
|
||||||
|
@ -172,13 +172,22 @@ describe('Component vnSearchbar', () => {
|
||||||
describe('removeParam()', () => {
|
describe('removeParam()', () => {
|
||||||
it(`should remove the parameter from the filter`, () => {
|
it(`should remove the parameter from the filter`, () => {
|
||||||
jest.spyOn(controller, 'doSearch');
|
jest.spyOn(controller, 'doSearch');
|
||||||
|
controller.model = {
|
||||||
|
refresh: jest.fn(),
|
||||||
|
userParams: {
|
||||||
|
id: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve());
|
||||||
|
jest.spyOn(controller.model, 'applyParams');
|
||||||
|
|
||||||
controller.filter = filter;
|
controller.filter = filter;
|
||||||
controller.removeParam(0);
|
controller.removeParam(0);
|
||||||
|
|
||||||
expect(controller.doSearch).toHaveBeenCalledWith({
|
expect(controller.doSearch).toHaveBeenCalledWith({
|
||||||
search: 'needle'
|
search: 'needle'
|
||||||
}, 'bar');
|
}, 'removeBar');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -199,7 +208,7 @@ describe('Component vnSearchbar', () => {
|
||||||
it(`should go to the summary state when one result`, () => {
|
it(`should go to the summary state when one result`, () => {
|
||||||
jest.spyOn($state, 'go');
|
jest.spyOn($state, 'go');
|
||||||
|
|
||||||
let data = [{id: 1}];
|
const data = [{id: 1}];
|
||||||
|
|
||||||
controller.baseState = 'foo';
|
controller.baseState = 'foo';
|
||||||
controller.onFilter(filter, 'any', data);
|
controller.onFilter(filter, 'any', data);
|
||||||
|
@ -214,7 +223,7 @@ describe('Component vnSearchbar', () => {
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
|
|
||||||
jest.spyOn($state, 'go');
|
jest.spyOn($state, 'go');
|
||||||
let data = [{id: 1}];
|
const data = [{id: 1}];
|
||||||
|
|
||||||
controller.baseState = 'foo';
|
controller.baseState = 'foo';
|
||||||
controller.onFilter(filter, 'any', data);
|
controller.onFilter(filter, 'any', data);
|
||||||
|
@ -229,7 +238,7 @@ describe('Component vnSearchbar', () => {
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
|
|
||||||
jest.spyOn($state, 'go');
|
jest.spyOn($state, 'go');
|
||||||
let data = [{id: 1}];
|
const data = [{id: 1}];
|
||||||
|
|
||||||
controller.baseState = 'foo';
|
controller.baseState = 'foo';
|
||||||
controller.onFilter(filter, 'any', data);
|
controller.onFilter(filter, 'any', data);
|
||||||
|
@ -247,7 +256,7 @@ describe('Component vnSearchbar', () => {
|
||||||
controller.onFilter(filter, 'any');
|
controller.onFilter(filter, 'any');
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
|
|
||||||
let queryParams = {q: JSON.stringify(filter)};
|
const queryParams = {q: JSON.stringify(filter)};
|
||||||
|
|
||||||
expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined);
|
expect($state.go).toHaveBeenCalledWith('search.state', queryParams, undefined);
|
||||||
expect(controller.filter).toEqual(filter);
|
expect(controller.filter).toEqual(filter);
|
||||||
|
|
|
@ -103,3 +103,4 @@
|
||||||
</div>
|
</div>
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
</vn-popover>
|
</vn-popover>
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,17 @@ export default class SmartTable extends Component {
|
||||||
this.$inputsScope;
|
this.$inputsScope;
|
||||||
this.columns = [];
|
this.columns = [];
|
||||||
this.autoSave = false;
|
this.autoSave = false;
|
||||||
|
this.autoState = true;
|
||||||
this.transclude();
|
this.transclude();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$onChanges() {
|
||||||
|
if (this.model) {
|
||||||
|
this.defaultFilter();
|
||||||
|
this.defaultOrder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
const styleElement = document.querySelector('style[id="smart-table"]');
|
const styleElement = document.querySelector('style[id="smart-table"]');
|
||||||
if (this.$.css && styleElement)
|
if (this.$.css && styleElement)
|
||||||
|
@ -47,10 +55,8 @@ export default class SmartTable extends Component {
|
||||||
|
|
||||||
set model(value) {
|
set model(value) {
|
||||||
this._model = value;
|
this._model = value;
|
||||||
if (value) {
|
if (value)
|
||||||
this.$.model = value;
|
this.$.model = value;
|
||||||
this.defaultOrder();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultViewConfig() {
|
getDefaultViewConfig() {
|
||||||
|
@ -160,8 +166,36 @@ export default class SmartTable extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defaultFilter() {
|
||||||
|
if (this.disabledTableFilter || !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() {
|
defaultOrder() {
|
||||||
const order = this.model.order;
|
if (this.disabledTableOrder) return;
|
||||||
|
|
||||||
|
let stateOrder;
|
||||||
|
if (this.$params.q)
|
||||||
|
stateOrder = JSON.parse(this.$params.q).tableOrder;
|
||||||
|
|
||||||
|
const order = stateOrder ? stateOrder : this.model.order;
|
||||||
|
|
||||||
if (!order) return;
|
if (!order) return;
|
||||||
|
|
||||||
const orderFields = order.split(', ');
|
const orderFields = order.split(', ');
|
||||||
|
@ -195,6 +229,9 @@ export default class SmartTable extends Component {
|
||||||
this.setPriority(column.element, priority);
|
this.setPriority(column.element, priority);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.model.order = order;
|
||||||
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerColumns() {
|
registerColumns() {
|
||||||
|
@ -395,28 +432,54 @@ export default class SmartTable extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
searchByColumn(field) {
|
searchByColumn(field) {
|
||||||
const searchCriteria = this.$inputsScope.searchProps[field];
|
|
||||||
const emptySearch = searchCriteria === '' || searchCriteria == null;
|
|
||||||
|
|
||||||
const filters = this.filterSanitizer(field);
|
const filters = this.filterSanitizer(field);
|
||||||
|
|
||||||
if (filters && filters.userFilter)
|
if (filters && filters.userFilter)
|
||||||
this.model.userFilter = filters.userFilter;
|
this.model.userFilter = filters.userFilter;
|
||||||
if (!emptySearch)
|
this.addFilter(field, this.$inputsScope.searchProps[field]);
|
||||||
this.addFilter(field, this.$inputsScope.searchProps[field]);
|
}
|
||||||
else this.model.refresh();
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
addFilter(field, value) {
|
addFilter(field, value) {
|
||||||
let where = {[field]: value};
|
if (value == '') value = null;
|
||||||
|
|
||||||
if (this.exprBuilder) {
|
let stateFilter = {tableQ: {}};
|
||||||
where = buildFilter(where, (param, value) =>
|
if (this.$params.q) {
|
||||||
this.exprBuilder({param, value})
|
stateFilter = JSON.parse(this.$params.q);
|
||||||
);
|
if (!stateFilter.tableQ)
|
||||||
|
stateFilter.tableQ = {};
|
||||||
|
delete stateFilter.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();
|
||||||
|
|
||||||
|
Object.assign(stateFilter.tableQ, searchProps);
|
||||||
|
|
||||||
|
const params = {q: JSON.stringify(stateFilter)};
|
||||||
|
|
||||||
|
this.$state.go(this.$state.current.name, params, {location: 'replace'});
|
||||||
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
applySort() {
|
applySort() {
|
||||||
|
@ -426,7 +489,18 @@ export default class SmartTable extends Component {
|
||||||
if (order)
|
if (order)
|
||||||
this.model.order = order;
|
this.model.order = order;
|
||||||
|
|
||||||
this.model.refresh();
|
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.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
filterSanitizer(field) {
|
filterSanitizer(field) {
|
||||||
|
@ -535,6 +609,8 @@ ngModule.vnComponent('smartTable', {
|
||||||
autoSave: '<?',
|
autoSave: '<?',
|
||||||
exprBuilder: '&?',
|
exprBuilder: '&?',
|
||||||
defaultNewData: '&?',
|
defaultNewData: '&?',
|
||||||
options: '<?'
|
options: '<?',
|
||||||
|
disabledTableFilter: '<?',
|
||||||
|
disabledTableOrder: '<?',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,11 @@ describe('Component smartTable', () => {
|
||||||
$httpBackend = _$httpBackend_;
|
$httpBackend = _$httpBackend_;
|
||||||
$element = $compile(`<smart-table></smart-table>`)($rootScope);
|
$element = $compile(`<smart-table></smart-table>`)($rootScope);
|
||||||
controller = $element.controller('smartTable');
|
controller = $element.controller('smartTable');
|
||||||
|
controller.model = {
|
||||||
|
refresh: jest.fn().mockReturnValue(new Promise(resolve => resolve())),
|
||||||
|
addFilter: jest.fn(),
|
||||||
|
userParams: {}
|
||||||
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -83,7 +88,7 @@ describe('Component smartTable', () => {
|
||||||
describe('defaultOrder', () => {
|
describe('defaultOrder', () => {
|
||||||
it('should insert a new object to the controller sortCriteria with a sortType value of "ASC"', () => {
|
it('should insert a new object to the controller sortCriteria with a sortType value of "ASC"', () => {
|
||||||
const element = document.createElement('div');
|
const element = document.createElement('div');
|
||||||
controller.model = {order: 'id'};
|
controller.model.order = 'id';
|
||||||
controller.columns = [
|
controller.columns = [
|
||||||
{field: 'id', element: element},
|
{field: 'id', element: element},
|
||||||
{field: 'test1', 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"', () => {
|
it('should add new entries to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => {
|
||||||
const element = document.createElement('div');
|
const element = document.createElement('div');
|
||||||
controller.model = {order: 'test1, id DESC'};
|
controller.model.order = 'test1, id DESC';
|
||||||
|
|
||||||
controller.columns = [
|
controller.columns = [
|
||||||
{field: 'id', element: element},
|
{field: 'id', element: element},
|
||||||
{field: 'test1', element: element},
|
{field: 'test1', element: element},
|
||||||
|
@ -125,8 +131,6 @@ describe('Component smartTable', () => {
|
||||||
|
|
||||||
describe('addFilter()', () => {
|
describe('addFilter()', () => {
|
||||||
it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => {
|
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');
|
controller.addFilter('myField', 'myValue');
|
||||||
|
|
||||||
const expectedFilter = {
|
const expectedFilter = {
|
||||||
|
@ -140,7 +144,6 @@ describe('Component smartTable', () => {
|
||||||
|
|
||||||
it('should call the model addFilter() with a built where filter resultant of exprBuilder()', () => {
|
it('should call the model addFilter() with a built where filter resultant of exprBuilder()', () => {
|
||||||
controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'});
|
controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'});
|
||||||
controller.model = {addFilter: jest.fn()};
|
|
||||||
|
|
||||||
controller.addFilter('myField', 'myValue');
|
controller.addFilter('myField', 'myValue');
|
||||||
|
|
||||||
|
@ -155,35 +158,48 @@ describe('Component smartTable', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('applySort()', () => {
|
describe('applySort()', () => {
|
||||||
it('should call the model refresh() without making changes on the model order', () => {
|
it('should call the $state go and model refresh without making changes on the model order', () => {
|
||||||
controller.model = {refresh: jest.fn()};
|
controller.$state = {
|
||||||
|
go: jest.fn(),
|
||||||
|
current: {
|
||||||
|
name: 'section'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
jest.spyOn(controller, 'refresh');
|
||||||
|
|
||||||
controller.applySort();
|
controller.applySort();
|
||||||
|
|
||||||
expect(controller.model.order).toBeUndefined();
|
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', () => {
|
it('should call the $state go and model refresh after setting model order according to the controller sortCriteria', () => {
|
||||||
controller.model = {refresh: jest.fn()};
|
|
||||||
const orderBy = {field: 'myField', sortType: 'ASC'};
|
const orderBy = {field: 'myField', sortType: 'ASC'};
|
||||||
|
controller.$state = {
|
||||||
|
go: jest.fn(),
|
||||||
|
current: {
|
||||||
|
name: 'section'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
jest.spyOn(controller, 'refresh');
|
||||||
|
|
||||||
controller.sortCriteria = [orderBy];
|
controller.sortCriteria = [orderBy];
|
||||||
|
|
||||||
controller.applySort();
|
controller.applySort();
|
||||||
|
|
||||||
expect(controller.model.order).toEqual(`${orderBy.field} ${orderBy.sortType}`);
|
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()', () => {
|
describe('filterSanitizer()', () => {
|
||||||
it('should remove the where filter after leaving no fields in it', () => {
|
it('should remove the where filter after leaving no fields in it', () => {
|
||||||
controller.model = {
|
controller.model.userFilter = {
|
||||||
userFilter: {
|
where: {fieldToRemove: 'valueToRemove'}
|
||||||
where: {fieldToRemove: 'valueToRemove'}
|
|
||||||
},
|
|
||||||
userParams: {}
|
|
||||||
};
|
};
|
||||||
|
controller.model.userParams = {};
|
||||||
|
|
||||||
const result = controller.filterSanitizer('fieldToRemove');
|
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', () => {
|
it('should remove the where filter after leaving no fields and "empty ands/ors" in it', () => {
|
||||||
controller.model = {
|
controller.model.userFilter = {
|
||||||
userFilter: {
|
where: {
|
||||||
where: {
|
and: [
|
||||||
and: [
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
{
|
||||||
{
|
or: [
|
||||||
or: [
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
]
|
||||||
]
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
},
|
||||||
},
|
controller.model.userParams = {};
|
||||||
userParams: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = controller.filterSanitizer('aFieldToRemove');
|
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', () => {
|
it('should not remove the where filter after leaving no empty "ands/ors" in it', () => {
|
||||||
controller.model = {
|
controller.model.userFilter = {
|
||||||
userFilter: {
|
where: {
|
||||||
where: {
|
and: [
|
||||||
and: [
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
{
|
||||||
{
|
or: [
|
||||||
or: [
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
{aFieldToRemove: 'aValueToRemove'},
|
||||||
{aFieldToRemove: 'aValueToRemove'},
|
]
|
||||||
]
|
}
|
||||||
}
|
],
|
||||||
],
|
or: [{dontKillMe: 'thanks'}]
|
||||||
or: [{dontKillMe: 'thanks'}]
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
userParams: {}
|
|
||||||
};
|
};
|
||||||
|
controller.model.userParams = {};
|
||||||
|
|
||||||
const result = controller.filterSanitizer('aFieldToRemove');
|
const result = controller.filterSanitizer('aFieldToRemove');
|
||||||
|
|
||||||
|
@ -249,7 +261,7 @@ describe('Component smartTable', () => {
|
||||||
describe('saveAll()', () => {
|
describe('saveAll()', () => {
|
||||||
it('should throw an error if there are no changes to save in the model', () => {
|
it('should throw an error if there are no changes to save in the model', () => {
|
||||||
jest.spyOn(controller.vnApp, 'showError');
|
jest.spyOn(controller.vnApp, 'showError');
|
||||||
controller.model = {isChanged: false};
|
controller.model.isChanged = false;
|
||||||
controller.saveAll();
|
controller.saveAll();
|
||||||
|
|
||||||
expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save');
|
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 => {
|
it('should call the showSuccess() if there are changes to save in the model', done => {
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
controller.model = {
|
controller.model.save = jest.fn().mockReturnValue(Promise.resolve());
|
||||||
save: jest.fn().mockReturnValue(Promise.resolve()),
|
controller.model.isChanged = true;
|
||||||
isChanged: true
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.saveAll().then(() => {
|
controller.saveAll().then(() => {
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
|
||||||
|
@ -269,4 +279,43 @@ describe('Component smartTable', () => {
|
||||||
}).catch(done.fail);
|
}).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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,8 @@ const crypto = require('crypto');
|
||||||
const nthash = require('smbhash').nthash;
|
const nthash = require('smbhash').nthash;
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
|
const shouldSync = process.env.NODE_ENV !== 'test';
|
||||||
|
|
||||||
Self.getSynchronizer = async function() {
|
Self.getSynchronizer = async function() {
|
||||||
return await Self.findOne({
|
return await Self.findOne({
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -30,6 +32,7 @@ module.exports = Self => {
|
||||||
},
|
},
|
||||||
|
|
||||||
async syncUser(userName, info, password) {
|
async syncUser(userName, info, password) {
|
||||||
|
|
||||||
let {
|
let {
|
||||||
client,
|
client,
|
||||||
accountConfig
|
accountConfig
|
||||||
|
@ -130,13 +133,14 @@ module.exports = Self => {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes.length)
|
if (shouldSync && changes.length)
|
||||||
await client.modify(dn, changes);
|
await client.modify(dn, changes);
|
||||||
} else
|
} else if (shouldSync)
|
||||||
await client.add(dn, newEntry);
|
await client.add(dn, newEntry);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await client.del(dn);
|
if (shouldSync)
|
||||||
|
await client.del(dn);
|
||||||
console.log(` -> User '${userName}' removed from LDAP`);
|
console.log(` -> User '${userName}' removed from LDAP`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name !== 'NoSuchObjectError') throw e;
|
if (e.name !== 'NoSuchObjectError') throw e;
|
||||||
|
@ -196,17 +200,19 @@ module.exports = Self => {
|
||||||
for (let group of groups) {
|
for (let group of groups) {
|
||||||
try {
|
try {
|
||||||
let dn = `cn=${group},${groupDn}`;
|
let dn = `cn=${group},${groupDn}`;
|
||||||
await client.modify(dn, new ldap.Change({
|
if (shouldSync) {
|
||||||
operation,
|
await client.modify(dn, new ldap.Change({
|
||||||
modification: {memberUid: userName}
|
operation,
|
||||||
}));
|
modification: {memberUid: userName}
|
||||||
|
}));
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name !== 'NoSuchObjectError')
|
if (err.name !== 'NoSuchObjectError')
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await applyOperations(deleteGroups, 'delete');
|
await applyOperations(deleteGroups, 'delete');
|
||||||
await applyOperations(addGroups, 'add');
|
await applyOperations(addGroups, 'add');
|
||||||
},
|
},
|
||||||
|
@ -266,8 +272,10 @@ module.exports = Self => {
|
||||||
filter: 'objectClass=posixGroup'
|
filter: 'objectClass=posixGroup'
|
||||||
};
|
};
|
||||||
let reqs = [];
|
let reqs = [];
|
||||||
await client.searchForeach(this.groupDn, opts,
|
await client.searchForeach(this.groupDn, opts, object => {
|
||||||
o => reqs.push(client.del(o.dn)));
|
if (shouldSync)
|
||||||
|
reqs.push(client.del(object.dn));
|
||||||
|
});
|
||||||
await Promise.all(reqs);
|
await Promise.all(reqs);
|
||||||
|
|
||||||
// Recreate roles
|
// Recreate roles
|
||||||
|
@ -291,7 +299,8 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let dn = `cn=${role.name},${this.groupDn}`;
|
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);
|
await Promise.all(reqs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,16 +60,19 @@ module.exports = Self => {
|
||||||
return `cn=Users,${dnBase}`;
|
return `cn=Users,${dnBase}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
async syncUser(userName, info, password) {
|
async syncUser(userName, info, password) {
|
||||||
let {sshClient} = this;
|
let {sshClient} = this;
|
||||||
|
|
||||||
let sambaUser = await this.adClient.searchOne(this.usersDn(), {
|
let sambaUser = await this.adClient.searchOne(this.usersDn(), {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['userAccountControl'],
|
attributes: ['userAccountControl'],
|
||||||
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
|
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
|
||||||
});
|
});
|
||||||
let isEnabled = sambaUser
|
let isEnabled = sambaUser
|
||||||
&& !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE);
|
&& !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'test')
|
||||||
|
return;
|
||||||
|
|
||||||
if (info.hasAccount) {
|
if (info.hasAccount) {
|
||||||
if (!sambaUser) {
|
if (!sambaUser) {
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
{
|
{
|
||||||
"url": "/note",
|
"url": "/note",
|
||||||
"state": "claim.card.note",
|
"state": "claim.card.note",
|
||||||
"component": "ui-view",
|
"component": "ui-view",
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"acl": ["salesPerson"]
|
"acl": ["salesPerson"]
|
||||||
},
|
},
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
"acl": ["claimManager"]
|
"acl": ["claimManager"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/action",
|
"url": "/action?q",
|
||||||
"state": "claim.card.action",
|
"state": "claim.card.action",
|
||||||
"component": "vn-claim-action",
|
"component": "vn-claim-action",
|
||||||
"description": "Action",
|
"description": "Action",
|
||||||
|
@ -131,4 +131,4 @@
|
||||||
"acl": ["claimManager"]
|
"acl": ["claimManager"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
{"state": "client.card.credit.index", "icon": "credit_card"},
|
{"state": "client.card.credit.index", "icon": "credit_card"},
|
||||||
{"state": "client.card.greuge.index", "icon": "work"},
|
{"state": "client.card.greuge.index", "icon": "work"},
|
||||||
{"state": "client.card.balance.index", "icon": "icon-invoice"},
|
{"state": "client.card.balance.index", "icon": "icon-invoice"},
|
||||||
{"state": "client.card.recovery.index", "icon": "icon-recovery"},
|
{"state": "client.card.recovery.index", "icon": "icon-recovery"},
|
||||||
{"state": "client.card.webAccess", "icon": "cloud"},
|
{"state": "client.card.webAccess", "icon": "cloud"},
|
||||||
{"state": "client.card.log", "icon": "history"},
|
{"state": "client.card.log", "icon": "history"},
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
{"state": "client.card.unpaid", "icon": "icon-defaulter"}
|
{"state": "client.card.unpaid", "icon": "icon-defaulter"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"keybindings": [
|
"keybindings": [
|
||||||
{"key": "c", "state": "client.index"}
|
{"key": "c", "state": "client.index"}
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
{
|
{
|
||||||
"url": "/note",
|
"url": "/note",
|
||||||
"state": "client.card.note",
|
"state": "client.card.note",
|
||||||
"component": "ui-view",
|
"component": "ui-view",
|
||||||
"abstract": true
|
"abstract": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -236,7 +236,7 @@
|
||||||
"client": "$ctrl.client"
|
"client": "$ctrl.client"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/create?payed&companyFk&bankFk&payedAmount",
|
"url": "/create?payed&companyFk&bankFk&payedAmount",
|
||||||
"state": "client.card.balance.create",
|
"state": "client.card.balance.create",
|
||||||
"component": "vn-client-balance-create",
|
"component": "vn-client-balance-create",
|
||||||
|
@ -406,13 +406,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/defaulter",
|
"url": "/defaulter?q",
|
||||||
"state": "client.defaulter",
|
"state": "client.defaulter",
|
||||||
"component": "vn-client-defaulter",
|
"component": "vn-client-defaulter",
|
||||||
"description": "Defaulter"
|
"description": "Defaulter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url" : "/notification",
|
"url" : "/notification?q",
|
||||||
"state": "client.notification",
|
"state": "client.notification",
|
||||||
"component": "vn-client-notification",
|
"component": "vn-client-notification",
|
||||||
"description": "Notifications"
|
"description": "Notifications"
|
||||||
|
@ -424,7 +424,7 @@
|
||||||
"description": "Unpaid"
|
"description": "Unpaid"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "/extended-list",
|
"url": "/extended-list?q",
|
||||||
"state": "client.extendedList",
|
"state": "client.extendedList",
|
||||||
"component": "vn-client-extended-list",
|
"component": "vn-client-extended-list",
|
||||||
"description": "Extended list"
|
"description": "Extended list"
|
||||||
|
|
|
@ -154,7 +154,8 @@ module.exports = Self => {
|
||||||
e.id,
|
e.id,
|
||||||
e.supplierFk,
|
e.supplierFk,
|
||||||
e.dated,
|
e.dated,
|
||||||
e.ref,
|
e.ref reference,
|
||||||
|
e.ref invoiceNumber,
|
||||||
e.isBooked,
|
e.isBooked,
|
||||||
e.isExcludedFromAvailable,
|
e.isExcludedFromAvailable,
|
||||||
e.notes,
|
e.notes,
|
||||||
|
|
|
@ -12,10 +12,15 @@ module.exports = Self => {
|
||||||
http: {source: 'path'}
|
http: {source: 'path'}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'ref',
|
arg: 'reference',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The buyed boxes ids',
|
description: 'The buyed boxes ids',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
arg: 'invoiceNumber',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The registered invoice number',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
arg: 'observation',
|
arg: 'observation',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -63,7 +68,8 @@ module.exports = Self => {
|
||||||
|
|
||||||
await entry.updateAttributes({
|
await entry.updateAttributes({
|
||||||
observation: args.observation,
|
observation: args.observation,
|
||||||
ref: args.ref
|
reference: args.reference,
|
||||||
|
invoiceNumber: args.invoiceNumber
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
const travel = entry.travel();
|
const travel = entry.travel();
|
||||||
|
|
|
@ -15,13 +15,15 @@ describe('entry import()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should import the buy rows', async() => {
|
it('should import the buy rows', async() => {
|
||||||
const expectedRef = '1, 2';
|
const expectedReference = '1, 2';
|
||||||
|
const expectedInvoiceNumber = '1, 2';
|
||||||
const expectedObservation = '123456';
|
const expectedObservation = '123456';
|
||||||
const ctx = {
|
const ctx = {
|
||||||
req: activeCtx,
|
req: activeCtx,
|
||||||
args: {
|
args: {
|
||||||
observation: expectedObservation,
|
observation: expectedObservation,
|
||||||
ref: expectedRef,
|
reference: expectedReference,
|
||||||
|
invoiceNumber: expectedInvoiceNumber,
|
||||||
buys: [
|
buys: [
|
||||||
{
|
{
|
||||||
itemFk: 1,
|
itemFk: 1,
|
||||||
|
@ -58,7 +60,8 @@ describe('entry import()', () => {
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
expect(updatedEntry.observation).toEqual(expectedObservation);
|
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);
|
expect(entryBuys.length).toEqual(4);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
|
|
@ -18,8 +18,17 @@
|
||||||
"dated": {
|
"dated": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
"ref": {
|
"reference": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"mysql": {
|
||||||
|
"columnName": "ref"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invoiceNumber": {
|
||||||
|
"type": "string",
|
||||||
|
"mysql": {
|
||||||
|
"columnName": "ref"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"isBooked": {
|
"isBooked": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
vn-one
|
vn-one
|
||||||
label="Reference"
|
label="Reference"
|
||||||
ng-model="$ctrl.entry.ref"
|
ng-model="$ctrl.entry.reference"
|
||||||
rule
|
rule
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
@ -61,17 +61,25 @@
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-textarea
|
<vn-textfield
|
||||||
vn-one
|
vn-one
|
||||||
label="Observation"
|
label="Invoice number"
|
||||||
ng-model="$ctrl.entry.observation"
|
ng-model="$ctrl.entry.invoiceNumber"
|
||||||
rule>
|
rule
|
||||||
</vn-textarea>
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-autocomplete
|
||||||
|
url="Companies"
|
||||||
|
label="Company"
|
||||||
|
show-field="code"
|
||||||
|
value-field="id"
|
||||||
|
ng-model="$ctrl.entry.companyFk">
|
||||||
|
</vn-autocomplete>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-one
|
vn-one
|
||||||
label="Currency"
|
label="Currency"
|
||||||
ng-model="$ctrl.entry.currencyFk"
|
ng-model="$ctrl.entry.currencyFk"
|
||||||
url="Currencies"
|
url="Currencies"
|
||||||
show-field="code"
|
show-field="code"
|
||||||
|
@ -84,13 +92,14 @@
|
||||||
ng-model="$ctrl.entry.commission"
|
ng-model="$ctrl.entry.commission"
|
||||||
rule>
|
rule>
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
<vn-autocomplete
|
</vn-horizontal>
|
||||||
url="Companies"
|
<vn-horizontal>
|
||||||
label="Company"
|
<vn-textarea
|
||||||
show-field="code"
|
vn-one
|
||||||
value-field="id"
|
label="Observation"
|
||||||
ng-model="$ctrl.entry.companyFk">
|
ng-model="$ctrl.entry.observation"
|
||||||
</vn-autocomplete>
|
rule>
|
||||||
|
</vn-textarea>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-check
|
<vn-check
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<vn-th field="id" number>Id</vn-th>
|
<vn-th field="id" number>Id</vn-th>
|
||||||
<vn-th field="landed" center expand>Landed</vn-th>
|
<vn-th field="landed" center expand>Landed</vn-th>
|
||||||
<vn-th>Reference</vn-th>
|
<vn-th>Reference</vn-th>
|
||||||
|
<vn-th>Invoice number</vn-th>
|
||||||
<vn-th field="supplierFk">Supplier</vn-th>
|
<vn-th field="supplierFk">Supplier</vn-th>
|
||||||
<vn-th field="isBooked" center>Booked</vn-th>
|
<vn-th field="isBooked" center>Booked</vn-th>
|
||||||
<vn-th field="isConfirmed" center>Confirmed</vn-th>
|
<vn-th field="isConfirmed" center>Confirmed</vn-th>
|
||||||
|
@ -45,7 +46,8 @@
|
||||||
{{::entry.landed | date:'dd/MM/yyyy'}}
|
{{::entry.landed | date:'dd/MM/yyyy'}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td expand>{{::entry.ref}}</vn-td>
|
<vn-td expand>{{::entry.reference}}</vn-td>
|
||||||
|
<vn-td expand>{{::entry.invoiceNumber}}</vn-td>
|
||||||
<vn-td expand>{{::entry.supplierName}}</vn-td>
|
<vn-td expand>{{::entry.supplierName}}</vn-td>
|
||||||
<vn-td center><vn-check ng-model="entry.isBooked" disabled="true"></vn-check></vn-td>
|
<vn-td center><vn-check ng-model="entry.isBooked" disabled="true"></vn-check></vn-td>
|
||||||
<vn-td center><vn-check ng-model="entry.isConfirmed" disabled="true"></vn-check></vn-td>
|
<vn-td center><vn-check ng-model="entry.isConfirmed" disabled="true"></vn-check></vn-td>
|
||||||
|
|
|
@ -14,4 +14,5 @@ Booked: Contabilizada
|
||||||
Is inventory: Inventario
|
Is inventory: Inventario
|
||||||
Notes: Notas
|
Notes: Notas
|
||||||
Status: Estado
|
Status: Estado
|
||||||
Selection: Selección
|
Selection: Selección
|
||||||
|
Invoice number: Núm. factura
|
|
@ -13,9 +13,16 @@
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
vn-one
|
vn-one
|
||||||
label="Reference"
|
label="Reference"
|
||||||
ng-model="filter.ref">
|
ng-model="filter.reference">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Invoice number"
|
||||||
|
ng-model="filter.invoiceNumber">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
vn-one
|
vn-one
|
||||||
label="Travel"
|
label="Travel"
|
||||||
ng-model="filter.travelFk">
|
ng-model="filter.travelFk">
|
||||||
|
|
|
@ -5,4 +5,5 @@ From: Desde
|
||||||
To: Hasta
|
To: Hasta
|
||||||
Agency: Agencia
|
Agency: Agencia
|
||||||
Warehouse: Almacén
|
Warehouse: Almacén
|
||||||
Search entry by id or a suppliers by name or alias: Buscar entrada por id o proveedores por nombre y alias
|
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
|
|
@ -27,7 +27,10 @@
|
||||||
value="{{$ctrl.entryData.company.code}}">
|
value="{{$ctrl.entryData.company.code}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Reference"
|
<vn-label-value label="Reference"
|
||||||
value="{{$ctrl.entryData.ref}}">
|
value="{{$ctrl.entryData.reference}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value label="Invoice number"
|
||||||
|
value="{{$ctrl.entryData.invoiceNumber}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Notes"
|
<vn-label-value label="Notes"
|
||||||
value="{{$ctrl.entryData.notes}}">
|
value="{{$ctrl.entryData.notes}}">
|
||||||
|
|
|
@ -8,4 +8,4 @@ Minimum price: Precio mínimo
|
||||||
Buys: Compras
|
Buys: Compras
|
||||||
Travel: Envio
|
Travel: Envio
|
||||||
Go to the entry: Ir a la entrada
|
Go to the entry: Ir a la entrada
|
||||||
|
Invoice number: Núm. factura
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
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 => {
|
||||||
|
const args = Object.assign({}, ctx.args);
|
||||||
|
const params = {
|
||||||
|
recipient: args.recipient,
|
||||||
|
lang: ctx.req.getLocale()
|
||||||
|
};
|
||||||
|
|
||||||
|
delete args.ctx;
|
||||||
|
for (const param in args)
|
||||||
|
params[param] = args[param];
|
||||||
|
|
||||||
|
const email = new Email('invoiceIn', params);
|
||||||
|
|
||||||
|
return email.send();
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
const {Report} = require('vn-print');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('invoiceInPdf', {
|
||||||
|
description: 'Returns the invoiceIn pdf',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The invoiceIn 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/invoice-in-pdf',
|
||||||
|
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"`];
|
||||||
|
};
|
||||||
|
};
|
|
@ -4,4 +4,6 @@ module.exports = Self => {
|
||||||
require('../methods/invoice-in/clone')(Self);
|
require('../methods/invoice-in/clone')(Self);
|
||||||
require('../methods/invoice-in/toBook')(Self);
|
require('../methods/invoice-in/toBook')(Self);
|
||||||
require('../methods/invoice-in/getTotals')(Self);
|
require('../methods/invoice-in/getTotals')(Self);
|
||||||
|
require('../methods/invoice-in/invoiceInPdf')(Self);
|
||||||
|
require('../methods/invoice-in/invoiceInEmail')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -94,6 +94,11 @@
|
||||||
"model": "Supplier",
|
"model": "Supplier",
|
||||||
"foreignKey": "supplierFk"
|
"foreignKey": "supplierFk"
|
||||||
},
|
},
|
||||||
|
"supplierContact": {
|
||||||
|
"type": "hasMany",
|
||||||
|
"model": "SupplierContact",
|
||||||
|
"foreignKey": "supplierFk"
|
||||||
|
},
|
||||||
"currency": {
|
"currency": {
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
"model": "Currency",
|
"model": "Currency",
|
||||||
|
|
|
@ -8,6 +8,14 @@ class Controller extends ModuleCard {
|
||||||
{
|
{
|
||||||
relation: 'supplier'
|
relation: 'supplier'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
relation: 'supplierContact',
|
||||||
|
scope: {
|
||||||
|
where: {
|
||||||
|
email: {neq: null}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
relation: 'invoiceInDueDay'
|
relation: 'invoiceInDueDay'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<vn-descriptor-content
|
<vn-descriptor-content
|
||||||
module="invoiceIn"
|
module="invoiceIn"
|
||||||
description="$ctrl.invoiceIn.supplierRef"
|
description="$ctrl.invoiceIn.supplierRef"
|
||||||
summary="$ctrl.$.summary">
|
summary="$ctrl.$.summary">
|
||||||
<slot-menu>
|
<slot-menu>
|
||||||
|
@ -10,7 +10,6 @@
|
||||||
translate>
|
translate>
|
||||||
To book
|
To book
|
||||||
</vn-item>
|
</vn-item>
|
||||||
|
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-click="deleteConfirmation.show()"
|
ng-click="deleteConfirmation.show()"
|
||||||
vn-acl="administrative"
|
vn-acl="administrative"
|
||||||
|
@ -26,6 +25,16 @@
|
||||||
translate>
|
translate>
|
||||||
Clone Invoice
|
Clone Invoice
|
||||||
</vn-item>
|
</vn-item>
|
||||||
|
<vn-item
|
||||||
|
ng-click="$ctrl.showPdfInvoice()"
|
||||||
|
translate>
|
||||||
|
Show agricultural invoice as PDF
|
||||||
|
</vn-item>
|
||||||
|
<vn-item
|
||||||
|
ng-click="sendPdfConfirmation.show({email: $ctrl.entity.supplierContact[0].email})"
|
||||||
|
translate>
|
||||||
|
Send agricultural invoice as PDF
|
||||||
|
</vn-item>
|
||||||
</slot-menu>
|
</slot-menu>
|
||||||
<slot-body>
|
<slot-body>
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
|
@ -37,7 +46,7 @@
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value label="Supplier">
|
<vn-label-value label="Supplier">
|
||||||
<span ng-click="supplierDescriptor.show($event, $ctrl.invoiceIn.supplier.id)" class="link">
|
<span ng-click="supplierDescriptor.show($event, $ctrl.invoiceIn.supplier.id)" class="link">
|
||||||
{{$ctrl.invoiceIn.supplier.nickname}}
|
{{$ctrl.invoiceIn.supplier.nickname}}
|
||||||
</span>
|
</span>
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,9 +66,9 @@
|
||||||
icon="icon-invoice-in">
|
icon="icon-invoice-in">
|
||||||
</vn-quick-link>
|
</vn-quick-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</slot-body>
|
</slot-body>
|
||||||
</vn-descriptor-content>
|
</vn-descriptor-content>
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
|
@ -75,11 +84,29 @@
|
||||||
<vn-supplier-descriptor-popover
|
<vn-supplier-descriptor-popover
|
||||||
vn-id="supplierDescriptor">
|
vn-id="supplierDescriptor">
|
||||||
</vn-supplier-descriptor-popover>
|
</vn-supplier-descriptor-popover>
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
vn-id="confirm-toBookAnyway"
|
vn-id="confirm-toBookAnyway"
|
||||||
message="Are you sure you want to book this invoice?"
|
message="Are you sure you want to book this invoice?"
|
||||||
on-accept="$ctrl.onAcceptToBook()">
|
on-accept="$ctrl.onAcceptToBook()">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
<vn-popup vn-id="summary">
|
<vn-popup vn-id="summary">
|
||||||
<vn-invoice-in-summary invoice-in="$ctrl.invoiceIn"></vn-invoice-in-summary>
|
<vn-invoice-in-summary invoice-in="$ctrl.invoiceIn"></vn-invoice-in-summary>
|
||||||
</vn-popup>
|
</vn-popup>
|
||||||
|
|
||||||
|
<!-- Send PDF invoice confirmation popup -->
|
||||||
|
<vn-dialog
|
||||||
|
vn-id="sendPdfConfirmation"
|
||||||
|
on-accept="$ctrl.sendPdfInvoice($data)"
|
||||||
|
message="Send PDF invoice">
|
||||||
|
<tpl-body>
|
||||||
|
<span translate>Are you sure you want to send it?</span>
|
||||||
|
<vn-textfield vn-one
|
||||||
|
label="Email"
|
||||||
|
ng-model="sendPdfConfirmation.data.email">
|
||||||
|
</vn-textfield>
|
||||||
|
</tpl-body>
|
||||||
|
<tpl-buttons>
|
||||||
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
|
<button response="accept" translate>Confirm</button>
|
||||||
|
</tpl-buttons>
|
||||||
|
</vn-dialog>
|
||||||
|
|
|
@ -96,6 +96,20 @@ class Controller extends Descriptor {
|
||||||
.then(() => this.$state.reload())
|
.then(() => this.$state.reload())
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('InvoiceIn booked')));
|
.then(() => this.vnApp.showSuccess(this.$t('InvoiceIn booked')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showPdfInvoice() {
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnInvoiceInDescriptor', {
|
ngModule.vnComponent('vnInvoiceInDescriptor', {
|
||||||
|
|
|
@ -19,3 +19,5 @@ To book: Contabilizar
|
||||||
Total amount: Total importe
|
Total amount: Total importe
|
||||||
Total net: Total neto
|
Total net: Total neto
|
||||||
Total stems: Total tallos
|
Total stems: Total tallos
|
||||||
|
Show agricultural invoice as PDF: Ver factura agrícola como PDF
|
||||||
|
Send agricultural invoice as PDF: Enviar factura agrícola como PDF
|
||||||
|
|
|
@ -23,9 +23,9 @@
|
||||||
</vn-portal>
|
</vn-portal>
|
||||||
<div class="vn-w-xl">
|
<div class="vn-w-xl">
|
||||||
<vn-card>
|
<vn-card>
|
||||||
<smart-table
|
<smart-table
|
||||||
model="model"
|
model="model"
|
||||||
options="$ctrl.smartTableOptions"
|
options="$ctrl.smartTableOptions"
|
||||||
expr-builder="$ctrl.exprBuilder(param, value)">
|
expr-builder="$ctrl.exprBuilder(param, value)">
|
||||||
<slot-table>
|
<slot-table>
|
||||||
<table>
|
<table>
|
||||||
|
@ -34,18 +34,18 @@
|
||||||
<th field="itemFk">
|
<th field="itemFk">
|
||||||
<span translate>Item ID</span>
|
<span translate>Item ID</span>
|
||||||
</th>
|
</th>
|
||||||
<th field="itemName">
|
<th field="name">
|
||||||
<span translate>Description</span>
|
<span translate>Description</span>
|
||||||
</th>
|
</th>
|
||||||
<th field="warehouseFk">
|
<th field="warehouseFk">
|
||||||
<span translate>Warehouse</span>
|
<span translate>Warehouse</span>
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
field="rate2"
|
field="rate2"
|
||||||
vn-tooltip="Price By Unit">
|
vn-tooltip="Price By Unit">
|
||||||
<span translate>P.P.U.</span>
|
<span translate>P.P.U.</span>
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
field="rate3"
|
field="rate3"
|
||||||
vn-tooltip="Price By Package">
|
vn-tooltip="Price By Package">
|
||||||
<span translate>P.P.P.</span>
|
<span translate>P.P.P.</span>
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</div>
|
</div>
|
||||||
</slot-table>
|
</slot-table>
|
||||||
</smart-table>
|
</smart-table>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</div>
|
</div>
|
||||||
<vn-item-descriptor-popover
|
<vn-item-descriptor-popover
|
||||||
|
@ -182,4 +182,4 @@
|
||||||
on-accept="$ctrl.removePrice($data.$index)"
|
on-accept="$ctrl.removePrice($data.$index)"
|
||||||
question="Are you sure you want to continue?"
|
question="Are you sure you want to continue?"
|
||||||
message="This row will be removed">
|
message="This row will be removed">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
|
|
|
@ -12,14 +12,6 @@ export default class Controller extends Section {
|
||||||
},
|
},
|
||||||
defaultSearch: true,
|
defaultSearch: true,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
|
||||||
field: 'itemName',
|
|
||||||
autocomplete: {
|
|
||||||
url: 'Items',
|
|
||||||
showField: 'name',
|
|
||||||
valueField: 'id'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
field: 'warehouseFk',
|
field: 'warehouseFk',
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
|
@ -105,8 +97,8 @@ export default class Controller extends Section {
|
||||||
|
|
||||||
exprBuilder(param, value) {
|
exprBuilder(param, value) {
|
||||||
switch (param) {
|
switch (param) {
|
||||||
case 'itemName':
|
case 'name':
|
||||||
return {'i.id': value};
|
return {'i.name': {like: `%${value}%`}};
|
||||||
case 'itemFk':
|
case 'itemFk':
|
||||||
case 'warehouseFk':
|
case 'warehouseFk':
|
||||||
case 'rate2':
|
case 'rate2':
|
||||||
|
|
|
@ -19,22 +19,24 @@
|
||||||
</vn-none>
|
</vn-none>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-card vn-id="card">
|
<vn-card vn-id="card">
|
||||||
<smart-table
|
<smart-table
|
||||||
model="model"
|
model="model"
|
||||||
options="$ctrl.smartTableOptions"
|
options="$ctrl.smartTableOptions"
|
||||||
expr-builder="$ctrl.exprBuilder(param, value)"
|
expr-builder="$ctrl.exprBuilder(param, value)"
|
||||||
|
disabled-table-filter="true"
|
||||||
|
disabled-table-order="true"
|
||||||
class="scrollable sm">
|
class="scrollable sm">
|
||||||
<slot-actions>
|
<slot-actions>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-date-picker
|
<vn-date-picker
|
||||||
class="vn-pa-xs"
|
class="vn-pa-xs"
|
||||||
label="From"
|
label="From"
|
||||||
ng-model="$ctrl.dateFrom"
|
ng-model="$ctrl.dateFrom"
|
||||||
on-change="$ctrl.addFilterDate()">
|
on-change="$ctrl.addFilterDate()">
|
||||||
</vn-date-picker>
|
</vn-date-picker>
|
||||||
<vn-date-picker
|
<vn-date-picker
|
||||||
class="vn-pa-xs"
|
class="vn-pa-xs"
|
||||||
label="To"
|
label="To"
|
||||||
ng-model="$ctrl.dateTo"
|
ng-model="$ctrl.dateTo"
|
||||||
on-change="$ctrl.addFilterDate()">
|
on-change="$ctrl.addFilterDate()">
|
||||||
</vn-date-picker>
|
</vn-date-picker>
|
||||||
|
@ -100,9 +102,9 @@
|
||||||
</slot-pagination>
|
</slot-pagination>
|
||||||
</smart-table>
|
</smart-table>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-worker-descriptor-popover
|
<vn-worker-descriptor-popover
|
||||||
vn-id="workerDescriptor">
|
vn-id="workerDescriptor">
|
||||||
</vn-worker-descriptor-popover>
|
</vn-worker-descriptor-popover>
|
||||||
<vn-client-descriptor-popover
|
<vn-client-descriptor-popover
|
||||||
vn-id="clientDescriptor">
|
vn-id="clientDescriptor">
|
||||||
</vn-client-descriptor-popover>
|
</vn-client-descriptor-popover>
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"component": "vn-route-card"
|
"component": "vn-route-card"
|
||||||
}, {
|
}, {
|
||||||
"url": "/agency-term",
|
"url": "/agency-term?q",
|
||||||
"abstract": true,
|
"abstract": true,
|
||||||
"state": "route.agencyTerm",
|
"state": "route.agencyTerm",
|
||||||
"component": "ui-view"
|
"component": "ui-view"
|
||||||
|
@ -49,12 +49,12 @@
|
||||||
"component": "vn-agency-term-index",
|
"component": "vn-agency-term-index",
|
||||||
"description": "Autonomous",
|
"description": "Autonomous",
|
||||||
"acl": ["administrative"]
|
"acl": ["administrative"]
|
||||||
},{
|
},{
|
||||||
"url": "/createInvoiceIn?q",
|
"url": "/createInvoiceIn?q",
|
||||||
"state": "route.agencyTerm.createInvoiceIn",
|
"state": "route.agencyTerm.createInvoiceIn",
|
||||||
"component": "vn-agency-term-create-invoice-in",
|
"component": "vn-agency-term-create-invoice-in",
|
||||||
"description": "File management",
|
"description": "File management",
|
||||||
"params": {
|
"params": {
|
||||||
"route": "$ctrl.route"
|
"route": "$ctrl.route"
|
||||||
},
|
},
|
||||||
"acl": ["administrative"]
|
"acl": ["administrative"]
|
||||||
|
@ -92,4 +92,4 @@
|
||||||
"acl": ["delivery"]
|
"acl": ["delivery"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,9 @@
|
||||||
"isSerious": {
|
"isSerious": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"isTrucker": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"note": {
|
"note": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,6 +41,7 @@ class Controller extends Descriptor {
|
||||||
'payDay',
|
'payDay',
|
||||||
'isActive',
|
'isActive',
|
||||||
'isSerious',
|
'isSerious',
|
||||||
|
'isTrucker',
|
||||||
'account'
|
'account'
|
||||||
],
|
],
|
||||||
include: [
|
include: [
|
||||||
|
|
|
@ -27,6 +27,7 @@ describe('Supplier Component vnSupplierDescriptor', () => {
|
||||||
'payDay',
|
'payDay',
|
||||||
'isActive',
|
'isActive',
|
||||||
'isSerious',
|
'isSerious',
|
||||||
|
'isTrucker',
|
||||||
'account'
|
'account'
|
||||||
],
|
],
|
||||||
include: [
|
include: [
|
||||||
|
|
|
@ -118,8 +118,6 @@
|
||||||
rule
|
rule
|
||||||
vn-focus>
|
vn-focus>
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-datalist vn-one
|
<vn-datalist vn-one
|
||||||
label="Postcode"
|
label="Postcode"
|
||||||
ng-model="$ctrl.supplier.postCode"
|
ng-model="$ctrl.supplier.postCode"
|
||||||
|
@ -144,6 +142,8 @@
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</append>
|
</append>
|
||||||
</vn-datalist>
|
</vn-datalist>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
<vn-datalist vn-id="town" vn-one
|
<vn-datalist vn-id="town" vn-one
|
||||||
label="City"
|
label="City"
|
||||||
ng-model="$ctrl.supplier.city"
|
ng-model="$ctrl.supplier.city"
|
||||||
|
@ -159,8 +159,6 @@
|
||||||
({{province.country.country}})
|
({{province.country.country}})
|
||||||
</tpl-item>
|
</tpl-item>
|
||||||
</vn-datalist>
|
</vn-datalist>
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-autocomplete vn-id="province" vn-one
|
<vn-autocomplete vn-id="province" vn-one
|
||||||
label="Province"
|
label="Province"
|
||||||
ng-model="$ctrl.supplier.provinceFk"
|
ng-model="$ctrl.supplier.provinceFk"
|
||||||
|
@ -172,6 +170,8 @@
|
||||||
rule>
|
rule>
|
||||||
<tpl-item>{{name}} ({{country.country}})</tpl-item>
|
<tpl-item>{{name}} ({{country.country}})</tpl-item>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
<vn-autocomplete vn-id="country" vn-one
|
<vn-autocomplete vn-id="country" vn-one
|
||||||
ng-model="$ctrl.supplier.countryFk"
|
ng-model="$ctrl.supplier.countryFk"
|
||||||
data="countries"
|
data="countries"
|
||||||
|
@ -180,6 +180,10 @@
|
||||||
label="Country"
|
label="Country"
|
||||||
rule>
|
rule>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
|
<vn-check
|
||||||
|
label="Trucker"
|
||||||
|
ng-model="$ctrl.supplier.isTrucker">
|
||||||
|
</vn-check>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-button-bar>
|
<vn-button-bar>
|
||||||
|
|
|
@ -3,3 +3,4 @@ Sage transaction type: Tipo de transacción Sage
|
||||||
Sage withholding: Retención Sage
|
Sage withholding: Retención Sage
|
||||||
Supplier activity: Actividad proveedor
|
Supplier activity: Actividad proveedor
|
||||||
Healt register: Pasaporte sanitario
|
Healt register: Pasaporte sanitario
|
||||||
|
Trucker: Transportista
|
|
@ -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!
|
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, <strong>Estamos aqui para
|
help: Cualquer dúvida que surja, no hesites em consultar-la, <strong>Estamos aqui para
|
||||||
atender-te!</strong>
|
atender-te!</strong>
|
||||||
conclusion: Obrigado por tua atenção!
|
conclusion: Obrigado por tua atenção!
|
||||||
|
|
|
@ -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();
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"filename": "invoiceIn.pdf",
|
||||||
|
"component": "invoiceIn"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header v-bind="$props"></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<h1>{{ $t('title') }}</h1>
|
||||||
|
<p>{{$t('dear')}},</p>
|
||||||
|
<p v-html="$t('description')"></p>
|
||||||
|
<p v-html="$t('conclusion')"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer v-bind="$props"></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,11 @@
|
||||||
|
const Component = require(`vn-print/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'invoiceIn',
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build()
|
||||||
|
}
|
||||||
|
};
|
|
@ -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!
|
|
@ -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!
|
|
@ -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!
|
|
@ -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.
|
|
@ -37,4 +37,9 @@ h2 {
|
||||||
|
|
||||||
.phytosanitary-info {
|
.phytosanitary-info {
|
||||||
margin-top: 10px
|
margin-top: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.observations{
|
||||||
|
text-align: justify;
|
||||||
|
text-justify: inter-word;
|
||||||
}
|
}
|
|
@ -1,293 +1,304 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html v-bind:lang="$i18n.locale">
|
<html v-bind:lang="$i18n.locale">
|
||||||
<body>
|
|
||||||
<table class="grid">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<!-- Header block -->
|
|
||||||
<report-header v-bind="$props"
|
|
||||||
v-bind:company-code="ticket.companyCode">
|
|
||||||
</report-header>
|
|
||||||
<!-- Block -->
|
|
||||||
<div class="grid-row">
|
|
||||||
<div class="grid-block">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="size50">
|
|
||||||
<div class="size75 vn-mt-ml">
|
|
||||||
<h1 class="title uppercase">{{$t(deliverNoteType)}}</h1>
|
|
||||||
<table class="row-oriented ticket-info">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td class="font gray uppercase">{{$t('clientId')}}</td>
|
|
||||||
<th>{{client.id}}</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="font gray uppercase">{{$t(deliverNoteType)}}</td>
|
|
||||||
<th>{{ticket.id}}</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="font gray uppercase">{{$t('date')}}</td>
|
|
||||||
<th>{{ticket.shipped | date('%d-%m-%Y')}}</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="font gray uppercase">{{$t('packages')}}</td>
|
|
||||||
<th>{{ticket.packages}}</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="size50">
|
|
||||||
<div class="panel">
|
|
||||||
<div class="header">{{$t('deliveryAddress')}}</div>
|
|
||||||
<div class="body">
|
|
||||||
<h3 class="uppercase">{{address.nickname}}</h3>
|
|
||||||
<div>
|
|
||||||
{{address.street}}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{address.postalCode}}, {{address.city}} ({{address.province}})
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel">
|
|
||||||
<div class="header">{{$t('fiscalData')}}</div>
|
|
||||||
<div class="body">
|
|
||||||
<div>
|
|
||||||
{{client.socialName}}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{client.street}}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{client.fi}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sales block -->
|
|
||||||
<h2>{{$t('saleLines')}}</h2>
|
|
||||||
<table class="column-oriented">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th width="5%">{{$t('reference')}}</th>
|
|
||||||
<th class="number">{{$t('quantity')}}</th>
|
|
||||||
<th width="50%">{{$t('concept')}}</th>
|
|
||||||
<th class="number" v-if="showPrices">{{$t('price')}}</th>
|
|
||||||
<th class="centered" width="5%" v-if="showPrices">{{$t('discount')}}</th>
|
|
||||||
<th class="centered" v-if="showPrices">{{$t('vat')}}</th>
|
|
||||||
<th class="number" v-if="showPrices">{{$t('amount')}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody v-for="sale in sales" class="no-page-break">
|
|
||||||
<tr>
|
|
||||||
<td width="5%">{{sale.itemFk | zerofill('000000')}}</td>
|
|
||||||
<td class="number">{{sale.quantity}}</td>
|
|
||||||
<td width="50%">{{sale.concept}}</td>
|
|
||||||
<td class="number" v-if="showPrices">{{sale.price | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
<td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) | percentage}}</td>
|
|
||||||
<td class="centered" v-if="showPrices">{{sale.vatType}}</td>
|
|
||||||
<td class="number" v-if="showPrices">{{sale.price * sale.quantity * (1 - sale.discount / 100) | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="description font light-gray">
|
|
||||||
<td colspan="7">
|
|
||||||
<span v-if="sale.value5">
|
|
||||||
<strong>{{sale.tag5}}</strong> {{sale.value5}}
|
|
||||||
</span>
|
|
||||||
<span v-if="sale.value6">
|
|
||||||
<strong>{{sale.tag6}}</strong> {{sale.value6}}
|
|
||||||
</span>
|
|
||||||
<span v-if="sale.value7">
|
|
||||||
<strong>{{sale.tag7}}</strong> {{sale.value7}}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot v-if="showPrices">
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="font bold">
|
|
||||||
<span class="pull-right">{{$t('subtotal')}}</span>
|
|
||||||
</td>
|
|
||||||
<td class="number">{{getSubTotal() | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
<!-- End of sales block -->
|
|
||||||
|
|
||||||
<div class="columns vn-mb-ml">
|
|
||||||
<!-- Services block-->
|
|
||||||
<div class="size100 no-page-break" v-if="services.length > 0">
|
|
||||||
<h2>{{$t('services.title')}}</h2>
|
|
||||||
<table class="column-oriented">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th width="5%"></th>
|
|
||||||
<th class="number">{{$t('services.theader.quantity')}}</th>
|
|
||||||
<th width="50%">{{$t('services.theader.concept')}}</th>
|
|
||||||
<th class="number">{{$t('services.theader.price')}}</th>
|
|
||||||
<th class="centered" width="5%"></th>
|
|
||||||
<th class="centered">{{$t('services.theader.vat')}}</th>
|
|
||||||
<th class="number">{{$t('services.theader.amount')}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="service in services">
|
|
||||||
<td width="5%"></td>
|
|
||||||
<td class="number">{{service.quantity}}</td>
|
|
||||||
<td width="50%">{{service.description}}</td>
|
|
||||||
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
<td class="centered" width="5%"></td>
|
|
||||||
<td class="centered">{{service.taxDescription}}</td>
|
|
||||||
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="font bold">
|
|
||||||
<span class="pull-right">{{$t('services.tfoot.subtotal')}}</span>
|
|
||||||
</td>
|
|
||||||
<td class="number">{{serviceTotal | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
<span class="font gray">* {{ $t('services.warning') }}</span>
|
|
||||||
</div>
|
|
||||||
<!-- End of services block -->
|
|
||||||
</div>
|
|
||||||
<div class="columns">
|
|
||||||
<!-- Packages block -->
|
|
||||||
<div id="packagings" class="size100 no-page-break" v-if="packagings.length > 0">
|
|
||||||
<h2>{{$t('packagings.title')}}</h2>
|
|
||||||
<table class="column-oriented">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{$t('packagings.theader.reference')}}</th>
|
|
||||||
<th class="number">{{$t('packagings.theader.quantity')}}</th>
|
|
||||||
<th wihth="75%">{{$t('packagings.theader.concept')}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="packaging in packagings">
|
|
||||||
<td>{{packaging.itemFk | zerofill('000000')}}</td>
|
|
||||||
<td class="number">{{packaging.quantity}}</td>
|
|
||||||
<td width="85%">{{packaging.name}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- End of packages block -->
|
|
||||||
</div>
|
|
||||||
<div class="columns vn-mt-xl" v-if="showPrices">
|
|
||||||
<!-- Taxes block -->
|
|
||||||
<div id="taxes" class="size50 pull-right no-page-break" v-if="taxes">
|
|
||||||
<table class="column-oriented">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th colspan="4">{{$t('taxes.title')}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<thead class="light">
|
|
||||||
<tr>
|
|
||||||
<th width="45%">{{$t('taxes.theader.type')}}</th>
|
|
||||||
<th width="25%" class="number">{{$t('taxes.theader.taxBase')}}</th>
|
|
||||||
<th>{{$t('taxes.theader.tax')}}</th>
|
|
||||||
<th class="number">{{$t('taxes.theader.fee')}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="tax in taxes">
|
|
||||||
<td width="45%">{{tax.name}}</td>
|
|
||||||
<td width="25%" class="number">
|
|
||||||
{{tax.Base | currency('EUR', $i18n.locale)}}
|
|
||||||
</td>
|
|
||||||
<td>{{tax.vatPercent | percentage}}</td>
|
|
||||||
<td class="number">{{tax.tax | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr class="font bold">
|
|
||||||
<td width="45%">{{$t('subtotal')}}</td>
|
|
||||||
<td width="20%" class="number">
|
|
||||||
{{getTotalBase() | currency('EUR', $i18n.locale)}}
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
<td class="number">{{getTotalTax()| currency('EUR', $i18n.locale)}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="font bold">
|
|
||||||
<td colspan="2">{{$t('total')}}</td>
|
|
||||||
<td colspan="2" class="number">{{getTotal() | currency('EUR', $i18n.locale)}}</td>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- End of taxes block -->
|
|
||||||
|
|
||||||
<!-- Phytosanitary block -->
|
<body>
|
||||||
<div id="phytosanitary" class="size50 pull-left no-page-break">
|
<table class="grid">
|
||||||
<div class="panel">
|
<tbody>
|
||||||
<div class="body">
|
<tr>
|
||||||
<div class="flag">
|
<td>
|
||||||
<div class="columns">
|
<!-- Header block -->
|
||||||
<div class="size25">
|
<report-header v-bind="$props" v-bind:company-code="ticket.companyCode">
|
||||||
<img v-bind:src="getReportSrc('europe.png')"/>
|
</report-header>
|
||||||
</div>
|
<!-- Block -->
|
||||||
<div class="size75 flag-text">
|
<div class="grid-row">
|
||||||
<strong>{{$t('plantPassport')}}</strong><br/>
|
<div class="grid-block">
|
||||||
</div>
|
<div class="columns">
|
||||||
</div>
|
<div class="size50">
|
||||||
</div>
|
<div class="size75 vn-mt-ml">
|
||||||
<div class="phytosanitary-info">
|
<h1 class="title uppercase">{{$t(deliverNoteType)}}</h1>
|
||||||
<div>
|
<table class="row-oriented ticket-info">
|
||||||
<strong>A</strong>
|
<tbody>
|
||||||
<span>{{getBotanical()}}</span>
|
<tr>
|
||||||
</div>
|
<td class="font gray uppercase">{{$t('clientId')}}</td>
|
||||||
<div>
|
<th>{{client.id}}</th>
|
||||||
<strong>B</strong>
|
</tr>
|
||||||
<span>ES17462130</span>
|
<tr>
|
||||||
</div>
|
<td class="font gray uppercase">{{$t(deliverNoteType)}}</td>
|
||||||
<div>
|
<th>{{ticket.id}}</th>
|
||||||
<strong>C</strong>
|
</tr>
|
||||||
<span>{{ticket.id}}</span>
|
<tr>
|
||||||
</div>
|
<td class="font gray uppercase">{{$t('date')}}</td>
|
||||||
<div>
|
<th>{{ticket.shipped | date('%d-%m-%Y')}}</th>
|
||||||
<strong>D</strong>
|
</tr>
|
||||||
<span>ES</span>
|
<tr>
|
||||||
</div>
|
<td class="font gray uppercase">{{$t('packages')}}</td>
|
||||||
</div>
|
<th>{{ticket.packages}}</th>
|
||||||
</div>
|
</tr>
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<!-- End of phytosanitary block -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="columns">
|
<div class="size50">
|
||||||
<!-- Signature block -->
|
<div class="panel">
|
||||||
<div class="size50 pull-left no-page-break">
|
<div class="header">{{$t('deliveryAddress')}}</div>
|
||||||
<div id="signature" class="panel" v-if="signature && signature.id">
|
<div class="body">
|
||||||
<div class="header">{{$t('digitalSignature')}}</div>
|
<h3 class="uppercase">{{address.nickname}}</h3>
|
||||||
<div class="body centered">
|
<div>
|
||||||
<img v-bind:src="dmsPath"/>
|
{{address.street}}
|
||||||
<div>{{signature.created | date('%d-%m-%Y')}}</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{{address.postalCode}}, {{address.city}} ({{address.province}})
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- End of signature block -->
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="header">{{$t('fiscalData')}}</div>
|
||||||
|
<div class="body">
|
||||||
|
<div>
|
||||||
|
{{client.socialName}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{client.street}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{client.fi}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sales block -->
|
||||||
|
<h2>{{$t('saleLines')}}</h2>
|
||||||
|
<table class="column-oriented">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="5%">{{$t('reference')}}</th>
|
||||||
|
<th class="number">{{$t('quantity')}}</th>
|
||||||
|
<th width="50%">{{$t('concept')}}</th>
|
||||||
|
<th class="number" v-if="showPrices">{{$t('price')}}</th>
|
||||||
|
<th class="centered" width="5%" v-if="showPrices">{{$t('discount')}}</th>
|
||||||
|
<th class="centered" v-if="showPrices">{{$t('vat')}}</th>
|
||||||
|
<th class="number" v-if="showPrices">{{$t('amount')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody v-for="sale in sales" class="no-page-break">
|
||||||
|
<tr>
|
||||||
|
<td width="5%">{{sale.itemFk | zerofill('000000')}}</td>
|
||||||
|
<td class="number">{{sale.quantity}}</td>
|
||||||
|
<td width="50%">{{sale.concept}}</td>
|
||||||
|
<td class="number" v-if="showPrices">{{sale.price | currency('EUR',
|
||||||
|
$i18n.locale)}}</td>
|
||||||
|
<td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) |
|
||||||
|
percentage}}</td>
|
||||||
|
<td class="centered" v-if="showPrices">{{sale.vatType}}</td>
|
||||||
|
<td class="number" v-if="showPrices">{{sale.price * sale.quantity * (1 -
|
||||||
|
sale.discount / 100) | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="description font light-gray">
|
||||||
|
<td colspan="7">
|
||||||
|
<span v-if="sale.value5">
|
||||||
|
<strong>{{sale.tag5}}</strong> {{sale.value5}}
|
||||||
|
</span>
|
||||||
|
<span v-if="sale.value6">
|
||||||
|
<strong>{{sale.tag6}}</strong> {{sale.value6}}
|
||||||
|
</span>
|
||||||
|
<span v-if="sale.value7">
|
||||||
|
<strong>{{sale.tag7}}</strong> {{sale.value7}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot v-if="showPrices">
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="font bold">
|
||||||
|
<span class="pull-right">{{$t('subtotal')}}</span>
|
||||||
|
</td>
|
||||||
|
<td class="number">{{getSubTotal() | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
<!-- End of sales block -->
|
||||||
|
|
||||||
|
<div class="columns vn-mb-ml">
|
||||||
|
<!-- Services block-->
|
||||||
|
<div class="size100 no-page-break" v-if="services.length > 0">
|
||||||
|
<h2>{{$t('services.title')}}</h2>
|
||||||
|
<table class="column-oriented">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="5%"></th>
|
||||||
|
<th class="number">{{$t('services.theader.quantity')}}</th>
|
||||||
|
<th width="50%">{{$t('services.theader.concept')}}</th>
|
||||||
|
<th class="number">{{$t('services.theader.price')}}</th>
|
||||||
|
<th class="centered" width="5%"></th>
|
||||||
|
<th class="centered">{{$t('services.theader.vat')}}</th>
|
||||||
|
<th class="number">{{$t('services.theader.amount')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="service in services">
|
||||||
|
<td width="5%"></td>
|
||||||
|
<td class="number">{{service.quantity}}</td>
|
||||||
|
<td width="50%">{{service.description}}</td>
|
||||||
|
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}
|
||||||
|
</td>
|
||||||
|
<td class="centered" width="5%"></td>
|
||||||
|
<td class="centered">{{service.taxDescription}}</td>
|
||||||
|
<td class="number">{{service.price | currency('EUR', $i18n.locale)}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="font bold">
|
||||||
|
<span class="pull-right">{{$t('services.tfoot.subtotal')}}</span>
|
||||||
|
</td>
|
||||||
|
<td class="number">{{serviceTotal | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
<span class="font gray">* {{ $t('services.warning') }}</span>
|
||||||
|
</div>
|
||||||
|
<!-- End of services block -->
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<!-- Packages block -->
|
||||||
|
<div id="packagings" class="size100 no-page-break" v-if="packagings.length > 0">
|
||||||
|
<h2>{{$t('packagings.title')}}</h2>
|
||||||
|
<table class="column-oriented">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{$t('packagings.theader.reference')}}</th>
|
||||||
|
<th class="number">{{$t('packagings.theader.quantity')}}</th>
|
||||||
|
<th wihth="75%">{{$t('packagings.theader.concept')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="packaging in packagings">
|
||||||
|
<td>{{packaging.itemFk | zerofill('000000')}}</td>
|
||||||
|
<td class="number">{{packaging.quantity}}</td>
|
||||||
|
<td width="85%">{{packaging.name}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- End of packages block -->
|
||||||
|
</div>
|
||||||
|
<div class="columns vn-mt-xl" v-if="showPrices">
|
||||||
|
<!-- Taxes block -->
|
||||||
|
<div id="taxes" class="size50 pull-right no-page-break" v-if="taxes">
|
||||||
|
<table class="column-oriented">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">{{$t('taxes.title')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<thead class="light">
|
||||||
|
<tr>
|
||||||
|
<th width="45%">{{$t('taxes.theader.type')}}</th>
|
||||||
|
<th width="25%" class="number">{{$t('taxes.theader.taxBase')}}</th>
|
||||||
|
<th>{{$t('taxes.theader.tax')}}</th>
|
||||||
|
<th class="number">{{$t('taxes.theader.fee')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="tax in taxes">
|
||||||
|
<td width="45%">{{tax.name}}</td>
|
||||||
|
<td width="25%" class="number">
|
||||||
|
{{tax.Base | currency('EUR', $i18n.locale)}}
|
||||||
|
</td>
|
||||||
|
<td>{{tax.vatPercent | percentage}}</td>
|
||||||
|
<td class="number">{{tax.tax | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr class="font bold">
|
||||||
|
<td width="45%">{{$t('subtotal')}}</td>
|
||||||
|
<td width="20%" class="number">
|
||||||
|
{{getTotalBase() | currency('EUR', $i18n.locale)}}
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td class="number">{{getTotalTax()| currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="font bold">
|
||||||
|
<td colspan="2">{{$t('total')}}</td>
|
||||||
|
<td colspan="2" class="number">{{getTotal() | currency('EUR',
|
||||||
|
$i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- End of taxes block -->
|
||||||
|
|
||||||
|
<!-- Phytosanitary block -->
|
||||||
|
<div id="phytosanitary" class="size50 pull-left no-page-break">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="body">
|
||||||
|
<div class="flag">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="size25">
|
||||||
|
<img v-bind:src="getReportSrc('europe.png')" />
|
||||||
|
</div>
|
||||||
|
<div class="size75 flag-text">
|
||||||
|
<strong>{{$t('plantPassport')}}</strong><br />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="phytosanitary-info">
|
||||||
|
<div>
|
||||||
|
<strong>A</strong>
|
||||||
|
<span>{{getBotanical()}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>B</strong>
|
||||||
|
<span>ES17462130</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>C</strong>
|
||||||
|
<span>{{ticket.id}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>D</strong>
|
||||||
|
<span>ES</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End of phytosanitary block -->
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<!-- Signature block -->
|
||||||
|
<div class="size50 pull-left no-page-break">
|
||||||
|
<div id="signature" class="panel" v-if="signature && signature.id">
|
||||||
|
<div class="header">{{$t('digitalSignature')}}</div>
|
||||||
|
<div class="body centered">
|
||||||
|
<img v-bind:src="dmsPath" />
|
||||||
|
<div>{{signature.created | date('%d-%m-%Y')}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End of signature block -->
|
||||||
|
</div>
|
||||||
|
<div class="columns vn-mb-ml" v-if="hasObservations">
|
||||||
|
<!-- Observations block-->
|
||||||
|
<div class="size100 no-page-break">
|
||||||
|
<h2>{{$t('observations')}}</h2>
|
||||||
|
<p class="observations">{{ticket.description}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Footer block -->
|
</div>
|
||||||
<report-footer id="pageFooter"
|
<!-- Footer block -->
|
||||||
v-bind:company-code="ticket.companyCode"
|
<report-footer id="pageFooter" v-bind:company-code="ticket.companyCode"
|
||||||
v-bind:left-text="footerType"
|
v-bind:left-text="footerType" v-bind:center-text="client.socialName" v-bind="$props">
|
||||||
v-bind:center-text="client.socialName"
|
</report-footer>
|
||||||
v-bind="$props">
|
</td>
|
||||||
</report-footer>
|
</tr>
|
||||||
</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
</tbody>
|
</body>
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
|
@ -54,7 +54,11 @@ module.exports = {
|
||||||
footerType() {
|
footerType() {
|
||||||
const translatedType = this.$t(this.deliverNoteType);
|
const translatedType = this.$t(this.deliverNoteType);
|
||||||
return `${translatedType} ${this.id}`;
|
return `${translatedType} ${this.id}`;
|
||||||
|
},
|
||||||
|
hasObservations() {
|
||||||
|
return this.ticket.description !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchClient(id) {
|
fetchClient(id) {
|
||||||
|
|
|
@ -46,4 +46,5 @@ taxes:
|
||||||
fee: Fee
|
fee: Fee
|
||||||
tfoot:
|
tfoot:
|
||||||
subtotal: Subtotal
|
subtotal: Subtotal
|
||||||
total: Total
|
total: Total
|
||||||
|
observations: Observations
|
|
@ -47,4 +47,5 @@ taxes:
|
||||||
fee: Cuota
|
fee: Cuota
|
||||||
tfoot:
|
tfoot:
|
||||||
subtotal: Subtotal
|
subtotal: Subtotal
|
||||||
total: Total
|
total: Total
|
||||||
|
observations: Observaciones
|
|
@ -47,4 +47,5 @@ taxes:
|
||||||
fee: Quote
|
fee: Quote
|
||||||
tfoot:
|
tfoot:
|
||||||
subtotal: Total partiel
|
subtotal: Total partiel
|
||||||
total: Total
|
total: Total
|
||||||
|
observations: Observations
|
|
@ -47,4 +47,5 @@ taxes:
|
||||||
fee: Compartilhado
|
fee: Compartilhado
|
||||||
tfoot:
|
tfoot:
|
||||||
subtotal: Subtotal
|
subtotal: Subtotal
|
||||||
total: Total
|
total: Total
|
||||||
|
observations: Observações
|
|
@ -2,7 +2,11 @@ SELECT
|
||||||
t.id,
|
t.id,
|
||||||
t.shipped,
|
t.shipped,
|
||||||
c.code companyCode,
|
c.code companyCode,
|
||||||
t.packages
|
t.packages,
|
||||||
|
tto.description
|
||||||
FROM ticket t
|
FROM ticket t
|
||||||
JOIN company c ON c.id = t.companyFk
|
JOIN company c ON c.id = t.companyFk
|
||||||
|
LEFT JOIN ticketObservation tto
|
||||||
|
ON tto.ticketFk = t.id
|
||||||
|
AND tto.observationTypeFk = (SELECT id FROM observationType WHERE code = 'deliveryNote')
|
||||||
WHERE t.id = ?
|
WHERE t.id = ?
|
|
@ -264,7 +264,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="row in intrastat">
|
<tr v-for="row in intrastat">
|
||||||
<td>{{row.code}}</td>
|
<td>{{row.code}}</td>
|
||||||
<td width="50%">{{row.description}}</td>
|
<td width="50%">{{row.description || $t('services') }}</td>
|
||||||
<td class="number">{{row.stems | number($i18n.locale)}}</td>
|
<td class="number">{{row.stems | number($i18n.locale)}}</td>
|
||||||
<td class="number">{{row.netKg | number($i18n.locale)}}</td>
|
<td class="number">{{row.netKg | number($i18n.locale)}}</td>
|
||||||
<td class="number">{{row.subtotal | currency('EUR', $i18n.locale)}}</td>
|
<td class="number">{{row.subtotal | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
|
|
@ -81,7 +81,7 @@ module.exports = {
|
||||||
return this.rawSqlFromDef(`taxes`, [reference]);
|
return this.rawSqlFromDef(`taxes`, [reference]);
|
||||||
},
|
},
|
||||||
fetchIntrastat(reference) {
|
fetchIntrastat(reference) {
|
||||||
return this.rawSqlFromDef(`intrastat`, [reference, reference, reference]);
|
return this.rawSqlFromDef(`intrastat`, [reference, reference, reference, reference]);
|
||||||
},
|
},
|
||||||
fetchRectified(reference) {
|
fetchRectified(reference) {
|
||||||
return this.rawSqlFromDef(`rectified`, [reference]);
|
return this.rawSqlFromDef(`rectified`, [reference]);
|
||||||
|
|
|
@ -33,4 +33,5 @@ issued: Issued
|
||||||
plantPassport: Plant passport
|
plantPassport: Plant passport
|
||||||
observations: Observations
|
observations: Observations
|
||||||
wireTransfer: "Pay method: Transferencia"
|
wireTransfer: "Pay method: Transferencia"
|
||||||
accountNumber: "Account number: {0}"
|
accountNumber: "Account number: {0}"
|
||||||
|
services: Services
|
|
@ -33,4 +33,5 @@ issued: F. emisión
|
||||||
plantPassport: Pasaporte fitosanitario
|
plantPassport: Pasaporte fitosanitario
|
||||||
observations: Observaciones
|
observations: Observaciones
|
||||||
wireTransfer: "Forma de pago: Transferencia"
|
wireTransfer: "Forma de pago: Transferencia"
|
||||||
accountNumber: "Número de cuenta: {0}"
|
accountNumber: "Número de cuenta: {0}"
|
||||||
|
services: Servicios
|
|
@ -1,4 +1,4 @@
|
||||||
SELECT
|
(SELECT
|
||||||
ir.id code,
|
ir.id code,
|
||||||
ir.description description,
|
ir.description description,
|
||||||
CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems,
|
CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems,
|
||||||
|
@ -19,4 +19,14 @@ SELECT
|
||||||
WHERE t.refFk = ?
|
WHERE t.refFk = ?
|
||||||
AND i.intrastatFk
|
AND i.intrastatFk
|
||||||
GROUP BY i.intrastatFk
|
GROUP BY i.intrastatFk
|
||||||
ORDER BY i.intrastatFk;
|
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 = ?);
|
|
@ -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();
|
|
@ -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
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
|
@ -0,0 +1,214 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
<!-- Header block -->
|
||||||
|
<report-header v-bind="$props"
|
||||||
|
v-bind:company-code="invoice.companyCode">
|
||||||
|
</report-header>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<div class="columns vn-mb-lg">
|
||||||
|
<div class="size50">
|
||||||
|
<div class="size75 vn-mt-ml">
|
||||||
|
<h1 class="title uppercase">{{$t('title')}}</h1>
|
||||||
|
<table class="row-oriented ticket-info">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray uppercase">{{$t('supplierId')}}</td>
|
||||||
|
<th>{{invoice.supplierId}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray uppercase">{{$t('invoiceId')}}</td>
|
||||||
|
<th>{{invoice.id}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray uppercase">{{$t('date')}}</td>
|
||||||
|
<th>{{invoice.created | date('%d-%m-%Y')}}</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="size50">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="header">{{$t('invoiceData')}}</div>
|
||||||
|
<div class="body">
|
||||||
|
<h3 class="uppercase">{{invoice.name}}</h3>
|
||||||
|
<div>
|
||||||
|
{{invoice.postalAddress}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{invoice.postcodeCity}}
|
||||||
|
</div>
|
||||||
|
<div v-if="invoice.nif">
|
||||||
|
{{$t('fiscalId')}}: {{invoice.nif}}
|
||||||
|
</div>
|
||||||
|
<div v-if="invoice.phone">
|
||||||
|
{{$t('phone')}}: {{invoice.phone}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vn-mt-lg" v-for="entry in entries">
|
||||||
|
<div class="table-title clearfix">
|
||||||
|
<div class="pull-left">
|
||||||
|
<h2>{{$t('invoiceId')}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="pull-left vn-mr-md">
|
||||||
|
<div class="field rectangle">
|
||||||
|
<span>{{entry.id}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pull-left">
|
||||||
|
<h2>{{$t('date')}}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="pull-left">
|
||||||
|
<div class="field rectangle">
|
||||||
|
<span>{{entry.landed | date}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span id="nickname" class="pull-right">
|
||||||
|
<div class="pull-left">
|
||||||
|
<h2>{{$t('reference')}}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="pull-left">
|
||||||
|
<div class="field rectangle">
|
||||||
|
<span>{{entry.ref}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<table class="column-oriented">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="50%">{{$t('item')}}</th>
|
||||||
|
<th class="number">{{$t('quantity')}}</th>
|
||||||
|
<th class="number">{{$t('buyingValue')}}</th>
|
||||||
|
<th class="number">{{$t('amount')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody v-for="buy in entry.buys" class="no-page-break">
|
||||||
|
<tr>
|
||||||
|
<td width="50%">{{buy.name}}</td>
|
||||||
|
<td class="number">{{buy.quantity}}</td>
|
||||||
|
<td class="number">{{buy.buyingValue}}</td>
|
||||||
|
<td class="number">{{buyImport(buy) | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="description font light-gray">
|
||||||
|
<td colspan="4">
|
||||||
|
<span v-if="buy.value5">
|
||||||
|
<strong>{{buy.tag5}}</strong> {{buy.value5}}
|
||||||
|
</span>
|
||||||
|
<span v-if="buy.value6">
|
||||||
|
<strong>{{buy.tag6}}</strong> {{buy.value6}}
|
||||||
|
</span>
|
||||||
|
<span v-if="buy.value7">
|
||||||
|
<strong>{{buy.tag7}}</strong> {{buy.value7}}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="font bold">
|
||||||
|
<span class="pull-right">{{$t('subtotal')}}</span>
|
||||||
|
</td>
|
||||||
|
<td class="number">{{entrySubtotal(entry) | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- End of sales block -->
|
||||||
|
|
||||||
|
<div class="columns vn-mt-xl">
|
||||||
|
<!-- Taxes block -->
|
||||||
|
<div id="taxes" class="size50 pull-right no-page-break" v-if="taxes">
|
||||||
|
<table class="column-oriented">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">{{$t('taxBreakdown')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<thead class="light">
|
||||||
|
<tr>
|
||||||
|
<th width="45%">{{$t('type')}}</th>
|
||||||
|
<th width="25%" class="number">
|
||||||
|
{{$t('taxBase')}}
|
||||||
|
</th>
|
||||||
|
<th>{{$t('tax')}}</th>
|
||||||
|
<th class="number">{{$t('fee')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="tax in taxes">
|
||||||
|
<td width="45%">{{tax.name}}</td>
|
||||||
|
<td width="25%" class="number">
|
||||||
|
{{tax.taxableBase | currency('EUR', $i18n.locale)}}
|
||||||
|
</td>
|
||||||
|
<td>{{tax.rate | percentage}}</td>
|
||||||
|
<td class="number">{{tax.vat | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr class="font bold">
|
||||||
|
<td width="45%">{{$t('subtotal')}}</td>
|
||||||
|
<td width="20%" class="number">
|
||||||
|
{{sumTotal(taxes, 'taxableBase') | currency('EUR', $i18n.locale)}}
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<td class="number">{{sumTotal(taxes, 'vat') | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="font bold">
|
||||||
|
<td colspan="2">{{$t('total')}}</td>
|
||||||
|
<td colspan="2" class="number">{{taxTotal() | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="panel" v-if="invoice.footNotes">
|
||||||
|
<div class="header">{{$t('notes')}}</div>
|
||||||
|
<div class="body">
|
||||||
|
<span>{{invoice.footNotes}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End of taxes block -->
|
||||||
|
|
||||||
|
<!-- Observations block -->
|
||||||
|
<div class="columns vn-mt-xl">
|
||||||
|
<div class="size50 pull-left no-page-break" >
|
||||||
|
<div class="panel" >
|
||||||
|
<div class="header">{{$t('observations')}}</div>
|
||||||
|
<div class="body">
|
||||||
|
<div>{{$t('payMethod')}}</div>
|
||||||
|
<div>{{invoice.payMethod}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- End of observations block -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<report-footer id="pageFooter"
|
||||||
|
v-bind:company-code="invoice.companyCode"
|
||||||
|
v-bind:left-text="$t('invoiceId')"
|
||||||
|
v-bind:center-text="invoice.name"
|
||||||
|
v-bind="$props">
|
||||||
|
</report-footer>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,94 @@
|
||||||
|
const Component = require(`vn-print/core/component`);
|
||||||
|
const reportHeader = new Component('report-header');
|
||||||
|
const reportFooter = new Component('report-footer');
|
||||||
|
|
||||||
|
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]);
|
||||||
|
},
|
||||||
|
async fetchTaxes(id) {
|
||||||
|
const taxes = await this.rawSqlFromDef(`taxes`, [id]);
|
||||||
|
return this.taxVat(taxes);
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
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: {
|
||||||
|
'report-header': reportHeader.build(),
|
||||||
|
'report-footer': reportFooter.build(),
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
description: 'The invoice id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
reportName: invoice
|
||||||
|
title: Agricultural invoice
|
||||||
|
invoiceId: Agricultural invoice
|
||||||
|
supplierId: Proveedor
|
||||||
|
invoiceData: Invoice data
|
||||||
|
reference: Reference
|
||||||
|
fiscalId: FI / NIF
|
||||||
|
phone: Phone
|
||||||
|
date: Date
|
||||||
|
item: Item
|
||||||
|
quantity: Qty.
|
||||||
|
concept: Concept
|
||||||
|
buyingValue: PSP/u
|
||||||
|
discount: Disc.
|
||||||
|
vat: VAT
|
||||||
|
amount: Amount
|
||||||
|
type: Type
|
||||||
|
taxBase: Tax base
|
||||||
|
tax: Tax
|
||||||
|
fee: Fee
|
||||||
|
total: Total
|
||||||
|
subtotal: Subtotal
|
||||||
|
taxBreakdown: Tax breakdown
|
||||||
|
observations: Observations
|
||||||
|
payMethod: Pay method
|
|
@ -0,0 +1,25 @@
|
||||||
|
reportName: factura
|
||||||
|
title: Factura Agrícola
|
||||||
|
invoiceId: Factura Agrícola
|
||||||
|
supplierId: Proveedor
|
||||||
|
invoiceData: Datos de facturación
|
||||||
|
reference: Referencia
|
||||||
|
fiscalId: CIF / NIF
|
||||||
|
phone: Tlf
|
||||||
|
date: Fecha
|
||||||
|
item: Artículo
|
||||||
|
quantity: Cant.
|
||||||
|
concept: Concepto
|
||||||
|
buyingValue: PVP/u
|
||||||
|
discount: Dto.
|
||||||
|
vat: IVA
|
||||||
|
amount: Importe
|
||||||
|
type: Tipo
|
||||||
|
taxBase: Base imp.
|
||||||
|
tax: Tasa
|
||||||
|
fee: Cuota
|
||||||
|
total: Total
|
||||||
|
subtotal: Subtotal
|
||||||
|
taxBreakdown: Desglose impositivo
|
||||||
|
observations: Observaciones
|
||||||
|
payMethod: Método de pago
|
|
@ -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 = ?
|
|
@ -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 = ?
|
|
@ -0,0 +1,14 @@
|
||||||
|
SELECT
|
||||||
|
i.id,
|
||||||
|
s.id supplierId,
|
||||||
|
i.created,
|
||||||
|
s.name,
|
||||||
|
s.street AS postalAddress,
|
||||||
|
s.nif,
|
||||||
|
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 = ?
|
|
@ -0,0 +1,8 @@
|
||||||
|
SELECT
|
||||||
|
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 = ?;
|
Loading…
Reference in New Issue