supplier consumption
This commit is contained in:
parent
ccc651a445
commit
e1ac64a8d9
|
@ -0,0 +1,165 @@
|
||||||
|
|
||||||
|
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||||
|
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||||
|
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('consumption', {
|
||||||
|
description: 'Find all instances of the model matched by filter from the data source.',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'filter',
|
||||||
|
type: 'Object',
|
||||||
|
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
|
||||||
|
}, {
|
||||||
|
arg: 'search',
|
||||||
|
type: 'String',
|
||||||
|
description: `If it's and integer searchs by id, otherwise it searchs by name`
|
||||||
|
}, {
|
||||||
|
arg: 'itemId',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'Item id'
|
||||||
|
}, {
|
||||||
|
arg: 'categoryId',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'Category id'
|
||||||
|
}, {
|
||||||
|
arg: 'typeId',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'Item type id',
|
||||||
|
}, {
|
||||||
|
arg: 'buyerId',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'Buyer id'
|
||||||
|
}, {
|
||||||
|
arg: 'from',
|
||||||
|
type: 'Date',
|
||||||
|
description: `The from date filter`
|
||||||
|
}, {
|
||||||
|
arg: 'to',
|
||||||
|
type: 'Date',
|
||||||
|
description: `The to date filter`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['Object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/consumption`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.consumption = async(ctx, filter) => {
|
||||||
|
const conn = Self.dataSource.connector;
|
||||||
|
const where = buildFilter(ctx.args, (param, value) => {
|
||||||
|
switch (param) {
|
||||||
|
case 'search':
|
||||||
|
return /^\d+$/.test(value)
|
||||||
|
? {'i.id': value}
|
||||||
|
: {'i.name': {like: `%${value}%`}};
|
||||||
|
case 'itemId':
|
||||||
|
return {'i.id': value};
|
||||||
|
case 'description':
|
||||||
|
return {'i.description': {like: `%${value}%`}};
|
||||||
|
case 'categoryId':
|
||||||
|
return {'it.categoryFk': value};
|
||||||
|
case 'typeId':
|
||||||
|
return {'it.id': value};
|
||||||
|
case 'buyerId':
|
||||||
|
return {'it.workerFk': value};
|
||||||
|
case 'from':
|
||||||
|
return {'e.shipped': {gte: value}};
|
||||||
|
case 'to':
|
||||||
|
return {'e.shipped': {lte: value}};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
filter = mergeFilters(filter, {where});
|
||||||
|
let stmts = [];
|
||||||
|
let stmt;
|
||||||
|
|
||||||
|
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.entry');
|
||||||
|
|
||||||
|
stmt = new ParameterizedSQL(
|
||||||
|
`CREATE TEMPORARY TABLE tmp.entry
|
||||||
|
(INDEX (id))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT
|
||||||
|
e.id,
|
||||||
|
e.ref,
|
||||||
|
e.supplierFk,
|
||||||
|
t.shipped
|
||||||
|
FROM vn.entry e
|
||||||
|
JOIN vn.travel t ON t.id = e.travelFk`);
|
||||||
|
|
||||||
|
stmt.merge(conn.makeGroupBy('e.id'));
|
||||||
|
stmts.push(stmt);
|
||||||
|
|
||||||
|
const entriesIndex = stmts.push('SELECT * FROM tmp.entry') - 1;
|
||||||
|
stmt = new ParameterizedSQL(
|
||||||
|
`SELECT
|
||||||
|
b.id AS buyId,
|
||||||
|
b.itemFk,
|
||||||
|
b.entryFk,
|
||||||
|
b.quantity,
|
||||||
|
CAST(b.buyingValue AS DECIMAL(10,2)) AS price,
|
||||||
|
CAST(SUM(b.buyingValue*b.quantity)AS DECIMAL(10,2)) AS total,
|
||||||
|
i.id,
|
||||||
|
i.description,
|
||||||
|
i.name AS itemName,
|
||||||
|
i.subName,
|
||||||
|
i.size AS itemSize,
|
||||||
|
i.typeFk AS itemTypeFk,
|
||||||
|
i.tag5,
|
||||||
|
i.value5,
|
||||||
|
i.tag6,
|
||||||
|
i.value6,
|
||||||
|
i.tag7,
|
||||||
|
i.value7,
|
||||||
|
i.tag8,
|
||||||
|
i.value8,
|
||||||
|
i.tag9,
|
||||||
|
i.value9,
|
||||||
|
i.tag10,
|
||||||
|
i.value10,
|
||||||
|
it.id,
|
||||||
|
it.workerFk,
|
||||||
|
it.categoryFk,
|
||||||
|
it.code AS itemTypeCode
|
||||||
|
FROM buy b
|
||||||
|
JOIN tmp.entry e ON e.id = b.entryFk
|
||||||
|
JOIN item i ON i.id = b.itemFk
|
||||||
|
JOIN itemType it ON it.id = i.typeFk`
|
||||||
|
);
|
||||||
|
stmt.merge(conn.makeWhere(filter.where));
|
||||||
|
stmt.merge(conn.makeGroupBy('b.id'));
|
||||||
|
const buysIndex = stmts.push(stmt) - 1;
|
||||||
|
stmts.push(`DROP TEMPORARY TABLE tmp.entry`);
|
||||||
|
const sql = ParameterizedSQL.join(stmts, ';');
|
||||||
|
|
||||||
|
stmt.merge(conn.makePagination(filter));
|
||||||
|
|
||||||
|
const result = await conn.executeStmt(sql);
|
||||||
|
|
||||||
|
const entries = result[entriesIndex];
|
||||||
|
const buys = result[buysIndex];
|
||||||
|
const entriesMap = new Map();
|
||||||
|
|
||||||
|
for (let entry of entries)
|
||||||
|
entriesMap.set(entry.id, entry);
|
||||||
|
|
||||||
|
for (let buy of buys) {
|
||||||
|
const entry = entriesMap.get(buy.entryFk);
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
if (!entry.buys) entry.buys = [];
|
||||||
|
|
||||||
|
entry.buys.push(buy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('supplier consumption() filter', () => {
|
||||||
|
it('should return a list of entries from the supplier 2', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: 9}}, args: {}};
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
supplierFk: 2
|
||||||
|
},
|
||||||
|
order: 'itemTypeFk, itemName, itemSize'
|
||||||
|
};
|
||||||
|
const result = await app.models.Supplier.consumption(ctx, filter);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a list of entries from the item id 1 and supplier 1', async() => {
|
||||||
|
const ctx = {req: {accessToken: {userId: 9}},
|
||||||
|
args: {
|
||||||
|
itemId: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
supplierFk: 1
|
||||||
|
},
|
||||||
|
order: 'itemTypeFk, itemName, itemSize'
|
||||||
|
};
|
||||||
|
const result = await app.models.Supplier.consumption(ctx, filter);
|
||||||
|
|
||||||
|
const expectedItemId = 1;
|
||||||
|
const firstRowBuys = result[0].buys[0];
|
||||||
|
|
||||||
|
expect(firstRowBuys.itemFk).toEqual(expectedItemId);
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,6 +5,7 @@ module.exports = Self => {
|
||||||
require('../methods/supplier/filter')(Self);
|
require('../methods/supplier/filter')(Self);
|
||||||
require('../methods/supplier/getSummary')(Self);
|
require('../methods/supplier/getSummary')(Self);
|
||||||
require('../methods/supplier/updateFiscalData')(Self);
|
require('../methods/supplier/updateFiscalData')(Self);
|
||||||
|
require('../methods/supplier/consumption')(Self);
|
||||||
|
|
||||||
Self.validatesPresenceOf('name', {
|
Self.validatesPresenceOf('name', {
|
||||||
message: 'The social name cannot be empty'
|
message: 'The social name cannot be empty'
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<div class="search-panel">
|
||||||
|
<form class="vn-pa-lg" ng-submit="$ctrl.onSearch()">
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield vn-focus
|
||||||
|
vn-one
|
||||||
|
label="General search"
|
||||||
|
ng-model="filter.search"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
vn-one
|
||||||
|
label="Item id"
|
||||||
|
ng-model="filter.itemId">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-autocomplete
|
||||||
|
vn-one
|
||||||
|
ng-model="filter.buyerId"
|
||||||
|
url="Clients/activeWorkersWithRole"
|
||||||
|
search-function="{firstName: $search}"
|
||||||
|
value-field="id"
|
||||||
|
where="{role: 'employee'}"
|
||||||
|
label="Buyer">
|
||||||
|
<tpl-item>{{nickname}}</tpl-item>
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete vn-one
|
||||||
|
ng-model="filter.typeId"
|
||||||
|
url="ItemTypes"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
label="Type"
|
||||||
|
fields="['categoryFk']"
|
||||||
|
include="'category'">
|
||||||
|
<tpl-item>
|
||||||
|
<div>{{name}}</div>
|
||||||
|
<div class="text-caption text-secondary">
|
||||||
|
{{category.name}}
|
||||||
|
</div>
|
||||||
|
</tpl-item>
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete vn-one
|
||||||
|
url="ItemCategories"
|
||||||
|
label="Category"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
ng-model="filter.categoryId">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-date-picker
|
||||||
|
vn-one
|
||||||
|
label="From"
|
||||||
|
ng-model="filter.from">
|
||||||
|
</vn-date-picker>
|
||||||
|
<vn-date-picker
|
||||||
|
vn-one
|
||||||
|
label="To"
|
||||||
|
ng-model="filter.to">
|
||||||
|
</vn-date-picker>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal class="vn-mt-lg">
|
||||||
|
<vn-submit label="Search"></vn-submit>
|
||||||
|
</vn-horizontal>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -0,0 +1,15 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||||
|
|
||||||
|
class Controller extends SearchPanel {
|
||||||
|
constructor($, $element) {
|
||||||
|
super($, $element);
|
||||||
|
|
||||||
|
this.filter = this.$.filter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnSupplierConsumptionSearchPanel', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
Item id: Id artículo
|
||||||
|
From: Desde
|
||||||
|
To: Hasta
|
||||||
|
Campaign: Campaña
|
||||||
|
allSaints: Día de todos los Santos
|
||||||
|
valentinesDay: Día de San Valentín
|
||||||
|
mothersDay: Día de la madre
|
|
@ -0,0 +1,82 @@
|
||||||
|
<vn-crud-model vn-id="model"
|
||||||
|
url="Suppliers/consumption"
|
||||||
|
link="{supplierFk: $ctrl.$params.id}"
|
||||||
|
limit="20"
|
||||||
|
user-params="::$ctrl.filterParams"
|
||||||
|
data="entries"
|
||||||
|
order="itemTypeFk, itemName, itemSize">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-portal slot="topbar">
|
||||||
|
<vn-searchbar
|
||||||
|
panel="vn-supplier-consumption-search-panel"
|
||||||
|
suggested-filter="$ctrl.filterParams"
|
||||||
|
info="Search by item id or name"
|
||||||
|
model="model"
|
||||||
|
auto-state="false">
|
||||||
|
</vn-searchbar>
|
||||||
|
</vn-portal>
|
||||||
|
<vn-data-viewer model="model">
|
||||||
|
<vn-card class="vn-pa-lg vn-w-lg">
|
||||||
|
<section class="header">
|
||||||
|
<vn-tool-bar class="vn-mb-md">
|
||||||
|
<vn-button disabled="!model.userParams.from || !model.userParams.to"
|
||||||
|
icon="picture_as_pdf"
|
||||||
|
ng-click="$ctrl.showReport()"
|
||||||
|
vn-tooltip="Open as PDF">
|
||||||
|
</vn-button>
|
||||||
|
<vn-button disabled="!model.userParams.from || !model.userParams.to"
|
||||||
|
icon="email"
|
||||||
|
ng-click="confirm.show()"
|
||||||
|
vn-tooltip="Send to email">
|
||||||
|
</vn-button>
|
||||||
|
</vn-tool-bar>
|
||||||
|
</section>
|
||||||
|
<vn-table model="model"
|
||||||
|
ng-repeat="entry in entries"
|
||||||
|
ng-if="entry.buys">
|
||||||
|
<vn-thead>
|
||||||
|
<vn-tr>
|
||||||
|
<vn-th field="entryFk">Entry </vn-th>
|
||||||
|
<vn-td >{{::entry.id}}</vn-td>
|
||||||
|
<vn-th field="data">Date</vn-th>
|
||||||
|
<vn-td>{{::entry.shipped | date: 'dd/MM/yyyy'}}</vn-td>
|
||||||
|
<vn-th field="ref">Reference</vn-th>
|
||||||
|
<vn-td >{{::entry.ref}}</vn-td>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-thead>
|
||||||
|
<vn-tbody>
|
||||||
|
<vn-tr ng-repeat="buy in entry.buys">
|
||||||
|
<vn-td expand>
|
||||||
|
<span>
|
||||||
|
{{::buy.itemName}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td expand>
|
||||||
|
<vn-fetched-tags
|
||||||
|
max-length="6"
|
||||||
|
item="::buy">
|
||||||
|
</vn-fetched-tags>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td number>{{::buy.quantity | dashIfEmpty}}</vn-td>
|
||||||
|
<vn-td number>{{::buy.price | dashIfEmpty}}</vn-td>
|
||||||
|
<vn-td number>{{::buy.total | dashIfEmpty}}</vn-td>
|
||||||
|
<vn-td></vn-td>
|
||||||
|
</vn-tr>
|
||||||
|
<vn-tr>
|
||||||
|
<vn-td>
|
||||||
|
<vn-label-value
|
||||||
|
label="Total entry"
|
||||||
|
value="{{$ctrl.getTotal(entry)}}">
|
||||||
|
</vn-label-value>
|
||||||
|
</vn-td>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-tbody>
|
||||||
|
</vn-table>
|
||||||
|
</vn-card>
|
||||||
|
</vn-data-viewer>
|
||||||
|
<vn-confirm
|
||||||
|
vn-id="confirm"
|
||||||
|
question="Please, confirm"
|
||||||
|
message="The consumption report will be sent"
|
||||||
|
on-accept="$ctrl.sendEmail()">
|
||||||
|
</vn-confirm>
|
|
@ -0,0 +1,65 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
class Controller extends Section {
|
||||||
|
constructor($element, $, vnReport, vnEmail) {
|
||||||
|
super($element, $);
|
||||||
|
this.vnReport = vnReport;
|
||||||
|
this.vnEmail = vnEmail;
|
||||||
|
|
||||||
|
this.setDefaultFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultFilter() {
|
||||||
|
const minDate = new Date();
|
||||||
|
minDate.setHours(0, 0, 0, 0);
|
||||||
|
minDate.setMonth(minDate.getMonth() - 2);
|
||||||
|
|
||||||
|
const maxDate = new Date();
|
||||||
|
maxDate.setHours(23, 59, 59, 59);
|
||||||
|
|
||||||
|
this.filterParams = {
|
||||||
|
from: minDate,
|
||||||
|
to: maxDate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get reportParams() {
|
||||||
|
const userParams = this.$.model.userParams;
|
||||||
|
return Object.assign({
|
||||||
|
authorization: this.vnToken.token,
|
||||||
|
recipientId: this.supplier.id
|
||||||
|
}, userParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
showReport() {
|
||||||
|
this.vnReport.show('supplier-campaign-metrics', this.reportParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEmail() {
|
||||||
|
this.vnEmail.send('supplier-campaign-metrics', this.reportParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotal(entry) {
|
||||||
|
if (entry.buys) {
|
||||||
|
let total = 0;
|
||||||
|
for (let buy of entry.buys)
|
||||||
|
total += buy.total;
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnSupplierConsumption', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
supplier: '<'
|
||||||
|
},
|
||||||
|
require: {
|
||||||
|
card: '^vnSupplierCard'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,72 @@
|
||||||
|
import './index.js';
|
||||||
|
import crudModel from 'core/mocks/crud-model';
|
||||||
|
|
||||||
|
describe('Supplier', () => {
|
||||||
|
describe('Component vnSupplierConsumption', () => {
|
||||||
|
let $scope;
|
||||||
|
let controller;
|
||||||
|
let $httpParamSerializer;
|
||||||
|
let $httpBackend;
|
||||||
|
|
||||||
|
beforeEach(ngModule('supplier'));
|
||||||
|
|
||||||
|
beforeEach(inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
|
||||||
|
$scope = $rootScope.$new();
|
||||||
|
$httpParamSerializer = _$httpParamSerializer_;
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
|
const $element = angular.element('<vn-supplier-consumption></vn-supplier-consumption');
|
||||||
|
controller = $componentController('vnSupplierConsumption', {$element, $scope});
|
||||||
|
controller.$.model = crudModel;
|
||||||
|
controller.supplier = {
|
||||||
|
id: 2
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('showReport()', () => {
|
||||||
|
it('should call the window.open function', () => {
|
||||||
|
jest.spyOn(window, 'open').mockReturnThis();
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
controller.$.model.userParams = {
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.showReport();
|
||||||
|
|
||||||
|
const expectedParams = {
|
||||||
|
recipientId: 2,
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
const serializedParams = $httpParamSerializer(expectedParams);
|
||||||
|
const path = `api/report/supplier-campaign-metrics?${serializedParams}`;
|
||||||
|
|
||||||
|
expect(window.open).toHaveBeenCalledWith(path);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendEmail()', () => {
|
||||||
|
it('should make a GET query sending the report', () => {
|
||||||
|
const now = new Date();
|
||||||
|
controller.$.model.userParams = {
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
const expectedParams = {
|
||||||
|
recipientId: 2,
|
||||||
|
from: now,
|
||||||
|
to: now
|
||||||
|
};
|
||||||
|
|
||||||
|
const serializedParams = $httpParamSerializer(expectedParams);
|
||||||
|
const path = `email/supplier-campaign-metrics?${serializedParams}`;
|
||||||
|
|
||||||
|
$httpBackend.expect('GET', path).respond({});
|
||||||
|
controller.sendEmail();
|
||||||
|
$httpBackend.flush();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
Total entry: Total entrada
|
|
@ -10,3 +10,5 @@ import './basic-data';
|
||||||
import './fiscal-data';
|
import './fiscal-data';
|
||||||
import './contact';
|
import './contact';
|
||||||
import './log';
|
import './log';
|
||||||
|
import './consumption';
|
||||||
|
import './consumption-search-panel';
|
||||||
|
|
|
@ -12,7 +12,8 @@
|
||||||
{"state": "supplier.card.basicData", "icon": "settings"},
|
{"state": "supplier.card.basicData", "icon": "settings"},
|
||||||
{"state": "supplier.card.fiscalData", "icon": "account_balance"},
|
{"state": "supplier.card.fiscalData", "icon": "account_balance"},
|
||||||
{"state": "supplier.card.contact", "icon": "contact_phone"},
|
{"state": "supplier.card.contact", "icon": "contact_phone"},
|
||||||
{"state": "supplier.card.log", "icon": "history"}
|
{"state": "supplier.card.log", "icon": "history"},
|
||||||
|
{"state": "supplier.card.consumption", "icon": "show_chart"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -75,6 +76,15 @@
|
||||||
"params": {
|
"params": {
|
||||||
"supplier": "$ctrl.supplier"
|
"supplier": "$ctrl.supplier"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/consumption?q",
|
||||||
|
"state": "supplier.card.consumption",
|
||||||
|
"component": "vn-supplier-consumption",
|
||||||
|
"description": "Consumption",
|
||||||
|
"params": {
|
||||||
|
"supplier": "$ctrl.supplier"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
const Stylesheet = require(`${appPath}/core/stylesheet`);
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${appPath}/common/css/spacing.css`,
|
||||||
|
`${appPath}/common/css/misc.css`,
|
||||||
|
`${appPath}/common/css/layout.css`,
|
||||||
|
`${appPath}/common/css/report.css`,
|
||||||
|
`${__dirname}/style.css`])
|
||||||
|
.mergeStyles();
|
|
@ -0,0 +1,11 @@
|
||||||
|
.column-oriented {
|
||||||
|
margin-top: 50px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-line > tr {
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-line tr:nth-last-child() {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
title: Consumo
|
||||||
|
Supplier: Proveedor
|
||||||
|
supplierData: Datos del proveedor
|
||||||
|
dated: Fecha
|
||||||
|
From: Desde
|
||||||
|
To: Hasta
|
||||||
|
supplier: Proveedor {0}
|
||||||
|
reference: Referencia
|
||||||
|
Quantity: Cantidad
|
||||||
|
entry: Entrada
|
||||||
|
itemName: Artículo
|
||||||
|
price: Precio
|
||||||
|
total: Total
|
|
@ -0,0 +1,33 @@
|
||||||
|
SELECT
|
||||||
|
b.id AS buyId,
|
||||||
|
b.itemFk,
|
||||||
|
b.entryFk,
|
||||||
|
CAST(b.buyingValue AS DECIMAL(10,2)) AS price,
|
||||||
|
b.quantity,
|
||||||
|
i.id,
|
||||||
|
i.description,
|
||||||
|
i.name AS itemName,
|
||||||
|
i.subName,
|
||||||
|
i.size AS itemSize,
|
||||||
|
i.typeFk AS itemTypeFk,
|
||||||
|
i.tag5,
|
||||||
|
i.value5,
|
||||||
|
i.tag6,
|
||||||
|
i.value6,
|
||||||
|
i.tag7,
|
||||||
|
i.value7,
|
||||||
|
i.tag8,
|
||||||
|
i.value8,
|
||||||
|
i.tag9,
|
||||||
|
i.value9,
|
||||||
|
i.tag10,
|
||||||
|
i.value10,
|
||||||
|
it.id,
|
||||||
|
it.workerFk,
|
||||||
|
it.categoryFk,
|
||||||
|
it.code AS itemTypeCode
|
||||||
|
FROM buy b
|
||||||
|
JOIN item i ON i.id = b.itemFk
|
||||||
|
JOIN itemType it ON it.id = i.typeFk
|
||||||
|
WHERE b.entryFk IN(:entriesId) AND b.quantity > 0
|
||||||
|
ORDER BY i.typeFk , i.name
|
|
@ -0,0 +1,8 @@
|
||||||
|
SELECT
|
||||||
|
e.id,
|
||||||
|
e.ref,
|
||||||
|
e.supplierFk,
|
||||||
|
t.shipped
|
||||||
|
FROM vn.entry e
|
||||||
|
JOIN vn.travel t ON t.id = e.travelFk
|
||||||
|
WHERE e.supplierFk = ? AND DATE(t.shipped) BETWEEN ? AND ?
|
|
@ -0,0 +1,12 @@
|
||||||
|
SELECT
|
||||||
|
s.street,
|
||||||
|
s.city,
|
||||||
|
s.postcode,
|
||||||
|
s.id,
|
||||||
|
s.name AS supplierName,
|
||||||
|
p.name AS province,
|
||||||
|
co.country
|
||||||
|
FROM supplier s
|
||||||
|
JOIN province p ON s.provinceFk = p.id
|
||||||
|
JOIN country co ON s.countryFk = co.id
|
||||||
|
WHERE s.id = ?
|
|
@ -0,0 +1,122 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Header block -->
|
||||||
|
<report-header v-bind="$props"></report-header>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="size50">
|
||||||
|
<h1 class="title uppercase">{{$t('title')}}</h1>
|
||||||
|
<div class="size75">
|
||||||
|
<table class="row-oriented">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray">{{$t('Supplier')}}</td>
|
||||||
|
<th>{{supplier.id}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray">{{$t('From')}}</td>
|
||||||
|
<th>{{from | date('%d-%m-%Y')}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray">{{$t('To')}}</td>
|
||||||
|
<th>{{to | date('%d-%m-%Y')}}</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="size50">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="header">{{$t('supplierData')}}</div>
|
||||||
|
<div class="body">
|
||||||
|
<h3 class="uppercase">{{supplier.supplierName}}</h3>
|
||||||
|
<div>
|
||||||
|
{{supplier.street}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{supplier.postcode}}, {{supplier.city}} ({{supplier.province}})
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{supplier.country}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="no-page-break" v-for="entry in entries" v-if="entry.buys">
|
||||||
|
<table class="column-oriented repeatable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="align-right">{{$t('entry')}}</th>
|
||||||
|
<td>{{entry.id}}</td>
|
||||||
|
<th class="align-right">{{$t('dated')}}</th>
|
||||||
|
<td>{{entry.shipped | date('%d-%m-%Y')}}</td>
|
||||||
|
<th class="align-right">{{$t('reference')}}</th>
|
||||||
|
<td>{{entry.ref}}</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
<div class="no-page-break">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="column-oriented">
|
||||||
|
<th width="50%">{{$t('itemName')}}</th>
|
||||||
|
<th>{{$t('Quantity')}}</th>
|
||||||
|
<th>{{$t('price')}}</th>
|
||||||
|
<th>{{$t('total')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody v-for="buy in entry.buys">
|
||||||
|
<tr>
|
||||||
|
<td width="50%">{{buy.itemName}}</td>
|
||||||
|
<td>{{buy.quantity}}</td>
|
||||||
|
<td>{{buy.price | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
<td>{{buy.quantity * buy.price | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td colspan="2">
|
||||||
|
<div v-if="buy.value5">
|
||||||
|
<strong class="font gray">{{buy.tag5}}</strong>
|
||||||
|
<span>{{buy.value5}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td colspan="2" class="centered">
|
||||||
|
<div v-if="buy.value6">
|
||||||
|
<strong class="font gray">{{buy.tag6}}</strong>
|
||||||
|
<span>{{buy.value6}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td colspan="2" class="align-right">
|
||||||
|
<div v-if="buy.value7">
|
||||||
|
<strong class="font gray">{{buy.tag7}}</strong>
|
||||||
|
<span>{{buy.value7}}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<report-footer id="pageFooter"
|
||||||
|
v-bind:left-text="$t('supplier', [supplier.id])"
|
||||||
|
v-bind:center-text="supplier.supplierName"
|
||||||
|
v-bind="$props">
|
||||||
|
</report-footer>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,61 @@
|
||||||
|
const Component = require(`${appPath}/core/component`);
|
||||||
|
const reportHeader = new Component('report-header');
|
||||||
|
const reportFooter = new Component('report-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'supplier-campaign-metrics',
|
||||||
|
async serverPrefetch() {
|
||||||
|
this.supplier = await this.fetchSupplier(this.recipientId);
|
||||||
|
let entries = await this.fetchEntries(this.recipientId, this.from, this.to);
|
||||||
|
|
||||||
|
const entriesId = [];
|
||||||
|
|
||||||
|
for (let entry of entries)
|
||||||
|
entriesId.push(entry.id);
|
||||||
|
|
||||||
|
const buys = await this.fetchBuys(entriesId);
|
||||||
|
|
||||||
|
const entriesMap = new Map();
|
||||||
|
for (let entry of entries)
|
||||||
|
entriesMap.set(entry.id, entry);
|
||||||
|
|
||||||
|
for (let buy of buys) {
|
||||||
|
const entry = entriesMap.get(buy.entryFk);
|
||||||
|
if (entry) {
|
||||||
|
if (!entry.buys) entry.buys = [];
|
||||||
|
|
||||||
|
entry.buys.push(buy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.entries = entries;
|
||||||
|
if (!this.supplier)
|
||||||
|
throw new Error('Something went wrong');
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchSupplier(supplierId) {
|
||||||
|
return this.findOneFromDef('supplier', [supplierId]);
|
||||||
|
},
|
||||||
|
fetchEntries(supplierId, from, to) {
|
||||||
|
return this.rawSqlFromDef('entries', [supplierId, from, to]);
|
||||||
|
},
|
||||||
|
fetchBuys(entriesId) {
|
||||||
|
return this.rawSqlFromDef('buys', {entriesId});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'report-header': reportHeader.build(),
|
||||||
|
'report-footer': reportFooter.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
recipientId: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
to: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue