Merge branch 'dev' into 2517-clientBalanceCompensaciones
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Javi Gallego 2021-02-01 09:47:00 +01:00
commit 7e95106ff5
34 changed files with 1607 additions and 1356 deletions

View File

@ -0,0 +1,2 @@
ALTER TABLE `vn`.`itemImageQueue`
ADD attempts INT default 0 NULL AFTER error;

View File

@ -3,4 +3,4 @@ host = localhost
port = 3306
user = root
password = root
default-character-set=utf8
default-character-set=utf8

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -85,6 +85,25 @@ vn-table {
max-width: 400px;
min-width: 0;
}
&[vn-fetched-tags] {
width: 235px;
min-width: 155px;
& > vn-one {
overflow: hidden;
text-overflow: ellipsis;
font-size: 0.75rem;
}
& > vn-one:nth-child(2) h3 {
color: $color-font-secondary;
text-transform: uppercase;
line-height: initial;
font-size: 0.75rem
}
}
&[vn-fetched-tags][wide] {
width: 430px;
}
vn-icon.bright, i.bright {
color: #f7931e;
}

View File

@ -31,6 +31,9 @@
{"state": "account.alias.card.users", "icon": "groups"}
]
},
"keybindings": [
{"key": "u", "state": "account.index"}
],
"routes": [
{
"url": "/account",

View File

@ -64,12 +64,15 @@
</span>
</vn-td>
<vn-td expand>{{::sale.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags wide>
<vn-one title="{{::sale.concept}}">{{::sale.concept}}</vn-one>
<vn-one ng-if="::sale.subName">
<h3 title="{{::sale.subName}}">{{::sale.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale"
name="::sale.concept"
sub-name="::sale.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity | dashIfEmpty}}</vn-td>

View File

@ -97,12 +97,15 @@
{{::buy.description | dashIfEmpty}}
</vn-td>
<vn-td number>{{::buy.size}}</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags>
<vn-one title="{{::buy.name}}">{{::buy.name}}</vn-one>
<vn-one ng-if="::buy.subName">
<h3 title="{{::buy.subName}}">{{::buy.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::buy"
name="::buy.name"
sub-name="::buy.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td shrink title="{{::buy.type}}">

View File

@ -16,6 +16,9 @@
{"state": "entry.card.log", "icon": "history"}
]
},
"keybindings": [
{"key": "e", "state": "entry.index"}
],
"routes": [
{
"url": "/entry",

View File

@ -142,12 +142,12 @@
{{::line.item.minPrice | currency: 'EUR':2}}
</span>
</td>
<td expand colspan="6">
<td vn-fetched-tags colspan="6">
<vn-one title="{{::line.item.name}}">{{::line.item.name}}</vn-one>
<vn-fetched-tags
expand
max-length="6"
item="::line.item"
name="::line.item.name"
sub-name="::line.item.subName">
tabindex="-1">
</vn-fetched-tags>
</td>
</tr>

View File

@ -18,39 +18,50 @@ module.exports = Self => {
Self.downloadImages = async() => {
const models = Self.app.models;
const container = await models.TempContainer.container('salix-image');
const tempPath = path.join(container.client.root, container.name);
const maxAttempts = 3;
try {
const tempPath = path.join('/tmp/salix-image');
const images = await Self.find({
where: {attempts: {eq: maxAttempts}}
});
// Create temporary path
await fs.mkdir(tempPath, {recursive: true});
for (let image of images) {
const currentStamp = new Date().getTime();
const updatedStamp = image.updated.getTime();
const graceTime = Math.abs(currentStamp - updatedStamp);
const maxTTL = 3600 * 48 * 1000; // 48 hours in ms;
const timer = setInterval(async() => {
const image = await Self.findOne({
where: {error: null, url: {neq: null}}
});
if (graceTime >= maxTTL)
await Self.destroyById(image.itemFk);
}
// Exit loop
if (!image) return clearInterval(timer);
download();
const srcFile = image.url.split('/').pop();
const fileName = srcFile.split('.')[0];
const file = `${fileName}.png`;
const filePath = path.join(tempPath, file);
async function download() {
const image = await Self.findOne({
where: {url: {neq: null}, attempts: {lt: maxAttempts}},
order: 'attempts, updated'
});
if (!image) return;
const srcFile = image.url.split('/').pop();
const dotIndex = srcFile.lastIndexOf('.');
const fileName = srcFile.substring(0, dotIndex);
const file = `${fileName}.png`;
const filePath = path.join(tempPath, file);
https.get(image.url, async response => {
if (response.statusCode != 200) {
const error = new Error(`Could not download the image. Status code ${response.statusCode}`);
return await errorHandler(image.itemFk, error, filePath);
}
const writeStream = fs.createWriteStream(filePath);
writeStream.on('open', () => {
https.get(image.url, async response => {
if (response.statusCode != 200) {
const error = new Error(`Could not download the image. Status code ${response.statusCode}`);
return await errorHandler(image.itemFk, error, filePath);
}
response.pipe(writeStream);
}).on('error', async error => {
await errorHandler(image.itemFk, error, filePath);
});
response.pipe(writeStream);
});
writeStream.on('error', async error => {
@ -58,31 +69,44 @@ module.exports = Self => {
});
writeStream.on('finish', async function() {
writeStream.end();
});
writeStream.on('close', async function() {
try {
await models.Image.registerImage('catalog', filePath, fileName, image.itemFk);
await image.destroy();
download();
} catch (error) {
await errorHandler(image.itemFk, error, filePath);
}
});
}, 1000);
} catch (error) {
throw new Error('Try-catch error: ', error);
}).on('error', async error => {
await errorHandler(image.itemFk, error, filePath);
});
}
async function errorHandler(rowId, error, filePath) {
try {
const row = await Self.findById(rowId);
if (!row)
throw new Error(`Could not update due error ${error}`);
if (!row) return;
await row.updateAttribute('error', error);
if (row.attempts < maxAttempts) {
await row.updateAttributes({
error: error,
attempts: row.attempts + 1,
updated: new Date()
});
}
if (filePath && fs.existsSync(filePath))
await fs.unlink(filePath);
download();
} catch (err) {
throw new Error(`ErrorHandler error: ${err}`);
throw new Error(`Image download failed: ${err}`);
}
}
};

View File

@ -9,17 +9,26 @@
},
"properties": {
"itemFk": {
"type": "Number",
"type": "number",
"id": true,
"description": "Identifier"
},
"url": {
"type": "String",
"type": "string",
"required": true
},
"error": {
"type": "String",
"type": "string",
"required": true
},
"attempts": {
"type": "number"
},
"created": {
"type": "date"
},
"updated": {
"type": "date"
}
},
"relations": {

View File

@ -1,8 +1,4 @@
<vn-horizontal>
<vn-one title="{{$ctrl.name}}">{{$ctrl.name}}</vn-one>
<vn-one ng-if="$ctrl.subName">
<h3 title="{{$ctrl.subName}}">{{$ctrl.subName}}</h3>
</vn-one>
<vn-auto>
<section
class="inline-tag ellipsize"

View File

@ -8,7 +8,5 @@ ngModule.vnComponent('vnFetchedTags', {
bindings: {
maxLength: '<',
item: '<',
name: '<?',
subName: '<?'
}
});

View File

@ -1,42 +1,30 @@
@import "variables";
vn-fetched-tags {
&.noTitle vn-one {
display: none !important;
}
& > vn-horizontal {
align-items: center;
& > vn-one {
overflow: hidden;
text-overflow: ellipsis;
min-width: 80px;
}
& > vn-auto {
flex-wrap: wrap;
& > vn-one:nth-child(2) h3 {
color: $color-font-secondary;
text-transform: uppercase;
line-height: initial;
text-align: center;
font-size: 1rem
& > .inline-tag {
margin: 1px;
}
}
& > vn-auto {
display: flex;
padding-left: 6px;
min-width: 192px;
& > .inline-tag {
display: inline-block;
color: $color-font-secondary;
margin-left: 6px;
text-align: center;
font-size: .75rem;
height: 20px;
height: 12px;
padding: 1px;
border-radius: 1px;
width: 64px;
min-width: 64px;
max-width: 64px;
flex: 1;
border: 1px solid $color-spacer;
&.empty {
@ -44,22 +32,5 @@ vn-fetched-tags {
}
}
}
@media screen and (max-width: 1600px) {
flex-direction: column;
& > vn-one {
padding-bottom: 3px
}
& > vn-auto {
white-space: initial;
padding-left: 0;
flex-wrap: wrap;
justify-content: center;
& > .inline-tag {
margin: 1px;
}
}
}
}
}

View File

@ -22,13 +22,13 @@
model="model">
</vn-searchbar>
</vn-portal>
<div class="vn-w-xl">
<div class="vn-w-lg">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="itemFk">Item ID</vn-th>
<vn-th field="itemFk">Item</vn-th>
<vn-th field="itemFk" shrink>Item ID</vn-th>
<vn-th field="itemFk">Description</vn-th>
<vn-th field="warehouseFk">Warehouse</vn-th>
<vn-th field="rate2">P.P.U.</vn-th>
<vn-th field="rate3">P.P.P.</vn-th>
@ -40,7 +40,7 @@
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="price in prices">
<vn-td>
<vn-td shrink>
<span
ng-if="price.itemFk"
ng-click="itemDescriptor.show($event, price.itemFk)"
@ -64,16 +64,16 @@
</tpl-item>
</vn-autocomplete>
</vn-td>
<vn-td expand>
<text>
<vn-fetched-tags
max-length="6"
item="price"
name="price.name"
sub-name="price.subName"
tabindex="-1">
</vn-fetched-tags>
</text>
<vn-td vn-fetched-tags>
<vn-one title="{{price.name}}">{{price.name}}</vn-one>
<vn-one ng-if="price.subName">
<h3 title="{{price.subName}}">{{price.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="price"
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td>
<vn-autocomplete

View File

@ -15,7 +15,7 @@
<vn-th field="id" shrink>Id</vn-th>
<vn-th field="grouping" shrink>Grouping</vn-th>
<vn-th field="packing" shrink>Packing</vn-th>
<vn-th field="description" style="text-align: center">Description</vn-th>
<vn-th field="description">Description</vn-th>
<vn-th field="stems" shrink>Stems</vn-th>
<vn-th field="size" shrink>Size</vn-th>
<vn-th field="niche" shrink>Niche</vn-th>
@ -50,12 +50,15 @@
</vn-td>
<vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td>
<vn-td shrink>{{::item.packing | dashIfEmpty}}</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags>
<vn-one title="{{::item.name}}">{{::item.name}}</vn-one>
<vn-one ng-if="::item.subName">
<h3 title="{{::item.subName}}">{{::item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::item"
name="::item.name"
sub-name="::item.subName">
item="item"
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td shrink>{{::item.stems}}</vn-td>

View File

@ -21,9 +21,6 @@ vn-item-product {
vn-label-value:first-of-type section{
margin-top: 9px;
}
vn-fetched-tags vn-horizontal{
margin-top: 14px;
}
}
vn-table {

View File

@ -21,7 +21,7 @@
<vn-th number>Id</vn-th>
<vn-th>Description</vn-th>
<vn-th>Warehouse</vn-th>
<vn-th expand>Shipped</vn-th>
<vn-th>Shipped</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Amount</vn-th>
@ -42,16 +42,19 @@
{{::row.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags>
<vn-one title="{{::row.item.name}}">{{::row.item.name}}</vn-one>
<vn-one ng-if="::row.item.subName">
<h3 title="{{::row.item.subName}}">{{::row.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::row.item"
name="::row.item.name"
sub-name="::row.item.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td shrink>{{::row.warehouse.name}}</vn-td>
<vn-td expand>{{::row.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td>{{::row.warehouse.name}}</vn-td>
<vn-td>{{::row.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::row.quantity}}</vn-td>
<vn-td number>
{{::row.price | currency: 'EUR':2}}

View File

@ -98,12 +98,15 @@
{{::row.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-td expand vn-fetched-tags>
<vn-one title="{{::row.item.name}}">{{::row.item.name}}</vn-one>
<vn-one ng-if="::row.item.subName">
<h3 title="{{::row.item.subName}}">{{::row.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::row.item"
name="::row.item.name"
sub-name="::row.item.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::row.quantity}}</vn-td>

View File

@ -24,31 +24,34 @@
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="itemFk" default-order="ASC" number>Item</vn-th>
<vn-th shrink field="itemFk" default-order="ASC" number>Item</vn-th>
<vn-th>Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th number>m³ per quantity</vn-th>
<vn-th shrink field="quantity" number>Quantity</vn-th>
<vn-th shrink number>m³ per quantity</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="row in rows">
<vn-td number>
<vn-td shrink number>
<span
ng-click="descriptor.show($event, row.itemFk)"
class="link">
{{::row.itemFk}}
</span>
</vn-td>
<vn-td expand>
<vn-td wide vn-fetched-tags>
<vn-one title="{{::row.item.name}}">{{::row.item.name}}</vn-one>
<vn-one ng-if="::row.item.subName">
<h3 title="{{::row.item.subName}}">{{::row.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::row.item"
name="::row.item.name"
sub-name="::row.item.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::row.quantity}}</vn-td>
<vn-td number>{{::row.volume | number:3}}</vn-td>
<vn-td shrink number>{{::row.quantity}}</vn-td>
<vn-td shrink number>{{::row.volume | number:3}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>

View File

@ -1,8 +1,7 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
// #2735 route updateVolume() returns inconsistent values
xdescribe('route updateVolume()', () => {
describe('route updateVolume()', () => {
const routeId = 1;
const userId = 50;
const activeCtx = {
@ -19,7 +18,6 @@ xdescribe('route updateVolume()', () => {
expect(route.m3).toEqual(1.8);
const ticket = await app.models.Ticket.findById(14);
await ticket.updateAttributes({routeFk: routeId});
await app.models.Route.updateVolume(ctx, routeId);
@ -30,7 +28,8 @@ xdescribe('route updateVolume()', () => {
const logs = await app.models.RouteLog.find({fields: ['id', 'newInstance']});
const m3Log = logs.filter(log => {
return log.newInstance.m3 === updatedRoute.m3;
if (log.newInstance)
return log.newInstance.m3 === updatedRoute.m3;
});
const logIdToDestroy = m3Log[0].id;

View File

@ -37,7 +37,7 @@
<vn-thead>
<vn-tr>
<vn-th field="entryFk" expand>Entry </vn-th>
<vn-td expand>{{::entry.id}}</vn-td>
<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>
@ -51,10 +51,15 @@
{{::buy.itemName}}
</span>
</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags wide>
<vn-one></vn-one>
<vn-one ng-if="::buy.subName">
<h3 title="{{::buy.subName}}">{{::buy.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::buy">
item="::buy"
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::buy.quantity | dashIfEmpty}}</vn-td>

View File

@ -17,6 +17,9 @@
{"state": "supplier.card.consumption", "icon": "show_chart"}
]
},
"keybindings": [
{"key": "p", "state": "supplier.index"}
],
"routes": [
{
"url": "/supplier",

View File

@ -18,11 +18,15 @@
<vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.ticket.sale.items track by sale.id">
<vn-td number>{{("000000"+sale.itemFk).slice(-6)}}</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags wide>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
<vn-one ng-if="::sale.item.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>

View File

@ -29,12 +29,15 @@
{{sale.itemFk | zeroFill:6}}
</span>
</td>
<td rowspan="{{::sale.components.length + 1}}" expand>
<td rowspan="{{::sale.components.length + 1}}" vn-fetched-tags>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
<vn-one ng-if="::sale.item.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
tabindex="-1">
</vn-fetched-tags>
</td>
<td rowspan="{{::sale.components.length + 1}}" number>

View File

@ -34,12 +34,15 @@
{{::sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
<vn-one ng-if="::sale.item.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>

View File

@ -8,7 +8,7 @@
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card class="vn-w-xl">
<vn-card class="vn-w-lg">
<vn-table model="model">
<vn-thead>
<vn-tr>
@ -39,12 +39,15 @@
{{sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-td vn-fetched-tags wide>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
<vn-one ng-if="::sale.item.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>

View File

@ -58,8 +58,8 @@
</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th number id="ticketId">Id</vn-th>
<vn-th>Quantity</vn-th>
<vn-th shrink id="ticketId">Id</vn-th>
<vn-th shrink>Quantity</vn-th>
<vn-th>Item</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc</vn-th>
@ -97,7 +97,7 @@
zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
on-error-src/>
</vn-td>
<vn-td number>
<vn-td shrink>
<span class="link" ng-if="sale.id"
ng-click="descriptor.show($event, sale.itemFk, sale.id)">
{{sale.itemFk}}
@ -117,7 +117,7 @@
</tpl-item>
</vn-autocomplete>
</vn-td>
<vn-td-editable disabled="!$ctrl.isEditable" number>
<vn-td-editable disabled="!$ctrl.isEditable" shrink>
<text>{{sale.quantity}}</text>
<field>
<vn-input-number class="dense"
@ -127,13 +127,16 @@
</vn-input-number>
</field>
</vn-td-editable>
<vn-td-editable disabled="!sale.id || !$ctrl.isEditable" expand>
<vn-td-editable vn-fetched-tags wide disabled="!sale.id || !$ctrl.isEditable">
<text>
<vn-one title="{{sale.concept}}">{{sale.concept}}</vn-one>
<vn-one ng-if="::sale.subName">
<h3 title="{{::sale.subName}}">{{::sale.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="sale.concept"
sub-name="::sale.subName">
tabindex="-1">
</vn-fetched-tags>
</text>
<field>

View File

@ -23,6 +23,25 @@ vn-ticket-sale {
margin: 3px;
}
}
vn-td-editable[vn-fetched-tags] {
& text {
max-width: 430px;
min-width: 150px;
& vn-one {
overflow: hidden;
text-overflow: ellipsis;
font-size: 0.75rem;
}
& vn-one:nth-child(2) h3 {
color: $color-font-secondary;
text-transform: uppercase;
line-height: initial;
font-size: 0.75rem
}
}
}
vn-dialog.edit {
@extend .edit-popover;

View File

@ -149,12 +149,15 @@
</span>
</vn-td>
<vn-td number shrink>{{::sale.quantity}}</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
<vn-td vn-fetched-tags wide>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
<vn-one ng-if="::sale.item.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName">
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.price | currency: 'EUR':2}}</vn-td>

View File

@ -42,12 +42,16 @@
{{sale.itemFk | zeroFill:6}}
</span>
</vn-td>
<vn-td expand>
<vn-fetched-tags
max-length="6"
<vn-td vn-fetched-tags>
<vn-one title="{{::sale.item.name}}">{{::sale.item.name}}</vn-one>
<vn-one ng-if="::sale.item.subName">
<h3 title="{{::sale.item.subName}}">{{::sale.item.subName}}</h3>
</vn-one>
<vn-fetched-tags
max-length="6"
item="::sale.item"
name="::sale.concept"
sub-name="::sale.item.subName"/>
tabindex="-1">
</vn-fetched-tags>
</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td>
<vn-td number>{{::sale.saleVolume.volume | number:3}}</vn-td>

View File

@ -18,6 +18,9 @@
{"state": "zone.card.events", "icon": "today"}
]
},
"keybindings": [
{"key": "z", "state": "zone.index"}
],
"routes": [
{
"url": "/zone",