Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4077-login_recover-password
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alex Moreno 2022-09-19 12:08:26 +02:00
commit cc57742766
15 changed files with 344 additions and 199 deletions

View File

@ -1,6 +1,6 @@
FROM mariadb:10.7.5 FROM mariadb:10.7.3
ENV MARIADB_ROOT_PASSWORD root ENV MYSQL_ROOT_PASSWORD root
ENV TZ Europe/Madrid ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
@ -22,7 +22,6 @@ COPY \
docker/docker-start.sh \ docker/docker-start.sh \
/usr/local/bin/ /usr/local/bin/
RUN chmod 775 /etc/mysql/conf.d/docker.cnf
RUN mkdir /mysql-data \ RUN mkdir /mysql-data \
&& chown -R mysql:mysql /mysql-data && chown -R mysql:mysql /mysql-data
@ -36,8 +35,6 @@ COPY \
dump/structure.sql \ dump/structure.sql \
dump/dumpedFixtures.sql \ dump/dumpedFixtures.sql \
./ ./
RUN chmod 775 config.ini
RUN gosu mysql docker-init.sh \ RUN gosu mysql docker-init.sh \
&& docker-dump.sh mysqlPlugins \ && docker-dump.sh mysqlPlugins \
&& docker-dump.sh mockDate \ && docker-dump.sh mockDate \

View File

@ -1014,9 +1014,9 @@ export default {
save: 'vn-travel-create vn-submit > button' save: 'vn-travel-create vn-submit > button'
}, },
travelExtraCommunity: { travelExtraCommunity: {
anySearchResult: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr', anySearchResult: 'vn-travel-extra-community > vn-card div > tbody > tr[ng-attr-id="{{::travel.id}}"]',
firstTravelReference: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="reference"]', firstTravelReference: 'vn-travel-extra-community tbody:nth-child(2) vn-textfield[ng-model="travel.ref"]',
firstTravelLockedKg: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="lockedKg"]', firstTravelLockedKg: 'vn-travel-extra-community tbody:nth-child(2) vn-input-number[ng-model="travel.kg"]',
removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i' removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i'
}, },
travelBasicData: { travelBasicData: {

View File

@ -19,18 +19,22 @@ describe('Travel extra community path', () => {
it('should edit the travel reference and the locked kilograms', async() => { it('should edit the travel reference and the locked kilograms', async() => {
await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter); await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter);
await page.waitForSpinnerLoad(); await page.waitForSpinnerLoad();
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference'); await page.clearInput(selectors.travelExtraCommunity.firstTravelReference);
await page.waitForSpinnerLoad(); await page.write(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelLockedKg, '1500'); await page.clearInput(selectors.travelExtraCommunity.firstTravelLockedKg);
await page.write(selectors.travelExtraCommunity.firstTravelLockedKg, '1500');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
}); });
it('should reload the index and confirm the reference and locked kg were edited', async() => { it('should reload the index and confirm the reference and locked kg were edited', async() => {
await page.accessToSection('travel.index'); await page.accessToSection('travel.index');
await page.accessToSection('travel.extraCommunity'); await page.accessToSection('travel.extraCommunity');
await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter); await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter);
await page.waitForTextInElement(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
const reference = await page.getProperty(selectors.travelExtraCommunity.firstTravelReference, 'innerText'); const reference = await page.waitToGetProperty(selectors.travelExtraCommunity.firstTravelReference, 'value');
const lockedKg = await page.getProperty(selectors.travelExtraCommunity.firstTravelLockedKg, 'innerText'); const lockedKg = await page.waitToGetProperty(selectors.travelExtraCommunity.firstTravelLockedKg, 'value');
expect(reference).toContain('edited reference'); expect(reference).toContain('edited reference');
expect(lockedKg).toContain(1500); expect(lockedKg).toContain(1500);

View File

@ -30,11 +30,24 @@ module.exports = Self => {
sum(ws.saleWaste) AS dwindle sum(ws.saleWaste) AS dwindle
FROM bs.waste ws FROM bs.waste ws
WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?)) WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?))
AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1) AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1)
GROUP BY buyer, family GROUP BY buyer, family
) sub ) sub
ORDER BY percentage DESC`, [date, date], myOptions); ORDER BY percentage DESC`, [date, date], myOptions);
const wastesTotal = await Self.rawSql(`
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
sum(ws.saleTotal) AS total,
sum(ws.saleWaste) AS dwindle
FROM bs.waste ws
WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?))
AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1)
GROUP BY buyer
) sub
ORDER BY percentage DESC`, [date, date], myOptions);
const details = []; const details = [];
for (let waste of wastes) { for (let waste of wastes) {
@ -55,6 +68,14 @@ module.exports = Self => {
buyerDetail.lines.push(waste); buyerDetail.lines.push(waste);
} }
for (let waste of details) {
let buyerTotal = wastesTotal.find(totals => {
return waste.buyer == totals.buyer;
});
Object.assign(waste, buyerTotal);
}
return details; return details;
}; };
}; };

