catalog filter #581

This commit is contained in:
Joan Sanchez 2018-08-21 13:38:16 +02:00
parent 46eb1e6f98
commit 300b2c2a07
41 changed files with 811 additions and 257 deletions

View File

@ -34,10 +34,10 @@
} }
}, },
{ {
"url": "/catalogue", "url": "/catalog?q",
"state": "order.card.catalogue", "state": "order.card.catalog",
"component": "vn-order-catalogue", "component": "vn-order-catalog",
"description": "Catalogue", "description": "Catalog",
"params": { "params": {
"order": "$ctrl.order" "order": "$ctrl.order"
}, },

View File

@ -0,0 +1,86 @@
<vn-crud-model
vn-id="model"
url="/order/api/Orders/CatalogFilter"
filter="::$ctrl.filter"
data="items" auto-load="false">
</vn-crud-model>
<vn-horizontal>
<vn-vertical vn-one>
<vn-card>
<vn-vertical>
<vn-horizontal class="catalog-header" pad-medium>
<vn-one>{{model.data.length}} <span translate>results</span></vn-one>
<vn-one>-</vn-one>
</vn-horizontal>
<vn-horizontal class="catalog-list" pad-small>
<section class="product" ng-repeat="item in items">
<vn-one>
<vn-horizontal>
<vn-one class="image">
<img
ng-src="//verdnatura.es/vn-image-data/catalog/200x200/{{::item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::item.image}}"
on-error-src pointer/>
</vn-one>
<vn-one pad-small class="description ellipsize">
<vn-vertical>
<h2 class="ellipsize" vn-tooltip="{{::item.name}}">
{{::item.name}}
</h2>
<span class="ellipsize" vn-tooltip="{{::item.subName}}">
{{::item.subName}}
</span>
<vn-label-value
label="{{::item.tag5}}"
value="{{::item.value5}}">
</vn-label-value>
<vn-label-value
label="{{::item.tag6}}"
value="{{::item.value6}}">
</vn-label-value>
<vn-label-value
label="{{::item.tag7}}"
value="{{::item.value7}}">
</vn-label-value>
<vn-label-value
label="{{::item.tag8}}"
value="{{::item.value8}}">
</vn-label-value>
<vn-horizontal class="price">
<vn-one>
<span>{{::item.available}}</span>
<span translate>from</span>
<span>{{::item.price | currency: ' €': 2}}</span>
</vn-one>
<vn-auto>
<a href="" vn-tooltip="Add">
<vn-icon icon="add_circle" ng-click="$ctrl.preview($event, item)"></vn-icon>
</a>
</vn-auto>
</vn-horizontal>
</vn-vertical>
</vn-one>
</vn-horizontal>
</vn-one>
</section>
</vn-horizontal>
<vn-horizontal ng-if="model.data.length == 0">
<vn-one pad-small translate style="text-align: center">
No results
</vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
</vn-vertical>
<vn-auto class="right-block">
<vn-catalog-filter order="$ctrl.order"></vn-catalog-filter>
</vn-auto>
</vn-horizontal>
<vn-order-prices-popover
vn-id="pricesPopover">
</vn-order-prices-popover>

View File

@ -17,10 +17,8 @@ class Controller {
if (!value) return; if (!value) return;
this.filter = { this.filter = {
where: { orderFk: value.id,
id: value.id, where: {}
typeFk: 1
}
}; };
} }
@ -28,7 +26,8 @@ class Controller {
return this._order; return this._order;
} }
openPricePopover(event, item) { preview(event, item) {
event.preventDefault();
this.$scope.pricesPopover.show(event, item); this.$scope.pricesPopover.show(event, item);
} }
@ -39,7 +38,7 @@ class Controller {
Controller.$inject = ['$scope', '$stateParams']; Controller.$inject = ['$scope', '$stateParams'];
ngModule.component('vnOrderCatalogue', { ngModule.component('vnOrderCatalog', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {

View File

@ -1,34 +0,0 @@
<vn-crud-model
vn-id="model"
url="/order/api/Orders/ItemFilter"
filter="::$ctrl.filter"
data="items" auto-load="false">
</vn-crud-model>
<vn-horizontal>
<vn-vertical vn-one>
<vn-card>
<vn-vertical>
<vn-horizontal class="catalogue header" pad-medium>
<vn-one>{{model.data.length}} <span translate>results</span></vn-one>
<vn-one>-</vn-one>
</vn-horizontal>
<vn-horizontal class="catalogue list" pad-small>
<vn-order-product
ng-repeat="item in items"
item="::item">
</vn-order-product>
<vn-one style="text-align: center" pad-small translate>No results</vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
</vn-vertical>
<vn-auto class="right-block">
<vn-filter order="$ctrl.order"></vn-filter>
</vn-auto>
</vn-horizontal>
<vn-order-prices-popover
vn-id="pricesPopover">
</vn-order-prices-popover>

View File

@ -1,50 +0,0 @@
<vn-one>
<vn-horizontal>
<vn-one class="image">
<img
ng-src="//verdnatura.es/vn-image-data/catalog/200x200/{{::$ctrl.item.image}}"
zoom-image="//verdnatura.es/vn-image-data/catalog/1600x900/{{::$ctrl.item.image}}"
on-error-src pointer/>
</vn-one>
<vn-one pad-small class="description ellipsize">
<vn-vertical>
<h2 class="ellipsize" vn-tooltip="{{::$ctrl.item.name}}">
{{::$ctrl.item.name}}
</h2>
<span class="ellipsize" vn-tooltip="{{::$ctrl.item.subName}}">
{{::$ctrl.item.subName}}
</span>
<vn-label-value
label="{{::$ctrl.item.tag5}}"
value="{{::$ctrl.item.value5}}">
</vn-label-value>
<vn-label-value
label="{{::$ctrl.item.tag6}}"
value="{{::$ctrl.item.value6}}">
</vn-label-value>
<vn-label-value
label="{{::$ctrl.item.tag7}}"
value="{{::$ctrl.item.value7}}">
</vn-label-value>
<vn-label-value
label="{{::$ctrl.item.tag8}}"
value="{{::$ctrl.item.value8}}">
</vn-label-value>
<vn-horizontal class="price">
<vn-one>
<span>{{::$ctrl.item.available}}</span>
<span translate>from</span>
<span>{{::$ctrl.item.price | currency: ' €': 2}}</span>
</vn-one>
<vn-auto>
<a href="" vn-tooltip="Add">
<vn-icon icon="add_circle" ng-click="$ctrl.preview($event)"></vn-icon>
</a>
</vn-auto>
</section>
</vn-vertical>
</vn-one>
</vn-horizontal>
</vn-one>

View File

@ -1,24 +0,0 @@
import ngModule from '../module';
class Controller {
onClick(event) {
if (event.defaultPrevented)
event.stopImmediatePropagation();
}
preview(event) {
event.preventDefault();
this.index.openPricePopover(event, this.item);
}
}
ngModule.component('vnOrderProduct', {
template: require('./product.html'),
controller: Controller,
bindings: {
item: '<'
},
require: {
index: '^vnOrderCatalogue'
}
});

View File

@ -1,9 +1,49 @@
<vn-crud-model
vn-id="model"
url="/order/api/ItemCategories"
data="categories">
</vn-crud-model>
<vn-horizontal> <vn-horizontal>
<vn-vertical vn-one> <vn-vertical vn-one>
<vn-card pad-medium> <vn-card >
<vn-vertical margin-medium> <vn-vertical>
<vn-submit ng-click="$ctrl.setFilter()" label="Filter"></vn-submit> <vn-horizontal pad-medium class="item-category">
<vn-one margin-small-v ng-repeat="category in categories">
<vn-icon
ng-class="{'active': $ctrl.categoryFk == category.id}"
pad-small
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.categoryFk = category.id">
</vn-icon>
</vn-one>
</vn-horizontal>
<vn-horizontal pad-medium class="catalog-header">
<vn-icon icon="search"></vn-icon>
<vn-autocomplete vn-one
initial-data="$ctrl.typeFk"
field="$ctrl.typeFk"
data="$ctrl.itemTypes"
show-field="name"
value-field="id"
label="Type">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal pad-medium class="catalog-header">
<vn-searchbar
panel="vn-order-search-panel"
on-search="$ctrl.onSearch(filter)"
expr-builder="$ctrl.exprBuilder(param, value)">
</vn-searchbar>
</vn-horizontal>
<vn-horizontal pad-medium>
tags
</vn-horizontal>
</vn-vertical> </vn-vertical>
</vn-card> </vn-card>
</vn-vertical> </vn-vertical>

View File

@ -1,16 +1,81 @@
import ngModule from '../module'; import ngModule from '../module';
import './style.scss';
class Controller { class Controller {
setFilter() { constructor($http) {
this.catalogue.applyFilter(); this.$http = $http;
this.itemTypes = [];
}
get where() {
return this.catalog.filter.where;
}
set categoryFk(value) {
if (this.where['it.categoryFk'] == value) {
this.where['it.categoryFk'] = null;
this.where['i.typeFk'] = null;
this.itemTypes = [];
return;
}
this.where['it.categoryFk'] = value;
this.where['i.typeFk'] = null;
let query = `/item/api/ItemCategories/${value}/itemTypes`;
this.$http.get(query).then(res => {
this.itemTypes = res.data;
});
}
get categoryFk() {
return this.where['it.categoryFk'];
}
set typeFk(value) {
this.where['i.typeFk'] = value;
this.catalog.applyFilter();
}
get typeFk() {
return this.where['i.typeFk'];
}
onSearch(filter) {
if (!Object.keys(filter).length) return;
if (filter.search) {
console.log(this.exprBuilder('search', filter.search));
Object.assign(this.where, this.exprBuilder('search', filter.search));
} else {
Object.assign(this.where, filter);
console.log(this.where);
}
this.catalog.applyFilter();
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return {'itg.value': {like: value}};
case 'itg.tagFk':
case 'itg.value':
return {[param]: value};
}
} }
} }
ngModule.component('vnFilter', { Controller.$inject = ['$http'];
ngModule.component('vnCatalogFilter', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
require: { require: {
catalogue: '^vnOrderCatalogue' catalog: '^vnOrderCatalog'
}, },
bindings: { bindings: {
order: '<' order: '<'

View File

@ -0,0 +1,47 @@
@import "colors";
vn-catalog-filter {
/* .item-category, .item-category * {
vn-button button {
border-radius: 50%;
padding: 1em;
width: 4.9em;
height: 4.9em;
vn-icon i {
font-size: 32pt
}
}
} */
.item-category {
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
vn-icon {
background-color: $secondary-font-color;
border-radius: 50%;
cursor: pointer;
i:before {
font-size: 32pt;
width: 1em;
height: 1em;
}
}
vn-icon.active {
background-color: $main-01;
color: #FFF
}
& > vn-one {
width: 33.33%;
text-align: center
}
}
}

View File

@ -2,11 +2,11 @@ export * from './module';
import './card'; import './card';
import './descriptor'; import './descriptor';
import './search-panel';
import './filter'; import './filter';
import './index/'; import './index/';
import './summary'; import './summary';
import './catalogue'; import './catalog';
import './catalogue/product';
import './line'; import './line';
import './prices-popover'; import './prices-popover';
import './volume'; import './volume';

View File

@ -18,11 +18,11 @@
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="order in orders" class="clickable" <vn-tr ng-repeat="order in orders" class="clickable"
ui-sref="order.card.catalogue({id: {{::order.id}}})"> ui-sref="order.card.catalog({id: {{::order.id}}})">
<vn-td>{{::order.id}}</vn-td> <vn-td>{{::order.id}}</vn-td>
<vn-td>{{::order.clientFk}}</vn-td> <vn-td>{{::order.clientFk}}</vn-td>
<vn-td>{{::order.companyFk}}</vn-td> <vn-td>{{::order.companyFk}}</vn-td>
<vn-td>{{::order.created}}</vn-td> <vn-td>{{::order.created | date:'dd/MM/yyyy'}}</vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
<vn-empty-rows ng-if="model.data.length === 0" translate> <vn-empty-rows ng-if="model.data.length === 0" translate>

View File

@ -20,7 +20,7 @@
value="{{$ctrl.item.name}}"> value="{{$ctrl.item.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Buyer" <vn-label-value label="Buyer"
value="{{$ctrl.item.workerFirstName}} {{$ctrl.item.workerName}}"> value="{{$ctrl.item.firstName}} {{$ctrl.item.lastName}}">
</vn-label-value> </vn-label-value>
<vn-label-value <vn-label-value
ng-repeat="tag in $ctrl.tags" ng-repeat="tag in $ctrl.tags"
@ -36,7 +36,7 @@
<vn-vertical class="prices"> <vn-vertical class="prices">
<vn-horizontal <vn-horizontal
ng-repeat="price in $ctrl.prices"> ng-repeat="price in $ctrl.prices">
<vn-one class="ellipsize text" title="{{::price.warehouseName}}">{{::price.warehouseName}}</vn-one> <vn-one class="ellipsize text" title="{{::price.warehouse}}">{{::price.warehouse}}</vn-one>
<vn-one class="number text"> <vn-one class="number text">
<span orange ng-click="$ctrl.addQuantity(price)" class="link">{{::price.grouping}} x </span><span>{{::price.price | currency: ' €': 2}}</span> <span orange ng-click="$ctrl.addQuantity(price)" class="link">{{::price.grouping}} x </span><span>{{::price.price | currency: ' €': 2}}</span>
</vn-one> </vn-one>

View File

@ -0,0 +1,25 @@
<div pad-large style="min-width: 10em">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-autocomplete
vn-one
label="Tag"
field="filter['itg.tagFk']"
url="/api/Tags"
show-field="name"
value-field="id">
<tpl-item>{{name}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Value"
model="filter['itg.value']">
</vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,7 @@
import ngModule from '../module';
import SearchPanel from 'core/src/components/searchbar/search-panel';
ngModule.component('vnOrderSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
});

View File

@ -2,45 +2,45 @@
@media screen and (max-width: 1920px){ @media screen and (max-width: 1920px){
vn-order-product { .catalog-list .product {
width: 25%; width: 25%;
}
} }
}
@media screen and (max-width: 1800px){ @media screen and (max-width: 1800px){
vn-order-product { .catalog-list .product {
width: 33.33% width: 33.33%
}
} }
}
@media screen and (max-width: 1600px){ @media screen and (max-width: 1600px){
vn-order-product { .catalog-list .product {
width: 50% width: 50%
}
} }
}
@media screen and (max-width: 1280px){ @media screen and (max-width: 1280px){
vn-order-product { .catalog-list .product {
width: 100% width: 100%
}
} }
}
.catalogue.header { .catalog-header {
border-color: $lines; border-color: $lines;
border-bottom: 1px solid rgba($lines, 0.5); border-bottom: 1px solid rgba($lines, 0.5);
span { span {
color: $secondary-font-color color: $secondary-font-color
}
} }
}
.catalogue.list { .catalog-list {
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
flex-wrap: wrap flex-wrap: wrap;
}
vn-order-product { .product {
box-sizing: border-box; box-sizing: border-box;
padding: 4px; padding: 4px;
@ -108,3 +108,4 @@
} }
} }
} }
}

View File

@ -11,8 +11,8 @@
<vn-card pad-large> <vn-card pad-large>
<vn-vertical> <vn-vertical>
<vn-title>Pictures</vn-title> <vn-title>Pictures</vn-title>
<vn-horizontal class="catalogue list" pad-small> <vn-horizontal class="catalog-list" pad-small>
<vn-order-product ng-repeat="sale in sales"> <section class="product" ng-repeat="sale in sales">
<vn-one> <vn-one>
<vn-horizontal> <vn-horizontal>
<vn-one class="image"> <vn-one class="image">
@ -54,12 +54,12 @@
<span translate>by</span> <span translate>by</span>
<span>{{::sale.price | currency: ' €': 2}}</span> <span>{{::sale.price | currency: ' €': 2}}</span>
</vn-one> </vn-one>
</section> </vn-horizontal>
</vn-vertical> </vn-vertical>
</vn-one> </vn-one>
</vn-horizontal> </vn-horizontal>
</vn-one> </vn-one>
</vn-order-product> </section>
</vn-horizontal> </vn-horizontal>
<vn-horizontal ng-if="model.data.length == 0"> <vn-horizontal ng-if="model.data.length == 0">
<vn-one pad-small-v translate> <vn-one pad-small-v translate>

View File

@ -28,7 +28,7 @@ module.exports = function(Self) {
}; };
let insurance = await Self.findOne(filter); let insurance = await Self.findOne(filter);
if (insurance && (!insurance.grade && this.grade || insurance.grade && ! this.grade)) if (insurance && (!insurance.grade && this.grade || insurance.grade && !this.grade))
err(); err();
done(); done();

View File

@ -1,25 +1,25 @@
#!/bin/bash #!/bin/bash
export MYSQL_PWD=root
if [ -d /data/mysql ]; then if [ -d /data/mysql ]; then
cp -R /data/mysql /var/lib cp -R /data/mysql /var/lib
echo "Restored database to default state" echo "Restored database to default state"
else else
# Dump structure # Dump structure
for file in dump/*-*.sql; do for file in dump/*-*.sql; do
echo "Imported $file" echo "Imported $file"
mysql -u root -proot -fc < $file mysql -u root -fc < $file
done done
# Import changes # Import changes
for file in changes/*/*.sql; do for file in changes/*/*.sql; do
echo "Imported $file" echo "Imported $file"
mysql -u root -proot -fc < $file mysql -u root -fc < $file
done done
# Import fixtures # Import fixtures
echo "Imported fixtures.sql" echo "Imported fixtures.sql"
mysql -u root -proot -f < dump/fixtures.sql mysql -u root -f < dump/fixtures.sql
# Copy dumpted data to volume # Copy dumpted data to volume
cp -R /var/lib/mysql /data cp -R /var/lib/mysql /data

View File

@ -17,5 +17,3 @@ VIEW `sample` AS
`e`.`hasCompany` AS `hasCompany` `e`.`hasCompany` AS `hasCompany`
FROM FROM
`vn2008`.`escritos` `e`; `vn2008`.`escritos` `e`;
DROP VIEW `vn`.`clientNotificationType`;

View File

@ -14,5 +14,3 @@ VIEW `clientSample` AS
`e`.`empresa_id` AS `companyFk` `e`.`empresa_id` AS `companyFk`
FROM FROM
`vn2008`.`escritos_det` `e`; `vn2008`.`escritos_det` `e`;
DROP VIEW `vn`.`clientNotification`;

View File

@ -0,0 +1,13 @@
USE `vn2008`;
ALTER TABLE `vn2008`.`reinos`
ADD COLUMN `icon` VARCHAR(45) NULL AFTER `mercancia`;
INSERT INTO `vn2008`.`reinos` (`reino`, `display`, `icon`) VALUES
('Handmade', '1', 'icon-handmade'),
('Artificial', '1', 'icon-artificial'),
('Green', '1', 'icon-greenery'),
('Accessories', '1', 'icon-accessory');
UPDATE `vn2008`.`reinos` SET `icon`='icon-plant' WHERE `id`=1;
UPDATE `vn2008`.`reinos` SET `icon`='icon-flower' WHERE `id`=2;

View File

@ -0,0 +1,14 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `itemCategory` AS
SELECT
`r`.`id` AS `id`,
`r`.`reino` AS `name`,
`r`.`display` AS `display`,
`r`.`color` AS `color`,
r.icon
FROM
`vn2008`.`reinos` `r`;

View File

@ -0,0 +1,117 @@
USE `vn`;
DROP procedure IF EXISTS `ticketCalculate`;
DELIMITER $$
USE `vn`$$
CREATE DEFINER=`root`@`%` PROCEDURE `ticketCalculate`(
vDate DATE,
vAddress INT,
vAgencyMode INT)
proc: BEGIN
/**
* Calcula los articulos disponibles y sus precios
*
* @param vDate Fecha de recepcion de mercancia
* @param vAddress Id del consignatario
* @param vAgencyMode Id de la agencia
* @return tmp.ticketCalculateItem, tmp.ticketComponentPrice
**/
DECLARE vAvailableCalc INT;
DECLARE vShipment DATE;
DECLARE vAgencyId INT;
DECLARE vClient INT;
DECLARE vWarehouseFk SMALLINT;
DECLARE vDone BOOL;
DECLARE cTravelTree CURSOR FOR
SELECT warehouseFk, shipped FROM tmp.agencyHourGetShipped;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
-- Establece los almacenes y las fechas que van a entrar al disponible
SELECT agencyFk INTO vAgencyId
FROM agencyMode WHERE id = vAgencyMode;
SELECT clientFk INTO vClient
FROM address WHERE id = vAddress;
CALL vn.agencyHourGetShipped(vDate, vAddress, vAgencyId);
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot(
`warehouseFk` smallint(5) unsigned NOT NULL,
`itemFk` int(11) NOT NULL,
`available` double DEFAULT NULL,
`buyFk` int(11) DEFAULT NULL,
`fix` tinyint(3) unsigned DEFAULT '0',
KEY `itemFk` (`itemFk`),
KEY `item_warehouse` (`itemFk`,`warehouseFk`) USING HASH
) ENGINE=MEMORY DEFAULT CHARSET=utf8;
OPEN cTravelTree;
l: LOOP
SET vDone = FALSE;
FETCH cTravelTree INTO vWarehouseFk, vShipment;
IF vDone THEN
LEAVE l;
END IF;
CALL `cache`.available_refresh (vAvailableCalc, FALSE, vWarehouseFk, vShipment);
CALL buyUltimate (vWarehouseFk, vShipment);
INSERT INTO tmp.ticketLot (warehouseFk, itemFk, available, buyFk)
SELECT
vWarehouseFk,
i.item_id,
IFNULL(i.available, 0),
bu.buyFk
FROM `cache`.available i
JOIN tmp.item br ON br.itemFk = i.item_id
LEFT JOIN item it ON it.id = i.item_id
LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = i.item_id
WHERE i.calc_id = vAvailableCalc
AND it.id != 100
AND i.available > 0;
DROP TEMPORARY TABLE tmp.buyUltimate;
END LOOP;
CLOSE cTravelTree;
CALL vn.ticketComponentCalculate(vAddress, vAgencyMode);
DROP TEMPORARY TABLE IF EXISTS tmp.ticketCalculateItem;
CREATE TEMPORARY TABLE tmp.ticketCalculateItem
ENGINE = MEMORY
SELECT
b.itemFk,
SUM(b.available) available,
p.name producer,
i.name item,
i.size size,
i.stems,
i.category,
i.inkFk,
i.image,
o.code origin, bl.price
FROM tmp.ticketLot b
JOIN item i ON b.itemFk = i.id
LEFT JOIN producer p ON p.id = i.producerFk AND p.isVisible
JOIN origin o ON o.id = i.originFk
JOIN (
SELECT MIN(price) price, itemFk
FROM tmp.ticketComponentPrice
GROUP BY itemFk
) bl ON bl.itemFk = b.itemFk
GROUP BY b.itemFk;
DROP TEMPORARY TABLE
tmp.ticketComponent,
tmp.ticketLot;
END$$
DELIMITER ;

View File

@ -29,5 +29,6 @@
"The credit must be an integer greater than or equal to zero": "The credit must be an integer greater than or equal to zero", "The credit must be an integer greater than or equal to zero": "The credit must be an integer greater than or equal to zero",
"The grade must be an integer greater than or equal to zero": "The grade must be an integer greater than or equal to zero", "The grade must be an integer greater than or equal to zero": "The grade must be an integer greater than or equal to zero",
"Sample type cannot be blank": "Sample type cannot be blank", "Sample type cannot be blank": "Sample type cannot be blank",
"The new quantity should be smaller than the old one": "La nueva cantidad debe ser menor que la anterior" "The new quantity should be smaller than the old one": "La nueva cantidad debe ser menor que la anterior",
"The package cannot be blank": "The package cannot be blank"
} }

View File

@ -0,0 +1,45 @@
{
"name": "ItemCategory",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemCategory"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String"
},
"display": {
"type": "Boolean"
},
"icon": {
"type": "String"
}
},
"relations": {
"itemTypes": {
"type": "hasMany",
"model": "ItemType",
"foreignKey": "categoryFk"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
],
"scope": {
"where": {
"display": {"gte": 1}
}
}
}

View File

@ -35,7 +35,12 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Warehouse", "model": "Warehouse",
"foreignKey": "warehouseFk" "foreignKey": "warehouseFk"
} },
"category": {
"type": "belongsTo",
"model": "ItemCategory",
"foreignKey": "categoryFk"
}
}, },
"acls": [ "acls": [
{ {

View File

@ -197,7 +197,7 @@ module.exports = function(Self) {
return sql + connector.columnEscaped(model, property); return sql + connector.columnEscaped(model, property);
}; };
return wrappedConnector.buildWhere(this.modelName, filter.where); return wrappedConnector.makeWhere(this.modelName, filter.where);
}; };
Self.buildLimit = function(filter) { Self.buildLimit = function(filter) {

View File

@ -1,8 +1,11 @@
var mysql = require('mysql'); var mysql = require('mysql');
var SqlConnector = require('loopback-connector').SqlConnector; const loopbackConnector = require('loopback-connector');
const SqlConnector = loopbackConnector.SqlConnector;
const ParameterizedSQL = loopbackConnector.ParameterizedSQL;
var MySQL = require('loopback-connector-mysql').MySQL; var MySQL = require('loopback-connector-mysql').MySQL;
var EnumFactory = require('loopback-connector-mysql').EnumFactory; var EnumFactory = require('loopback-connector-mysql').EnumFactory;
var debug = require('debug')('loopback-connector-sql');
exports.initialize = function(dataSource, callback) { exports.initialize = function(dataSource, callback) {
dataSource.driver = mysql; dataSource.driver = mysql;
@ -53,3 +56,140 @@ VnMySQL.prototype.toColumnValue = function(prop, val) {
return v < 10 ? '0' + v : v; return v < 10 ? '0' + v : v;
} }
}; };
/**
* Private make method
* @param {Object} model Model instance
* @param {Object} where Where filter
* @return {ParameterizedSQL} Parametized object
*/
VnMySQL.prototype._makeWhere = function(model, where) {
let columnValue;
let sqlExp;
if (!where) {
return new ParameterizedSQL('');
}
if (typeof where !== 'object' || Array.isArray(where)) {
debug('Invalid value for where: %j', where);
return new ParameterizedSQL('');
}
var self = this;
var props = self.getModelDefinition(model).properties;
var whereStmts = [];
for (var key in where) {
var stmt = new ParameterizedSQL('', []);
// Handle and/or operators
if (key === 'and' || key === 'or') {
var branches = [];
var branchParams = [];
var clauses = where[key];
if (Array.isArray(clauses)) {
for (var i = 0, n = clauses.length; i < n; i++) {
var stmtForClause = self._makeWhere(model, clauses[i]);
if (stmtForClause.sql) {
stmtForClause.sql = '(' + stmtForClause.sql + ')';
branchParams = branchParams.concat(stmtForClause.params);
branches.push(stmtForClause.sql);
}
}
stmt.merge({
sql: branches.join(' ' + key.toUpperCase() + ' '),
params: branchParams,
});
whereStmts.push(stmt);
continue;
}
// The value is not an array, fall back to regular fields
}
var p = props[key];
// eslint-disable one-var
var expression = where[key];
var columnName = self.columnEscaped(model, key);
// eslint-enable one-var
if (expression === null || expression === undefined) {
stmt.merge(columnName + ' IS NULL');
} else if (expression && expression.constructor === Object) {
var operator = Object.keys(expression)[0];
// Get the expression without the operator
expression = expression[operator];
if (operator === 'inq' || operator === 'nin' || operator === 'between') {
columnValue = [];
if (Array.isArray(expression)) {
// Column value is a list
for (var j = 0, m = expression.length; j < m; j++) {
columnValue.push(this.toColumnValue(p, expression[j]));
}
} else {
columnValue.push(this.toColumnValue(p, expression));
}
if (operator === 'between') {
// BETWEEN v1 AND v2
var v1 = columnValue[0] === undefined ? null : columnValue[0];
var v2 = columnValue[1] === undefined ? null : columnValue[1];
columnValue = [v1, v2];
} else {
// IN (v1,v2,v3) or NOT IN (v1,v2,v3)
if (columnValue.length === 0) {
if (operator === 'inq') {
columnValue = [null];
} else {
// nin () is true
continue;
}
}
}
} else if (operator === 'regexp' && expression instanceof RegExp) {
// do not coerce RegExp based on property definitions
columnValue = expression;
} else {
columnValue = this.toColumnValue(p, expression);
}
sqlExp = self.buildExpression(columnName, operator, columnValue, p);
stmt.merge(sqlExp);
} else {
// The expression is the field value, not a condition
columnValue = self.toColumnValue(p, expression);
if (columnValue === null) {
stmt.merge(columnName + ' IS NULL');
} else {
if (columnValue instanceof ParameterizedSQL) {
stmt.merge(columnName + '=').merge(columnValue);
} else {
stmt.merge({
sql: columnName + '=?',
params: [columnValue],
});
}
}
}
whereStmts.push(stmt);
}
var params = [];
var sqls = [];
for (var k = 0, s = whereStmts.length; k < s; k++) {
sqls.push(whereStmts[k].sql);
params = params.concat(whereStmts[k].params);
}
var whereStmt = new ParameterizedSQL({
sql: sqls.join(' AND '),
params: params,
});
return whereStmt;
};
/**
* Build the SQL WHERE clause for the where object
* @param {string} model Model name
* @param {object} where An object for the where conditions
* @return {ParameterizedSQL} The SQL WHERE clause
*/
VnMySQL.prototype.makeWhere = function(model, where) {
var whereClause = this._makeWhere(model, where);
if (whereClause.sql) {
whereClause.sql = 'WHERE ' + whereClause.sql;
}
return whereClause;
};

View File

@ -90,6 +90,9 @@
"ItemType": { "ItemType": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ItemCategory": {
"dataSource": "vn"
},
"Expence": { "Expence": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,119 @@
const ParameterizedSQL = require('vn-loopback/node_modules/loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('catalogFilter', {
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',
http: {source: 'query'}
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/catalogFilter`,
verb: 'GET'
}
});
Self.catalogFilter = async filter => {
let stmts = [];
let stmt;
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.item');
stmt = new ParameterizedSQL(
`CREATE TEMPORARY TABLE tmp.item
(PRIMARY KEY (itemFk)) ENGINE = MEMORY
SELECT
i.id AS itemFk,
i.typeFk,
it.categoryFk
FROM vn.item i
JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.itemCategory ic ON ic.id = it.categoryFk`
);
if (filter.where['itg.tagFk'] || filter.where['itg.value']) {
stmt.merge('JOIN vn.itemTag itg ON itg.itemFk = i.id');
}
stmt.merge(Self.buildSuffix(filter));
stmts.push(stmt);
let order = await Self.findById(filter.orderFk);
stmts.push(new ParameterizedSQL(
'CALL vn.ticketCalculate(?, ?, ?)', [
order.landed,
order.address_id,
order.agency_id
]
));
let itemsIndex = stmts.push(
`SELECT
i.id,
i.name,
i.subName,
i.image,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7,
i.tag8,
i.value8,
tci.price,
tci.available,
w.name AS lastName,
w.firstName
FROM tmp.ticketCalculateItem tci
JOIN vn.item i ON i.id = tci.itemFk
JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.worker w on w.id = it.workerFk
ORDER BY relevancy DESC, itemFk ASC, producer DESC`
) - 1;
let pricesIndex = stmts.push(
`SELECT
tcp.itemFk,
tcp.grouping,
tcp.price,
w.name AS warehouse
FROM tmp.ticketComponentPrice tcp
JOIN vn.warehouse w ON w.id = tcp.warehouseFk`
) - 1;
stmts.push(
`DROP TEMPORARY TABLE
tmp.item,
tmp.ticketCalculateItem,
tmp.ticketComponentPrice`
);
let sql = ParameterizedSQL.join(stmts, ';');
let result = await Self.rawStmt(sql);
result[itemsIndex].forEach(item => {
result[pricesIndex].forEach(price => {
if (item.id === price.itemFk) {
if (item.prices) {
item.prices.push(price);
} else {
item.prices = [price];
}
item.available = price.grouping;
}
});
});
return result[itemsIndex];
};
};

View File

@ -1,61 +0,0 @@
module.exports = Self => {
Self.remoteMethod('itemFilter', {
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',
http: {source: 'query'}
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/itemFilter`,
verb: 'GET'
}
});
Self.itemFilter = async filter => {
let where = filter.where;
let order = await Self.findById(where.id);
let stmt = `CALL vn2008.bionic_from_type(?, ?, ?, ?);
SELECT bi.*, i.*, w.name AS workerName, w.firstName AS workerFirstName FROM tmp.bionic_item bi
JOIN vn.item i ON i.id = bi.item_id
JOIN vn.itemType it ON it.id = i.typeFk
JOIN vn.worker w on w.id = it.workerFk
ORDER BY relevancy DESC, item_id ASC, producer DESC;
SELECT pri.*, w.name AS warehouseName FROM tmp.bionic_price pri
JOIN vn.warehouse w ON w.id = pri.warehouse_id;`;
let [rs, items, prices] = await Self.rawSql(stmt, [
order.landed,
order.address_id,
order.agency_id,
where.typeFk
]);
if (items) {
items.forEach(item => {
prices.forEach(price => {
if (item.item_id === price.item_id) {
if (item.prices) {
item.prices.push(price);
} else {
item.prices = [price];
}
item.disponible = price.grouping;
}
});
});
}
return items;
};
};

