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

This commit is contained in:
Carlos Jimenez Ruiz 2021-04-07 09:30:05 +00:00
commit 2d041142c3
27 changed files with 418 additions and 43 deletions

View File

@ -1 +0,0 @@
Delete this

View File

@ -0,0 +1,26 @@
CREATE OR REPLACE DEFINER = root@`%` VIEW `vn`.`saleValue` AS
SELECT `wh`.`name` AS `warehouse`,
`c`.`name` AS `client`,
`c`.`typeFk` AS `clientTypeFk`,
`u`.`name` AS `buyer`,
`it`.`id` AS `itemTypeFk`,
`it`.`name` AS `family`,
`s`.`itemFk` AS `itemFk`,
`s`.`concept` AS `concept`,
`s`.`quantity` AS `quantity`,
`b`.`buyingValue` + `b`.`freightValue` + `b`.`comissionValue` + `b`.`packageValue` AS `cost`,
(`b`.`buyingValue` + `b`.`freightValue` + `b`.`comissionValue` + `b`.`packageValue`) * `s`.`quantity` AS `value`,
`tm`.`year` AS `year`,
`tm`.`week` AS `week`
FROM `vn`.`sale` `s`
JOIN `vn`.`item` `i` ON `i`.`id` = `s`.`itemFk`
JOIN `vn`.`itemType` `it` ON `it`.`id` = `i`.`typeFk`
JOIN `account`.`user` `u` ON `u`.`id` = `it`.`workerFk`
JOIN `vn`.`ticket` `t` ON `t`.`id` = `s`.`ticketFk`
JOIN `vn`.`client` `c` ON `c`.`id` = `t`.`clientFk`
JOIN `vn`.`warehouse` `wh` ON `wh`.`id` = `t`.`warehouseFk`
JOIN `vn`.`time` `tm` ON `tm`.`dated` = CAST(`t`.`shipped` AS DATE)
JOIN `cache`.`last_buy` `lb` ON `lb`.`item_id` = `i`.`id` AND `lb`.`warehouse_id` = `wh`.`id`
JOIN `vn`.`buy` `b` ON `b`.`id` = `lb`.`buy_id`
WHERE `wh`.`isManaged` <> 0;

View File

@ -0,0 +1,24 @@
drop procedure weekWaste;
create definer = root@`%` procedure weekWaste__()
BEGIN
DECLARE vWeek INT;
DECLARE vYear INT;
SELECT week, year
INTO vWeek, vYear
FROM vn.time
WHERE dated = DATE_ADD(CURDATE(), INTERVAL -1 WEEK);
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
sum(saleTotal) as total,
sum(saleWaste) as dwindle
FROM bs.waste
WHERE year = vYear and week = vWeek
GROUP BY buyer
) sub
ORDER BY percentage DESC;
END;

View File

@ -0,0 +1,28 @@
drop procedure weekWaste_byWorker;
create definer = root@`%` procedure weekWaste_byWorker__(IN vWorkerFk int)
BEGIN
DECLARE vWeek INT;
DECLARE vYear INT;
SELECT week, year
INTO vWeek, vYear
FROM vn.time
WHERE dated = TIMESTAMPADD(WEEK,-1,CURDATE());
SELECT *, 100 * mermas / total as porcentaje
FROM (
SELECT ws.family,
sum(ws.saleTotal) as total,
sum(ws.saleWaste) as mermas
FROM bs.waste ws
JOIN vn.worker w ON w.user = ws.buyer
WHERE year = vYear AND week = vWeek
AND w.id = vWorkerFk
GROUP BY family
) sub
ORDER BY porcentaje DESC;
END;

View File

@ -0,0 +1,25 @@
drop procedure weekWaste_getDetail;
create definer = root@`%` procedure weekWaste_getDetail__()
BEGIN
DECLARE vLastWeek DATE;
DECLARE vWeek INT;
DECLARE vYear INT;
SET vLastWeek = TIMESTAMPADD(WEEK,-1,CURDATE());
SET vYear = YEAR(vLastWeek);
SET vWeek = WEEK(vLastWeek, 1);
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
ws.family,
sum(ws.saleTotal) AS total,
sum(ws.saleWaste) AS dwindle
FROM bs.waste ws
WHERE year = vYear AND week = vWeek
GROUP BY buyer, family
) sub
ORDER BY percentage DESC;
END;

View File

