Merge branch 'dev' into 5677-fallo-saveSign
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alexandre Riera 2023-05-24 10:18:26 +00:00
commit 133c5dbca5
21 changed files with 300 additions and 48 deletions

View File

@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador'
### Fixed
-
- (Tickets -> Líneas) Se permite hacer split de líneas al mismo ticket

View File

@ -0,0 +1,127 @@
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT)
BEGIN
/**
* Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar.
*
* @param vDateFuture Fecha de los tickets que se quieren adelantar.
* @param vDateToAdvance Fecha a cuando se quiere adelantar.
* @param vWarehouseFk Almacén
*/
DECLARE vDateInventory DATE;
SELECT inventoried INTO vDateInventory FROM config;
DROP TEMPORARY TABLE IF EXISTS tmp.stock;
CREATE TEMPORARY TABLE tmp.stock
(itemFk INT PRIMARY KEY,
amount INT)
ENGINE = MEMORY;
INSERT INTO tmp.stock(itemFk, amount)
SELECT itemFk, SUM(quantity) amount FROM
(
SELECT itemFk, quantity
FROM itemTicketOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM itemEntryIn
WHERE landed >= vDateInventory
AND landed < vDateFuture
AND isVirtualStock = FALSE
AND warehouseInFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM itemEntryOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseOutFk = vWarehouseFk
) t
GROUP BY itemFk HAVING amount != 0;
DROP TEMPORARY TABLE IF EXISTS tmp.filter;
CREATE TEMPORARY TABLE tmp.filter
(INDEX (id))
SELECT
origin.ticketFk futureId,
dest.ticketFk id,
dest.state,
origin.futureState,
origin.futureIpt,
dest.ipt,
origin.workerFk,
origin.futureLiters,
origin.futureLines,
dest.shipped,
origin.shipped futureShipped,
dest.totalWithVat,
origin.totalWithVat futureTotalWithVat,
dest.agency,
origin.futureAgency,
dest.lines,
dest.liters,
origin.futureLines - origin.hasStock AS notMovableLines,
(origin.futureLines = origin.hasStock) AS isFullMovable,
origin.classColor,
dest.classColor futureClassColor
FROM (
SELECT
s.ticketFk,
t.workerFk,
t.shipped,
t.totalWithVat,
st.name futureState,
t.addressFk,
am.name futureAgency,
count(s.id) futureLines,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt,
CAST(SUM(litros) AS DECIMAL(10,0)) futureLiters,
SUM((s.quantity <= IFNULL(st.amount,0))) hasStock,
st.classColor
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN saleVolume sv ON sv.saleFk = s.id
JOIN item i ON i.id = s.itemFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state st ON st.id = ts.stateFk
JOIN agencyMode am ON t.agencyModeFk = am.id
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
LEFT JOIN tmp.stock st ON st.itemFk = i.id
WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture)
AND t.warehouseFk = vWarehouseFk
GROUP BY t.id
) origin
JOIN (
SELECT
t.id ticketFk,
t.addressFk,
st.name state,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt,
t.shipped,
t.totalWithVat,
am.name agency,
CAST(SUM(litros) AS DECIMAL(10,0)) liters,
CAST(COUNT(*) AS DECIMAL(10,0)) `lines`,
st.classColor
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN saleVolume sv ON sv.saleFk = s.id
JOIN item i ON i.id = s.itemFk
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state st ON st.id = ts.stateFk
JOIN agencyMode am ON t.agencyModeFk = am.id
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
WHERE t.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance)
AND t.warehouseFk = vWarehouseFk
AND st.order <= 5
GROUP BY t.id
) dest ON dest.addressFk = origin.addressFk
WHERE origin.hasStock != 0;
DROP TEMPORARY TABLE tmp.stock;
END$$
DELIMITER ;

View File