View File

@ -0,0 +1,17 @@
const app = require(`${servicesDir}/order/server/server`);
describe('order catalogFilter()', () => {
it('should return an array of items', async() => {
let filter = {
orderFk: 1,
where: {
typeFk: 1
}
};
let result = await app.models.Order.catalogFilter(filter);
let firstItemId = result[0].itemFk;
expect(result.length).toEqual(2);
expect(firstItemId).toEqual(3);
});
});

View File

@ -1,17 +0,0 @@
const app = require(`${servicesDir}/order/server/server`);
describe('order itemFilter()', () => {
it('should call the itemFilter method and return an array of items', async() => {
let filter = {
where: {
id: 1,
typeFk: 1
}
};
let result = await app.models.Order.itemFilter(filter);
let firstItemId = result[0].item_id;
expect(result.length).toEqual(2);
expect(firstItemId).toEqual(3);
});
});

View File

@ -5,5 +5,5 @@ module.exports = Self => {
require('../methods/order/getTaxes')(Self); require('../methods/order/getTaxes')(Self);
require('../methods/order/isEditable')(Self); require('../methods/order/isEditable')(Self);
require('../methods/order/getTotal')(Self); require('../methods/order/getTotal')(Self);
require('../methods/order/itemFilter')(Self); require('../methods/order/catalogFilter')(Self);
}; };