@ -0,0 +1,22 @@
ALTER TABLE `bs`.`waste`
ADD itemFk INT NULL AFTER `family`;
ALTER TABLE `bs`.`waste`
ADD itemTypeFk SMALLINT(5) UNSIGNED NULL AFTER `itemFk`;
ALTER TABLE `bs`.`waste`
ADD CONSTRAINT waste_itemType_id
FOREIGN KEY (itemTypeFk) REFERENCES vn.itemType (id)
ON UPDATE CASCADE;
ALTER TABLE `bs`.`waste`
ADD CONSTRAINT waste_item_id
FOREIGN KEY (itemFk) REFERENCES vn.item (id)
ON UPDATE CASCADE;
ALTER TABLE `bs`.`waste` DROP PRIMARY KEY;
ALTER TABLE `bs`.`waste`
AD PRIMARY KEY (buyer, year, week, family, itemFk);

View File

@ -0,0 +1,38 @@
UPDATE `bs`.nightTask t SET t.`procedure` = 'waste_addSales' WHERE t.id = 54;
DROP PROCEDURE IF EXISTS `bs`.`waste_Add`;
CREATE
DEFINER = root@`%` PROCEDURE `bs`.`waste_addSales`()
BEGIN
DECLARE vWeek INT;
DECLARE vYear INT;
SELECT week, year
INTO vWeek, vYear
FROM vn.time
WHERE dated = CURDATE();
REPLACE bs.waste
SELECT *, 100 * mermas / total as porcentaje
FROM (
SELECT buyer,
year,
week,
family,
itemFk,
itemTypeFk,
floor(sum(value)) as total,
floor(sum(IF(clientTypeFk = 'loses', value, 0))) as mermas
FROM vn.saleValue
where year = vYear and week = vWeek
GROUP BY family, itemFk
) sub
ORDER BY mermas DESC;
END;

View File

@ -1300,18 +1300,23 @@ INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRa
(103, 2000, 0.00, 0.00, 0.02, 1.00),
(104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `saleTotal`, `saleWaste`, `rate`)
INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeFk`, `saleTotal`, `saleWaste`, `rate`)
VALUES
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', '1062', '51', '4.8'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Colombia', '35074', '687', '2.0'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Mini', '1777', '13', '0.7'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Short', '9182', '59', '0.6'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Containers', '-74', '0', '0.0'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Packagings', '-7', '0', '0.0'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Freight', '1100', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Funeral Accessories', '848', '-187', '-22.1'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', '186', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', '277', '0', '0.0');
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', 1, 1, '1062', '51', '4.8'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Colombia', 2, 1, '35074', '687', '2.0'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Mini', 3, 1, '1777', '13', '0.7'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation Short', 4, 1, '3182', '59', '0.6'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Crisantemo', 5, 1, '1747', '13', '0.7'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Lilium Oriental', 6, 1, '7182', '59', '0.6'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Alstroemeria', 7, 1, '1777', '13', '0.7'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Cymbidium', 1, 1, '4181', '59', '0.6'),
('CharlesXavier', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Cymbidium', 2, 1, '7268', '59', '0.6'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Containers', 2, 1, '-74', '0', '0.0'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Packagings', 3, 1, '-7', '0', '0.0'),
('DavidCharlesHaller', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Freight', 4, 1, '1100', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Funeral Accessories', 5, 1, '848', '-187', '-22.1'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', 6, 1, '186', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', 7, 1, '277', '0', '0.0');
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`producer`,`printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
VALUES

View File

@ -77,9 +77,10 @@ class Controller extends Section {
if (sampleType.hasCompany)
params.companyId = this.clientSample.companyFk;
if (isPreview) params.isPreview = true;
let query = `email/${sampleType.code}`;
if (isPreview)
query = `email/${sampleType.code}/preview`;
const query = `email/${sampleType.code}`;
this.$http.get(query, {params}).then(cb);
}
}

View File