View File

@ -12,6 +12,7 @@ describe('Item getWasteByWorker()', () => {
const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
expect(anyResult.buyer).toMatch(/(CharlesXavier|HankPym|DavidCharlesHaller)/); expect(anyResult.buyer).toMatch(/(CharlesXavier|HankPym|DavidCharlesHaller)/);
expect(anyResult.total).toBeGreaterThanOrEqual(1000);
expect(anyResult.lines.length).toBeGreaterThanOrEqual(3); expect(anyResult.lines.length).toBeGreaterThanOrEqual(3);
await tx.rollback(); await tx.rollback();

View File

@ -4,39 +4,46 @@
data="details"> data="details">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer model="model"> <vn-data-viewer model="model">
<section ng-repeat="detail in details" class="vn-pa-md"> <vn-card>
<vn-horizontal class="header"> <vn-table>
<h5><span translate>{{detail.buyer}}</span></h5> <vn-thead>
<vn-none> <vn-tr class="header">
<vn-icon <vn-th class="waste-family">Buyer</vn-th>
ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}" <vn-th class="waste-family">Family</vn-th>
class="arrow pointer" <vn-th number>Percentage</vn-th>
icon="keyboard_arrow_up" <vn-th number>Dwindle</vn-th>
vn-tooltip="Minimize/Maximize" <vn-th number>Total</vn-th>
ng-click="$ctrl.toggleHidePanel(detail)"> <vn-th shrink></vn-th>
</vn-icon> </vn-tr>
</vn-none> </vn-thead>
</vn-horizontal> <vn-tbody ng-repeat="detail in details" class="vn-mb-md">
<vn-card ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}"> <vn-tr class="header">
<vn-table> <vn-td>{{::detail.buyer}}</vn-td>
<vn-thead> <vn-td>{{::detail.family}}</vn-td>
<vn-tr> <vn-td number>{{::(detail.percentage / 100) | percentage: 2}}</vn-td>
<vn-th class="waste-family">Family</vn-th> <vn-td number>{{::detail.dwindle | currency: 'EUR'}}</vn-td>
<vn-th number>Percentage</vn-th> <vn-td number>{{::detail.total | currency: 'EUR'}}</vn-td>
<vn-th number>Dwindle</vn-th> <vn-td shrink>
<vn-th number>Total</vn-th> <vn-icon-button
</vn-tr> ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}"
</vn-thead> class="arrow pointer"
<vn-tbody> icon="keyboard_arrow_up"
<a ng-repeat="waste in detail.lines" class="clickable vn-tr" vn-tooltip="Minimize/Maximize"
ui-sref="item.waste.detail({buyer: waste.buyer, family: waste.family})"> ng-click="$ctrl.toggleHidePanel(detail)">
<vn-td class="waste-family">{{::waste.family}}</vn-td> </vn-icon-button>
<vn-td number>{{::(waste.percentage / 100) | percentage: 2}}</vn-td> </vn-td>
<vn-td number>{{::waste.dwindle | currency: 'EUR'}}</vn-td> </vn-tr>
<vn-td number>{{::waste.total | currency: 'EUR'}}</vn-td> <vn-tr ng-repeat="waste in detail.lines" class="clickable vn-tr"
</vn-tr> ui-sref="item.waste.detail({buyer: waste.buyer, family: waste.family})"
</vn-tbody> ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}">
</vn-table> <vn-td></vn-td>
</vn-card> <vn-td>{{::waste.family}}</vn-td>
</section> <vn-td number>{{::(waste.percentage / 100) | percentage: 2}}</vn-td>
</vn-data-viewer> <vn-td number>{{::waste.dwindle | currency: 'EUR'}}</vn-td>
<vn-td number>{{::waste.total | currency: 'EUR'}}</vn-td>
<vn-td shrink></vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>

