2983 - Invoice tickets from ticket index #685

Merged
carlosjr merged 7 commits from 2983-ticket_invoice into dev 2021-07-01 12:57:59 +00:00
13 changed files with 304 additions and 237 deletions
Showing only changes of commit 03b1ab0166 - Show all commits

View File

@ -3,7 +3,6 @@
SCHEMAS=( SCHEMAS=(
account account
bs bs
bi
cache cache
edi edi
hedera hedera

View File

@ -18,13 +18,8 @@
"acquireTimeout": 20000 "acquireTimeout": 20000
}, },
"osticket": { "osticket": {
"connector": "vn-mysql", "connector": "memory",
"database": "vn", "timezone": "local"
"debug": false,
"host": "localhost",
"port": "3306",
"username": "root",
"password": "root"
}, },
"tempStorage": { "tempStorage": {
"name": "tempStorage", "name": "tempStorage",

View File

@ -60,7 +60,8 @@
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number
ng-model="$ctrl.deliveredAmount" ng-model="$ctrl.deliveredAmount"
label="Delivered amount"> label="Delivered amount"
step="0.01">
</vn-input-number> </vn-input-number>
<vn-input-number <vn-input-number
disabled="true" disabled="true"

View File

@ -10,73 +10,73 @@ module.exports = Self => {
accepts: [ accepts: [
{ {
arg: 'filter', arg: 'filter',
type: 'Object', type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'search', arg: 'search',
type: 'String', type: 'string',
description: 'Searchs the invoiceOut by id', description: 'Searchs the invoiceOut by id',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'clientFk', arg: 'clientFk',
type: 'Integer', type: 'integer',
description: 'The client id', description: 'The client id',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'fi', arg: 'fi',
type: 'String', type: 'string',
description: 'The client fiscal id', description: 'The client fiscal id',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'hasPdf', arg: 'hasPdf',
type: 'Boolean', type: 'boolean',
description: 'Whether the the invoiceOut has PDF or not', description: 'Whether the the invoiceOut has PDF or not',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'amount', arg: 'amount',
type: 'Number', type: 'number',
description: 'The amount filter', description: 'The amount filter',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'min', arg: 'min',
type: 'Number', type: 'number',
description: 'The minimun amount flter', description: 'The minimun amount flter',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'max', arg: 'max',
type: 'Number', type: 'number',
description: 'The maximun amount flter', description: 'The maximun amount flter',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'issued', arg: 'issued',
type: 'Date', type: 'date',
description: 'The issued date filter', description: 'The issued date filter',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'created', arg: 'created',
type: 'Date', type: 'date',
description: 'The created date filter', description: 'The created date filter',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'dued', arg: 'dued',
type: 'Date', type: 'date',
description: 'The due date filter', description: 'The due date filter',
http: {source: 'query'} http: {source: 'query'}
} }
], ],
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {

View File

@ -10,48 +10,57 @@ module.exports = Self => {
accepts: [ accepts: [
{ {
arg: 'filter', arg: 'filter',
type: 'Object', type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
}, { },
{
arg: 'tags', arg: 'tags',
type: ['Object'], type: ['object'],
description: 'List of tags to filter with', description: 'List of tags to filter with',
}, { },
{
arg: 'search', arg: 'search',
type: 'String', type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by name`, description: `If it's and integer searchs by id, otherwise it searchs by name`,
}, { },
{
arg: 'id', arg: 'id',
type: 'Integer', type: 'integer',
description: 'Item id', description: 'Item id',
}, { },
{
arg: 'categoryFk', arg: 'categoryFk',
type: 'Integer', type: 'integer',
description: 'Category id', description: 'Category id',
}, { },
{
arg: 'typeFk', arg: 'typeFk',
type: 'Integer', type: 'integer',
description: 'Type id', description: 'Type id',
}, { },
{
arg: 'isActive', arg: 'isActive',
type: 'Boolean', type: 'boolean',
description: 'Whether the the item is or not active', description: 'Whether the the item is or not active',
}, { },
{
arg: 'salesPersonFk', arg: 'salesPersonFk',
type: 'Integer', type: 'integer',
description: 'The buyer of the item', description: 'The buyer of the item',
}, { },
{
arg: 'description', arg: 'description',
type: 'String', type: 'string',
description: 'The item description', description: 'The item description',
}, { },
{
arg: 'stemMultiplier', arg: 'stemMultiplier',
type: 'Integer', type: 'integer',
description: 'The item multiplier', description: 'The item multiplier',
} }
], ],
returns: { returns: {
type: ['Object'], type: ['object'],
root: true root: true
}, },
http: { http: {
@ -60,23 +69,28 @@ module.exports = Self => {
} }
}); });
Self.filter = async(ctx, filter) => { Self.filter = async(ctx, filter, options) => {
let conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
let codeWhere; let codeWhere;
if (ctx.args.search) { if (ctx.args.search) {
let items = await Self.app.models.ItemBarcode.find({ const items = await Self.app.models.ItemBarcode.find({
where: {code: ctx.args.search}, where: {code: ctx.args.search},
fields: ['itemFk'] fields: ['itemFk']
}); }, myOptions);
let itemIds = []; const itemIds = [];
for (const item of items) for (const item of items)
itemIds.push(item.itemFk); itemIds.push(item.itemFk);
codeWhere = {'i.id': {inq: itemIds}}; codeWhere = {'i.id': {inq: itemIds}};
} }
let where = buildFilter(ctx.args, (param, value) => { const where = buildFilter(ctx.args, (param, value) => {
switch (param) { switch (param) {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
@ -90,8 +104,8 @@ module.exports = Self => {
return {'i.stemMultiplier': value}; return {'i.stemMultiplier': value};
case 'typeFk': case 'typeFk':
return {'i.typeFk': value}; return {'i.typeFk': value};
case 'category': case 'categoryFk':
return {'ic.name': value}; return {'ic.id': value};
case 'salesPersonFk': case 'salesPersonFk':
return {'it.workerFk': value}; return {'it.workerFk': value};
case 'origin': case 'origin':
@ -104,7 +118,7 @@ module.exports = Self => {
}); });
filter = mergeFilters(filter, {where}); filter = mergeFilters(filter, {where});
let stmts = []; const stmts = [];
let stmt; let stmt;
stmt = new ParameterizedSQL( stmt = new ParameterizedSQL(
@ -173,9 +187,10 @@ module.exports = Self => {
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));
stmt.merge(conn.makePagination(filter)); stmt.merge(conn.makePagination(filter));
let itemsIndex = stmts.push(stmt) - 1; const itemsIndex = stmts.push(stmt) - 1;
let sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql); const result = await conn.executeStmt(sql, myOptions);
return itemsIndex === 0 ? result : result[itemsIndex]; return itemsIndex === 0 ? result : result[itemsIndex];
}; };
}; };

View File

@ -2,29 +2,62 @@ const app = require('vn-loopback/server/server');
describe('item filter()', () => { describe('item filter()', () => {
it('should return 1 result filtering by id', async() => { it('should return 1 result filtering by id', async() => {
let filter = {}; const tx = await app.models.Item.beginTransaction({});
let result = await app.models.Item.filter({args: {filter: filter, search: 1}}); const options = {transaction: tx};
expect(result.length).toEqual(1); try {
expect(result[0].id).toEqual(1); const filter = {};
const ctx = {args: {filter: filter, search: 1}};
const result = await app.models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(1);
expect(result[0].id).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should return 1 result filtering by barcode', async() => { it('should return 1 result filtering by barcode', async() => {
let filter = {}; const tx = await app.models.Item.beginTransaction({});
let result = await app.models.Item.filter({args: {filter: filter, search: 4444444444}}); const options = {transaction: tx};
expect(result.length).toEqual(1); try {
expect(result[0].id).toEqual(2); const filter = {};
const ctx = {args: {filter: filter, search: 4444444444}};
const result = await app.models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(1);
expect(result[0].id).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
it('should return 2 results using filter and tags', async() => { it('should return 2 results using filter and tags', async() => {
let filter = { const tx = await app.models.Item.beginTransaction({});
order: 'isActive ASC, name', const options = {transaction: tx};
limit: 8
};
let tags = [{value: 'medical box', tagFk: 58}];
let result = await app.models.Item.filter({args: {filter: filter, typeFk: 5, tags: tags}});
expect(result.length).toEqual(2); try {
const filter = {
order: 'isActive ASC, name',
limit: 8
};
const tags = [{value: 'medical box', tagFk: 58}];
const ctx = {args: {filter: filter, typeFk: 5, tags: tags}};
const result = await app.models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
}); });
}); });

View File

@ -91,8 +91,10 @@ class Controller extends Section {
} }
getActiveContract() { getActiveContract() {
this.$http.get(`Workers/${this.worker.id}/activeContract`) this.$http.get(`Workers/${this.worker.id}/activeContract`).then(res => {
.then(res => this.businessId = res.data.businessFk); if (res.data)
this.businessId = res.data.businessFk;
});
} }
getContractHolidays() { getContractHolidays() {

View File

@ -90,7 +90,10 @@ class Controller extends Section {
getActiveContract() { getActiveContract() {
return this.$http.get(`Workers/${this.worker.id}/activeContract`) return this.$http.get(`Workers/${this.worker.id}/activeContract`)
.then(res => this.businessId = res.data.businessFk); .then(res => {
if (res.data)
this.businessId = res.data.businessFk;
});
} }
fetchHours() { fetchHours() {
@ -111,6 +114,8 @@ class Controller extends Section {
} }
getAbsences() { getAbsences() {
if (!this.businessId) return;
const fullYear = this.started.getFullYear(); const fullYear = this.started.getFullYear();
let params = { let params = {
businessFk: this.businessId, businessFk: this.businessId,

View File

@ -1,163 +1,166 @@
<!DOCTYPE html> <!DOCTYPE html>
<html v-bind:lang="$i18n.locale"> <html v-bind:lang="$i18n.locale">
<body> <body>
<table class="grid"> <table class="grid">
<tbody> <tbody>
<tr> <tr>
<td> <td>
<!-- Header block --> <!-- Header block -->
<report-header v-bind="$props"></report-header> <report-header v-bind="$props"></report-header>
<!-- Block --> <!-- Block -->
<div class="grid-row route-block" v-for="route in routes"> <div class="grid-row route-block" v-for="route in routes">
<div class="grid-block"> <div class="grid-block">
<h1 class="title uppercase">{{$t('route')}} {{route.id}}</h1> <h1 class="title uppercase">{{$t('route')}} {{route.id}}</h1>
<div class="panel"> <div class="panel">
<div class="header">{{$t('information')}}</div> <div class="header">{{$t('information')}}</div>
<div class="body"> <div class="body">
<div>
<table width="100%">
<tbody>
<tr>
<th class="font gray align-right">{{$t('route')}}</th>
<td>{{route.id}}</td>
<th class="font gray align-right">{{$t('driver')}}</th>
<td>{{route.userNickName}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('date')}}</th>
<td>{{route.created | date('%d-%m-%Y')}}</td>
<th class="font gray align-right">{{$t('vehicle')}}</th>
<td>{{route.vehicleTradeMark}} {{route.vehicleModel}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('time')}}</th>
<td>{{route.time | date('%H:%M')}}</td>
<td></td>
<td>{{route.plateNumber}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('volume')}}</th>
<td>{{route.m3}}</td>
<th class="font gray align-right">{{$t('agency')}}</th>
<td>{{route.agencyName}}</td>
</tr>
</tbody>
</table>
<div class="contained">
<table class="middle centered" width="70%">
<tbody>
<tr>
<td>
<p class="small">Hora inicio</p>
</td>
<td>
<p class="small">Hora fin</p>
</td>
<td class="gap"></td>
<td>
<p class="small">Km inicio</p>
</td>
<td>
<p class="small">Km fin</p>
</td>
</tr>
<tr>
<td v-for="i in 2">
<div class="field rectangle">
<span></span>
</div>
</td>
<td class="gap"></td>
<td v-for="i in 2">
<div class="field rectangle">
<span></span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Route ticket list -->
<div class="no-page-break" v-for="ticket in route.tickets">
<div>
<table class="column-oriented repeatable">
<thead>
<tr>
<th class="number">{{$t('order')}}</th>
<th class="number">{{$t('ticket')}}</th>
<th width="50%">{{$t('client')}}</th>
<th class="number">{{$t('address')}}</th>
<th class="number">{{$t('packages')}}</th>
</tr>
</thead>
<tbody>
<tr>
<td class="number">{{ticket.priority}}</td>
<td class="number">{{ticket.id}}</td>
<td width="50%">{{ticket.clientFk}} {{ticket.addressName}}</td>
<td v-if="ticket.addressFk" class="number">
{{ticket.addressFk.toString().substr(0, ticket.addressFk.toString().length - 3)}}
<span class="black-container">
{{ticket.addressFk.toString().substr(-3, 3)}}
</span>
</td>
<td class="number">{{ticket.packages}}</td>
</tr>
</tbody>
</table>
</div>
<div> <div>
<table width="100%"> <table width="100%">
<tbody> <tbody>
<tr> <tr>
<th class="font gray align-right">{{$t('street')}}</th> <th class="font gray align-right">{{$t('route')}}</th>
<td>{{ticket.street}}</td> <td>{{route.id}}</td>
<th class="font gray align-right">{{$t('postcode')}}</th> <th class="font gray align-right">{{$t('driver')}}</th>
<td>{{ticket.postalCode}}</td> <td>{{route.userNickName}}</td>
</tr> </tr>
<tr> <tr>
<th class="font gray align-right">{{$t('city')}}</th> <th class="font gray align-right">{{$t('date')}}</th>
<td>{{ticket.city}}</td> <td>{{route.created | date('%d-%m-%Y')}}</td>
<th class="font gray align-right">{{$t('vehicle')}}</th>
<td>{{route.vehicleTradeMark}} {{route.vehicleModel}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('time')}}</th>
<td>{{route.time | date('%H:%M')}}</td>
<td></td>
<td>{{route.plateNumber}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('volume')}}</th>
<td>{{route.m3}}</td>
<th class="font gray align-right">{{$t('agency')}}</th> <th class="font gray align-right">{{$t('agency')}}</th>
<td>{{ticket.ticketAgency}}</td> <td>{{route.agencyName}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('mobile')}}</th>
<td>{{ticket.mobile}}</td>
<th class="font gray align-right">{{$t('phone')}}</th>
<td>{{ticket.phone}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('warehouse')}}</th>
<td>{{ticket.warehouseName}}</td>
<th class="font gray align-right">{{$t('salesPerson')}}</th>
<td>{{ticket.salesPersonName}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('import')}}</th>
<td>{{ticket.import | currency('EUR', $i18n.locale)}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div v-if="ticket.description || ticket.shipFk" class="text-area"> <div class="contained">
<p>{{ticket.description}}</p> <table class="middle centered" width="70%">
<p v-if="ticket.shipFk">{{$t('stowaway')}}: {{ticket.shipFk}}</p> <tbody>
<tr>
<td>
<p class="small">Hora inicio</p>
</td>
<td>
<p class="small">Hora fin</p>
</td>
<td class="gap"></td>
<td>
<p class="small">Km inicio</p>
</td>
<td>
<p class="small">Km fin</p>
</td>
</tr>
<tr>
<td v-for="i in 2">
<div class="field rectangle">
<span></span>
</div>
</td>
<td class="gap"></td>
<td v-for="i in 2">
<div class="field rectangle">
<span></span>
</div>
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Route ticket list -->
<div class="no-page-break" v-for="ticket in route.tickets">
<div>
<table class="column-oriented repeatable">
<thead>
<tr>
<th class="number">{{$t('order')}}</th>
<th class="number">{{$t('ticket')}}</th>
<th width="50%">{{$t('client')}}</th>
<th class="number">{{$t('address')}}</th>
<th class="number">{{$t('packages')}}</th>
<th>{{$t('packagingType')}}</th>
</tr>
</thead>
<tbody>
<tr>
<td class="number">{{ticket.priority}}</td>
<td class="number">{{ticket.id}}</td>
<td width="50%">{{ticket.clientFk}} {{ticket.addressName}}</td>
<td v-if="ticket.addressFk" class="number">
{{ticket.addressFk.toString().substr(0,
ticket.addressFk.toString().length - 3)}}
<span class="black-container">
{{ticket.addressFk.toString().substr(-3, 3)}}
</span>
</td>
<td class="number">{{ticket.packages}}</td>
<td>{{ticket.itemPackingTypes}}</td>
</tr>
</tbody>
</table>
</div>
<div>
<table width="100%">
<tbody>
<tr>
<th class="font gray align-right">{{$t('street')}}</th>
<td>{{ticket.street}}</td>
<th class="font gray align-right">{{$t('postcode')}}</th>
<td>{{ticket.postalCode}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('city')}}</th>
<td>{{ticket.city}}</td>
<th class="font gray align-right">{{$t('agency')}}</th>
<td>{{ticket.ticketAgency}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('mobile')}}</th>
<td>{{ticket.mobile}}</td>
<th class="font gray align-right">{{$t('phone')}}</th>
<td>{{ticket.phone}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('warehouse')}}</th>
<td>{{ticket.warehouseName}}</td>
<th class="font gray align-right">{{$t('salesPerson')}}</th>
<td>{{ticket.salesPersonName}}</td>
</tr>
<tr>
<th class="font gray align-right">{{$t('import')}}</th>
<td>{{ticket.import | currency('EUR', $i18n.locale)}}</td>
</tr>
</tbody>
</table>
<div v-if="ticket.description || ticket.shipFk" class="text-area">
<p>{{ticket.description}}</p>
<p v-if="ticket.shipFk">{{$t('stowaway')}}: {{ticket.shipFk}}</p>
</div>
</div>
</div>
</div> </div>
<!-- Footer block --> </div>
<report-footer id="pageFooter" <!-- Footer block -->
v-bind:left-text="$t('routeId', [routeId])" <report-footer id="pageFooter"
v-bind="$props"> v-bind:left-text="$t('routeId', [routeId])"
</report-footer> v-bind="$props">
</td> </report-footer>
</tr> </td>
</tbody> </tr>
</table> </tbody>
</body> </table>
</body>
</html> </html>

View File

@ -30,7 +30,7 @@ module.exports = {
return this.rawSqlFromDef('routes', [routesId]); return this.rawSqlFromDef('routes', [routesId]);
}, },
fetchTickets(routesId) { fetchTickets(routesId) {
return this.rawSqlFromDef('tickets', [routesId]); return this.rawSqlFromDef('tickets', [routesId, routesId]);
} }
}, },
components: { components: {

View File

@ -10,6 +10,7 @@ order: Orden
client: Cliente client: Cliente
address: Consignatario address: Consignatario
packages: Bultos packages: Bultos
packagingType: Encajado
street: Dirección street: Dirección
postcode: Código Postal postcode: Código Postal
city: Ciudad city: Ciudad

View File

@ -18,7 +18,8 @@ SELECT
am.name ticketAgency, am.name ticketAgency,
tob.description, tob.description,
s.shipFk, s.shipFk,
u.nickName salesPersonName u.nickName salesPersonName,
ipkg.itemPackingTypes
FROM route r FROM route r
LEFT JOIN ticket t ON t.routeFk = r.id LEFT JOIN ticket t ON t.routeFk = r.id
LEFT JOIN address a ON a.id = t.addressFk LEFT JOIN address a ON a.id = t.addressFk
@ -30,5 +31,15 @@ FROM route r
LEFT JOIN warehouse wh ON wh.id = t.warehouseFk LEFT JOIN warehouse wh ON wh.id = t.warehouseFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN stowaway s ON s.id = t.id LEFT JOIN stowaway s ON s.id = t.id
WHERE r.id IN(?) LEFT JOIN (
ORDER BY t.priority, t.id SELECT t.id AS ticketFk,
GROUP_CONCAT(DISTINCT(i.itemPackingTypeFk)) AS itemPackingTypes
FROM route r
JOIN ticket t ON t.routeFk = r.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE r.id IN (?)
GROUP BY t.id
) ipkg ON ipkg.ticketFk = t.id
WHERE r.id IN (?)
ORDER BY t.priority, t.id;

View File

@ -1,9 +1,11 @@
SELECT SELECT
r.id, r.id,
r.amountPaid, r.amountPaid,
r.amountUnpaid, cr.amount AS amountUnpaid,
r.payed, r.payed,
r.companyFk r.companyFk
FROM receipt r FROM receipt r
JOIN client c ON c.id = r.clientFk JOIN client c ON c.id = r.clientFk
JOIN vn.clientRisk cr ON cr.clientFk = c.id
AND cr.companyFk = r.companyFk
WHERE r.id = ? WHERE r.id = ?