@ -0,0 +1,69 @@
module.exports = Self => {
Self.remoteMethod('getWasteByItem', {
description: 'Returns the details of losses by worker and item',
accessType: 'READ',
accepts: [
{
arg: 'buyer',
type: 'string',
required: true,
description: 'The buyer name'
},
{
arg: 'family',
type: 'string',
required: true,
description: 'The item family name'
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/getWasteByItem`,
verb: 'GET'
}
});
Self.getWasteByItem = async(buyer, family) => {
const wastes = await Self.rawSql(`
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
ws.family,
ws.itemFk,
sum(ws.saleTotal) AS total,
sum(ws.saleWaste) AS dwindle
FROM bs.waste ws
WHERE buyer = ? AND family = ?
AND year = YEAR(TIMESTAMPADD(WEEK,-1,CURDATE()))
AND week = WEEK(TIMESTAMPADD(WEEK,-1,CURDATE()), 1)
GROUP BY buyer, itemFk
) sub
ORDER BY family, percentage DESC`, [buyer, family]);
const details = [];
for (let waste of wastes) {
const buyerName = waste.buyer;
let buyerDetail = details.find(waste => {
return waste.buyer == buyerName;
});
if (!buyerDetail) {
buyerDetail = {
buyer: buyerName,
family: waste.family,
lines: []
};
details.push(buyerDetail);
}
buyerDetail.lines.push(waste);
}
return details;
};
};

View File

@ -1,5 +1,5 @@
module.exports = Self => {
Self.remoteMethod('getWasteDetail', {
Self.remoteMethod('getWasteByWorker', {
description: 'Returns the details of losses by worker',
accessType: 'READ',
accepts: [],
@ -8,13 +8,25 @@ module.exports = Self => {
root: true
},
http: {
path: `/getWasteDetail`,
path: `/getWasteByWorker`,
verb: 'GET'
}
});
Self.getWasteDetail = async() => {
const [wastes] = await Self.rawSql(`CALL bs.weekWaste_getDetail()`);
Self.getWasteByWorker = async() => {
const wastes = await Self.rawSql(`
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
ws.family,
sum(ws.saleTotal) AS total,
sum(ws.saleWaste) AS dwindle
FROM bs.waste ws
WHERE year = YEAR(TIMESTAMPADD(WEEK,-1,CURDATE()))
AND week = WEEK(TIMESTAMPADD(WEEK,-1,CURDATE()), 1)
GROUP BY buyer, family
) sub
ORDER BY percentage DESC`);
const details = [];

View File

@ -0,0 +1,14 @@
const app = require('vn-loopback/server/server');
describe('Item getWasteByItem()', () => {
it('should check for the waste breakdown by worker and item', async() => {
const result = await app.models.Item.getWasteByItem('CharlesXavier', 'Cymbidium');
const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
expect(anyResult.buyer).toEqual('CharlesXavier');
expect(anyResult.family).toEqual('Cymbidium');
expect(anyResult.lines.length).toBeGreaterThanOrEqual(2);
});
});

View File

@ -1,8 +1,8 @@
const app = require('vn-loopback/server/server');
describe('item getWasteDetail()', () => {
describe('Item getWasteByWorker()', () => {
it('should check for the waste breakdown for every worker', async() => {
const result = await app.models.Item.getWasteDetail();
const result = await app.models.Item.getWasteByWorker();
const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))];

View File

@ -11,7 +11,8 @@ module.exports = Self => {
require('../methods/item/regularize')(Self);
require('../methods/item/getVisibleAvailable')(Self);
require('../methods/item/new')(Self);
require('../methods/item/getWasteDetail')(Self);
require('../methods/item/getWasteByWorker')(Self);
require('../methods/item/getWasteByItem')(Self);
require('../methods/item/createIntrastat')(Self);
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});

View File

@ -20,7 +20,8 @@ import './niche';
import './botanical';
import './barcode';
import './summary';
import './waste';
import './waste/index/';
import './waste/detail';
import './fixed-price';
import './fixed-price-search-panel';

View File

@ -64,3 +64,4 @@ Item diary: Registro de compra-venta
Last entries: Últimas entradas
Tags: Etiquetas
Waste breakdown: Desglose de mermas
Waste breakdown by item: Desglose de mermas por artículo

View File

@ -8,7 +8,7 @@
"main": [
{"state": "item.index", "icon": "icon-item"},
{"state": "item.request", "icon": "pan_tool"},
{"state": "item.waste", "icon": "icon-claims"},
{"state": "item.waste.index", "icon": "icon-claims"},
{"state": "item.fixedPrice", "icon": "contact_support"}
],
"card": [
@ -155,12 +155,25 @@
"acl": ["employee"]
},
{
"url" : "/waste",
"url": "/waste",
"state": "item.waste",
"component": "vn-item-waste",
"component": "ui-view",
"abstract": true
},
{
"url" : "/index",
"state": "item.waste.index",
"component": "vn-item-waste-index",
"description": "Waste breakdown",
"acl": ["buyer"]
},
{
"url" : "/detail?buyer&family",
"state": "item.waste.detail",
"component": "vn-item-waste-detail",
"description": "Waste breakdown by item",
"acl": ["buyer"]
},
{
"url" : "/fixed-price?q",
"state": "item.fixedPrice",

View File

@ -0,0 +1,42 @@
<vn-crud-model auto-load="true"
vn-id="model"
url="Items/getWasteByItem"
params="$ctrl.$params"
data="details">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card class="vn-w-md">
<section ng-repeat="detail in details" class="vn-pa-md">
<vn-horizontal class="header">
<h5>{{detail.family}} ({{detail.buyer}})</h5>
</vn-horizontal>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th shrink>Item</vn-th>
<vn-th number>Percentage</vn-th>
<vn-th number>Dwindle</vn-th>
<vn-th number>Total</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="waste in detail.lines">
<vn-td shrink>
<span
ng-click="descriptor.show($event, waste.itemFk)"
class="link">
{{::waste.itemFk}}
</span>
</vn-td>
<vn-td number>{{::(waste.percentage / 100) | percentage: 2}}</vn-td>
<vn-td number>{{::waste.dwindle | currency: 'EUR'}}</vn-td>
<vn-td number>{{::waste.total | currency: 'EUR'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</section>
</vn-card>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="descriptor">
</vn-item-descriptor-popover>

View File

@ -0,0 +1,7 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnItemWasteDetail', {
template: require('./index.html'),
controller: Section
});

View File

@ -1,13 +1,14 @@
<vn-crud-model auto-load="true"
vn-id="model"
url="Items/getWasteDetail"
url="Items/getWasteByWorker"
data="details">
</vn-crud-model>
<vn-data-viewer model="model">
<vn-card>
<section ng-repeat="detail in details" class="vn-pa-md">
<vn-horizontal class="header">
<h5><span translate>{{detail.buyer}}</span></h5>
<h5><span><span translate>{{detail.buyer}}</span></h5>
</vn-horizontal>
<vn-table>
<vn-thead>
@ -19,7 +20,8 @@
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="waste in detail.lines">
<a ng-repeat="waste in detail.lines" class="clickable vn-tr"
ui-sref="item.waste.detail({buyer: waste.buyer, family: waste.family})" >
<vn-td class="waste-family">{{::waste.family}}</vn-td>
<vn-td number>{{::(waste.percentage / 100) | percentage: 2}}</vn-td>
<vn-td number>{{::waste.dwindle | currency: 'EUR'}}</vn-td>

View File

@ -1,8 +1,8 @@
import ngModule from '../module';
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
ngModule.vnComponent('vnItemWaste', {
ngModule.vnComponent('vnItemWasteIndex', {
template: require('./index.html'),
controller: Section
});

View File

@ -0,0 +1,26 @@
@import "variables";
vn-item-waste-index,
vn-item-waste-detail {
.header {
margin-bottom: 16px;
text-transform: uppercase;
font-size: 1.25rem;
line-height: 1;
padding: 7px;
padding-bottom: 7px;
padding-bottom: 4px;
font-weight: lighter;
background-color: #fde6ca;
border-bottom: 1px solid #f7931e;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
vn-table vn-th.waste-family,
vn-table vn-td.waste-family {
max-width: 64px;
width: 64px
}
}

View File

@ -6,7 +6,7 @@ const imageSrc = {
getEmailSrc(image) {
let src = `cid:${image}`;
if (this.isPreview === 'true')
if (this.isPreview)
src = `/api/${this.$options.name}/assets/images/${image}`;
return src;

View File

@ -6,17 +6,26 @@ module.exports = app => {
const reportName = req.params.name;
const email = new Email(reportName, req.args);
if (req.args.isPreview === 'true') {
const rendered = await email.render();
await email.send();
res.send(rendered);
} else {
await email.send();
res.status(200).json({
message: 'Sent'
});
} catch (e) {
next(e);
}
});
res.status(200).json({
message: 'Sent'
});
}
app.get(`/api/email/:name/preview`, async(req, res, next) => {
try {
const reportName = req.params.name;
const args = req.args;
args.isPreview = true;
const email = new Email(reportName, args);
const rendered = await email.render();
res.send(rendered);
} catch (e) {
next(e);
}

View File

@ -1,5 +1,4 @@
const Component = require(`${appPath}/core/component`);
const db = require(`${appPath}/core/database`);
const emailHeader = new Component('email-header');
const emailFooter = new Component('email-footer');
@ -20,7 +19,7 @@ module.exports = {
},
methods: {
fetchWastes() {
return db.findOne(`CALL bs.weekWaste()`);
return this.rawSqlFromDef('wasteWeekly');
}
},
components: {

View File

@ -0,0 +1,11 @@
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
sum(saleTotal) as total,
sum(saleWaste) as dwindle
FROM bs.waste w
JOIN vn.time t ON w.year = t.year AND w.week = t.week
WHERE t.dated = DATE_ADD(CURDATE(), INTERVAL -1 WEEK)
GROUP BY buyer
) sub
ORDER BY percentage DESC;