View File

@ -5,20 +5,9 @@ vn-item-waste-index,
vn-item-waste-detail { vn-item-waste-detail {
.header { .header {
padding: 12px 0 5px 0; padding: 12px 0 5px 0;
color: gray; background-color: $color-bg;
font-size: 1.2rem; font-size: 1.2rem;
border-bottom: $border;
margin-bottom: 10px; margin-bottom: 10px;
& > vn-none > vn-icon {
@extend %clickable-light;
color: $color-button;
font-size: 1.4rem;
}
vn-none > .arrow {
transition: transform 200ms;
}
} }
vn-table vn-th.waste-family, vn-table vn-th.waste-family,
@ -26,12 +15,13 @@ vn-item-waste-detail {
max-width: 64px; max-width: 64px;
width: 64px width: 64px
} }
.hidden { .hidden {
display: none; display: none;
} }
.header > vn-none > .arrow.hidden {
.arrow.hidden {
display: block; display: block;
transform: rotate(180deg); transform: rotate(180deg);
} }
} }

View File

@ -50,8 +50,10 @@
}, },
"scope": { "scope": {
"where": { "where": {
"isActive": true "isActive": {
} "neq": false
}
}
}, },
"acls": [ "acls": [
{ {

View File

@ -128,7 +128,7 @@ module.exports = Self => {
w.name AS warehouseInFk, w.name AS warehouseInFk,
w.name AS warehouseInName, w.name AS warehouseInName,
SUM(b.stickers) AS stickers, SUM(b.stickers) AS stickers,
s.id AS supplierFk, s.id AS cargoSupplierFk,
s.nickname AS cargoSupplierNickname, s.nickname AS cargoSupplierNickname,
CAST(SUM(i.density * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as loadedKg, CAST(SUM(i.density * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as loadedKg,
CAST(SUM(167.5 * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as volumeKg CAST(SUM(167.5 * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as volumeKg
@ -160,13 +160,14 @@ module.exports = Self => {
e.travelFk, e.travelFk,
e.ref, e.ref,
e.loadPriority, e.loadPriority,
s.id AS supplierFk,
s.name AS supplierName, s.name AS supplierName,
SUM(b.stickers) AS stickers, SUM(b.stickers) AS stickers,
e.evaNotes, e.evaNotes,
e.notes, e.notes,
CAST(SUM(i.density * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as loadedkg, CAST(SUM(i.density * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as loadedkg,
CAST(SUM(167.5 * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as volumeKg CAST(SUM(167.5 * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as volumeKg
FROM tmp.travel tr FROM tmp.travel tr
JOIN entry e ON e.travelFk = tr.id JOIN entry e ON e.travelFk = tr.id
JOIN buy b ON b.entryFk = e.id JOIN buy b ON b.entryFk = e.id
JOIN packaging pkg ON pkg.id = b.packageFk JOIN packaging pkg ON pkg.id = b.packageFk

View File

@ -50,15 +50,15 @@
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete vn-one
label="Warehouse In" label="Warehouse Out"
ng-model="filter.warehouseInFk" ng-model="filter.warehouseOutFk"
url="Warehouses" url="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete vn-one <vn-autocomplete vn-one
label="Warehouse Out" label="Warehouse In"
ng-model="filter.warehouseOutFk" ng-model="filter.warehouseInFk"
url="Warehouses" url="Warehouses"
show-field="name" show-field="name"
value-field="id"> value-field="id">
@ -84,4 +84,4 @@
<vn-submit label="Search"></vn-submit> <vn-submit label="Search"></vn-submit>
</vn-horizontal> </vn-horizontal>
</form> </form>
</div> </div>

View File

@ -1,6 +1,7 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="Travels/extraCommunityFilter" url="Travels/extraCommunityFilter"
filter="::$ctrl.filter"
data="travels" data="travels"
order="shipped ASC, landed ASC, travelFk, loadPriority, agencyModeFk, evaNotes" order="shipped ASC, landed ASC, travelFk, loadPriority, agencyModeFk, evaNotes"
limit="20" limit="20"
@ -18,112 +19,174 @@
model="model"> model="model">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-data-viewer model="model" class="travel-list"> <vn-card class="travel-list scrollable">
<vn-card> <smart-table
<section class="vn-pa-md"> model="model"
<vn-tool-bar class="vn-mb-md"> options="$ctrl.smartTableOptions">
<vn-button disabled="!$ctrl.hasDateRange" <slot-actions>
icon="picture_as_pdf" <section>
ng-click="$ctrl.showReport()" <vn-tool-bar class="vn-mb-md">
vn-tooltip="Open as PDF"> <vn-button
</vn-button> disabled="!$ctrl.hasDateRange"
</vn-tool-bar> icon="picture_as_pdf"
<vn-table> ng-click="$ctrl.showReport()"
<vn-thead> vn-tooltip="Open as PDF">
<vn-tr> </vn-button>
<vn-th shrink>Id</vn-th> </vn-tool-bar>
<vn-th expand>Supplier</vn-th> </section>
<vn-th expand>Freighter</vn-th> </slot-actions>
<vn-th>Reference</vn-th> <slot-table>
<vn-th number>Packages</vn-th> <table>
<vn-th number>Bl. KG</vn-th> <thead>
<vn-th number>Phy. KG</vn-th> <tr>
<vn-th number>Vol. KG</vn-th> <th field="id" shrink>
<vn-th expand translate-attr="{title: 'Warehouse Out'}"> <span translate>Id</span>
Wh. Out </th>
</vn-th> <th field="cargoSupplierFk" expand>
<vn-th expand>W. Shipped</vn-th> <span translate>Supplier</span>
<vn-th expand translate-attr="{title: 'Warehouse In'}"> </th>
Wh. In <th field="agencyModeFk" expand>
</vn-th> <span translate>Agency</span>
<vn-th expand>W. Landed</vn-th> </th>
</vn-tr> <th field="ref">
</vn-thead> <span translate>Reference</span>
<vn-tbody ng-repeat="travel in travels" class="vn-mb-md" vn-droppable="$ctrl.onDrop($event)" ng-attr-id="{{::travel.id}}"> </th>
<vn-tr class="header"> <th field="stickers" number>
<vn-td> <span translate>Packages</span>
<span class="link" </th>
<th field="kg" number>
<span translate>Bl. KG</span>
</th>
<th field="loadedKg" number>
<span translate>Phy. KG</span>
</th>
<th field="volumeKg" number>
<span translate>Vol. KG</span>
</th>
<th
field="warehouseOutFk"
translate-attr="{title: 'Warehouse Out'}">
<span translate>Wh. Out</span>
</th>
<th field="shipped">
<span translate>W. Shipped</span>
</th>
<th
field="warehouseInFk"
translate-attr="{title: 'Warehouse In'}">
<span translate>Wh. In</span>
</th>
<th field="landed">
<span translate>W. Landed</span>
</th>
</tr>
</thead>
<tbody
ng-repeat="travel in travels"
class="vn-mb-md"
vn-droppable="$ctrl.onDrop($event)"
ng-attr-id="{{::travel.id}}"
vn-stop-click>
<tr
class="header"
vn-anchor="::{
state: 'travel.card.basicData',
params: {id: travel.id}
}">
<td vn-click-stop>
<span
class="link"
ng-click="travelDescriptor.show($event, travel.id)"> ng-click="travelDescriptor.show($event, travel.id)">
{{::travel.id}} {{::travel.id}}
</span> </span>
</vn-td> </td>
<vn-td expand>{{::travel.agencyModeName}}</vn-td> <td expand vn-click-stop>
<vn-td expand>{{::travel.cargoSupplierNickname}}</vn-td> <span
<vn-td-editable name="reference" expand> class="link"
<text>{{travel.ref}}</text> ng-click="supplierDescriptor.show($event, travel.cargoSupplierFk)">
<field> {{::travel.cargoSupplierNickname}}
<vn-textfield class="dense" vn-focus </span>
ng-model="travel.ref" </td>
on-change="$ctrl.save(travel.id, {ref: value})"> <td expand>{{::travel.agencyModeName}}</td>
</vn-textfield> <td
</field> name="reference"
</vn-td-editable> expand
<vn-td number>{{::travel.stickers}}</vn-td> vn-click-stop>
<vn-td-editable name="lockedKg" expand style="text-align: right"> <vn-textfield
<text number>{{travel.kg}}</text> class="dense td-editable"
<field> ng-model="travel.ref"
<vn-input-number class="dense" vn-focus on-change="$ctrl.save(travel.id, {ref: value})">
ng-model="travel.kg" </vn-textfield>
on-change="$ctrl.save(travel.id, {kg: value})" </vn-icon>
min="0"> </td>
</vn-input-number> <td number>{{::travel.stickers}}</td>
</field> <td
</vn-td-editable> name="lockedKg"
<vn-td number>{{::travel.loadedKg}}</vn-td> expand
<vn-td number>{{::travel.volumeKg}}</vn-td> vn-click-stop>
<vn-td expand>{{::travel.warehouseOutName}}</vn-td> <vn-input-number
<vn-td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</vn-td> number
<vn-td expand>{{::travel.warehouseInName}}</vn-td> class="td-editable number"
<vn-td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</vn-td> ng-model="travel.kg"
</vn-tr> on-change="$ctrl.save(travel.id, {kg: value})"
<a href="#" ng-repeat="entry in travel.entries" class="vn-tr" draggable min="0">
</vn-input-number>
</td>
<td number>{{::travel.loadedKg}}</td>
<td number>{{::travel.volumeKg}}</td>
<td expand>{{::travel.warehouseOutName}}</td>
<td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</td>
<td expand>{{::travel.warehouseInName}}</td>
<td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</td>
</tr>
<tr
ng-repeat="entry in travel.entries"
draggable
ng-attr-id="{{::entry.id}}" ng-attr-id="{{::entry.id}}"
ng-click="$event.preventDefault()"> ng-click="$event.preventDefault()">
<vn-td> <td>
<span class="link" <span class="link"
ng-click="entryDescriptor.show($event, entry.id)"> ng-click="entryDescriptor.show($event, entry.id)">
{{::entry.id}} {{::entry.id}}
</span> </span>
</vn-td> </td>
<vn-td>{{::entry.supplierName}}</vn-td> <td>
<vn-td></vn-td> <span
<vn-td expand>{{::entry.ref}}</vn-td> class="link"
<vn-td number>{{::entry.stickers}}</vn-td> ng-click="supplierDescriptor.show($event, entry.supplierFk)">
<vn-td number></vn-td> {{::entry.supplierName}}
<vn-td number>{{::entry.loadedkg}}</vn-td> </span>
<vn-td number>{{::entry.volumeKg}}</vn-td> </td>
<vn-td> <td></td>
<td expand>{{::entry.ref}}</td>
<td number>{{::entry.stickers}}</td>
<td number></td>
<td number>{{::entry.loadedkg}}</td>
<td number>{{::entry.volumeKg}}</td>
<td>
<span ng-if="::entry.notes" vn-tooltip="{{::entry.notes}}"> <span ng-if="::entry.notes" vn-tooltip="{{::entry.notes}}">
{{::entry.notes}} {{::entry.notes}}
</span> </span>
</vn-td> </td>
<vn-td> <td>
<span ng-if="::entry.evaNotes" vn-tooltip="{{::entry.evaNotes}}"> <span ng-if="::entry.evaNotes" vn-tooltip="{{::entry.evaNotes}}">
{{::entry.evaNotes}} {{::entry.evaNotes}}
</span> </span>
</vn-td> </td>
<vn-td></vn-td> <td></td>
<vn-td></vn-td> <td></td>
</a> </tr>
</vn-tbody> </tbody>
</vn-table> </table>
</section> </slot-table>
</vn-card> </smart-table>
</vn-data-viewer> </vn-card>
<vn-travel-descriptor-popover <vn-travel-descriptor-popover
vn-id="travelDescriptor"> vn-id="travelDescriptor">
</vn-travel-descriptor-popover> </vn-travel-descriptor-popover>
<vn-entry-descriptor-popover <vn-entry-descriptor-popover
vn-id="entryDescriptor"> vn-id="entryDescriptor">
</vn-entry-descriptor-popover> </vn-entry-descriptor-popover>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>

View File

@ -14,8 +14,15 @@ class Controller extends Section {
draggable.addEventListener('dragend', draggable.addEventListener('dragend',
event => this.dragEnd(event)); event => this.dragEnd(event));
this.draggableElement = 'a[draggable]'; draggable.addEventListener('dragover',
this.droppableElement = 'vn-tbody[vn-droppable]'; event => this.dragOver(event));
draggable.addEventListener('dragenter',
event => this.dragEnter(event));
draggable.addEventListener('dragleave',
event => this.dragLeave(event));
this.draggableElement = 'tr[draggable]';
this.droppableElement = 'tbody[vn-droppable]';
const twoDays = 2; const twoDays = 2;
const shippedFrom = new Date(); const shippedFrom = new Date();
@ -32,6 +39,8 @@ class Controller extends Section {
landedTo: landedTo, landedTo: landedTo,
continent: 'AM' continent: 'AM'
}; };
this.smartTableOptions = {};
} }
get hasDateRange() { get hasDateRange() {
@ -44,6 +53,15 @@ class Controller extends Section {
return hasLanded || hasShipped || hasContinent || hasWarehouseOut; return hasLanded || hasShipped || hasContinent || hasWarehouseOut;
} }
onDragInterval() {
if (this.dragClientY > 0 && this.dragClientY < 75)
this.$window.scrollTo(0, this.$window.scrollY - 10);
const maxHeight = window.screen.availHeight - (window.outerHeight - window.innerHeight);
if (this.dragClientY > maxHeight - 75 && this.dragClientY < maxHeight)
this.$window.scrollTo(0, this.$window.scrollY + 10);
}
findDraggable($event) { findDraggable($event) {
const target = $event.target; const target = $event.target;
const draggable = target.closest(this.draggableElement); const draggable = target.closest(this.draggableElement);
@ -65,6 +83,7 @@ class Controller extends Section {
const id = parseInt(draggable.id); const id = parseInt(draggable.id);
this.entryId = id; this.entryId = id;
this.entry = draggable; this.entry = draggable;
this.interval = setInterval(() => this.onDragInterval(), 50);
} }
dragEnd($event) { dragEnd($event) {
@ -72,6 +91,8 @@ class Controller extends Section {
draggable.classList.remove('dragging'); draggable.classList.remove('dragging');
this.entryId = null; this.entryId = null;
this.entry = null; this.entry = null;
clearInterval(this.interval);
} }
onDrop($event) { onDrop($event) {
@ -91,6 +112,37 @@ class Controller extends Section {
} }
} }
undrop() {
if (!this.dropping) return;
this.dropping.classList.remove('dropping');
this.dropping = null;
}
dragOver($event) {
this.dragClientY = $event.clientY;
$event.preventDefault();
}
dragEnter($event) {
let element = this.findDroppable($event);
if (element) this.dropCount++;
if (element != this.dropping) {
this.undrop();
if (element) element.classList.add('dropping');
this.dropping = element;
}
}
dragLeave($event) {
let element = this.findDroppable($event);
if (element) {
this.dropCount--;
if (this.dropCount == 0) this.undrop();
}
}
save(id, data) { save(id, data) {
const endpoint = `Travels/${id}`; const endpoint = `Travels/${id}`;
this.$http.patch(endpoint, data) this.$http.patch(endpoint, data)

View File

@ -27,7 +27,7 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('findDraggable()', () => { describe('findDraggable()', () => {
it('should find the draggable element', () => { it('should find the draggable element', () => {
const draggable = document.createElement('a'); const draggable = document.createElement('tr');
draggable.setAttribute('draggable', true); draggable.setAttribute('draggable', true);
const $event = new Event('dragstart'); const $event = new Event('dragstart');
@ -43,7 +43,7 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('findDroppable()', () => { describe('findDroppable()', () => {
it('should find the droppable element', () => { it('should find the droppable element', () => {
const droppable = document.createElement('vn-tbody'); const droppable = document.createElement('tbody');
droppable.setAttribute('vn-droppable', true); droppable.setAttribute('vn-droppable', true);
const $event = new Event('drop'); const $event = new Event('drop');
@ -58,9 +58,9 @@ describe('Travel Component vnTravelExtraCommunity', () => {
}); });
describe('dragStart()', () => { describe('dragStart()', () => {
it(`should add the class "dragging" to the draggable element it(`should add the class "dragging" to the draggable element
and then set the entryId controller property`, () => { and then set the entryId controller property`, () => {
const draggable = document.createElement('a'); const draggable = document.createElement('tr');
draggable.setAttribute('draggable', true); draggable.setAttribute('draggable', true);
draggable.setAttribute('id', 3); draggable.setAttribute('id', 3);
@ -78,9 +78,9 @@ describe('Travel Component vnTravelExtraCommunity', () => {
}); });
describe('dragEnd()', () => { describe('dragEnd()', () => {
it(`should remove the class "dragging" from the draggable element it(`should remove the class "dragging" from the draggable element
and then set the entryId controller property to null`, () => { and then set the entryId controller property to null`, () => {
const draggable = document.createElement('a'); const draggable = document.createElement('tr');
draggable.setAttribute('draggable', true); draggable.setAttribute('draggable', true);
draggable.setAttribute('id', 3); draggable.setAttribute('id', 3);
draggable.classList.add('dragging'); draggable.classList.add('dragging');
@ -100,13 +100,13 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('onDrop()', () => { describe('onDrop()', () => {
it('should make an HTTP patch query', () => { it('should make an HTTP patch query', () => {
const droppable = document.createElement('vn-tbody'); const droppable = document.createElement('tbody');
droppable.setAttribute('vn-droppable', true); droppable.setAttribute('vn-droppable', true);
droppable.setAttribute('id', 1); droppable.setAttribute('id', 1);
jest.spyOn(controller, 'findDroppable').mockReturnValue(droppable); jest.spyOn(controller, 'findDroppable').mockReturnValue(droppable);
const oldDroppable = document.createElement('vn-tbody'); const oldDroppable = document.createElement('tbody');
oldDroppable.setAttribute('vn-droppable', true); oldDroppable.setAttribute('vn-droppable', true);
const entry = document.createElement('div'); const entry = document.createElement('div');
oldDroppable.appendChild(entry); oldDroppable.appendChild(entry);

View File

@ -15,41 +15,44 @@ vn-travel-extra-community {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
cursor: pointer;
} }
vn-td-editable text { table[vn-droppable] {
background-color: transparent;
padding: 0;
border: 0;
border-bottom: 1px dashed $color-active;
border-radius: 0;
color: $color-active
}
vn-td-editable:hover text:after {
font-family: 'Material Icons';
content: 'edit';
position: absolute;
right: -15px;
color: $color-spacer
}
vn-table[vn-droppable] {
border-radius: 0; border-radius: 0;
} }
a[draggable] { tr[draggable] {
transition: all .5s; transition: all .5s;
cursor: move; cursor: move;
overflow: auto;
outline: 0; outline: 0;
height: 65px;
pointer-events: fill;
user-select:all;
} }
a[draggable]:hover { tr[draggable] *::selection{
background-color: $color-hover-cd background-color: transparent;
} }
a[draggable].dragging { tr[draggable]:hover {
background-color: $color-success-light; background-color: $color-hover-cd;
font-weight:bold
} }
}
tr[draggable].dragging {
background-color: $color-primary-light;
color: $color-font-light;
font-weight:bold;
}
.td-editable{
input{
font-size: 1.25rem!important;
}
}
.number *{
text-align: right;
}
}

View File

@ -1,6 +1,10 @@
{ {
"name": "ZoneExclusion", "name": "ZoneExclusion",
"base": "VnModel", "base": "Loggable",
"log": {
"model":"ZoneLog",
"relation": "zone"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "zoneExclusion" "table": "zoneExclusion"