@ -0,0 +1,72 @@
DELIMITER $$
$$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canbePostponed`(vOriginDated DATE, vFutureDated DATE, vWarehouseFk INT)
BEGIN
/**
* Devuelve un listado de tickets susceptibles de fusionarse con otros tickets en el futuro
*
* @param vOriginDated Fecha en cuestión
* @param vFutureDated Fecha en el futuro a sondear
* @param vWarehouseFk Identificador de vn.warehouse
*/
DROP TEMPORARY TABLE IF EXISTS tmp.filter;
CREATE TEMPORARY TABLE tmp.filter
(INDEX (id))
SELECT sv.ticketFk id,
sub2.id futureId,
GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt,
CAST(sum(litros) AS DECIMAL(10,0)) liters,
CAST(count(*) AS DECIMAL(10,0)) `lines`,
st.name state,
sub2.iptd futureIpt,
sub2.state futureState,
t.clientFk,
t.warehouseFk,
ts.alertLevel,
t.shipped,
sub2.shipped futureShipped,
t.workerFk,
st.code stateCode,
sub2.code futureStateCode,
st.classColor
FROM vn.saleVolume sv
JOIN vn.sale s ON s.id = sv.saleFk
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.ticket t ON t.id = sv.ticketFk
JOIN vn.address a ON a.id = t.addressFk
JOIN vn.province p ON p.id = a.provinceFk
JOIN vn.country c ON c.id = p.countryFk
JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.state st ON st.id = ts.stateFk
JOIN vn.alertLevel al ON al.id = ts.alertLevel
LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id
LEFT JOIN (
SELECT *
FROM (
SELECT
t.addressFk,
t.id,
t.shipped,
st.name state,
st.code code,
GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) iptd
FROM vn.ticket t
JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.state st ON st.id = ts.stateFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
WHERE t.shipped BETWEEN vFutureDated
AND util.dayend(vFutureDated)
AND t.warehouseFk = vWarehouseFk
GROUP BY t.id
) sub
GROUP BY sub.addressFk
) sub2 ON sub2.addressFk = t.addressFk AND t.id != sub2.id
WHERE t.shipped BETWEEN vOriginDated AND util.dayend(vOriginDated)
AND t.warehouseFk = vWarehouseFk
AND al.code = 'FREE'
AND tp.ticketFk IS NULL
GROUP BY sv.ticketFk
HAVING futureId;
END$$
DELIMITER ;

View File

@ -0,0 +1,20 @@
-- vn.defaulter source
CREATE OR REPLACE
ALGORITHM = UNDEFINED VIEW `vn`.`defaulter` AS
select
`d`.`clientFk` AS `clientFk`,
`d`.`created` AS `created`,
`d`.`amount` AS `amount`,
`d`.`defaulterSinced` AS `defaulterSinced`,
`d`.`hasChanged` AS `hasChanged`,
`c`.`countryFk` AS `country`,
`c`.`payMethodFk` AS `payMethod`
from
(((`bs`.`defaulter` `d`
join `vn`.`client` `c` on
(`c`.`id` = `d`.`clientFk`))
join `vn`.`country` `co` on
(`co`.`id` = `c`.`countryFk`))
join `vn`.`payMethod` `pm` on
(`pm`.`id` = `c`.`payMethodFk`));

View File

@ -313,7 +313,7 @@ export default {
anyClient: 'vn-client-defaulter tbody > tr',
firstClientName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(2) > span',
firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(3) > span',
firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]',
firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(8) > vn-textarea[ng-model="defaulter.observation"]',
allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check',
addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]',
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',

View File

@ -203,7 +203,7 @@ export default class Searchbar extends Component {
doSearch(filter, source) {
if (filter === this.filter && !this.isIndex) return;
let promise = this.onSearch({$params: filter});
let promise = this.onSearch({$params: filter}, source);
promise = promise || this.$q.resolve();
promise.then(data => this.onFilter(filter, source, data));
this.toBar(filter);
@ -259,12 +259,6 @@ export default class Searchbar extends Component {
this.filter = filter;
if (source == 'removeBar') {
delete params[this.toRemove];
delete this.model.userParams[this.toRemove];
this.model.refresh();
}
if (!filter && this.model)
this.model.clear();
if (source != 'state')
@ -279,7 +273,7 @@ export default class Searchbar extends Component {
return {id: params.$row.id};
}
onSearch(args) {
onSearch(args, source) {
if (!this.model) return;
let filter = args.$params;
@ -325,6 +319,12 @@ export default class Searchbar extends Component {
for (let param in stateFilter.tableQ)
params[param] = stateFilter.tableQ[param];
if (source == 'removeBar') {
delete params[this.toRemove];
delete this.model.userParams[this.toRemove];
delete stateFilter[this.toRemove];
}
const newParams = Object.assign(stateFilter, params);
return this.model.applyParams(newParams)
.then(() => this.model.data);

View File

@ -197,7 +197,7 @@ describe('Component vnSearchbar', () => {
controller.doSearch(filter, 'any');
$scope.$apply();
expect(controller.onSearch).toHaveBeenCalledWith({$params: filter});
expect(controller.onSearch).toHaveBeenCalledWith({$params: filter}, 'any');
expect(controller.onFilter).toHaveBeenCalledWith(filter, 'any', undefined);
});
});

View File

@ -67,9 +67,13 @@ module.exports = Self => {
uw.id workerFk,
uw.name workerName,
c.creditInsurance,
d.defaulterSinced
d.defaulterSinced,
cn.country,
pm.name payMethod
FROM vn.defaulter d
JOIN vn.client c ON c.id = d.clientFk
JOIN vn.country cn ON cn.id = c.countryFk
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
LEFT JOIN vn.clientObservation co ON co.clientFk = c.id
LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN account.user uw ON uw.id = co.workerFk

View File

@ -29,6 +29,16 @@
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
},
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "country"
},
"payMethod": {
"type": "belongsTo",
"model": "PayMethod",
"foreignKey": "payMethod"
}
}
}

View File

@ -57,6 +57,13 @@
<th field="salesPersonFk">
<span translate>Comercial</span>
</th>
<th field="country">
<span translate>Country</span>
</th>
<th field="payMethod"
vn-tooltip="Pay Method">
<span translate>P.Method</span>
</th>
<th
field="amount"
vn-tooltip="Balance due">
@ -111,6 +118,12 @@
{{::defaulter.salesPersonName | dashIfEmpty}}
</span>
</td>
<td>
{{::defaulter.country}}
</td>
<td>
{{::defaulter.payMethod}}
</td>
<td>{{::defaulter.amount | currency: 'EUR': 2}}</td>
<td>
<span

View File

@ -20,8 +20,7 @@ export default class Controller extends Section {
showField: 'name',
valueField: 'id'
}
},
{
}, {
field: 'salesPersonFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
@ -30,6 +29,18 @@ export default class Controller extends Section {
showField: 'name',
valueField: 'id',
}
}, {
field: 'country',
autocomplete: {
showField: 'country',
valueField: 'country'
}
}, {
field: 'payMethodFk',
autocomplete: {
showField: 'name',
valueField: 'id'
}
},
{
field: 'workerFk',
@ -132,7 +143,7 @@ export default class Controller extends Section {
sendMail() {
const params = {
defaulters: this.checked,
observation: this.defaulter.observation
observation: this.defaulter.observation,
};
this.$http.post(`Defaulters/observationEmail`, params);
}
@ -143,6 +154,8 @@ export default class Controller extends Section {
case 'amount':
case 'clientFk':
case 'workerFk':
case 'country':
case 'payMethod':
case 'salesPersonFk':
return {[`d.${param}`]: value};
case 'created':

View File

@ -9,3 +9,6 @@ Search client: Buscar clientes
Worker who made the last observation: Trabajador que ha realizado la última observación
Email sended!: Email enviado!
Observation saved!: Observación añadida!
P.Method: F.Pago
Pay Method: Forma de Pago
Country: Pais

View File

@ -78,7 +78,7 @@ module.exports = Self => {
const saleIds = sales.map(sale => sale.id);
const hasClaimedSales = await models.ClaimBeginning.findOne({where: {saleFk: {inq: saleIds}}});
if (hasClaimedSales)
if (ticketId != id && hasClaimedSales)
throw new UserError(`Can't transfer claimed sales`);
for (const sale of sales) {

View File

@ -122,7 +122,7 @@
<td>{{::ticket.ipt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.state)}}">
class="chip {{ticket.classColor}}">
{{::ticket.state | dashIfEmpty}}
</span>
</td>
@ -150,7 +150,7 @@
<td>{{::ticket.futureIpt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.futureState)}}">
class="chip {{ticket.classColor}}">
{{::ticket.futureState | dashIfEmpty}}
</span>
</td>

View File

@ -137,7 +137,7 @@
<td>{{::ticket.ipt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.state)}}">
class="chip {{ticket.classColor}}">
{{::ticket.state}}
</span>
</td>
@ -158,7 +158,7 @@
<td>{{::ticket.futureIpt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.futureState)}}">
class="chip {{ticket.classColor}}">
{{::ticket.futureState}}
</span>
</td>

View File

@ -95,13 +95,6 @@ export default class Controller extends Section {
return checkedLines;
}
stateColor(state) {
if (state === 'OK')
return 'success';
else if (state === 'Libre')
return 'notice';
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);

View File

@ -60,24 +60,6 @@ describe('Component vnTicketFuture', () => {
});
});
describe('stateColor()', () => {
it('should return success to the OK tickets', () => {
const ok = controller.stateColor(controller.$.model.data[0].state);
const notOk = controller.stateColor(controller.$.model.data[1].state);
expect(ok).toEqual('success');
expect(notOk).not.toEqual('success');
});
it('should return success to the FREE tickets', () => {
const notFree = controller.stateColor(controller.$.model.data[0].state);
const free = controller.stateColor(controller.$.model.data[1].state);
expect(free).toEqual('notice');
expect(notFree).not.toEqual('notice');
});
});
describe('dateRange()', () => {
it('should return two dates with the hours at the start and end of the given date', () => {
const dateRange = controller.dateRange(today);

View File

@ -16,4 +16,4 @@
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>
<ui-view></ui-view>

View File

@ -1,5 +1,6 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
const UserError = require('vn-loopback/util/user-error');
export default class Ticket extends ModuleMain {
fetchParams($params) {
@ -14,10 +15,19 @@ export default class Ticket extends ModuleMain {
'scopeDays'
];
const seachPanelParams = Object.entries($params);
const hasFromParam = seachPanelParams.some(subarray => subarray.length > 0 && subarray[0] === 'from');
const hasToParam = seachPanelParams.some(subarray => subarray.length > 0 && subarray[0] === 'to');
if ((hasFromParam && !hasToParam) || (!hasFromParam && hasToParam))
throw new UserError(`Date range must have 'from' and 'to'`);
const hasExcludedParams = excludedParams.some(param => {
return $params && $params[param] != undefined;
});
const hasParams = Object.entries($params).length;
if (!hasParams || !hasExcludedParams)
$params.scopeDays = 1;
@ -28,7 +38,6 @@ export default class Ticket extends ModuleMain {
const to = new Date(from.getTime());
to.setDate(to.getDate() + $params.scopeDays);
to.setHours(23, 59, 59, 999);
Object.assign($params, {from, to});
}

View File

@ -0,0 +1 @@
Date range must have 'from' and 'to': El rango de fechas debe tener 'desde' y 'hasta'

View File

@ -244,7 +244,12 @@ class Controller extends Section {
const query = `tickets/${this.ticket.id}/transferSales`;
this.$http.post(query, params)
.then(res => this.$state.go('ticket.card.sale', {id: res.data.id}));
.then(res => {
if (res.data && res.data.id === this.ticket.id) {
this.$.transfer.hide();
this.$.model.refresh();
} else this.$state.go('ticket.card.sale', {id: res.data.id});
});
}
showEditPricePopover(event, sale) {